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
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.
-
unit::ByteLength publicKeySize() const noexcept
Get the encoded public-key length.
-
unit::ByteLength privateKeySize() const noexcept
Get the encoded private-key length.
Get the shared-secret length.
-
CryptographicSecurity security() const noexcept
Get the coarse security level.
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.
-
constexpr KeyAgreementAlgorithm() noexcept = default
-
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.
-
KeyAgreementPrivateKey() noexcept = default
-
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 mem::ConstByteSpan span() const noexcept
Access a borrowed public-key view.
-
KeyAgreementPublicKey() noexcept = default
A move-only shared secret retained in application-protected storage.
See: Key Agreement and Signing
Public Functions
Create an empty shared-secret placeholder.
Securely erase the protected envelope.
Securely erase the protected envelope and restore the empty state.
Test whether no shared secret is stored.
Get the key-agreement algorithm.
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.
-
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
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:
trueonly if the signature is valid.
-
auto verifyTlsCertificateVerifySignature(TlsSignatureScheme scheme, mem::ConstByteSpan message, mem::ConstByteSpan signature) const -> bool
Verify a TLS 1.3
CertificateVerifysignature 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.
messagemust 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:
trueonly if the signature is valid.
-
inline const mem::ByteBlock &toDer() const noexcept
Get the exact canonical DER SubjectPublicKeyInfo.
-
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 KEYPEM block, returning an empty key on error.
-
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.
-
PublicKey() = default
-
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.
-
enumerator Unknown
-
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.
-
enumerator EcdsaP256
-
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
CertificateVerifycontent.- 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
CertificateVerifyscheme.
-
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
Unknownfor 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.
-
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 KEYPEM 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 KEYPEM block, returning an empty key on any error.
-
static SigningPrivateKey fromPemOrThrow(const text::String &pem)
Parse one strict unencrypted
PRIVATE KEYPEM 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 KEYPEM block, returning an empty key on failure.
-
static SigningPrivateKey fromEncryptedPemOrThrow(const text::String &pem, const text::String &password)
Decrypt one
ENCRYPTED PRIVATE KEYPEM 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.
-
SigningPrivateKey() noexcept = default
-
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.
-
enumerator Automatic