Skip to content
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

Implement did:key for RSA #309

Merged
merged 1 commit into from
Jan 12, 2022
Merged
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
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.