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 <xrpld/app/misc/CredentialHelpers.h>
21#include <xrpld/ledger/View.h>
22#include <xrpl/protocol/digest.h>
23
24#include <unordered_set>
25
26namespace ripple {
27namespace credentials {
28
29bool
31 std::shared_ptr<SLE const> const& sleCredential,
32 NetClock::time_point const& closed)
33{
34 std::uint32_t const exp = (*sleCredential)[~sfExpiration].value_or(
36 std::uint32_t const now = closed.time_since_epoch().count();
37 return now > exp;
38}
39
40bool
41removeExpired(ApplyView& view, STTx const& tx, beast::Journal const j)
42{
43 auto const closeTime = view.info().parentCloseTime;
44 bool foundExpired = false;
45
46 STVector256 const& arr(tx.getFieldV256(sfCredentialIDs));
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 JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
82 return tecINTERNAL;
83 }
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 {
90 JLOG(j.fatal()) << "Unable to delete Credential from owner.";
91 return tefBAD_LEDGER;
92 }
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 (!ctx.tx.isFieldPresent(sfCredentialIDs))
125 return tesSUCCESS;
126
127 auto const& credentials = ctx.tx.getFieldV256(sfCredentialIDs);
128 if (credentials.empty() || (credentials.size() > maxCredentialsArraySize))
129 {
130 JLOG(ctx.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(ctx.j.trace())
143 << "Malformed transaction: duplicates in credentials.";
144 return temMALFORMED;
145 }
146 }
147
148 return tesSUCCESS;
149}
150
151TER
152valid(PreclaimContext const& ctx, AccountID const& src)
153{
154 if (!ctx.tx.isFieldPresent(sfCredentialIDs))
155 return tesSUCCESS;
156
157 auto const& credIDs(ctx.tx.getFieldV256(sfCredentialIDs));
158 for (auto const& h : credIDs)
159 {
160 auto const sleCred = ctx.view.read(keylet::credential(h));
161 if (!sleCred)
162 {
163 JLOG(ctx.j.trace()) << "Credential doesn't exist. Cred: " << h;
164 return tecBAD_CREDENTIALS;
165 }
166
167 if (sleCred->getAccountID(sfSubject) != src)
168 {
169 JLOG(ctx.j.trace())
170 << "Credential doesn't belong to the source account. Cred: "
171 << h;
172 return tecBAD_CREDENTIALS;
173 }
174
175 if (!(sleCred->getFlags() & lsfAccepted))
176 {
177 JLOG(ctx.j.trace()) << "Credential isn't accepted. Cred: " << h;
178 return tecBAD_CREDENTIALS;
179 }
180
181 // Expiration checks are in doApply
182 }
183
184 return tesSUCCESS;
185}
186
187TER
188authorized(ApplyContext const& ctx, AccountID const& dst)
189{
190 auto const& credIDs(ctx.tx.getFieldV256(sfCredentialIDs));
193 lifeExtender.reserve(credIDs.size());
194 for (auto const& h : credIDs)
195 {
196 auto sleCred = ctx.view().read(keylet::credential(h));
197 if (!sleCred) // already checked in preclaim
198 return tefINTERNAL;
199
200 auto [it, ins] =
201 sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]);
202 if (!ins)
203 return tefINTERNAL;
204 lifeExtender.push_back(std::move(sleCred));
205 }
206
207 if (!ctx.view().exists(keylet::depositPreauth(dst, sorted)))
208 {
209 JLOG(ctx.journal.trace()) << "DepositPreauth doesn't exist";
210 return tecNO_PERMISSION;
211 }
212
213 return tesSUCCESS;
214}
215
217makeSorted(STArray const& credentials)
218{
220 for (auto const& cred : credentials)
221 {
222 auto [it, ins] = out.emplace(cred[sfIssuer], cred[sfCredentialType]);
223 if (!ins)
224 return {};
225 }
226 return out;
227}
228
229NotTEC
230checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j)
231{
232 if (credentials.empty() || (credentials.size() > maxSize))
233 {
234 JLOG(j.trace()) << "Malformed transaction: "
235 "Invalid credentials size: "
236 << credentials.size();
237 return credentials.empty() ? temARRAY_EMPTY : temARRAY_TOO_LARGE;
238 }
239
241 for (auto const& credential : credentials)
242 {
243 auto const& issuer = credential[sfIssuer];
244 if (!issuer)
245 {
246 JLOG(j.trace()) << "Malformed transaction: "
247 "Issuer account is invalid: "
248 << to_string(issuer);
250 }
251
252 auto const ct = credential[sfCredentialType];
253 if (ct.empty() || (ct.size() > maxCredentialTypeLength))
254 {
255 JLOG(j.trace()) << "Malformed transaction: "
256 "Invalid credentialType size: "
257 << ct.size();
258 return temMALFORMED;
259 }
260
261 auto [it, ins] = duplicates.insert(sha512Half(issuer, ct));
262 if (!ins)
263 {
264 JLOG(j.trace()) << "Malformed transaction: "
265 "duplicates in credenentials.";
266 return temMALFORMED;
267 }
268 }
269
270 return tesSUCCESS;
271}
272
273} // namespace credentials
274
275TER
277 ApplyContext& ctx,
278 AccountID const& src,
279 AccountID const& dst,
280 std::shared_ptr<SLE> const& sleDst)
281{
282 // If depositPreauth is enabled, then an account that requires
283 // authorization has at least two ways to get a payment in:
284 // 1. If src == dst, or
285 // 2. If src is deposit preauthorized by dst (either by account or by
286 // credentials).
287
288 bool const credentialsPresent = ctx.tx.isFieldPresent(sfCredentialIDs);
289
290 if (credentialsPresent &&
291 credentials::removeExpired(ctx.view(), ctx.tx, ctx.journal))
292 return tecEXPIRED;
293
294 if (sleDst && (sleDst->getFlags() & lsfDepositAuth))
295 {
296 if (src != dst)
297 {
298 if (!ctx.view().exists(keylet::depositPreauth(dst, src)))
299 return !credentialsPresent ? tecNO_PERMISSION
300 : credentials::authorized(ctx, dst);
301 }
302 }
303
304 return tesSUCCESS;
305}
306
307} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
Stream fatal() const
Definition: Journal.h:341
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
State information when applying a tx.
Definition: ApplyContext.h:36
ApplyView & view()
Definition: ApplyContext.h:54
beast::Journal const journal
Definition: ApplyContext.h:51
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:140
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
Definition: ApplyView.cpp:189
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.
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
const STVector256 & getFieldV256(SField const &field) const
Definition: STObject.cpp:649
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
T emplace(T... args)
T insert(T... args)
NotTEC checkFields(PreflightContext const &ctx)
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
bool removeExpired(ApplyView &view, STTx const &tx, beast::Journal const j)
TER authorized(ApplyContext const &ctx, AccountID const &dst)
TER valid(PreclaimContext const &ctx, AccountID const &src)
NotTEC checkArray(STArray const &credentials, unsigned maxSize, beast::Journal j)
bool checkExpired(std::shared_ptr< SLE const > const &sleCredential, NetClock::time_point const &closed)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition: Indexes.cpp:521
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:350
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:318
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
@ lsfDepositAuth
bool isTesSuccess(TER x)
Definition: TER.h:656
@ tefBAD_LEDGER
Definition: TER.h:170
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
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
@ tecNO_ENTRY
Definition: TER.h:293
@ tecINTERNAL
Definition: TER.h:297
@ tecBAD_CREDENTIALS
Definition: TER.h:346
@ tecNO_PERMISSION
Definition: TER.h:292
@ tecEXPIRED
Definition: TER.h:301
@ tesSUCCESS
Definition: TER.h:242
TER verifyDepositPreauth(ApplyContext &ctx, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
@ credential
Credentials signature.
TERSubset< CanCvtToTER > TER
Definition: TER.h:627
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition: digest.h:223
@ 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
Definition: LedgerHeader.h:42
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:53
ReadView const & view
Definition: Transactor.h:56
beast::Journal const j
Definition: Transactor.h:60
State information when preflighting a tx.
Definition: Transactor.h:32
beast::Journal const j
Definition: Transactor.h:38
T time_since_epoch(T... args)