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
282 changes: 279 additions & 3 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ log = { version = "0.4.27", features = ["kv", "std"] }
regorus = "0.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = { version = "1.0.140", features = ["raw_value"] }
pem = "3.0.6"
picky-asn1-der = "0.5.4"
picky-asn1-x509 = "0.15.2"
elliptic-curve = { version = "0.13.8", features = ["arithmetic"] }
p256 = "0.13.2"
p384 = "0.13.1"
p521 = "0.13.3"

[dev-dependencies]
ciborium = "0.2.2"
Expand Down
5 changes: 5 additions & 0 deletions src/lib/cca/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ impl Scheme for CcaScheme {
) -> Result<Vec<Ect<'a>>, Error> {
let key_bytes: Vec<u8> = match trust_anchor {
CryptoKeyTypeChoice::Bytes(bytes) => Ok(bytes.into()),
CryptoKeyTypeChoice::PkixBase64Key(b64key) => {
let pem_bytes = b64key.as_bytes();
let jwk_string = crate::util::pem_spki_to_jwk_string(pem_bytes)?;
Ok(jwk_string.into())
}
_ => Err(Error::custom(format!(
"invalid trust anchor type: {:?}",
trust_anchor
Expand Down
144 changes: 144 additions & 0 deletions src/lib/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,147 @@ pub fn b64decode(v: &str) -> Result<Vec<u8>, Error> {
.decode(v)
.map_err(|e| Error::invalid_value(e.to_string(), "a base64-encoded string"))
}

// Helper function to convert PEM-encoded SubjectPublicKeyInfo into JWK
pub fn pem_spki_to_jwk_string(pem_bytes: &[u8]) -> Result<String, Error> {
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use elliptic_curve::{AffinePoint, CurveArithmetic, FieldBytesSize};
use elliptic_curve::{PublicKey as EcPublicKey, pkcs8::DecodePublicKey};
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
use pem;
use picky_asn1_x509::{
AlgorithmIdentifierParameters, EcParameters, PublicKey, SubjectPublicKeyInfo,
};
use serde::Serialize;

#[derive(Serialize, Debug)]
struct EcJwk {
pub kty: String,
pub crv: String,
pub x: String,
pub y: String,
}

// Returns a pair (X, Y) of Base64Url-encoded strings representing the curve points of the
// given public key.
fn extract_ec_point_x_y<C>(ec_pub: EcPublicKey<C>) -> Result<(String, String), Error>
where
C: CurveArithmetic,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,
{
let point = ec_pub.to_encoded_point(false);
if let Some(x) = point.x()
&& let Some(y) = point.y()
{
let xb64 = URL_SAFE_NO_PAD.encode(x);
let yb64 = URL_SAFE_NO_PAD.encode(y);
Ok((xb64, yb64))
} else {
Err(Error::Custom(format!(
"points x and y not populated in encoded point {:?}",
point
)))
}
}

let pem = pem::parse(pem_bytes).map_err(Error::custom)?;
match pem.tag() {
"PUBLIC KEY" => {
let contents = pem.contents();
let spki: SubjectPublicKeyInfo =
picky_asn1_der::from_bytes(contents).map_err(Error::custom)?;
match spki.subject_public_key {
PublicKey::Ec(_) => {
if let AlgorithmIdentifierParameters::Ec(EcParameters::NamedCurve(oid)) =
spki.algorithm.parameters()
{
let oid_str: String = oid.0.clone().into();
let (crv, x, y) = match oid_str.as_str() {
"1.2.840.10045.3.1.7" => {
let ec_pub: EcPublicKey<NistP256> =
EcPublicKey::from_public_key_der(contents).unwrap();
let (x, y) = extract_ec_point_x_y(ec_pub)?;
("P-256", x, y)
}
"1.3.132.0.34" => {
let ec_pub: EcPublicKey<NistP384> =
EcPublicKey::from_public_key_der(contents).unwrap();
let (x, y) = extract_ec_point_x_y(ec_pub)?;
("P-384", x, y)
}
"1.3.132.0.35" => {
let ec_pub: EcPublicKey<NistP521> =
EcPublicKey::from_public_key_der(contents).unwrap();
let (x, y) = extract_ec_point_x_y(ec_pub)?;
("P-521", x, y)
}
c => {
return Err(Error::Custom(format!(
"EC curve oid {} is not supported",
c
)));
}
};

let jwk = EcJwk {
kty: "EC".into(),
crv: crv.into(),
x,
y,
};

Ok(serde_json::to_string(&jwk).unwrap())
} else {
Err(Error::Custom(
"the EC public key does not contain EC parameters".to_string(),
))
}
}
_ => Err(Error::Custom("only EC keys supported".to_string())),
}
}
t => Err(Error::Custom(format!(
"PEM tag {} is not supported - must be PUBLIC KEY (SubjectPublicKeyInfo)",
t
))),
}
}

#[cfg(test)]
mod test {
use super::*;
use jsonwebtoken::jwk::Jwk;

fn pem_to_jwk(pem_bytes: &[u8], expected_jwk: &str) {
let jwk_converted = pem_spki_to_jwk_string(pem_bytes).unwrap();
let expected: Jwk =
serde_json::from_str(expected_jwk).expect("failed to deserialize expected JWK");
let actual: Jwk =
serde_json::from_str(&jwk_converted).expect("failed to deserialize actual JWK");
assert_eq!(actual, expected);
}

#[test]
fn pem_to_jwk_256() {
let pem_bytes = include_bytes!("../../test/keys/pkey_256.pem");
let expected_jwk = include_str!("../../test/keys/pkey_256.json");
pem_to_jwk(pem_bytes, expected_jwk);
}

#[test]
fn pem_to_jwk_384() {
let pem_bytes = include_bytes!("../../test/keys/pkey_384.pem");
let expected_jwk = include_str!("../../test/keys/pkey_384.json");
pem_to_jwk(pem_bytes, expected_jwk);
}

#[test]
fn pem_to_jwk_521() {
let pem_bytes = include_bytes!("../../test/keys/pkey_521.pem");
let expected_jwk = include_str!("../../test/keys/pkey_521.json");
pem_to_jwk(pem_bytes, expected_jwk);
}
}
6 changes: 6 additions & 0 deletions test/keys/pkey_256.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"crv": "P-256",
"kty": "EC",
"x": "tgbU5ewCDfgHLy1nxeMsgVvx9bnSic7qN74vRJ_Uwto",
"y": "BUhhTCrq8kJ87rghGBlvrZVi_CVtbWxmKTTNs5-bv9c"
}
4 changes: 4 additions & 0 deletions test/keys/pkey_256.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtgbU5ewCDfgHLy1nxeMsgVvx9bnS
ic7qN74vRJ/UwtoFSGFMKuryQnzuuCEYGW+tlWL8JW1tbGYpNM2zn5u/1w==
-----END PUBLIC KEY-----
6 changes: 6 additions & 0 deletions test/keys/pkey_384.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"crv": "P-384",
"kty": "EC",
"x": "mxOLLqA9CwehvuiSwmTXrPpsLv0b02UhUktcFrNSVGX4xw1SduiC485rga8dhszE",
"y": "LwNj95ahl1DM59pn-gm4iLc8km1J-CzLY4zEvVB13lYLC5KweOM17AasKfBtIV6y"
}
5 changes: 5 additions & 0 deletions test/keys/pkey_384.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEmxOLLqA9CwehvuiSwmTXrPpsLv0b02Uh
UktcFrNSVGX4xw1SduiC485rga8dhszELwNj95ahl1DM59pn+gm4iLc8km1J+CzL
Y4zEvVB13lYLC5KweOM17AasKfBtIV6y
-----END PUBLIC KEY-----
6 changes: 6 additions & 0 deletions test/keys/pkey_521.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"crv": "P-521",
"kty": "EC",
"x": "Abc8mbQKS796c-64-nisJ_uy0iVt8uoMsZICrmk8XvCZ1jt4kijzzYIHyTfxpfSIPg-LiADyyzaf7V2fPUpqDWv0",
"y": "ABqW6xEwm_JJqRF5ZnieolL6nBC18BfB-3k1raBt9INw6mHuCW9-hKoMmsC-LAv9eMiv-rbmuC3I-phVddwayiA7"
}
6 changes: 6 additions & 0 deletions test/keys/pkey_521.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBtzyZtApLv3pz7rj6eKwn+7LSJW3y
6gyxkgKuaTxe8JnWO3iSKPPNggfJN/Gl9Ig+D4uIAPLLNp/tXZ89SmoNa/QAGpbr
ETCb8kmpEXlmeJ6iUvqcELXwF8H7eTWtoG30g3DqYe4Jb36EqgyawL4sC/14yK/6
tua4Lcj6mFV13BrKIDs=
-----END PUBLIC KEY-----