Skip to content

Commit

Permalink
Move back to using JWK all the time for multikeys
Browse files Browse the repository at this point in the history
This is a temporary move to make it work easily with secp keys (see
tests in did:key)
  • Loading branch information
sbihel committed Jun 20, 2024
1 parent d6e2721 commit 812dbf8
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 103 deletions.
2 changes: 1 addition & 1 deletion crates/claims/crates/jwt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ chrono = { version = "0.4", features = ["serde", "wasmbind"] }

[dev-dependencies]
async-std.workspace = true
ssi-jws = { workspace = true, features = ["secp256r1"] }
ssi-jws = { workspace = true, features = ["secp256r1"] }
3 changes: 2 additions & 1 deletion crates/dids/methods/key/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ p256 = { workspace = true, optional = true, features = ["ecdsa"] }
[dev-dependencies]
ssi-jwk = { workspace = true, features = ["rsa"] }
ssi-verification-methods-core.workspace = true
ssi-verification-methods = { workspace = true, features = ["secp384r1"] }
ssi-claims = { workspace = true, features = ["w3c", "ed25519", "secp256r1", "secp256k1"] }
ssi-data-integrity.workspace = true
xsd-types.workspace = true
linked-data.workspace = true
json-syntax.workspace = true
serde = { workspace = true, features = ["derive"] }
async-std = { version = "1.9", features = ["attributes"] }
rand_chacha.workspace = true
rand_chacha.workspace = true
42 changes: 42 additions & 0 deletions crates/dids/methods/key/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,13 +447,15 @@ mod tests {
use resolution::Parameters;
use ssi_claims::{
data_integrity::{AnyInputContext, AnyInputSuiteOptions, AnySuite},
jws::JWSVerifier,
vc::JsonCredential,
Verifiable,
};
use ssi_data_integrity::{CryptographicSuite, ProofOptions as SuiteOptions};
use ssi_dids_core::{
did, resolution::Options, DIDResolver, VerificationMethodDIDResolver, DIDURL,
};
use ssi_verification_methods::AnyMethod;
use ssi_verification_methods_core::{ProofPurpose, ReferenceOrOwned, SingleSecretSigner};
use static_iref::uri;

Expand Down Expand Up @@ -830,4 +832,44 @@ mod tests {
// It should fail.
assert!(vc_bad_issuer.verify(&didkey).await.unwrap().is_err());
}

async fn fetch_jwk(jwk: JWK) {
let did = DIDKey::generate(&jwk).unwrap();
let resolver: VerificationMethodDIDResolver<_, AnyMethod> =
VerificationMethodDIDResolver::new(DIDKey);
let vm = DIDKey
.resolve_into_any_verification_method(&did)
.await
.unwrap()
.unwrap();
let public_jwk = resolver.fetch_public_jwk(Some(&vm.id)).await.unwrap();
assert_eq!(*public_jwk, jwk.to_public());
}

#[async_std::test]
async fn fetch_jwk_ed25519() {
let jwk = JWK::generate_ed25519().unwrap();
fetch_jwk(jwk).await;
}

#[async_std::test]
#[cfg(feature = "secp256k1")]
async fn fetch_jwk_secp256k1() {
let jwk = JWK::generate_secp256k1().unwrap();
fetch_jwk(jwk).await;
}

#[async_std::test]
#[cfg(feature = "secp256r1")]
async fn fetch_jwk_secp256r1() {
let jwk = JWK::generate_p256();
fetch_jwk(jwk).await;
}

#[async_std::test]
#[cfg(feature = "secp384r1")]
async fn fetch_jwk_secp384r1() {
let jwk = JWK::generate_p384().unwrap();
fetch_jwk(jwk).await;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,17 @@ impl TryFrom<GenericVerificationMethod> for EcdsaSecp256r1VerificationKey2019 {
.get("publicKeyMultibase")
.ok_or_else(|| InvalidVerificationMethod::missing_property("publicKeyMultibase"))?
.as_str()
.ok_or_else(|| InvalidVerificationMethod::invalid_property("publicKeyMultibase"))?
.ok_or_else(|| {
InvalidVerificationMethod::invalid_property(
"publicKeyMultibase is not a string",
)
})?
.parse()
.map_err(|_| InvalidVerificationMethod::invalid_property("publicKeyMultibase"))?,
.map_err(|e| {
InvalidVerificationMethod::invalid_property(&format!(
"publicKeyMultibase parsing failed because: {e}"
))
})?,
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,17 @@ impl TryFrom<GenericVerificationMethod> for Ed25519VerificationKey2020 {
.get("publicKeyMultibase")
.ok_or_else(|| InvalidVerificationMethod::missing_property("publicKeyMultibase"))?
.as_str()
.ok_or_else(|| InvalidVerificationMethod::invalid_property("publicKeyMultibase"))?
.ok_or_else(|| {
InvalidVerificationMethod::invalid_property(
"publicKeyMultibase is not a string",
)
})?
.parse()
.map_err(|_| InvalidVerificationMethod::invalid_property("publicKeyMultibase"))?,
.map_err(|e| {
InvalidVerificationMethod::invalid_property(&format!(
"publicKeyMultibase parsing failed because: {e}"
))
})?,
})
}
}
Expand Down
148 changes: 51 additions & 97 deletions crates/verification-methods/src/methods/w3c/multikey.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::{borrow::Cow, hash::Hash, str::FromStr};

