Skip to content

Commit

Permalink
Implement did:key for RSA
Browse files Browse the repository at this point in the history
  • Loading branch information
clehner committed Jan 12, 2022
1 parent 7bdfa1f commit 5ebd115
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 3 deletions.
1 change: 1 addition & 0 deletions did-key/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
50 changes: 50 additions & 0 deletions did-key/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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 {
Expand Down Expand Up @@ -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]
{
Expand Down Expand Up @@ -316,6 +327,14 @@ impl DIDMethod for DIDKey {
_ => return None,
}
}
Params::RSA(ref params) => {
let der = simple_asn1::der_encode(&params.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)
Expand Down Expand Up @@ -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};
Expand Down
32 changes: 31 additions & 1 deletion src/der.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
56 changes: 54 additions & 2 deletions src/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use zeroize::Zeroize;

use crate::der::{
BitString, Ed25519PrivateKey, Ed25519PublicKey, Integer, OctetString, RSAPrivateKey,
RSAPublicKey,
RSAPublicKey, RSAPublicKeyFromASN1Error,
};
use crate::error::Error;

Expand Down Expand Up @@ -812,6 +812,54 @@ pub fn p256_parse(pk_bytes: &[u8]) -> Result<JWK, Error> {
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<Self, Self::Error> {
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<JWK, RsaX509PubParseError> {
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;
Expand Down Expand Up @@ -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]
Expand Down
Binary file added tests/rsa2048-2020-08-25-pk.der
Binary file not shown.

0 comments on commit 5ebd115

Please sign in to comment.