Skip to content

Commit

Permalink
Merge pull request #29 from ADORSYS-GIS/iss-28--keypair-jwk-coercion
Browse files Browse the repository at this point in the history
Implement conversion between KeyPair and Jwk
  • Loading branch information
francis-pouatcha authored Sep 7, 2023
2 parents e7ea4a5 + 930a23b commit a3f6cb3
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 2 deletions.
23 changes: 23 additions & 0 deletions did-utils/examples/dsa-verify-with-jwk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use did_utils::{
crypto::{ed25519::Ed25519KeyPair, traits::CoreSign},
didcore::Jwk,
};
use multibase::Base::Base64Url;

fn main() {
let jwk: Jwk = serde_json::from_str(
r#"{
"kty": "OKP",
"crv": "Ed25519",
"x": "tjOTPcs4OEMNrmn2ScYZDS-aCCbRFhJgaAmGnRsdmEo"
}"#,
)
.unwrap();

let payload = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
let signature = "2QH7Qrt8clEn4ETh9lgcGUyo26cJj1U8U0CBFQvgCWHe1dwXXXb16SzPTVNVGm-J6m6eALjWrxuJfmbApdoBAQ";
let signature = Base64Url.decode(signature).unwrap();

let keypair: Ed25519KeyPair = jwk.try_into().expect("ConversionError");
assert!(keypair.verify(payload, &signature).is_ok());
}
163 changes: 163 additions & 0 deletions did-utils/src/crypto/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use crate::{
crypto::{
ed25519::Ed25519KeyPair,
traits::{Error as CryptoError, Generate, KeyMaterial, BYTES_LENGTH_32},
x25519::X25519KeyPair,
},
didcore::Jwk,
};
use multibase::Base::Base64Url;

impl TryFrom<Ed25519KeyPair> for Jwk {
type Error = CryptoError;

fn try_from(keypair: Ed25519KeyPair) -> Result<Self, Self::Error> {
Ok(Jwk {
key_id: None,
key_type: String::from("OKP"),
curve: String::from("Ed25519"),
x: Some(Base64Url.encode(keypair.public_key_bytes()?)),
y: None,
d: Some(Base64Url.encode(keypair.private_key_bytes()?)),
})
}
}

impl TryFrom<Jwk> for Ed25519KeyPair {
type Error = CryptoError;

fn try_from(jwk: Jwk) -> Result<Self, Self::Error> {
if jwk.key_type != "OKP" {
return Err(CryptoError::Unsupported);
}

if jwk.curve != "Ed25519" {
return Err(CryptoError::InvalidCurve);
}

match jwk.d {
Some(secret_key) => {
let secret_key = base64url_to_bytes(&secret_key).map_err(|_| CryptoError::InvalidSecretKey)?;
Ed25519KeyPair::from_secret_key(&secret_key)
}
None => {
let public_key = jwk.x.ok_or(CryptoError::InvalidPublicKey)?;
let public_key = base64url_to_bytes(&public_key).map_err(|_| CryptoError::InvalidPublicKey)?;
Ed25519KeyPair::from_public_key(&public_key)
}
}
}
}

impl TryFrom<X25519KeyPair> for Jwk {
type Error = CryptoError;

fn try_from(keypair: X25519KeyPair) -> Result<Self, Self::Error> {
Ok(Jwk {
key_id: None,
key_type: String::from("OKP"),
curve: String::from("X25519"),
x: Some(Base64Url.encode(keypair.public_key_bytes()?)),
y: None,
d: Some(Base64Url.encode(keypair.private_key_bytes()?)),
})
}
}

impl TryFrom<Jwk> for X25519KeyPair {
type Error = CryptoError;

fn try_from(jwk: Jwk) -> Result<Self, Self::Error> {
if jwk.key_type != "OKP" {
return Err(CryptoError::Unsupported);
}

if jwk.curve != "X25519" {
return Err(CryptoError::InvalidCurve);
}

match jwk.d {
Some(secret_key) => {
let secret_key = base64url_to_bytes(&secret_key).map_err(|_| CryptoError::InvalidSecretKey)?;
X25519KeyPair::from_secret_key(&secret_key)
}
None => {
let public_key = jwk.x.ok_or(CryptoError::InvalidPublicKey)?;
let public_key = base64url_to_bytes(&public_key).map_err(|_| CryptoError::InvalidPublicKey)?;
X25519KeyPair::from_public_key(&public_key)
}
}
}
}

