Key Agreement and Signing

Key Agreement

KeyAgreementPrivateKey is move-only and stores its private scalar in a protected block. It caches the ordinary KeyAgreementPublicKey, so accessing the public key does not decrypt private material. Create a key with generate() or import exact private bytes with fromBytes(). There is intentionally no raw private-key accessor.

agree() accepts a public key with the same KeyAgreementAlgorithm and returns a move-only KeyAgreementSharedSecret. Pass that shared secret directly to Hkdf extract() to derive keying material through scoped plaintext access.

X25519 is the first supported algorithm. It clamps private scalars, accepts the exact 32-byte peer encodings permitted by RFC 7748, masks the peer coordinate’s top bit, and rejects an all-zero agreement result. P-256 and P-384 can be added through the same generic dispatch later. Platform-backed key-agreement private-key handles are not part of this API milestone; native references here belong only to protected-data providers.

Signing Keys

Key Generation and Import

SigningPrivateKey is the move-only entry point for TLS server signing. It generates ECDSA P-256/P-384 and two-prime RSA-2048/3072/4096 keys, or imports one unencrypted PKCS#8 PrivateKeyInfo version 0 from canonical DER or one exact PRIVATE KEY PEM block. The default SigningKeyProfile is ECDSA P-256. The profile enum deliberately fixes every algorithm parameter instead of exposing unsafe individual knobs. Ed25519 import and signing remain available, but Ed25519 has no generation profile.

The importer accepts RFC 8410 Ed25519 seeds, RFC 5915 P-256 and P-384 keys wrapped by id-ecPublicKey, and RFC 8017 two-prime RSA keys wrapped by rsaEncryption or a supported id-RSASSA-PSS identifier. It rejects encrypted and legacy top-level containers, PKCS#8 attributes, trailing blocks or values, unsupported curves, multiprime RSA, and algorithm parameters outside this profile.

Import validates the derived public value. P-256 and P-384 scalars must have the curve width and lie in the range 1..n-1. RSA moduli are limited to 2048–8192 bits, public exponents are odd and greater than 65536, and every modulus, exponent, CRT exponent, and CRT coefficient relationship is checked before the normalized secret is transferred into ProtectedByteBlock.

ECDSA generation samples scalars by rejection from application secure randomness and derives the public point with a fixed secret-scalar schedule. RSA generation uses e=65537, exact-width random odd candidates, trial division, 25 Miller–Rabin rounds with fresh random bases, prime-separation and private-exponent checks, CRT derivation, and a final public/private consistency validation. It uses the signing implementation’s bounded fixed-width integer layer for secret arithmetic, not the general-purpose decimal integer API. These choices follow the applicable FIPS 186-5 requirements, but the library has not been submitted for FIPS validation and makes no certification claim.

Key Serialization and Files

toDer() and toPem() reconstruct canonical unencrypted PKCS#8 while publicKey() returns an immutable PublicKey that serializes as SubjectPublicKeyInfo DER or PUBLIC KEY PEM. Private-key DER and PEM values are marked sensitive. File writes use create-new behavior; private-key files additionally request user-only access.

PemDerFormat is shared by keys, public keys, certificates, and requests. Automatic output recognizes .key /.p8 for private keys, .pub for public keys, and the common .pem and .der suffixes. Unknown suffixes require an explicit format.

Password-Protected PKCS#8

Encrypted output uses RFC 8018 PBES2 with PBKDF2-HMAC-SHA-256, a fresh 16-octet salt, 1,000,000 iterations, and AES-256-CBC with a fresh 16-octet IV and PKCS#7 padding. The reader accepts this explicit profile with bounded input and an iteration limit of 10,000,000. Empty passwords are rejected. Password UTF-8 copies, derived keys, decrypted PKCS#8, padding buffers, and intermediate secret values are explicitly erased, and every wrong-password or invalid-padding path reports only a generic decryption failure.

Signing Algorithms

SigningKeyAlgorithm distinguishes Ed25519, ECDSA P-256, ECDSA P-384, and RSA keys. supports() applies the complete TLS 1.3 key-family and RSA-PSS restriction matrix. Certificate-only PKCS#1 signature schemes are never accepted for CertificateVerify.

