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