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