From 5ebd115ee130b282d79145afc02fe68ab8f85d2a Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Thu, 23 Sep 2021 17:51:31 -0400 Subject: [PATCH] Implement did:key for RSA --- did-key/Cargo.toml | 1 + did-key/src/lib.rs | 50 ++++++++++++++++++++++++++++ src/der.rs | 32 +++++++++++++++++- src/jwk.rs | 56 ++++++++++++++++++++++++++++++-- tests/rsa2048-2020-08-25-pk.der | Bin 0 -> 270 bytes 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 tests/rsa2048-2020-08-25-pk.der diff --git a/did-key/Cargo.toml b/did-key/Cargo.toml index f0f894c92..0fa77bb46 100644 --- a/did-key/Cargo.toml +++ b/did-key/Cargo.toml @@ -23,6 +23,7 @@ multibase = "0.8" k256 = { version = "0.8", optional = true, features = ["zeroize", "ecdsa"] } p256 = { version = "0.8", optional = true, features = ["zeroize", "ecdsa"] } serde_json = "1.0" +simple_asn1 = "^0.5.2" [dev-dependencies] serde = { version = "1.0", features = ["derive"] } diff --git a/did-key/src/lib.rs b/did-key/src/lib.rs index b228bcf0b..caaad46d7 100644 --- a/did-key/src/lib.rs +++ b/did-key/src/lib.rs @@ -13,6 +13,7 @@ use ssi::did_resolve::{ }; #[cfg(feature = "secp256r1")] use ssi::jwk::p256_parse; +use ssi::jwk::rsa_x509_pub_parse; #[cfg(feature = "secp256k1")] use ssi::jwk::secp256k1_parse; use ssi::jwk::{Base64urlUInt, OctetParams, Params, JWK}; @@ -21,6 +22,7 @@ const DID_KEY_ED25519_PREFIX: [u8; 2] = [0xed, 0x01]; const DID_KEY_SECP256K1_PREFIX: [u8; 2] = [0xe7, 0x01]; const DID_KEY_BLS12381_G2_PREFIX: [u8; 2] = [0xeb, 0x01]; const DID_KEY_P256_PREFIX: [u8; 2] = [0x80, 0x24]; +const DID_KEY_RSA_PREFIX: [u8; 2] = [0x85, 0x24]; #[derive(Error, Debug)] pub enum DIDKeyError { @@ -165,6 +167,15 @@ impl DIDResolver for DIDKey { None, None, ); + } else if data[0] == DID_KEY_RSA_PREFIX[0] && data[1] == DID_KEY_RSA_PREFIX[1] { + match rsa_x509_pub_parse(&data[2..]) { + Ok(jwk) => { + vm_type = "JsonWebKey2020".to_string(); + vm_type_iri = "https://w3id.org/security#JsonWebKey2020".to_string(); + jwk + } + Err(err) => return (ResolutionMetadata::from_error(&err.to_string()), None, None), + } } else if data[0] == DID_KEY_BLS12381_G2_PREFIX[0] && data[1] == DID_KEY_BLS12381_G2_PREFIX[1] { @@ -316,6 +327,14 @@ impl DIDMethod for DIDKey { _ => return None, } } + Params::RSA(ref params) => { + let der = simple_asn1::der_encode(¶ms.to_public()).ok()?; + "did:key:".to_string() + + &multibase::encode( + multibase::Base::Base58Btc, + [DID_KEY_RSA_PREFIX.to_vec(), der.to_vec()].concat(), + ) + } _ => return None, // _ => return Some(Err(DIDKeyError::UnsupportedKeyType)), }; Some(did) @@ -439,6 +458,37 @@ mod tests { assert_eq!(did1, did); } + #[async_std::test] + async fn from_did_key_rsa() { + let did = "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"; + let (res_meta, _doc, _doc_meta) = DIDKey + .resolve(did, &ResolutionInputMetadata::default()) + .await; + assert_eq!(res_meta.error, None); + + let vm = "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"; + let (res_meta, object, _meta) = + dereference(&DIDKey, &vm, &DereferencingInputMetadata::default()).await; + assert_eq!(res_meta.error, None); + let vm = match object { + Content::Object(Resource::VerificationMethod(vm)) => vm, + _ => unreachable!(), + }; + let key = vm.public_key_jwk.unwrap(); + eprintln!("key {}", serde_json::to_string_pretty(&key).unwrap()); + + let key_expected: JWK = serde_json::from_value(serde_json::json!({ + "kty": "RSA", + "e": "AQAB", + "n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ" + })) + .unwrap(); + assert_eq!(key, key_expected); + + let did1 = DIDKey.generate(&Source::Key(&key)).unwrap(); + assert_eq!(did1, did); + } + #[async_std::test] async fn credential_prove_verify_did_key() { use ssi::vc::{get_verification_method, Credential, Issuer, LinkedDataProofOptions, URI}; diff --git a/src/der.rs b/src/der.rs index bb9d43ac8..6ea0b3a9e 100644 --- a/src/der.rs +++ b/src/der.rs @@ -7,7 +7,7 @@ // https://tools.ietf.org/html/rfc8410 use num_bigint::{BigInt, Sign}; -use simple_asn1::{der_encode, ASN1Block, ASN1Class, ToASN1}; +use simple_asn1::{der_encode, ASN1Block, ASN1Class, ASN1DecodeErr, FromASN1, ToASN1}; use crate::error::Error; @@ -25,6 +25,7 @@ pub struct RSAPrivateKey { } #[derive(Debug, Clone)] +// https://datatracker.ietf.org/doc/html/rfc3447#appendix-A.1.1 pub struct RSAPublicKey { pub modulus: Integer, pub public_exponent: Integer, @@ -105,6 +106,35 @@ impl ToASN1 for RSAPublicKey { } } +#[derive(thiserror::Error, Debug)] +pub enum RSAPublicKeyFromASN1Error { + #[error("Expected single sequence")] + ExpectedSingleSequence, + #[error("Expected two integers")] + ExpectedTwoIntegers, + #[error("ASN1 decoding error: {0:?}")] + ASN1Decode(#[from] ASN1DecodeErr), +} + +impl FromASN1 for RSAPublicKey { + type Error = RSAPublicKeyFromASN1Error; + fn from_asn1(v: &[ASN1Block]) -> Result<(Self, &[ASN1Block]), Self::Error> { + let vec = match v { + [ASN1Block::Sequence(_, vec)] => vec, + _ => return Err(RSAPublicKeyFromASN1Error::ExpectedSingleSequence), + }; + let (n, e) = match vec.as_slice() { + [ASN1Block::Integer(_, n), ASN1Block::Integer(_, e)] => (n, e), + _ => return Err(RSAPublicKeyFromASN1Error::ExpectedTwoIntegers), + }; + let pk = Self { + modulus: Integer(n.clone()), + public_exponent: Integer(e.clone()), + }; + Ok((pk, &[])) + } +} + impl Ed25519PrivateKey { fn oid() -> ASN1Block { use simple_asn1::BigUint; diff --git a/src/jwk.rs b/src/jwk.rs index 4935ef223..609f3e8b1 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -6,7 +6,7 @@ use zeroize::Zeroize; use crate::der::{ BitString, Ed25519PrivateKey, Ed25519PublicKey, Integer, OctetString, RSAPrivateKey, - RSAPublicKey, + RSAPublicKey, RSAPublicKeyFromASN1Error, }; use crate::error::Error; @@ -812,6 +812,54 @@ pub fn p256_parse(pk_bytes: &[u8]) -> Result { Ok(jwk) } +#[derive(thiserror::Error, Debug)] +pub enum RSAParamsFromPublicKeyError { + #[error("RSA Public Key from ASN1 error: {0:?}")] + RSAPublicKeyFromASN1(RSAPublicKeyFromASN1Error), + #[error("Expected positive integer in RSA key")] + ExpectedPlus, +} + +impl TryFrom<&RSAPublicKey> for RSAParams { + type Error = RSAParamsFromPublicKeyError; + fn try_from(pk: &RSAPublicKey) -> Result { + let (sign, n) = pk.modulus.0.to_bytes_be(); + if sign != Sign::Plus { + return Err(RSAParamsFromPublicKeyError::ExpectedPlus); + } + let (sign, e) = pk.public_exponent.0.to_bytes_be(); + if sign != Sign::Plus { + return Err(RSAParamsFromPublicKeyError::ExpectedPlus); + } + Ok(RSAParams { + modulus: Some(Base64urlUInt(n)), + exponent: Some(Base64urlUInt(e)), + private_exponent: None, + first_prime_factor: None, + second_prime_factor: None, + first_prime_factor_crt_exponent: None, + second_prime_factor_crt_exponent: None, + first_crt_coefficient: None, + other_primes_info: None, + }) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum RsaX509PubParseError { + #[error("RSAPublicKey from ASN1: {0:?}")] + RSAPublicKeyFromASN1(#[from] RSAPublicKeyFromASN1Error), + #[error("RSA JWK params from RSAPublicKey: {0:?}")] + RSAParamsFromPublicKey(#[from] RSAParamsFromPublicKeyError), +} + +/// Parse a "RSA public key (X.509 encoded)" (multicodec) into a JWK. +pub fn rsa_x509_pub_parse(pk_bytes: &[u8]) -> Result { + let rsa_pk: RSAPublicKey = simple_asn1::der_decode(&pk_bytes)?; + let rsa_params = RSAParams::try_from(&rsa_pk)?; + Ok(JWK::from(Params::RSA(rsa_params))) +} + #[cfg(feature = "k256")] impl TryFrom<&ECParams> for k256::SecretKey { type Error = Error; @@ -943,13 +991,17 @@ mod tests { const RSA_JSON: &'static str = include_str!("../tests/rsa2048-2020-08-25.json"); const RSA_DER: &'static [u8] = include_bytes!("../tests/rsa2048-2020-08-25.der"); + const RSA_PK_DER: &'static [u8] = include_bytes!("../tests/rsa2048-2020-08-25-pk.der"); const ED25519_JSON: &'static str = include_str!("../tests/ed25519-2020-10-18.json"); #[test] - fn jwk_to_der_rsa() { + fn jwk_to_from_der_rsa() { let key: JWK = serde_json::from_str(RSA_JSON).unwrap(); let der = simple_asn1::der_encode(&key).unwrap(); assert_eq!(der, RSA_DER); + let rsa_pk: RSAPublicKey = simple_asn1::der_decode(RSA_PK_DER).unwrap(); + let rsa_params = RSAParams::try_from(&rsa_pk).unwrap(); + assert_eq!(key.to_public().params, Params::RSA(rsa_params)); } #[test] diff --git a/tests/rsa2048-2020-08-25-pk.der b/tests/rsa2048-2020-08-25-pk.der new file mode 100644 index 0000000000000000000000000000000000000000..a3591c519d6e152712d243b442f3943b767495cb GIT binary patch literal 270 zcmV+p0rCDYf&mHwf&l>lv9HhYB)qBdbJ3PMv|N{8$)1E?Sx662JrKn z^;MAK7=IhSBAcl6pVH-U%?>v+$7}xHfZ^xhh#o~a>Kny)E9p@$i}5m2ZUyv`HDvy7 z9wEPQhpp?jVER)($_CJ==4p}PF)eVmN@3cJr+fm8-;;popwsEv UByXGS3Dyj7Sdkb#0s{d60Yz+vQ~&?~ literal 0 HcmV?d00001