ring-native-ossl 0.1.8

A ring-compatible API backed by native-ossl (OpenSSL)
Documentation
//! OpenSSL-backed implementation of a `ring`-compatible cryptography API.
//!
//! This crate mirrors the public surface of several `ring` modules so that
//! code written against `ring` can be compiled against OpenSSL instead, without
//! pulling in `ring` itself.  All cryptographic operations are delegated to
//! OpenSSL through the `native-ossl` crate.
//!
//! # Modules mirrored
//!
//! | Module | `ring` counterpart |
//! |---|---|
//! | [`aead`] | `ring::aead` |
//! | [`agreement`] | `ring::agreement` |
//! | [`digest`] | `ring::digest` |
//! | [`error`] | `ring::error` |
//! | [`hkdf`] | `ring::hkdf` |
//! | [`hmac`] | `ring::hmac` |
//! | [`rand`] | `ring::rand` |
//! | [`signature`] | `ring::signature` |
//!
//! The internal `spki` module is not public; it holds the shared
//! `SubjectPublicKeyInfo` DER header constants used by `agreement` and `signature`.
//!
//! # What is not included
//!
//! This crate does not reproduce `ring`-internal sealed-trait hierarchies.  The
//! [`rand::SecureRandom`] trait is defined in this crate and is used as a bound
//! in [`agreement`] and [`signature`]; callers should use it in place of
//! `ring::rand::SecureRandom`.
//!
//! RSA key generation is not implemented; RSA keys can be loaded from PKCS#8
//! or PKCS#1 DER through the [`signature`] types.
//!
//! # Example
//!
//! ```rust
//! use ring_native_ossl::{digest, hmac, rand, agreement};
//!
//! // One-shot digest
//! let hash = digest::digest(&digest::SHA256, b"hello world");
//! assert_eq!(hash.as_ref().len(), 32);
//!
//! // HMAC sign and verify
//! let key = hmac::Key::new(hmac::HMAC_SHA256, b"my-key");
//! let tag = hmac::sign(&key, b"data");
//! hmac::verify(&key, b"data", tag.as_ref()).unwrap();
//!
//! // X25519 ephemeral key agreement
//! let rng = rand::SystemRandom::new();
//! let alice = agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng).unwrap();
//! let alice_pub = alice.compute_public_key().unwrap();
//! ```

