Skip to content

Commit 0f9eb9c

Browse files
wulfraemUMR1352
andauthored
Add traits to convert key types from and to Jwk (#1654)
* add traits to convert key types from and to `Jwk` * enable identity_verification/jwk-conversion in identity_storage * add tests and fix issues, that where found --------- Co-authored-by: umr1352 <[email protected]>
1 parent 500b135 commit 0f9eb9c

File tree

20 files changed

+542
-198
lines changed

20 files changed

+542
-198
lines changed

identity_iota_core/tests/e2e/asset.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Copyright 2020-2024 IOTA Stiftung
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use crate::common::get_funded_test_client;
5-
use crate::common::TEST_GAS_BUDGET;
4+
use std::str::FromStr as _;
5+
66
use identity_core::common::Object;
77
use identity_core::common::Timestamp;
88
use identity_core::common::Url;
@@ -20,13 +20,17 @@ use identity_iota_core::IotaDID;
2020
use identity_iota_core::IotaDocument;
2121
use identity_iota_interaction::IotaClientTrait;
2222
use identity_iota_interaction::MoveType as _;
23+
use identity_jose::jwk::ToJwk as _;
2324
use identity_storage::JwkDocumentExt;
2425
use identity_storage::JwsSignatureOptions;
2526
use identity_verification::VerificationMethod;
2627
use iota_sdk::types::TypeTag;
2728
use itertools::Itertools as _;
2829
use move_core_types::language_storage::StructTag;
29-
use std::str::FromStr;
30+
use secret_storage::Signer as _;
31+
32+
use crate::common::get_funded_test_client;
33+
use crate::common::TEST_GAS_BUDGET;
3034

3135
#[tokio::test]
3236
async fn creating_authenticated_asset_works() -> anyhow::Result<()> {
@@ -212,7 +216,7 @@ async fn hosting_vc_works() -> anyhow::Result<()> {
212216
.id(did.clone().into())
213217
.verification_method(VerificationMethod::new_from_jwk(
214218
did.clone(),
215-
identity_client.signer().public_key().clone(),
219+
identity_client.signer().public_key().await?.to_jwk()?,
216220
Some(identity_client.signer().key_id().as_str()),
217221
)?)
218222
.build()?;

identity_iota_core/tests/e2e/common.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use identity_iota_interaction::IotaKeySignature;
2525
use identity_iota_interaction::IotaTransactionBlockEffectsMutAPI;
2626
use identity_iota_interaction::OptionalSync;
2727
use identity_jose::jwk::Jwk;
28+
use identity_jose::jwk::ToJwk as _;
2829
use identity_jose::jws::JwsAlgorithm;
2930
use identity_storage::JwkMemStore;
3031
use identity_storage::JwkStorage;
@@ -292,7 +293,7 @@ impl TestClient {
292293
let public_key = identity_client.signer().public_key();
293294
let key_id = identity_client.signer().key_id();
294295
let fragment = key_id.as_str();
295-
let method = VerificationMethod::new_from_jwk(did, public_key.clone(), Some(fragment))?;
296+
let method = VerificationMethod::new_from_jwk(did, public_key.await?.to_jwk()?, Some(fragment))?;
296297
let method_digest: MethodDigest = MethodDigest::new(&method)?;
297298

298299
self

identity_iota_core/tests/e2e/identity.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use identity_iota_core::rebased::proposals::ProposalResult;
1818
use identity_iota_core::IotaDID;
1919
use identity_iota_core::IotaDocument;
2020
use identity_iota_interaction::KeytoolSigner;
21+
use identity_jose::jwk::ToJwk as _;
2122
use identity_verification::MethodScope;
2223
use identity_verification::VerificationMethod;
2324
use iota_sdk::rpc_types::IotaObjectData;
@@ -29,6 +30,7 @@ use iota_sdk::types::transaction::ObjectArg;
2930
use iota_sdk::types::TypeTag;
3031
use iota_sdk::types::IOTA_FRAMEWORK_PACKAGE_ID;
3132
use move_core_types::ident_str;
33+
use secret_storage::Signer as _;
3234

3335
#[tokio::test]
3436
async fn identity_deactivation_works() -> anyhow::Result<()> {
@@ -78,7 +80,7 @@ async fn updating_onchain_identity_did_doc_with_single_controller_works() -> any
7880
doc.insert_method(
7981
VerificationMethod::new_from_jwk(
8082
did,
81-
identity_client.signer().public_key().clone(),
83+
identity_client.signer().public_key().await?.to_jwk()?,
8284
Some(identity_client.signer().key_id().as_str()),
8385
)?,
8486
MethodScope::VerificationMethod,
@@ -122,7 +124,7 @@ async fn approving_proposal_works() -> anyhow::Result<()> {
122124
doc.insert_method(
123125
VerificationMethod::new_from_jwk(
124126
did,
125-
alice_client.signer().public_key().clone(),
127+
alice_client.signer().public_key().await?.to_jwk()?,
126128
Some(alice_client.signer().key_id().as_str()),
127129
)?,
128130
MethodScope::VerificationMethod,

identity_jose/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,29 @@ repository.workspace = true
1111
description = "A library for JOSE (JSON Object Signing and Encryption)"
1212

1313
[dependencies]
14+
anyhow = { version = "1", optional = true }
1415
bls12_381_plus.workspace = true
16+
fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "2f502fd8570fe4e9cff36eea5bbd6fef22002898", package = "fastcrypto", optional = true }
1517
identity_core = { version = "=1.6.0-alpha", path = "../identity_core" }
1618
iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha"] }
1719
json-proof-token.workspace = true
20+
k256 = { version = "0.13.3", default-features = false, features = ["std", "ecdsa", "ecdsa-core", "jwk"], optional = true }
21+
p256 = { version = "0.13.2", default-features = false, features = ["std", "ecdsa", "ecdsa-core", "jwk"], optional = true }
1822
serde.workspace = true
1923
serde_json = { version = "1.0", default-features = false, features = ["std"] }
2024
thiserror.workspace = true
2125
zeroize = { version = "1.6", default-features = false, features = ["std", "zeroize_derive"] }
2226

27+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
28+
identity_iota_interaction = { version = "=1.6.0-alpha", path = "../identity_iota_interaction" }
29+
30+
[target.'cfg(target_arch = "wasm32")'.dependencies]
31+
identity_iota_interaction = { version = "=1.6.0-alpha", path = "../identity_iota_interaction", default-features = false }
32+
2333
[dev-dependencies]
2434
iota-crypto = { version = "0.23", features = ["ed25519", "random", "hmac"] }
2535
p256 = { version = "0.13.0", default-features = false, features = ["std", "ecdsa", "ecdsa-core"] }
36+
rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] }
2637
signature = { version = "2", default-features = false }
2738

2839
[[example]]
@@ -33,7 +44,14 @@ test = true
3344
workspace = true
3445

3546
[features]
47+
default = []
3648
custom_alg = []
49+
jwk-conversion = [
50+
"dep:anyhow",
51+
"dep:k256",
52+
"dep:p256",
53+
"fastcrypto/copy_key",
54+
]
3755

3856
[[test]]
3957
name = "custom_alg"

identity_jose/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,10 @@ pub enum Error {
4949
/// Caused by a missing `alg` claim in the protected header.
5050
#[error("missing alg in protected header")]
5151
ProtectedHeaderWithoutAlg,
52+
/// Caused by converting keys to different types.
53+
#[error("failed to convert key: `{0}`")]
54+
KeyConversion(String),
55+
/// Key type not supported.
56+
#[error("key type not supported; {0}")]
57+
UnsupportedKeyType(String),
5258
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2020-2023 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use fastcrypto::ed25519::Ed25519KeyPair;
5+
use fastcrypto::ed25519::Ed25519PrivateKey;
6+
use fastcrypto::ed25519::Ed25519PublicKey;
7+
use fastcrypto::ed25519::Ed25519PublicKeyAsBytes;
8+
use fastcrypto::traits::KeyPair as _;
9+
use fastcrypto::traits::SigningKey;
10+
use fastcrypto::traits::ToFromBytes;
11+
12+
use crate::error::Error;
13+
use crate::jwk::EdCurve;
14+
use crate::jwk::Jwk;
15+
use crate::jwk::JwkParamsOkp;
16+
use crate::jwu;
17+
use crate::jwu::decode_b64;
18+
use crate::jwu::encode_b64;
19+
20+
pub(crate) fn from_public_jwk(jwk: &Jwk) -> anyhow::Result<Ed25519PublicKey> {
21+
let bytes = decode_b64(&jwk.try_okp_params()?.x)?;
22+
Ok(Ed25519PublicKey::from_bytes(&bytes)?)
23+
}
24+
25+
pub(crate) fn jwk_to_keypair(jwk: &Jwk) -> Result<Ed25519KeyPair, Error> {
26+
let params: &JwkParamsOkp = jwk.try_okp_params()?;
27+
28+
if params
29+
.try_ed_curve()
30+
.map_err(|err| Error::UnsupportedKeyType(err.to_string()))?
31+
!= EdCurve::Ed25519
32+
{
33+
return Err(Error::UnsupportedKeyType(format!(
34+
"expected an {} key",
35+
EdCurve::Ed25519.name()
36+
)));
37+
}
38+
39+
let sk: [u8; Ed25519PrivateKey::LENGTH] = params
40+
.d
41+
.as_deref()
42+
.map(jwu::decode_b64)
43+
.ok_or_else(|| Error::KeyConversion("expected Jwk `d` param to be present".to_string()))?
44+
.map_err(|err| Error::KeyConversion(format!("unable to decode `d` param; {}", err)))?
45+
.try_into()
46+
.map_err(|_| Error::KeyConversion(format!("expected key of length {}", Ed25519PrivateKey::LENGTH)))?;
47+
48+
Ed25519KeyPair::from_bytes(&sk).map_err(|_| Error::KeyConversion("invalid key".to_string()))
49+
}
50+
51+
#[allow(dead_code)]
52+
pub(crate) fn encode_jwk(key_pair: Ed25519KeyPair) -> Jwk {
53+
let x = jwu::encode_b64(key_pair.public().as_ref());
54+
let d = jwu::encode_b64(key_pair.private().as_ref());
55+
let mut params = JwkParamsOkp::new();
56+
params.x = x;
57+
params.d = Some(d);
58+
params.crv = EdCurve::Ed25519.name().to_string();
59+
Jwk::from_params(params)
60+
}
61+
62+
#[allow(dead_code)]
63+
pub(crate) fn pk_to_jwk(pk: &Ed25519PublicKeyAsBytes) -> Jwk {
64+
use crate::jws::JwsAlgorithm;
65+
66+
let params = JwkParamsOkp {
67+
crv: EdCurve::Ed25519.to_string(),
68+
x: encode_b64(pk.0),
69+
d: None,
70+
};
71+
72+
let mut jwk = Jwk::from_params(params);
73+
jwk.set_alg(JwsAlgorithm::EdDSA.name());
74+
75+
jwk
76+
}

0 commit comments

Comments
 (0)