fn base64url_to_bytes(key: &str) -> Result<[u8; BYTES_LENGTH_32], ()> {
let key: Vec<u8> = Base64Url.decode(key).map_err(|_| ())?;
let key: [u8; BYTES_LENGTH_32] = key.try_into().map_err(|_| ())?;
Ok(key)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::traits::{CoreSign, Generate, ECDH};

// Tests conversion between Ed25519KeyPair and Jwk
#[test]
fn test_conversion_ed25519_jwk() -> Result<(), CryptoError> {
let seed = b"TMwLj2p2qhcuVhaFAj3QkkJGhK6pdyKx";
let payload = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit.";

let keypair = Ed25519KeyPair::new_with_seed(seed)?;
let signature = keypair.sign(payload).unwrap();

let jwk: Jwk = keypair.try_into()?;
let keypair: Ed25519KeyPair = jwk.try_into()?;
assert!(keypair.verify(payload, &signature).is_ok());

Ok(())
}

// Tests conversion from Jwk to Ed25519KeyPair with external signature
#[test]
fn test_conversion_ed25519_jwk_with_external_signature() -> Result<(), CryptoError> {
let jwk: Jwk = serde_json::from_str(
r#"{
"kty": "OKP",
"crv": "Ed25519",
"x": "tjOTPcs4OEMNrmn2ScYZDS-aCCbRFhJgaAmGnRsdmEo"
}"#,
)
.unwrap();

let payload = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
let signature = "2QH7Qrt8clEn4ETh9lgcGUyo26cJj1U8U0CBFQvgCWHe1dwXXXb16SzPTVNVGm-J6m6eALjWrxuJfmbApdoBAQ";
let signature = Base64Url.decode(signature).unwrap();

let keypair: Ed25519KeyPair = jwk.try_into()?;
assert!(keypair.verify(payload, &signature).is_ok());

Ok(())
}

// Tests conversion between X25519KeyPair and Jwk
#[test]
fn test_conversion_x25519_jwk() -> Result<(), CryptoError> {
let alice_seed = b"TMwLj2p2qhcuVhaFAj3QkkJGhK6pdyKx";
let bob_seed = b"NWB6DbnIlewWVp5jIJOSgyX8msXNPPAL";

let alice = X25519KeyPair::new_with_seed(alice_seed)?;
let bob = X25519KeyPair::new_with_seed(bob_seed)?;

let alice_shared_secret = alice.key_exchange(&bob);

let alice_jwk: Jwk = alice.try_into()?;
let alice: X25519KeyPair = alice_jwk.try_into()?;
let bob_jwk: Jwk = bob.try_into()?;
let bob: X25519KeyPair = bob_jwk.try_into()?;

let bob_shared_secret = bob.key_exchange(&alice);

assert_eq!(alice_shared_secret, bob_shared_secret);
Ok(())
}
}
2 changes: 2 additions & 0 deletions did-utils/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pub mod traits;
pub mod utils;
pub mod x25519;

mod format;

pub struct AsymmetricKey<P, S> {
pub public_key: P,
pub secret_key: Option<S>,
Expand Down
6 changes: 4 additions & 2 deletions did-utils/src/crypto/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ pub const BYTES_LENGTH_32: usize = 32;

#[derive(Debug)]
pub enum Error {
CanNotComputePublicKey,
CanNotRetrieveSignature,
InvalidCurve,
InvalidKeyLength,
InvalidSecretKey,
InvalidSeed,
InvalidPublicKey,
ConNotComputePublicKey,
CanNotRetrieveSignature,
SignatureError,
VerificationError,
Unsupported,
Unknown(String),
}

Expand Down

0 comments on commit a3f6cb3

Please sign in to comment.