use super::{Jwk, JwkError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeyCodec {
Secp256k1,
X25519,
Ed25519,
P256,
Rsa,
}
impl KeyCodec {
pub const fn code(self) -> u64 {
match self {
Self::Secp256k1 => 0xe7,
Self::X25519 => 0xec,
Self::Ed25519 => 0xed,
Self::P256 => 0x1200,
Self::Rsa => 0x1205,
}
}
pub const fn name(self) -> &'static str {
match self {
Self::Secp256k1 => "secp256k1-pub",
Self::X25519 => "x25519-pub",
Self::Ed25519 => "ed25519-pub",
Self::P256 => "p256-pub",
Self::Rsa => "rsa-pub",
}
}
pub const fn from_code(code: u64) -> Option<Self> {
Some(match code {
0xe7 => Self::Secp256k1,
0xec => Self::X25519,
0xed => Self::Ed25519,
0x1200 => Self::P256,
0x1205 => Self::Rsa,
_ => return None,
})
}
pub const fn key_len(self) -> Option<usize> {
match self {
Self::Secp256k1 | Self::P256 => Some(33),
Self::X25519 | Self::Ed25519 => Some(32),
Self::Rsa => None,
}
}
const fn key_name(self) -> &'static str {
match self {
Self::Secp256k1 => "secp256k1",
Self::X25519 => "X25519",
Self::Ed25519 => "Ed25519",
Self::P256 => "P-256",
Self::Rsa => "RSA",
}
}
}
impl Jwk {
pub fn from_multikey(multikey: &str) -> Result<Self, JwkError> {
let (codec, raw) = decode(multikey)?;
if let Some(expected) = codec.key_len()
&& raw.len() != expected
{
return Err(JwkError::InvalidKeyLength {
key: codec.key_name(),
expected,
actual: raw.len(),
});
}
match codec {
KeyCodec::Ed25519 => Self::from_ed25519_public(&raw),
KeyCodec::X25519 => Self::from_x25519_public(&raw),
KeyCodec::P256 => Self::from_p256_public(&raw),
#[cfg(feature = "secp256k1")]
KeyCodec::Secp256k1 => Self::from_secp256k1_public(&raw),
#[cfg(not(feature = "secp256k1"))]
KeyCodec::Secp256k1 => Err(JwkError::FeatureDisabled {
key: "secp256k1",
feature: "secp256k1",
}),
KeyCodec::Rsa => Self::from_pkcs1_der(&raw),
}
}
pub fn to_multikey(&self) -> Result<String, JwkError> {
let (codec, raw) = match (self.kty(), self.crv()) {
("OKP", Some("Ed25519")) => (KeyCodec::Ed25519, self.ed25519_public_bytes()?.to_vec()),
("OKP", Some("X25519")) => (KeyCodec::X25519, self.x25519_public_bytes()?.to_vec()),
("EC", Some("P-256")) => {
use p256::elliptic_curve::sec1::ToSec1Point as _;
let point = self.p256_public_key()?.to_sec1_point(true);
(KeyCodec::P256, point.as_bytes().to_vec())
}
#[cfg(feature = "secp256k1")]
("EC", Some("secp256k1")) => {
use k256::elliptic_curve::sec1::ToSec1Point as _;
let point = self.secp256k1_public_key()?.to_sec1_point(true);
(KeyCodec::Secp256k1, point.as_bytes().to_vec())
}
("RSA", _) => (KeyCodec::Rsa, self.to_pkcs1_der()?),
_ => return Err(self.unsupported()),
};
Ok(encode(codec, &raw))
}
}
pub(crate) fn encode(codec: KeyCodec, raw: &[u8]) -> String {
let mut bytes = varint::encode(codec.code());
bytes.extend_from_slice(raw);
format!("z{}", bs58::encode(bytes).into_string())
}
pub(crate) fn decode(multikey: &str) -> Result<(KeyCodec, Vec<u8>), JwkError> {
let Some(base58) = multikey.strip_prefix('z') else {
return Err(JwkError::InvalidMultikey(
"expected the base58btc multibase prefix `z`".to_owned(),
));
};
let bytes = bs58::decode(base58)
.into_vec()
.map_err(|e| JwkError::InvalidMultikey(format!("invalid base58btc: {e}")))?;
let (code, header_len) = varint::decode(&bytes)?;
let codec = KeyCodec::from_code(code).ok_or(JwkError::UnsupportedMulticodec(code))?;
Ok((codec, bytes[header_len..].to_vec()))
}
mod varint {
use crate::jose::JwkError;
const MAX_LEN: usize = 9;
pub(super) fn encode(mut value: u64) -> Vec<u8> {
let mut out = Vec::with_capacity(2);
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
if value == 0 {
out.push(byte);
return out;
}
out.push(byte | 0x80);
}
}
pub(super) fn decode(bytes: &[u8]) -> Result<(u64, usize), JwkError> {
let mut value = 0u64;
for (i, &byte) in bytes.iter().enumerate().take(MAX_LEN) {
value |= u64::from(byte & 0x7f) << (7 * i);
if byte & 0x80 == 0 {
if i > 0 && byte == 0 {
return Err(JwkError::InvalidMultikey(
"multicodec header is not minimally encoded".to_owned(),
));
}
return Ok((value, i + 1));
}
}
Err(JwkError::InvalidMultikey(
"multicodec header is truncated or too long".to_owned(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn varint_round_trips_multicodec_headers() {
assert_eq!(varint::encode(0xed), [0xed, 0x01]);
assert_eq!(varint::encode(0x1200), [0x80, 0x24]);
assert_eq!(varint::encode(0x1205), [0x85, 0x24]);
assert_eq!(varint::encode(0x7f), [0x7f]);
for code in [0u64, 1, 0x7f, 0x80, 0xed, 0x1200, 0x1205, u64::MAX >> 1] {
let encoded = varint::encode(code);
assert_eq!(varint::decode(&encoded).unwrap(), (code, encoded.len()));
}
assert!(varint::decode(&[0x80]).is_err(), "truncated");
assert!(varint::decode(&[0xed, 0x80, 0x00]).is_err(), "not minimal");
assert!(varint::decode(&[0x80; 10]).is_err(), "too long");
}
}