use ed25519_dalek::Signer;
use iref::{Iri, IriBuf, UriBuf};
use rand_core::{CryptoRng, RngCore};
use rdf_types::{Interpretation, Vocabulary};
use serde::{Deserialize, Serialize};
use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity};
Expand Down Expand Up @@ -69,11 +67,11 @@ pub enum InvalidPublicKey {
#[error(transparent)]
Multicodec(#[from] ssi_multicodec::Error),

#[error("invalid key type")]
InvalidKeyType,
#[error("unsupported key type: `{0}`")]
UnsupportedKeyType(String),

#[error(transparent)]
Ed25519(#[from] ed25519_dalek::SignatureError),
Jwk(#[from] ssi_jwk::Error),
}

impl Multikey {
Expand All @@ -84,78 +82,41 @@ impl Multikey {
self.public_key.to_jwk()
}

pub fn generate_key_pair(
id: IriBuf,
controller: UriBuf,
csprng: &mut (impl RngCore + CryptoRng),
) -> (Self, ed25519_dalek::SigningKey) {
let key = ed25519_dalek::SigningKey::generate(csprng);
(
Self::from_public_key(id, controller, key.verifying_key()),
key,
)
}

pub fn from_public_key(
id: IriBuf,
controller: UriBuf,
public_key: ed25519_dalek::VerifyingKey,
) -> Self {
Self {
id,
controller,
public_key: PublicKey::encode(public_key),
}
}

pub fn sign_bytes<'a>(
pub fn sign_bytes(
&self,
secret_key: impl Into<SecretKeyRef<'a>>,
secret_key: &JWK,
signing_bytes: &[u8],
) -> Result<Vec<u8>, MessageSignatureError> {
match secret_key.into() {
SecretKeyRef::Ed25519(key_pair) => {
let signature = key_pair.sign(signing_bytes);
Ok(signature.to_bytes().to_vec())
}
SecretKeyRef::Jwk(secret_key) => {
let algorithm = ssi_jwk::Algorithm::EdDSA;
let key_algorithm = secret_key.algorithm.unwrap_or(algorithm);
if !algorithm.is_compatible_with(key_algorithm) {
return Err(MessageSignatureError::InvalidSecretKey);
}

ssi_jws::sign_bytes(algorithm, signing_bytes, secret_key)
.map_err(|_| MessageSignatureError::InvalidSecretKey)
}
let algorithm = self
.public_key
.decoded
.algorithm
.ok_or(MessageSignatureError::MissingAlgorithm)?;
let key_algorithm = secret_key.algorithm.unwrap_or(algorithm);
if !algorithm.is_compatible_with(key_algorithm) {
return Err(MessageSignatureError::InvalidSecretKey);
}
ssi_jws::sign_bytes(algorithm, signing_bytes, secret_key)
.map_err(|_| MessageSignatureError::InvalidSecretKey)
}

pub fn verify_bytes(
&self,
signing_bytes: &[u8],
signature: &[u8],
) -> Result<ProofValidity, ProofValidationError> {
let signature = ed25519_dalek::Signature::try_from(signature)
.map_err(|_| ProofValidationError::InvalidSignature)?;
Ok(self.public_key.verify(signing_bytes, &signature))
}
}

pub enum SecretKeyRef<'a> {
Ed25519(&'a ed25519_dalek::SigningKey),
Jwk(&'a JWK),
}

impl<'a> From<&'a ed25519_dalek::SigningKey> for SecretKeyRef<'a> {
fn from(value: &'a ed25519_dalek::SigningKey) -> Self {
Self::Ed25519(value)
}
}

impl<'a> From<&'a JWK> for SecretKeyRef<'a> {
fn from(value: &'a JWK) -> Self {
Self::Jwk(value)
let algorithm = self
.public_key
.decoded
.algorithm
.ok_or(ProofValidationError::MissingAlgorithm)?;
Ok(ssi_jws::verify_bytes(
algorithm,
signing_bytes,
&self.public_key.decoded,
signature,
)
.map_err(|_| InvalidProof::Signature))
}
}

Expand Down Expand Up @@ -220,9 +181,17 @@ impl TryFrom<GenericVerificationMethod> for Multikey {
.get("publicKeyMultibase")
.ok_or_else(|| InvalidVerificationMethod::missing_property("publicKeyMultibase"))?
.as_str()
.ok_or_else(|| InvalidVerificationMethod::invalid_property("publicKeyMultibase"))?
.ok_or_else(|| {
InvalidVerificationMethod::invalid_property(
"publicKeyMultibase is not a string",
)
})?
.parse()
.map_err(|_| InvalidVerificationMethod::invalid_property("publicKeyMultibase"))?,
.map_err(|e| {
InvalidVerificationMethod::invalid_property(&format!(
"publicKeyMultibase parsing failed because: {e}"
))
})?,
})
}
}
Expand All @@ -233,49 +202,34 @@ pub struct PublicKey {
encoded: MultibaseBuf,

/// Decoded public key.
decoded: ed25519_dalek::VerifyingKey,
decoded: JWK,
}

