#RSA Signatures
#define FIO_RSA
#include "fio-stl.h"RSA here means signature work for TLS and X.509: verify PKCS#1 v1.5 signatures, verify RSA-PSS signatures, and create RSA-PSS signatures for TLS 1.3 CertificateVerify.
Supported modulus sizes are 2048, 3072, and 4096 bits. Smaller keys do not get a party hat.
Security note: this implementation has not been independently audited. Prefer a tested crypto library for high-value private keys, compliance, HSM integration, or anything that keeps auditors awake.
#Constants and Hash IDs
#define FIO_RSA_MAX_BITS 4096
#define FIO_RSA_MAX_BYTES (FIO_RSA_MAX_BITS / 8)
#define FIO_RSA_MAX_WORDS (FIO_RSA_MAX_BYTES / 8)typedef enum {
FIO_RSA_HASH_SHA256 = 0,
FIO_RSA_HASH_SHA384 = 1,
FIO_RSA_HASH_SHA512 = 2,
} fio_rsa_hash_e;hash_len must match the selected hash: 32 bytes for SHA-256, 48 for SHA-384, and 64 for SHA-512.
#Key Types
#fio_rsa_pubkey_s
typedef struct {
const uint8_t *n;
size_t n_len;
const uint8_t *e;
size_t e_len;
} fio_rsa_pubkey_s;Public key for verification. n is the modulus and e is the public exponent, both as big-endian byte arrays. This matches the DER layout used in X.509 certificates.
#fio_rsa_privkey_s
typedef struct {
const uint8_t *n; size_t n_len;
const uint8_t *d; size_t d_len;
const uint8_t *e; size_t e_len;
const uint8_t *p; size_t p_len;
const uint8_t *q; size_t q_len;
const uint8_t *dP; size_t dP_len;
const uint8_t *dQ; size_t dQ_len;
const uint8_t *qInv; size_t qInv_len;
} fio_rsa_privkey_s;Private key for RSA-PSS signing. n and d are required. The public exponent e and CRT fields (p, q, dP, dQ, qInv) are optional.
When CRT parameters are available, signing uses CRT with message blinding. If only n and d are available, signing falls back to the non-CRT path.
#Verification
#fio_rsa_verify_pkcs1
SFUNC int fio_rsa_verify_pkcs1(const uint8_t *sig,
size_t sig_len,
const uint8_t *msg_hash,
size_t hash_len,
fio_rsa_hash_e hash_alg,
const fio_rsa_pubkey_s *key);Verifies an RSA PKCS#1 v1.5 signature with DigestInfo encoding.
This covers the X.509 signature algorithms commonly named:
sha256WithRSAEncryptionsha384WithRSAEncryptionsha512WithRSAEncryption
Parameters:
sig: signature bytes; length must match the modulus length.sig_len: signature length in bytes.msg_hash: pre-computed message hash.hash_len: 32, 48, or 64 bytes.hash_alg: matchingFIO_RSA_HASH_*value.key: RSA public key.
Returns 0 for a valid signature, -1 on invalid input or a bad signature.
#fio_rsa_verify_pss
SFUNC int fio_rsa_verify_pss(const uint8_t *sig,
size_t sig_len,
const uint8_t *msg_hash,
size_t hash_len,
fio_rsa_hash_e hash_alg,
const fio_rsa_pubkey_s *key);Verifies an RSA-PSS signature. This is the RSA signature style TLS 1.3 uses for CertificateVerify.
The implementation uses:
- MGF1 with the same hash function;
- salt length equal to the hash length;
- trailer byte
0xBC.
Returns 0 for a valid signature, -1 on invalid input or a bad signature.
#Signing
#fio_rsa_sign_pss
SFUNC int fio_rsa_sign_pss(uint8_t *signature,
size_t *sig_len,
const uint8_t *msg_hash,
size_t hash_len,
fio_rsa_hash_e hash_alg,
const fio_rsa_privkey_s *key);Creates an RSA-PSS signature using RSASSA-PSS-SIGN from RFC 8017, with the TLS 1.3 settings listed above.
signaturemust have room forkey->n_lenbytes.sig_lenreceives the signature length, which equalskey->n_lenon success.msg_hashis already hashed; this function does not hash the message.keymust includenandd; CRT fields are optional.
Returns 0 on success, -1 on error.
PKCS#1 v1.5 signing is not exposed. TLS 1.3 does not want it for CertificateVerify, and this header agrees.
#Example: Verify an RSA-PSS Signature
#define FIO_RSA
#define FIO_SHA2
#include "fio-stl.h"
int verify_pss(const uint8_t *sig,
size_t sig_len,
const uint8_t *modulus,
size_t modulus_len,
const uint8_t *exponent,
size_t exponent_len,
const void *message,
size_t message_len) {
fio_rsa_pubkey_s key = {
.n = modulus,
.n_len = modulus_len,
.e = exponent,
.e_len = exponent_len,
};
fio_u256 hash = fio_sha256(message, message_len);
return fio_rsa_verify_pss(sig, sig_len, hash.u8, 32,
FIO_RSA_HASH_SHA256, &key);
}#Practical Notes
- The signature length must match the modulus length.
- The public exponent is usually
65537(0x010001), but the API accepts DER-style big-endian bytes. - The module works on pre-computed hashes. Pick the hash to match the certificate or protocol signature algorithm.
- For X.509 parsing, this module is normally used through X.509, which extracts the RSA key and dispatches verification.