rippled
Loading...
Searching...
No Matches
CredentialHelpers.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/ledger/View.h>
22
23#include <xrpl/protocol/TER.h>
24#include <xrpl/protocol/digest.h>
25
26#include <unordered_set>
27
28namespace ripple {
29namespace credentials {
30
31bool
33 std::shared_ptr<SLE const> const& sleCredential,
34 NetClock::time_point const& closed)
35{
36 std::uint32_t const exp = (*sleCredential)[~sfExpiration].value_or(
38 std::uint32_t const now = closed.time_since_epoch().count();
39 return now > exp;
40}
41
42bool
44{
45 auto const closeTime = view.info().parentCloseTime;
46 bool foundExpired = false;
47
48 for (auto const& h : arr)
49 {
50 // Credentials already checked in preclaim. Look only for expired here.
51 auto const k = keylet::credential(h);
52 auto const sleCred = view.peek(k);
53
54 if (sleCred && checkExpired(sleCred, closeTime))
55 {
56 JLOG(j.trace())
57 << "Credentials are expired. Cred: " << sleCred->getText();
58 // delete expired credentials even if the transaction failed
59 deleteSLE(view, sleCred, j);
60 foundExpired = true;
61 }
62 }
63
64 return foundExpired;
65}
66
67TER
69 ApplyView& view,
70 std::shared_ptr<SLE> const& sleCredential,
72{
73 if (!sleCredential)
74 return tecNO_ENTRY;
75
76 auto delSLE =
77 [&view, &sleCredential, j](
78 AccountID const& account, SField const& node, bool isOwner) -> TER {
79 auto const sleAccount = view.peek(keylet::account(account));
80 if (!sleAccount)
81 { // LCOV_EXCL_START
82 JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
83 return tecINTERNAL;
84 } // LCOV_EXCL_STOP
85
86 // Remove object from owner directory
87 std::uint64_t const page = sleCredential->getFieldU64(node);
88 if (!view.dirRemove(
89 keylet::ownerDir(account), page, sleCredential->key(), false))
90 { // LCOV_EXCL_START
91 JLOG(j.fatal()) << "Unable to delete Credential from owner.";
92 return tefBAD_LEDGER;
93 } // LCOV_EXCL_STOP
94
95 if (isOwner)
96 adjustOwnerCount(view, sleAccount, -1, j);
97
98 return tesSUCCESS;
99 };
100
101 auto const issuer = sleCredential->getAccountID(sfIssuer);
102 auto const subject = sleCredential->getAccountID(sfSubject);
103 bool const accepted = sleCredential->getFlags() & lsfAccepted;
104
105 auto err = delSLE(issuer, sfIssuerNode, !accepted || (subject == issuer));
106 if (!isTesSuccess(err))
107 return err;
108
109 if (subject != issuer)
110 {
111 err = delSLE(subject, sfSubjectNode, accepted);
112 if (!isTesSuccess(err))
113 return err;
114 }
115
116 // Remove object from ledger
117 view.erase(sleCredential);
118
119 return tesSUCCESS;
120}
121
122NotTEC
124{
125 if (!ctx.tx.isFieldPresent(sfCredentialIDs))
126 return tesSUCCESS;
127
128 auto const& credentials = ctx.tx.getFieldV256(sfCredentialIDs);
129 if (credentials.empty() || (credentials.size() > maxCredentialsArraySize))
130 {
131 JLOG(ctx.j.trace())
132 << "Malformed transaction: Credentials array size is invalid: "
133 << credentials.size();
134 return temMALFORMED;
135 }
136
138 for (auto const& cred : credentials)
139 {
140 auto [it, ins] = duplicates.insert(cred);
141 if (!ins)
142 {
143 JLOG(ctx.j.trace())
144 << "Malformed transaction: duplicates in credentials.";
145 return temMALFORMED;
146 }
147 }
148
149 return tesSUCCESS;
150}
151
152TER
153valid(PreclaimContext const& ctx, AccountID const& src)
154{
155 if (!ctx.tx.isFieldPresent(sfCredentialIDs))
156 return tesSUCCESS;
157
158 auto const& credIDs(ctx.tx.getFieldV256(sfCredentialIDs));
159 for (auto const& h : credIDs)
160 {
161 auto const sleCred = ctx.view.read(keylet::credential(h));
162 if (!sleCred)
163 {
164 JLOG(ctx.j.trace()) << "Credential doesn't exist. Cred: " << h;
165 return tecBAD_CREDENTIALS;
166 }
167
168 if (sleCred->getAccountID(sfSubject) != src)
169 {
170 JLOG(ctx.j.trace())
171 << "Credential doesn't belong to the source account. Cred: "
172 << h;
173 return tecBAD_CREDENTIALS;
174 }
175
176 if (!(sleCred->getFlags() & lsfAccepted))
177 {
178 JLOG(ctx.j.trace()) << "Credential isn't accepted. Cred: " << h;
179 return tecBAD_CREDENTIALS;
180 }
181
182 // Expiration checks are in doApply
183 }
184
185 return tesSUCCESS;
186}
187
188TER
189validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
190{
191 // Note, permissioned domain objects can be deleted at any time
192 auto const slePD = view.read(keylet::permissionedDomain(domainID));
193 if (!slePD)
194 return tecOBJECT_NOT_FOUND;
195
196 auto const closeTime = view.info().parentCloseTime;
197 bool foundExpired = false;
198 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
199 {
200 auto const issuer = h.getAccountID(sfIssuer);
201 auto const type = h.getFieldVL(sfCredentialType);
202 auto const keyletCredential =
203 keylet::credential(subject, issuer, makeSlice(type));
204 auto const sleCredential = view.read(keyletCredential);
205
206 // We cannot delete expired credentials, that would require ApplyView&
207 // However we can check if credentials are expired. Expected transaction
208 // flow is to use `validDomain` in preclaim, converting tecEXPIRED to
209 // tesSUCCESS, then proceed to call `verifyValidDomain` in doApply. This
210 // allows expired credentials to be deleted by any transaction.
211 if (sleCredential)
212 {
213 if (checkExpired(sleCredential, closeTime))
214 {
215 foundExpired = true;
216 continue;
217 }
218 else if (sleCredential->getFlags() & lsfAccepted)
219 return tesSUCCESS;
220 else
221 continue;
222 }
223 }
224
225 return foundExpired ? tecEXPIRED : tecNO_AUTH;
226}
227
228TER
230 ApplyView const& view,
231 STVector256 const& credIDs,
232 AccountID const& dst)
233{
236 lifeExtender.reserve(credIDs.size());
237 for (auto const& h : credIDs)
238 {
239 auto sleCred = view.read(keylet::credential(h));
240 if (!sleCred) // already checked in preclaim
241 return tefINTERNAL; // LCOV_EXCL_LINE
242
243 auto [it, ins] =
244 sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]);
245 if (!ins)
246 return tefINTERNAL; // LCOV_EXCL_LINE
247 lifeExtender.push_back(std::move(sleCred));
248 }
249
250 if (!view.exists(keylet::depositPreauth(dst, sorted)))
251 return tecNO_PERMISSION;
252
253 return tesSUCCESS;
254}
255
257makeSorted(STArray const& credentials)
258{
260 for (auto const& cred : credentials)
261 {
262 auto [it, ins] = out.emplace(cred[sfIssuer], cred[sfCredentialType]);
263 if (!ins)
264 return {};
265 }
266 return out;
267}
268
269NotTEC
270checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j)
271{
272 if (credentials.empty() || (credentials.size() > maxSize))
273 {
274 JLOG(j.trace()) << "Malformed transaction: "
275 "Invalid credentials size: "
276 << credentials.size();
277 return credentials.empty() ? temARRAY_EMPTY : temARRAY_TOO_LARGE;
278 }
279
281 for (auto const& credential : credentials)
282 {
283 auto const& issuer = credential[sfIssuer];
284 if (!issuer)
285 {
286 JLOG(j.trace()) << "Malformed transaction: "
287 "Issuer account is invalid: "
288 << to_string(issuer);
290 }
291
292 auto const ct = credential[sfCredentialType];
293 if (ct.empty() || (ct.size() > maxCredentialTypeLength))
294 {
295 JLOG(j.trace()) << "Malformed transaction: "
296 "Invalid credentialType size: "
297 << ct.size();
298 return temMALFORMED;
299 }
300
301 auto [it, ins] = duplicates.insert(sha512Half(issuer, ct));
302 if (!ins)
303 {
304 JLOG(j.trace()) << "Malformed transaction: "
305 "duplicates in credenentials.";
306 return temMALFORMED;
307 }
308 }
309
310 return tesSUCCESS;
311}
312
313} // namespace credentials
314
315TER
317 ApplyView& view,
318 AccountID const& account,
319 uint256 domainID,
321{
322 auto const slePD = view.read(keylet::permissionedDomain(domainID));
323 if (!slePD)
324 return tecOBJECT_NOT_FOUND;
325
326 // Collect all matching credentials on a side, so we can remove expired ones
327 // We may finish the loop with this collection empty, it's fine.
328 STVector256 credentials;
329 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
330 {
331 auto const issuer = h.getAccountID(sfIssuer);
332 auto const type = h.getFieldVL(sfCredentialType);
333 auto const keyletCredential =
334 keylet::credential(account, issuer, makeSlice(type));
335 if (view.exists(keyletCredential))
336 credentials.push_back(keyletCredential.key);
337 }
338
339 // Result intentionally ignored.
340 [[maybe_unused]] bool _ = credentials::removeExpired(view, credentials, j);
341
342 for (auto const& h : credentials)
343 {
344 auto sleCredential = view.read(keylet::credential(h));
345 if (!sleCredential)
346 continue; // expired, i.e. deleted in credentials::removeExpired
347
348 if (sleCredential->getFlags() & lsfAccepted)
349 return tesSUCCESS;
350 }
351
352 return tecNO_PERMISSION;
353}
354
355TER
357 ApplyContext& ctx,
358 AccountID const& src,
359 AccountID const& dst,
360 std::shared_ptr<SLE> const& sleDst)
361{
362 // If depositPreauth is enabled, then an account that requires
363 // authorization has at least two ways to get a payment in:
364 // 1. If src == dst, or
365 // 2. If src is deposit preauthorized by dst (either by account or by
366 // credentials).
367
368 bool const credentialsPresent = ctx.tx.isFieldPresent(sfCredentialIDs);
369
370 if (credentialsPresent &&
372 ctx.view(), ctx.tx.getFieldV256(sfCredentialIDs), ctx.journal))
373 return tecEXPIRED;
374
375 if (sleDst && (sleDst->getFlags() & lsfDepositAuth))
376 {
377 if (src != dst)
378 {
379 if (!ctx.view().exists(keylet::depositPreauth(dst, src)))
380 return !credentialsPresent
383 ctx.view(),
384 ctx.tx.getFieldV256(sfCredentialIDs),
385 dst);
386 }
387 }
388
389 return tesSUCCESS;
390}
391
392} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream fatal() const
Definition: Journal.h:352
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
State information when applying a tx.
Definition: ApplyContext.h:37
ApplyView & view()
Definition: ApplyContext.h:78
beast::Journal const journal
Definition: ApplyContext.h:75
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:144
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
Definition: ApplyView.cpp:190
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
A view into a ledger.
Definition: ReadView.h:52
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
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.
Identifies fields.
Definition: SField.h:143
bool empty() const
Definition: STArray.h:254
size_type size() const
Definition: STArray.h:248
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
STVector256 const & getFieldV256(SField const &field) const
Definition: STObject.cpp:679
std::size_t size() const
Definition: STVector256.h:168
void push_back(uint256 const &v)
Definition: STVector256.h:218
T emplace(T... args)
T insert(T... args)
NotTEC checkFields(PreflightContext const &ctx)
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
bool removeExpired(ApplyView &view, STVector256 const &arr, beast::Journal const j)
TER validDomain(ReadView const &view, uint256 domainID, AccountID const &subject)
TER valid(PreclaimContext const &ctx, AccountID const &src)
TER authorizedDepositPreauth(ApplyView const &view, STVector256 const &credIDs, AccountID const &dst)
NotTEC checkArray(STArray const &credentials, unsigned maxSize, beast::Journal j)
bool checkExpired(std::shared_ptr< SLE const > const &sleCredential, NetClock::time_point const &closed)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition: Indexes.cpp:563
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition: Indexes.cpp:546
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:177
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:367
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:335
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
@ lsfDepositAuth
@ tefBAD_LEDGER
Definition: TER.h:170
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition: Protocol.h:107
@ accepted
Manifest is valid.
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition: Protocol.h:104
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:244
@ tecNO_ENTRY
Definition: TER.h:306
@ tecOBJECT_NOT_FOUND
Definition: TER.h:326
@ tecINTERNAL
Definition: TER.h:310
@ tecBAD_CREDENTIALS
Definition: TER.h:359
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecEXPIRED
Definition: TER.h:314
@ tecNO_AUTH
Definition: TER.h:300
@ tesSUCCESS
Definition: TER.h:244
TER verifyDepositPreauth(ApplyContext &ctx, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst)
bool isTesSuccess(TER x) noexcept
Definition: TER.h:672
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
@ credential
Credentials signature.
TERSubset< CanCvtToTER > TER
Definition: TER.h:643
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition: digest.h:225
@ temMALFORMED
Definition: TER.h:87
@ temARRAY_EMPTY
Definition: TER.h:140
@ temARRAY_TOO_LARGE
Definition: TER.h:141
@ temINVALID_ACCOUNT_ID
Definition: TER.h:119
T push_back(T... args)
T reserve(T... args)
NetClock::time_point parentCloseTime
Definition: LedgerHeader.h:42
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:79
ReadView const & view
Definition: Transactor.h:82
beast::Journal const j
Definition: Transactor.h:87
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:41
T time_since_epoch(T... args)