Skip to content

Commit

Permalink
Merge pull request #568 from lukpueh/mv-spx
Browse files Browse the repository at this point in the history
signer: move spx keygen, sign and verify API
  • Loading branch information
lukpueh committed May 24, 2023
2 parents f1997d2 + c0671c4 commit d85e2f0
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 192 deletions.
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ ignore_missing_imports = True

[mypy-sigstore_protobuf_specs.*]
ignore_missing_imports = True

[mypy-pyspx.*]
ignore_missing_imports = True
10 changes: 0 additions & 10 deletions securesystemslib/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@
SCHEMA.String("ed25519"),
SCHEMA.String("ecdsa"),
SCHEMA.RegularExpression(r"ecdsa-sha2-nistp(256|384)"),
SCHEMA.String("sphincs"),
]
)

Expand Down Expand Up @@ -283,27 +282,18 @@
# An ED25519 raw public key, which must be 32 bytes.
ED25519PUBLIC_SCHEMA = SCHEMA.LengthBytes(32)

SPHINCSPUBLIC_SCHEMA = SCHEMA.LengthBytes(32)

# An ED25519 raw seed key, which must be 32 bytes.
ED25519SEED_SCHEMA = SCHEMA.LengthBytes(32)

SPHINCSPRIVATE_SCHEMA = SCHEMA.LengthBytes(64)

# An ED25519 raw signature, which must be 64 bytes.
ED25519SIGNATURE_SCHEMA = SCHEMA.LengthBytes(64)

SPHINCSSIGNATURE_SCHEMA = SCHEMA.LengthBytes(7_856)

# An ECDSA signature.
ECDSASIGNATURE_SCHEMA = SCHEMA.AnyBytes()

# Ed25519 signature schemes. The vanilla Ed25519 signature scheme is currently
# supported.
ED25519_SIG_SCHEMA = SCHEMA.OneOf([SCHEMA.String("ed25519")])

SPHINCS_SIG_SCHEMA = SCHEMA.OneOf([SCHEMA.String("sphincs-shake-128s")])

# An ed25519 key.
ED25519KEY_SCHEMA = SCHEMA.Object(
object_name="ED25519KEY_SCHEMA",
Expand Down
42 changes: 0 additions & 42 deletions securesystemslib/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
formats,
rsa_keys,
settings,
sphincs_keys,
util,
)
from securesystemslib.hash import digest
Expand Down Expand Up @@ -344,33 +343,6 @@ def generate_ed25519_key(scheme="ed25519"):
return ed25519_key


def generate_sphincs_key(scheme="sphincs-shake-128s"):
"""Generate a SPHINCS+ key pair.
Arguments:
scheme (str): Name of the scheme as defined in formats.py.
Returns:
dict: A dictionary containing the SPHINCS+ keys.
Raises:
UnsupportedLibraryError: In case pyspx is not available.
"""
formats.SPHINCS_SIG_SCHEMA.check_match(scheme)

sphincs_key = {}
keytype = "sphincs"
public, private = sphincs_keys.generate_public_and_private()

key_value = {"public": public.hex(), "private": private.hex()}
keyid = _get_keyid(keytype, scheme, key_value)

