Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: address feedback #40

Merged
merged 2 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions didcomm_messaging/crypto/backend/askar.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,6 @@ def multikey(self) -> str:
"""Get the key in multibase format."""
return self._multikey

@property
def key_bytes(self) -> bytes:
"""Get the bytes of the key."""
return self.key.get_public_bytes()


class AskarSecretKey(SecretKey):
"""Secret key implementation for Askar."""
Expand All @@ -135,6 +130,10 @@ def kid(self) -> str:
"""Get the key ID."""
return self._kid

def as_public_key(self) -> AskarKey:
"""Return AskarKey representation."""
return AskarKey(self.key, self.kid)


class AskarCryptoService(CryptoService[AskarKey, AskarSecretKey]):
"""CryptoService backend implemented using Askar."""
Expand Down Expand Up @@ -418,4 +417,4 @@ async def get_secret_by_kid(self, kid: str) -> Optional[AskarSecretKey]:
return None

# cached_property doesn't play nice with pyright
return AskarKey(key_entry.key, kid) # type: ignore
return AskarSecretKey(key_entry.key, kid) # type: ignore
12 changes: 0 additions & 12 deletions didcomm_messaging/crypto/backend/authlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,6 @@ def multikey(self) -> str:
"""Return the key in multikey format."""
return self._multikey

@property
def key_bytes(self) -> bytes:
"""Get the bytes of the key."""
jwk = self.key.as_dict(is_private=False)
codec = self.kty_crv_to_codec.get((jwk["kty"], jwk.get("crv")))

if not codec:
raise ValueError("Unsupported key type")

key_bytes = b64url.decode(jwk["x"])
return key_bytes

@classmethod
def key_to_multikey(cls, key: AsymmetricKey) -> str:
"""Convert an Authlib key to a multikey."""
Expand Down
5 changes: 0 additions & 5 deletions didcomm_messaging/crypto/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,6 @@ def kid(self) -> str:
def multikey(self) -> str:
"""Get the key in multikey format."""

@property
@abstractmethod
def key_bytes(self) -> bytes:
"""Get the bytes of the key."""


class SecretKey(ABC):
"""Secret Key Type."""
Expand Down
4 changes: 2 additions & 2 deletions didcomm_messaging/messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class UnpackResult:
"""Result of unpacking a message."""

