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
33 changes: 22 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ corim-rs = { git = "https://github.com/veraison/corim-rs" }
chrono = "0.4.42"
cmw = "0.1.0"
base64 = "0.22.1"

[features]
openssl = ["corim-rs/openssl"]
226 changes: 226 additions & 0 deletions src/coserv/crypto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: Apache-2.0

use super::{CoseAlgorithm, CoservError};

/// Interface for COSE signer.
pub trait CoseSigner {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the CoseSigner and CoseVerifier traits, might it be possible to import those from corim-rs rather than re-define them here? The definitions are not identical, but given @thomas-fossati 's other comment on whether the algorithm field is necessary, I wonder if that makes it easier to just re-use the bits from corim-rs? Just to avoid the duplication.

fn algorithm(&self) -> CoseAlgorithm;

fn sign(&self, data: &[u8]) -> Result<Vec<u8>, CoservError>;
}

/// Interface for COSE verifier.
pub trait CoseVerifier {
fn algorithm(&self) -> CoseAlgorithm;

fn verify_signature(&self, sig: &[u8], data: &[u8]) -> Result<(), CoservError>;
}

#[cfg(feature = "openssl")]
mod openssl {
use super::{CoseSigner, CoseVerifier};
use crate::coserv::{CoseAlgorithm, CoservError};
use corim_rs::CoseKey;
use corim_rs::{CoseSigner as _, CoseVerifier as _};
use std::collections::HashMap;

/// Wrapper around [corim_rs::OpensslSigner] that
/// simplifies signing.
pub struct OpensslSigner {
inner: corim_rs::OpensslSigner,
algorithm: CoseAlgorithm,
}

/// Wrapper around [corim_rs::OpensslSigner] that simplifies
/// verification.
// note: only supports EC keys because corim-rs only supports EC keys
pub struct OpensslVerifier {
inner: corim_rs::OpensslSigner,
algorithm: CoseAlgorithm,
}

impl OpensslSigner {
/// Construct [OpensslSigner] from PEM private key
pub fn from_pem(bytes: &[u8], alg: CoseAlgorithm) -> Result<Self, CoservError> {
alg_ok(alg)?;
Ok(Self {
inner: corim_rs::OpensslSigner::private_key_from_pem(bytes)?,
algorithm: alg,
})
}

/// Construct [OpensslSigner] from JWK private key
pub fn from_jwk(bytes: &[u8]) -> Result<Self, CoservError> {
let bytes = fix_kty(bytes).map_err(|e| CoservError::SigningError(e.into()))?;
let key: CoseKey = serde_json::from_slice(bytes.as_slice())
.map_err(|e| CoservError::SigningError(e.into()))?;
Ok(Self {
inner: key.clone().into(),
algorithm: key
.alg
.ok_or(CoservError::custom("alg field missing in jwk"))?,
})
}
}

impl CoseSigner for OpensslSigner {
fn algorithm(&self) -> CoseAlgorithm {
self.algorithm
}

fn sign(&self, data: &[u8]) -> Result<Vec<u8>, CoservError> {
Ok(self.inner.sign(self.algorithm, data)?)
}
}

impl OpensslVerifier {
/// Construct [OpensslVerifier] from PEM public key
pub fn from_pem(bytes: &[u8], alg: CoseAlgorithm) -> Result<Self, CoservError> {
alg_ok(alg)?;
Ok(Self {
inner: corim_rs::OpensslSigner::public_key_from_pem(bytes)?,
algorithm: alg,
})
}

/// Construct [OpensslVerifier] from PEM private key
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment should say "from JWK public key" here

pub fn from_jwk(bytes: &[u8]) -> Result<Self, CoservError> {
let bytes = fix_kty(bytes).map_err(|e| CoservError::VerificationError(e.into()))?;
let mut key: CoseKey = serde_json::from_slice(bytes.as_slice())
.map_err(|e| CoservError::SigningError(e.into()))?;
key.d = None;
Ok(Self {
inner: key.clone().into(),
algorithm: key
.alg
.ok_or(CoservError::custom("alg field missing in jwk"))?,
})
}
}

impl CoseVerifier for OpensslVerifier {
fn algorithm(&self) -> CoseAlgorithm {
self.algorithm
}

fn verify_signature(&self, sig: &[u8], data: &[u8]) -> Result<(), CoservError> {
Ok(self.inner.verify_signature(self.algorithm, sig, data)?)
}
}

// only EC key is supported so, only these signing algorithms can be used.
fn alg_ok(alg: CoseAlgorithm) -> Result<(), CoservError> {
match alg {
CoseAlgorithm::ES256 | CoseAlgorithm::ES384 | CoseAlgorithm::ES512 => Ok(()),
other => Err(CoservError::custom(format!(
"unsupported COSE algorithm: {other}"
))),
}
}

// this function is a workaround for a bug in corim-rs:
// key type for elliptic curve keys is serialized as EC2
// instead of EC in corim-rs
fn fix_kty(bytes: &[u8]) -> Result<Vec<u8>, CoservError> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a bug in corim-rs. What's actually happening here is a format conversion from JWK to COSE key. I think this function needs to be called jwk_to_cose() (or something like that). The code itself might be fine as it is (certainly for a PoC), but please check the specs to make sure that no additional conversion steps are needed.

let mut val: HashMap<&str, &str> =
serde_json::from_slice(bytes).map_err(CoservError::custom)?;
let Some(ec) = val.remove("kty") else {
Err(CoservError::custom("key field is missing in jwk"))?
};
if ec != "EC" {
Err(CoservError::Custom(format!("unsupported kty: {ec}")))?
}
val.insert("kty", "EC2");
serde_json::to_vec(&val).map_err(CoservError::custom)
}
}