sphincs_key["keytype"] = keytype
sphincs_key["scheme"] = scheme
sphincs_key["keyid"] = keyid
sphincs_key["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS
sphincs_key["keyval"] = key_value

return sphincs_key


def format_keyval_to_metadata(keytype, scheme, key_value, private=False):
"""
<Purpose>
Expand Down Expand Up @@ -716,11 +688,6 @@ def create_signature(key_dict, data):
elif keytype in ["ecdsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]:
sig, scheme = ecdsa_keys.create_signature(public, private, data, scheme)

elif keytype == "sphincs":
sig, scheme = sphincs_keys.create_signature(
bytes.fromhex(public), bytes.fromhex(private), data, scheme
)

# 'securesystemslib.formats.ANYKEY_SCHEMA' should have detected invalid key
# types. This is a defensive check against an invalid key type.
else: # pragma: no cover
Expand Down Expand Up @@ -879,15 +846,6 @@ def verify_signature(
raise exceptions.UnsupportedAlgorithmError(
"Unsupported" " signature scheme is specified: " + repr(scheme)
)
elif keytype == "sphincs":
if scheme == "sphincs-shake-128s":
valid_signature = sphincs_keys.verify_signature(
bytes.fromhex(public), scheme, sig, data
)
else:
raise exceptions.UnsupportedAlgorithmError(
"Unsupported" " signature scheme is specified: " + repr(scheme)
)

# 'securesystemslib.formats.ANYKEY_SCHEMA' should have detected invalid key
# types. This is a defensive check against an invalid key type.
Expand Down
7 changes: 6 additions & 1 deletion securesystemslib/signer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
SSlibSigner,
)
from securesystemslib.signer._sigstore_signer import SigstoreKey, SigstoreSigner
from securesystemslib.signer._spx_signer import (
SpxKey,
SpxSigner,
generate_spx_key_pair,
)

# Register supported private key uri schemes and the Signers implementing them
SIGNER_FOR_URI_SCHEME.update(
Expand Down Expand Up @@ -44,7 +49,7 @@
("rsa", "rsa-pkcs1v15-sha256"): SSlibKey,
("rsa", "rsa-pkcs1v15-sha384"): SSlibKey,
("rsa", "rsa-pkcs1v15-sha512"): SSlibKey,
("sphincs", "sphincs-shake-128s"): SSlibKey,
("sphincs", "sphincs-shake-128s"): SpxKey,
("rsa", "pgp+rsa-pkcsv1.5"): GPGKey,
("dsa", "pgp+dsa-fips-180-2"): GPGKey,
("eddsa", "pgp+eddsa-ed25519"): GPGKey,
Expand Down
2 changes: 1 addition & 1 deletion securesystemslib/signer/_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def verify_signature(self, signature: Signature, data: bytes) -> None:


class SSlibKey(Key):
"""Key implementation for RSA, Ed25519, ECDSA and Sphincs keys"""
"""Key implementation for RSA, Ed25519, ECDSA keys"""

def to_securesystemslib_key(self) -> Dict[str, Any]:
"""Internal helper, returns a classic securesystemslib keydict"""
Expand Down
134 changes: 134 additions & 0 deletions securesystemslib/signer/_spx_signer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""Signer implementation for project SPHINCS+ post-quantum signature support.
"""
import logging
import os
from typing import Any, Dict, Optional, Tuple

from securesystemslib.exceptions import (
UnsupportedLibraryError,
UnverifiedSignatureError,
VerificationError,
)
from securesystemslib.signer._key import Key
from securesystemslib.signer._signature import Signature
from securesystemslib.signer._signer import SecretsHandler, Signer

SPX_IMPORT_ERROR = None
try:
from pyspx import shake_128s
except ImportError:
SPX_IMPORT_ERROR = "spinhcs+ key support requires the pyspx library"

_SHAKE_SEED_LEN = 48

logger = logging.getLogger(__name__)


def generate_spx_key_pair() -> Tuple[bytes, bytes]:
"""Generate SPHINCS+ key pair and return public and private bytes."""
if SPX_IMPORT_ERROR:
raise UnsupportedLibraryError(SPX_IMPORT_ERROR)

seed = os.urandom(_SHAKE_SEED_LEN)
public, private = shake_128s.generate_keypair(seed)

return public, private


class SpxKey(Key):
"""SPHINCS+ verifier."""

DEFAULT_KEY_TYPE = "sphincs"
DEFAULT_SCHEME = "sphincs-shake-128s"

@classmethod
def from_dict(cls, keyid: str, key_dict: Dict[str, Any]) -> "SpxKey":
keytype, scheme, keyval = cls._from_dict(key_dict)
return cls(keyid, keytype, scheme, keyval, key_dict)

@classmethod
def from_bytes(cls, public: bytes) -> "SpxKey":
"""Create SpxKey instance from public key bytes."""
keytype = cls.DEFAULT_KEY_TYPE
scheme = cls.DEFAULT_SCHEME
keyval = {"public": public.hex()}

keyid = SpxSigner._get_keyid( # pylint: disable=protected-access
keytype, scheme, keyval
)
return cls(keyid, keytype, scheme, keyval)

def to_dict(self) -> Dict[str, Any]:
return self._to_dict()

def verify_signature(self, signature: Signature, data: bytes) -> None:
valid = None
try:
if SPX_IMPORT_ERROR:
raise UnsupportedLibraryError(SPX_IMPORT_ERROR)

key = bytes.fromhex(self.keyval["public"])
sig = bytes.fromhex(signature.signature)

valid = shake_128s.verify(data, sig, key)

except Exception as e:
logger.info("Key %s failed to verify sig: %s", self.keyid, str(e))
raise VerificationError(
f"Unknown failure to verify signature by {self.keyid}"
) from e

if not valid:
raise UnverifiedSignatureError(
f"Failed to verify signature by {self.keyid}"
)


class SpxSigner(Signer):
"""SPHINCS+ signer.
Usage::
public_bytes, private_bytes = generate_spx_key_pair()
public_key = SpxKey.from_bytes(public_bytes)
signer = SpxSigner(private_bytes, public_key)
signature = signer.sign(b"payload")
# Use public_key.to_dict() / Key.from_dict() to transport public key data
public_key = signer.public_key
public_key.verify_signature(signature, b"payload")
"""

def __init__(self, private: bytes, public: SpxKey):
self.private_key = private
self.public_key = public

@classmethod
def from_priv_key_uri(
cls,
priv_key_uri: str,
public_key: Key,
secrets_handler: Optional[SecretsHandler] = None,
) -> "SpxSigner":
raise NotImplementedError

def sign(self, payload: bytes) -> Signature:
"""Signs payload with SPHINCS+ private key on the instance.
Arguments:
payload: bytes to be signed.
Raises:
UnsupportedLibraryError: PySPX is not available.
Returns:
Signature.
"""
if SPX_IMPORT_ERROR:
raise UnsupportedLibraryError(SPX_IMPORT_ERROR)

raw = shake_128s.sign(payload, self.private_key)
return Signature(self.public_key.keyid, raw.hex())
95 changes: 0 additions & 95 deletions securesystemslib/sphincs_keys.py

This file was deleted.

Loading

0 comments on commit d85e2f0

Please sign in to comment.