Skip to content

Commit 06ce50a

Browse files
committed
Token Examples
1 parent 83ea714 commit 06ce50a

File tree

1 file changed

+91
-2
lines changed

1 file changed

+91
-2
lines changed

text/0000-cargo-asymmetric-tokens.md

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ The claims within the PASETO will include at least:
9898
- If this is a mutation: which one (publish or yank or unyank), the package, the version, the SHA256 checksum of the `.crate` file as stored in the `cksum` in the index. (`mutation`, `name`, `vers`, `cksum` keys respectively.)
9999

100100
The "footer" (which is part of the signature) will be a JSON string in UTF-8 and include:
101-
- The URL where cargo got the config.json file (in the `aud` key).
101+
- The URL where cargo got the config.json file (in the `url` key).
102102
- If this is a registry with an HTTP index, then this is the base URL that all index queries are relative to.
103103
- If this is a registry with a GIT index, it is the URL Cargo used to clone the index.
104104
- The `key ID` (in the `kid` key). Which can be obtained from the public key using the [PASERK IDs](https://github.com/paseto-standard/paserk/blob/master/operations/ID.md) standard.
@@ -114,6 +114,8 @@ The registry server will validate the PASETO, and check the footer and claims:
114114
- If the server issues challenges, that the challenge has not yet been answered.
115115
- If the operation is a mutation, that the package, version, and hash match the request.
116116

117+
See the [Appendix: Token Examples](#token-examples) for a walk through of constructing some tokens.
118+
117119
## Credential Processes
118120

119121
Credential Processes as defined in [RFC 2730](https://github.com/rust-lang/rfcs/pull/2730) are outside programs cargo can call on to change where and how secrets are stored. That RFC defines `special strings` which go in the `credential-process` field to describe what data the process needs from cargo. This RFC adds `{claims}`. If used Cargo will replace it with a JSON encoded set of key value pairs that should be in the generated token. Cargo will check that the output of such a process looks like a valid PASETO v3.public token that Cargo would have generated, and that the PASETO token includes all the claims Cargo provided. The credential process may add additional claims (e.g. 2fa, TOTP), as long as they are nested in `custom`.
@@ -226,4 +228,91 @@ Storing plain text secret tokens is only a problem in practice not in theory. Ho
226228

227229
> Fundamentally these are all problems only because once an attacker has seen a secret token they have all that is needed to act on that user's behalf.
228230
229-
Without the private key an asymmetric token can only be used for the intended registry, for the intended action, and for a limited amount of time. This mitigates the risk of disclosure.
231+
Without the private key an asymmetric token can only be used for the intended registry, for the intended action, and for a limited amount of time. This mitigates the risk of disclosure.
232+
233+
## Token Examples
234+
235+
### A Simple Read Operation
236+
237+
For example: If cargo needs to construct an asymmetric token for a simple read operation it will gather some basic information:
238+
- The private key ([`PASERK` secret format](https://github.com/paseto-standard/paserk/blob/master/types/secret.md)): `"k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36"`
239+
- The current time: `"2022-02-28T18:33:24+00:00"`
240+
- The url to the root of the index: `"https://registry.com/crate-index"`
241+
242+
It will then derive:
243+
- The public key for the private key ([`PASERK` public format](https://github.com/paseto-standard/paserk/blob/master/types/public.md)): `"k3.public.AmDwjlyf8jAV3gm5Z7Kz9xAOcsKslt_Vwp5v-emjFzBHLCtcANzTaVEghTNEMj9PkQ"`
244+
- The [`PASERK ID`](https://github.com/paseto-standard/paserk/blob/master/operations/ID.md) for the public key: `"k3.pid.QB3WNBP-5j-0XQV2MOuvuOcLlJ8uz-pmqtIZus1x3YTu"`
245+
246+
It will then construct a PASETO in the [v3.public format](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md). In this case:
247+
```
248+
v3.public.eyJpYXQiOiAiMjAyMi0wMi0yOFQxODozMzoyNCswMDowMCJ99q655qLlH5HYwCh86OGvPvY26X0rrd7Ibci3fmHz6MgAKK3RugUQ1rvNRjBEJZvfWqqq2WxEOrjMujkuk8jpmJ2B_i3BTIzYYZZRhjZeWAi0erCNqmtFZMeC3_2oqSka.eyJ1cmwiOiAiaHR0cHM6Ly9yZWdpc3RyeS5jb20vY3JhdGUtaW5kZXgiLCAia2lkIjogImszLnBpZC5RQjNXTkJQLTVqLTBYUVYyTU91dnVPY0xsSjh1ei1wbXF0SVp1czF4M1lUdSJ9
249+
```
250+
251+
The server will validate that this looks like a properly formatted `v3.public` PASETO.
252+
It will decode the footer and get:
253+
```
254+
{"url": "https://registry.com/crate-index", "kid": "k3.pid.QB3WNBP-5j-0XQV2MOuvuOcLlJ8uz-pmqtIZus1x3YTu"}
255+
```
256+
It will check that:
257+
- The `url` is for the index of the registry that the request is for.
258+
- The `kid` is for a public key it has on file.
259+
- The PASETO signature can be validated using the public key related to `kid`.
260+
261+
It can then decode the payload and get:
262+
```
263+
{"iat": "2022-02-28T18:33:24+00:00"}
264+
```
265+
It will check that the `iat` is within the valid time period picked by the server.
266+
Given that there is no mutation claim, it will check that the request is a read.
267+
(A read token can be used for multiple requests. See [Rationale and alternatives](#rationale-and-alternatives) for why.)
268+
At this point the server has validated the PASETO, it should now go on to determining if the user associated with this public key should be allowed to read this object.
269+
270+
### A Complicated Publish Operation
271+
272+
For example: If cargo needs to construct an asymmetric token for a complicated publish operation it will gather some basic information:
273+
- The private key ([`PASERK` secret format](https://github.com/paseto-standard/paserk/blob/master/types/secret.md)): `"k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36"`
274+
- The `private-key-subject` for that key: `"private-key-subject"`
275+
- The current time: `"2022-02-28T18:33:24+00:00"`
276+
- The url to the root of the index: `"https://registry-challenge-subject.com/crate-index"`
277+
- The challenge received from the most recent 401/403: `"challenge"`
278+
279+
Because it's a published operation cargo will also gather:
280+
- The crate name: `"foo"`
281+
- The crate version: `"0.0.0"`
282+
- The hash of the `.crate` file: `"f7dbb6acfeff1d490fba693a402456f76b344fea77a5e7cae43b5970c3332b8f"`
283+
284+
It will then derive:
285+
- The public key for the private key ([`PASERK` public format](https://github.com/paseto-standard/paserk/blob/master/types/public.md)): `"k3.public.AmDwjlyf8jAV3gm5Z7Kz9xAOcsKslt_Vwp5v-emjFzBHLCtcANzTaVEghTNEMj9PkQ"`
286+
- The [`PASERK ID`](https://github.com/paseto-standard/paserk/blob/master/operations/ID.md) for the public key: `"k3.pid.QB3WNBP-5j-0XQV2MOuvuOcLlJ8uz-pmqtIZus1x3YTu"`
287+
288+
It will then construct a PASETO in the [v3.public format](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md). In this case:
289+
```
290+
v3.public.eyJjaGFsbGVuZ2UiOiAiY2hhbGxlbmdlIiwgIm11dGF0aW9uIjogInB1Ymxpc2giLCAibmFtZSI6ICJmb28iLCAidmVycyI6ICIwLjAuMCIsICJja3N1bSI6ICJmN2RiYjZhY2ZlZmYxZDQ5MGZiYTY5M2E0MDI0NTZmNzZiMzQ0ZmVhNzdhNWU3Y2FlNDNiNTk3MGMzMzMyYjhmIiwgInN1YiI6ICJwcml2YXRlLWtleS1zdWJqZWN0IiwgImlhdCI6ICIyMDIyLTAyLTI4VDE4OjMzOjI0KzAwOjAwIn36ifmVYCSBYcjHVjQ_JD6R16dcWPEjHYVFOR7QRx3riOLiH7o-m236uNs2NEu-NzOCDZZbsVXvxhop-aUKRc9D-jphV5KFuC8y6mNLklfg1PpH37QeDsyzJDZy604gZ5c.eyJ1cmwiOiAiaHR0cHM6Ly9yZWdpc3RyeS1jaGFsbGVuZ2Utc3ViamVjdC5jb20vY3JhdGUtaW5kZXgiLCAia2lkIjogImszLnBpZC5RQjNXTkJQLTVqLTBYUVYyTU91dnVPY0xsSjh1ei1wbXF0SVp1czF4M1lUdSJ9
291+
```
292+
293+
The server will validate that this looks like a properly formatted `v3.public` PASETO.
294+
It will decode the footer and get:
295+
```
296+
{"url": "https://registry-challenge-subject.com/crate-index", "kid": "k3.pid.QB3WNBP-5j-0XQV2MOuvuOcLlJ8uz-pmqtIZus1x3YTu"}
297+
```
298+
It will check that:
299+
- The `url` is for the index of the registry that the request is for.
300+
301+
It can then decode the payload and get:
302+
```
303+
{"challenge": "challenge", "mutation": "publish", "name": "foo", "vers": "0.0.0", "cksum": "f7dbb6acfeff1d490fba693a402456f76b344fea77a5e7cae43b5970c3332b8f", "sub": "private-key-subject", "iat": "2022-02-28T18:33:24+00:00"}
304+
```
305+
306+
It will check that:
307+
- The `iat` is within the valid time period picked by the server.
308+
- The `sub` and `kid` is for a public key it has on file.
309+
- The PASETO signature can be validated using that public key.
310+
- The `challenge` was issued by this server and has not been revoked.
311+
312+
Given that there is a mutation claim it will check that:
313+
- The request is for a `publish`.
314+
- The request is to publish a crate with the same name as `name`.
315+
- The request is to publish a crate with the same version as `vers`.
316+
- The request is to publish a crate with the same hash as `cksum`.
317+
318+
At this point the server has validated the PASETO, it should now go on to determining if the user associated with this public key should be allowed to publish this object.

0 commit comments

Comments
 (0)