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 and P-256 are supported.
X25519 follows RFC 7748. 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 follows SEC 1 sections 3.2.1, 3.2.2.1, and 3.3.1 and RFC 8446 sections 4.2.8.2 and 7.4.2. Private scalars are
exactly 32 bytes and must lie in the interval [1,n-1].
Peer keys must be finite, on-curve, 65-byte uncompressed points; compressed TLS key shares are rejected.
Agreement retains leading zero octets in the fixed 32-byte x-coordinate result.
Private material and shared-secret results use protected storage with scoped plaintext access during computation.
Signing Keys
Key Generation and Import
SigningPrivateKey is the move-only entry point for TLS server
signing.
It generates Ed25519, 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 generation uses a fresh 32-octet seed from application secure randomness.
Generic data signing is available only through the purpose-separated
SignedByteBlock framing API.
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 signing scalars must have the exact curve width and satisfy the extreme-scalar policy below.
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 uses full-width rejection sampling from application secure randomness and derives the public point with
a fixed secret-scalar schedule.
The library adds an extreme-scalar policy to the sampling mechanism described in FIPS 186-5 section A.2.2: for curve
width b and subgroup order n, a signing scalar d must satisfy L <= d <= n-L, where L = 2^(b-64).
The lower bound is therefore 2^192 for P-256 and 2^320 for P-384. Both boundaries are included.
This rejects tiny or severely truncated private values and values extremely close to the group order.
For uniformly sampled valid scalars, the probability of rejection by this additional policy is approximately 2^-63.
The check does not establish entropy or detect every predictable key.
Generation silently erases rejected candidates and draws again, allowing at most 128 total candidate draws.
A successful 128th candidate is accepted; rejecting all 128 throws
erbsland::random::RandomFailure, a fatal
erbsland::err::LogicFailure.
An entropy-source failure propagates immediately without retrying.
Import applies the same scalar policy before public-point derivation and rejects affected keys, including keys accepted
by earlier library versions, with erbsland::err::ParseError.
Non-throwing import methods return an empty key; encrypted imports preserve their generic decryption failure.
Import never substitutes a new key for the supplied identity.
This policy applies to ECDSA signing-key generation and import, not to low-level curve arithmetic, key agreement, or
ECDSA per-message nonces.
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.
The EC interval restriction is an additional library policy, not a FIPS requirement.
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, SHA-384, and SHA-512 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.
Raw key-import factories report incorrect byte lengths with OutOfRangeError.
Invalid algorithm selections remain caller precondition failures.
Encrypted private-key imports reject empty passwords with CryptologyError.
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.
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.
This private key must not be empty.
- Parameters:
peer – The peer public key. Must not be empty and must use the same algorithm as this private key.
- Throws:
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.
This private key must not be empty.
Public Static Functions
-
static KeyAgreementPrivateKey generate(KeyAgreementAlgorithm algorithm)
Generate a private key with the application secure random source.
- Parameters:
algorithm – The key-agreement algorithm. Must be a valid supported algorithm.
-
static auto fromBytes(KeyAgreementAlgorithm algorithm, mem::ConstByteSpan data) -> KeyAgreementPrivateKey
Import exact private-key bytes into protected storage.
The borrowed source remains owned by the caller and is not erased.
- Parameters:
algorithm – The key-agreement algorithm. Must be a valid supported algorithm.
data – The exact encoded private key. Its length must match the selected algorithm.
- Throws:
err::OutOfRangeError – If the input length does not match the selected algorithm.
-
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.
- Parameters:
algorithm – The key-agreement algorithm. Must be a valid supported algorithm.
data – The exact encoded public key. Its length must match the selected algorithm.
-
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. This public key must not be empty.
- 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::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. This public key must not be empty.- 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::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. Must fit the fixed DER parser resource limits.
- Throws:
err::ParseError – If the DER or SubjectPublicKeyInfo structure is malformed.
- 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 auto fromFile(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic) noexcept -> PublicKey
Read one SubjectPublicKeyInfo file, returning an empty key on error.
-
static auto fromFileOrThrow(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic) -> PublicKey
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 Ed25519
Pure Ed25519 using a securely generated 32-octet seed.
-
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 Ed25519
-
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.
-
auto signTlsCertificateVerify(TlsSignatureScheme scheme, mem::ConstByteSpan message) const -> mem::ByteBlock
Sign exact TLS 1.3
CertificateVerifycontent.This signing key must not be empty.
- Parameters:
scheme – A compatible TLS 1.3 signature scheme.
message – The complete RFC 8446 section 4.4.3 signed content.
- Throws:
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.
This signing key must not be 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.
EC generation silently rejects extreme scalars; exhausting 128 draws throws
random::RandomFailure.
-
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.
- Parameters:
der – The complete unencrypted PKCS#8 DER key. Must fit the fixed parser resource limits.
- Throws:
err::ParseError – If DER, PKCS#8, or algorithm-specific key data is invalid or rejected by key policy.
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.- Parameters:
pem – The unencrypted PRIVATE KEY PEM block. Text and decoded key must fit the fixed parser resource limits.
- Throws:
err::ParseError – If PEM, DER, PKCS#8, or algorithm-specific key data is invalid.
CryptologyError – If private material cannot be protected.
-
static auto fromFile(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic) noexcept -> SigningPrivateKey
Read one unencrypted PKCS#8 key from a file, returning an empty key on error.
-
static auto fromFileOrThrow(const path::Path &path, PemDerFormat format = PemDerFormat::Automatic) -> SigningPrivateKey
Read one unencrypted PKCS#8 key from a file.
-
static auto fromEncryptedDer(const mem::ByteBlock &der, const text::String &password) noexcept -> SigningPrivateKey
Decrypt EncryptedPrivateKeyInfo DER, returning an empty key on failure.
-
static auto fromEncryptedDerOrThrow(const mem::ByteBlock &der, const text::String &password) -> SigningPrivateKey
Decrypt EncryptedPrivateKeyInfo DER.
- Throws:
CryptologyError – If decryption fails. Empty passwords are preserved for imported keys.
-
static auto fromEncryptedPem(const text::String &pem, const text::String &password) noexcept -> SigningPrivateKey
Decrypt one
ENCRYPTED PRIVATE KEYPEM block, returning an empty key on failure.
-
static auto fromEncryptedPemOrThrow(const text::String &pem, const text::String &password) -> SigningPrivateKey
Decrypt one
ENCRYPTED PRIVATE KEYPEM block.- Throws:
CryptologyError – If decryption fails. Empty passwords are preserved for imported keys.
-
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.
- Throws:
CryptologyError – If decryption fails. Empty passwords are preserved for imported keys.
-
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