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 <xrpl/ledger/CredentialHelpers.h>
21#include <xrpl/ledger/View.h>
22#include <xrpl/protocol/TER.h>
23#include <xrpl/protocol/digest.h>
24
25#include <unordered_set>
26
27namespace ripple {
28namespace credentials {
29
30bool
32 std::shared_ptr<SLE const> const& sleCredential,
33 NetClock::time_point const& closed)
34{
35 std::uint32_t const exp = (*sleCredential)[~sfExpiration].value_or(
37 std::uint32_t const now = closed.time_since_epoch().count();
38 return now > exp;
39}
40
41bool
43{
44 auto const closeTime = view.info().parentCloseTime;
45 bool foundExpired = false;
46
47 for (auto const& h : arr)
48 {
49 // Credentials already checked in preclaim. Look only for expired here.
50 auto const k = keylet::credential(h);
51 auto const sleCred = view.peek(k);
52
53 if (sleCred && checkExpired(sleCred, closeTime))
54 {
55 JLOG(j.trace())
56 << "Credentials are expired. Cred: " << sleCred->getText();
57 // delete expired credentials even if the transaction failed
58 deleteSLE(view, sleCred, j);
59 foundExpired = true;
60 }
61 }
62
63 return foundExpired;
64}
65
66TER
68 ApplyView& view,
69 std::shared_ptr<SLE> const& sleCredential,
71{
72 if (!sleCredential)
73 return tecNO_ENTRY;
74
75 auto delSLE =
76 [&view, &sleCredential, j](
77 AccountID const& account, SField const& node, bool isOwner) -> TER {
78 auto const sleAccount = view.peek(keylet::account(account));
79 if (!sleAccount)
80 {
81 // LCOV_EXCL_START
82 JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
83 return tecINTERNAL;
84 // LCOV_EXCL_STOP
85 }
86
87 // Remove object from owner directory
88 std::uint64_t const page = sleCredential->getFieldU64(node);
89 if (!view.dirRemove(
90 keylet::ownerDir(account), page, sleCredential->key(), false))
91 {
92 // LCOV_EXCL_START
93 JLOG(j.fatal()) << "Unable to delete Credential from owner.";
94 return tefBAD_LEDGER;
95 // LCOV_EXCL_STOP
96 }
97
98 if (isOwner)
99 adjustOwnerCount(view, sleAccount, -1, j);
100
101 return tesSUCCESS;
102 };
103
104 auto const issuer = sleCredential->getAccountID(sfIssuer);
105 auto const subject = sleCredential->getAccountID(sfSubject);
106 bool const accepted = sleCredential->getFlags() & lsfAccepted;
107
108 auto err = delSLE(issuer, sfIssuerNode, !accepted || (subject == issuer));
109 if (!isTesSuccess(err))
110 return err;
111
112 if (subject != issuer)
113 {
114 err = delSLE(subject, sfSubjectNode, accepted);
115 if (!isTesSuccess(err))
116 return err;
117 }
118
119 // Remove object from ledger
120 view.erase(sleCredential);
121
122 return tesSUCCESS;
123}
124
125NotTEC
127{
128 if (!tx.isFieldPresent(sfCredentialIDs))
129 return tesSUCCESS;
130
131 auto const& credentials = tx.getFieldV256(sfCredentialIDs);
132 if (credentials.empty() || (credentials.size() > maxCredentialsArraySize))
133 {
134 JLOG(j.trace())
135 << "Malformed transaction: Credentials array size is invalid: "
136 << credentials.size();
137 return temMALFORMED;
138 }
139
141 for (auto const& cred : credentials)
142 {
143 auto [it, ins] = duplicates.insert(cred);
144 if (!ins)
145 {
146 JLOG(j.trace())
147 << "Malformed transaction: duplicates in credentials.";
148 return temMALFORMED;
149 }
150 }
151
152 return tesSUCCESS;
153}
154
155TER
157 STTx const& tx,
158 ReadView const& view,
159 AccountID const& src,
161{
162 if (!tx.isFieldPresent(sfCredentialIDs))
163 return tesSUCCESS;
164
165 auto const& credIDs(tx.getFieldV256(sfCredentialIDs));
166 for (auto const& h : credIDs)
167 {
168 auto const sleCred = view.read(keylet::credential(h));
169 if (!sleCred)
170 {
171 JLOG(j.trace()) << "Credential doesn't exist. Cred: " << h;
172 return tecBAD_CREDENTIALS;
173 }
174
175 if (sleCred->getAccountID(sfSubject) != src)
176 {
177 JLOG(j.trace())
178 << "Credential doesn't belong to the source account. Cred: "
179 << h;
180 return tecBAD_CREDENTIALS;
181 }
182
183 if (!(sleCred->getFlags() & lsfAccepted))
184 {
185 JLOG(j.trace()) << "Credential isn't accepted. Cred: " << h;
186 return tecBAD_CREDENTIALS;
187 }
188
189 // Expiration checks are in doApply
190 }
191
192 return tesSUCCESS;
193}
194
195TER
196validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
197{
198 // Note, permissioned domain objects can be deleted at any time
199 auto const slePD = view.read(keylet::permissionedDomain(domainID));
200 if (!slePD)
201 return tecOBJECT_NOT_FOUND;
202
203 auto const closeTime = view.info().parentCloseTime;
204 bool foundExpired = false;
205 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
206 {
207 auto const issuer = h.getAccountID(sfIssuer);
208 auto const type = h.getFieldVL(sfCredentialType);
209 auto const keyletCredential =
210 keylet::credential(subject, issuer, makeSlice(type));
211 auto const sleCredential = view.read(keyletCredential);
212
213 // We cannot delete expired credentials, that would require ApplyView&
214 // However we can check if credentials are expired. Expected transaction
215 // flow is to use `validDomain` in preclaim, converting tecEXPIRED to
216 // tesSUCCESS, then proceed to call `verifyValidDomain` in doApply. This
217 // allows expired credentials to be deleted by any transaction.
218 if (sleCredential)
219 {
220 if (checkExpired(sleCredential, closeTime))
221 {
222 foundExpired = true;
223 continue;
224 }
225 else if (sleCredential->getFlags() & lsfAccepted)
226 return tesSUCCESS;
227 else
228 continue;
229 }
230 }
231
232 return foundExpired ? tecEXPIRED : tecNO_AUTH;
233}
234
235TER
237 ApplyView const& view,
238 STVector256 const& credIDs,
239 AccountID const& dst)
240{
243 lifeExtender.reserve(credIDs.size());
244 for (auto const& h : credIDs)
245 {
246 auto sleCred = view.read(keylet::credential(h));
247 if (!sleCred) // already checked in preclaim
248 return tefINTERNAL; // LCOV_EXCL_LINE
249
250 auto [it, ins] =
251 sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]);
252 if (!ins)
253 return tefINTERNAL; // LCOV_EXCL_LINE
254 lifeExtender.push_back(std::move(sleCred));
255 }
256
257 if (!view.exists(keylet::depositPreauth(dst, sorted)))
258 return tecNO_PERMISSION;
259
260 return tesSUCCESS;
261}
262
264makeSorted(STArray const& credentials)
265{
267 for (auto const& cred : credentials)
268 {
269 auto [it, ins] = out.emplace(cred[sfIssuer], cred[sfCredentialType]);
270 if (!ins)
271 return {};
272 }
273 return out;
274}
275
276NotTEC
277checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j)
278{
279 if (credentials.empty() || (credentials.size() > maxSize))
280 {
281 JLOG(j.trace()) << "Malformed transaction: "
282 "Invalid credentials size: "
283 << credentials.size();
284 return credentials.empty() ? temARRAY_EMPTY : temARRAY_TOO_LARGE;
285 }
286
288 for (auto const& credential : credentials)
289 {
290 auto const& issuer = credential[sfIssuer];
291 if (!issuer)
292 {
293 JLOG(j.trace()) << "Malformed transaction: "
294 "Issuer account is invalid: "
295 << to_string(issuer);
297 }
298
299 auto const ct = credential[sfCredentialType];
300 if (ct.empty() || (ct.size() > maxCredentialTypeLength))
301 {
302 JLOG(j.trace()) << "Malformed transaction: "
303 "Invalid credentialType size: "
304 << ct.size();
305 return temMALFORMED;
306 }
307
308 auto [it, ins] = duplicates.insert(sha512Half(issuer, ct));
309 if (!ins)
310 {
311 JLOG(j.trace()) << "Malformed transaction: "
312 "duplicates in credenentials.";
313 return temMALFORMED;
314 }
315 }
316
317 return tesSUCCESS;
318}
319
320} // namespace credentials
321
322TER
324 ApplyView& view,
325 AccountID const& account,
326 uint256 domainID,
328{
329 auto const slePD = view.read(keylet::permissionedDomain(domainID));
330 if (!slePD)
331 return tecOBJECT_NOT_FOUND;
332
333 // Collect all matching credentials on a side, so we can remove expired ones
334 // We may finish the loop with this collection empty, it's fine.
335 STVector256 credentials;
336 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
337 {
338 auto const issuer = h.getAccountID(sfIssuer);
339 auto const type = h.getFieldVL(sfCredentialType);
340 auto const keyletCredential =
341 keylet::credential(account, issuer, makeSlice(type));
342 if (view.exists(keyletCredential))
343 credentials.push_back(keyletCredential.key);
344 }
345
346 bool const foundExpired = credentials::removeExpired(view, credentials, j);
347 for (auto const& h : credentials)
348 {
349 auto sleCredential = view.read(keylet::credential(h));
350 if (!sleCredential)
351 continue; // expired, i.e. deleted in credentials::removeExpired
352
353 if (sleCredential->getFlags() & lsfAccepted)
354 return tesSUCCESS;
355 }
356
357 return foundExpired ? tecEXPIRED : tecNO_PERMISSION;
358}
359
360TER
362 STTx const& tx,
363 ApplyView& view,
364 AccountID const& src,
365 AccountID const& dst,
366 std::shared_ptr<SLE> const& sleDst,
368{
369 // If depositPreauth is enabled, then an account that requires
370 // authorization has at least two ways to get a payment in:
371 // 1. If src == dst, or
372 // 2. If src is deposit preauthorized by dst (either by account or by
373 // credentials).
374
375 bool const credentialsPresent = tx.isFieldPresent(sfCredentialIDs);
376
377 if (credentialsPresent &&
378 credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j))
379 return tecEXPIRED;
380
381 if (sleDst && (sleDst->getFlags() & lsfDepositAuth))
382 {
383 if (src != dst)
384 {
385 if (!view.exists(keylet::depositPreauth(dst, src)))
386 return !credentialsPresent
389 view, tx.getFieldV256(sfCredentialIDs), dst);
390 }
391 }
392
393 return tesSUCCESS;
394}
395
396} // 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:143
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:51
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:146
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:685
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)
NotTEC checkArray(STArray const &credentials, unsigned maxSize, beast::Journal j)
bool checkExpired(std::shared_ptr< SLE const > const &sleCredential, NetClock::time_point const &closed)
TER authorizedDepositPreauth(ApplyView const &view, STVector256 const &ctx, AccountID const &dst)
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
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1032
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
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:109
@ accepted
Manifest is valid.
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:106
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:307
@ tecOBJECT_NOT_FOUND
Definition TER.h:327
@ tecINTERNAL
Definition TER.h:311
@ tecBAD_CREDENTIALS
Definition TER.h:360
@ tecNO_PERMISSION
Definition TER.h:306
@ tecEXPIRED
Definition TER.h:315
@ tecNO_AUTH
Definition TER.h:301
@ tesSUCCESS
Definition TER.h:245
bool isTesSuccess(TER x) noexcept
Definition TER.h:678
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:649
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)