Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions v-api-param/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,16 @@ impl StringParam {
///
/// For inline values, returns the value directly.
/// For path-based values, reads the file contents and trims trailing whitespace.
pub fn resolve(&self) -> Result<SecretString, ParamResolutionError> {
pub fn resolve(&self, base: Option<PathBuf>) -> Result<SecretString, ParamResolutionError> {
match self {
StringParam::Inline(value) => Ok(value.clone()),
StringParam::FromPath { path } => {
let content = std::fs::read_to_string(path).map_err(|source| {
let path = if let Some(base) = base {
base.join(path)
} else {
path.clone()
};
let content = std::fs::read_to_string(&path).map_err(|source| {
ParamResolutionError::FileRead {
path: path.display().to_string(),
source,
Expand Down Expand Up @@ -93,7 +98,7 @@ mod tests {
#[test]
fn test_inline_value() {
let param = StringParam::Inline("my-param".to_string().into());
assert_eq!(param.resolve().unwrap().expose_secret(), "my-param");
assert_eq!(param.resolve(None).unwrap().expose_secret(), "my-param");
}

#[test]
Expand All @@ -104,7 +109,23 @@ mod tests {
let param = StringParam::FromPath {
path: file.path().to_path_buf(),
};
assert_eq!(param.resolve().unwrap().expose_secret(), "file-param");
assert_eq!(param.resolve(None).unwrap().expose_secret(), "file-param");
}

#[test]
fn test_from_path_with_base() {
let mut file = NamedTempFile::new().unwrap();
write!(file, "file-param").unwrap();

let param = StringParam::FromPath {
path: PathBuf::from(file.path().file_name().unwrap()),
};
let base_path = std::env::temp_dir();

assert_eq!(
param.resolve(Some(base_path)).unwrap().expose_secret(),
"file-param"
);
}

#[test]
Expand All @@ -116,15 +137,15 @@ mod tests {
let param = StringParam::FromPath {
path: file.path().to_path_buf(),
};
assert_eq!(param.resolve().unwrap().expose_secret(), "file-param");
assert_eq!(param.resolve(None).unwrap().expose_secret(), "file-param");
}

#[test]
fn test_from_path_file_not_found() {
let param = StringParam::FromPath {
path: PathBuf::from("/nonexistent/path"),
};
let result = param.resolve();
let result = param.resolve(None);
assert!(matches!(result, Err(ParamResolutionError::FileRead { .. })));
}

Expand All @@ -139,7 +160,7 @@ mod tests {

let config: Config = toml::from_str(toml).unwrap();
assert_eq!(
config.key.resolve().unwrap().expose_secret(),
config.key.resolve(None).unwrap().expose_secret(),
"inline-value"
);
}
Expand All @@ -157,6 +178,9 @@ mod tests {
}

let config: Config = toml::from_str(&toml).unwrap();
assert_eq!(config.key.resolve().unwrap().expose_secret(), "path-value");
assert_eq!(
config.key.resolve(None).unwrap().expose_secret(),
"path-value"
);
}
}
52 changes: 9 additions & 43 deletions v-api/src/authn/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,25 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::{fmt::Debug, sync::Arc};

use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use chrono::{DateTime, Utc};
use jsonwebtoken::{
decode, decode_header,
jwk::{
AlgorithmParameters, CommonParameters, Jwk, KeyAlgorithm, PublicKeyUse, RSAKeyParameters,
RSAKeyType,
},
jwk::{AlgorithmParameters, Jwk},
Algorithm, DecodingKey, Header, Validation,
};
use newtype_uuid::TypedUuid;
use rsa::traits::PublicKeyParts;
use serde::{Deserialize, Serialize};
use tap::TapFallible;
use std::{fmt::Debug, sync::Arc};
use thiserror::Error;
use tracing::instrument;
use v_model::{AccessTokenId, UserId, UserProviderId};

use crate::{config::AsymmetricKey, context::VContext, permissions::VAppPermission};
use crate::{authn::Signer, context::VContext, permissions::VAppPermission};

use super::{Signer, SigningKeyError};
use super::SigningKeyError;

pub static DEFAULT_JWT_EXPIRATION: i64 = 3600;

#[derive(Debug, Error)]
pub enum JwtError {
Expand Down Expand Up @@ -153,20 +149,15 @@ pub struct JwtSigner {
#[allow(dead_code)]
header: Header,
encoded_header: String,
signer: Arc<dyn Signer>,
signer: Arc<Signer>,
}

impl JwtSigner {
pub fn new(key: &AsymmetricKey) -> Result<Self, JwtSignerError> {
pub fn new(signer: Arc<Signer>) -> Result<Self, JwtSignerError> {
let mut header = Header::new(Algorithm::RS256);
header.kid = Some(key.kid().to_string());
header.kid = Some(signer.kid.clone());
let encoded_header = to_base64_json(&header)?;

let signer = key
.as_signer()
.map_err(JwtSignerError::InvalidKey)
.tap_err(|err| tracing::error!(?err, "Unable to construct signer for JWT key"))?;

Ok(Self {
header,
encoded_header,
Expand Down Expand Up @@ -200,31 +191,6 @@ impl JwtSigner {
}
}

impl AsymmetricKey {
pub fn as_jwk(&self) -> Result<Jwk, JwtSignerError> {
let key_id = self.kid();
let public_key = self.public_key().map_err(JwtSignerError::InvalidKey)?;

Ok(Jwk {
common: CommonParameters {
public_key_use: Some(PublicKeyUse::Signature),
key_operations: None,
key_algorithm: Some(KeyAlgorithm::RS256),
key_id: Some(key_id.to_string()),
x509_chain: None,
x509_sha1_fingerprint: None,
x509_sha256_fingerprint: None,
x509_url: None,
},
algorithm: AlgorithmParameters::RSA(RSAKeyParameters {
key_type: RSAKeyType::RSA,
n: URL_SAFE_NO_PAD.encode(public_key.n().to_bytes_be()),
e: URL_SAFE_NO_PAD.encode(public_key.e().to_bytes_be()),
}),
})
}
}

fn to_base64_json<T>(value: &T) -> Result<String, serde_json::error::Error>
where
T: Serialize,
Expand Down
26 changes: 13 additions & 13 deletions v-api/src/authn/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use secrecy::{ExposeSecret, SecretSlice, SecretString};
use thiserror::Error;
use uuid::Uuid;

use crate::authn::Verifier;
use crate::authn::{Sign, Verify};

use super::{Signer, SigningKeyError};
use super::SigningKeyError;

pub struct RawKey {
clear: SecretSlice<u8>,
Expand Down Expand Up @@ -49,7 +49,7 @@ impl RawKey {
&self.clear.expose_secret()[0..16]
}

pub async fn sign(self, signer: &dyn Signer) -> Result<SignedKey, ApiKeyError> {
pub async fn sign(self, signer: &dyn Sign) -> Result<SignedKey, ApiKeyError> {
let signature = hex::encode(
signer
.sign(self.clear.expose_secret())
Expand All @@ -64,7 +64,7 @@ impl RawKey {

pub fn verify<T>(&self, verifier: &T, signature: &[u8]) -> Result<(), ApiKeyError>
where
T: Verifier,
T: Verify,
{
let signature = hex::decode(signature)?;
if verifier
Expand Down Expand Up @@ -143,15 +143,15 @@ mod tests {

use super::RawKey;
use crate::{
authn::{VerificationResult, Verifier},
authn::{VerificationResult, Verify},
util::tests::{mock_key, MockKey},
};

struct TestVerifier {
verifier: Arc<dyn Verifier>,
verifier: Arc<dyn Verify>,
}

impl Verifier for TestVerifier {
impl Verify for TestVerifier {
fn verify(&self, message: &[u8], signature: &[u8]) -> VerificationResult {
self.verifier.verify(message, signature)
}
Expand All @@ -161,13 +161,13 @@ mod tests {
async fn test_verifies_signature() {
let id = Uuid::new_v4();
let MockKey { signer, verifier } = mock_key("test");
let signer = signer.as_signer().unwrap();
let signer = signer.resolve_signer(None).unwrap();
let verifier = TestVerifier {
verifier: verifier.as_verifier().unwrap(),
verifier: Arc::new(verifier.resolve_verifier(None).await.unwrap()),
};

let raw = RawKey::generate::<8>(&id);
let signed = raw.sign(&*signer).await.unwrap();
let signed = raw.sign(&signer).await.unwrap();

let raw2 = RawKey::try_from(signed.key.expose_secret()).unwrap();

Expand All @@ -181,13 +181,13 @@ mod tests {
async fn test_generates_signatures() {
let id = Uuid::new_v4();
let MockKey { signer, .. } = mock_key("test");
let signer = signer.as_signer().unwrap();
let signer = signer.resolve_signer(None).unwrap();

let raw1 = RawKey::generate::<8>(&id);
let signed1 = raw1.sign(&*signer).await.unwrap();
let signed1 = raw1.sign(&signer).await.unwrap();

let raw2 = RawKey::generate::<8>(&id);
let signed2 = raw2.sign(&*signer).await.unwrap();
let signed2 = raw2.sign(&signer).await.unwrap();

assert_ne!(signed1.signature(), signed2.signature())
}
Expand Down
Loading
Loading