pub mod aead;
pub mod agreement;
pub mod digest;
pub mod error;
pub mod hkdf;
pub mod hmac;
pub mod rand;
pub mod signature;
pub(crate) mod spki;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sha256_digest_known_vector() {
        // SHA-256("") = e3b0c44298fc1c149afb...
        let d = digest::digest(&digest::SHA256, b"");
        assert_eq!(d.as_ref().len(), 32);
        assert_eq!(
            d.as_ref(),
            &[
                0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
                0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
                0x78, 0x52, 0xb8, 0x55,
            ]
        );
    }

    #[test]
    fn sha256_digest_incremental() {
        let mut ctx = digest::Context::new(&digest::SHA256);
        ctx.update(b"abc");
        let d = ctx.finish();
        // SHA-256("abc")
        assert_eq!(
            d.as_ref(),
            &[
                0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
                0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
                0xf2, 0x00, 0x15, 0xad,
            ]
        );
    }

    #[test]
    fn hmac_sha256_sign_verify_roundtrip() {
        let key = hmac::Key::new(hmac::HMAC_SHA256, b"my-secret-key");
        let tag = hmac::sign(&key, b"hello world");
        let result = hmac::verify(&key, b"hello world", tag.as_ref());
        assert!(result.is_ok());
    }

    #[test]
    fn hmac_sha256_wrong_data_rejected() {
        let key = hmac::Key::new(hmac::HMAC_SHA256, b"my-secret-key");
        let tag = hmac::sign(&key, b"hello world");
        let result = hmac::verify(&key, b"hello WORLD", tag.as_ref());
        assert!(result.is_err());
    }

    #[test]
    fn hkdf_sha256_expand_known_length() {
        struct Len32;
        impl hkdf::KeyType for Len32 {
            fn len(&self) -> usize {
                32
            }
        }
        let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, b"test-salt");
        let prk = salt.extract(b"input-keying-material");
        let mut out = [0u8; 32];
        prk.expand(&[b"info"], Len32)
            .unwrap()
            .fill(&mut out)
            .unwrap();
        assert_ne!(out, [0u8; 32]);
    }

    #[test]
    fn x25519_agreement_roundtrip() {
        let rng = rand::SystemRandom::new();
        let alice = agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng).unwrap();
        let alice_pub = alice.compute_public_key().unwrap();

        let bob = agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng).unwrap();
        let bob_pub = bob.compute_public_key().unwrap();

        let alice_secret = agreement::agree_ephemeral(
            alice,
            &agreement::UnparsedPublicKey::new(&agreement::X25519, bob_pub.as_ref()),
            (),
            |s| Ok::<Vec<u8>, ()>(s.to_vec()),
        )
        .unwrap();

        let bob_secret = agreement::agree_ephemeral(
            bob,
            &agreement::UnparsedPublicKey::new(&agreement::X25519, alice_pub.as_ref()),
            (),
            |s| Ok::<Vec<u8>, ()>(s.to_vec()),
        )
        .unwrap();

        assert_eq!(alice_secret, bob_secret);
        assert!(!alice_secret.is_empty());
    }

    #[test]
    fn aes_128_gcm_seal_open_roundtrip() {
        let key_bytes = [0u8; 16];
        let key = aead::UnboundKey::new(&aead::AES_128_GCM, &key_bytes).unwrap();
        let lsk = aead::LessSafeKey::new(key);
        let nonce = aead::Nonce::assume_unique_for_key([0u8; 12]);
        let aad = aead::Aad::from(b"additional data".as_ref());

        let plaintext = b"hello aead world!";
        let mut in_out = plaintext.to_vec();
        lsk.seal_in_place_append_tag(nonce, &aad, &mut in_out)
            .unwrap();
        assert!(in_out.len() > plaintext.len());

        let nonce2 = aead::Nonce::assume_unique_for_key([0u8; 12]);
        let aad2 = aead::Aad::from(b"additional data".as_ref());
        let result = lsk.open_in_place(nonce2, &aad2, &mut in_out).unwrap();
        assert_eq!(result, plaintext);
    }

    #[test]
    fn ed25519_sign_verify() {
        let keypair = signature::Ed25519KeyPair::generate().unwrap();
        let msg = b"sign this message";
        let sig = keypair.sign(msg).expect("Ed25519 sign failed");

        let pub_key_bytes = keypair.public_key();
        let result = signature::verify(&signature::ED25519, pub_key_bytes, msg, sig.as_ref());
        assert!(result.is_ok(), "Ed25519 verification failed");
    }

    #[test]
    fn aead_tampered_ciphertext_fails() {
        let key = aead::UnboundKey::new(&aead::AES_128_GCM, &[0u8; 16]).unwrap();
        let lsk = aead::LessSafeKey::new(key);
        let mut in_out = b"secret".to_vec();
        lsk.seal_in_place_append_tag(
            aead::Nonce::assume_unique_for_key([0u8; 12]),
            &aead::Aad::from(b"".as_ref()),
            &mut in_out,
        )
        .unwrap();
        in_out[0] ^= 0xff; // corrupt one ciphertext byte
        assert!(lsk
            .open_in_place(
                aead::Nonce::assume_unique_for_key([0u8; 12]),
                &aead::Aad::from(b"".as_ref()),
                &mut in_out,
            )
            .is_err());
    }

    #[test]
    fn ecdsa_wrong_key_verify_fails() {
        let rng = rand::SystemRandom::new();
        let kp1 = signature::EcdsaKeyPair::generate(&signature::ECDSA_P256_SHA256_ASN1).unwrap();
        let kp2 = signature::EcdsaKeyPair::generate(&signature::ECDSA_P256_SHA256_ASN1).unwrap();
        let msg = b"test message";
        let sig = kp1.sign(&rng, msg).unwrap();
        assert!(signature::verify(
            &signature::ECDSA_P256_SHA256_ASN1,
            kp2.public_key(),
            msg,
            sig.as_ref(),
        )
        .is_err());
    }

    #[test]
    fn agreement_wrong_length_peer_key_fails() {
        let rng = rand::SystemRandom::new();
        let alice = agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng).unwrap();
        let result = agreement::agree_ephemeral(
            alice,
            &agreement::UnparsedPublicKey::new(&agreement::X25519, &[0u8; 31]),
            (),
            |_| Ok::<(), ()>(()),
        );
        assert!(result.is_err());
    }

    #[test]
    fn agreement_ec_compressed_point_rejected() {
        let rng = rand::SystemRandom::new();
        let alice = agreement::EphemeralPrivateKey::generate(&agreement::ECDH_P256, &rng).unwrap();
        let mut bad = [0u8; 65];
        bad[0] = 0x02; // compressed EC point, not uncompressed 0x04
        let result = agreement::agree_ephemeral(
            alice,
            &agreement::UnparsedPublicKey::new(&agreement::ECDH_P256, &bad),
            (),
            |_| Ok::<(), ()>(()),
        );
        assert!(result.is_err());
    }
}