#[cfg(feature = "openssl")]
pub use openssl::*;

#[cfg(test)]
#[cfg(feature = "openssl")]
mod tests {
use super::*;
use crate::coserv::Coserv;

const COSERV: [u8; 158] = [
0xa3, 0x00, 0x63, 0x66, 0x6f, 0x6f, 0x01, 0xa4, 0x00, 0x02, 0x01, 0xa1, 0x01, 0x82, 0x81,
0xd9, 0x02, 0x30, 0x43, 0x00, 0x01, 0x02, 0x81, 0xd9, 0x02, 0x30, 0x43, 0x01, 0x02, 0x03,
0x02, 0xc0, 0x78, 0x19, 0x32, 0x30, 0x32, 0x35, 0x2d, 0x31, 0x30, 0x2d, 0x32, 0x37, 0x54,
0x31, 0x39, 0x3a, 0x31, 0x31, 0x3a, 0x33, 0x30, 0x2b, 0x30, 0x35, 0x3a, 0x33, 0x30, 0x03,
0x01, 0x02, 0xa2, 0x00, 0x82, 0xa2, 0x01, 0x81, 0xd9, 0x02, 0x30, 0x42, 0x00, 0x01, 0x02,
0x82, 0xbf, 0x01, 0xd9, 0x02, 0x30, 0x43, 0x00, 0x01, 0x02, 0xff, 0x81, 0xbf, 0x01, 0xbf,
0x0b, 0x63, 0x66, 0x6f, 0x6f, 0xff, 0xff, 0xa2, 0x01, 0x81, 0xd9, 0x02, 0x30, 0x42, 0x00,
0x01, 0x02, 0x82, 0xbf, 0x01, 0xd9, 0x02, 0x30, 0x43, 0x01, 0x02, 0x03, 0xff, 0x81, 0xbf,
0x01, 0xbf, 0x0b, 0x63, 0x66, 0x6f, 0x6f, 0xff, 0xff, 0x0a, 0xc0, 0x78, 0x19, 0x32, 0x30,
0x32, 0x35, 0x2d, 0x31, 0x31, 0x2d, 0x32, 0x31, 0x54, 0x31, 0x36, 0x3a, 0x30, 0x38, 0x3a,
0x35, 0x36, 0x2b, 0x30, 0x35, 0x3a, 0x33, 0x30,
];

const PRIV_PEM: &str = r#"
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIGcXyKllYJ/Ll0jUI9LfK/7uokvFibisW5lM8DZaRO+toAoGCCqGSM49
AwEHoUQDQgAE/gPssLIiLnF0XrTGU73XMKlTIk4QhU80ttXzJ7waTpoeCJsPxG2h
zMuUkHMOLrZxNpwxH004vyaHpF9TYTeXCQ==
-----END EC PRIVATE KEY-----
"#;

const PUB_PEM: &str = r#"
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/gPssLIiLnF0XrTGU73XMKlTIk4Q
hU80ttXzJ7waTpoeCJsPxG2hzMuUkHMOLrZxNpwxH004vyaHpF9TYTeXCQ==
-----END PUBLIC KEY-----
"#;

const PRIV_JWK: &str = r#"
{
"kty": "EC",
"crv": "P-256",
"alg": "ES256",
"d": "ZxfIqWVgn8uXSNQj0t8r_u6iS8WJuKxbmUzwNlpE760",
"x": "_gPssLIiLnF0XrTGU73XMKlTIk4QhU80ttXzJ7waTpo",
"y": "HgibD8RtoczLlJBzDi62cTacMR9NOL8mh6RfU2E3lwk"
}
"#;

const PUB_JWK: &str = r#"
{
"kty": "EC",
"crv": "P-256",
"alg": "ES256",
"x": "_gPssLIiLnF0XrTGU73XMKlTIk4QhU80ttXzJ7waTpo",
"y": "HgibD8RtoczLlJBzDi62cTacMR9NOL8mh6RfU2E3lwk"
}
"#;

#[test]
fn test_sign_verify_pem() {
let resp = Coserv::from_cbor(&COSERV[..]).unwrap();
let signer = OpensslSigner::from_pem(PRIV_PEM.as_bytes(), CoseAlgorithm::ES256).unwrap();
let signed_resp = resp.sign(&signer).unwrap();

let verifier = OpensslVerifier::from_pem(PUB_PEM.as_bytes(), CoseAlgorithm::ES256).unwrap();
let res = Coserv::verify_and_extract(&verifier, signed_resp.as_slice());
assert!(res.is_ok());
}

#[test]
fn test_sign_verify_jwk() {
let resp = Coserv::from_cbor(&COSERV[..]).unwrap();
let signer = OpensslSigner::from_jwk(PRIV_JWK.as_bytes()).unwrap();
let signed_resp = resp.sign(&signer).unwrap();

let verifier = OpensslVerifier::from_jwk(PUB_JWK.as_bytes()).unwrap();
let res = Coserv::verify_and_extract(&verifier, signed_resp.as_slice());
assert!(res.is_ok());
}

#[test]
fn test_non_ec_alg() {
let res1 = OpensslSigner::from_pem(PRIV_PEM.as_bytes(), CoseAlgorithm::RS512);
let res2 = OpensslVerifier::from_pem(PUB_PEM.as_bytes(), CoseAlgorithm::RS512);
assert!(res1.is_err());
assert!(res2.is_err());
}
}
Loading