impl PublicKey {
pub fn encode(decoded: ed25519_dalek::VerifyingKey) -> Self {
let multi_encoded =
MultiEncodedBuf::encode(ssi_multicodec::ED25519_PUB, decoded.as_bytes());

Self {
encoded: MultibaseBuf::encode(multibase::Base::Base58Btc, multi_encoded.as_bytes()),
decoded,
}
}

pub fn decode(encoded: MultibaseBuf) -> Result<Self, InvalidPublicKey> {
let pk_multi_encoded = MultiEncodedBuf::new(encoded.decode()?.1)?;

let (pk_codec, pk_data) = pk_multi_encoded.parts();
if pk_codec == ssi_multicodec::ED25519_PUB {
let decoded = ed25519_dalek::VerifyingKey::try_from(pk_data)?;
Ok(Self { encoded, decoded })
} else {
Err(InvalidPublicKey::InvalidKeyType)
}
let decoded = match pk_codec {
#[cfg(feature = "ed25519")]
ssi_multicodec::ED25519_PUB => ssi_jwk::ed25519_parse(pk_data)?,
#[cfg(feature = "secp256k1")]
ssi_multicodec::SECP256K1_PUB => ssi_jwk::secp256k1_parse(pk_data)?,
#[cfg(feature = "secp256r1")]
ssi_multicodec::P256_PUB => ssi_jwk::p256_parse(pk_data)?,
#[cfg(feature = "secp384r1")]
ssi_multicodec::P384_PUB => ssi_jwk::p384_parse(pk_data)?,
c => return Err(InvalidPublicKey::UnsupportedKeyType(format!("{c:#x}")))?,
};
Ok(Self { encoded, decoded })
}

pub fn encoded(&self) -> &Multibase {
&self.encoded
}

pub fn decoded(&self) -> &ed25519_dalek::VerifyingKey {
&self.decoded
}

pub fn to_jwk(&self) -> JWK {
self.decoded.into()
}

pub fn verify(&self, data: &[u8], signature: &ed25519_dalek::Signature) -> ProofValidity {
use ed25519_dalek::Verifier;
self.decoded
.verify(data, signature)
.map_err(|_| InvalidProof::Signature)
self.decoded.clone()
}
}

Expand Down

0 comments on commit 812dbf8

Please sign in to comment.