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 (!tx.isFieldPresent(sfCredentialIDs))
126 return tesSUCCESS;
127
128 auto const& credentials = tx.getFieldV256(sfCredentialIDs);
129 if (credentials.empty() || (credentials.size() > maxCredentialsArraySize))
130 {
131 JLOG(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(j.trace())
144 << "Malformed transaction: duplicates in credentials.";
145 return temMALFORMED;
146 }
147 }
148
149 return tesSUCCESS;
150}
151
152TER
154 STTx const& tx,
155 ReadView const& view,
156 AccountID const& src,
158{
159 if (!tx.isFieldPresent(sfCredentialIDs))
160 return tesSUCCESS;
161
162 auto const& credIDs(tx.getFieldV256(sfCredentialIDs));
163 for (auto const& h : credIDs)
164 {
165 auto const sleCred = view.read(keylet::credential(h));
166 if (!sleCred)
167 {
168 JLOG(j.trace()) << "Credential doesn't exist. Cred: " << h;
169 return tecBAD_CREDENTIALS;
170 }
171
172 if (sleCred->getAccountID(sfSubject) != src)
173 {
174 JLOG(j.trace())
175 << "Credential doesn't belong to the source account. Cred: "
176 << h;
177 return tecBAD_CREDENTIALS;
178 }
179
180 if (!(sleCred->getFlags() & lsfAccepted))
181 {
182 JLOG(j.trace()) << "Credential isn't accepted. Cred: " << h;
183 return tecBAD_CREDENTIALS;
184 }
185
186 // Expiration checks are in doApply
187 }
188
189 return tesSUCCESS;
190}
191
192TER
193validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
194{
195 // Note, permissioned domain objects can be deleted at any time
196 auto const slePD = view.read(keylet::permissionedDomain(domainID));
197 if (!slePD)
198 return tecOBJECT_NOT_FOUND;
199
200 auto const closeTime = view.info().parentCloseTime;
201 bool foundExpired = false;
202 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
203 {
204 auto const issuer = h.getAccountID(sfIssuer);
205 auto const type = h.getFieldVL(sfCredentialType);
206 auto const keyletCredential =
207 keylet::credential(subject, issuer, makeSlice(type));
208 auto const sleCredential = view.read(keyletCredential);
209
210 // We cannot delete expired credentials, that would require ApplyView&
211 // However we can check if credentials are expired. Expected transaction
212 // flow is to use `validDomain` in preclaim, converting tecEXPIRED to
213 // tesSUCCESS, then proceed to call `verifyValidDomain` in doApply. This
214 // allows expired credentials to be deleted by any transaction.
215 if (sleCredential)
216 {
217 if (checkExpired(sleCredential, closeTime))
218 {
219 foundExpired = true;
220 continue;
221 }
222 else if (sleCredential->getFlags() & lsfAccepted)
223 return tesSUCCESS;
224 else
225 continue;
226 }
227 }
228
229 return foundExpired ? tecEXPIRED : tecNO_AUTH;
230}
231
232TER
234 ApplyView const& view,
235 STVector256 const& credIDs,
236 AccountID const& dst)
237{
240 lifeExtender.reserve(credIDs.size());
241 for (auto const& h : credIDs)
242 {
243 auto sleCred = view.read(keylet::credential(h));
244 if (!sleCred) // already checked in preclaim
245 return tefINTERNAL; // LCOV_EXCL_LINE
246
247 auto [it, ins] =
248 sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]);
249 if (!ins)
250 return tefINTERNAL; // LCOV_EXCL_LINE
251 lifeExtender.push_back(std::move(sleCred));
252 }
253
254 if (!view.exists(keylet::depositPreauth(dst, sorted)))
255 return tecNO_PERMISSION;
256
257 return tesSUCCESS;
258}
259
261makeSorted(STArray const& credentials)
262{
264 for (auto const& cred : credentials)
265 {
266 auto [it, ins] = out.emplace(cred[sfIssuer], cred[sfCredentialType]);
267 if (!ins)
268 return {};
269 }
270 return out;
271}
272
273NotTEC
274checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j)
275{
276 if (credentials.empty() || (credentials.size() > maxSize))
277 {
278 JLOG(j.trace()) << "Malformed transaction: "
279 "Invalid credentials size: "
280 << credentials.size();
281 return credentials.empty() ? temARRAY_EMPTY : temARRAY_TOO_LARGE;
282 }
283
285 for (auto const& credential : credentials)
286 {
287 auto const& issuer = credential[sfIssuer];
288 if (!issuer)
289 {
290 JLOG(j.trace()) << "Malformed transaction: "
291 "Issuer account is invalid: "
292 << to_string(issuer);
294 }
295
296 auto const ct = credential[sfCredentialType];
297 if (ct.empty() || (ct.size() > maxCredentialTypeLength))
298 {
299 JLOG(j.trace()) << "Malformed transaction: "
300 "Invalid credentialType size: "
301 << ct.size();
302 return temMALFORMED;
303 }
304
305 auto [it, ins] = duplicates.insert(sha512Half(issuer, ct));
306 if (!ins)
307 {
308 JLOG(j.trace()) << "Malformed transaction: "
309 "duplicates in credenentials.";
310 return temMALFORMED;
311 }
312 }
313
314 return tesSUCCESS;
315}
316
317} // namespace credentials
318
319TER
321 ApplyView& view,
322 AccountID const& account,
323 uint256 domainID,
325{
326 auto const slePD = view.read(keylet::permissionedDomain(domainID));
327 if (!slePD)
328 return tecOBJECT_NOT_FOUND;
329
330 // Collect all matching credentials on a side, so we can remove expired ones
331 // We may finish the loop with this collection empty, it's fine.
332 STVector256 credentials;
333 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
334 {
335 auto const issuer = h.getAccountID(sfIssuer);
336 auto const type = h.getFieldVL(sfCredentialType);
337 auto const keyletCredential =
338 keylet::credential(account, issuer, makeSlice(type));
339 if (view.exists(keyletCredential))
340 credentials.push_back(keyletCredential.key);
341 }
342
343 bool const foundExpired = credentials::removeExpired(view, credentials, j);
344 for (auto const& h : credentials)
345 {
346 auto sleCredential = view.read(keylet::credential(h));
347 if (!sleCredential)
348 continue; // expired, i.e. deleted in credentials::removeExpired
349
350 if (sleCredential->getFlags() & lsfAccepted)
351 return tesSUCCESS;
352 }
353
354 return foundExpired ? tecEXPIRED : tecNO_PERMISSION;
355}
356
357TER
359 STTx const& tx,
360 ApplyView& view,
361 AccountID const& src,
362 AccountID const& dst,
363 std::shared_ptr<SLE> const& sleDst,
365{
366 // If depositPreauth is enabled, then an account that requires
367 // authorization has at least two ways to get a payment in:
368 // 1. If src == dst, or
369 // 2. If src is deposit preauthorized by dst (either by account or by
370 // credentials).
371
372 bool const credentialsPresent = tx.isFieldPresent(sfCredentialIDs);
373
374 if (credentialsPresent &&
375 credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j))
376 return tecEXPIRED;
377
378 if (sleDst && (sleDst->getFlags() & lsfDepositAuth))
379 {
380 if (src != dst)
381 {
382 if (!view.exists(keylet::depositPreauth(dst, src)))
383 return !credentialsPresent
386 view, tx.getFieldV256(sfCredentialIDs), dst);
387 }
388 }
389
390 return tesSUCCESS;
391}
392
393} // 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
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.
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
void push_back(uint256 const &v)
T emplace(T... args)
T insert(T... args)
NotTEC checkFields(STTx const &tx, beast::Journal j)
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(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
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:570
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition Indexes.cpp:553
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:342
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst, beast::Journal j)
@ tefBAD_LEDGER
Definition TER.h:170
@ tefINTERNAL
Definition TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
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
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
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:645
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:224
@ 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
T time_since_epoch(T... args)