rippled
Loading...
Searching...
No Matches
Credentials.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/misc/CredentialHelpers.h>
21#include <xrpld/app/tx/detail/Credentials.h>
22
23#include <xrpld/core/TimeKeeper.h>
24#include <xrpld/ledger/ApplyView.h>
25#include <xrpld/ledger/View.h>
26#include <xrpl/basics/Log.h>
27#include <xrpl/protocol/Feature.h>
28#include <xrpl/protocol/Indexes.h>
29#include <xrpl/protocol/TxFlags.h>
30#include <xrpl/protocol/st.h>
31
32#include <chrono>
33
34namespace ripple {
35
36/*
37 Credentials
38 ======
39
40 A verifiable credentials (VC
41 https://en.wikipedia.org/wiki/Verifiable_credentials), as defined by the W3C
42 specification (https://www.w3.org/TR/vc-data-model-2.0/), is a
43 secure and tamper-evident way to represent information about a subject, such
44 as an individual, organization, or even an IoT device. These credentials are
45 issued by a trusted entity and can be verified by third parties without
46 directly involving the issuer at all.
47*/
48
49using namespace credentials;
50
51// ------- CREATE --------------------------
52
55{
56 if (!ctx.rules.enabled(featureCredentials))
57 {
58 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
59 return temDISABLED;
60 }
61
62 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
63 return ret;
64
65 auto const& tx = ctx.tx;
66 auto& j = ctx.j;
67
68 if (ctx.rules.enabled(fixInvalidTxFlags) &&
69 (tx.getFlags() & tfUniversalMask))
70 {
71 JLOG(ctx.j.debug()) << "CredentialCreate: invalid flags.";
72 return temINVALID_FLAG;
73 }
74
75 if (!tx[sfSubject])
76 {
77 JLOG(j.trace()) << "Malformed transaction: Invalid Subject";
78 return temMALFORMED;
79 }
80
81 auto const uri = tx[~sfURI];
82 if (uri && (uri->empty() || (uri->size() > maxCredentialURILength)))
83 {
84 JLOG(j.trace()) << "Malformed transaction: invalid size of URI.";
85 return temMALFORMED;
86 }
87
88 auto const credType = tx[sfCredentialType];
89 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
90 {
91 JLOG(j.trace())
92 << "Malformed transaction: invalid size of CredentialType.";
93 return temMALFORMED;
94 }
95
96 return preflight2(ctx);
97}
98
99TER
101{
102 auto const credType(ctx.tx[sfCredentialType]);
103 auto const subject = ctx.tx[sfSubject];
104
105 if (!ctx.view.exists(keylet::account(subject)))
106 {
107 JLOG(ctx.j.trace()) << "Subject doesn't exist.";
108 return tecNO_TARGET;
109 }
110
111 if (ctx.view.exists(
112 keylet::credential(subject, ctx.tx[sfAccount], credType)))
113 {
114 JLOG(ctx.j.trace()) << "Credential already exists.";
115 return tecDUPLICATE;
116 }
117
118 return tesSUCCESS;
119}
120
121TER
123{
124 auto const subject = ctx_.tx[sfSubject];
125 auto const credType(ctx_.tx[sfCredentialType]);
126 Keylet const credentialKey =
127 keylet::credential(subject, account_, credType);
128
129 auto const sleCred = std::make_shared<SLE>(credentialKey);
130 if (!sleCred)
131 return tefINTERNAL;
132
133 auto const optExp = ctx_.tx[~sfExpiration];
134 if (optExp)
135 {
136 std::uint32_t const closeTime =
138
139 if (closeTime > *optExp)
140 {
141 JLOG(j_.trace()) << "Malformed transaction: "
142 "Expiration time is in the past.";
143 return tecEXPIRED;
144 }
145
146 sleCred->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration));
147 }
148
149 auto const sleIssuer = view().peek(keylet::account(account_));
150 if (!sleIssuer)
151 return tefINTERNAL;
152
153 {
154 STAmount const reserve{view().fees().accountReserve(
155 sleIssuer->getFieldU32(sfOwnerCount) + 1)};
156 if (mPriorBalance < reserve)
158 }
159
160 sleCred->setAccountID(sfSubject, subject);
161 sleCred->setAccountID(sfIssuer, account_);
162 sleCred->setFieldVL(sfCredentialType, credType);
163
164 if (ctx_.tx.isFieldPresent(sfURI))
165 sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
166
167 {
168 auto const page = view().dirInsert(
170 credentialKey,
172 JLOG(j_.trace()) << "Adding Credential to owner directory "
173 << to_string(credentialKey.key) << ": "
174 << (page ? "success" : "failure");
175 if (!page)
176 return tecDIR_FULL;
177 sleCred->setFieldU64(sfIssuerNode, *page);
178
179 adjustOwnerCount(view(), sleIssuer, 1, j_);
180 }
181
182 if (subject == account_)
183 {
184 sleCred->setFieldU32(sfFlags, lsfAccepted);
185 }
186 else
187 {
188 auto const page = view().dirInsert(
189 keylet::ownerDir(subject),
190 credentialKey,
191 describeOwnerDir(subject));
192 JLOG(j_.trace()) << "Adding Credential to owner directory "
193 << to_string(credentialKey.key) << ": "
194 << (page ? "success" : "failure");
195 if (!page)
196 return tecDIR_FULL;
197 sleCred->setFieldU64(sfSubjectNode, *page);
198 view().update(view().peek(keylet::account(subject)));
199 }
200
201 view().insert(sleCred);
202
203 return tesSUCCESS;
204}
205
206// ------- DELETE --------------------------
207NotTEC
209{
210 if (!ctx.rules.enabled(featureCredentials))
211 {
212 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
213 return temDISABLED;
214 }
215
216 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
217 return ret;
218
219 if (ctx.rules.enabled(fixInvalidTxFlags) &&
220 (ctx.tx.getFlags() & tfUniversalMask))
221 {
222 JLOG(ctx.j.debug()) << "CredentialDelete: invalid flags.";
223 return temINVALID_FLAG;
224 }
225
226 auto const subject = ctx.tx[~sfSubject];
227 auto const issuer = ctx.tx[~sfIssuer];
228
229 if (!subject && !issuer)
230 {
231 // Neither field is present, the transaction is malformed.
232 JLOG(ctx.j.trace()) << "Malformed transaction: "
233 "No Subject or Issuer fields.";
234 return temMALFORMED;
235 }
236
237 // Make sure that the passed account is valid.
238 if ((subject && subject->isZero()) || (issuer && issuer->isZero()))
239 {
240 JLOG(ctx.j.trace()) << "Malformed transaction: Subject or Issuer "
241 "field zeroed.";
243 }
244
245 auto const credType = ctx.tx[sfCredentialType];
246 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
247 {
248 JLOG(ctx.j.trace())
249 << "Malformed transaction: invalid size of CredentialType.";
250 return temMALFORMED;
251 }
252
253 return preflight2(ctx);
254}
255
256TER
258{
259 AccountID const account{ctx.tx[sfAccount]};
260 auto const subject = ctx.tx[~sfSubject].value_or(account);
261 auto const issuer = ctx.tx[~sfIssuer].value_or(account);
262 auto const credType(ctx.tx[sfCredentialType]);
263
264 if (!ctx.view.exists(keylet::credential(subject, issuer, credType)))
265 return tecNO_ENTRY;
266
267 return tesSUCCESS;
268}
269
270TER
272{
273 auto const subject = ctx_.tx[~sfSubject].value_or(account_);
274 auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
275
276 auto const credType(ctx_.tx[sfCredentialType]);
277 auto const sleCred =
278 view().peek(keylet::credential(subject, issuer, credType));
279 if (!sleCred)
280 return tefINTERNAL;
281
282 if ((subject != account_) && (issuer != account_) &&
284 {
285 JLOG(j_.trace()) << "Can't delete non-expired credential.";
286 return tecNO_PERMISSION;
287 }
288
289 return deleteSLE(view(), sleCred, j_);
290}
291
292// ------- APPLY --------------------------
293
294NotTEC
296{
297 if (!ctx.rules.enabled(featureCredentials))
298 {
299 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
300 return temDISABLED;
301 }
302
303 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
304 return ret;
305
306 if (ctx.rules.enabled(fixInvalidTxFlags) &&
307 (ctx.tx.getFlags() & tfUniversalMask))
308 {
309 JLOG(ctx.j.debug()) << "CredentialAccept: invalid flags.";
310 return temINVALID_FLAG;
311 }
312
313 if (!ctx.tx[sfIssuer])
314 {
315 JLOG(ctx.j.trace()) << "Malformed transaction: Issuer field zeroed.";
317 }
318
319 auto const credType = ctx.tx[sfCredentialType];
320 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
321 {
322 JLOG(ctx.j.trace())
323 << "Malformed transaction: invalid size of CredentialType.";
324 return temMALFORMED;
325 }
326
327 return preflight2(ctx);
328}
329
330TER
332{
333 AccountID const subject = ctx.tx[sfAccount];
334 AccountID const issuer = ctx.tx[sfIssuer];
335 auto const credType(ctx.tx[sfCredentialType]);
336
337 if (!ctx.view.exists(keylet::account(issuer)))
338 {
339 JLOG(ctx.j.warn()) << "No issuer: " << to_string(issuer);
340 return tecNO_ISSUER;
341 }
342
343 auto const sleCred =
344 ctx.view.read(keylet::credential(subject, issuer, credType));
345 if (!sleCred)
346 {
347 JLOG(ctx.j.warn()) << "No credential: " << to_string(subject) << ", "
348 << to_string(issuer) << ", " << credType;
349 return tecNO_ENTRY;
350 }
351
352 if (sleCred->getFieldU32(sfFlags) & lsfAccepted)
353 {
354 JLOG(ctx.j.warn()) << "Credential already accepted: "
355 << to_string(subject) << ", " << to_string(issuer)
356 << ", " << credType;
357 return tecDUPLICATE;
358 }
359
360 return tesSUCCESS;
361}
362
363TER
365{
366 AccountID const issuer{ctx_.tx[sfIssuer]};
367
368 // Both exist as credential object exist itself (checked in preclaim)
369 auto const sleSubject = view().peek(keylet::account(account_));
370 auto const sleIssuer = view().peek(keylet::account(issuer));
371
372 if (!sleSubject || !sleIssuer)
373 return tefINTERNAL;
374
375 {
376 STAmount const reserve{view().fees().accountReserve(
377 sleSubject->getFieldU32(sfOwnerCount) + 1)};
378 if (mPriorBalance < reserve)
380 }
381
382 auto const credType(ctx_.tx[sfCredentialType]);
383 Keylet const credentialKey = keylet::credential(account_, issuer, credType);
384 auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
385
386 if (checkExpired(sleCred, view().info().parentCloseTime))
387 {
388 JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText();
389 // delete expired credentials even if the transaction failed
390 auto const err = credentials::deleteSLE(view(), sleCred, j_);
391 return isTesSuccess(err) ? tecEXPIRED : err;
392 }
393
394 sleCred->setFieldU32(sfFlags, lsfAccepted);
395 view().update(sleCred);
396
397 adjustOwnerCount(view(), sleIssuer, -1, j_);
398 adjustOwnerCount(view(), sleSubject, 1, j_);
399
400 return tesSUCCESS;
401}
402
403} // namespace ripple
Stream debug() const
Definition: Journal.h:317
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
Stream warn() const
Definition: Journal.h:329
ApplyView & view()
Definition: ApplyContext.h:54
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition: ApplyView.h:314
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
Definition: Credentials.cpp:54
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual LedgerInfo const & info() const =0
Returns information about the ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:122
Blob getFieldVL(SField const &field) const
Definition: STObject.cpp:627
std::uint32_t getFieldU32(SField const &field) const
Definition: STObject.cpp:585
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
std::uint32_t getFlags() const
Definition: STObject.cpp:507
AccountID const account_
Definition: Transactor.h:91
ApplyView & view()
Definition: Transactor.h:107
beast::Journal const j_
Definition: Transactor.h:89
XRPAmount mPriorBalance
Definition: Transactor.h:92
ApplyContext & ctx_
Definition: Transactor.h:88
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
bool checkExpired(std::shared_ptr< SLE const > const &sleCredential, NetClock::time_point const &closed)
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition: Indexes.cpp:521
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:350
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::size_t constexpr maxCredentialURILength
The maximum length of a URI inside a Credential.
Definition: Protocol.h:100
bool isTesSuccess(TER x)
Definition: TER.h:656
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:925
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:82
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:134
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition: Protocol.h:103
@ tecNO_ENTRY
Definition: TER.h:293
@ tecNO_ISSUER
Definition: TER.h:286
@ tecNO_TARGET
Definition: TER.h:291
@ tecDIR_FULL
Definition: TER.h:274
@ tecDUPLICATE
Definition: TER.h:302
@ tecNO_PERMISSION
Definition: TER.h:292
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ tecEXPIRED
Definition: TER.h:301
@ tesSUCCESS
Definition: TER.h:242
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:62
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
@ temMALFORMED
Definition: TER.h:87
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
@ temINVALID_ACCOUNT_ID
Definition: TER.h:119
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
Definition: protocol/Fees.h:49
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
uint256 key
Definition: Keylet.h:40
NetClock::time_point parentCloseTime
Definition: LedgerHeader.h:42
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:53
ReadView const & view
Definition: Transactor.h:56
beast::Journal const j
Definition: Transactor.h:60
State information when preflighting a tx.
Definition: Transactor.h:32
beast::Journal const j
Definition: Transactor.h:38
T time_since_epoch(T... args)