]> git.proxmox.com Git - rustc.git/blame - src/tools/cargo/src/cargo/util/auth/asymmetric.rs
New upstream version 1.72.1+dfsg1
[rustc.git] / src / tools / cargo / src / cargo / util / auth / asymmetric.rs
CommitLineData
fe692bf9
FG
1//! Registry asymmetric authentication support. See [RFC 3231] for more.
2//!
3//! [RFC 3231]: https://rust-lang.github.io/rfcs/3231-cargo-asymmetric-tokens.html
4
5use pasetors::keys::AsymmetricPublicKey;
6use pasetors::keys::AsymmetricSecretKey;
7use pasetors::paserk;
8use pasetors::paserk::FormatAsPaserk;
9use pasetors::version3;
10use pasetors::version3::PublicToken;
11use time::format_description::well_known::Rfc3339;
12use time::OffsetDateTime;
13
14use crate::core::SourceId;
15use crate::ops::RegistryCredentialConfig;
16use crate::CargoResult;
17
18use super::Mutation;
19use super::Secret;
20
21/// The main body of an asymmetric token as describe in RFC 3231.
22#[derive(serde::Serialize)]
23struct Message<'a> {
24 iat: &'a str,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 sub: Option<&'a str>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 mutation: Option<&'a str>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 name: Option<&'a str>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 vers: Option<&'a str>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 cksum: Option<&'a str>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 challenge: Option<&'a str>,
37 /// This field is not yet used. This field can be set to a value >1 to
38 /// indicate a breaking change in the token format.
39 #[serde(skip_serializing_if = "Option::is_none")]
40 v: Option<u8>,
41}
42
43/// The footer of an asymmetric token as describe in RFC 3231.
44#[derive(serde::Serialize)]
45struct Footer<'a> {
46 url: &'a str,
47 kip: paserk::Id,
48}
49
50/// Checks that a secret key is valid, and returns the associated public key in
51/// Paserk format.
52pub fn paserk_public_from_paserk_secret(secret_key: Secret<&str>) -> Option<String> {
53 let secret: Secret<AsymmetricSecretKey<version3::V3>> =
54 secret_key.map(|key| key.try_into()).transpose().ok()?;
55 let public: AsymmetricPublicKey<version3::V3> = secret
56 .as_ref()
57 .map(|key| key.try_into())
58 .transpose()
59 .ok()?
60 .expose();
61 let mut paserk_pub_key = String::new();
62 FormatAsPaserk::fmt(&public, &mut paserk_pub_key).unwrap();
63 Some(paserk_pub_key)
64}
65
66/// Generates a public token from a registry's `credential` configuration for
67/// authenticating to a `source_id`
68///
69/// An optional `mutation` for authenticating a mutation operation aganist the
70/// registry.
71pub fn public_token_from_credential(
72 credential: RegistryCredentialConfig,
73 source_id: &SourceId,
74 mutation: Option<&'_ Mutation<'_>>,
75) -> CargoResult<Secret<String>> {
76 let RegistryCredentialConfig::AsymmetricKey((secret_key, secret_key_subject)) = credential else {
77 anyhow::bail!("credential must be an asymmetric secret key")
78 };
79
80 let secret: Secret<AsymmetricSecretKey<version3::V3>> =
81 secret_key.map(|key| key.as_str().try_into()).transpose()?;
82 let public: AsymmetricPublicKey<version3::V3> = secret
83 .as_ref()
84 .map(|key| key.try_into())
85 .transpose()?
86 .expose();
87 let kip = (&public).try_into()?;
88 let iat = OffsetDateTime::now_utc();
89
90 let message = Message {
91 iat: &iat.format(&Rfc3339)?,
92 sub: secret_key_subject.as_deref(),
93 mutation: mutation.and_then(|m| {
94 Some(match m {
95 Mutation::PrePublish => return None,
96 Mutation::Publish { .. } => "publish",
97 Mutation::Yank { .. } => "yank",
98 Mutation::Unyank { .. } => "unyank",
99 Mutation::Owners { .. } => "owners",
100 })
101 }),
102 name: mutation.and_then(|m| {
103 Some(match m {
104 Mutation::PrePublish => return None,
105 Mutation::Publish { name, .. }
106 | Mutation::Yank { name, .. }
107 | Mutation::Unyank { name, .. }
108 | Mutation::Owners { name, .. } => *name,
109 })
110 }),
111 vers: mutation.and_then(|m| {
112 Some(match m {
113 Mutation::PrePublish | Mutation::Owners { .. } => return None,
114 Mutation::Publish { vers, .. }
115 | Mutation::Yank { vers, .. }
116 | Mutation::Unyank { vers, .. } => *vers,
117 })
118 }),
119 cksum: mutation.and_then(|m| {
120 Some(match m {
121 Mutation::PrePublish
122 | Mutation::Yank { .. }
123 | Mutation::Unyank { .. }
124 | Mutation::Owners { .. } => return None,
125 Mutation::Publish { cksum, .. } => *cksum,
126 })
127 }),
128 challenge: None, // todo: PASETO with challenges
129 v: None,
130 };
131
132 let footer = Footer {
133 url: &source_id.url().to_string(),
134 kip,
135 };
136
137 let secret = secret
138 .map(|secret| {
139 PublicToken::sign(
140 &secret,
141 serde_json::to_string(&message)
142 .expect("cannot serialize")
143 .as_bytes(),
144 Some(
145 serde_json::to_string(&footer)
146 .expect("cannot serialize")
147 .as_bytes(),
148 ),
149 None,
150 )
151 })
152 .transpose()?;
153
154 Ok(secret)
155}