Skip to content

Commit 6066ba4

Browse files
committed
feat: more progress
Signed-off-by: Daniel Bluhm <[email protected]>
1 parent 8c5c0d8 commit 6066ba4

File tree

8 files changed

+421
-138
lines changed

8 files changed

+421
-138
lines changed

didcomm_messaging/askar/__init__.py

Lines changed: 52 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
"""Askar backend for DIDComm Messaging."""
22
from collections import OrderedDict
33
import json
4-
from typing import Mapping, Optional, Union
5-
from didcomm_messaging.crypto import KMS, SecretsManager
4+
from typing import Mapping, Optional, Sequence, Union
5+
6+
from pydid import VerificationMethod
7+
from didcomm_messaging.crypto import SecretsManager
68
from didcomm_messaging.jwe import (
79
JweBuilder,
810
JweEnvelope,
911
JweRecipient,
1012
b64url,
1113
)
12-
from didcomm_messaging.kms import CryptoService, CryptoServiceError, PublicKey, SecretKey
14+
from didcomm_messaging.crypto import (
15+
CryptoService,
16+
CryptoServiceError,
17+
PublicKey,
18+
SecretKey,
19+
)
1320
from didcomm_messaging.multiformats import multibase, multicodec
1421

1522
try:
@@ -102,39 +109,27 @@ def _expected_alg_and_material_to_key(
102109
raise ValueError("Failed to parse key")
103110

104111
@classmethod
105-
def from_verification_method(cls, vm: dict) -> "PublicKey":
112+
def from_verification_method(cls, vm: VerificationMethod) -> "AskarKey":
106113
"""Create a Key instance from a DID Document Verification Method."""
107-
vm_type = vm.get("type")
108-
ident = vm.get("id")
109-
controller = vm.get("controller")
110-
if not vm_type:
111-
raise ValueError("Verification method missing type")
112-
113-
if not ident:
114-
raise ValueError("Verification method missing id")
115-
116-
if not controller:
117-
raise ValueError("Verification method missing controller")
118-
119-
if ident.startswith("#"):
120-
kid = f"{controller}#{ident}"
114+
if not vm.id.did:
115+
kid = vm.id.as_absolute(vm.controller)
121116
else:
122-
kid = ident
117+
kid = vm.id
123118

124-
if vm_type == "Multikey":
125-
multikey = vm.get("publicKeyMultiBase")
119+
if vm.type == "Multikey":
120+
multikey = vm.public_key_multibase
126121
if not multikey:
127122
raise ValueError("Multikey verification method missing key")
128123

129124
key = cls._multikey_to_key(multikey)
130125
return cls(key, kid)
131126

132-
alg = cls.type_to_alg.get(vm_type)
127+
alg = cls.type_to_alg.get(vm.type)
133128
if not alg:
134129
raise ValueError("Unsupported verification method type: {vm_type}")
135130

136-
base58 = vm.get("publicKeyBase58")
137-
multi = vm.get("publicKeyMultiBase")
131+
base58 = vm.public_key_base58
132+
multi = vm.public_key_multibase
138133
key = cls._expected_alg_and_material_to_key(
139134
alg, public_key_base58=base58, public_key_multibase=multi
140135
)
@@ -165,12 +160,11 @@ def kid(self) -> str:
165160
return self._kid
166161

167162

168-
169163
class AskarCryptoService(CryptoService[AskarKey, AskarSecretKey]):
170-
"""Askar backend for DIDComm Messaging."""
164+
"""CryptoService backend implemented using Askar."""
171165

172166
async def ecdh_es_encrypt(
173-
self, to_keys: Mapping[str, AskarKey], message: bytes
167+
self, to_keys: Sequence[AskarKey], message: bytes
174168
) -> bytes:
175169
"""Encode a message into DIDComm v2 anonymous encryption."""
176170
builder = JweBuilder(with_flatten_recipients=False)
@@ -188,7 +182,7 @@ async def ecdh_es_encrypt(
188182
except AskarError:
189183
raise CryptoServiceError("Error creating content encryption key")
190184

191-
for kid, recip_key in to_keys.items():
185+
for recip_key in to_keys:
192186
try:
193187
epk = Key.generate(recip_key.key.algorithm, ephemeral=True)
194188
except AskarError:
@@ -199,7 +193,7 @@ async def ecdh_es_encrypt(
199193
builder.add_recipient(
200194
JweRecipient(
201195
encrypted_key=enc_key.ciphertext,
202-
header={"kid": kid, "epk": epk.get_jwk_public()},
196+
header={"kid": recip_key.kid, "epk": epk.get_jwk_public()},
203197
)
204198
)
205199

@@ -222,7 +216,7 @@ async def ecdh_es_encrypt(
222216
async def ecdh_es_decrypt(
223217
self,
224218
wrapper: Union[JweEnvelope, str, bytes],
225-
recip_key: AskarKey,
219+
recip_key: AskarSecretKey,
226220
) -> bytes:
227221
"""Decode a message from DIDComm v2 anonymous encryption."""
228222
if isinstance(wrapper, bytes):
@@ -235,7 +229,9 @@ async def ecdh_es_decrypt(
235229
if alg_id and alg_id in ("ECDH-ES+A128KW", "ECDH-ES+A256KW"):
236230
wrap_alg = alg_id[8:]
237231
else:
238-
raise CryptoServiceError(f"Missing or unsupported ECDH-ES algorithm: {alg_id}")
232+
raise CryptoServiceError(
233+
f"Missing or unsupported ECDH-ES algorithm: {alg_id}"
234+
)
239235

240236
recip = wrapper.get_recipient(recip_key.kid)
241237
if not recip:
@@ -249,7 +245,9 @@ async def ecdh_es_decrypt(
249245
"A256CBC-HS512",
250246
"XC20P",
251247
):
252-
raise CryptoServiceError(f"Unsupported ECDH-ES content encryption: {enc_alg}")
248+
raise CryptoServiceError(
249+
f"Unsupported ECDH-ES content encryption: {enc_alg}"
250+
)
253251

254252
epk_header = recip.header.get("epk")
255253
if not epk_header:
@@ -289,9 +287,8 @@ async def ecdh_es_decrypt(
289287

290288
async def ecdh_1pu_encrypt(
291289
self,
292-
to_keys: Mapping[str, AskarKey],
293-
sender_kid: str,
294-
sender_key: AskarKey,
290+
to_keys: Sequence[AskarKey],
291+
sender_key: AskarSecretKey,
295292
message: bytes,
296293
) -> bytes:
297294
"""Encode a message into DIDComm v2 authenticated encryption."""
@@ -316,15 +313,15 @@ async def ecdh_1pu_encrypt(
316313
except AskarError:
317314
raise CryptoServiceError("Error creating ephemeral key")
318315

319-
apu = b64url(sender_kid)
316+
apu = b64url(sender_key.kid)
320317
apv = []
321-
for kid, recip_key in to_keys.items():
318+
for recip_key in to_keys:
322319
if agree_alg:
323320
if agree_alg != recip_key.key.algorithm:
324321
raise CryptoServiceError("Recipient key types must be consistent")
325322
else:
326323
agree_alg = recip_key.key.algorithm
327-
apv.append(kid)
324+
apv.append(recip_key.kid)
328325
apv.sort()
329326
apv = b64url(".".join(apv))
330327

@@ -336,7 +333,7 @@ async def ecdh_1pu_encrypt(
336333
("apu", apu),
337334
("apv", apv),
338335
("epk", json.loads(epk.get_jwk_public())),
339-
("skid", sender_kid),
336+
("skid", sender_key.kid),
340337
]
341338
)
342339
)
@@ -346,20 +343,22 @@ async def ecdh_1pu_encrypt(
346343
raise CryptoServiceError("Error encrypting message payload")
347344
builder.set_payload(payload.ciphertext, payload.nonce, payload.tag)
348345

349-
for kid, recip_key in to_keys.items():
346+
for recip_key in to_keys:
350347
enc_key = ecdh.Ecdh1PU(alg_id, apu, apv).sender_wrap_key(
351348
wrap_alg, epk, sender_key.key, recip_key.key, cek, cc_tag=payload.tag
352349
)
353350
builder.add_recipient(
354-
JweRecipient(encrypted_key=enc_key.ciphertext, header={"kid": kid})
351+
JweRecipient(
352+
encrypted_key=enc_key.ciphertext, header={"kid": recip_key.kid}
353+
)
355354
)
356355

357356
return builder.build().to_json().encode("utf-8")
358357

359358
async def ecdh_1pu_decrypt(
360359
self,
361360
wrapper: Union[JweEnvelope, str, bytes],
362-
recip_key: AskarKey,
361+
recip_key: AskarSecretKey,
363362
sender_key: AskarKey,
364363
):
365364
"""Decode a message from DIDComm v2 authenticated encryption."""
@@ -376,7 +375,9 @@ async def ecdh_1pu_decrypt(
376375

377376
enc_alg = wrapper.protected.get("enc")
378377
if not enc_alg or enc_alg not in ("A128CBC-HS256", "A256CBC-HS512"):
379-
raise CryptoServiceError(f"Unsupported ECDH-1PU content encryption: {enc_alg}")
378+
raise CryptoServiceError(
379+
f"Unsupported ECDH-1PU content encryption: {enc_alg}"
380+
)
380381

381382
recip = wrapper.get_recipient(recip_key.kid)
382383
if not recip:
@@ -420,14 +421,20 @@ async def ecdh_1pu_decrypt(
420421

421422
return plaintext
422423

423-
class AskarWithStore(KMS, AskarCryptoService, SecretsManager):
424+
@classmethod
425+
def verification_method_to_public_key(cls, vm: VerificationMethod) -> AskarKey:
426+
"""Convert a verification method into a public key."""
427+
return AskarKey.from_verification_method(vm)
428+
429+
430+
class AskarSecretsManager(SecretsManager[AskarSecretKey]):
424431
"""Askar KMS with an Askar Store for secrets management."""
425432

426433
def __init__(self, store: Store):
427434
"""Initialize a new Askar instance."""
428435
self.store = store
429436

430-
async def fetch_key_by_kid(self, kid: str) -> Optional[AskarSecretKey]:
437+
async def get_secret_by_kid(self, kid: str) -> Optional[AskarSecretKey]:
431438
"""Fetch a public key by key ID."""
432439
async with self.store.session() as session:
433440
key_entry = await session.fetch_key(kid)
@@ -436,15 +443,3 @@ async def fetch_key_by_kid(self, kid: str) -> Optional[AskarSecretKey]:
436443

437444
# cached_property doesn't play nice with pyright
438445
return AskarKey(key_entry.key, kid) # type: ignore
439-
440-
441-
class AskarDelegatedSecrets(KMS, AskarCryptoService, SecretsManager):
442-
"""Askar KMS with delegated secrets management."""
443-
444-
def __init__(self, secrets: SecretsManager):
445-
"""Initialize a new AskarDelegatedSecrets instance."""
446-
self.secrets = secrets
447-
448-
async def get_secret_by_kid(self, kid: str) -> Optional[SecretKey]:
449-
"""Get a secret key by its kid."""
450-
return await self.secrets.get_secret_by_kid(kid)

0 commit comments

Comments
 (0)