-
Notifications
You must be signed in to change notification settings - Fork 3
Implementation of CoSERV signing and verification #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| 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 { | ||
| 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a bug in |
||
| 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()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the
CoseSignerandCoseVerifiertraits, might it be possible to import those fromcorim-rsrather 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 fromcorim-rs? Just to avoid the duplication.