xrpld
Loading...
Searching...
No Matches
CredentialHelpers.cpp
1#include <xrpl/ledger/helpers/CredentialHelpers.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Slice.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/basics/chrono.h>
7#include <xrpl/beast/utility/Journal.h>
8#include <xrpl/ledger/ApplyView.h>
9#include <xrpl/ledger/ReadView.h>
10#include <xrpl/ledger/helpers/AccountRootHelpers.h>
11#include <xrpl/protocol/AccountID.h>
12#include <xrpl/protocol/Feature.h>
13#include <xrpl/protocol/Indexes.h>
14#include <xrpl/protocol/LedgerFormats.h>
15#include <xrpl/protocol/Protocol.h>
16#include <xrpl/protocol/SField.h>
17#include <xrpl/protocol/STArray.h>
18#include <xrpl/protocol/STLedgerEntry.h>
19#include <xrpl/protocol/STObject.h>
20#include <xrpl/protocol/STTx.h>
21#include <xrpl/protocol/STVector256.h>
22#include <xrpl/protocol/TER.h>
23#include <xrpl/protocol/digest.h>
24
25#include <algorithm>
26#include <cstdint>
27#include <expected>
28#include <limits>
29#include <set>
30#include <unordered_set>
31#include <utility>
32#include <vector>
33
34namespace xrpl {
35namespace credentials {
36
37bool
38checkExpired(SLE const& sleCredential, NetClock::time_point const& closed)
39{
40 std::uint32_t const exp =
41 sleCredential[~sfExpiration].value_or(std::numeric_limits<std::uint32_t>::max());
42 std::uint32_t const now = closed.time_since_epoch().count();
43 return now > exp;
44}
45
46[[nodiscard]]
47static std::expected<bool, TER>
49{
50 auto const closeTime = view.header().parentCloseTime;
51 bool foundExpired = false;
52
53 for (auto const& h : arr)
54 {
55 // Credentials already checked in preclaim. Look only for expired here.
56 if (view.rules().enabled(fixCleanup3_4_0) && h.isZero())
57 return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
58
59 auto const k = keylet::credential(h);
60 auto const sleCred = view.peek(k);
61
62 if (sleCred && checkExpired(*sleCred, closeTime))
63 {
64 JLOG(j.trace()) << "Credentials are expired. Cred: " << sleCred->getText();
65 // delete expired credentials even if the transaction failed
66 auto const err = deleteSLE(view, sleCred, j);
67 if (view.rules().enabled(fixCleanup3_1_3) && !isTesSuccess(err))
68 return std::unexpected(err);
69 foundExpired = true;
70 }
71 }
72
73 return foundExpired;
74}
75
76TER
78{
79 if (!sleCredential)
80 return tecNO_ENTRY;
81
82 auto delSLE = [&view, &sleCredential, j](
83 AccountID const& account, SField const& node, bool isOwner) -> TER {
84 auto const sleAccount = view.peek(keylet::account(account));
85 if (!sleAccount)
86 {
87 // LCOV_EXCL_START
88 JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
89 return tecINTERNAL;
90 // LCOV_EXCL_STOP
91 }
92
93 // Remove object from owner directory
94 std::uint64_t const page = sleCredential->getFieldU64(node);
95 if (!view.dirRemove(keylet::ownerDir(account), page, sleCredential->key(), false))
96 {
97 // LCOV_EXCL_START
98 JLOG(j.fatal()) << "Unable to delete Credential from owner.";
99 return tefBAD_LEDGER;
100 // LCOV_EXCL_STOP
101 }
102
103 if (isOwner)
104 decreaseOwnerCountForObject(view, sleAccount, sleCredential, 1, j);
105
106 return tesSUCCESS;
107 };
108
109 auto const issuer = sleCredential->getAccountID(sfIssuer);
110 auto const subject = sleCredential->getAccountID(sfSubject);
111 bool const accepted = sleCredential->isFlag(lsfAccepted);
112
113 auto err = delSLE(issuer, sfIssuerNode, !accepted || (subject == issuer));
114 if (!isTesSuccess(err))
115 return err;
116
117 if (subject != issuer)
118 {
119 err = delSLE(subject, sfSubjectNode, accepted);
120 if (!isTesSuccess(err))
121 return err;
122 }
123
124 // Remove object from ledger
125 view.erase(sleCredential);
126
127 return tesSUCCESS;
128}
129
130NotTEC
131checkFields(STTx const& tx, Rules const& rules, beast::Journal j)
132{
133 if (!tx.isFieldPresent(sfCredentialIDs))
134 return tesSUCCESS;
135
136 auto const& credentials = tx.getFieldV256(sfCredentialIDs);
137 if (credentials.empty() || (credentials.size() > kMaxCredentialsArraySize))
138 {
139 JLOG(j.trace()) << "Malformed transaction: Credentials array size is invalid: "
140 << credentials.size();
141 return temMALFORMED;
142 }
143
144 if (rules.enabled(fixCleanup3_4_0) &&
145 std::ranges::any_of(credentials, [](uint256 const& id) { return id.isZero(); }))
146 {
147 JLOG(j.trace()) << "Malformed transaction: zero credential ID.";
148 return temMALFORMED;
149 }
150
152 for (auto const& cred : credentials)
153 {
154 auto [it, ins] = duplicates.insert(cred);
155 if (!ins)
156 {
157 JLOG(j.trace()) << "Malformed transaction: duplicates in credentials.";
158 return temMALFORMED;
159 }
160 }
161
162 return tesSUCCESS;
163}
164
165TER
166valid(STTx const& tx, ReadView const& view, AccountID const& src, beast::Journal j)
167{
168 if (!tx.isFieldPresent(sfCredentialIDs))
169 return tesSUCCESS;
170
171 auto const& credIDs(tx.getFieldV256(sfCredentialIDs));
172 for (auto const& h : credIDs)
173 {
174 if (view.rules().enabled(fixCleanup3_4_0) && h.isZero())
175 {
176 // LCOV_EXCL_START
177 JLOG(j.trace()) << "Zero credential ID.";
178 return tecINTERNAL;
179 // LCOV_EXCL_STOP
180 }
181
182 auto const sleCred = view.read(keylet::credential(h));
183 if (!sleCred)
184 {
185 JLOG(j.trace()) << "Credential doesn't exist. Cred: " << h;
186 return tecBAD_CREDENTIALS;
187 }
188
189 if (sleCred->getAccountID(sfSubject) != src)
190 {
191 JLOG(j.trace()) << "Credential doesn't belong to the source account. Cred: " << h;
192 return tecBAD_CREDENTIALS;
193 }
194
195 if (!sleCred->isFlag(lsfAccepted))
196 {
197 JLOG(j.trace()) << "Credential isn't accepted. Cred: " << h;
198 return tecBAD_CREDENTIALS;
199 }
200
201 // Expiration checks are in doApply
202 }
203
204 return tesSUCCESS;
205}
206
207TER
208validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
209{
210 // Note, permissioned domain objects can be deleted at any time
211 auto const slePD = view.read(keylet::permissionedDomain(domainID));
212 if (!slePD)
213 return tecOBJECT_NOT_FOUND;
214
215 auto const closeTime = view.header().parentCloseTime;
216 bool foundExpired = false;
217 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
218 {
219 auto const issuer = h.getAccountID(sfIssuer);
220 auto const type = h.getFieldVL(sfCredentialType);
221 auto const keyletCredential = keylet::credential(subject, issuer, makeSlice(type));
222 auto const sleCredential = view.read(keyletCredential);
223
224 // We cannot delete expired credentials, that would require ApplyView&
225 // However we can check if credentials are expired. Expected transaction
226 // flow is to use `validDomain` in preclaim, converting tecEXPIRED to
227 // tesSUCCESS, then proceed to call `verifyValidDomain` in doApply. This
228 // allows expired credentials to be deleted by any transaction.
229 if (sleCredential)
230 {
231 if (checkExpired(*sleCredential, closeTime))
232 {
233 foundExpired = true;
234 continue;
235 }
236 if (sleCredential->isFlag(lsfAccepted))
237 {
238 return tesSUCCESS;
239 }
240
241 continue;
242 }
243 }
244
245 return foundExpired ? tecEXPIRED : tecNO_AUTH;
246}
247
248TER
249authorizedDepositPreauth(ReadView const& view, STVector256 const& credIDs, AccountID const& dst)
250{
253 lifeExtender.reserve(credIDs.size());
254 for (auto const& h : credIDs)
255 {
256 if (view.rules().enabled(fixCleanup3_4_0) && h.isZero())
257 return tefINTERNAL; // LCOV_EXCL_LINE
258
259 auto sleCred = view.read(keylet::credential(h));
260 if (!sleCred) // already checked in preclaim
261 return tefINTERNAL; // LCOV_EXCL_LINE
262
263 auto [it, ins] = sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]);
264 if (!ins)
265 return tefINTERNAL; // LCOV_EXCL_LINE
266 lifeExtender.push_back(std::move(sleCred));
267 }
268
269 if (!view.exists(keylet::depositPreauth(dst, sorted)))
270 return tecNO_PERMISSION;
271
272 return tesSUCCESS;
273}
274
277{
279 for (auto const& cred : credentials)
280 {
281 auto [it, ins] = out.emplace(cred[sfIssuer], cred[sfCredentialType]);
282 if (!ins)
283 return {};
284 }
285 return out;
286}
287
288NotTEC
289checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j)
290{
291 if (credentials.empty() || (credentials.size() > maxSize))
292 {
293 JLOG(j.trace()) << "Malformed transaction: "
294 "Invalid credentials size: "
295 << credentials.size();
297 }
298
300 for (auto const& credential : credentials)
301 {
302 auto const& issuer = credential[sfIssuer];
303 if (!issuer)
304 {
305 JLOG(j.trace()) << "Malformed transaction: "
306 "Issuer account is invalid: "
307 << to_string(issuer);
309 }
310
311 auto const ct = credential[sfCredentialType];
312 if (ct.empty() || (ct.size() > kMaxCredentialTypeLength))
313 {
314 JLOG(j.trace()) << "Malformed transaction: "
315 "Invalid credentialType size: "
316 << ct.size();
317 return temMALFORMED;
318 }
319
320 auto [it, ins] = duplicates.insert(sha512Half(issuer, ct));
321 if (!ins)
322 {
323 JLOG(j.trace()) << "Malformed transaction: "
324 "duplicates in credentials.";
325 return temMALFORMED;
326 }
327 }
328
329 return tesSUCCESS;
330}
331
332} // namespace credentials
333
334TER
335verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, beast::Journal j)
336{
337 auto const slePD = view.read(keylet::permissionedDomain(domainID));
338 if (!slePD)
339 return tecOBJECT_NOT_FOUND;
340
341 // Collect all matching credentials on a side, so we can remove expired ones
342 // We may finish the loop with this collection empty, it's fine.
344 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
345 {
346 auto const issuer = h.getAccountID(sfIssuer);
347 auto const type = h.getFieldVL(sfCredentialType);
348 auto const keyletCredential = keylet::credential(account, issuer, makeSlice(type));
349 if (view.exists(keyletCredential))
350 credentials.pushBack(keyletCredential.key);
351 }
352
353 auto const foundExpired = credentials::removeExpired(view, credentials, j);
354 if (!foundExpired.has_value())
355 return foundExpired.error();
356
357 for (auto const& h : credentials)
358 {
359 auto sleCredential = view.read(keylet::credential(h));
360 if (!sleCredential)
361 continue; // expired, i.e. deleted in credentials::removeExpired
362
363 if (sleCredential->isFlag(lsfAccepted))
364 return tesSUCCESS;
365 }
366
367 return *foundExpired ? tecEXPIRED : tecNO_PERMISSION;
368}
369
370TER
372 STTx const& tx,
373 ReadView const& view,
374 AccountID const& src,
375 AccountID const& dst,
376 SLE::const_ref sleDst,
378{
379 // If depositPreauth is enabled, then an account that requires
380 // authorization has at least two ways to get a payment in:
381 // 1. If src == dst, or
382 // 2. If src is deposit preauthorized by dst (either by account or by
383 // credentials).
384
385 if (sleDst && ((sleDst->getFlags() & lsfDepositAuth) != 0u))
386 {
387 if (src != dst)
388 {
389 if (!view.exists(keylet::depositPreauth(dst, src)))
390 {
391 return !tx.isFieldPresent(sfCredentialIDs)
394 view, tx.getFieldV256(sfCredentialIDs), dst);
395 }
396 }
397 }
398
399 return tesSUCCESS;
400}
401
402TER
404{
405 if (tx.isFieldPresent(sfCredentialIDs))
406 {
407 auto const foundExpired =
408 credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j);
409 if (!foundExpired.has_value())
410 return foundExpired.error();
411 if (*foundExpired)
412 return tecEXPIRED;
413 }
414
415 return tesSUCCESS;
416}
417
418TER
420 STTx const& tx,
421 ApplyView& view,
422 AccountID const& src,
423 AccountID const& dst,
424 SLE::const_ref sleDst,
426{
427 if (auto const err = cleanupExpiredCredentials(tx, view, j); !isTesSuccess(err))
428 return err;
429
430 return checkDepositPreauth(tx, view, src, dst, sleDst, j);
431}
432
433} // namespace xrpl
T any_of(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Stream fatal() const
Definition Journal.h:368
Stream trace() const
Severity stream access functions.
Definition Journal.h:338
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:134
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
virtual void erase(SLE::ref sle)=0
Remove a peeked SLE.
std::chrono::time_point< NetClock > time_point
Definition chrono.h:48
A view into a ledger.
Definition ReadView.h:41
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
Rules controlling protocol behavior.
Definition Rules.h:40
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:180
Identifies fields.
Definition SField.h:132
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
STVector256 const & getFieldV256(SField const &field) const
Definition STObject.cpp:671
std::size_t size() const
T emplace(T... args)
T insert(T... args)
T max(T... args)
TER validDomain(ReadView const &view, uint256 domainID, AccountID const &subject)
TER deleteSLE(ApplyView &view, SLE::ref sleCredential, beast::Journal j)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
NotTEC checkFields(STTx const &tx, Rules const &rules, beast::Journal j)
static std::expected< bool, TER > removeExpired(ApplyView &view, STVector256 const &arr, beast::Journal const j)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
NotTEC checkArray(STArray const &credentials, unsigned maxSize, beast::Journal j)
TER authorizedDepositPreauth(ReadView const &view, STVector256 const &ctx, AccountID const &dst)
bool checkExpired(SLE const &sleCredential, NetClock::time_point const &closed)
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:344
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:373
Keylet permissionedDomain(AccountID const &account, SeqProxy const &seq) noexcept
Definition Indexes.cpp:579
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition Indexes.cpp:555
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
void decreaseOwnerCountForObject(ApplyView &view, SLE::ref accountSle, SLE::ref objectSle, std::uint32_t count, beast::Journal j)
Decrease owner-count fields for an existing ledger object.
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:215
constexpr std::size_t kMaxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:275
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
@ tefBAD_LEDGER
Definition TER.h:162
@ tefINTERNAL
Definition TER.h:165
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
STLedgerEntry SLE
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:607
constexpr std::size_t kMaxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:280
TER cleanupExpiredCredentials(STTx const &tx, ApplyView &view, beast::Journal j)
Remove expired credentials referenced by the transaction.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
@ temARRAY_TOO_LARGE
Definition TER.h:129
@ temMALFORMED
Definition TER.h:75
@ temARRAY_EMPTY
Definition TER.h:128
@ temINVALID_ACCOUNT_ID
Definition TER.h:107
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
@ tecNO_ENTRY
Definition TER.h:309
@ tecOBJECT_NOT_FOUND
Definition TER.h:329
@ tecNO_AUTH
Definition TER.h:303
@ tecINTERNAL
Definition TER.h:313
@ tecBAD_CREDENTIALS
Definition TER.h:362
@ tecEXPIRED
Definition TER.h:317
@ tecNO_PERMISSION
Definition TER.h:308
BaseUInt< 256 > uint256
Definition base_uint.h:580
@ tesSUCCESS
Definition TER.h:245
TER checkDepositPreauth(STTx const &tx, ReadView const &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE const > const &sleDst, beast::Journal j)
Check whether src is authorized to deposit to dst.
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::const_ref sleDst, beast::Journal j)
T push_back(T... args)
T reserve(T... args)
NetClock::time_point parentCloseTime
T time_since_epoch(T... args)
T unexpected(T... args)