Pure Ed25519 follows RFC 8032 sections 5.1.5 and 5.1.6. Secret scalar multiplication uses a separate fixed 256-bit schedule from the public verification path. ECDSA follows FIPS 186-5 section 6.4.1 with P-256/SHA-256 or P-384/SHA-384 and RFC 6979 section 3.2 deterministic nonces. Its signature output is canonical DER.

RSA-PSS follows RFC 8017 sections 5.1.2, 8.1.1, and 9.1.1. SHA-256 and SHA-384 use digest-sized random salts. Private exponentiation uses fixed-width Montgomery schedules, message blinding, and two-prime CRT acceleration. Every result is verified with the cached public key before it leaves the signing operation. Secret scalars, expanded seeds, nonces, RSA components, blinding values, and arithmetic scratch are guarded for erasure on normal and exceptional exits.

Interface

class KeyAgreementAlgorithm

A supported asymmetric key-agreement algorithm.

See: Key Agreement and Signing

Public Types

enum Value

Raw key-agreement algorithm value.

Values:

enumerator X25519

X25519 over Curve25519.

Public Functions

constexpr KeyAgreementAlgorithm() noexcept = default

Create the default X25519 algorithm.

inline constexpr KeyAgreementAlgorithm(const Value value) noexcept

Create an algorithm from its raw value.

inline constexpr Value toRawValue() const noexcept

Get the raw algorithm value.

unit::ByteLength publicKeySize() const noexcept

Get the encoded public-key length.

unit::ByteLength privateKeySize() const noexcept

Get the encoded private-key length.

unit::ByteLength sharedSecretSize() const noexcept

Get the shared-secret length.

CryptographicSecurity security() const noexcept

Get the coarse security level.

text::String toString() const

Convert the algorithm to its stable lowercase identifier.

Public Static Functions

static std::optional<KeyAgreementAlgorithm> fromString(const text::String &text) noexcept

Parse an exact lowercase algorithm identifier.

static KeyAgreementAlgorithm fromStringOrThrow(const text::String &text)

Parse an exact lowercase algorithm identifier or throw.

Throws:

err::ParseError – If the identifier is unsupported.

static std::span<const KeyAgreementAlgorithm> all() noexcept

Get all supported key-agreement algorithms.

class KeyAgreementPrivateKey

A move-only private key retained in application-protected storage.

See: Key Agreement and Signing

Public Functions

KeyAgreementPrivateKey() noexcept = default

Create an empty private-key placeholder.

~KeyAgreementPrivateKey()

Securely erase the protected private-key envelope.

KeyAgreementSharedSecret agree(const KeyAgreementPublicKey &peer) const

Agree with a peer public key.

Throws:
  • err::LogicError – If this private key or the peer key is empty.

  • err::ParameterError – If the algorithms do not match.

  • CryptologyError – If agreement produces the forbidden all-zero result.

Returns:

The protected shared secret.

void secureErase() noexcept

Securely erase private material and restore the empty state.

inline bool isEmpty() const noexcept

Test whether no private key is stored.

inline KeyAgreementAlgorithm algorithm() const noexcept

Get the key-agreement algorithm.

KeyAgreementPublicKey publicKey() const

Get the cached public key without decrypting private material.

Throws:

err::LogicError – If this private key is empty.

Public Static Functions

static KeyAgreementPrivateKey generate(KeyAgreementAlgorithm algorithm)

Generate a private key with the application secure random source.

Throws:

err::ParameterError – If the algorithm is invalid.

static KeyAgreementPrivateKey fromBytes(KeyAgreementAlgorithm algorithm, mem::ConstByteSpan data)

Import exact private-key bytes into protected storage.

The borrowed source remains owned by the caller and is not erased.

Throws:

err::ParameterError – If the algorithm or private-key length is invalid.

class KeyAgreementPublicKey

A public key for one generic key-agreement algorithm.

See: Key Agreement and Signing

Public Functions

KeyAgreementPublicKey() noexcept = default

Create an empty public key.

KeyAgreementPublicKey(KeyAgreementAlgorithm algorithm, mem::ConstByteSpan data)

Create a public key by copying its exact encoded bytes.

Throws:

err::ParameterError – If the algorithm or key length is invalid.

inline bool isEmpty() const noexcept

Test whether no public key is stored.

inline KeyAgreementAlgorithm algorithm() const noexcept

Get the key-agreement algorithm.

inline const mem::ByteBlock &data() const noexcept

Access the owning public-key bytes.

inline mem::ConstByteSpan span() const noexcept

Access a borrowed public-key view.

class KeyAgreementSharedSecret

A move-only shared secret retained in application-protected storage.

See: Key Agreement and Signing

Public Functions

KeyAgreementSharedSecret() noexcept = default

Create an empty shared-secret placeholder.

~KeyAgreementSharedSecret()

Securely erase the protected envelope.

void secureErase() noexcept

Securely erase the protected envelope and restore the empty state.

inline bool isEmpty() const noexcept

Test whether no shared secret is stored.

inline KeyAgreementAlgorithm algorithm() const noexcept

Get the key-agreement algorithm.

inline unit::ByteLength byteLength() const noexcept

Get the shared-secret length without decrypting it.

class PublicKey

An immutable public-key facade for X.509 SubjectPublicKeyInfo.

RSA verification follows RFC 8017 sections 8 and 9 and RFC 4055 section 3. ECDSA verification follows FIPS 186-5 section 6.4.2, RFC 5480 sections 2.1.1 and 2.2, and RFC 5758 section 3.2. Ed25519 verification follows RFC 8032 section 5.1.7 and RFC 8410 sections 3, 4, and 6.

See: X.509 Certificates

Public Functions

PublicKey() = default

Create an empty public key.

inline bool isEmpty() const noexcept

Test if this facade contains SubjectPublicKeyInfo.

inline const X509AlgorithmIdentifier &algorithm() const noexcept

Get the public-key algorithm identifier.

inline const mem::ByteBlock &keyData() const noexcept

Get the subjectPublicKey BIT STRING data without its unused-bit-count octet.

inline uint8_t unusedBitCount() const noexcept

Get the number of unused low bits in the final key-data octet.

inline const Asn1Node &asn1() const noexcept

Get the exact SubjectPublicKeyInfo ASN.1 node.

auto verifySignature(const X509AlgorithmIdentifier &signatureAlgorithm, mem::ConstByteSpan message, mem::ConstByteSpan signature) const -> bool

Verify a supported X.509 signature over an exact message.

RSA-PSS and RSASSA-PKCS1-v1_5 with SHA-256/SHA-384 follow RFC 8017 and RFC 4055. ECDSA P-256/SHA-256 and P-384/SHA-384 follow FIPS 186-5 section 6.4.2, RFC 5480, and RFC 5758. ECDSA signatures use canonical DER and accept compressed or uncompressed validated public points. Pure Ed25519 follows RFC 8032 section 5.1.7 and RFC

  1. Ed25519ctx, Ed25519ph, and Ed448 are not accepted.

Parameters:
  • signatureAlgorithm – The complete X.509 signature AlgorithmIdentifier.

  • message – The exact message bytes covered by the signature.

  • signature – The signature octets without an ASN.1 BIT STRING unused-bit-count octet.

Throws:
  • err::LogicError – If this public key is empty.

  • err::ParseError – If the key or algorithm encoding is malformed, unsupported, or outside fixed bounds.

Returns:

true only if the signature is valid.

auto verifyTlsCertificateVerifySignature(TlsSignatureScheme scheme, mem::ConstByteSpan message, mem::ConstByteSpan signature) const -> bool

Verify a TLS 1.3 CertificateVerify signature over exact caller-supplied content.

This operation follows RFC 8446 sections 4.2.3 and 4.4.3. It rejects the certificate-only PKCS#1 v1.5 schemes, enforces the RSAE versus RSASSA-PSS public-key OID distinction, and uses digest-sized PSS salts. message must already contain the RFC 8446 section 4.4.3 pad, context string, separator, and transcript hash. All inputs and intermediates are public verification data; no secret state requiring erasure is created.

Parameters:
  • scheme – The supported TLS SignatureScheme received in CertificateVerify.

  • message – The exact content covered by the TLS signature.

  • signature – The signature octets from the TLS message.