unpacked: bytes
encrytped: bool
encrypted: bool
authenticated: bool
recipient_kid: str
sender_kid: Optional[str] = None
Expand Down Expand Up @@ -139,7 +139,7 @@ async def unpack(
)
return UnpackResult(
unpacked,
encrytped=bool(metadata.method),
encrypted=bool(metadata.method),
authenticated=bool(metadata.sender_kid),
recipient_kid=metadata.recip_key.kid,
sender_kid=metadata.sender_kid,
Expand Down
2 changes: 1 addition & 1 deletion didcomm_messaging/quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ async def setup_relay(
await secrets.add_secret(AskarSecretKey(verkey, f"{new_did}#key-1"))
await secrets.add_secret(AskarSecretKey(xkey, f"{new_did}#key-2"))

# V1 formats
# Legacy formats
# verkey
await secrets.add_secret(AskarSecretKey(verkey, doc.authentication[0]))
# xkey
Expand Down
4 changes: 1 addition & 3 deletions didcomm_messaging/resolver/peer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ class Peer4(DIDResolver):

async def is_resolvable(self, did: str) -> bool:
"""Check to see if a DID is resolvable."""
return bool(peer_4_pattern_short.match(did)) or bool(
peer_4_pattern_long.match(did)
)
return bool(peer_4_pattern_short.match(did) or peer_4_pattern_long.match(did))

async def resolve(self, did: str) -> dict:
"""Resolve a did:peer:4 DID."""
Expand Down
27 changes: 18 additions & 9 deletions didcomm_messaging/v1/crypto/askar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
from typing import Optional, Sequence, Tuple, cast

from base58 import b58decode
import base58
from pydid import VerificationMethod

from didcomm_messaging.crypto.jwe import JweBuilder, JweEnvelope, JweRecipient
from .base import (
V1CryptoService,
V1UnpackResult,
V1CryptoServiceError,
V1CryptoUnpackResult,
RecipData,
)

Expand All @@ -24,13 +26,19 @@
class AskarV1CryptoService(V1CryptoService[AskarKey, AskarSecretKey]):
"""V1 crypto service implementation for askar."""

def kid_to_public_key(self, kid: str) -> AskarKey:
def v1_kid_to_public_key(self, kid: str) -> AskarKey:
"""Get a public key from a kid.

In DIDComm v1, kids are the base58 encoded keys.
"""
return AskarKey(Key.from_public_bytes(KeyAlg.ED25519, b58decode(kid)), kid)

def public_key_to_v1_kid(self, key: AskarKey) -> str:
"""Convert a public key into a v1 kid representation."""
if key.key.algorithm != KeyAlg.ED25519:
raise V1CryptoServiceError()
return base58.b58encode(key.key.get_public_bytes()).decode()

@classmethod
def verification_method_to_public_key(cls, vm: VerificationMethod) -> AskarKey:
"""Convert a verification method to a public key."""
Expand All @@ -50,11 +58,14 @@ async def pack_message(
# avoid converting to bytes object: this way the only copy is zeroed afterward
# tell type checking it's bytes to make it happy
cek_b = cast(bytes, key_get_secret_bytes(cek._handle))
sender_vk = from_key.kid if from_key else None
sender_vk = (
self.public_key_to_v1_kid(from_key.as_public_key()) if from_key else None
)
sender_xk = from_key.key.convert_key(KeyAlg.X25519) if from_key else None

for target_vk in to_verkeys:
target_xk = target_vk.key.convert_key(KeyAlg.X25519)
target_vk_kid = self.public_key_to_v1_kid(target_vk)
if sender_vk and sender_xk:
enc_sender = crypto_box.crypto_box_seal(target_xk, sender_vk)
nonce = crypto_box.random_nonce()
Expand All @@ -64,19 +75,17 @@ async def pack_message(
encrypted_key=enc_cek,
header=OrderedDict(
[
("kid", target_vk.kid),
("kid", target_vk_kid),
("sender", self.b64url.encode(enc_sender)),
("iv", self.b64url.encode(nonce)),
]
),
)
)
else:
enc_sender = None
nonce = None
enc_cek = crypto_box.crypto_box_seal(target_xk, cek_b)
builder.add_recipient(
JweRecipient(encrypted_key=enc_cek, header={"kid": target_vk.kid})
JweRecipient(encrypted_key=enc_cek, header={"kid": target_vk_kid})
)
builder.set_protected(
OrderedDict(
Expand All @@ -97,7 +106,7 @@ async def unpack_message(
wrapper: JweEnvelope,
recip_key: AskarSecretKey,
recip_data: RecipData,
) -> V1UnpackResult:
) -> V1CryptoUnpackResult:
"""Decode a message using the DIDComm v1 'unpack' algorithm."""
payload_key, sender_vk = self._extract_payload_key(recip_key.key, recip_data)

Expand All @@ -108,7 +117,7 @@ async def unpack_message(
tag=wrapper.tag,
aad=wrapper.protected_b64,
)
return V1UnpackResult(message, recip_key.kid, sender_vk)
return V1CryptoUnpackResult(message, recip_key.kid, sender_vk)

def _extract_payload_key(
self, recip_key: Key, recip_data: RecipData
Expand Down
16 changes: 12 additions & 4 deletions didcomm_messaging/v1/crypto/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,34 @@ class RecipData(NamedTuple):
enc_cek: bytes


class V1UnpackResult(NamedTuple):
class V1CryptoUnpackResult(NamedTuple):
"""Result of unpacking."""

message: bytes
unpacked: bytes
recip: str
sender: Optional[str]


class V1CryptoServiceError(Exception):
"""Raised on errors in crypto service."""


class V1CryptoService(ABC, Generic[P, S]):
"""CryptoService interface for DIDComm v1."""

b64url = Base64UrlEncoder()

@abstractmethod
def kid_to_public_key(self, kid: str) -> P:
def v1_kid_to_public_key(self, kid: str) -> P:
"""Get a public key from a kid.

In DIDComm v1, kids are the base58 encoded keys.
"""

@abstractmethod
def public_key_to_v1_kid(self, key: P) -> str:
"""Return the DIDComm v1 kid representation for a key."""

@classmethod
@abstractmethod
def verification_method_to_public_key(cls, vm: VerificationMethod) -> P:
Expand All @@ -52,5 +60,5 @@ async def pack_message(
@abstractmethod
async def unpack_message(
self, wrapper: JweEnvelope, recip_key: S, recip_data: RecipData
) -> V1UnpackResult:
) -> V1CryptoUnpackResult:
"""Decode a message using DIDCvomm v1 'unpack' algorithm."""
30 changes: 15 additions & 15 deletions didcomm_messaging/v1/crypto/nacl.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from didcomm_messaging.crypto.jwe import JweBuilder, JweEnvelope, JweRecipient
from didcomm_messaging.multiformats import multibase, multicodec

from .base import V1CryptoService, V1UnpackResult, RecipData
from .base import V1CryptoService, V1CryptoUnpackResult, RecipData

try:
import nacl.bindings
Expand Down Expand Up @@ -60,7 +60,7 @@ def key(self) -> str:
@property
def kid(self) -> str:
"""Get the key ID."""
return self.key
raise NotImplementedError()

@property
def multikey(self) -> str:
Expand All @@ -69,22 +69,21 @@ def multikey(self) -> str:
multicodec.wrap("ed25519-pub", base58.b58decode(self.key)), "base58btc"
)

@property
def key_bytes(self) -> bytes:
"""Get the bytes of the key."""
return self.value


class NaclV1CryptoService(V1CryptoService[EdPublicKey, KeyPair]):
"""V1 crypto service using pynacl."""

def kid_to_public_key(self, kid: str):
def v1_kid_to_public_key(self, kid: str):
"""Get a public key from a kid.

In DIDComm v1, kids are the base58 encoded keys.
"""
return EdPublicKey(base58.b58decode(kid))

def public_key_to_v1_kid(self, key: EdPublicKey) -> str:
"""Convert a public key into a v1 kid representation."""
return base58.b58encode(key.value).decode()

@classmethod
def verification_method_to_public_key(cls, vm: VerificationMethod) -> EdPublicKey:
"""Convert a verification method to a public key."""
Expand Down Expand Up @@ -120,19 +119,20 @@ async def pack_message(
encrypted_key=enc_cek,
header=OrderedDict(
[
("kid", target_vk.kid),
("kid", self.public_key_to_v1_kid(target_vk)),
("sender", self.b64url.encode(enc_sender)),
("iv", self.b64url.encode(nonce)),
]
),
)
)
else:
enc_sender = None
nonce = None
enc_cek = nacl.bindings.crypto_box_seal(cek, target_xk)
builder.add_recipient(
JweRecipient(encrypted_key=enc_cek, header={"kid": target_vk.kid})
JweRecipient(
encrypted_key=enc_cek,
header={"kid": self.public_key_to_v1_kid(target_vk)},
)
)

builder.set_protected(
Expand Down Expand Up @@ -180,15 +180,15 @@ def _extract_payload_key(self, recip_key: KeyPair, recip_data: RecipData):

async def unpack_message(
self, wrapper: JweEnvelope, recip_key: KeyPair, recip_data: RecipData
) -> V1UnpackResult:
) -> V1CryptoUnpackResult:
"""Decode a message using DIDCvomm v1 'unpack' algorithm."""
cek, sender_vk = self._extract_payload_key(recip_key, recip_data)

payload_bin = wrapper.ciphertext + wrapper.tag
message = nacl.bindings.crypto_aead_chacha20poly1305_ietf_decrypt(
payload_bin, wrapper.protected_b64, wrapper.iv, cek
)
return V1UnpackResult(message, recip_key.kid, sender_vk)
return V1CryptoUnpackResult(message, recip_key.kid, sender_vk)


class InMemSecretsManager(SecretsManager[KeyPair]):
Expand All @@ -206,7 +206,7 @@ def _create_keypair(self, seed: Optional[bytes] = None) -> Tuple[bytes, bytes]:
"""Create a keypair."""
if seed:
if not isinstance(seed, bytes):
raise ValueError("Seed value is not a string or bytes")
raise ValueError("Seed value is not bytes")
if len(seed) != 32:
raise ValueError("Seed value must be 32 bytes in length")
else:
Expand Down
Loading