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#include <xrpld/ledger/ApplyView.h>
23#include <xrpld/ledger/View.h>
24
25#include <xrpl/basics/Log.h>
26#include <xrpl/protocol/Feature.h>
27#include <xrpl/protocol/Indexes.h>
28#include <xrpl/protocol/TxFlags.h>
29
30#include <chrono>
31
32namespace ripple {
33
34/*
35 Credentials
36 ======
37
38 A verifiable credentials (VC
39 https://en.wikipedia.org/wiki/Verifiable_credentials), as defined by the W3C
40 specification (https://www.w3.org/TR/vc-data-model-2.0/), is a
41 secure and tamper-evident way to represent information about a subject, such
42 as an individual, organization, or even an IoT device. These credentials are
43 issued by a trusted entity and can be verified by third parties without
44 directly involving the issuer at all.
45*/
46
47using namespace credentials;
48
49// ------- CREATE --------------------------
50
53{
54 if (!ctx.rules.enabled(featureCredentials))
55 {
56 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
57 return temDISABLED;
58 }
59
60 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
61 return ret;
62
63 auto const& tx = ctx.tx;
64 auto& j = ctx.j;
65
66 if (ctx.rules.enabled(fixInvalidTxFlags) &&
67 (tx.getFlags() & tfUniversalMask))
68 {
69 JLOG(ctx.j.debug()) << "CredentialCreate: invalid flags.";
70 return temINVALID_FLAG;
71 }
72
73 if (!tx[sfSubject])
74 {
75 JLOG(j.trace()) << "Malformed transaction: Invalid Subject";
76 return temMALFORMED;
77 }
78
79 auto const uri = tx[~sfURI];
80 if (uri && (uri->empty() || (uri->size() > maxCredentialURILength)))
81 {
82 JLOG(j.trace()) << "Malformed transaction: invalid size of URI.";
83 return temMALFORMED;
84 }
85
86 auto const credType = tx[sfCredentialType];
87 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
88 {
89 JLOG(j.trace())
90 << "Malformed transaction: invalid size of CredentialType.";
91 return temMALFORMED;
92 }
93
94 return preflight2(ctx);
95}
96
97TER
99{
100 auto const credType(ctx.tx[sfCredentialType]);
101 auto const subject = ctx.tx[sfSubject];
102
103 if (!ctx.view.exists(keylet::account(subject)))
104 {
105 JLOG(ctx.j.trace()) << "Subject doesn't exist.";
106 return tecNO_TARGET;
107 }
108
109 if (ctx.view.exists(
110 keylet::credential(subject, ctx.tx[sfAccount], credType)))
111 {
112 JLOG(ctx.j.trace()) << "Credential already exists.";
113 return tecDUPLICATE;
114 }
115
116 return tesSUCCESS;
117}
118
119TER
121{
122 auto const subject = ctx_.tx[sfSubject];
123 auto const credType(ctx_.tx[sfCredentialType]);
124 Keylet const credentialKey =
125 keylet::credential(subject, account_, credType);
126
127 auto const sleCred = std::make_shared<SLE>(credentialKey);
128 if (!sleCred)
129 return tefINTERNAL;
130
131 auto const optExp = ctx_.tx[~sfExpiration];
132 if (optExp)
133 {
134 std::uint32_t const closeTime =
136
137 if (closeTime > *optExp)
138 {
139 JLOG(j_.trace()) << "Malformed transaction: "
140 "Expiration time is in the past.";
141 return tecEXPIRED;
142 }
143
144 sleCred->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration));
145 }
146
147 auto const sleIssuer = view().peek(keylet::account(account_));
148 if (!sleIssuer)
149 return tefINTERNAL;
150
151 {
152 STAmount const reserve{view().fees().accountReserve(
153 sleIssuer->getFieldU32(sfOwnerCount) + 1)};
154 if (mPriorBalance < reserve)
156 }
157
158 sleCred->setAccountID(sfSubject, subject);
159 sleCred->setAccountID(sfIssuer, account_);
160 sleCred->setFieldVL(sfCredentialType, credType);
161
162 if (ctx_.tx.isFieldPresent(sfURI))
163 sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
164
165 {
166 auto const page = view().dirInsert(
168 credentialKey,
170 JLOG(j_.trace()) << "Adding Credential to owner directory "
171 << to_string(credentialKey.key) << ": "
172 << (page ? "success" : "failure");
173 if (!page)
174 return tecDIR_FULL;
175 sleCred->setFieldU64(sfIssuerNode, *page);
176
177 adjustOwnerCount(view(), sleIssuer, 1, j_);
178 }
179
180 if (subject == account_)
181 {
182 sleCred->setFieldU32(sfFlags, lsfAccepted);
183 }
184 else
185 {
186 auto const page = view().dirInsert(
187 keylet::ownerDir(subject),
188 credentialKey,
189 describeOwnerDir(subject));
190 JLOG(j_.trace()) << "Adding Credential to owner directory "
191 << to_string(credentialKey.key) << ": "
192 << (page ? "success" : "failure");
193 if (!page)
194 return tecDIR_FULL;
195 sleCred->setFieldU64(sfSubjectNode, *page);
196 view().update(view().peek(keylet::account(subject)));
197 }
198
199 view().insert(sleCred);
200
201 return tesSUCCESS;
202}
203
204// ------- DELETE --------------------------
205NotTEC
207{
208 if (!ctx.rules.enabled(featureCredentials))
209 {
210 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
211 return temDISABLED;
212 }
213
214 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
215 return ret;
216
217 if (ctx.rules.enabled(fixInvalidTxFlags) &&
218 (ctx.tx.getFlags() & tfUniversalMask))
219 {
220 JLOG(ctx.j.debug()) << "CredentialDelete: invalid flags.";
221 return temINVALID_FLAG;
222 }
223
224 auto const subject = ctx.tx[~sfSubject];
225 auto const issuer = ctx.tx[~sfIssuer];
226
227 if (!subject && !issuer)
228 {
229 // Neither field is present, the transaction is malformed.
230 JLOG(ctx.j.trace()) << "Malformed transaction: "
231 "No Subject or Issuer fields.";
232 return temMALFORMED;
233 }
234
235 // Make sure that the passed account is valid.
236 if ((subject && subject->isZero()) || (issuer && issuer->isZero()))
237 {
238 JLOG(ctx.j.trace()) << "Malformed transaction: Subject or Issuer "
239 "field zeroed.";
241 }
242
243 auto const credType = ctx.tx[sfCredentialType];
244 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
245 {
246 JLOG(ctx.j.trace())
247 << "Malformed transaction: invalid size of CredentialType.";
248 return temMALFORMED;
249 }
250
251 return preflight2(ctx);
252}
253
254TER
256{
257 AccountID const account{ctx.tx[sfAccount]};
258 auto const subject = ctx.tx[~sfSubject].value_or(account);
259 auto const issuer = ctx.tx[~sfIssuer].value_or(account);
260 auto const credType(ctx.tx[sfCredentialType]);
261
262 if (!ctx.view.exists(keylet::credential(subject, issuer, credType)))
263 return tecNO_ENTRY;
264
265 return tesSUCCESS;
266}
267
268TER
270{
271 auto const subject = ctx_.tx[~sfSubject].value_or(account_);
272 auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
273
274 auto const credType(ctx_.tx[sfCredentialType]);
275 auto const sleCred =
276 view().peek(keylet::credential(subject, issuer, credType));
277 if (!sleCred)
278 return tefINTERNAL;
279
280 if ((subject != account_) && (issuer != account_) &&
282 {
283 JLOG(j_.trace()) << "Can't delete non-expired credential.";
284 return tecNO_PERMISSION;
285 }
286
287 return deleteSLE(view(), sleCred, j_);
288}
289
290// ------- APPLY --------------------------
291
292NotTEC
294{
295 if (!ctx.rules.enabled(featureCredentials))
296 {
297 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
298 return temDISABLED;
299 }
300
301 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
302 return ret;
303
304 if (ctx.rules.enabled(fixInvalidTxFlags) &&
305 (ctx.tx.getFlags() & tfUniversalMask))
306 {
307 JLOG(ctx.j.debug()) << "CredentialAccept: invalid flags.";
308 return temINVALID_FLAG;
309 }
310
311 if (!ctx.tx[sfIssuer])
312 {
313 JLOG(ctx.j.trace()) << "Malformed transaction: Issuer field zeroed.";
315 }
316
317 auto const credType = ctx.tx[sfCredentialType];
318 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
319 {
320 JLOG(ctx.j.trace())
321 << "Malformed transaction: invalid size of CredentialType.";
322 return temMALFORMED;
323 }
324
325 return preflight2(ctx);
326}
327
328TER
330{
331 AccountID const subject = ctx.tx[sfAccount];
332 AccountID const issuer = ctx.tx[sfIssuer];
333 auto const credType(ctx.tx[sfCredentialType]);
334
335 if (!ctx.view.exists(keylet::account(issuer)))
336 {
337 JLOG(ctx.j.warn()) << "No issuer: " << to_string(issuer);
338 return tecNO_ISSUER;
339 }
340
341 auto const sleCred =
342 ctx.view.read(keylet::credential(subject, issuer, credType));
343 if (!sleCred)
344 {
345 JLOG(ctx.j.warn()) << "No credential: " << to_string(subject) << ", "
346 << to_string(issuer) << ", " << credType;
347 return tecNO_ENTRY;
348 }
349
350 if (sleCred->getFieldU32(sfFlags) & lsfAccepted)
351 {
352 JLOG(ctx.j.warn()) << "Credential already accepted: "
353 << to_string(subject) << ", " << to_string(issuer)
354 << ", " << credType;
355 return tecDUPLICATE;
356 }
357
358 return tesSUCCESS;
359}
360
361TER
363{
364 AccountID const issuer{ctx_.tx[sfIssuer]};
365
366 // Both exist as credential object exist itself (checked in preclaim)
367 auto const sleSubject = view().peek(keylet::account(account_));
368 auto const sleIssuer = view().peek(keylet::account(issuer));
369
370 if (!sleSubject || !sleIssuer)
371 return tefINTERNAL;
372
373 {
374 STAmount const reserve{view().fees().accountReserve(
375 sleSubject->getFieldU32(sfOwnerCount) + 1)};
376 if (mPriorBalance < reserve)
378 }
379
380 auto const credType(ctx_.tx[sfCredentialType]);
381 Keylet const credentialKey = keylet::credential(account_, issuer, credType);
382 auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
383
384 if (checkExpired(sleCred, view().info().parentCloseTime))
385 {
386 JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText();
387 // delete expired credentials even if the transaction failed
388 auto const err = credentials::deleteSLE(view(), sleCred, j_);
389 return isTesSuccess(err) ? tecEXPIRED : err;
390 }
391
392 sleCred->setFieldU32(sfFlags, lsfAccepted);
393 view().update(sleCred);
394
395 adjustOwnerCount(view(), sleIssuer, -1, j_);
396 adjustOwnerCount(view(), sleSubject, 1, j_);
397
398 return tesSUCCESS;
399}
400
401} // namespace ripple
Stream debug() const
Definition: Journal.h:328
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
Stream warn() const
Definition: Journal.h:340
ApplyView & view()
Definition: ApplyContext.h:55
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:315
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)
Definition: Credentials.cpp:98
static NotTEC preflight(PreflightContext const &ctx)
Definition: Credentials.cpp:52
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:130
Blob getFieldVL(SField const &field) const
Definition: STObject.cpp:657
std::uint32_t getFieldU32(SField const &field) const
Definition: STObject.cpp:615
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
std::uint32_t getFlags() const
Definition: STObject.cpp:537
AccountID const account_
Definition: Transactor.h:93
ApplyView & view()
Definition: Transactor.h:109
beast::Journal const j_
Definition: Transactor.h:91
XRPAmount mPriorBalance
Definition: Transactor.h:94
ApplyContext & ctx_
Definition: Transactor.h:90
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:545
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:176
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:366
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:101
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:83
@ 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:144
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition: Protocol.h:104
@ 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:630
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:55
ReadView const & view
Definition: Transactor.h:58
beast::Journal const j
Definition: Transactor.h:62
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:40
T time_since_epoch(T... args)