identity_storage/key_storage/
keytool.rs

1// Copyright 2020-2025 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use async_trait::async_trait;
5use identity_verification::jwk::FromJwk as _;
6use identity_verification::jwk::Jwk;
7use identity_verification::jwk::ToJwk as _;
8use identity_verification::jws::JwsAlgorithm;
9use iota_interaction::types::base_types::IotaAddress;
10use iota_interaction::types::crypto::IotaKeyPair;
11use iota_interaction::types::crypto::SignatureScheme;
12use iota_interaction::KeytoolStorage;
13
14use super::JwkGenOutput;
15use super::JwkStorage;
16use super::KeyId;
17use super::KeyStorageError;
18use super::KeyStorageErrorKind;
19use super::KeyStorageResult;
20use super::KeyType;
21
22#[cfg_attr(feature = "send-sync-storage", async_trait)]
23#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
24impl JwkStorage for KeytoolStorage {
25  async fn generate(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult<JwkGenOutput> {
26    let key_scheme = match key_type.as_str() {
27      "ed25519" => SignatureScheme::ED25519,
28      "secp256r1" => SignatureScheme::Secp256r1,
29      "secp256k1" => SignatureScheme::Secp256k1,
30      _ => return Err(KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType)),
31    };
32
33    check_key_alg_compatibility(&key_type, &alg)?;
34
35    let (pk, _alias) = self
36      .generate_key(key_scheme)
37      .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(e))?;
38
39    let address = IotaAddress::from(&pk);
40    let mut jwk = pk
41      .to_jwk()
42      .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(e))?;
43    jwk.set_kid(jwk.thumbprint_sha256_b64());
44
45    Ok(JwkGenOutput {
46      key_id: KeyId::new(address.to_string()),
47      jwk,
48    })
49  }
50
51  async fn insert(&self, jwk: Jwk) -> KeyStorageResult<KeyId> {
52    let sk = IotaKeyPair::from_jwk(&jwk)
53      .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::RetryableIOFailure).with_source(e))?;
54    let pk = sk.public();
55    let _alias = self
56      .insert_key(sk)
57      .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::RetryableIOFailure).with_source(e))?;
58
59    let address = IotaAddress::from(&pk);
60    Ok(KeyId::new(address.to_string()))
61  }
62
63  async fn sign(&self, key_id: &KeyId, data: &[u8], _pk_jwk: &Jwk) -> KeyStorageResult<Vec<u8>> {
64    let iota_address = key_id.as_str().parse().map_err(|_| {
65      KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message("invalid IOTA address")
66    })?;
67
68    self
69      .sign_raw(iota_address, data)
70      .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(e))
71  }
72
73  async fn delete(&self, _key_id: &KeyId) -> KeyStorageResult<()> {
74    Err(
75      KeyStorageError::new(KeyStorageErrorKind::Unspecified)
76        .with_custom_message("IOTA Keytool doesn't support key deletion"),
77    )
78  }
79
80  async fn exists(&self, key_id: &KeyId) -> KeyStorageResult<bool> {
81    let iota_address = key_id.as_str().parse().map_err(|_| {
82      KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message("invalid IOTA address")
83    })?;
84    let exists = self
85      .get_key(iota_address)
86      .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::RetryableIOFailure).with_source(e))?
87      .is_some();
88
89    Ok(exists)
90  }
91}
92
93/// Check that the key type can be used with the algorithm.
94fn check_key_alg_compatibility(key_type: &KeyType, alg: &JwsAlgorithm) -> KeyStorageResult<()> {
95  match (key_type.as_str(), alg) {
96    ("ed25519", JwsAlgorithm::EdDSA) => Ok(()),
97    ("secp256r1", JwsAlgorithm::ES256) => Ok(()),
98    ("secp256k1", JwsAlgorithm::ES256K) => Ok(()),
99    (key_type, alg) => Err(
100      KeyStorageError::new(crate::key_storage::KeyStorageErrorKind::KeyAlgorithmMismatch)
101        .with_custom_message(format!("`cannot use key type `{key_type}` with algorithm `{alg}`")),
102    ),
103  }
104}
105#[cfg(test)]
106mod tests {
107  use crate::JwkDocumentExt as _;
108  use crate::JwsSignatureOptions;
109  use crate::KeyIdStorage as _;
110  use crate::KeyType;
111  use crate::KeytoolStorage;
112  use crate::MethodDigest;
113  use anyhow::anyhow;
114  use identity_credential::credential::CredentialBuilder;
115  use identity_credential::credential::Subject;
116  use identity_credential::validator::FailFast;
117  use identity_credential::validator::JwtCredentialValidationOptions;
118  use identity_credential::validator::JwtCredentialValidator;
119  use identity_did::DID;
120  use identity_ecdsa_verifier::EcDSAJwsVerifier;
121  use identity_iota_core::IotaDocument;
122  use identity_verification::jws::JwsAlgorithm;
123  use identity_verification::MethodScope;
124  use iota_interaction::KeytoolStorage as Keytool;
125  use product_common::network_name::NetworkName;
126  use serde_json::Value;
127
128  fn make_storage() -> KeytoolStorage {
129    let keytool = Keytool::default();
130    KeytoolStorage::new(keytool.clone(), keytool)
131  }
132
133  #[tokio::test]
134  async fn keytool_storage_works() -> anyhow::Result<()> {
135    let storage = make_storage();
136
137    let mut did_doc = IotaDocument::new(&NetworkName::try_from("iota".to_string())?);
138    let fragment = did_doc
139      .generate_method(
140        &storage,
141        KeyType::from_static_str("secp256r1"),
142        JwsAlgorithm::ES256,
143        None,
144        MethodScope::VerificationMethod,
145      )
146      .await?;
147    let vm = did_doc.resolve_method(&fragment, None).expect("just created it");
148
149    let address_of_vm_key = storage
150      .key_id_storage()
151      .get_key_id(&MethodDigest::new(vm)?)
152      .await?
153      .as_str()
154      .parse()?;
155
156    let (_pk, alias) = storage
157      .key_storage()
158      .get_key(address_of_vm_key)?
159      .ok_or_else(|| anyhow!("something wrong with the new VM key!!"))?;
160
161    assert!(alias.starts_with("identity__"));
162
163    let credential = CredentialBuilder::new(Value::default())
164      .id("https://example.com/credentials/42".parse()?)
165      .issuer(did_doc.id().to_url())
166      .subject(Subject::with_id("https://example.com/users/123".parse()?))
167      .build()?;
168
169    let jwt = did_doc
170      .create_credential_jwt(&credential, &storage, &fragment, &JwsSignatureOptions::default(), None)
171      .await?;
172
173    let validator = JwtCredentialValidator::with_signature_verifier(EcDSAJwsVerifier::default());
174    validator.validate::<IotaDocument, Value>(
175      &jwt,
176      &did_doc,
177      &JwtCredentialValidationOptions::default(),
178      FailFast::FirstError,
179    )?;
180
181    Ok(())
182  }
183}