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 { // LCOV_EXCL_START
81 JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
82 return tecINTERNAL;
83 } // LCOV_EXCL_STOP
84
85 // Remove object from owner directory
86 std::uint64_t const page = sleCredential->getFieldU64(node);
87 if (!view.dirRemove(
88 keylet::ownerDir(account), page, sleCredential->key(), false))
89 { // LCOV_EXCL_START
90 JLOG(j.fatal()) << "Unable to delete Credential from owner.";
91 return tefBAD_LEDGER;
92 } // LCOV_EXCL_STOP
93
94 if (isOwner)
95 adjustOwnerCount(view, sleAccount, -1, j);
96
97 return tesSUCCESS;
98 };
99
100 auto const issuer = sleCredential->getAccountID(sfIssuer);
101 auto const subject = sleCredential->getAccountID(sfSubject);
102 bool const accepted = sleCredential->getFlags() & lsfAccepted;
103
104 auto err = delSLE(issuer, sfIssuerNode, !accepted || (subject == issuer));
105 if (!isTesSuccess(err))
106 return err;
107
108 if (subject != issuer)
109 {
110 err = delSLE(subject, sfSubjectNode, accepted);
111 if (!isTesSuccess(err))
112 return err;
113 }
114
115 // Remove object from ledger
116 view.erase(sleCredential);
117
118 return tesSUCCESS;
119}
120
121NotTEC
123{
124 if (!tx.isFieldPresent(sfCredentialIDs))
125 return tesSUCCESS;
126
127 auto const& credentials = tx.getFieldV256(sfCredentialIDs);
128 if (credentials.empty() || (credentials.size() > maxCredentialsArraySize))
129 {
130 JLOG(j.trace())
131 << "Malformed transaction: Credentials array size is invalid: "
132 << credentials.size();
133 return temMALFORMED;
134 }
135
137 for (auto const& cred : credentials)
138 {
139 auto [it, ins] = duplicates.insert(cred);
140 if (!ins)
141 {
142 JLOG(j.trace())
143 << "Malformed transaction: duplicates in credentials.";
144 return temMALFORMED;
145 }
146 }
147
148 return tesSUCCESS;
149}
150
151TER
153 STTx const& tx,
154 ReadView const& view,
155 AccountID const& src,
157{
158 if (!tx.isFieldPresent(sfCredentialIDs))
159 return tesSUCCESS;
160
161 auto const& credIDs(tx.getFieldV256(sfCredentialIDs));
162 for (auto const& h : credIDs)
163 {
164 auto const sleCred = view.read(keylet::credential(h));
165 if (!sleCred)
166 {
167 JLOG(j.trace()) << "Credential doesn't exist. Cred: " << h;
168 return tecBAD_CREDENTIALS;
169 }
170
171 if (sleCred->getAccountID(sfSubject) != src)
172 {
173 JLOG(j.trace())
174 << "Credential doesn't belong to the source account. Cred: "
175 << h;
176 return tecBAD_CREDENTIALS;
177 }
178
179 if (!(sleCred->getFlags() & lsfAccepted))
180 {
181 JLOG(j.trace()) << "Credential isn't accepted. Cred: " << h;
182 return tecBAD_CREDENTIALS;
183 }
184
185 // Expiration checks are in doApply
186 }
187
188 return tesSUCCESS;
189}
190
191TER
192validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
193{
194 // Note, permissioned domain objects can be deleted at any time
195 auto const slePD = view.read(keylet::permissionedDomain(domainID));
196 if (!slePD)
197 return tecOBJECT_NOT_FOUND;
198
199 auto const closeTime = view.info().parentCloseTime;
200 bool foundExpired = false;
201 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
202 {
203 auto const issuer = h.getAccountID(sfIssuer);
204 auto const type = h.getFieldVL(sfCredentialType);
205 auto const keyletCredential =
206 keylet::credential(subject, issuer, makeSlice(type));
207 auto const sleCredential = view.read(keyletCredential);
208
209 // We cannot delete expired credentials, that would require ApplyView&
210 // However we can check if credentials are expired. Expected transaction
211 // flow is to use `validDomain` in preclaim, converting tecEXPIRED to
212 // tesSUCCESS, then proceed to call `verifyValidDomain` in doApply. This
213 // allows expired credentials to be deleted by any transaction.
214 if (sleCredential)
215 {
216 if (checkExpired(sleCredential, closeTime))
217 {
218 foundExpired = true;
219 continue;
220 }
221 else if (sleCredential->getFlags() & lsfAccepted)
222 return tesSUCCESS;
223 else
224 continue;
225 }
226 }
227
228 return foundExpired ? tecEXPIRED : tecNO_AUTH;
229}
230
231TER
233 ApplyView const& view,
234 STVector256 const& credIDs,
235 AccountID const& dst)
236{
239 lifeExtender.reserve(credIDs.size());
240 for (auto const& h : credIDs)
241 {
242 auto sleCred = view.read(keylet::credential(h));
243 if (!sleCred) // already checked in preclaim
244 return tefINTERNAL; // LCOV_EXCL_LINE
245
246 auto [it, ins] =
247 sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]);
248 if (!ins)
249 return tefINTERNAL; // LCOV_EXCL_LINE
250 lifeExtender.push_back(std::move(sleCred));
251 }
252
253 if (!view.exists(keylet::depositPreauth(dst, sorted)))
254 return tecNO_PERMISSION;
255
256 return tesSUCCESS;
257}
258
260makeSorted(STArray const& credentials)
261{
263 for (auto const& cred : credentials)
264 {
265 auto [it, ins] = out.emplace(cred[sfIssuer], cred[sfCredentialType]);
266 if (!ins)
267 return {};
268 }
269 return out;
270}
271
272NotTEC
273checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j)
274{
275 if (credentials.empty() || (credentials.size() > maxSize))
276 {
277 JLOG(j.trace()) << "Malformed transaction: "
278 "Invalid credentials size: "
279 << credentials.size();
280 return credentials.empty() ? temARRAY_EMPTY : temARRAY_TOO_LARGE;
281 }
282
284 for (auto const& credential : credentials)
285 {
286 auto const& issuer = credential[sfIssuer];
287 if (!issuer)
288 {
289 JLOG(j.trace()) << "Malformed transaction: "
290 "Issuer account is invalid: "
291 << to_string(issuer);
293 }
294
295 auto const ct = credential[sfCredentialType];
296 if (ct.empty() || (ct.size() > maxCredentialTypeLength))
297 {
298 JLOG(j.trace()) << "Malformed transaction: "
299 "Invalid credentialType size: "
300 << ct.size();
301 return temMALFORMED;
302 }
303
304 auto [it, ins] = duplicates.insert(sha512Half(issuer, ct));
305 if (!ins)
306 {
307 JLOG(j.trace()) << "Malformed transaction: "
308 "duplicates in credenentials.";
309 return temMALFORMED;
310 }
311 }
312
313 return tesSUCCESS;
314}
315
316} // namespace credentials
317
318TER
320 ApplyView& view,
321 AccountID const& account,
322 uint256 domainID,
324{
325 auto const slePD = view.read(keylet::permissionedDomain(domainID));
326 if (!slePD)
327 return tecOBJECT_NOT_FOUND;
328
329 // Collect all matching credentials on a side, so we can remove expired ones
330 // We may finish the loop with this collection empty, it's fine.
331 STVector256 credentials;
332 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
333 {
334 auto const issuer = h.getAccountID(sfIssuer);
335 auto const type = h.getFieldVL(sfCredentialType);
336 auto const keyletCredential =
337 keylet::credential(account, issuer, makeSlice(type));
338 if (view.exists(keyletCredential))
339 credentials.push_back(keyletCredential.key);
340 }
341
342 bool const foundExpired = credentials::removeExpired(view, credentials, j);
343 for (auto const& h : credentials)
344 {
345 auto sleCredential = view.read(keylet::credential(h));
346 if (!sleCredential)
347 continue; // expired, i.e. deleted in credentials::removeExpired
348
349 if (sleCredential->getFlags() & lsfAccepted)
350 return tesSUCCESS;
351 }
352
353 return foundExpired ? tecEXPIRED : tecNO_PERMISSION;
354}
355
356TER
358 STTx const& tx,
359 ApplyView& view,
360 AccountID const& src,
361 AccountID const& dst,
362 std::shared_ptr<SLE> const& sleDst,
364{
365 // If depositPreauth is enabled, then an account that requires
366 // authorization has at least two ways to get a payment in:
367 // 1. If src == dst, or
368 // 2. If src is deposit preauthorized by dst (either by account or by
369 // credentials).
370
371 bool const credentialsPresent = tx.isFieldPresent(sfCredentialIDs);
372
373 if (credentialsPresent &&
374 credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j))
375 return tecEXPIRED;
376
377 if (sleDst && (sleDst->getFlags() & lsfDepositAuth))
378 {
379 if (src != dst)
380 {
381 if (!view.exists(keylet::depositPreauth(dst, src)))
382 return !credentialsPresent
385 view, tx.getFieldV256(sfCredentialIDs), 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
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:144
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)
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:1029
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:106
@ accepted
Manifest is valid.
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:103
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)