Skip to content

Commit

Permalink
crypto: serialize keys with alg-prefixed hex strings
Browse files Browse the repository at this point in the history
add `from_bytes_string()` and `to_bytes_string()` on `PublicKey` and `PrivateKey`, which look like the datalog syntax (`ed25519/<hex bytes>`), so that the key algorithm is explicit.

This will avoid confusions at program boundaries, when the keys must be read from strings (eg in biscuit-cli or when reading keys from environment variables)
  • Loading branch information
divarvel committed Jan 17, 2025
1 parent f4473ee commit 018294f
Showing 1 changed file with 129 additions and 0 deletions.
129 changes: 129 additions & 0 deletions biscuit-auth/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ impl PrivateKey {
hex::encode(self.to_bytes())
}

/// serializes to an hex-encoded string, prefixed with the key algorithm
pub fn to_bytes_string(&self) -> String {
let algorithm = match self.algorithm() {
schema::public_key::Algorithm::Ed25519 => "ed25519",
schema::public_key::Algorithm::Secp256r1 => "secp256r1",
};
format!("{algorithm}/{}", self.to_bytes_hex())
}

/// deserializes from a byte array
pub fn from_bytes(bytes: &[u8], algorithm: Algorithm) -> Result<Self, error::Format> {
match algorithm {
Expand All @@ -152,6 +161,20 @@ impl PrivateKey {
Self::from_bytes(&bytes, algorithm)
}

/// deserializes from a string, prefixed with the algorithm
pub fn from_string(str: &str) -> Result<Self, error::Format> {
match str.split_once('/') {
Some(("ed25519", bytes)) => Self::from_bytes_hex(bytes, Algorithm::Ed25519),
Some(("secp256r1", bytes)) => Self::from_bytes_hex(bytes, Algorithm::Secp256r1),
Some((alg, _)) => Err(error::Format::InvalidKey(format!(

Check warning on line 169 in biscuit-auth/src/crypto/mod.rs

View check run for this annotation

Codecov / codecov/patch

biscuit-auth/src/crypto/mod.rs#L169

Added line #L169 was not covered by tests
"Unsupported key algorithm {alg}"
))),
None => Err(error::Format::InvalidKey(
"Missing key algorithm".to_string(),

Check warning on line 173 in biscuit-auth/src/crypto/mod.rs

View check run for this annotation

Codecov / codecov/patch

biscuit-auth/src/crypto/mod.rs#L172-L173

Added lines #L172 - L173 were not covered by tests
)),
}
}

/// returns the matching public key
pub fn public(&self) -> PublicKey {
match self {
Expand Down Expand Up @@ -189,6 +212,15 @@ impl PublicKey {
hex::encode(self.to_bytes())
}

/// serializes to an hex-encoded string, prefixed with the key algorithm
pub fn to_bytes_string(&self) -> String {
let algorithm = match self.algorithm() {
schema::public_key::Algorithm::Ed25519 => "ed25519",
schema::public_key::Algorithm::Secp256r1 => "secp256r1",
};
format!("{algorithm}/{}", self.to_bytes_hex())
}

/// deserializes from a byte array
pub fn from_bytes(bytes: &[u8], algorithm: Algorithm) -> Result<Self, error::Format> {
match algorithm {
Expand All @@ -203,6 +235,20 @@ impl PublicKey {
Self::from_bytes(&bytes, algorithm)
}

/// deserializes from a string, prefixed with the algorithm
pub fn from_string(str: &str) -> Result<Self, error::Format> {
match str.split_once('/') {
Some(("ed25519", bytes)) => Self::from_bytes_hex(bytes, Algorithm::Ed25519),
Some(("secp256r1", bytes)) => Self::from_bytes_hex(bytes, Algorithm::Secp256r1),
Some((alg, _)) => Err(error::Format::InvalidKey(format!(

Check warning on line 243 in biscuit-auth/src/crypto/mod.rs

View check run for this annotation

Codecov / codecov/patch

biscuit-auth/src/crypto/mod.rs#L243

Added line #L243 was not covered by tests
"Unsupported key algorithm {alg}"
))),
None => Err(error::Format::InvalidKey(
"Missing key algorithm".to_string(),

Check warning on line 247 in biscuit-auth/src/crypto/mod.rs

View check run for this annotation

Codecov / codecov/patch

biscuit-auth/src/crypto/mod.rs#L246-L247

Added lines #L246 - L247 were not covered by tests
)),
}
}

pub fn from_proto(key: &schema::PublicKey) -> Result<Self, error::Format> {
if key.algorithm == schema::public_key::Algorithm::Ed25519 as i32 {
Ok(PublicKey::Ed25519(ed25519::PublicKey::from_bytes(
Expand Down Expand Up @@ -592,3 +638,86 @@ impl TokenNext {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn roundtrip_from_string() {
let ed_root = KeyPair::new_with_algorithm(Algorithm::Ed25519);
assert_eq!(
ed_root.public(),
PublicKey::from_string(&ed_root.public().to_bytes_string()).unwrap()
);
assert_eq!(
ed_root.private().to_bytes(),
PrivateKey::from_string(&ed_root.private().to_bytes_string())
.unwrap()
.to_bytes()
);
let p256_root = KeyPair::new_with_algorithm(Algorithm::Secp256r1);
assert_eq!(
p256_root.public(),
PublicKey::from_string(&p256_root.public().to_bytes_string()).unwrap()
);
assert_eq!(
p256_root.private().to_bytes(),
PrivateKey::from_string(&p256_root.private().to_bytes_string())
.unwrap()
.to_bytes()
)
}

#[test]
fn parsing_ed25519() {
let private_ed = PrivateKey::from_bytes_hex(
"bf6065d753c4a2c679dcd28828ac625c6c713efee2d4dd4b9c9ff3c9a2b2f966",
Algorithm::Ed25519,
)
.unwrap();

assert_eq!(
private_ed.to_bytes_string(),
"ed25519/bf6065d753c4a2c679dcd28828ac625c6c713efee2d4dd4b9c9ff3c9a2b2f966".to_string()
);

let public_ed = PublicKey::from_bytes_hex(
"eb396fa7a681c614fefc5bd8d1fa0383f30a8c562a99d8e8a830286e844be074",
Algorithm::Ed25519,
)
.unwrap();

assert_eq!(
public_ed.to_bytes_string(),
"ed25519/eb396fa7a681c614fefc5bd8d1fa0383f30a8c562a99d8e8a830286e844be074".to_string()
);
}

#[test]
fn parsing_secp256r1() {
let private_p256 = PrivateKey::from_bytes_hex(
"4e85237ab258ca7d53051073dd6c1e501ea4699f2fed6b0f5d399dc2a5f7d38f",
Algorithm::Secp256r1,
)
.unwrap();

assert_eq!(
private_p256.to_bytes_string(),
"secp256r1/4e85237ab258ca7d53051073dd6c1e501ea4699f2fed6b0f5d399dc2a5f7d38f"
.to_string()
);

let public_p256 = PublicKey::from_bytes_hex(
"03b6d94743381d3452f11a1aec8d73b0a899827d48be2e4387112e4d2faacfcc29",
Algorithm::Secp256r1,
)
.unwrap();

assert_eq!(
public_p256.to_bytes_string(),
"secp256r1/03b6d94743381d3452f11a1aec8d73b0a899827d48be2e4387112e4d2faacfcc29"
.to_string()
);
}
}

0 comments on commit 018294f

Please sign in to comment.