"""Ed25519 key implementations backed by HSM."""
from __future__ import annotations
from typing import TYPE_CHECKING
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ed25519
from pkcs11 import Attribute, KeyType, Mechanism
from ..exceptions import HSMOperationError, HSMUnsupportedError
from .base import PKCS11PrivateKeyMixin
if TYPE_CHECKING:
from pkcs11 import Session
[docs]
class PKCS11Ed25519PrivateKey(PKCS11PrivateKeyMixin, ed25519.Ed25519PrivateKey):
"""Ed25519 private key backed by HSM.
This class implements the cryptography library's Ed25519PrivateKey
interface while performing all cryptographic operations on the HSM.
"""
_key_type = KeyType.EC_EDWARDS
def __init__(
self,
session: Session,
key_id: bytes | None = None,
key_label: str | None = None,
) -> None:
"""Initialize HSM Ed25519 private key.
Args:
session: PKCS#11 session
key_id: Key ID (CKA_ID)
key_label: Key label (CKA_LABEL)
"""
PKCS11PrivateKeyMixin.__init__(self, session, key_id, key_label)
[docs]
def public_key(self) -> "PKCS11Ed25519PublicKey":
"""Return the public key corresponding to this private key."""
return PKCS11Ed25519PublicKey.from_pkcs11_key(
self._session,
self.pkcs11_public_key,
self._key_id,
self._key_label,
)
[docs]
def sign(self, data: bytes) -> bytes:
"""Sign data using Ed25519.
Ed25519 does not require pre-hashing; the algorithm handles
hashing internally.
Args:
data: Data to sign
Returns:
64-byte signature
Raises:
HSMOperationError: If signing fails
"""
try:
signature = self.pkcs11_private_key.sign(
data,
mechanism=Mechanism.EDDSA,
)
return bytes(signature)
except Exception as e:
raise HSMOperationError(f"Ed25519 signing failed: {e}") from e
[docs]
def private_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PrivateFormat,
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
"""Not supported for HSM keys."""
self._raise_unsupported("private_bytes()")
[docs]
def private_bytes_raw(self) -> bytes:
"""Not supported for HSM keys."""
self._raise_unsupported("private_bytes_raw()")
[docs]
class PKCS11Ed25519PublicKey(ed25519.Ed25519PublicKey):
"""Ed25519 public key from HSM.
This class wraps the public key data extracted from HSM and provides
the standard cryptography library interface.
"""
def __init__(self, public_key_bytes: bytes) -> None:
"""Initialize Ed25519 public key.
Args:
public_key_bytes: 32-byte public key
"""
if len(public_key_bytes) != 32:
raise ValueError(f"Ed25519 public key must be 32 bytes, got {len(public_key_bytes)}")
self._public_key_bytes = public_key_bytes
# Create internal cryptography public key for verification
self._crypto_key = ed25519.Ed25519PublicKey.from_public_bytes(public_key_bytes)
[docs]
@classmethod
def from_pkcs11_key(
cls,
session: "Session",
pkcs11_key,
key_id: bytes | None = None,
key_label: str | None = None,
) -> "PKCS11Ed25519PublicKey":
"""Create public key from PKCS#11 public key object.
Args:
session: PKCS#11 session
pkcs11_key: PKCS#11 public key object
key_id: Key ID
key_label: Key label
Returns:
PKCS11Ed25519PublicKey instance
"""
# Get EC_POINT which contains the public key
ec_point = bytes(pkcs11_key[Attribute.EC_POINT])
# EC_POINT format varies by implementation
# Could be: raw 32 bytes, or OCTET STRING wrapped (04 20 <32 bytes>)
if len(ec_point) == 32:
public_key_bytes = ec_point
elif len(ec_point) == 34 and ec_point[0:2] == b'\x04\x20':
# OCTET STRING with length 32
public_key_bytes = ec_point[2:]
elif len(ec_point) > 32:
# Try extracting last 32 bytes
public_key_bytes = ec_point[-32:]
else:
raise ValueError(f"Invalid Ed25519 EC_POINT format: {ec_point.hex()}")
return cls(public_key_bytes)
[docs]
def public_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat,
) -> bytes:
"""Serialize public key."""
return self._crypto_key.public_bytes(encoding, format)
[docs]
def public_bytes_raw(self) -> bytes:
"""Return raw 32-byte public key."""
return self._public_key_bytes
[docs]
def verify(self, signature: bytes, data: bytes) -> None:
"""Verify a signature.
Args:
signature: 64-byte signature
data: Original data
Raises:
InvalidSignature: If verification fails
"""
self._crypto_key.verify(signature, data)
def __eq__(self, other: object) -> bool:
"""Check equality."""
if not isinstance(other, (PKCS11Ed25519PublicKey, ed25519.Ed25519PublicKey)):
return False
return self._public_key_bytes == other.public_bytes_raw()
def __hash__(self) -> int:
"""Hash based on public key bytes."""
return hash(self._public_key_bytes)
def __copy__(self) -> "PKCS11Ed25519PublicKey":
"""Create a copy."""
return PKCS11Ed25519PublicKey(self._public_key_bytes)
def __deepcopy__(self, memo: dict) -> "PKCS11Ed25519PublicKey":
"""Create a deep copy."""
return self.__copy__()