facil.io

#RSA Signatures

c
#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

c
#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)
c
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

c
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

c
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

c
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:

  • sha256WithRSAEncryption
  • sha384WithRSAEncryption
  • sha512WithRSAEncryption

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: matching FIO_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

c
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

c
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.

  • signature must have room for key->n_len bytes.
  • sig_len receives the signature length, which equals key->n_len on success.
  • msg_hash is already hashed; this function does not hash the message.
  • key must include n and d; 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

c
#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.