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