Throws:
  • err::LogicError – If this public key is empty.

  • err::ParseError – If the scheme is forbidden, the key is incompatible, or an encoding is malformed.

Returns:

true only if the signature is valid.

inline const mem::ByteBlock &toDer() const noexcept

Get the exact canonical DER SubjectPublicKeyInfo.

text::String toPem() const

Encode this key as one RFC 7468 PUBLIC KEY block.

void writeToFile(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic) const

Write this public key without replacing an existing file.

Public Static Functions

static PublicKey fromDer(const mem::ByteBlock &der) noexcept

Parse one canonical DER SubjectPublicKeyInfo, returning an empty key on error.

Parameters:

der – The complete DER SubjectPublicKeyInfo.

Returns:

The parsed public key, or an empty key on error.

static PublicKey fromDerOrThrow(const mem::ByteBlock &der)

Parse one canonical DER SubjectPublicKeyInfo.

Parameters:

der – The complete DER SubjectPublicKeyInfo.

Throws:
  • err::ParseError – If the DER or SubjectPublicKeyInfo structure is malformed.

  • err::OutOfRangeError – If a fixed parser resource limit is exceeded.

Returns:

The parsed public key.

static PublicKey fromPem(const text::String &pem) noexcept

Parse one strict PUBLIC KEY PEM block, returning an empty key on error.

static PublicKey fromPemOrThrow(const text::String &pem)

Parse one strict PUBLIC KEY PEM block.

static PublicKey fromFile(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic) noexcept

Read one SubjectPublicKeyInfo file, returning an empty key on error.

static PublicKey fromFileOrThrow(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic)

Read one SubjectPublicKeyInfo file.

enum class erbsland::cryptology::SigningKeyAlgorithm : uint8_t

A private-key algorithm supported for TLS 1.3 signing.

Values:

enumerator Unknown

No signing-key algorithm.

enumerator Ed25519

Pure Ed25519 using a 32-octet private seed.

enumerator EcdsaP256

ECDSA over NIST P-256 with SHA-256.

enumerator EcdsaP384

ECDSA over NIST P-384 with SHA-384.

enumerator Rsa

Two-prime RSA for RSASSA-PSS.

enum class erbsland::cryptology::SigningKeyProfile : uint8_t

A modern signing-key generation profile with fixed safe parameters.

Values:

enumerator EcdsaP256

ECDSA over NIST P-256.

enumerator EcdsaP384

ECDSA over NIST P-384.

enumerator Rsa2048

Two-prime RSA with a 2048-bit modulus and e=65537.

enumerator Rsa3072

Two-prime RSA with a 3072-bit modulus and e=65537.

enumerator Rsa4096

Two-prime RSA with a 4096-bit modulus and e=65537.

class SigningPrivateKey

A move-only private signing key retained in application-protected storage.

Keys use canonical PKCS#8. Ed25519 follows RFC 8410 section 7 and RFC 8032 sections 5.1.5-5.1.6; ECDSA P-256/P-384 follows RFC 5915 and FIPS 186-5 section 6.4.1; RSA-PSS follows RFC 5208 and RFC 8017 sections 8.1 and 9.1. Serialization exposes private material only through explicitly sensitive return values or user-only files.

See: Key Agreement and Signing

Public Functions

SigningPrivateKey() noexcept = default

Create an empty private-key placeholder.

~SigningPrivateKey()

Securely erase protected private material.

mem::ByteBlock signTlsCertificateVerify(TlsSignatureScheme scheme, mem::ConstByteSpan message) const

Sign exact TLS 1.3 CertificateVerify content.

Parameters:
  • scheme – A compatible TLS 1.3 signature scheme.

  • message – The complete RFC 8446 section 4.4.3 signed content.

Throws:
  • err::LogicError – If this key is empty.

  • err::ParameterError – If the scheme is incompatible with the key.

  • CryptologyError – If protected storage, randomness, signing, or the RSA fault check fails.

Returns:

The signature bytes used in CertificateVerify.

void secureErase() noexcept

Securely erase private material and restore the empty state.

inline bool isEmpty() const noexcept

Test whether no private key is stored.

bool supports(TlsSignatureScheme scheme) const noexcept

