···11111212## Generating the Credentials File
13131414-The HS256 JWT secret can be generated with the `openssl` utility:
1414+The RS256 JWT secret can be generated with the `openssl` utility:
15151616```bash
1717-openssl rand 64 | base64 -w0
1717+nix run nixpkgs#openssl -- genrsa -traditional 4096 | base64 -w0
1818```
19192020Create a file on the server containing the following contents:
21212222```
2323-ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="output from openssl"
2323+ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64="output from above"
2424```
25252626Ensure the file is only accessible by root.
···46464747 settings = {
4848 listen = "[::]:8080";
4949+5050+ jwt = { };
49515052 # Data chunking
5153 #
···1616 } ''
1717 cat $configFile
18181919- export ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="dGVzdCBzZWNyZXQ="
1919+ export ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64="$(${pkgs.openssl}/bin/openssl genrsa -traditional 4096 | ${pkgs.coreutils}/bin/base64 -w0)"
2020 export ATTIC_SERVER_DATABASE_URL="sqlite://:memory:"
2121 ${cfg.package}/bin/atticd --mode check-config -f $configFile
2222 cat <$configFile >$out
···7979 Path to an EnvironmentFile containing required environment
8080 variables:
81818282- - ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64: The Base64-encoded version of the
8383- HS256 JWT secret. Generate it with `openssl rand 64 | base64 -w0`.
8282+ - ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64: The base64-encoded RSA PEM PKCS1 of the
8383+ RS256 JWT secret. Generate it with `openssl genrsa -traditional 4096 | base64 -w0`.
8484 '';
8585 type = types.nullOr types.path;
8686 default = null;
···154154 message = ''
155155 <option>services.atticd.credentialsFile</option> is not set.
156156157157- Run `openssl rand 64 | base64 -w0` and create a file with the following contents:
157157+ Run `openssl genrsa -traditional -out private_key.pem 4096 | base64 -w0` and create a file with the following contents:
158158159159- ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="output from command"
159159+ ATTIC_SERVER_TOKEN_RS256_SECRET="output from command"
160160161161 Then, set `services.atticd.credentialsFile` to the quoted absolute path of the file.
162162 '';
···115115 if sub.dump_claims {
116116 println!("{}", serde_json::to_string(token.opaque_claims())?);
117117 } else {
118118- let encoded_token = token.encode(&config.token_hs256_secret)?;
118118+ let signature_type = config.jwt.signing_config.into();
119119+120120+ let encoded_token = token.encode(
121121+ &signature_type,
122122+ &config.jwt.token_bound_issuer,
123123+ &config.jwt.token_bound_audiences,
124124+ )?;
119125 println!("{}", encoded_token);
120126 }
121127
+35-8
server/src/config-template.toml
···3434# cache.
3535#require-proof-of-possession = true
36363737-# JWT signing token
3838-#
3939-# Set this to the Base64 encoding of some random data.
4040-# You can also set it via the `ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64` environment
4141-# variable.
4242-token-hs256-secret-base64 = "%token_hs256_secret_base64%"
4343-4437# Database connection
4538[database]
4639# Connection URL
···8578#[storage.credentials]
8679# access_key_id = ""
8780# secret_access_key = ""
8888-8181+8982# Data chunking
9083#
9184# Warning: If you change any of the values here, it will be
···134127# Zero (default) means time-based garbage-collection is
135128# disabled by default. You can enable it on a per-cache basis.
136129#default-retention-period = "6 months"
130130+131131+[jwt]
132132+# WARNING: Changing _anything_ in this section will break any existing
133133+# tokens. If you need to regenerate them, ensure that you use the the
134134+# correct secret and include the `iss` and `aud` claims.
135135+136136+# JWT `iss` claim
137137+#
138138+# Set this to the JWT issuer that you want to validate.
139139+# If this is set, all received JWTs will validate that the `iss` claim
140140+# matches this value.
141141+#token-bound-issuer = "some-issuer"
142142+143143+# JWT `aud` claim
144144+#
145145+# Set this to the JWT audience(s) that you want to validate.
146146+# If this is set, all received JWTs will validate that the `aud` claim
147147+# contains at least one of these values.
148148+#token-bound-audiences = ["some-audience1", "some-audience2"]
149149+150150+[jwt.signing]
151151+# JWT RS256 secret key
152152+#
153153+# Set this to the base64-encoded private half of an RSA PEM PKCS1 key.
154154+# You can also set it via the `ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64`
155155+# environment variable.
156156+token-rs256-secret-base64 = "%token_rs256_secret_base64%"
157157+158158+# JWT HS256 secret key
159159+#
160160+# Set this to the base64-encoded HMAC secret key.
161161+# You can also set it via the `ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64`
162162+# environment variable.
163163+#token-hs256-secret-base64 = ""
+209-12
server/src/config.rs
···11//! Server configuration.
2233+use std::collections::HashSet;
34use std::env;
45use std::net::SocketAddr;
56use std::path::{Path, PathBuf};
···7889use anyhow::Result;
910use async_compression::Level as CompressionLevel;
1111+use attic_token::SignatureType;
1012use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
1113use derivative::Derivative;
1214use serde::{de, Deserialize};
1315use xdg::BaseDirectories;
14161515-use crate::access::{decode_token_hs256_secret_base64, HS256Key};
1717+use crate::access::{
1818+ decode_token_hs256_secret_base64, decode_token_rs256_pubkey_base64,
1919+ decode_token_rs256_secret_base64, HS256Key, RS256KeyPair, RS256PublicKey,
2020+};
1621use crate::narinfo::Compression as NixCompression;
1722use crate::storage::{LocalStorageConfig, S3StorageConfig};
1823···2631/// This is useful for deploying to certain application platforms like Fly.io
2732const ENV_CONFIG_BASE64: &str = "ATTIC_SERVER_CONFIG_BASE64";
28332929-/// Environment variable storing the Base64-encoded HS256 JWT secret.
3434+/// Environment variable storing the base64-encoded HMAC secret (used for signing and verifying
3535+/// received JWTs).
3036const ENV_TOKEN_HS256_SECRET_BASE64: &str = "ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64";
3737+3838+/// Environment variable storing the base64-encoded RSA PEM PKCS1 private key (used for signing and
3939+/// verifying received JWTs).
4040+const ENV_TOKEN_RS256_SECRET_BASE64: &str = "ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64";
4141+4242+/// Environment variable storing the base64-encoded RSA PEM PKCS1 public key (used for verifying
4343+/// received JWTs only).
4444+const ENV_TOKEN_RS256_PUBKEY_BASE64: &str = "ATTIC_SERVER_TOKEN_RS256_PUBKEY_BASE64";
31453246/// Environment variable storing the database connection string.
3347const ENV_DATABASE_URL: &str = "ATTIC_SERVER_DATABASE_URL";
···108122 #[serde(default = "Default::default")]
109123 pub garbage_collection: GarbageCollectionConfig,
110124125125+ /// JSON Web Token.
126126+ pub jwt: JWTConfig,
127127+128128+ /// (Deprecated Stub)
129129+ ///
130130+ /// This simply results in an error telling the user to update
131131+ /// their configuration.
132132+ #[serde(rename = "token-hs256-secret-base64")]
133133+ #[serde(default = "Default::default")]
134134+ #[serde(deserialize_with = "deserialize_deprecated_token_hs256_secret")]
135135+ #[derivative(Debug = "ignore")]
136136+ pub _depreated_token_hs256_secret: Option<String>,
137137+}
138138+139139+/// JSON Web Token configuration.
140140+#[derive(Clone, Derivative, Deserialize)]
141141+#[derivative(Debug)]
142142+pub struct JWTConfig {
143143+ /// The `iss` claim of the JWT.
144144+ ///
145145+ /// If specified, received JWTs must have this claim, and its value must match this
146146+ /// configuration.
147147+ #[serde(rename = "token-bound-issuer")]
148148+ #[serde(default = "Default::default")]
149149+ pub token_bound_issuer: Option<String>,
150150+151151+ /// The `aud` claim of the JWT.
152152+ ///
153153+ /// If specified, received JWTs must have this claim, and must contain one of the configured
154154+ /// values.
155155+ #[serde(rename = "token-bound-audiences")]
156156+ #[serde(default = "Default::default")]
157157+ pub token_bound_audiences: Option<HashSet<String>>,
158158+159159+ /// JSON Web Token signing.
160160+ #[serde(rename = "signing")]
161161+ #[serde(default = "load_jwt_signing_config_from_env")]
162162+ #[derivative(Debug = "ignore")]
163163+ pub signing_config: JWTSigningConfig,
164164+}
165165+166166+/// JSON Web Token signing configuration.
167167+#[derive(Clone, Deserialize)]
168168+pub enum JWTSigningConfig {
169169+ /// JSON Web Token RSA pubkey.
170170+ ///
171171+ /// Set this to the base64-encoded RSA PEM PKCS1 public key to use for verifying JWTs only.
172172+ #[serde(rename = "token-rs256-pubkey-base64")]
173173+ #[serde(deserialize_with = "deserialize_token_rs256_pubkey_base64")]
174174+ RS256VerifyOnly(RS256PublicKey),
175175+176176+ /// JSON Web Token RSA secret.
177177+ ///
178178+ /// Set this to the base64-encoded RSA PEM PKCS1 private key to use for signing and verifying
179179+ /// JWTs.
180180+ #[serde(rename = "token-rs256-secret-base64")]
181181+ #[serde(deserialize_with = "deserialize_token_rs256_secret_base64")]
182182+ RS256SignAndVerify(RS256KeyPair),
183183+111184 /// JSON Web Token HMAC secret.
112185 ///
113113- /// Set this to the base64 encoding of a randomly generated secret.
186186+ /// Set this to the base64-encoded HMAC secret to use for signing and verifying JWTs.
114187 #[serde(rename = "token-hs256-secret-base64")]
115188 #[serde(deserialize_with = "deserialize_token_hs256_secret_base64")]
116116- #[serde(default = "load_token_hs256_secret_from_env")]
117117- #[derivative(Debug = "ignore")]
118118- pub token_hs256_secret: HS256Key,
189189+ HS256SignAndVerify(HS256Key),
190190+}
191191+192192+impl From<JWTSigningConfig> for SignatureType {
193193+ fn from(value: JWTSigningConfig) -> Self {
194194+ match value {
195195+ JWTSigningConfig::RS256VerifyOnly(key) => Self::RS256PubkeyOnly(key),
196196+ JWTSigningConfig::RS256SignAndVerify(key) => Self::RS256(key),
197197+ JWTSigningConfig::HS256SignAndVerify(key) => Self::HS256(key),
198198+ }
199199+ }
119200}
120201121202/// Database connection configuration.
···240321 pub default_retention_period: Duration,
241322}
242323243243-fn load_token_hs256_secret_from_env() -> HS256Key {
244244- let s = env::var(ENV_TOKEN_HS256_SECRET_BASE64)
245245- .expect("The HS256 secret must be specified in either token_hs256_secret or the ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64 environment.");
324324+fn load_jwt_signing_config_from_env() -> JWTSigningConfig {
325325+ let config = if let Some(config) = load_token_rs256_pubkey_from_env() {
326326+ config
327327+ } else if let Some(config) = load_token_rs256_secret_from_env() {
328328+ config
329329+ } else if let Some(config) = load_token_hs256_secret_from_env() {
330330+ config
331331+ } else {
332332+ panic!(
333333+ "\n\
334334+ You must configure JWT signing and verification inside your TOML \
335335+ configuration by setting one of the following options in the \
336336+ [jwt.signing] block:\n\
337337+ \n\
338338+ * token-rs256-pubkey-base64\n\
339339+ * token-rs256-secret-base64\n\
340340+ * token-hs256-secret-base64\n\
341341+ \n\
342342+ or by setting one of the following environment variables:\n\
343343+ \n\
344344+ * {ENV_TOKEN_RS256_PUBKEY_BASE64}\n\
345345+ * {ENV_TOKEN_RS256_SECRET_BASE64}\n\
346346+ * {ENV_TOKEN_HS256_SECRET_BASE64}\n\
347347+ \n\
348348+ Options will be tried in that same order (configuration options \
349349+ first, then environment options if none of the configuration options \
350350+ were set, starting with the respective RSA pubkey option, the RSA \
351351+ secret option, and finally the HMAC secret option). \
352352+ The first option that is found will be used.\n\
353353+ \n\
354354+ If an RS256 pubkey (asymmetric RSA PEM PKCS1 public key) is \
355355+ provided, it will only be possible to verify received JWTs, and not \
356356+ sign new JWTs.\n\
357357+ \n\
358358+ If an RS256 secret (asymmetric RSA PEM PKCS1 private key) is \
359359+ provided, it will be used for both signing new JWTs and verifying \
360360+ received JWTs.\n\
361361+ \n\
362362+ If an HS256 secret (symmetric HMAC secret) is provided, it will be \
363363+ used for both signing new JWTs and verifying received JWTs.\n\
364364+ "
365365+ )
366366+ };
367367+368368+ config
369369+}
370370+371371+fn load_token_hs256_secret_from_env() -> Option<JWTSigningConfig> {
372372+ let s = env::var(ENV_TOKEN_HS256_SECRET_BASE64).ok()?;
373373+374374+ decode_token_hs256_secret_base64(&s)
375375+ .ok()
376376+ .map(JWTSigningConfig::HS256SignAndVerify)
377377+}
378378+379379+fn load_token_rs256_secret_from_env() -> Option<JWTSigningConfig> {
380380+ let s = env::var(ENV_TOKEN_RS256_SECRET_BASE64).ok()?;
381381+382382+ decode_token_rs256_secret_base64(&s)
383383+ .ok()
384384+ .map(JWTSigningConfig::RS256SignAndVerify)
385385+}
386386+387387+fn load_token_rs256_pubkey_from_env() -> Option<JWTSigningConfig> {
388388+ let s = env::var(ENV_TOKEN_RS256_PUBKEY_BASE64).ok()?;
246389247247- decode_token_hs256_secret_base64(&s).expect("Failed to load as decoding key")
390390+ decode_token_rs256_pubkey_base64(&s)
391391+ .ok()
392392+ .map(JWTSigningConfig::RS256VerifyOnly)
248393}
249394250395fn load_database_url_from_env() -> String {
251251- env::var(ENV_DATABASE_URL)
252252- .expect("Database URL must be specified in either database.url or the ATTIC_SERVER_DATABASE_URL environment.")
396396+ env::var(ENV_DATABASE_URL).expect(&format!(
397397+ "Database URL must be specified in either database.url \
398398+ or the {ENV_DATABASE_URL} environment."
399399+ ))
253400}
254401255402impl CompressionConfig {
···296443 }
297444}
298445446446+fn deserialize_deprecated_token_hs256_secret<'de, D>(
447447+ _deserializer: D,
448448+) -> Result<Option<String>, D::Error>
449449+where
450450+ D: de::Deserializer<'de>,
451451+{
452452+ use de::Error;
453453+454454+ Err(Error::custom(
455455+ "\n\
456456+ The token-hs256-secret-base64 field has been moved to [jwt.signing].\n\
457457+ \n\
458458+ To continue using HS256 signing, move your current config:\n\
459459+ \n\
460460+ token-hs256-secret-base64 = \"your token\"\n\
461461+ \n\
462462+ To the bottom of the file like so:\n\
463463+ \n\
464464+ [jwt.signing]\n\
465465+ token-hs256-secret-base64 = \"your token\"\n\
466466+ ",
467467+ ))
468468+}
469469+299470fn deserialize_token_hs256_secret_base64<'de, D>(deserializer: D) -> Result<HS256Key, D::Error>
300471where
301472 D: de::Deserializer<'de>,
···304475305476 let s = String::deserialize(deserializer)?;
306477 let key = decode_token_hs256_secret_base64(&s).map_err(Error::custom)?;
478478+479479+ Ok(key)
480480+}
481481+482482+fn deserialize_token_rs256_secret_base64<'de, D>(deserializer: D) -> Result<RS256KeyPair, D::Error>
483483+where
484484+ D: de::Deserializer<'de>,
485485+{
486486+ use de::Error;
487487+488488+ let s = String::deserialize(deserializer)?;
489489+ let key = decode_token_rs256_secret_base64(&s).map_err(Error::custom)?;
490490+491491+ Ok(key)
492492+}
493493+494494+fn deserialize_token_rs256_pubkey_base64<'de, D>(
495495+ deserializer: D,
496496+) -> Result<RS256PublicKey, D::Error>
497497+where
498498+ D: de::Deserializer<'de>,
499499+{
500500+ use de::Error;
501501+502502+ let s = String::deserialize(deserializer)?;
503503+ let key = decode_token_rs256_pubkey_base64(&s).map_err(Error::custom)?;
307504308505 Ok(key)
309506}
···11//! Access control.
22//!
33//! Access control in Attic is simple and stateless [0] - The server validates
44-//! the JWT against a HS256 key and allows access based on the `https://jwt.attic.rs/v1`
44+//! the JWT against the configured key and allows access based on the `https://jwt.attic.rs/v1`
55//! claim.
66//!
77//! One primary goal of the Attic Server is easy scalability. It's designed
···8383#[cfg(test)]
8484mod tests;
85858686+use std::collections::HashSet;
8687use std::error::Error as StdError;
87888889use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
8990use chrono::{DateTime, Utc};
9091use displaydoc::Display;
9192use indexmap::IndexMap;
9393+use jwt_simple::prelude::{Duration, RSAKeyPairLike, RSAPublicKeyLike, VerificationOptions};
9294pub use jwt_simple::{
9393- algorithms::{HS256Key, MACLike},
9595+ algorithms::{HS256Key, MACLike, RS256KeyPair, RS256PublicKey},
9496 claims::{Claims, JWTClaims},
9597 prelude::UnixTimeStamp,
9698};
···155157pub struct CachePermission {
156158 /// Can pull objects from the cache.
157159 #[serde(default = "CachePermission::permission_default")]
158158- #[serde(skip_serializing_if = "is_false")]
160160+ #[serde(skip_serializing_if = "std::ops::Not::not")]
159161 #[serde(rename = "r")]
160162 #[serde_as(as = "BoolFromInt")]
161163 pub pull: bool,
162164163165 /// Can push objects to the cache.
164166 #[serde(default = "CachePermission::permission_default")]
165165- #[serde(skip_serializing_if = "is_false")]
167167+ #[serde(skip_serializing_if = "std::ops::Not::not")]
166168 #[serde(rename = "w")]
167169 #[serde_as(as = "BoolFromInt")]
168170 pub push: bool,
169171170172 /// Can delete objects from the cache.
171173 #[serde(default = "CachePermission::permission_default")]
172172- #[serde(skip_serializing_if = "is_false")]
174174+ #[serde(skip_serializing_if = "std::ops::Not::not")]
173175 #[serde(rename = "d")]
174176 #[serde_as(as = "BoolFromInt")]
175177 pub delete: bool,
176178177179 /// Can create the cache itself.
178180 #[serde(default = "CachePermission::permission_default")]
179179- #[serde(skip_serializing_if = "is_false")]
181181+ #[serde(skip_serializing_if = "std::ops::Not::not")]
180182 #[serde(rename = "cc")]
181183 #[serde_as(as = "BoolFromInt")]
182184 pub create_cache: bool,
183185184186 /// Can reconfigure the cache.
185187 #[serde(default = "CachePermission::permission_default")]
186186- #[serde(skip_serializing_if = "is_false")]
188188+ #[serde(skip_serializing_if = "std::ops::Not::not")]
187189 #[serde(rename = "cr")]
188190 #[serde_as(as = "BoolFromInt")]
189191 pub configure_cache: bool,
190192191193 /// Can configure retention/quota settings.
192194 #[serde(default = "CachePermission::permission_default")]
193193- #[serde(skip_serializing_if = "is_false")]
195195+ #[serde(skip_serializing_if = "std::ops::Not::not")]
194196 #[serde(rename = "cq")]
195197 #[serde_as(as = "BoolFromInt")]
196198 pub configure_cache_retention: bool,
197199198200 /// Can destroy the cache itself.
199201 #[serde(default = "CachePermission::permission_default")]
200200- #[serde(skip_serializing_if = "is_false")]
202202+ #[serde(skip_serializing_if = "std::ops::Not::not")]
201203 #[serde(rename = "cd")]
202204 #[serde_as(as = "BoolFromInt")]
203205 pub destroy_cache: bool,
···223225224226 /// Base64 decode error: {0}
225227 Base64Error(base64::DecodeError),
228228+229229+ /// RSA Key error: {0}
230230+ RsaKeyError(rsa::pkcs1::Error),
231231+232232+ /// Failure decoding the base64 layer of the base64 encoded PEM
233233+ Utf8Error(std::str::Utf8Error),
234234+235235+ /// Pubkey-only JWT authentication cannot create signed JWTs
236236+ PubkeyOnlyCannotCreateToken,
237237+}
238238+239239+/// The supported JWT signature types.
240240+pub enum SignatureType {
241241+ HS256(HS256Key),
242242+ RS256(RS256KeyPair),
243243+ RS256PubkeyOnly(RS256PublicKey),
226244}
227245228246impl Token {
229247 /// Verifies and decodes a token.
230230- pub fn from_jwt(token: &str, key: &HS256Key) -> Result<Self> {
231231- key.verify_token(token, None)
232232- .map_err(Error::TokenError)
233233- .map(Token)
248248+ pub fn from_jwt(
249249+ token: &str,
250250+ signature_type: &SignatureType,
251251+ maybe_bound_issuer: &Option<String>,
252252+ maybe_bound_audiences: &Option<HashSet<String>>,
253253+ ) -> Result<Self> {
254254+ let opts = VerificationOptions {
255255+ reject_before: None,
256256+ accept_future: false,
257257+ required_subject: None,
258258+ required_key_id: None,
259259+ required_public_key: None,
260260+ required_nonce: None,
261261+ allowed_issuers: maybe_bound_issuer
262262+ .as_ref()
263263+ .map(|s| [s.to_owned()].into())
264264+ .to_owned(),
265265+ allowed_audiences: maybe_bound_audiences.to_owned(),
266266+ time_tolerance: None,
267267+ max_validity: None,
268268+ max_token_length: None,
269269+ max_header_length: None,
270270+ artificial_time: None,
271271+ };
272272+273273+ match signature_type {
274274+ SignatureType::HS256(key) => key
275275+ .verify_token(token, Some(opts))
276276+ .map_err(Error::TokenError)
277277+ .map(Token),
278278+ SignatureType::RS256(key) => {
279279+ let public_key = key.public_key();
280280+ public_key
281281+ .verify_token(token, Some(opts))
282282+ .map_err(Error::TokenError)
283283+ .map(Token)
284284+ }
285285+ SignatureType::RS256PubkeyOnly(key) => key
286286+ .verify_token(token, Some(opts))
287287+ .map_err(Error::TokenError)
288288+ .map(Token),
289289+ }
234290 }
235291236292 /// Creates a new token with an expiration timestamp.
···239295 attic_ns: Default::default(),
240296 };
241297298298+ let now_epoch = Utc::now().signed_duration_since(DateTime::UNIX_EPOCH);
299299+242300 Self(JWTClaims {
243301 issued_at: None,
244302 expires_at: Some(UnixTimeStamp::from_secs(
245303 exp.timestamp().try_into().unwrap(),
246304 )),
247247- invalid_before: None,
305305+ invalid_before: Some(Duration::new(
306306+ now_epoch.num_seconds().try_into().unwrap(),
307307+ 0,
308308+ )),
248309 issuer: None,
249310 subject: Some(sub),
250311 audiences: None,
···255316 }
256317257318 /// Encodes the token.
258258- pub fn encode(&self, key: &HS256Key) -> Result<String> {
259259- key.authenticate(self.0.clone()).map_err(Error::TokenError)
319319+ pub fn encode(
320320+ &self,
321321+ signature_type: &SignatureType,
322322+ maybe_bound_issuer: &Option<String>,
323323+ maybe_bound_audiences: &Option<HashSet<String>>,
324324+ ) -> Result<String> {
325325+ let mut token = self.0.clone();
326326+327327+ if let Some(issuer) = maybe_bound_issuer {
328328+ token = token.with_issuer(issuer);
329329+ }
330330+ if let Some(audiences) = maybe_bound_audiences {
331331+ token = token.with_audiences(audiences.to_owned());
332332+ }
333333+334334+ match signature_type {
335335+ SignatureType::HS256(key) => key.authenticate(token).map_err(Error::TokenError),
336336+ SignatureType::RS256(key) => key.sign(token).map_err(Error::TokenError),
337337+ SignatureType::RS256PubkeyOnly(_) => {
338338+ return Err(Error::PubkeyOnlyCannotCreateToken);
339339+ }
340340+ }
260341 }
261342262343 /// Returns the subject of the token.
···362443impl StdError for Error {}
363444364445pub fn decode_token_hs256_secret_base64(s: &str) -> Result<HS256Key> {
365365- let secret = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?;
366366- Ok(HS256Key::from_bytes(&secret))
446446+ let decoded = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?;
447447+ let secret = std::str::from_utf8(&decoded).map_err(Error::Utf8Error)?;
448448+ Ok(HS256Key::from_bytes(&secret.as_bytes()))
367449}
368450369369-// bruh
370370-fn is_false(b: &bool) -> bool {
371371- !b
451451+pub fn decode_token_rs256_secret_base64(s: &str) -> Result<RS256KeyPair> {
452452+ let decoded = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?;
453453+ let secret = std::str::from_utf8(&decoded).map_err(Error::Utf8Error)?;
454454+ let keypair = RS256KeyPair::from_pem(secret).map_err(Error::TokenError)?;
455455+456456+ Ok(keypair)
457457+}
458458+459459+pub fn decode_token_rs256_pubkey_base64(s: &str) -> Result<RS256PublicKey> {
460460+ let decoded = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?;
461461+ let pubkey = std::str::from_utf8(&decoded).map_err(Error::Utf8Error)?;
462462+ let pubkey = RS256PublicKey::from_pem(pubkey).map_err(Error::TokenError)?;
463463+464464+ Ok(pubkey)
372465}
+86-56
token/src/tests.rs
···10101111#[test]
1212fn test_basic() {
1313- // "very secure secret"
1414- let base64_secret = "dmVyeSBzZWN1cmUgc2VjcmV0";
1515-1616- let dec_key = decode_token_hs256_secret_base64(base64_secret).unwrap();
1717-1813 /*
1414+ $ cat json
1915 {
2016 "sub": "meow",
2117 "exp": 4102324986,
1818+ "nbf": 0,
2219 "https://jwt.attic.rs/v1": {
2320 "caches": {
2421 "all-*": {"r":1},
···3128 }
3229 */
33303434- let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjQxMDIzMjQ5ODYsImh0dHBzOi8vand0LmF0dGljLnJzL3YxIjp7ImNhY2hlcyI6eyJhbGwtKiI6eyJyIjoxfSwiYWxsLWNpLSoiOnsidyI6MX0sImNhY2hlLXJvIjp7InIiOjF9LCJjYWNoZS1ydyI6eyJyIjoxLCJ3IjoxfSwidGVhbS0qIjp7ImNjIjoxLCJyIjoxLCJ3IjoxfX19LCJpYXQiOjE3MTY2NjA1ODksInN1YiI6Im1lb3cifQ.8vtxp_1OEYdcnkGPM4c9ORXooJZV7DOTS4NRkMKN8mw";
3131+ let tokens: &[(&str, Box<dyn Fn() -> Token>)] = &[
3232+ (
3333+ "hs256",
3434+ Box::new(|| {
3535+ // "very secure secret"
3636+ let base64_secret = "dmVyeSBzZWN1cmUgc2VjcmV0";
3737+ let dec_key = decode_token_hs256_secret_base64(base64_secret).unwrap();
3838+3939+ let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjQxMDIzMjQ5ODYsImh0dHBzOi8vand0LmF0dGljLnJzL3YxIjp7ImNhY2hlcyI6eyJhbGwtKiI6eyJyIjoxfSwiYWxsLWNpLSoiOnsidyI6MX0sImNhY2hlLXJvIjp7InIiOjF9LCJjYWNoZS1ydyI6eyJyIjoxLCJ3IjoxfSwidGVhbS0qIjp7ImNjIjoxLCJyIjoxLCJ3IjoxfX19LCJpYXQiOjE3MTY2NjA1ODksInN1YiI6Im1lb3cifQ.8vtxp_1OEYdcnkGPM4c9ORXooJZV7DOTS4NRkMKN8mw";
4040+4141+ Token::from_jwt(token, &SignatureType::HS256(dec_key), &None, &None).unwrap()
4242+ }),
4343+ ),
4444+ (
4545+ "rs256",
4646+ Box::new(|| {
4747+ // nix shell nixpkgs#jwt-cli
4848+ // openssl genpkey -out rs256 -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -outform der
4949+ // BASE64_SECRET=$(openssl rsa -in rs256 -outform PEM -traditional | base64 -w0)
5050+ let base64_secret = "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNUZranRMRzV5eS9pMFlnYkQxeUJBK21GckNmLzZiQ2F0TDFFQ3ppNG1tZWhSZTcwCkFEL0dSSHhTVUErc0pZeCtZNjlyL0RqQWs2OFJlQ1c4b2FQWXhtc21RNG5VM2ZwZ2E3WWFqZ3ZoWmVsa3JtaC8KZ1ZURWtFTG1IZlJtQkwvOWlsT20yRHNtYTVhUFo0SFl6ellpdjJvcFF5UGRndXcyWXFtbzE3Nk5MdllCMmpJTwovR3FkdE55K3NPV296NktVSVlJa0hWWU5HMENVcFNzdXBqUTJ6VTVZMFc2UXlNQWFWd1BONElJT3lXWUNwZXRECjFJbWxYekhROXM4NXFSWnlLa21iZFhtTVBVWmUvekRxc2FFd3lscFlpT0RjbDdRYU5QTzEzZnk3UGtQMmVwdUkKTk5tZ1E0WEF0MkF4ZXNKck5ibUs4aG1iM3doRXZkNjRFMGdEV1FJREFRQUJBb0lCQUJEemNRd2IyVi8wK1JCMgoyeE5qMll2eHpPTi93S2FYWHBTbUxDUHRIUDhSVEU2RnM0VkZOckdrelBOMmhsL3ZNdjZ4YWdHNk1NbUZ5SFV6CnovSHIyTTY1NjRnOTloaFlXc29FSmFwL3hVYXNjYlhrdWZwZTBZeW4rcThra21JdDRtTmZYRlpXNWI0ODJmNWsKRERVdG5weTVBOEVoSzNOcGw0dnhia0E5dS90TlVlT1NHTkhPYVZjcHdERVhDNXJ4bmFxTm5wMkMwa1A4ODRINgpSb2lZVkF4bytHaVpNVzhIOFRmSXVsenh3c04yQnVNcUNmOGVhNG1EM0pRVHZ2REhhUHM4eVJTUlB3UmlHYUkzCnVybFRmdjg4U20va09oL0N2SkpoRnhCVkVNVjIydWRNUmU3L3NpTWtlbVlvUnhaTWJjRGVQK2h1RktJWTRSMEoKNnRJUHQ3VUNnWUVBOTlhL2IzeFBsQWh0ck02dUlUUXNQd0FYQUg3Q1NXL1FSdVJUTWVhYXVIMk9sRitjZmpMNApJS1Nsdy9QaUtaUEk1TFRWM2ZVZk5WNTVsOFZHTytsT2ViTFhnaXBYM3BqSDBma3AyY3Q2Smk3aGw0aUlXK0h0ClpJNE9KYkYwTTBETHdySkd3T25QL2trRHNxSW9IbC9MdTBRM2FxSm1RVCsvcG54R083R21kbDhDZ1lFQTY5NFcKZHF2NnF4VjF5V0Z4QWZOOE1hZStpTC9xY1VhTm85ZzMva2YvOXZ3VXdtcERvR0xnaVVLMWZKb3BUYlBjcWgwRwptbUZEQ3V2M1Q0OS9yU2k5dU4zYm82cmlXRUl4VFg1YUtFSjlpSEFMWDJGWDdGSDJRdUZGWEwzQ2c0ckdvL1pDCmdjUkxuS3dma3JUVnRxeEdaNjN4YmsvcFpHWjZtTW01VkNDck1VY0NnWUVBc3JUT1pQMG1CSC92VldQU2UyNjcKV05JZncrT2pCSUR6bGFxZHNxV3Rlc3BPUFA2VVFRdFBqM29wYlJvMlFmU21Md09XRXUzbEN2Nk1mcnRvNFZwaAprNjg1WmtwU0FkZjRmWmRFYmg4aWZOWGhKUHIyR0FyWXVtRVVJbW5LZUFxSTRtTGFVZEJHZ2Z6MEJhS1hldzlvClFDZjRMWlBjVjhBMzJUeFRDRWdZMTlFQ2dZQU04U2F5WkVWZzFkQ2N1Q2dIUDJEMUtJc2YzY2Z6WnplbVlkclEKclFxeWRxcDg4Rys5Z1M5bzJLdzBwaERXSHFSaEFTNjNrZGFuNXNLdkx1U0dqOUc1THhNNks4bzNwWW9uQW1QWQpDYTN4cXBRMUs1WXpkVnZaMTVxQ3VEYlFHUEZGVmVIWVZQa0JJOENud0J4cDVaSUhabGYxQVpXQTJNNnBTNGhMCndXOGpTUUtCZ1FDQmNJbjU4Y0lmZkhmMjM4SUJvZnR1UVVzREZGcnkzaUVpaWpTYmJ1WnB1Vm8zL2pWbUsyaEYKS2xUL2xoRDdWdGJ1V3phMG9WQmZDaWZqMnZ2S2pmZ0l6NnF3Um1UbC9DSjlWdUNHTUI1VG55cGl3OEtodXorSAo0L2twdDdNcW9WQ0dRSjd1WVQyQzY1K0JqNklnUnBQT09za3VKNW1RZ0FlbTQ3eDBrVnRSemc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=";
5151+5252+ let dec_key = decode_token_rs256_secret_base64(base64_secret).unwrap();
5353+5454+ // TOKEN=$(jq -c < json | jwt encode --alg RS256 --secret @./rs256 -)
5555+ let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjQxMDIzMjQ5ODYsImh0dHBzOi8vand0LmF0dGljLnJzL3YxIjp7ImNhY2hlcyI6eyJhbGwtKiI6eyJyIjoxfSwiYWxsLWNpLSoiOnsidyI6MX0sImNhY2hlLXJvIjp7InIiOjF9LCJjYWNoZS1ydyI6eyJyIjoxLCJ3IjoxfSwidGVhbS0qIjp7ImNjIjoxLCJyIjoxLCJ3IjoxfX19LCJpYXQiOjE3MjIwMDUwNzksIm5iZiI6MCwic3ViIjoibWVvdyJ9.Zs24IUbQOpOjhEe0sfsoSSJhDrzf4v-_wX_ceKqHeb2MERY8XSIQ1RPTNVeOW4LfJHumJj_rxh8Wv2BRGZSMldrTt0Ab_N7FnkhA37_jnRvgvEjSG3V4fC8aA4KoOa-43NRpg4HmPxiXte5-6LneBOR94Wss868wC1b_2yX2zCc1wQoZA3LNo-CRLnL4Yp5wY4Bbgyguv_9mfqXVYZykZnxumyGwVFD-Rub3KQ9d53Rf9tKcvRk9qxO2q8F2PKjeaUBG2xZtGwkWTMvSmwR1dKtkPUyPggOzbLoUG-6fxfo7D3NyL5qWCSN_7CkI-xlsRSLY1gTq-FqXvcpHeZbc8w";
5656+5757+ Token::from_jwt(token, &SignatureType::RS256(dec_key), &None, &None).unwrap()
5858+ }),
5959+ ),
6060+ ];
6161+6262+ for (name, decode) in tokens {
6363+ eprintln!("Testing {name}");
35643636- // NOTE(cole-h): check that we get a consistent iteration order when getting permissions for
3737- // caches -- this depends on the order of the fields in the token, but should otherwise be
3838- // consistent between iterations
3939- let mut was_ever_wrong = false;
4040- for _ in 0..=1_000 {
4141- // NOTE(cole-h): we construct a new Token every iteration in order to get different "random
4242- // state"
4343- let decoded = Token::from_jwt(token, &dec_key).unwrap();
4444- let perm_all_ci = decoded.get_permission_for_cache(&cache! { "all-ci-abc" });
6565+ // NOTE(cole-h): check that we get a consistent iteration order when getting permissions for
6666+ // caches -- this depends on the order of the fields in the token, but should otherwise be
6767+ // consistent between iterations
6868+ let mut was_ever_wrong = false;
6969+ for _ in 0..=1_000 {
7070+ // NOTE(cole-h): we construct a new Token every iteration in order to get different "random
7171+ // state"
7272+ let decoded = decode();
7373+ let perm_all_ci = decoded.get_permission_for_cache(&cache! { "all-ci-abc" });
45744646- // NOTE(cole-h): if the iteration order of the token is inconsistent, the permissions may be
4747- // retrieved from the `all-ci-*` pattern (which only allows writing/pushing), even though
4848- // the `all-*` pattern (which only allows reading/pulling) is specified first
4949- if perm_all_ci.require_pull().is_err() || perm_all_ci.require_push().is_ok() {
5050- was_ever_wrong = true;
7575+ // NOTE(cole-h): if the iteration order of the token is inconsistent, the permissions may be
7676+ // retrieved from the `all-ci-*` pattern (which only allows writing/pushing), even though
7777+ // the `all-*` pattern (which only allows reading/pulling) is specified first
7878+ if perm_all_ci.require_pull().is_err() || perm_all_ci.require_push().is_ok() {
7979+ was_ever_wrong = true;
8080+ }
5181 }
5252- }
5353- assert!(
5454- !was_ever_wrong,
5555- "Iteration order should be consistent to prevent random auth failures (and successes)"
5656- );
8282+ assert!(
8383+ !was_ever_wrong,
8484+ "Iteration order should be consistent to prevent random auth failures (and successes)"
8585+ );
57865858- let decoded = Token::from_jwt(token, &dec_key).unwrap();
8787+ let decoded = decode();
59886060- let perm_rw = decoded.get_permission_for_cache(&cache! { "cache-rw" });
8989+ let perm_rw = decoded.get_permission_for_cache(&cache! { "cache-rw" });
61906262- assert!(perm_rw.pull);
6363- assert!(perm_rw.push);
6464- assert!(!perm_rw.delete);
6565- assert!(!perm_rw.create_cache);
9191+ assert!(perm_rw.pull);
9292+ assert!(perm_rw.push);
9393+ assert!(!perm_rw.delete);
9494+ assert!(!perm_rw.create_cache);
66956767- assert!(perm_rw.require_pull().is_ok());
6868- assert!(perm_rw.require_push().is_ok());
6969- assert!(perm_rw.require_delete().is_err());
7070- assert!(perm_rw.require_create_cache().is_err());
9696+ assert!(perm_rw.require_pull().is_ok());
9797+ assert!(perm_rw.require_push().is_ok());
9898+ assert!(perm_rw.require_delete().is_err());
9999+ assert!(perm_rw.require_create_cache().is_err());
711007272- let perm_ro = decoded.get_permission_for_cache(&cache! { "cache-ro" });
101101+ let perm_ro = decoded.get_permission_for_cache(&cache! { "cache-ro" });
731027474- assert!(perm_ro.pull);
7575- assert!(!perm_ro.push);
7676- assert!(!perm_ro.delete);
7777- assert!(!perm_ro.create_cache);
103103+ assert!(perm_ro.pull);
104104+ assert!(!perm_ro.push);
105105+ assert!(!perm_ro.delete);
106106+ assert!(!perm_ro.create_cache);
781077979- assert!(perm_ro.require_pull().is_ok());
8080- assert!(perm_ro.require_push().is_err());
8181- assert!(perm_ro.require_delete().is_err());
8282- assert!(perm_ro.require_create_cache().is_err());
108108+ assert!(perm_ro.require_pull().is_ok());
109109+ assert!(perm_ro.require_push().is_err());
110110+ assert!(perm_ro.require_delete().is_err());
111111+ assert!(perm_ro.require_create_cache().is_err());
831128484- let perm_team = decoded.get_permission_for_cache(&cache! { "team-xyz" });
113113+ let perm_team = decoded.get_permission_for_cache(&cache! { "team-xyz" });
851148686- assert!(perm_team.pull);
8787- assert!(perm_team.push);
8888- assert!(!perm_team.delete);
8989- assert!(perm_team.create_cache);
115115+ assert!(perm_team.pull);
116116+ assert!(perm_team.push);
117117+ assert!(!perm_team.delete);
118118+ assert!(perm_team.create_cache);
901199191- assert!(perm_team.require_pull().is_ok());
9292- assert!(perm_team.require_push().is_ok());
9393- assert!(perm_team.require_delete().is_err());
9494- assert!(perm_team.require_create_cache().is_ok());
120120+ assert!(perm_team.require_pull().is_ok());
121121+ assert!(perm_team.require_push().is_ok());
122122+ assert!(perm_team.require_delete().is_err());
123123+ assert!(perm_team.require_create_cache().is_ok());
951249696- assert!(!decoded
9797- .get_permission_for_cache(&cache! { "forbidden-cache" })
9898- .can_discover());
125125+ assert!(!decoded
126126+ .get_permission_for_cache(&cache! { "forbidden-cache" })
127127+ .can_discover());
128128+ }
99129}