Test whether this key supports a TLS 1.3 CertificateVerify scheme.

bool matches(const PublicKey &publicKey) const

Test whether this key semantically matches a public key.

inline SigningKeyAlgorithm algorithm() const noexcept

Get the signing-key algorithm, or Unknown for an empty key.

PublicKey publicKey() const

Get the cached public key without decrypting private material.

Throws:

err::LogicError – If this key is empty.

mem::ByteBlock toDer() const

Encode this key as canonical unencrypted PKCS#8 DER in sensitive memory.

text::String toPem() const

Encode this key as one PRIVATE KEY PEM block in sensitive memory.

void writeToFile(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic) const

Write this key without replacing an existing file and with user-only access.

mem::ByteBlock toEncryptedDer(const text::String &password) const

Encrypt this key as PBES2 EncryptedPrivateKeyInfo DER.

text::String toEncryptedPem(const text::String &password) const

Encrypt this key as one ENCRYPTED PRIVATE KEY PEM block.

void writeEncryptedToFile(const path::Path &path, const text::String &password, PemDerFormat format = PemDerFormat::Automatic) const

Write an encrypted key without replacing an existing file and with user-only access.

Public Static Functions

static SigningPrivateKey generate(SigningKeyProfile profile = SigningKeyProfile::EcdsaP256)

Generate a signing key from application secure randomness.

static SigningPrivateKey fromDer(const mem::ByteBlock &der) noexcept

Parse one strict unencrypted PKCS#8 DER key, returning an empty key on any error.

static SigningPrivateKey fromDerOrThrow(const mem::ByteBlock &der)

Parse one strict unencrypted PKCS#8 DER key.

Throws:
  • err::ParseError – If DER, PKCS#8, or algorithm-specific key data is invalid.

  • err::OutOfRangeError – If a fixed resource bound is exceeded.

  • CryptologyError – If private material cannot be protected.

static SigningPrivateKey fromPem(const text::String &pem) noexcept

Parse one strict unencrypted PRIVATE KEY PEM block, returning an empty key on any error.

static SigningPrivateKey fromPemOrThrow(const text::String &pem)

Parse one strict unencrypted PRIVATE KEY PEM block.

Throws:
  • err::ParseError – If PEM, DER, PKCS#8, or algorithm-specific key data is invalid.

  • err::OutOfRangeError – If a fixed resource bound is exceeded.

  • CryptologyError – If private material cannot be protected.

static SigningPrivateKey fromFile(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic) noexcept

Read one unencrypted PKCS#8 key from a file, returning an empty key on error.

static SigningPrivateKey fromFileOrThrow(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic)

Read one unencrypted PKCS#8 key from a file.

static SigningPrivateKey fromEncryptedDer(const mem::ByteBlock &der, const text::String &password) noexcept

Decrypt EncryptedPrivateKeyInfo DER, returning an empty key on failure.

static SigningPrivateKey fromEncryptedDerOrThrow(const mem::ByteBlock &der, const text::String &password)

Decrypt EncryptedPrivateKeyInfo DER.

static SigningPrivateKey fromEncryptedPem(const text::String &pem, const text::String &password) noexcept

Decrypt one ENCRYPTED PRIVATE KEY PEM block, returning an empty key on failure.

static SigningPrivateKey fromEncryptedPemOrThrow(const text::String &pem, const text::String &password)

Decrypt one ENCRYPTED PRIVATE KEY PEM block.

static auto fromEncryptedFile(const path::Path &path, const text::String &password, PemDerFormat format = PemDerFormat::Automatic) noexcept -> SigningPrivateKey

Read and decrypt one encrypted PKCS#8 key, returning an empty key on failure.

static auto fromEncryptedFileOrThrow(const path::Path &path, const text::String &password, PemDerFormat format = PemDerFormat::Automatic) -> SigningPrivateKey

Read and decrypt one encrypted PKCS#8 key.

enum class erbsland::cryptology::PemDerFormat : uint8_t

The binary or textual representation of a DER-based cryptographic artifact.

Values:

enumerator Automatic

Select from a recognized artifact-specific suffix or input content.

enumerator Pem

RFC 7468 textual representation.

enumerator Der

Canonical DER representation.