diff --git a/BUILD.generated.bzl b/BUILD.generated.bzl index 24f805773..e9a40b958 100644 --- a/BUILD.generated.bzl +++ b/BUILD.generated.bzl @@ -788,21 +788,22 @@ rust_bssl_sys = [ rust_bssl_crypto = [ "src/rust/bssl-crypto/src/aead.rs", "src/rust/bssl-crypto/src/aes.rs", - "src/rust/bssl-crypto/src/bn.rs", "src/rust/bssl-crypto/src/cipher/aes_cbc.rs", "src/rust/bssl-crypto/src/cipher/aes_ctr.rs", "src/rust/bssl-crypto/src/cipher/mod.rs", "src/rust/bssl-crypto/src/digest.rs", "src/rust/bssl-crypto/src/ec.rs", "src/rust/bssl-crypto/src/ecdh.rs", + "src/rust/bssl-crypto/src/ecdsa.rs", "src/rust/bssl-crypto/src/ed25519.rs", "src/rust/bssl-crypto/src/hkdf.rs", "src/rust/bssl-crypto/src/hmac.rs", "src/rust/bssl-crypto/src/lib.rs", "src/rust/bssl-crypto/src/macros.rs", "src/rust/bssl-crypto/src/mem.rs", - "src/rust/bssl-crypto/src/pkey.rs", "src/rust/bssl-crypto/src/rand.rs", + "src/rust/bssl-crypto/src/rsa.rs", + "src/rust/bssl-crypto/src/scoped.rs", "src/rust/bssl-crypto/src/test_helpers.rs", "src/rust/bssl-crypto/src/x25519.rs", ] diff --git a/apple-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-apple.S b/apple-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-apple.S index 188ce5644..81e2f071b 100644 --- a/apple-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-apple.S +++ b/apple-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-apple.S @@ -1155,14 +1155,15 @@ _CET_ENDBR L$128_dec_start: vzeroupper vmovdqa (%rdx),%xmm0 + + + vmovdqu 16(%rdx),%xmm15 + vpor OR_MASK(%rip),%xmm15,%xmm15 movq %rdx,%rax leaq 32(%rax),%rax leaq 32(%rcx),%rcx - - vmovdqu (%rdi,%r9,1),%xmm15 - vpor OR_MASK(%rip),%xmm15,%xmm15 andq $~15,%r9 @@ -2377,14 +2378,15 @@ _CET_ENDBR L$256_dec_start: vzeroupper vmovdqa (%rdx),%xmm0 + + + vmovdqu 16(%rdx),%xmm15 + vpor OR_MASK(%rip),%xmm15,%xmm15 movq %rdx,%rax leaq 32(%rax),%rax leaq 32(%rcx),%rcx - - vmovdqu (%rdi,%r9,1),%xmm15 - vpor OR_MASK(%rip),%xmm15,%xmm15 andq $~15,%r9 diff --git a/linux-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-linux.S b/linux-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-linux.S index f5255d346..a8de4a9ab 100644 --- a/linux-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-linux.S +++ b/linux-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-linux.S @@ -1165,14 +1165,15 @@ _CET_ENDBR .L128_dec_start: vzeroupper vmovdqa (%rdx),%xmm0 + + + vmovdqu 16(%rdx),%xmm15 + vpor OR_MASK(%rip),%xmm15,%xmm15 movq %rdx,%rax leaq 32(%rax),%rax leaq 32(%rcx),%rcx - - vmovdqu (%rdi,%r9,1),%xmm15 - vpor OR_MASK(%rip),%xmm15,%xmm15 andq $~15,%r9 @@ -2387,14 +2388,15 @@ _CET_ENDBR .L256_dec_start: vzeroupper vmovdqa (%rdx),%xmm0 + + + vmovdqu 16(%rdx),%xmm15 + vpor OR_MASK(%rip),%xmm15,%xmm15 movq %rdx,%rax leaq 32(%rax),%rax leaq 32(%rcx),%rcx - - vmovdqu (%rdi,%r9,1),%xmm15 - vpor OR_MASK(%rip),%xmm15,%xmm15 andq $~15,%r9 diff --git a/sources.json b/sources.json index 353a357bd..a2806931c 100644 --- a/sources.json +++ b/sources.json @@ -2917,21 +2917,22 @@ "rust_bssl_crypto": [ "src/rust/bssl-crypto/src/aead.rs", "src/rust/bssl-crypto/src/aes.rs", - "src/rust/bssl-crypto/src/bn.rs", "src/rust/bssl-crypto/src/cipher/aes_cbc.rs", "src/rust/bssl-crypto/src/cipher/aes_ctr.rs", "src/rust/bssl-crypto/src/cipher/mod.rs", "src/rust/bssl-crypto/src/digest.rs", "src/rust/bssl-crypto/src/ec.rs", "src/rust/bssl-crypto/src/ecdh.rs", + "src/rust/bssl-crypto/src/ecdsa.rs", "src/rust/bssl-crypto/src/ed25519.rs", "src/rust/bssl-crypto/src/hkdf.rs", "src/rust/bssl-crypto/src/hmac.rs", "src/rust/bssl-crypto/src/lib.rs", "src/rust/bssl-crypto/src/macros.rs", "src/rust/bssl-crypto/src/mem.rs", - "src/rust/bssl-crypto/src/pkey.rs", "src/rust/bssl-crypto/src/rand.rs", + "src/rust/bssl-crypto/src/rsa.rs", + "src/rust/bssl-crypto/src/scoped.rs", "src/rust/bssl-crypto/src/test_helpers.rs", "src/rust/bssl-crypto/src/x25519.rs" ], diff --git a/src/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl b/src/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl index e044259f1..624123772 100644 --- a/src/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl +++ b/src/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl @@ -1257,14 +1257,15 @@ ___ .L${labelPrefix}_dec_start: vzeroupper vmovdqa ($POL), $T + # The claimed tag is provided after the current calculated tag value. + # CTRBLKs is made from it. + vmovdqu 16($POL), $CTR + vpor OR_MASK(%rip), $CTR, $CTR # CTR = [1]TAG[126...32][00..00] movq $POL, $secureBuffer leaq 32($secureBuffer), $secureBuffer leaq 32($Htbl), $Htbl - # make CTRBLKs from given tag. - vmovdqu ($CT,$LEN), $CTR - vpor OR_MASK(%rip), $CTR, $CTR # CTR = [1]TAG[126...32][00..00] andq \$~15, $LEN # If less then 6 blocks, make singles diff --git a/src/crypto/cipher_extra/e_aesgcmsiv.c b/src/crypto/cipher_extra/e_aesgcmsiv.c index 15601e19b..c2bf993d4 100644 --- a/src/crypto/cipher_extra/e_aesgcmsiv.c +++ b/src/crypto/cipher_extra/e_aesgcmsiv.c @@ -126,16 +126,16 @@ extern void aesgcmsiv_htable_polyval(const uint8_t htable[16 * 8], uint8_t in_out_poly[16]); // aes128gcmsiv_dec decrypts |in_len| & ~15 bytes from |out| and writes them to -// |in|. (The full value of |in_len| is still used to find the authentication -// tag appended to the ciphertext, however, so must not be pre-masked.) +// |in|. |in| and |out| may be equal, but must not otherwise alias. // -// |in| and |out| may be equal, but must not otherwise overlap. +// |in_out_calculated_tag_and_scratch|, on entry, must contain: +// 1. The current value of the calculated tag, which will be updated during +// decryption and written back to the beginning of this buffer on exit. +// 2. The claimed tag, which is needed to derive counter values. // -// While decrypting, it updates the POLYVAL value found at the beginning of -// |in_out_calculated_tag_and_scratch| and writes the updated value back before -// return. During executation, it may use the whole of this space for other -// purposes. In order to decrypt and update the POLYVAL value, it uses the -// expanded key from |key| and the table of powers in |htable|. +// While decrypting, the whole of |in_out_calculated_tag_and_scratch| may be +// used for other purposes. In order to decrypt and update the POLYVAL value, it +// uses the expanded key from |key| and the table of powers in |htable|. extern void aes128gcmsiv_dec(const uint8_t *in, uint8_t *out, uint8_t in_out_calculated_tag_and_scratch[16 * 8], const uint8_t htable[16 * 6], @@ -393,14 +393,10 @@ static int aead_aes_gcm_siv_asm_seal_scatter( return 1; } -// TODO(martinkr): Add aead_aes_gcm_siv_asm_open_gather. N.B. aes128gcmsiv_dec -// expects ciphertext and tag in a contiguous buffer. - -static int aead_aes_gcm_siv_asm_open(const EVP_AEAD_CTX *ctx, uint8_t *out, - size_t *out_len, size_t max_out_len, - const uint8_t *nonce, size_t nonce_len, - const uint8_t *in, size_t in_len, - const uint8_t *ad, size_t ad_len) { +static int aead_aes_gcm_siv_asm_open_gather( + const EVP_AEAD_CTX *ctx, uint8_t *out, const uint8_t *nonce, + size_t nonce_len, const uint8_t *in, size_t in_len, const uint8_t *in_tag, + size_t in_tag_len, const uint8_t *ad, size_t ad_len) { const uint64_t ad_len_64 = ad_len; if (ad_len_64 >= (UINT64_C(1) << 61)) { OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE); @@ -408,8 +404,8 @@ static int aead_aes_gcm_siv_asm_open(const EVP_AEAD_CTX *ctx, uint8_t *out, } const uint64_t in_len_64 = in_len; - if (in_len < EVP_AEAD_AES_GCM_SIV_TAG_LEN || - in_len_64 > (UINT64_C(1) << 36) + AES_BLOCK_SIZE) { + if (in_len_64 > UINT64_C(1) << 36 || + in_tag_len != EVP_AEAD_AES_GCM_SIV_TAG_LEN) { OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT); return 0; } @@ -420,13 +416,6 @@ static int aead_aes_gcm_siv_asm_open(const EVP_AEAD_CTX *ctx, uint8_t *out, } const struct aead_aes_gcm_siv_asm_ctx *gcm_siv_ctx = asm_ctx_from_ctx(ctx); - const size_t plaintext_len = in_len - EVP_AEAD_AES_GCM_SIV_TAG_LEN; - const uint8_t *const given_tag = in + plaintext_len; - - if (max_out_len < plaintext_len) { - OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL); - return 0; - } alignas(16) uint64_t record_auth_key[2]; alignas(16) uint64_t record_enc_key[4]; @@ -459,27 +448,27 @@ static int aead_aes_gcm_siv_asm_open(const EVP_AEAD_CTX *ctx, uint8_t *out, alignas(16) uint8_t htable[16 * 6]; aesgcmsiv_htable6_init(htable, (const uint8_t *)record_auth_key); + // aes[128|256]gcmsiv_dec needs access to the claimed tag. So it's put into + // its scratch space. + memcpy(calculated_tag + 16, in_tag, EVP_AEAD_AES_GCM_SIV_TAG_LEN); if (gcm_siv_ctx->is_128_bit) { - aes128gcmsiv_dec(in, out, calculated_tag, htable, &expanded_key, - plaintext_len); + aes128gcmsiv_dec(in, out, calculated_tag, htable, &expanded_key, in_len); } else { - aes256gcmsiv_dec(in, out, calculated_tag, htable, &expanded_key, - plaintext_len); + aes256gcmsiv_dec(in, out, calculated_tag, htable, &expanded_key, in_len); } - if (plaintext_len & 15) { + if (in_len & 15) { aead_aes_gcm_siv_asm_crypt_last_block(gcm_siv_ctx->is_128_bit, out, in, - plaintext_len, given_tag, - &expanded_key); + in_len, in_tag, &expanded_key); OPENSSL_memset(scratch, 0, sizeof(scratch)); - OPENSSL_memcpy(scratch, out + (plaintext_len & ~15), plaintext_len & 15); + OPENSSL_memcpy(scratch, out + (in_len & ~15), in_len & 15); aesgcmsiv_polyval_horner(calculated_tag, (const uint8_t *)record_auth_key, scratch, 1); } uint8_t length_block[16]; CRYPTO_store_u64_le(length_block, ad_len * 8); - CRYPTO_store_u64_le(length_block + 8, plaintext_len * 8); + CRYPTO_store_u64_le(length_block + 8, in_len * 8); aesgcmsiv_polyval_horner(calculated_tag, (const uint8_t *)record_auth_key, length_block, 1); @@ -495,13 +484,12 @@ static int aead_aes_gcm_siv_asm_open(const EVP_AEAD_CTX *ctx, uint8_t *out, aes256gcmsiv_ecb_enc_block(calculated_tag, calculated_tag, &expanded_key); } - if (CRYPTO_memcmp(calculated_tag, given_tag, EVP_AEAD_AES_GCM_SIV_TAG_LEN) != + if (CRYPTO_memcmp(calculated_tag, in_tag, EVP_AEAD_AES_GCM_SIV_TAG_LEN) != 0) { OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT); return 0; } - *out_len = in_len - EVP_AEAD_AES_GCM_SIV_TAG_LEN; return 1; } @@ -515,9 +503,9 @@ static const EVP_AEAD aead_aes_128_gcm_siv_asm = { aead_aes_gcm_siv_asm_init, NULL /* init_with_direction */, aead_aes_gcm_siv_asm_cleanup, - aead_aes_gcm_siv_asm_open, + NULL /* open */, aead_aes_gcm_siv_asm_seal_scatter, - NULL /* open_gather */, + aead_aes_gcm_siv_asm_open_gather, NULL /* get_iv */, NULL /* tag_len */, }; @@ -532,9 +520,9 @@ static const EVP_AEAD aead_aes_256_gcm_siv_asm = { aead_aes_gcm_siv_asm_init, NULL /* init_with_direction */, aead_aes_gcm_siv_asm_cleanup, - aead_aes_gcm_siv_asm_open, + NULL /* open */, aead_aes_gcm_siv_asm_seal_scatter, - NULL /* open_gather */, + aead_aes_gcm_siv_asm_open_gather, NULL /* get_iv */, NULL /* tag_len */, }; @@ -647,8 +635,8 @@ static void gcm_siv_polyval( } uint8_t length_block[16]; - CRYPTO_store_u64_le(length_block, ad_len * 8); - CRYPTO_store_u64_le(length_block + 8, in_len * 8); + CRYPTO_store_u64_le(length_block, ((uint64_t) ad_len) * 8); + CRYPTO_store_u64_le(length_block + 8, ((uint64_t) in_len) * 8); CRYPTO_POLYVAL_update_blocks(&polyval_ctx, length_block, sizeof(length_block)); diff --git a/src/rust/bssl-crypto/Cargo.toml b/src/rust/bssl-crypto/Cargo.toml index 315c35b88..755da8c3e 100644 --- a/src/rust/bssl-crypto/Cargo.toml +++ b/src/rust/bssl-crypto/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "bssl-crypto" -version = "0.1.0" +version = "0.2.0" edition = "2021" publish = false -license = "MIT" +license = "ISC" [dependencies] bssl-sys = {path = "../bssl-sys"} diff --git a/src/rust/bssl-crypto/deny.toml b/src/rust/bssl-crypto/deny.toml index cb3f34516..b97bb4c9b 100644 --- a/src/rust/bssl-crypto/deny.toml +++ b/src/rust/bssl-crypto/deny.toml @@ -76,7 +76,7 @@ unlicensed = "deny" # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ - "MIT", + "ISC", ] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses @@ -164,7 +164,7 @@ highlight = "all" # encouraged not to add dependencies here. allow = [ # bssl-crypto should be allowed, version appropriately. - { name = "bssl-crypto", version = "=0.1.0" }, + { name = "bssl-crypto", version = "=0.2.0" }, # bssl-sys should be allowed, version appropriately. { name = "bssl-sys", version = "=0.1.0" }, ] diff --git a/src/rust/bssl-crypto/src/aead.rs b/src/rust/bssl-crypto/src/aead.rs index a387e3088..d3677646b 100644 --- a/src/rust/bssl-crypto/src/aead.rs +++ b/src/rust/bssl-crypto/src/aead.rs @@ -13,242 +13,268 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -use crate::{CSlice, CSliceMut}; -use alloc::vec::Vec; -use bssl_sys::{EVP_AEAD, EVP_AEAD_CTX}; +//! Authenticated Encryption with Additional Data. +//! +//! AEAD couples confidentiality and integrity in a single primitive. AEAD +//! algorithms take a key and then can seal and open individual messages. Each +//! message has a unique, per-message nonce and, optionally, additional data +//! which is authenticated but not included in the ciphertext. +//! +//! No two distinct plaintexts must ever be sealed using the same (key, nonce) +//! pair. It is up to the user of these algorithms to ensure this. For example, +//! when encrypting a stream of messages (e.g. over a TCP socket) a message +//! counter can provide distinct nonces as long as the key is randomly generated +//! for the specific connection and is distinct in each direction. +//! +//! To implement that example: +//! +//! ``` +//! use bssl_crypto::aead::{Aead, Aes256Gcm}; +//! +//! let key = bssl_crypto::rand_array(); +//! let aead = Aes256Gcm::new(&key); +//! +//! let mut message_counter: u64 = 0; +//! let mut nonce = bssl_crypto::rand_array(); +//! nonce[4..].copy_from_slice(message_counter.to_be_bytes().as_slice()); +//! message_counter += 1; +//! let plaintext = b"message"; +//! let ciphertext = aead.seal(&nonce, plaintext, b""); +//! +//! let decrypted = aead.open(&nonce, ciphertext.as_slice(), b""); +//! assert_eq!(plaintext, decrypted.unwrap().as_slice()); +//! ``` -/// Error returned in the event of an unsuccessful AEAD operation. +use crate::{with_output_array, with_output_vec, with_output_vec_fallible, FfiMutSlice, FfiSlice}; +use alloc::vec::Vec; + +/// The error type returned when a fallible, in-place operation fails. #[derive(Debug)] -pub struct AeadError; +pub struct InvalidCiphertext; /// Authenticated Encryption with Associated Data (AEAD) algorithm trait. pub trait Aead { - /// The size of the auth tag for the given AEAD implementation. This is the amount of bytes - /// appended to the data when it is encrypted. - const TAG_SIZE: usize; + /// The type of tags produced by this AEAD. Generally a u8 array of fixed + /// length. + type Tag: AsRef<[u8]>; - /// The byte array nonce type which specifies the size of the nonce used in the aes operations. + /// The type of nonces used by this AEAD. Generally a u8 array of fixed + /// length. type Nonce: AsRef<[u8]>; - /// Encrypt the given buffer containing a plaintext message. On success returns the encrypted - /// `msg` and appended auth tag, which will result in a Vec which is `Self::TAG_SIZE` bytes - /// greater than the initial message. - fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result, AeadError>; + /// Encrypt and authenticate `plaintext`, and authenticate `ad`, returning + /// the result as a freshly allocated [`Vec`]. The `nonce` must never + /// be used in any sealing operation with the same key, ever again. + fn seal(&self, nonce: &Self::Nonce, plaintext: &[u8], ad: &[u8]) -> Vec; - /// Decrypt the message, returning the decrypted plaintext or an error in the event the - /// provided authentication tag does not match the given ciphertext. On success the returned - /// Vec will only contain the plaintext and so will be `Self::TAG_SIZE` bytes less than the - /// initial message. - fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result, AeadError>; + /// Encrypt and authenticate `plaintext`, and authenticate `ad`, writing + /// the ciphertext over `plaintext` and additionally returning the calculated + /// tag, which is usually appended to the ciphertext. The `nonce` must never + /// be used in any sealing operation with the same key, ever again. + fn seal_in_place(&self, nonce: &Self::Nonce, plaintext: &mut [u8], ad: &[u8]) -> Self::Tag; + + /// Authenticate `ciphertext` and `ad` and, if valid, decrypt `ciphertext`, + /// returning the original plaintext in a newly allocated [`Vec`]. The `nonce` + /// must be the same value as given to the sealing operation that produced + /// `ciphertext`. + fn open(&self, nonce: &Self::Nonce, ciphertext: &[u8], ad: &[u8]) -> Option>; + + /// Authenticate `ciphertext` and `ad` using `tag` and, if valid, decrypt + /// `ciphertext` in place. The `nonce` must be the same value as given to + /// the sealing operation that produced `ciphertext`. + fn open_in_place( + &self, + nonce: &Self::Nonce, + ciphertext: &mut [u8], + tag: &Self::Tag, + ad: &[u8], + ) -> Result<(), InvalidCiphertext>; } -/// AES-GCM-SIV implementation. -pub struct AesGcmSiv(AeadImpl<12, 16>); +/// AES-128 in Galois Counter Mode. +pub struct Aes128Gcm(EvpAead<16, 12, 16>); +aead_algo!(Aes128Gcm, EVP_aead_aes_128_gcm, 16, 12, 16); -/// Instantiates a new AES-128-GCM-SIV instance from key material. -pub fn new_aes_128_gcm_siv(key: &[u8; 16]) -> AesGcmSiv { - AesGcmSiv(AeadImpl::new::(key)) -} +/// AES-256 in Galois Counter Mode. +pub struct Aes256Gcm(EvpAead<32, 12, 16>); +aead_algo!(Aes256Gcm, EVP_aead_aes_256_gcm, 32, 12, 16); -/// Instantiates a new AES-256-GCM-SIV instance from key material. -pub fn new_aes_256_gcm_siv(key: &[u8; 32]) -> AesGcmSiv { - AesGcmSiv(AeadImpl::new::(key)) -} +/// AES-128 in GCM-SIV mode (which is different from SIV mode!). +pub struct Aes128GcmSiv(EvpAead<16, 12, 16>); +aead_algo!(Aes128GcmSiv, EVP_aead_aes_128_gcm_siv, 16, 12, 16); -impl Aead for AesGcmSiv { - const TAG_SIZE: usize = 16; - type Nonce = [u8; 12]; +/// AES-256 in GCM-SIV mode (which is different from SIV mode!). +pub struct Aes256GcmSiv(EvpAead<32, 12, 16>); +aead_algo!(Aes256GcmSiv, EVP_aead_aes_256_gcm_siv, 32, 12, 16); - fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result, AeadError> { - self.0.encrypt(msg, aad, nonce) +/// The AEAD built from ChaCha20 and Poly1305 as described in . +pub struct Chacha20Poly1305(EvpAead<32, 12, 16>); +aead_algo!(Chacha20Poly1305, EVP_aead_chacha20_poly1305, 32, 12, 16); + +/// Chacha20Poly1305 with an extended nonce that makes random generation of nonces safe. +pub struct XChacha20Poly1305(EvpAead<32, 24, 16>); +aead_algo!(XChacha20Poly1305, EVP_aead_xchacha20_poly1305, 32, 24, 16); + +/// An internal struct that implements AEAD operations given an `EVP_AEAD`. +struct EvpAead( + *mut bssl_sys::EVP_AEAD_CTX, +); + +#[allow(clippy::unwrap_used)] +impl + EvpAead +{ + // Tagged unsafe because `evp_aead` must be valid. + unsafe fn new(key: &[u8; KEY_LEN], evp_aead: *const bssl_sys::EVP_AEAD) -> Self { + // `evp_aead` is assumed to be valid. The function will validate + // the other lengths and return NULL on error. In that case we + // crash the address space because that should never happen. + let ptr = + unsafe { bssl_sys::EVP_AEAD_CTX_new(evp_aead, key.as_ffi_ptr(), key.len(), TAG_LEN) }; + assert!(!ptr.is_null()); + Self(ptr) } - fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result, AeadError> { - self.0.decrypt(msg, aad, nonce) - } -} - -trait EvpAeadType { - type Key: AsRef<[u8]>; - fn evp_aead() -> *const EVP_AEAD; -} - -struct EvpAes128GcmSiv; -impl EvpAeadType for EvpAes128GcmSiv { - type Key = [u8; 16]; - - fn evp_aead() -> *const EVP_AEAD { - // Safety: - // - this just returns a constant value - unsafe { bssl_sys::EVP_aead_aes_128_gcm_siv() } - } -} - -struct EvpAes256GcmSiv; -impl EvpAeadType for EvpAes256GcmSiv { - type Key = [u8; 32]; - - fn evp_aead() -> *const EVP_AEAD { - // Safety: - // - this just returns a constant value - unsafe { bssl_sys::EVP_aead_aes_256_gcm_siv() } - } -} - -/// AES-GCM implementation. -pub struct AesGcm(AeadImpl<12, 16>); - -/// Instantiates a new AES-128-GCM instance from key material. -pub fn new_aes_128_gcm(key: &[u8; 16]) -> AesGcm { - AesGcm(AeadImpl::new::(key)) -} - -/// Instantiates a new AES-256-GCM instance from key material. -pub fn new_aes_256_gcm(key: &[u8; 32]) -> AesGcm { - AesGcm(AeadImpl::new::(key)) -} - -impl Aead for AesGcm { - const TAG_SIZE: usize = 16; - type Nonce = [u8; 12]; - - fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result, AeadError> { - self.0.encrypt(msg, aad, nonce) - } - - fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result, AeadError> { - self.0.decrypt(msg, aad, nonce) - } -} - -struct EvpAes128Gcm; -impl EvpAeadType for EvpAes128Gcm { - type Key = [u8; 16]; - - fn evp_aead() -> *const EVP_AEAD { - // Safety: - // - this just returns a constant value - unsafe { bssl_sys::EVP_aead_aes_128_gcm() } - } -} - -struct EvpAes256Gcm; -impl EvpAeadType for EvpAes256Gcm { - type Key = [u8; 32]; - - fn evp_aead() -> *const EVP_AEAD { - // Safety: - // - this just returns a constant value - unsafe { bssl_sys::EVP_aead_aes_256_gcm() } - } -} - -// Private implementation of an AEAD which is generic over Nonce size and Tag size. This should -// only be exposed publicly by wrapper types which provide the correctly sized const generics for -// the given aead algorithm. -struct AeadImpl(*mut EVP_AEAD_CTX); - -impl AeadImpl { - // Create a new AeadImpl instance from key material and for a supported AeadType. - fn new(key: &A::Key) -> Self { - let key_cslice = CSlice::from(key.as_ref()); - - // Safety: - // - This is always safe as long as the correct key size is set by the wrapper type. - let ctx = unsafe { - bssl_sys::EVP_AEAD_CTX_new( - A::evp_aead(), - key_cslice.as_ptr(), - key_cslice.len(), - bssl_sys::EVP_AEAD_DEFAULT_TAG_LENGTH as usize, - ) - }; - assert!(!ctx.is_null()); - AeadImpl(ctx) - } - - // Encrypts msg in-place, adding enough space to msg for the auth tag. - fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; N]) -> Result, AeadError> { - let mut out = Vec::new(); - out.resize(msg.len() + T, 0u8); - - let mut out_cslice = CSliceMut::from(out.as_mut_slice()); - let msg_cslice = CSlice::from(msg); - let aad_cslice = CSlice::from(aad); - let nonce_cslice = CSlice::from(nonce.as_slice()); - let mut out_len = 0usize; - - // Safety: - // - The buffers are all valid, with corresponding ptr and length - let result = unsafe { - bssl_sys::EVP_AEAD_CTX_seal( - self.0, - out_cslice.as_mut_ptr(), - &mut out_len, - out_cslice.len(), - nonce_cslice.as_ptr(), - nonce_cslice.len(), - msg_cslice.as_ptr(), - msg_cslice.len(), - aad_cslice.as_ptr(), - aad_cslice.len(), - ) - }; - - if result == 1 { - // Verify the correct number of bytes were written. - assert_eq!(out_len, out.len()); - Ok(out) - } else { - Err(AeadError) + fn seal(&self, nonce: &[u8; NONCE_LEN], plaintext: &[u8], ad: &[u8]) -> Vec { + let max_output = plaintext.len() + TAG_LEN; + unsafe { + with_output_vec(max_output, |out_buf| { + let mut out_len = 0usize; + // Safety: the input buffers are all valid, with corresponding + // ptr and length. The output buffer has at least `max_output` + // bytes of space and that maximum is passed to + // `EVP_AEAD_CTX_seal` as a limit. + let result = bssl_sys::EVP_AEAD_CTX_seal( + self.0, + out_buf, + &mut out_len, + max_output, + nonce.as_ffi_ptr(), + nonce.len(), + plaintext.as_ffi_ptr(), + plaintext.len(), + ad.as_ffi_ptr(), + ad.len(), + ); + // Sealing never fails unless there's a programmer error. + assert_eq!(result, 1); + // For the implemented AEADs, we should always have calculated + // the overhead exactly. + assert_eq!(out_len, max_output); + // Safety: `out_len` bytes have been written to. + out_len + }) } } - // Decrypts msg in-place, on success msg will contain the plain text alone, without the auth - // tag. - fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; N]) -> Result, AeadError> { - if msg.len() < T { - return Err(AeadError); + fn seal_in_place( + &self, + nonce: &[u8; NONCE_LEN], + plaintext: &mut [u8], + ad: &[u8], + ) -> [u8; TAG_LEN] { + // Safety: the buffers are all valid, with corresponding ptr and length. + // `tag_len` is passed at the maximum size of `tag` and `out_tag_len` + // is checked to ensure that the whole output was written to. + unsafe { + with_output_array(|tag, tag_len| { + let mut out_tag_len = 0usize; + let result = bssl_sys::EVP_AEAD_CTX_seal_scatter( + self.0, + plaintext.as_mut_ffi_ptr(), + tag, + &mut out_tag_len, + tag_len, + nonce.as_ffi_ptr(), + nonce.len(), + plaintext.as_ffi_ptr(), + plaintext.len(), + /*extra_in=*/ core::ptr::null(), + /*extra_in_len=*/ 0, + ad.as_ffi_ptr(), + ad.len(), + ); + // Failure indicates that one of the configured lengths was wrong. + // Crashing is a good answer in that case. + assert_eq!(result, 1); + // The whole output must have been written to. + assert_eq!(out_tag_len, TAG_LEN); + }) } - let mut out = Vec::new(); - out.resize(msg.len() - T, 0u8); + } - let mut out_cslice = CSliceMut::from(out.as_mut_slice()); - let aad_cslice = CSlice::from(aad); - let msg_cslice = CSlice::from(msg); - let mut out_len = 0usize; + fn open(&self, nonce: &[u8; NONCE_LEN], ciphertext: &[u8], ad: &[u8]) -> Option> { + if ciphertext.len() < TAG_LEN { + return None; + } + let max_output = ciphertext.len() - TAG_LEN; + unsafe { + with_output_vec_fallible(max_output, |out_buf| { + let mut out_len = 0usize; + // Safety: the input buffers are all valid, with corresponding + // ptr and length. The output buffer has at least `max_output` + // bytes of space and that maximum is passed to + // `EVP_AEAD_CTX_open` as a limit. + let result = bssl_sys::EVP_AEAD_CTX_open( + self.0, + out_buf, + &mut out_len, + max_output, + nonce.as_ffi_ptr(), + nonce.len(), + ciphertext.as_ffi_ptr(), + ciphertext.len(), + ad.as_ffi_ptr(), + ad.len(), + ); + if result == 1 { + // Safety: `out_len` bytes have been written to. + Some(out_len) + } else { + None + } + }) + } + } + + fn open_in_place( + &self, + nonce: &[u8; NONCE_LEN], + ciphertext: &mut [u8], + tag: &[u8; TAG_LEN], + ad: &[u8], + ) -> Result<(), InvalidCiphertext> { // Safety: // - The buffers are all valid, with corresponding ptr and length let result = unsafe { - bssl_sys::EVP_AEAD_CTX_open( + bssl_sys::EVP_AEAD_CTX_open_gather( self.0, - out_cslice.as_mut_ptr(), - &mut out_len, - out_cslice.len(), - nonce.as_ptr(), + ciphertext.as_mut_ffi_ptr(), + nonce.as_ffi_ptr(), nonce.len(), - msg_cslice.as_ptr(), - msg_cslice.len(), - aad_cslice.as_ptr(), - aad_cslice.len(), + ciphertext.as_ffi_ptr(), + ciphertext.len(), + tag.as_ffi_ptr(), + tag.len(), + ad.as_ffi_ptr(), + ad.len(), ) }; - if result == 1 { - // Verify the correct number of bytes were written. - assert_eq!(out_len, out.len()); - Ok(out) + Ok(()) } else { - Err(AeadError) + Err(InvalidCiphertext) } } } -impl Drop for AeadImpl { +impl Drop + for EvpAead +{ fn drop(&mut self) { - // Safety: - // - `self.0` was allocated by `EVP_AEAD_CTX_new` and has not yet been freed. + // Safety: `self.0` was initialized by `EVP_AEAD_CTX_init` because all + // paths to create an `EvpAead` do so. unsafe { bssl_sys::EVP_AEAD_CTX_free(self.0) } } } @@ -256,168 +282,216 @@ impl Drop for AeadImpl { #[cfg(test)] mod test { use super::*; - use crate::test_helpers::decode_hex; + use crate::test_helpers::{decode_hex, decode_hex_into_vec}; - #[test] - fn aes_128_gcm_siv_tests() { - // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json - // TC1 - Empty Message - let key = decode_hex("01000000000000000000000000000000"); - let nonce = decode_hex("030000000000000000000000"); - let tag: [u8; 16] = decode_hex("dc20e2d83f25705bb49e439eca56de25"); - let mut buf = Vec::from(&[] as &[u8]); - let aes = new_aes_128_gcm_siv(&key); - let result = aes.encrypt(&mut buf, b"", &nonce); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), &tag); + fn check_aead_invariants< + const NONCE_LEN: usize, + const TAG_LEN: usize, + A: Aead, + >( + aead: A, + ) { + let plaintext = b"plaintext"; + let ad = b"additional data"; + let nonce: A::Nonce = [0u8; NONCE_LEN]; - // TC2 - let msg: [u8; 8] = decode_hex("0100000000000000"); - let ct: [u8; 8] = decode_hex("b5d839330ac7b786"); - let tag: [u8; 16] = decode_hex("578782fff6013b815b287c22493a364c"); - let result = aes.encrypt(&msg, b"", &nonce); - assert!(result.is_ok()); - let mut result_vec = result.unwrap(); - assert_eq!(&result_vec[..8], &ct); - assert_eq!(&result_vec[8..], &tag); - let result = aes.decrypt(result_vec.as_mut_slice(), b"", &nonce); - assert!(result.is_ok()); - assert_eq!(&result.unwrap(), &msg); + let mut ciphertext = aead.seal(&nonce, plaintext, ad); + let plaintext2 = aead + .open(&nonce, ciphertext.as_slice(), ad) + .expect("should decrypt"); + assert_eq!(plaintext, plaintext2.as_slice()); - // TC14 contains associated data - let msg: [u8; 4] = decode_hex("02000000"); - let ct: [u8; 4] = decode_hex("a8fe3e87"); - let aad: [u8; 12] = decode_hex("010000000000000000000000"); - let tag: [u8; 16] = decode_hex("07eb1f84fb28f8cb73de8e99e2f48a14"); - let result = aes.encrypt(&msg, &aad, &nonce); - assert!(result.is_ok()); - let mut result_vec = result.unwrap(); - assert_eq!(&result_vec[..4], &ct); - assert_eq!(&result_vec[4..], &tag); - let result = aes.decrypt(result_vec.as_mut_slice(), &aad, &nonce); - assert!(result.is_ok()); - assert_eq!(&result.unwrap(), &msg); + ciphertext[0] ^= 1; + assert!(aead.open(&nonce, ciphertext.as_slice(), ad).is_none()); + ciphertext[0] ^= 1; + + let (ciphertext_in_place, tag_slice) = + ciphertext.as_mut_slice().split_at_mut(plaintext.len()); + let tag: [u8; TAG_LEN] = tag_slice.try_into().unwrap(); + aead.open_in_place(&nonce, ciphertext_in_place, &tag, ad) + .expect("should decrypt"); + assert_eq!(plaintext, ciphertext_in_place); + + let tag = aead.seal_in_place(&nonce, ciphertext_in_place, ad); + aead.open_in_place(&nonce, ciphertext_in_place, &tag, ad) + .expect("should decrypt"); + assert_eq!(plaintext, ciphertext_in_place); + + assert!(aead.open(&nonce, b"tooshort", b"").is_none()); } #[test] - fn aes_256_gcm_siv_tests() { - // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json - // TC77 - let test_key = - decode_hex("0100000000000000000000000000000000000000000000000000000000000000"); - let nonce = decode_hex("030000000000000000000000"); - let aes = new_aes_256_gcm_siv(&test_key); - let mut msg: [u8; 8] = decode_hex("0100000000000000"); - let ct: [u8; 8] = decode_hex("c2ef328e5c71c83b"); - let tag: [u8; 16] = decode_hex("843122130f7364b761e0b97427e3df28"); - let enc_result = aes.encrypt(&mut msg, b"", &nonce); - assert!(enc_result.is_ok()); - let mut enc_data = enc_result.unwrap(); - assert_eq!(&enc_data[..8], &ct); - assert_eq!(&enc_data[8..], &tag); - let result = aes.decrypt(enc_data.as_mut_slice(), b"", &nonce); - assert!(result.is_ok()); - assert_eq!(&result.unwrap(), &msg); - - // TC78 - let mut msg: [u8; 12] = decode_hex("010000000000000000000000"); - let ct: [u8; 12] = decode_hex("9aab2aeb3faa0a34aea8e2b1"); - let tag: [u8; 16] = decode_hex("8ca50da9ae6559e48fd10f6e5c9ca17e"); - let enc_result = aes.encrypt(&mut msg, b"", &nonce); - assert!(enc_result.is_ok()); - let mut enc_data = enc_result.unwrap(); - assert_eq!(&enc_data[..12], &ct); - assert_eq!(&enc_data[12..], &tag); - let result = aes.decrypt(enc_data.as_mut_slice(), b"", &nonce); - assert!(result.is_ok()); - assert_eq!(&result.unwrap(), &msg); - - // TC89 contains associated data - let mut msg: [u8; 4] = decode_hex("02000000"); - let ct: [u8; 4] = decode_hex("22b3f4cd"); - let tag: [u8; 16] = decode_hex("1835e517741dfddccfa07fa4661b74cf"); - let aad: [u8; 12] = decode_hex("010000000000000000000000"); - let enc_result = aes.encrypt(&mut msg, &aad, &nonce); - assert!(enc_result.is_ok()); - let mut enc_data = enc_result.unwrap(); - assert_eq!(&enc_data[..4], &ct); - assert_eq!(&enc_data[4..], &tag); - let result = aes.decrypt(enc_data.as_mut_slice(), &aad, &nonce); - assert!(result.is_ok()); - assert_eq!(&result.unwrap(), &msg); + fn aes_128_gcm_invariants() { + check_aead_invariants(Aes128Gcm::new(&[0u8; 16])); } #[test] - fn aes_128_gcm_tests() { - // TC 1 from crypto/cipher_extra/test/aes_128_gcm_tests.txt - let key = decode_hex("d480429666d48b400633921c5407d1d1"); - let nonce = decode_hex("3388c676dc754acfa66e172a"); - let tag: [u8; 16] = decode_hex("7d7daf44850921a34e636b01adeb104f"); - let mut buf = Vec::from(&[] as &[u8]); - let aes = new_aes_128_gcm(&key); - let result = aes.encrypt(&mut buf, b"", &nonce); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), &tag); - - // TC2 - let key = decode_hex("3881e7be1bb3bbcaff20bdb78e5d1b67"); - let nonce = decode_hex("dcf5b7ae2d7552e2297fcfa9"); - let msg: [u8; 5] = decode_hex("0a2714aa7d"); - let ad: [u8; 5] = decode_hex("c60c64bbf7"); - let ct: [u8; 5] = decode_hex("5626f96ecb"); - let tag: [u8; 16] = decode_hex("ff4c4f1d92b0abb1d0820833d9eb83c7"); - - let mut buf = Vec::from(msg.as_slice()); - let aes = new_aes_128_gcm(&key); - let result = aes.encrypt(&mut buf, &ad, &nonce); - assert!(result.is_ok()); - let mut data = result.unwrap(); - assert_eq!(&data[..5], &ct); - assert_eq!(&data[5..], &tag); - let result = aes.decrypt(data.as_mut_slice(), &ad, &nonce); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), &msg); + fn aes_256_gcm_invariants() { + check_aead_invariants(Aes256Gcm::new(&[0u8; 32])); } #[test] - fn aes_256_gcm_tests() { - // TC 1 from crypto/cipher_extra/test/aes_256_gcm_tests.txt - let key = decode_hex("e5ac4a32c67e425ac4b143c83c6f161312a97d88d634afdf9f4da5bd35223f01"); - let nonce = decode_hex("5bf11a0951f0bfc7ea5c9e58"); - let tag: [u8; 16] = decode_hex("d7cba289d6d19a5af45dc13857016bac"); - let mut buf = Vec::from(&[] as &[u8]); - let aes = new_aes_256_gcm(&key); - let result = aes.encrypt(&mut buf, b"", &nonce); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), &tag); - - // TC2 - let key = decode_hex("73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a"); - let nonce = decode_hex("a330a184fc245812f4820caa"); - let msg: [u8; 5] = decode_hex("f0535fe211"); - let ad: [u8; 5] = decode_hex("e91428be04"); - let ct: [u8; 5] = decode_hex("e9b8a896da"); - let tag: [u8; 16] = decode_hex("9115ed79f26a030c14947b3e454db9e7"); - - let mut buf = Vec::from(msg.as_slice()); - let aes = new_aes_256_gcm(&key); - let result = aes.encrypt(&mut buf, &ad, &nonce); - assert!(result.is_ok()); - let mut data = result.unwrap(); - assert_eq!(&data[..5], &ct); - assert_eq!(&data[5..], &tag); - let result = aes.decrypt(data.as_mut_slice(), &ad, &nonce); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), &msg); + fn aes_128_gcm_siv_invariants() { + check_aead_invariants(Aes128GcmSiv::new(&[0u8; 16])); } #[test] - fn test_invalid_data_length_decrypt() { - let key = decode_hex("00000000000000000000000000000000"); - let nonce = decode_hex("000000000000000000000000"); - let buf = Vec::from(&[] as &[u8]); - let aes = new_aes_128_gcm_siv(&key); - let result = aes.decrypt(&buf, b"", &nonce); - assert!(result.is_err()); + fn aes_256_gcm_siv_invariants() { + check_aead_invariants(Aes256GcmSiv::new(&[0u8; 32])); + } + + #[test] + fn chacha20_poly1305_invariants() { + check_aead_invariants(Chacha20Poly1305::new(&[0u8; 32])); + } + + #[test] + fn xchacha20_poly1305_invariants() { + check_aead_invariants(XChacha20Poly1305::new(&[0u8; 32])); + } + + struct TestCase { + key: [u8; KEY_LEN], + nonce: [u8; NONCE_LEN], + msg: Vec, + ad: Vec, + ciphertext: Vec, + } + + fn check_test_cases< + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + F: Fn(&[u8; KEY_LEN]) -> Box>, + >( + new_func: F, + test_cases: &[TestCase], + ) { + for (test_num, test) in test_cases.iter().enumerate() { + let ctx = new_func(&test.key); + let ciphertext = ctx.seal(&test.nonce, test.msg.as_slice(), test.ad.as_slice()); + assert_eq!(ciphertext, test.ciphertext, "Failed on test #{}", test_num); + + let plaintext = ctx + .open(&test.nonce, ciphertext.as_slice(), test.ad.as_slice()) + .unwrap(); + assert_eq!(plaintext, test.msg, "Decrypt failed on test #{}", test_num); + } + } + + #[test] + fn aes_128_gcm_siv() { + let test_cases: &[TestCase<16, 12>] = &[ + TestCase { + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json + // TC1 - Empty Message + key: decode_hex("01000000000000000000000000000000"), + nonce: decode_hex("030000000000000000000000"), + msg: Vec::new(), + ad: Vec::new(), + ciphertext: decode_hex_into_vec("dc20e2d83f25705bb49e439eca56de25"), + }, + TestCase { + // TC2 + key: decode_hex("01000000000000000000000000000000"), + nonce: decode_hex("030000000000000000000000"), + msg: decode_hex_into_vec("0100000000000000"), + ad: Vec::new(), + ciphertext: decode_hex_into_vec("b5d839330ac7b786578782fff6013b815b287c22493a364c"), + }, + TestCase { + // TC14 + key: decode_hex("01000000000000000000000000000000"), + nonce: decode_hex("030000000000000000000000"), + msg: decode_hex_into_vec("02000000"), + ad: decode_hex_into_vec("010000000000000000000000"), + ciphertext: decode_hex_into_vec("a8fe3e8707eb1f84fb28f8cb73de8e99e2f48a14"), + }, + ]; + + check_test_cases(|key| Box::new(Aes128GcmSiv::new(key)), test_cases); + } + + #[test] + fn aes_256_gcm_siv() { + let test_cases: &[TestCase<32, 12>] = &[ + TestCase { + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json + // TC77 + key: decode_hex("0100000000000000000000000000000000000000000000000000000000000000"), + nonce: decode_hex("030000000000000000000000"), + msg: decode_hex_into_vec("0100000000000000"), + ad: Vec::new(), + ciphertext: decode_hex_into_vec("c2ef328e5c71c83b843122130f7364b761e0b97427e3df28"), + }, + TestCase { + // TC78 + key: decode_hex("0100000000000000000000000000000000000000000000000000000000000000"), + nonce: decode_hex("030000000000000000000000"), + msg: decode_hex_into_vec("010000000000000000000000"), + ad: Vec::new(), + ciphertext: decode_hex_into_vec( + "9aab2aeb3faa0a34aea8e2b18ca50da9ae6559e48fd10f6e5c9ca17e", + ), + }, + TestCase { + // TC89 contains associated data + key: decode_hex("0100000000000000000000000000000000000000000000000000000000000000"), + nonce: decode_hex("030000000000000000000000"), + msg: decode_hex_into_vec("02000000"), + ad: decode_hex_into_vec("010000000000000000000000"), + ciphertext: decode_hex_into_vec("22b3f4cd1835e517741dfddccfa07fa4661b74cf"), + }, + ]; + + check_test_cases(|key| Box::new(Aes256GcmSiv::new(key)), test_cases); + } + + #[test] + fn aes_128_gcm() { + let test_cases: &[TestCase<16, 12>] = &[ + TestCase { + // TC 1 from crypto/cipher_extra/test/aes_128_gcm_tests.txt + key: decode_hex("d480429666d48b400633921c5407d1d1"), + nonce: decode_hex("3388c676dc754acfa66e172a"), + msg: Vec::new(), + ad: Vec::new(), + ciphertext: decode_hex_into_vec("7d7daf44850921a34e636b01adeb104f"), + }, + TestCase { + // TC2 + key: decode_hex("3881e7be1bb3bbcaff20bdb78e5d1b67"), + nonce: decode_hex("dcf5b7ae2d7552e2297fcfa9"), + msg: decode_hex_into_vec("0a2714aa7d"), + ad: decode_hex_into_vec("c60c64bbf7"), + ciphertext: decode_hex_into_vec("5626f96ecbff4c4f1d92b0abb1d0820833d9eb83c7"), + }, + ]; + + check_test_cases(|key| Box::new(Aes128Gcm::new(key)), test_cases); + } + + #[test] + fn aes_256_gcm() { + let test_cases: &[TestCase<32, 12>] = &[ + TestCase { + // TC 1 from crypto/cipher_extra/test/aes_128_gcm_tests.txt + key: decode_hex("e5ac4a32c67e425ac4b143c83c6f161312a97d88d634afdf9f4da5bd35223f01"), + nonce: decode_hex("5bf11a0951f0bfc7ea5c9e58"), + msg: Vec::new(), + ad: Vec::new(), + ciphertext: decode_hex_into_vec("d7cba289d6d19a5af45dc13857016bac"), + }, + TestCase { + // TC2 + key: decode_hex("73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a"), + nonce: decode_hex("a330a184fc245812f4820caa"), + msg: decode_hex_into_vec("f0535fe211"), + ad: decode_hex_into_vec("e91428be04"), + ciphertext: decode_hex_into_vec("e9b8a896da9115ed79f26a030c14947b3e454db9e7"), + }, + ]; + + check_test_cases(|key| Box::new(Aes256Gcm::new(key)), test_cases); } } diff --git a/src/rust/bssl-crypto/src/aes.rs b/src/rust/bssl-crypto/src/aes.rs index 090042071..4602ee120 100644 --- a/src/rust/bssl-crypto/src/aes.rs +++ b/src/rust/bssl-crypto/src/aes.rs @@ -13,214 +13,165 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -/// Block size in bytes for AES. +//! Advanced Encryption Standard. +//! +//! AES is a 128-bit block cipher that supports key sizes of 128, 192, or 256 +//! bits. (Although 192 isn't supported here.) +//! +//! Each key defines a permutation of the set of 128-bit blocks and AES can +//! perform the forward and reverse permutation. (These directions are +//! arbitrarily labeled "encryption" and "decryption".) +//! +//! AES requires relatively expensive preprocessing of keys and thus the +//! processed form of the key is represented here using [`EncryptKey`] and +//! [`DecryptKey`]. +//! +//! ``` +//! use bssl_crypto::aes; +//! +//! let key_bytes = bssl_crypto::rand_array(); +//! let enc_key = aes::EncryptKey::new_256(&key_bytes); +//! let block = [0u8; aes::BLOCK_SIZE]; +//! let mut transformed_block = enc_key.encrypt(&block); +//! +//! let dec_key = aes::DecryptKey::new_256(&key_bytes); +//! dec_key.decrypt_in_place(&mut transformed_block); +//! assert_eq!(block, transformed_block); +//! ``` +//! +//! AES is a low-level primitive and must be used in a more complex construction +//! in nearly every case. See the `aead` crate for usable encryption and +//! decryption primitives. + +use crate::{initialized_struct_fallible, FfiMutSlice, FfiSlice}; +use core::ffi::c_uint; + +/// AES block size in bytes. pub const BLOCK_SIZE: usize = bssl_sys::AES_BLOCK_SIZE as usize; /// A single AES block. -pub type AesBlock = [u8; BLOCK_SIZE]; - -/// AES implementation used for encrypting/decrypting a single `AesBlock` at a time. -pub struct Aes; - -impl Aes { - /// Encrypts `block` in place. - pub fn encrypt(key: &AesEncryptKey, block: &mut AesBlock) { - let input = *block; - // Safety: - // - AesBlock is always a valid size and key is guaranteed to already be initialized. - unsafe { bssl_sys::AES_encrypt(input.as_ptr(), block.as_mut_ptr(), &key.0) } - } - - /// Decrypts `block` in place. - pub fn decrypt(key: &AesDecryptKey, block: &mut AesBlock) { - let input = *block; - // Safety: - // - AesBlock is always a valid size and key is guaranteed to already be initialized. - unsafe { bssl_sys::AES_decrypt(input.as_ptr(), block.as_mut_ptr(), &key.0) } - } -} +pub type Block = [u8; BLOCK_SIZE]; /// An initialized key which can be used for encrypting. -pub struct AesEncryptKey(bssl_sys::AES_KEY); +pub struct EncryptKey(bssl_sys::AES_KEY); -impl AesEncryptKey { - /// Initializes an encryption key from an appropriately sized array of bytes for AES-128 operations. - pub fn new_aes_128(key: [u8; 16]) -> AesEncryptKey { - new_encrypt_key(key) +impl EncryptKey { + /// Initializes an encryption key from an appropriately sized array of bytes + // for AES-128 operations. + pub fn new_128(key: &[u8; 16]) -> Self { + new_encrypt_key(key.as_slice()) } - /// Initializes an encryption key from an appropriately sized array of bytes for AES-256 operations. - pub fn new_aes_256(key: [u8; 32]) -> AesEncryptKey { - new_encrypt_key(key) + /// Initializes an encryption key from an appropriately sized array of bytes + // for AES-256 operations. + pub fn new_256(key: &[u8; 32]) -> Self { + new_encrypt_key(key.as_slice()) + } + + /// Return the encrypted version of the given block. + pub fn encrypt(&self, block: &Block) -> Block { + let mut ret = *block; + self.encrypt_in_place(&mut ret); + ret + } + + /// Replace `block` with its encrypted version. + pub fn encrypt_in_place(&self, block: &mut Block) { + // Safety: + // - block is always a valid size and key is guaranteed to already be initialized. + unsafe { bssl_sys::AES_encrypt(block.as_ffi_ptr(), block.as_mut_ffi_ptr(), &self.0) } } } /// An initialized key which can be used for decrypting -pub struct AesDecryptKey(bssl_sys::AES_KEY); +pub struct DecryptKey(bssl_sys::AES_KEY); -impl AesDecryptKey { +impl DecryptKey { /// Initializes a decryption key from an appropriately sized array of bytes for AES-128 operations. - pub fn new_aes_128(key: [u8; 16]) -> AesDecryptKey { - new_decrypt_key(key) + pub fn new_128(key: &[u8; 16]) -> DecryptKey { + new_decrypt_key(key.as_slice()) } /// Initializes a decryption key from an appropriately sized array of bytes for AES-256 operations. - pub fn new_aes_256(key: [u8; 32]) -> AesDecryptKey { - new_decrypt_key(key) + pub fn new_256(key: &[u8; 32]) -> DecryptKey { + new_decrypt_key(key.as_slice()) + } + + /// Return the decrypted version of the given block. + pub fn decrypt(&self, block: &Block) -> Block { + let mut ret = *block; + self.decrypt_in_place(&mut ret); + ret + } + + /// Replace `block` with its decrypted version. + pub fn decrypt_in_place(&self, block: &mut Block) { + // Safety: + // - block is always a valid size and key is guaranteed to already be initialized. + unsafe { bssl_sys::AES_decrypt(block.as_ffi_ptr(), block.as_mut_ffi_ptr(), &self.0) } } } -/// Private generically implemented function for creating a new `AesEncryptKey` from an array of bytes. /// This should only be publicly exposed by wrapper types with the correct key lengths -fn new_encrypt_key(key: [u8; N]) -> AesEncryptKey { - let mut enc_key_uninit = core::mem::MaybeUninit::uninit(); - - // Safety: - // - key is guaranteed to point to bits/8 bytes determined by the len() * 8 used below. - // - bits is always a valid AES key size, as defined by the new_aes_* fns defined on the public - // key structs. - let result = unsafe { - bssl_sys::AES_set_encrypt_key( - key.as_ptr(), - key.len() as core::ffi::c_uint * 8, - enc_key_uninit.as_mut_ptr(), - ) - }; - assert_eq!(result, 0, "Error occurred in bssl_sys::AES_set_encrypt_key"); - - // Safety: - // - since we have checked above that initialization succeeded, this will never be UB - let enc_key = unsafe { enc_key_uninit.assume_init() }; - - AesEncryptKey(enc_key) +#[allow(clippy::unwrap_used)] +fn new_encrypt_key(key: &[u8]) -> EncryptKey { + EncryptKey( + unsafe { + initialized_struct_fallible(|aes_key| { + // The return value of this function differs from the usual BoringSSL + // convention. + bssl_sys::AES_set_encrypt_key(key.as_ffi_ptr(), key.len() as c_uint * 8, aes_key) + == 0 + }) + } + // unwrap: this function only fails if `key` is the wrong length, which + // must be prevented by the pub functions that call this. + .unwrap(), + ) } -/// Private generically implemented function for creating a new `AesDecryptKey` from an array of bytes. /// This should only be publicly exposed by wrapper types with the correct key lengths. -fn new_decrypt_key(key: [u8; N]) -> AesDecryptKey { - let mut dec_key_uninit = core::mem::MaybeUninit::uninit(); - - // Safety: - // - key is guaranteed to point to bits/8 bytes determined by the len() * 8 used below. - // - bits is always a valid AES key size, as defined by the new_aes_* fns defined on the public - // key structs. - let result = unsafe { - bssl_sys::AES_set_decrypt_key( - key.as_ptr(), - key.len() as core::ffi::c_uint * 8, - dec_key_uninit.as_mut_ptr(), - ) - }; - assert_eq!(result, 0, "Error occurred in bssl_sys::AES_set_decrypt_key"); - - // Safety: - // - Since we have checked above that initialization succeeded, this will never be UB. - let dec_key = unsafe { dec_key_uninit.assume_init() }; - - AesDecryptKey(dec_key) +#[allow(clippy::unwrap_used)] +fn new_decrypt_key(key: &[u8]) -> DecryptKey { + DecryptKey( + unsafe { + initialized_struct_fallible(|aes_key| { + // The return value of this function differs from the usual BoringSSL + // convention. + bssl_sys::AES_set_decrypt_key(key.as_ffi_ptr(), key.len() as c_uint * 8, aes_key) + == 0 + }) + } + // unwrap: this function only fails if `key` is the wrong length, which + // must be prevented by the pub functions that call this. + .unwrap(), + ) } #[cfg(test)] mod tests { use crate::{ - aes::{Aes, AesDecryptKey, AesEncryptKey}, + aes::{DecryptKey, EncryptKey}, test_helpers::decode_hex, }; - // test data from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.1 #[test] - fn aes_128_test_encrypt() { - let key = AesEncryptKey::new_aes_128(decode_hex("2b7e151628aed2a6abf7158809cf4f3c")); - let mut block = [0_u8; 16]; - - block.copy_from_slice(&decode_hex::<16>("6bc1bee22e409f96e93d7e117393172a")); - Aes::encrypt(&key, &mut block); - assert_eq!(decode_hex("3ad77bb40d7a3660a89ecaf32466ef97"), block); - - block.copy_from_slice(&decode_hex::<16>("ae2d8a571e03ac9c9eb76fac45af8e51")); - Aes::encrypt(&key, &mut block); - assert_eq!(decode_hex("f5d3d58503b9699de785895a96fdbaaf"), block); - - block.copy_from_slice(&decode_hex::<16>("30c81c46a35ce411e5fbc1191a0a52ef")); - Aes::encrypt(&key, &mut block); - assert_eq!(decode_hex("43b1cd7f598ece23881b00e3ed030688"), block); - - block.copy_from_slice(&decode_hex::<16>("f69f2445df4f9b17ad2b417be66c3710")); - Aes::encrypt(&key, &mut block); - assert_eq!(decode_hex("7b0c785e27e8ad3f8223207104725dd4"), block); + fn aes_128() { + // test data from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.1 + let key = decode_hex("2b7e151628aed2a6abf7158809cf4f3c"); + let plaintext = decode_hex("6bc1bee22e409f96e93d7e117393172a"); + let ciphertext = decode_hex("3ad77bb40d7a3660a89ecaf32466ef97"); + assert_eq!(ciphertext, EncryptKey::new_128(&key).encrypt(&plaintext)); + assert_eq!(plaintext, DecryptKey::new_128(&key).decrypt(&ciphertext)); } - // test data from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.2 #[test] - fn aes_128_test_decrypt() { - let key = AesDecryptKey::new_aes_128(decode_hex("2b7e151628aed2a6abf7158809cf4f3c")); - let mut block = [0_u8; 16]; - - block.copy_from_slice(&decode_hex::<16>("3ad77bb40d7a3660a89ecaf32466ef97")); - Aes::decrypt(&key, &mut block); - assert_eq!(decode_hex::<16>("6bc1bee22e409f96e93d7e117393172a"), block); - - block.copy_from_slice(&decode_hex::<16>("f5d3d58503b9699de785895a96fdbaaf")); - Aes::decrypt(&key, &mut block); - assert_eq!(decode_hex::<16>("ae2d8a571e03ac9c9eb76fac45af8e51"), block); - - block.copy_from_slice(&decode_hex::<16>("43b1cd7f598ece23881b00e3ed030688")); - Aes::decrypt(&key, &mut block); - assert_eq!(decode_hex::<16>("30c81c46a35ce411e5fbc1191a0a52ef"), block); - - block.copy_from_slice(&decode_hex::<16>("7b0c785e27e8ad3f8223207104725dd4").as_slice()); - Aes::decrypt(&key, &mut block); - assert_eq!(decode_hex::<16>("f69f2445df4f9b17ad2b417be66c3710"), block); - } - - // test data from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.5 - #[test] - pub fn aes_256_test_encrypt() { - let key = AesEncryptKey::new_aes_256(decode_hex( - "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", - )); - let mut block: [u8; 16]; - - block = decode_hex("6bc1bee22e409f96e93d7e117393172a"); - Aes::encrypt(&key, &mut block); - assert_eq!(decode_hex("f3eed1bdb5d2a03c064b5a7e3db181f8"), block); - - block = decode_hex("ae2d8a571e03ac9c9eb76fac45af8e51"); - Aes::encrypt(&key, &mut block); - assert_eq!(decode_hex("591ccb10d410ed26dc5ba74a31362870"), block); - - block = decode_hex("30c81c46a35ce411e5fbc1191a0a52ef"); - Aes::encrypt(&key, &mut block); - assert_eq!(decode_hex("b6ed21b99ca6f4f9f153e7b1beafed1d"), block); - - block = decode_hex("f69f2445df4f9b17ad2b417be66c3710"); - Aes::encrypt(&key, &mut block); - assert_eq!(decode_hex("23304b7a39f9f3ff067d8d8f9e24ecc7"), block); - } - - // test data from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.6 - #[test] - fn aes_256_test_decrypt() { - let key = AesDecryptKey::new_aes_256(decode_hex( - "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", - )); - - let mut block: [u8; 16]; - - block = decode_hex("f3eed1bdb5d2a03c064b5a7e3db181f8"); - Aes::decrypt(&key, &mut block); - assert_eq!(decode_hex("6bc1bee22e409f96e93d7e117393172a"), block); - - block = decode_hex("591ccb10d410ed26dc5ba74a31362870"); - Aes::decrypt(&key, &mut block); - assert_eq!(decode_hex("ae2d8a571e03ac9c9eb76fac45af8e51"), block); - - block = decode_hex("b6ed21b99ca6f4f9f153e7b1beafed1d"); - Aes::decrypt(&key, &mut block); - assert_eq!(decode_hex("30c81c46a35ce411e5fbc1191a0a52ef"), block); - - block = decode_hex("23304b7a39f9f3ff067d8d8f9e24ecc7"); - Aes::decrypt(&key, &mut block); - assert_eq!(decode_hex("f69f2445df4f9b17ad2b417be66c3710"), block); + fn aes_256() { + // test data from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.5 + let key = decode_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); + let plaintext = decode_hex("6bc1bee22e409f96e93d7e117393172a"); + let ciphertext = decode_hex("f3eed1bdb5d2a03c064b5a7e3db181f8"); + assert_eq!(ciphertext, EncryptKey::new_256(&key).encrypt(&plaintext)); + assert_eq!(plaintext, DecryptKey::new_256(&key).decrypt(&ciphertext)); } } diff --git a/src/rust/bssl-crypto/src/bn.rs b/src/rust/bssl-crypto/src/bn.rs deleted file mode 100644 index 35a196a77..000000000 --- a/src/rust/bssl-crypto/src/bn.rs +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright (c) 2023, Google Inc. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -use crate::{CSlice, ForeignType}; - -pub(crate) struct BigNum { - ptr: *mut bssl_sys::BIGNUM, -} - -// Safety: Implementation ensures `from_ptr(x).as_ptr() == x` -unsafe impl ForeignType for BigNum { - type CType = bssl_sys::BIGNUM; - - unsafe fn from_ptr(ptr: *mut Self::CType) -> Self { - Self { ptr } - } - - fn as_ptr(&self) -> *mut Self::CType { - self.ptr - } -} - -impl BigNum { - pub(crate) fn new() -> Self { - // Safety: There are no preconditions for BN_new() - unsafe { Self::from_ptr(bssl_sys::BN_new()) } - } -} - -impl From<&[u8]> for BigNum { - fn from(value: &[u8]) -> Self { - let value_ffi = CSlice(value); - // Safety: - // - `value` is a CSlice from safe Rust. - // - The `ret` argument can be null to request allocating a new result. - let ptr = unsafe { - bssl_sys::BN_bin2bn(value_ffi.as_ptr(), value_ffi.len(), core::ptr::null_mut()) - }; - assert!(!ptr.is_null()); - Self { ptr } - } -} - -impl Drop for BigNum { - fn drop(&mut self) { - // Safety: `self.ptr` is owned by `self`. - unsafe { bssl_sys::BN_free(self.ptr) } - } -} diff --git a/src/rust/bssl-crypto/src/digest.rs b/src/rust/bssl-crypto/src/digest.rs index a10b5ab6e..e5578e5f8 100644 --- a/src/rust/bssl-crypto/src/digest.rs +++ b/src/rust/bssl-crypto/src/digest.rs @@ -31,6 +31,7 @@ //! ``` use crate::{sealed, FfiSlice, ForeignTypeRef}; +use alloc::vec::Vec; use bssl_sys; #[non_exhaustive] @@ -49,6 +50,9 @@ pub trait Algorithm { /// Gets a reference to a message digest algorithm to be used by the HKDF implementation. #[doc(hidden)] fn get_md(_: sealed::Sealed) -> &'static MdRef; + + /// Hashes a message. + fn hash_to_vec(input: &[u8]) -> Vec; } /// The insecure SHA-1 hash algorithm. diff --git a/src/rust/bssl-crypto/src/ec.rs b/src/rust/bssl-crypto/src/ec.rs index 8bd8bda4a..90452c547 100644 --- a/src/rust/bssl-crypto/src/ec.rs +++ b/src/rust/bssl-crypto/src/ec.rs @@ -13,409 +13,582 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -//! `EcKey` and `EcGroup` structs for working with elliptic curve cryptography. This module is -//! intended for internal use within this crate only, to create higher-level abstractions suitable -//! to be exposed externally. +//! Definitions of NIST elliptic curves. +//! +//! If you're looking for curve25519, see the `x25519` and `ed25519` modules. -use alloc::{borrow::ToOwned, vec, vec::Vec}; -use core::{borrow::Borrow, fmt::Debug, ops::Deref, panic}; +// This module is substantially internal-only and is only public for the +// [`Curve`] trait, which is shared by ECDH and ECDSA. -use crate::{bn::BigNum, CSlice, CSliceMut, ForeignType, ForeignTypeRef}; +use crate::{cbb_to_buffer, parse_with_cbs, scoped, sealed, Buffer, FfiSlice}; +use alloc::{fmt::Debug, vec::Vec}; +use core::ptr::{null, null_mut}; -#[derive(Debug)] -pub(crate) struct EcKey { - ptr: *mut bssl_sys::EC_KEY, -} - -// Safety: Implementation ensures `from_ptr(x).as_ptr() == x` -unsafe impl ForeignType for EcKey { - type CType = bssl_sys::EC_KEY; - - unsafe fn from_ptr(ptr: *mut Self::CType) -> Self { - Self { ptr } - } - - fn as_ptr(&self) -> *mut Self::CType { - self.ptr - } -} - -// Safety: -// - `EC_KEY`'s documentation says "A given object may be used concurrently on multiple threads by -// non-mutating functions, provided no other thread is concurrently calling a mutating function.", -// which matches Rust's aliasing rules. -// - `ptr(&self)` and `ptr_mut(&mut self)` ensures that only a mutable reference can get a mutable -// `EC_KEY` pointer outside of this module. -unsafe impl Send for EcKey {} - -impl Clone for EcKey { - fn clone(&self) -> Self { - // Safety: - // - EcKey makes sure self.ptr is a valid pointer. - let ptr = unsafe { bssl_sys::EC_KEY_dup(self.ptr) }; - Self { ptr } - } -} - -/// Error type returned when conversion to or from an `EcKey` failed. -pub(crate) struct ConversionFailed; - -impl EcKey { - pub fn new_by_ec_group(ec_group: &EcGroupRef) -> Self { - // Safety: `EC_KEY_new` does not have preconditions - let eckey = unsafe { bssl_sys::EC_KEY_new() }; - assert!(!eckey.is_null()); - // Safety: - // - `eckey` is just allocated and doesn't have its group set yet - // - `EcGroup` ensures the `ptr` it contains is valid - unsafe { - assert_eq!( - bssl_sys::EC_KEY_set_group(eckey, ec_group.as_ptr()), - 1, - "EC_KEY_set_group failed" - ); - } - // Safety: `eckey` is allocated and null-checked - unsafe { Self::from_ptr(eckey) } - } - - /// Try to create a public-key version of `EcKey` from the given `value`. Returns error if the - /// slice is not a valid representation of a public key for the given curve. - /// - /// `curve_nid` should be a value defined in `bssl_sys::NID_*`. - #[allow(clippy::panic)] - pub(crate) fn try_new_public_key_from_bytes( - ec_group: &EcGroupRef, - value: &[u8], - ) -> Result { - let eckey = Self::new_by_ec_group(ec_group); - let value_ffi = CSlice(value); - - // Safety: The input slice `value_ffi` is a CSlice from safe Rust. - let result = unsafe { - bssl_sys::EC_KEY_oct2key( - eckey.ptr, - value_ffi.as_ptr(), - value_ffi.len(), - core::ptr::null_mut(), - ) - }; - match result { - 0 => Err(ConversionFailed), - 1 => Ok(eckey), - _ => panic!("Unexpected return value {result} from EC_KEY_oct2key"), - } - } - - pub(crate) fn to_affine_coordinates(&self) -> (BigNum, BigNum) { - let ecpoint = unsafe { bssl_sys::EC_KEY_get0_public_key(self.ptr) }; - let bn_x = BigNum::new(); - let bn_y = BigNum::new(); - - // Safety: - // - `EcKey` and `BigNum` structs ensures validity of their pointers. - let result = unsafe { - bssl_sys::EC_POINT_get_affine_coordinates( - bssl_sys::EC_KEY_get0_group(self.ptr), - ecpoint, - bn_x.as_ptr(), - bn_y.as_ptr(), - core::ptr::null_mut(), - ) - }; - assert_eq!( - result, 1, - "bssl_sys::EC_POINT_get_affine_coordinates failed" - ); - (bn_x, bn_y) - } - - pub(crate) fn generate(ec_group: &EcGroupRef) -> Self { - let eckey = EcKey::new_by_ec_group(ec_group); - // Safety: `EcKey` ensures eckey.ptr is valid. - let result = unsafe { bssl_sys::EC_KEY_generate_key(eckey.as_ptr()) }; - assert_eq!(result, 1, "bssl_sys::EC_KEY_generate_key failed"); - eckey - } - - pub(crate) fn try_new_public_key_from_affine_coordinates( - ec_group: &EcGroupRef, - x: &[u8], - y: &[u8], - ) -> Result { - let bn_x = BigNum::from(x); - let bn_y = BigNum::from(y); - - let eckey = EcKey::new_by_ec_group(ec_group); - // Safety: - // - Wrapper classes `EcKey` and `BigNum` ensures validity of the pointers - let result = unsafe { - bssl_sys::EC_KEY_set_public_key_affine_coordinates( - eckey.as_ptr(), - bn_x.as_ptr(), - bn_y.as_ptr(), - ) - }; - if result == 1 { - Ok(eckey) - } else { - Err(ConversionFailed) - } - } - - /// Tries to convert the given bytes into a private key contained within `EcKey`. - /// - /// `private_key_bytes` must be padded to the size of `curve_nid`'s group order, otherwise the - /// conversion will fail. - pub(crate) fn try_from_raw_bytes( - ec_group: &EcGroupRef, - private_key_bytes: &[u8], - ) -> Result { - let eckey = EcKey::new_by_ec_group(ec_group); - let private_key_bytes_ffi = CSlice(private_key_bytes); - // Safety: - // - `EcKey` ensures `eckey.ptr` is valid. - // - `private_key_bytes` is a CSlice from safe-rust. - let result = unsafe { - bssl_sys::EC_KEY_oct2priv( - eckey.as_ptr(), - private_key_bytes_ffi.as_ptr(), - private_key_bytes_ffi.len(), - ) - }; - if result != 1 { - return Err(ConversionFailed); - } - - Ok(eckey) - } - - /// Converts between the private key component of `eckey` and octet form. The octet form - /// consists of the content octets of the `privateKey` `OCTET STRING` in an `ECPrivateKey` ASN.1 - /// structure - pub(crate) fn to_raw_bytes(&self) -> Vec { - let mut output = vec![0_u8; 66]; - let mut private_key_bytes_ffi = CSliceMut::from(&mut output[..]); - // Safety: - // - `EcKey` ensures `self.ptr` is valid. - // - `private_key_bytes_ffi` is a CSliceMut we just allocated. - // - 66 bytes is guaranteed to be sufficient to store an EC private key - let num_octets_stored = unsafe { - bssl_sys::EC_KEY_priv2oct( - self.as_ptr(), - private_key_bytes_ffi.as_mut_ptr(), - private_key_bytes_ffi.len(), - ) - }; - // Safety: `EC_KEY_priv2oct` just wrote `num_octets_stored` into the buffer. - unsafe { output.set_len(num_octets_stored) } - output - } - - pub(crate) fn public_key_eq(&self, other: &Self) -> bool { - let result = unsafe { - bssl_sys::EC_POINT_cmp( - bssl_sys::EC_KEY_get0_group(self.ptr), - bssl_sys::EC_KEY_get0_public_key(self.ptr), - bssl_sys::EC_KEY_get0_public_key(other.ptr), - core::ptr::null_mut(), - ) - }; - assert_ne!(result, -1, "bssl_sys::EC_POINT_cmp failed"); - result == 0 - } - - pub(crate) fn to_vec(&self) -> Vec { - // Safety: `self.ptr` is owned by `self` - let ecgroup = unsafe { bssl_sys::EC_KEY_get0_group(self.ptr) }; - let ecpoint = unsafe { bssl_sys::EC_KEY_get0_public_key(self.ptr) }; - let conv_form = unsafe { bssl_sys::EC_KEY_get_conv_form(self.ptr) }; - // Safety: - // - When passing null to EC_POINT_point2oct's `buf` argument, it returns the size of the - // resulting buffer. - let output_size = unsafe { - bssl_sys::EC_POINT_point2oct( - ecgroup, - ecpoint, - conv_form, - core::ptr::null_mut(), - 0, - core::ptr::null_mut(), - ) - }; - assert_ne!(output_size, 0, "bssl_sys::EC_POINT_point2oct failed"); - let mut result_vec = Vec::::with_capacity(output_size); - let buf_len = unsafe { - bssl_sys::EC_POINT_point2oct( - ecgroup, - ecpoint, - conv_form, - result_vec.as_mut_ptr(), - output_size, - core::ptr::null_mut(), - ) - }; - assert_ne!(buf_len, 0, "bssl_sys::EC_POINT_point2oct failed"); - // Safety: The length is what EC_POINT_point2oct just told us it filled into the buffer. - unsafe { result_vec.set_len(buf_len) } - result_vec - } -} - -impl Drop for EcKey { - fn drop(&mut self) { - // Safety: `self.ptr` is owned by this struct - unsafe { bssl_sys::EC_KEY_free(self.ptr) } - } -} - -/// Describes an elliptic curve. -#[non_exhaustive] -pub struct EcGroupRef; - -// Safety: Default implementation in ForeignTypeRef ensures the preconditions -// required by that trait holds. -unsafe impl ForeignTypeRef for EcGroupRef { - type CType = bssl_sys::EC_GROUP; -} - -impl Borrow for EcGroup { - fn borrow(&self) -> &EcGroupRef { - unsafe { EcGroupRef::from_ptr(self.ptr) } - } -} - -impl ToOwned for EcGroupRef { - type Owned = EcGroup; - - fn to_owned(&self) -> Self::Owned { - // Safety: `EcGroupRef` is a valid pointer - let new_ec_group = unsafe { bssl_sys::EC_GROUP_dup(self.as_ptr()) }; - assert!(!new_ec_group.is_null(), "EC_GROUP_dup failed"); - EcGroup { ptr: new_ec_group } - } -} - -impl AsRef for EcGroup { - fn as_ref(&self) -> &EcGroupRef { - self.deref() - } -} - -impl PartialEq for EcGroupRef { - fn eq(&self, other: &Self) -> bool { - // Safety: - // - Self and other are valid pointers since they come from `EcGroupRef` - // - Third argument is ignored - unsafe { - bssl_sys::EC_GROUP_cmp( - self.as_ptr(), - other.as_ptr(), - /* ignored */ core::ptr::null_mut(), - ) == 0 - } - } -} - -impl Eq for EcGroupRef {} - -pub struct EcGroup { - ptr: *mut bssl_sys::EC_GROUP, -} - -impl Deref for EcGroup { - type Target = EcGroupRef; - - fn deref(&self) -> &Self::Target { - unsafe { EcGroupRef::from_ptr(self.ptr) } - } -} - -impl Drop for EcGroup { - fn drop(&mut self) { - unsafe { bssl_sys::EC_GROUP_free(self.ptr) } - } -} - -/// An elliptic curve, used as the type parameter for [`PublicKey`] and [`PrivateKey`]. +/// An elliptic curve. pub trait Curve: Debug { - /// The size of the affine coordinates for this curve. - const AFFINE_COORDINATE_SIZE: usize; + #[doc(hidden)] + fn group(_: sealed::Sealed) -> Group; - /// Create a new [`EcGroup`] for this curve. - fn ec_group() -> &'static EcGroupRef; + /// Hash `data` using a hash function suitable for the curve. (I.e. + /// SHA-256 for P-256 and SHA-384 for P-384.) + #[doc(hidden)] + fn hash(data: &[u8]) -> Vec; } -/// The P-224 curve, corresponding to `NID_secp224r1`. -#[derive(Debug)] -pub struct P224; - -impl Curve for P224 { - const AFFINE_COORDINATE_SIZE: usize = 28; - - fn ec_group() -> &'static EcGroupRef { - // Safety: EC_group_p224 does not have any preconditions - unsafe { EcGroupRef::from_ptr(bssl_sys::EC_group_p224() as *mut _) } - } -} - -/// The P-256 curve, corresponding to `NID_X9_62_prime256v1`. +/// The NIST P-256 curve, also called secp256r1. #[derive(Debug)] pub struct P256; impl Curve for P256 { - const AFFINE_COORDINATE_SIZE: usize = 32; + fn group(_: sealed::Sealed) -> Group { + Group::P256 + } - fn ec_group() -> &'static EcGroupRef { - // Safety: EC_group_p256 does not have any preconditions - unsafe { EcGroupRef::from_ptr(bssl_sys::EC_group_p256() as *mut _) } + fn hash(data: &[u8]) -> Vec { + crate::digest::Sha256::hash(data).to_vec() } } -/// The P-384 curve, corresponding to `NID_secp384r1`. +/// The NIST P-384 curve, also called secp384r1. #[derive(Debug)] pub struct P384; impl Curve for P384 { - const AFFINE_COORDINATE_SIZE: usize = 48; + fn group(_: sealed::Sealed) -> Group { + Group::P384 + } - fn ec_group() -> &'static EcGroupRef { - // Safety: EC_group_p384 does not have any preconditions - unsafe { EcGroupRef::from_ptr(bssl_sys::EC_group_p384() as *mut _) } + fn hash(data: &[u8]) -> Vec { + crate::digest::Sha384::hash(data).to_vec() } } -/// The P-521 curve, corresponding to `NID_secp521r1`. -#[derive(Debug)] -pub struct P521; +#[derive(Copy, Clone)] +#[doc(hidden)] +pub enum Group { + P256, + P384, +} -impl Curve for P521 { - const AFFINE_COORDINATE_SIZE: usize = 66; - - fn ec_group() -> &'static EcGroupRef { - // Safety: EC_group_p521 does not have any preconditions - unsafe { EcGroupRef::from_ptr(bssl_sys::EC_group_p521() as *mut _) } +impl Group { + fn as_ffi_ptr(self) -> *const bssl_sys::EC_GROUP { + // Safety: `group` is an address-space constant. These functions + // cannot fail and no resources need to be released in the future. + match self { + Group::P256 => unsafe { bssl_sys::EC_group_p256() }, + Group::P384 => unsafe { bssl_sys::EC_group_p384() }, + } } } +/// Point is a valid, finite point on some curve. +pub(crate) struct Point { + group: *const bssl_sys::EC_GROUP, + point: *mut bssl_sys::EC_POINT, +} + +impl Point { + /// Construct an uninitialized curve point. This is not public and all + /// callers must ensure that the point is initialized before being returned. + fn new(group: Group) -> Self { + let group = group.as_ffi_ptr(); + // Safety: `group` is valid because it was constructed just above. + let point = unsafe { bssl_sys::EC_POINT_new(group) }; + // `EC_POINT_new` only fails if out of memory, which is not a case that + // is handled short of panicking. + assert!(!point.is_null()); + Self { group, point } + } + + /// Construct a point by multipling the curve's base point by the given + /// scalar. + unsafe fn from_scalar(group: Group, scalar: *const bssl_sys::BIGNUM) -> Option { + let point = Self::new(group); + // Safety: the members of `point` are valid by construction. `scalar` + // is assumed to be valid. + let result = unsafe { + bssl_sys::EC_POINT_mul( + point.group, + point.point, + scalar, + /*q=*/ null(), + /*m=*/ null(), + /*ctx=*/ null_mut(), + ) + }; + if result != 1 { + return None; + } + if 1 == unsafe { bssl_sys::EC_POINT_is_at_infinity(point.group, point.point) } { + return None; + } + Some(point) + } + + /// Duplicate the given finite point. + unsafe fn clone_from_ptr( + group: *const bssl_sys::EC_GROUP, + point: *const bssl_sys::EC_POINT, + ) -> Point { + assert_eq!(0, unsafe { + bssl_sys::EC_POINT_is_at_infinity(group, point) + }); + + // Safety: we assume that the caller is passing valid pointers + let new_point = unsafe { bssl_sys::EC_POINT_dup(point, group) }; + // `EC_POINT_dup` only fails if out of memory, which is not a case that + // is handled short of panicking. + assert!(!new_point.is_null()); + + Self { + group, + point: new_point, + } + } + + pub fn as_ffi_ptr(&self) -> *const bssl_sys::EC_POINT { + self.point + } + + /// Create a new point from an uncompressed X9.62 representation. + /// + /// (X9.62 is the standard representation of an elliptic-curve point that + /// starts with an 0x04 byte.) + pub fn from_x962_uncompressed(group: Group, x962: &[u8]) -> Option { + const UNCOMPRESSED: u8 = + bssl_sys::point_conversion_form_t::POINT_CONVERSION_UNCOMPRESSED as u8; + if x962.first()? != &UNCOMPRESSED { + return None; + } + + let point = Self::new(group); + // Safety: `point` is valid by construction. `x962` is a valid memory + // buffer. + let result = unsafe { + bssl_sys::EC_POINT_oct2point( + point.group, + point.point, + x962.as_ffi_ptr(), + x962.len(), + /*bn_ctx=*/ null_mut(), + ) + }; + if result == 1 { + // X9.62 format cannot represent the point at infinity, so this + // should be moot, but `Point` must never contain infinity. + assert_eq!(0, unsafe { + bssl_sys::EC_POINT_is_at_infinity(point.group, point.point) + }); + Some(point) + } else { + None + } + } + + pub fn to_x962_uncompressed(&self) -> Buffer { + // Safety: arguments are valid, `EC_KEY` ensures that the the group is + // correct for the point, and a `Point` is always finite. + unsafe { to_x962_uncompressed(self.group, self.point) } + } + + pub fn from_der_subject_public_key_info(group: Group, spki: &[u8]) -> Option { + let mut pkey = scoped::EvpPkey::from_ptr(parse_with_cbs( + spki, + // Safety: if called, `pkey` is the non-null result of `EVP_parse_public_key`. + |pkey| unsafe { bssl_sys::EVP_PKEY_free(pkey) }, + // Safety: `cbs` is a valid pointer in this context. + |cbs| unsafe { bssl_sys::EVP_parse_public_key(cbs) }, + )?); + let ec_key = unsafe { bssl_sys::EVP_PKEY_get0_EC_KEY(pkey.as_ffi_ptr()) }; + if ec_key.is_null() { + // Not an ECC key. + return None; + } + let parsed_group = unsafe { bssl_sys::EC_KEY_get0_group(ec_key) }; + if parsed_group != group.as_ffi_ptr() { + // ECC key for a different curve. + return None; + } + let point = unsafe { bssl_sys::EC_KEY_get0_public_key(ec_key) }; + if point.is_null() { + return None; + } + // Safety: `ec_key` is still owned by `pkey` and doesn't need to be freed. + Some(unsafe { Self::clone_from_ptr(parsed_group, point) }) + } + + /// Calls `func` with an `EC_KEY` that contains a copy of this point. + pub fn with_point_as_ec_key(&self, func: F) -> T + where + F: FnOnce(*mut bssl_sys::EC_KEY) -> T, + { + let mut ec_key = scoped::EcKey::new(); + // Safety: `self.group` is always valid by construction and this doesn't + // pass ownership. + assert_eq!(1, unsafe { + bssl_sys::EC_KEY_set_group(ec_key.as_ffi_ptr(), self.group) + }); + // Safety: `self.point` is always valid by construction and this doesn't + // pass ownership. + assert_eq!(1, unsafe { + bssl_sys::EC_KEY_set_public_key(ec_key.as_ffi_ptr(), self.point) + }); + func(ec_key.as_ffi_ptr()) + } + + pub fn to_der_subject_public_key_info(&self) -> Buffer { + // Safety: `ec_key` is a valid pointer in this context. + self.with_point_as_ec_key(|ec_key| unsafe { to_der_subject_public_key_info(ec_key) }) + } +} + +// Safety: +// +// An `EC_POINT` can be used concurrently from multiple threads so long as no +// mutating operations are performed. The mutating operations used here are +// `EC_POINT_mul` and `EC_POINT_oct2point` (which can be observed by setting +// `point` to be `*const` in the struct and seeing what errors trigger. +// +// Both those operations are done internally, however, before a `Point` is +// returned. So, after construction, callers cannot mutate the `EC_POINT`. +unsafe impl Sync for Point {} +unsafe impl Send for Point {} + +impl Drop for Point { + fn drop(&mut self) { + // Safety: `self.point` must be valid because only valid `Point`s can + // be constructed. `self.group` does not need to be freed. + unsafe { bssl_sys::EC_POINT_free(self.point) } + } +} + +/// Key holds both a public and private key. While BoringSSL allows an `EC_KEY` +/// to also be a) empty, b) holding only a private scalar, or c) holding only +// a public key, those cases are never exposed as a `Key`. +pub(crate) struct Key(*mut bssl_sys::EC_KEY); + +impl Key { + /// Construct an uninitialized key. This is not public and all + /// callers must ensure that the key is initialized before being returned. + fn new(group: Group) -> Self { + let key = unsafe { bssl_sys::EC_KEY_new() }; + // `EC_KEY_new` only fails if out of memory, which is not a case that + // is handled short of panicking. + assert!(!key.is_null()); + + // Setting the group on a fresh `EC_KEY` never fails. + assert_eq!(1, unsafe { + bssl_sys::EC_KEY_set_group(key, group.as_ffi_ptr()) + }); + + Self(key) + } + + pub fn as_ffi_ptr(&self) -> *const bssl_sys::EC_KEY { + self.0 + } + + /// Generate a random private key. + pub fn generate(group: Group) -> Self { + let key = Self::new(group); + // Generation only fails if out of memory, which is only handled by + // panicking. + assert_eq!(1, unsafe { bssl_sys::EC_KEY_generate_key(key.0) }); + // `EC_KEY_generate_key` is documented as also setting the public key. + key + } + + /// Construct a private key from a big-endian representation of the private + /// scalar. The scalar must be zero padded to the correct length for the + /// curve. + pub fn from_big_endian(group: Group, scalar: &[u8]) -> Option { + let key = Self::new(group); + // Safety: `key.0` is always valid by construction. + let result = unsafe { bssl_sys::EC_KEY_oct2priv(key.0, scalar.as_ffi_ptr(), scalar.len()) }; + if result != 1 { + return None; + } + + // BoringSSL allows an `EC_KEY` to have a private scalar without a + // public point, but `Key` is never exposed in that state. + + // Safety: `key.0` is valid by construction. The returned value is + // still owned the `EC_KEY`. + let scalar = unsafe { bssl_sys::EC_KEY_get0_private_key(key.0) }; + assert!(!scalar.is_null()); + + // Safety: `scalar` is a valid pointer. + let point = unsafe { Point::from_scalar(group, scalar)? }; + // Safety: `key.0` is valid by construction, as is `point.point`. The + // point is copied into the `EC_KEY` so ownership isn't being moved. + let result = unsafe { bssl_sys::EC_KEY_set_public_key(key.0, point.point) }; + // Setting the public key should only fail if out of memory, which this + // crate doesn't handle, or if the groups don't match, which is + // impossible. + assert_eq!(result, 1); + + Some(key) + } + + pub fn to_big_endian(&self) -> Buffer { + let mut ptr: *mut u8 = null_mut(); + // Safety: `self.0` is valid by construction. If this returns non-zero + // then ptr holds ownership of a buffer. + let len = unsafe { bssl_sys::EC_KEY_priv2buf(self.0, &mut ptr) }; + assert!(len != 0); + Buffer { ptr, len } + } + + /// Parses an ECPrivateKey structure (from RFC 5915). + pub fn from_der_ec_private_key(group: Group, der: &[u8]) -> Option { + let key = parse_with_cbs( + der, + // Safety: in this context, `key` is the non-null result of + // `EC_KEY_parse_private_key`. + |key| unsafe { bssl_sys::EC_KEY_free(key) }, + // Safety: `cbs` is valid per `parse_with_cbs` and `group` always + // returns a valid pointer. + |cbs| unsafe { bssl_sys::EC_KEY_parse_private_key(cbs, group.as_ffi_ptr()) }, + )?; + Some(Self(key)) + } + + /// Serializes this private key as an ECPrivateKey structure from RFC 5915. + pub fn to_der_ec_private_key(&self) -> Buffer { + cbb_to_buffer(64, |cbb| unsafe { + // Safety: the `EC_KEY` is always valid so `EC_KEY_marshal_private_key` + // should only fail if out of memory, which this crate doesn't handle. + assert_eq!( + 1, + bssl_sys::EC_KEY_marshal_private_key( + cbb, + self.0, + bssl_sys::EC_PKEY_NO_PARAMETERS as u32 + ) + ); + }) + } + + /// Parses a PrivateKeyInfo structure (from RFC 5208). + pub fn from_der_private_key_info(group: Group, der: &[u8]) -> Option { + let mut pkey = scoped::EvpPkey::from_ptr(parse_with_cbs( + der, + // Safety: in this context, `pkey` is the non-null result of + // `EVP_parse_private_key`. + |pkey| unsafe { bssl_sys::EVP_PKEY_free(pkey) }, + // Safety: `cbs` is valid per `parse_with_cbs`. + |cbs| unsafe { bssl_sys::EVP_parse_private_key(cbs) }, + )?); + let ec_key = unsafe { bssl_sys::EVP_PKEY_get1_EC_KEY(pkey.as_ffi_ptr()) }; + if ec_key.is_null() { + return None; + } + // Safety: `ec_key` is now owned by this function. + let parsed_group = unsafe { bssl_sys::EC_KEY_get0_group(ec_key) }; + if parsed_group == group.as_ffi_ptr() { + // Safety: parsing an EC_KEY always set the public key. It should + // be impossible for the public key to be infinity, but double-check. + let is_infinite = unsafe { + bssl_sys::EC_POINT_is_at_infinity( + bssl_sys::EC_KEY_get0_group(ec_key), + bssl_sys::EC_KEY_get0_public_key(ec_key), + ) + }; + if is_infinite == 0 { + // Safety: `EVP_PKEY_get1_EC_KEY` returned ownership, which we can move + // into the returned object. + return Some(Self(ec_key)); + } + } + unsafe { bssl_sys::EC_KEY_free(ec_key) }; + None + } + + /// Serializes this private key as a PrivateKeyInfo structure from RFC 5208. + pub fn to_der_private_key_info(&self) -> Buffer { + let mut pkey = scoped::EvpPkey::new(); + // Safety: `pkey` was just allocated above; the `EC_KEY` is valid by + // construction. This call takes a reference to the `EC_KEY` and so + // hasn't stolen ownership from `self`. + assert_eq!(1, unsafe { + bssl_sys::EVP_PKEY_set1_EC_KEY(pkey.as_ffi_ptr(), self.0) + }); + cbb_to_buffer(64, |cbb| unsafe { + // `EVP_marshal_private_key` should always return one because this + // key is valid by construction. + assert_eq!(1, bssl_sys::EVP_marshal_private_key(cbb, pkey.as_ffi_ptr())); + }) + } + + pub fn to_point(&self) -> Point { + // Safety: `self.0` is valid by construction. + let group = unsafe { bssl_sys::EC_KEY_get0_group(self.0) }; + let point = unsafe { bssl_sys::EC_KEY_get0_public_key(self.0) }; + // A `Key` is never constructed without a public key. + assert!(!point.is_null()); + // Safety: pointers are valid and `clone_from_ptr` doesn't take + // ownership. + unsafe { Point::clone_from_ptr(group, point) } + } + + pub fn to_x962_uncompressed(&self) -> Buffer { + // Safety: `self.0` is valid by construction. + let group = unsafe { bssl_sys::EC_KEY_get0_group(self.0) }; + let point = unsafe { bssl_sys::EC_KEY_get0_public_key(self.0) }; + // Safety: arguments are valid, `EC_KEY` ensures that the the group is + // correct for the point, and a `Key` always holds a finite public point. + unsafe { to_x962_uncompressed(group, point) } + } + + pub fn to_der_subject_public_key_info(&self) -> Buffer { + // Safety: `self.0` is always valid by construction. + unsafe { to_der_subject_public_key_info(self.0) } + } +} + +// Safety: +// +// An `EC_KEY` is safe to use from multiple threads so long as no mutating +// operations are performed. (Reference count changes don't count as mutating.) +// The mutating operations used here are: +// * EC_KEY_generate_key +// * EC_KEY_oct2priv +// * EC_KEY_set_public_key +// But those are all done internally, before a `Key` is returned. So, once +// constructed, callers cannot mutate the `EC_KEY`. +unsafe impl Sync for Key {} +unsafe impl Send for Key {} + +impl Drop for Key { + fn drop(&mut self) { + // Safety: `self.0` must be valid because only valid `Key`s can + // be constructed. + unsafe { bssl_sys::EC_KEY_free(self.0) } + } +} + +/// Serialize a finite point to uncompressed X9.62 format. +/// +/// Callers must ensure that the arguments are valid, that the point has the +/// specified group, and that the point is finite. +unsafe fn to_x962_uncompressed( + group: *const bssl_sys::EC_GROUP, + point: *const bssl_sys::EC_POINT, +) -> Buffer { + cbb_to_buffer(65, |cbb| unsafe { + // Safety: the caller must ensure that the arguments are valid. + let result = bssl_sys::EC_POINT_point2cbb( + cbb, + group, + point, + bssl_sys::point_conversion_form_t::POINT_CONVERSION_UNCOMPRESSED, + /*bn_ctx=*/ null_mut(), + ); + // The public key is always finite, so `EC_POINT_point2cbb` only fails + // if out of memory, which isn't handled by this crate. + assert_eq!(result, 1); + }) +} + +unsafe fn to_der_subject_public_key_info(ec_key: *mut bssl_sys::EC_KEY) -> Buffer { + let mut pkey = scoped::EvpPkey::new(); + // Safety: this takes a reference to `ec_key` and so doesn't steal ownership. + assert_eq!(1, unsafe { + bssl_sys::EVP_PKEY_set1_EC_KEY(pkey.as_ffi_ptr(), ec_key) + }); + cbb_to_buffer(65, |cbb| unsafe { + // The arguments are valid so this will only fail if out of memory, + // which this crate doesn't handle. + assert_eq!(1, bssl_sys::EVP_marshal_public_key(cbb, pkey.as_ffi_ptr())); + }) +} + #[cfg(test)] mod test { - use crate::ec::P521; + use super::*; - use super::{Curve, EcGroupRef, P256}; + fn test_point_format(serialize_func: Serialize, parse_func: Parse) + where + Serialize: FnOnce(&Point) -> Buffer, + Parse: Fn(&[u8]) -> Option, + { + let key = Key::generate(Group::P256); + let point = key.to_point(); - #[test] - fn test_ec_group_clone_and_eq() { - let group = P256::ec_group(); - let group_clone = group.to_owned(); - let group2: &EcGroupRef = &group_clone; - assert!(group == group2); + let mut vec = serialize_func(&point).as_ref().to_vec(); + let point2 = parse_func(vec.as_slice()).unwrap(); + assert_eq!( + point.to_x962_uncompressed().as_ref(), + point2.to_x962_uncompressed().as_ref() + ); + + assert!(parse_func(&vec.as_slice()[0..16]).is_none()); + + vec[10] ^= 1; + assert!(parse_func(vec.as_slice()).is_none()); + vec[10] ^= 1; + + assert!(parse_func(b"").is_none()); } #[test] - fn test_ec_group_not_equal() { - let group = P256::ec_group(); - let group2 = P521::ec_group(); - assert!(group != group2) + fn x962() { + let x962 = b"\x04\x74\xcf\x69\xcb\xd1\x2b\x75\x07\x42\x85\xcf\x69\x6f\xc2\x56\x4b\x90\xe7\xeb\xbc\xd0\xe7\x20\x36\x86\x66\xbe\xcc\x94\x75\xa2\xa4\x4c\x2a\xf8\xa2\x56\xb8\x92\xb7\x7d\x17\xba\x97\x93\xbb\xf2\x9f\x52\x26\x7d\x90\xf9\x2c\x37\x26\x02\xbb\x4e\xd1\x89\x7c\xad\x54"; + assert!(Point::from_x962_uncompressed(Group::P256, x962).is_some()); + + test_point_format( + |point| point.to_x962_uncompressed(), + |buf| Point::from_x962_uncompressed(Group::P256, buf), + ); + } + + #[test] + fn spki() { + test_point_format( + |point| point.to_der_subject_public_key_info(), + |buf| Point::from_der_subject_public_key_info(Group::P256, buf), + ); + } + + fn test_key_format(serialize_func: Serialize, parse_func: Parse) + where + Serialize: FnOnce(&Key) -> Buffer, + Parse: Fn(&[u8]) -> Option, + { + let key = Key::generate(Group::P256); + + let vec = serialize_func(&key).as_ref().to_vec(); + let key2 = parse_func(vec.as_slice()).unwrap(); + assert_eq!( + key.to_x962_uncompressed().as_ref(), + key2.to_x962_uncompressed().as_ref() + ); + + assert!(parse_func(&vec.as_slice()[0..16]).is_none()); + assert!(parse_func(b"").is_none()); + } + + #[test] + fn der_ec_private_key() { + test_key_format( + |key| key.to_der_ec_private_key(), + |buf| Key::from_der_ec_private_key(Group::P256, buf), + ); + } + + #[test] + fn der_private_key_info() { + test_key_format( + |key| key.to_der_private_key_info(), + |buf| Key::from_der_private_key_info(Group::P256, buf), + ); + } + + #[test] + fn big_endian() { + test_key_format( + |key| key.to_big_endian(), + |buf| Key::from_big_endian(Group::P256, buf), + ); } } diff --git a/src/rust/bssl-crypto/src/ecdh.rs b/src/rust/bssl-crypto/src/ecdh.rs index aca711bd8..85476a9fb 100644 --- a/src/rust/bssl-crypto/src/ecdh.rs +++ b/src/rust/bssl-crypto/src/ecdh.rs @@ -13,403 +13,192 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +//! Elliptic Curve Diffie-Hellman operations. +//! +//! This module implements ECDH over the NIST curves P-256 and P-384. +//! +//! ``` +//! use bssl_crypto::{ecdh, ec::P256}; +//! +//! let alice_private_key = ecdh::PrivateKey::::generate(); +//! let alice_public_key_serialized = alice_private_key.to_x962_uncompressed(); +//! +//! // Somehow, Alice's public key is sent to Bob. +//! let bob_private_key = ecdh::PrivateKey::::generate(); +//! let alice_public_key = +//! ecdh::PublicKey::::from_x962_uncompressed( +//! alice_public_key_serialized.as_ref()) +//! .unwrap(); +//! let shared_key1 = bob_private_key.compute_shared_key(&alice_public_key); +//! +//! // Likewise, Alice gets Bob's public key and computes the same shared key. +//! let bob_public_key = bob_private_key.to_public_key(); +//! let shared_key2 = alice_private_key.compute_shared_key(&bob_public_key); +//! assert_eq!(shared_key1, shared_key2); +//! ``` + +use crate::{ec, sealed, with_output_vec, Buffer}; use alloc::vec::Vec; use core::marker::PhantomData; -use crate::{ - ec::{Curve, EcKey}, - pkey::{Pkey, PkeyCtx}, - CSliceMut, ForeignType, -}; - -pub use crate::ec::P256; - -/// Private key used in a elliptic curve Diffie-Hellman. -pub struct PrivateKey { - /// An EcKey containing the private-public key pair - eckey: EcKey, +/// An ECDH public key over the given curve. +pub struct PublicKey { + point: ec::Point, marker: PhantomData, } -/// Error type for ECDH operations. -#[derive(Debug)] -pub enum Error { - /// Failed when trying to convert between representations. - ConversionFailed, - /// The Diffie-Hellman key exchange failed. - DiffieHellmanFailed, -} - -impl PrivateKey { - /// Derives a shared secret from this private key and the given public key. - /// - /// # Panics - /// When `OUTPUT_SIZE` is insufficient to store the output of the shared secret. - #[allow(clippy::expect_used)] - pub fn diffie_hellman( - &self, - other_public_key: &PublicKey, - ) -> Result, Error> { - let pkey: Pkey = (&self.eckey).into(); - let pkey_ctx = PkeyCtx::new(&pkey); - let other_pkey: Pkey = (&other_public_key.eckey).into(); - let mut output = [0_u8; OUTPUT_SIZE]; - pkey_ctx - .diffie_hellman(&other_pkey, CSliceMut(&mut output)) - .map(|_| SharedSecret(output)) - .map_err(|_| Error::DiffieHellmanFailed) +impl PublicKey { + /// Parse a public key in uncompressed X9.62 format. (This is the common + /// format for elliptic curve points beginning with an 0x04 byte.) + pub fn from_x962_uncompressed(x962: &[u8]) -> Option { + let point = ec::Point::from_x962_uncompressed(C::group(sealed::Sealed), x962)?; + Some(Self { + point, + marker: PhantomData, + }) } - /// Generate a new private key for use in a Diffie-Hellman key exchange. + /// Serialize this key as uncompressed X9.62 format. + pub fn to_x962_uncompressed(&self) -> Buffer { + self.point.to_x962_uncompressed() + } +} + +/// An ECDH private key over the given curve. +pub struct PrivateKey { + key: ec::Key, + marker: PhantomData, +} + +impl PrivateKey { + /// Generate a random private key. pub fn generate() -> Self { Self { - eckey: EcKey::generate(C::ec_group()), + key: ec::Key::generate(C::group(sealed::Sealed)), marker: PhantomData, } } - /// Tries to convert the given bytes into an private key. - /// - /// `private_key_bytes` is the octet form that consists of the content octets of the - /// `privateKey` `OCTET STRING` in an `ECPrivateKey` ASN.1 structure. - /// - /// Returns an error if the given bytes is not a valid representation of a P-256 private key. - pub fn from_private_bytes(private_key_bytes: &[u8]) -> Result { - EcKey::try_from_raw_bytes(C::ec_group(), private_key_bytes) - .map(|eckey| Self { - eckey, - marker: PhantomData, - }) - .map_err(|_| Error::ConversionFailed) - } - - /// Serializes this private key as a big-endian integer, zero-padded to the size of key's group - /// order and returns the result. - pub fn to_bytes(&self) -> Vec { - self.eckey.to_raw_bytes() - } -} - -impl<'a, C: Curve> From<&'a PrivateKey> for PublicKey { - fn from(value: &'a PrivateKey) -> Self { - Self { - eckey: value.eckey.clone(), + /// Parse a `PrivateKey` from a zero-padded, big-endian representation of the secret scalar. + pub fn from_big_endian(scalar: &[u8]) -> Option { + let key = ec::Key::from_big_endian(C::group(sealed::Sealed), scalar)?; + Some(Self { + key, marker: PhantomData, + }) + } + + /// Return the private scalar as zero-padded, big-endian bytes. + pub fn to_big_endian(&self) -> Buffer { + self.key.to_big_endian() + } + + /// Parse an ECPrivateKey structure (from RFC 5915). The key must be on the + /// specified curve. + pub fn from_der_ec_private_key(der: &[u8]) -> Option { + let key = ec::Key::from_der_ec_private_key(C::group(sealed::Sealed), der)?; + Some(Self { + key, + marker: PhantomData, + }) + } + + /// Serialize this private key as an ECPrivateKey structure (from RFC 5915). + pub fn to_der_ec_private_key(&self) -> Buffer { + self.key.to_der_ec_private_key() + } + + /// Parse a PrivateKeyInfo structure (from RFC 5208). The key must be on the + /// specified curve. + pub fn from_der_private_key_info(der: &[u8]) -> Option { + let key = ec::Key::from_der_private_key_info(C::group(sealed::Sealed), der)?; + Some(Self { + key, + marker: PhantomData, + }) + } + + /// Serialize this private key as a PrivateKeyInfo structure (from RFC 5208). + pub fn to_der_private_key_info(&self) -> Buffer { + self.key.to_der_private_key_info() + } + + /// Serialize the _public_ part of this key in uncompressed X9.62 format. + pub fn to_x962_uncompressed(&self) -> Buffer { + self.key.to_x962_uncompressed() + } + + /// Compute the shared key between this private key and the given public key. + /// The result should be used with a key derivation function that includes + /// the two public keys. + pub fn compute_shared_key(&self, other_public_key: &PublicKey) -> Vec { + // 384 bits is the largest curve supported. The buffer is sized to be + // larger than this so that truncation of the output can be noticed. + let max_output = 384 / 8 + 1; + unsafe { + with_output_vec(max_output, |out_buf| { + // Safety: + // - `out_buf` points to at least `max_output` bytes, as + // required. + // - The `EC_POINT` and `EC_KEY` pointers are valid by construction. + let num_out_bytes = bssl_sys::ECDH_compute_key( + out_buf as *mut core::ffi::c_void, + max_output, + other_public_key.point.as_ffi_ptr(), + self.key.as_ffi_ptr(), + None, + ); + // Out of memory is not handled by this crate. + assert!(num_out_bytes > 0); + let num_out_bytes = num_out_bytes as usize; + // If the buffer was completely filled then it was probably + // truncated, which should never happen. + assert!(num_out_bytes < max_output); + num_out_bytes + }) } } -} -/// A public key for elliptic curve. -#[derive(Clone, Debug)] -pub struct PublicKey { - /// An EcKey containing the public key - eckey: EcKey, - marker: PhantomData, -} - -impl Eq for PublicKey {} - -impl PartialEq for PublicKey { - fn eq(&self, other: &Self) -> bool { - self.eckey.public_key_eq(&other.eckey) - } -} - -impl PublicKey { - /// Converts this public key to its byte representation. - pub fn to_vec(&self) -> Vec { - self.eckey.to_vec() - } - - /// Converts the given affine coordinates into a public key. - pub fn from_affine_coordinates( - x: &[u8; AFFINE_COORDINATE_SIZE], - y: &[u8; AFFINE_COORDINATE_SIZE], - ) -> Result { - assert_eq!(AFFINE_COORDINATE_SIZE, C::AFFINE_COORDINATE_SIZE); - EcKey::try_new_public_key_from_affine_coordinates(C::ec_group(), &x[..], &y[..]) - .map(|eckey| Self { - eckey, - marker: PhantomData, - }) - .map_err(|_| Error::ConversionFailed) - } - - /// Converts this public key to its affine coordinates. - pub fn to_affine_coordinates( - &self, - ) -> ([u8; AFFINE_COORDINATE_SIZE], [u8; AFFINE_COORDINATE_SIZE]) { - assert_eq!(AFFINE_COORDINATE_SIZE, C::AFFINE_COORDINATE_SIZE); - let (bn_x, bn_y) = self.eckey.to_affine_coordinates(); - - let mut x_bytes_uninit = core::mem::MaybeUninit::<[u8; AFFINE_COORDINATE_SIZE]>::uninit(); - let mut y_bytes_uninit = core::mem::MaybeUninit::<[u8; AFFINE_COORDINATE_SIZE]>::uninit(); - // Safety: - // - `BigNum` guarantees the validity of its ptr - // - The size of `x/y_bytes_uninit` and the length passed to `BN_bn2bin_padded` are both - // `AFFINE_COORDINATE_SIZE` - let (result_x, result_y) = unsafe { - ( - bssl_sys::BN_bn2bin_padded( - x_bytes_uninit.as_mut_ptr() as *mut _, - AFFINE_COORDINATE_SIZE, - bn_x.as_ptr(), - ), - bssl_sys::BN_bn2bin_padded( - y_bytes_uninit.as_mut_ptr() as *mut _, - AFFINE_COORDINATE_SIZE, - bn_y.as_ptr(), - ), - ) - }; - assert_eq!(result_x, 1, "bssl_sys::BN_bn2bin_padded failed"); - assert_eq!(result_y, 1, "bssl_sys::BN_bn2bin_padded failed"); - - // Safety: Fields initialized by `BN_bn2bin_padded` above. - unsafe { (x_bytes_uninit.assume_init(), y_bytes_uninit.assume_init()) } - } -} - -impl TryFrom<&[u8]> for PublicKey { - type Error = Error; - - fn try_from(value: &[u8]) -> Result { - EcKey::try_new_public_key_from_bytes(C::ec_group(), value) - .map(|eckey| Self { - eckey, - marker: PhantomData, - }) - .map_err(|_| Error::ConversionFailed) - } -} - -/// Shared secret derived from a Diffie-Hellman key exchange. Don't use the shared key directly, -/// rather use a KDF and also include the two public values as inputs. -pub struct SharedSecret(pub(crate) [u8; SIZE]); - -impl SharedSecret { - /// Gets a copy of the shared secret. - pub fn to_bytes(&self) -> [u8; SIZE] { - self.0 - } - - /// Gets a reference to the underlying data in this shared secret. - pub fn as_bytes(&self) -> &[u8; SIZE] { - &self.0 + /// Return the public key corresponding to this private key. + pub fn to_public_key(&self) -> PublicKey { + PublicKey { + point: self.key.to_point(), + marker: PhantomData, + } } } #[cfg(test)] -#[allow(clippy::unwrap_used, clippy::expect_used)] -mod tests { - use crate::{ - ec::{Curve, P224, P256, P384, P521}, - ecdh::{PrivateKey, PublicKey}, - test_helpers::decode_hex, - }; +mod test { + use super::*; + use crate::ec::{P256, P384}; - #[test] - fn p224_test_diffie_hellman() { - // From wycheproof ecdh_secp224r1_ecpoint_test.json, tcId 1 - // sec1 public key manually extracted from the ASN encoded test data - let public_key_bytes: [u8; 57] = decode_hex(concat!( - "047d8ac211e1228eb094e285a957d9912e93deee433ed777440ae9fc719b01d0", - "50dfbe653e72f39491be87fb1a2742daa6e0a2aada98bb1aca", - )); - let private_key_bytes: [u8; 28] = - decode_hex("565577a49415ca761a0322ad54e4ad0ae7625174baf372c2816f5328"); - let expected_shared_secret: [u8; 28] = - decode_hex("b8ecdb552d39228ee332bafe4886dbff272f7109edf933bc7542bd4f"); + fn check_curve() { + let alice_private_key = PrivateKey::::generate(); + let alice_public_key = alice_private_key.to_public_key(); + let alice_private_key = + PrivateKey::::from_big_endian(alice_private_key.to_big_endian().as_ref()).unwrap(); + let alice_private_key = PrivateKey::::from_der_ec_private_key( + alice_private_key.to_der_ec_private_key().as_ref(), + ) + .unwrap(); - let public_key: PublicKey = (&public_key_bytes[..]).try_into().unwrap(); - let private_key = PrivateKey::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - let actual_shared_secret = private_key.diffie_hellman(&public_key).unwrap(); + let bob_private_key = PrivateKey::::generate(); + let bob_public_key = bob_private_key.to_public_key(); - assert_eq!(actual_shared_secret.0, expected_shared_secret); + let shared_key1 = alice_private_key.compute_shared_key(&bob_public_key); + let shared_key2 = bob_private_key.compute_shared_key(&alice_public_key); + + assert_eq!(shared_key1, shared_key2); } #[test] - fn p256_test_diffie_hellman() { - // From wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 1 - // sec1 public key manually extracted from the ASN encoded test data - let public_key_bytes: [u8; 65] = decode_hex(concat!( - "0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f", - "26ac333a93a9e70a81cd5a95b5bf8d13990eb741c8c38872b4a07d275a014e30cf", - )); - let private_key_bytes: [u8; 32] = - decode_hex("0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346"); - let expected_shared_secret: [u8; 32] = - decode_hex("53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285"); - - let public_key: PublicKey = (&public_key_bytes[..]).try_into().unwrap(); - let private_key = PrivateKey::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - let actual_shared_secret = private_key.diffie_hellman(&public_key).unwrap(); - - assert_eq!(actual_shared_secret.0, expected_shared_secret); + fn p256() { + check_curve::(); } #[test] - fn p384_test_diffie_hellman() { - // From wycheproof ecdh_secp384r1_ecpoint_test.json, tcId 1 - // sec1 public key manually extracted from the ASN encoded test data - let public_key_bytes: [u8; 97] = decode_hex(concat!( - "04790a6e059ef9a5940163183d4a7809135d29791643fc43a2f17ee8bf677ab8", - "4f791b64a6be15969ffa012dd9185d8796d9b954baa8a75e82df711b3b56eadf", - "f6b0f668c3b26b4b1aeb308a1fcc1c680d329a6705025f1c98a0b5e5bfcb163caa", - )); - let private_key_bytes: [u8; 48] = decode_hex(concat!( - "766e61425b2da9f846c09fc3564b93a6f8603b7392c785165bf20da948c49fd1", - "fb1dee4edd64356b9f21c588b75dfd81" - )); - let expected_shared_secret: [u8; 48] = decode_hex(concat!( - "6461defb95d996b24296f5a1832b34db05ed031114fbe7d98d098f93859866e4", - "de1e229da71fef0c77fe49b249190135" - )); - - let public_key: PublicKey = (&public_key_bytes[..]).try_into().unwrap(); - let private_key = PrivateKey::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - let actual_shared_secret = private_key.diffie_hellman(&public_key).unwrap(); - - assert_eq!(actual_shared_secret.0, expected_shared_secret); - } - - #[test] - fn p521_test_diffie_hellman() { - // From wycheproof ecdh_secp521r1_ecpoint_test.json, tcId 1 - // sec1 public key manually extracted from the ASN encoded test data - let public_key_bytes: [u8; 133] = decode_hex(concat!( - "040064da3e94733db536a74a0d8a5cb2265a31c54a1da6529a198377fbd38575", - "d9d79769ca2bdf2d4c972642926d444891a652e7f492337251adf1613cf30779", - "99b5ce00e04ad19cf9fd4722b0c824c069f70c3c0e7ebc5288940dfa92422152", - "ae4a4f79183ced375afb54db1409ddf338b85bb6dbfc5950163346bb63a90a70", - "c5aba098f7", - )); - let private_key_bytes: [u8; 66] = decode_hex(concat!( - "01939982b529596ce77a94bc6efd03e92c21a849eb4f87b8f619d506efc9bb22", - "e7c61640c90d598f795b64566dc6df43992ae34a1341d458574440a7371f611c", - "7dcd" - )); - let expected_shared_secret: [u8; 66] = decode_hex(concat!( - "01f1e410f2c6262bce6879a3f46dfb7dd11d30eeee9ab49852102e1892201dd1", - "0f27266c2cf7cbccc7f6885099043dad80ff57f0df96acf283fb090de53df95f", - "7d87", - )); - - let public_key: PublicKey = (&public_key_bytes[..]).try_into().unwrap(); - let private_key = PrivateKey::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - let actual_shared_secret = private_key.diffie_hellman(&public_key).unwrap(); - - assert_eq!(actual_shared_secret.0, expected_shared_secret); - } - - #[test] - fn p224_generate_diffie_hellman_matches() { - generate_diffie_hellman_matches::() - } - - #[test] - fn p256_generate_diffie_hellman_matches() { - generate_diffie_hellman_matches::() - } - - #[test] - fn p384_generate_diffie_hellman_matches() { - generate_diffie_hellman_matches::() - } - - #[test] - fn p521_generate_diffie_hellman_matches() { - generate_diffie_hellman_matches::() - } - - fn generate_diffie_hellman_matches() { - let private_key_1 = PrivateKey::::generate(); - let private_key_2 = PrivateKey::::generate(); - let public_key_1 = PublicKey::from(&private_key_1); - let public_key_2 = PublicKey::from(&private_key_2); - - let diffie_hellman_1 = private_key_1 - .diffie_hellman::(&public_key_2) - .unwrap(); - let diffie_hellman_2 = private_key_2 - .diffie_hellman::(&public_key_1) - .unwrap(); - - assert_eq!(diffie_hellman_1.to_bytes(), diffie_hellman_2.to_bytes()); - } - - #[test] - fn p224_to_private_bytes() { - let private_key_bytes: [u8; 28] = - decode_hex("565577a49415ca761a0322ad54e4ad0ae7625174baf372c2816f5328"); - let private_key = PrivateKey::::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - assert_eq!(&private_key.to_bytes()[..], &private_key_bytes[..]); - } - - #[test] - fn p256_to_private_bytes() { - let private_key_bytes: [u8; 32] = - decode_hex("0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346"); - let private_key = PrivateKey::::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - assert_eq!(&private_key.to_bytes()[..], &private_key_bytes[..]); - } - - #[test] - fn p384_to_private_bytes() { - let private_key_bytes: [u8; 48] = decode_hex(concat!( - "766e61425b2da9f846c09fc3564b93a6f8603b7392c785165bf20da948c49fd1", - "fb1dee4edd64356b9f21c588b75dfd81" - )); - let private_key = PrivateKey::::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - assert_eq!(&private_key.to_bytes()[..], &private_key_bytes[..]); - } - - #[test] - fn p521_to_private_bytes() { - let private_key_bytes: [u8; 66] = decode_hex(concat!( - "01939982b529596ce77a94bc6efd03e92c21a849eb4f87b8f619d506efc9bb22", - "e7c61640c90d598f795b64566dc6df43992ae34a1341d458574440a7371f611c", - "7dcd", - )); - let private_key = PrivateKey::::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - assert_eq!(&private_key.to_bytes()[..], &private_key_bytes[..]); - } - - #[test] - fn p224_affine_coordinates_test() { - affine_coordinates_test::(); - } - - #[test] - fn p256_affine_coordinates_test() { - affine_coordinates_test::(); - } - - #[test] - fn p384_affine_coordinates_test() { - affine_coordinates_test::(); - } - - #[test] - fn p521_affine_coordinates_test() { - affine_coordinates_test::(); - } - - fn affine_coordinates_test() { - let private_key = PrivateKey::::generate(); - let public_key = PublicKey::from(&private_key); - - let (x, y) = public_key.to_affine_coordinates::(); - - let recreated_public_key = PublicKey::from_affine_coordinates(&x, &y); - assert_eq!(public_key, recreated_public_key.unwrap()); + fn p384() { + check_curve::(); } } diff --git a/src/rust/bssl-crypto/src/ecdsa.rs b/src/rust/bssl-crypto/src/ecdsa.rs new file mode 100644 index 000000000..c0b5feeee --- /dev/null +++ b/src/rust/bssl-crypto/src/ecdsa.rs @@ -0,0 +1,249 @@ +/* Copyright (c) 2024, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +//! Elliptic Curve Digital Signature Algorithm. +//! +//! The module implements ECDSA for the NIST curves P-256 and P-384. +//! +//! ``` +//! use bssl_crypto::{ecdsa, ec::P256}; +//! +//! let key = ecdsa::PrivateKey::::generate(); +//! // Publish your public key. +//! let public_key_bytes = key.to_der_subject_public_key_info(); +//! +//! // Sign and publish some message. +//! let signed_message = b"hello world"; +//! let mut sig = key.sign(signed_message); +//! +//! // Anyone with the public key can verify it. +//! let public_key = ecdsa::PublicKey::::from_der_subject_public_key_info( +//! public_key_bytes.as_ref()).unwrap(); +//! assert!(public_key.verify(signed_message, sig.as_slice()).is_ok()); +//! ``` + +use crate::{ec, sealed, with_output_vec, Buffer, FfiSlice, InvalidSignatureError}; +use alloc::vec::Vec; +use core::marker::PhantomData; + +/// An ECDSA public key over the given curve. +pub struct PublicKey { + point: ec::Point, + marker: PhantomData, +} + +impl PublicKey { + /// Parse a public key in uncompressed X9.62 format. (This is the common + /// format for elliptic curve points beginning with an 0x04 byte.) + pub fn from_x962_uncompressed(x962: &[u8]) -> Option { + let point = ec::Point::from_x962_uncompressed(C::group(sealed::Sealed), x962)?; + Some(Self { + point, + marker: PhantomData, + }) + } + + /// Serialize this key as uncompressed X9.62 format. + pub fn to_x962_uncompressed(&self) -> Buffer { + self.point.to_x962_uncompressed() + } + + /// Parse a public key in SubjectPublicKeyInfo format. + /// (This is found in, e.g., X.509 certificates.) + pub fn from_der_subject_public_key_info(spki: &[u8]) -> Option { + let point = ec::Point::from_der_subject_public_key_info(C::group(sealed::Sealed), spki)?; + Some(Self { + point, + marker: PhantomData, + }) + } + + /// Serialize this key in SubjectPublicKeyInfo format. + pub fn to_der_subject_public_key_info(&self) -> Buffer { + self.point.to_der_subject_public_key_info() + } + + /// Verify `signature` as a valid signature of a digest of `signed_msg` + /// with this public key. SHA-256 will be used to produce the digest if the + /// curve of this public key is P-256. SHA-384 will be used to produce the + /// digest if the curve of this public key is P-384. + pub fn verify(&self, signed_msg: &[u8], signature: &[u8]) -> Result<(), InvalidSignatureError> { + let digest = C::hash(signed_msg); + let result = self.point.with_point_as_ec_key(|ec_key| unsafe { + // Safety: `ec_key` is valid per `with_point_as_ec_key`. + bssl_sys::ECDSA_verify( + /*type=*/ 0, + digest.as_slice().as_ffi_ptr(), + digest.len(), + signature.as_ffi_ptr(), + signature.len(), + ec_key, + ) + }); + if result == 1 { + Ok(()) + } else { + Err(InvalidSignatureError) + } + } +} + +/// An ECDH private key over the given curve. +pub struct PrivateKey { + key: ec::Key, + marker: PhantomData, +} + +impl PrivateKey { + /// Generate a random private key. + pub fn generate() -> Self { + Self { + key: ec::Key::generate(C::group(sealed::Sealed)), + marker: PhantomData, + } + } + + /// Parse a `PrivateKey` from a zero-padded, big-endian representation of the secret scalar. + pub fn from_big_endian(scalar: &[u8]) -> Option { + let key = ec::Key::from_big_endian(C::group(sealed::Sealed), scalar)?; + Some(Self { + key, + marker: PhantomData, + }) + } + + /// Return the private key as zero-padded, big-endian bytes. + pub fn to_big_endian(&self) -> Buffer { + self.key.to_big_endian() + } + + /// Parse an ECPrivateKey structure (from RFC 5915). The key must be on the + /// specified curve. + pub fn from_der_ec_private_key(der: &[u8]) -> Option { + let key = ec::Key::from_der_ec_private_key(C::group(sealed::Sealed), der)?; + Some(Self { + key, + marker: PhantomData, + }) + } + + /// Serialize this private key as an ECPrivateKey structure (from RFC 5915). + pub fn to_der_ec_private_key(&self) -> Buffer { + self.key.to_der_ec_private_key() + } + + /// Parse a PrivateKeyInfo structure (from RFC 5208), commonly called + /// "PKCS#8 format". The key must be on the specified curve. + pub fn from_der_private_key_info(der: &[u8]) -> Option { + let key = ec::Key::from_der_private_key_info(C::group(sealed::Sealed), der)?; + Some(Self { + key, + marker: PhantomData, + }) + } + + /// Serialize this private key as a PrivateKeyInfo structure (from RFC 5208), + /// commonly called "PKCS#8 format". + pub fn to_der_private_key_info(&self) -> Buffer { + self.key.to_der_private_key_info() + } + + /// Serialize the _public_ part of this key in uncompressed X9.62 format. + pub fn to_x962_uncompressed(&self) -> Buffer { + self.key.to_x962_uncompressed() + } + + /// Serialize this key in SubjectPublicKeyInfo format. + pub fn to_der_subject_public_key_info(&self) -> Buffer { + self.key.to_der_subject_public_key_info() + } + + /// Return the public key corresponding to this private key. + pub fn to_public_key(&self) -> PublicKey { + PublicKey { + point: self.key.to_point(), + marker: PhantomData, + } + } + + /// Sign a digest of `to_be_signed` using this key and return the signature. + /// SHA-256 will be used to produce the digest if the curve of this public + /// key is P-256. SHA-384 will be used to produce the digest if the curve + /// of this public key is P-384. + pub fn sign(&self, to_be_signed: &[u8]) -> Vec { + // Safety: `self.key` is valid by construction. + let max_size = unsafe { bssl_sys::ECDSA_size(self.key.as_ffi_ptr()) }; + // No curve can be empty. + assert_ne!(max_size, 0); + + let digest = C::hash(to_be_signed); + + unsafe { + with_output_vec(max_size, |out_buf| { + let mut out_len: core::ffi::c_uint = 0; + // Safety: `out_buf` points to at least `max_size` bytes, + // as required. + let result = { + bssl_sys::ECDSA_sign( + /*type=*/ 0, + digest.as_slice().as_ffi_ptr(), + digest.len(), + out_buf, + &mut out_len, + self.key.as_ffi_ptr(), + ) + }; + // Signing should never fail unless we're out of memory, + // which this crate doesn't handle. + assert_eq!(result, 1); + let out_len = out_len as usize; + assert!(out_len <= max_size); + // Safety: `out_len` bytes have been written. + out_len + }) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ec::{P256, P384}; + + fn check_curve() { + let signed_message = b"hello world"; + let key = PrivateKey::::generate(); + let mut sig = key.sign(signed_message); + + let public_key = PublicKey::::from_der_subject_public_key_info( + key.to_der_subject_public_key_info().as_ref(), + ) + .unwrap(); + assert!(public_key.verify(signed_message, sig.as_slice()).is_ok()); + + sig[10] ^= 1; + assert!(public_key.verify(signed_message, sig.as_slice()).is_err()); + } + + #[test] + fn p256() { + check_curve::(); + } + + #[test] + fn p384() { + check_curve::(); + } +} diff --git a/src/rust/bssl-crypto/src/ed25519.rs b/src/rust/bssl-crypto/src/ed25519.rs index f4ab5becb..5ac52918a 100644 --- a/src/rust/bssl-crypto/src/ed25519.rs +++ b/src/rust/bssl-crypto/src/ed25519.rs @@ -13,108 +13,126 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -use crate::CSlice; +//! Ed25519, a signature scheme. +//! +//! Ed25519 builds a signature scheme over a curve that is isogenous to +//! curve25519. This module provides the "pure" signature scheme described in +//! . +//! +//! ``` +//! use bssl_crypto::ed25519; +//! +//! let key = ed25519::PrivateKey::generate(); +//! // Publish your public key. +//! let public_key_bytes = *key.to_public().as_bytes(); +//! +//! // Sign and publish some message. +//! let signed_message = b"hello world"; +//! let mut sig = key.sign(signed_message); +//! +//! // Anyone with the public key can verify it. +//! let public_key = ed25519::PublicKey::from_bytes(&public_key_bytes); +//! assert!(public_key.verify(signed_message, &sig).is_ok()); +//! ``` + +use crate::{FfiMutSlice, FfiSlice, InvalidSignatureError}; /// The length in bytes of an Ed25519 public key. -pub const PUBLIC_KEY_LENGTH: usize = bssl_sys::ED25519_PUBLIC_KEY_LEN as usize; +pub const PUBLIC_KEY_LEN: usize = bssl_sys::ED25519_PUBLIC_KEY_LEN as usize; -/// The length in bytes of an Ed25519 seed which is the 32-byte private key representation defined -/// in RFC 8032. -pub const SEED_LENGTH: usize = +/// The length in bytes of an Ed25519 seed which is the 32-byte private key +/// representation defined in RFC 8032. +pub const SEED_LEN: usize = (bssl_sys::ED25519_PRIVATE_KEY_LEN - bssl_sys::ED25519_PUBLIC_KEY_LEN) as usize; /// The length in bytes of an Ed25519 signature. -pub const SIGNATURE_LENGTH: usize = bssl_sys::ED25519_SIGNATURE_LEN as usize; +pub const SIGNATURE_LEN: usize = bssl_sys::ED25519_SIGNATURE_LEN as usize; // The length in bytes of an Ed25519 keypair. In BoringSSL, the private key is suffixed with the // public key, so the keypair length is the same as the private key length. -const KEYPAIR_LENGTH: usize = bssl_sys::ED25519_PRIVATE_KEY_LEN as usize; +const KEYPAIR_LEN: usize = bssl_sys::ED25519_PRIVATE_KEY_LEN as usize; /// An Ed25519 private key. -pub struct PrivateKey([u8; KEYPAIR_LENGTH]); - -/// An Ed25519 signature created by signing a message with a private key. -pub struct Signature([u8; SIGNATURE_LENGTH]); +pub struct PrivateKey([u8; KEYPAIR_LEN]); /// An Ed25519 public key used to verify a signature + message. -pub struct PublicKey([u8; PUBLIC_KEY_LENGTH]); +pub struct PublicKey([u8; PUBLIC_KEY_LEN]); -/// Error returned if the verification on the signature + message fails. -#[derive(Debug)] -pub struct SignatureError; +/// An Ed25519 signature created by signing a message with a private key. +pub type Signature = [u8; SIGNATURE_LEN]; impl PrivateKey { /// Generates a new Ed25519 keypair. pub fn generate() -> Self { - let mut public_key = [0u8; PUBLIC_KEY_LENGTH]; - let mut private_key = [0u8; KEYPAIR_LENGTH]; + let mut public_key = [0u8; PUBLIC_KEY_LEN]; + let mut private_key = [0u8; KEYPAIR_LEN]; // Safety: // - Public key and private key are the correct length. - unsafe { bssl_sys::ED25519_keypair(public_key.as_mut_ptr(), private_key.as_mut_ptr()) } + unsafe { + bssl_sys::ED25519_keypair(public_key.as_mut_ffi_ptr(), private_key.as_mut_ffi_ptr()) + } PrivateKey(private_key) } - /// Converts the key-pair to an array of bytes consisting of the bytes of the private key - /// followed by the bytes of the public key. - pub fn to_seed(&self) -> [u8; SEED_LENGTH] { + /// Returns the "seed" of this private key, as defined in RFC 8032. + pub fn to_seed(&self) -> [u8; SEED_LEN] { // This code will never panic because a length 32 slice will always fit into a // size 32 byte array. The private key is the first 32 bytes of the keypair. #[allow(clippy::expect_used)] - self.0[..SEED_LENGTH].try_into().expect( - "A slice of length SEED_LENGTH will always fit into an array of length SEED_LENGTH", - ) + self.0[..SEED_LEN] + .try_into() + .expect("A slice of length SEED_LEN will always fit into an array of length SEED_LEN") } - /// Builds this key-pair from `seed`, which is the 32-byte private key representation defined + /// Derives a key-pair from `seed`, which is the 32-byte private key representation defined /// in RFC 8032. - pub fn new_from_seed(seed: &[u8; SEED_LENGTH]) -> Self { - let mut public_key = [0u8; PUBLIC_KEY_LENGTH]; - let mut private_key = [0u8; KEYPAIR_LENGTH]; + pub fn from_seed(seed: &[u8; SEED_LEN]) -> Self { + let mut public_key = [0u8; PUBLIC_KEY_LEN]; + let mut private_key = [0u8; KEYPAIR_LEN]; // Safety: // - Public key, private key, and seed are the correct lengths. unsafe { bssl_sys::ED25519_keypair_from_seed( - public_key.as_mut_ptr(), - private_key.as_mut_ptr(), - seed.as_ptr(), + public_key.as_mut_ffi_ptr(), + private_key.as_mut_ffi_ptr(), + seed.as_ffi_ptr(), ) } PrivateKey(private_key) } - /// Signs the given message and returns a digital signature. + /// Signs the given message and returns the signature. pub fn sign(&self, msg: &[u8]) -> Signature { - let mut sig_bytes = [0u8; SIGNATURE_LENGTH]; + let mut sig_bytes = [0u8; SIGNATURE_LEN]; - let msg_ffi = CSlice(msg); // Safety: // - On allocation failure we panic. // - Signature and private keys are always the correct length. let result = unsafe { bssl_sys::ED25519_sign( - sig_bytes.as_mut_ptr(), - msg_ffi.as_ptr(), - msg_ffi.len(), - self.0.as_ptr(), + sig_bytes.as_mut_ffi_ptr(), + msg.as_ffi_ptr(), + msg.len(), + self.0.as_ffi_ptr(), ) }; assert_eq!(result, 1, "allocation failure in bssl_sys::ED25519_sign"); - Signature(sig_bytes) + sig_bytes } - /// Returns the PublicKey of the KeyPair. - pub fn public(&self) -> PublicKey { - let keypair_bytes = self.0; + /// Returns the [`PublicKey`] corresponding to this private key. + pub fn to_public(&self) -> PublicKey { + let keypair_bytes = &self.0; // This code will never panic because a length 32 slice will always fit into a // size 32 byte array. The public key is the last 32 bytes of the keypair. #[allow(clippy::expect_used)] PublicKey( - keypair_bytes[PUBLIC_KEY_LENGTH..] + keypair_bytes[PUBLIC_KEY_LEN..] .try_into() .expect("The slice is always the correct size for a public key"), ) @@ -123,78 +141,66 @@ impl PrivateKey { impl PublicKey { /// Builds the public key from an array of bytes. - pub fn from_bytes(bytes: [u8; PUBLIC_KEY_LENGTH]) -> Self { - PublicKey(bytes) + pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_LEN]) -> Self { + PublicKey(*bytes) } /// Returns the bytes of the public key. - pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { - self.0 + pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_LEN] { + &self.0 } - /// Succeeds if the signature is a valid signature created by this keypair, otherwise returns an Error. - pub fn verify(&self, message: &[u8], signature: Signature) -> Result<(), SignatureError> { - let message_cslice = CSlice::from(message); + /// Verifies that `signature` is a valid signature, by this key, of `msg`. + pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), InvalidSignatureError> { let ret = unsafe { + // Safety: `self.0` is the correct length and other buffers are valid. bssl_sys::ED25519_verify( - message_cslice.as_ptr(), - message_cslice.len(), - signature.0.as_ptr(), - self.0.as_ptr(), + msg.as_ffi_ptr(), + msg.len(), + signature.as_ffi_ptr(), + self.0.as_ffi_ptr(), ) }; if ret == 1 { Ok(()) } else { - Err(SignatureError) + Err(InvalidSignatureError) } } } -impl Signature { - /// Creates a signature from a byte array. - pub fn from_bytes(bytes: [u8; SIGNATURE_LENGTH]) -> Self { - Self(bytes) - } - - /// Returns the bytes of the signature. - pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] { - self.0 - } -} - #[cfg(test)] mod test { use super::*; use crate::test_helpers; #[test] - fn ed25519_kp_gen_roundtrip() { + fn gen_roundtrip() { let private_key = PrivateKey::generate(); assert_ne!([0u8; 64], private_key.0); let seed = private_key.to_seed(); - let new_private_key = PrivateKey::new_from_seed(&seed); + let new_private_key = PrivateKey::from_seed(&seed); assert_eq!(private_key.0, new_private_key.0); } #[test] - fn ed25519_empty_msg() { + fn empty_msg() { // Test Case 1 from RFC test vectors: https://www.rfc-editor.org/rfc/rfc8032#section-7.1 let pk = test_helpers::decode_hex( "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", ); - let sk = test_helpers::decode_hex( + let seed = test_helpers::decode_hex( "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", ); let msg = [0u8; 0]; let sig_expected = test_helpers::decode_hex("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); - let kp = PrivateKey::new_from_seed(&sk); + let kp = PrivateKey::from_seed(&seed); let sig = kp.sign(&msg); - assert_eq!(sig_expected, sig.0); + assert_eq!(sig_expected, sig); - let pub_key = PublicKey::from_bytes(pk); - assert_eq!(pub_key.to_bytes(), kp.public().to_bytes()); - assert!(pub_key.verify(&msg, sig).is_ok()); + let pub_key = PublicKey::from_bytes(&pk); + assert_eq!(pub_key.as_bytes(), kp.to_public().as_bytes()); + assert!(pub_key.verify(&msg, &sig).is_ok()); } #[test] @@ -208,13 +214,13 @@ mod test { ); let msg: [u8; 14] = test_helpers::decode_hex("55c7fa434f5ed8cdec2b7aeac173"); let sig_expected = test_helpers::decode_hex("6ee3fe81e23c60eb2312b2006b3b25e6838e02106623f844c44edb8dafd66ab0671087fd195df5b8f58a1d6e52af42908053d55c7321010092748795ef94cf06"); - let kp = PrivateKey::new_from_seed(&sk); + let kp = PrivateKey::from_seed(&sk); let sig = kp.sign(&msg); - assert_eq!(sig_expected, sig.0); + assert_eq!(sig_expected, sig); - let pub_key = PublicKey::from_bytes(pk); - assert_eq!(pub_key.to_bytes(), kp.public().to_bytes()); - assert!(pub_key.verify(&msg, sig).is_ok()); + let pub_key = PublicKey::from_bytes(&pk); + assert_eq!(pub_key.as_bytes(), kp.to_public().as_bytes()); + assert!(pub_key.verify(&msg, &sig).is_ok()); } } diff --git a/src/rust/bssl-crypto/src/hmac.rs b/src/rust/bssl-crypto/src/hmac.rs index 2a07fbc23..bf482f760 100644 --- a/src/rust/bssl-crypto/src/hmac.rs +++ b/src/rust/bssl-crypto/src/hmac.rs @@ -30,7 +30,7 @@ //! ``` //! use bssl_crypto::hmac::HmacSha256; //! -//! let key = [0u8; 32]; +//! let key = bssl_crypto::rand_array(); //! let mut ctx = HmacSha256::new(&key); //! ctx.update(b"hel"); //! ctx.update(b"lo"); @@ -46,7 +46,7 @@ //! ``` //! use bssl_crypto::hmac::HmacSha256; //! -//! let key = [0u8; 32]; +//! let key = bssl_crypto::rand_array(); //! let mut keyed_ctx = HmacSha256::new(&key); //! let mut ctx1 = keyed_ctx.clone(); //! ctx1.update(b"foo"); diff --git a/src/rust/bssl-crypto/src/lib.rs b/src/rust/bssl-crypto/src/lib.rs index 885323878..bb80ef4c2 100644 --- a/src/rust/bssl-crypto/src/lib.rs +++ b/src/rust/bssl-crypto/src/lib.rs @@ -24,51 +24,43 @@ #![cfg_attr(not(any(feature = "std", test)), no_std)] //! Rust BoringSSL bindings -extern crate alloc; +extern crate alloc; extern crate core; +use alloc::vec::Vec; use core::ffi::c_void; #[macro_use] mod macros; -/// Authenticated Encryption with Additional Data algorithms. pub mod aead; - -/// AES block operations. pub mod aes; /// Ciphers. pub mod cipher; pub mod digest; - -/// Ed25519, a signature scheme. +pub mod ec; +pub mod ecdh; +pub mod ecdsa; pub mod ed25519; - pub mod hkdf; - pub mod hmac; - -/// Random number generation. -pub mod rand; - +pub mod rsa; pub mod x25519; -/// Memory-manipulation operations. -pub mod mem; - -/// Elliptic curve diffie-hellman operations. -pub mod ecdh; - -pub(crate) mod bn; -pub(crate) mod ec; -pub(crate) mod pkey; +mod scoped; #[cfg(test)] mod test_helpers; +mod mem; +pub use mem::constant_time_compare; + +mod rand; +pub use rand::{rand_array, rand_bytes}; + /// Error type for when a "signature" (either a public-key signature or a MAC) /// is incorrect. #[derive(Debug)] @@ -266,6 +258,24 @@ where unsafe { out_uninit.assume_init() } } +/// Returns a BoringSSL structure that is initialized by some function. +/// Requires that the given function completely initializes the value or else +/// returns false. +/// +/// (Tagged `unsafe` because a no-op argument would otherwise expose +/// uninitialized memory.) +unsafe fn initialized_struct_fallible(init: F) -> Option +where + F: FnOnce(*mut T) -> bool, +{ + let mut out_uninit = core::mem::MaybeUninit::::uninit(); + if init(out_uninit.as_mut_ptr()) { + Some(unsafe { out_uninit.assume_init() }) + } else { + None + } +} + /// Wrap a closure that initializes an output buffer and return that buffer as /// an array. Requires that the closure fully initialize the given buffer. /// @@ -294,7 +304,7 @@ where /// Safety: the closure must fully initialize the array if it returns one. unsafe fn with_output_array_fallible(func: F) -> Option<[u8; N]> where - F: FnOnce(*mut u8, usize) -> core::ffi::c_int, + F: FnOnce(*mut u8, usize) -> bool, { let mut out_uninit = core::mem::MaybeUninit::<[u8; N]>::uninit(); let out_ptr = if N != 0 { @@ -302,7 +312,7 @@ where } else { core::ptr::null_mut() }; - if func(out_ptr, N) == 1 { + if func(out_ptr, N) { // Safety: `func` promises to fill all of `out_uninit` if it returns one. unsafe { Some(out_uninit.assume_init()) } } else { @@ -310,6 +320,123 @@ where } } +/// Wrap a closure that writes at most `max_output` bytes to fill a vector. +/// It must return the number of bytes written. +#[allow(clippy::unwrap_used)] +unsafe fn with_output_vec(max_output: usize, func: F) -> Vec +where + F: FnOnce(*mut u8) -> usize, +{ + unsafe { + with_output_vec_fallible(max_output, |out_buf| Some(func(out_buf))) + // The closure cannot fail and thus neither can + // `with_output_array_fallible`. + .unwrap() + } +} + +/// Wrap a closure that writes at most `max_output` bytes to fill a vector. +/// If successful, it must return the number of bytes written. +unsafe fn with_output_vec_fallible(max_output: usize, func: F) -> Option> +where + F: FnOnce(*mut u8) -> Option, +{ + let mut ret = Vec::with_capacity(max_output); + let out = ret.spare_capacity_mut(); + let out_buf = out + .get_mut(0) + .map_or(core::ptr::null_mut(), |x| x.as_mut_ptr()); + + let num_written = func(out_buf)?; + assert!(num_written <= ret.capacity()); + + unsafe { + // Safety: `num_written` bytes have been written to. + ret.set_len(num_written); + } + + Some(ret) +} + +/// Buffer represents an owned chunk of memory on the BoringSSL heap. +/// Call `as_ref()` to get a `&[u8]` from it. +pub struct Buffer { + // This pointer is always allocated by BoringSSL and must be freed using + // `OPENSSL_free`. + pub(crate) ptr: *mut u8, + pub(crate) len: usize, +} + +impl AsRef<[u8]> for Buffer { + fn as_ref(&self) -> &[u8] { + if self.len == 0 { + return &[]; + } + // Safety: `ptr` and `len` describe a valid area of memory and `ptr` + // must be Rust-valid because `len` is non-zero. + unsafe { core::slice::from_raw_parts(self.ptr, self.len) } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + // Safety: `ptr` is owned by this object and is on the BoringSSL heap. + unsafe { + bssl_sys::OPENSSL_free(self.ptr as *mut core::ffi::c_void); + } + } +} + +/// Calls `parse_func` with a `CBS` structure pointing at `data`. +/// If that returns a null pointer then it returns [None]. +/// Otherwise, if there's still data left in CBS, it calls `free_func` on the +/// pointer and returns [None]. Otherwise it returns the pointer. +fn parse_with_cbs(data: &[u8], free_func: Free, parse_func: Parse) -> Option<*mut T> +where + Parse: FnOnce(*mut bssl_sys::CBS) -> *mut T, + Free: FnOnce(*mut T), +{ + // Safety: type checking ensures that `cbs` is the correct size. + let mut cbs = + unsafe { initialized_struct(|cbs| bssl_sys::CBS_init(cbs, data.as_ffi_ptr(), data.len())) }; + let ptr = parse_func(&mut cbs); + if ptr.is_null() { + return None; + } + // Safety: `cbs` is still valid after parsing. + if unsafe { bssl_sys::CBS_len(&cbs) } != 0 { + // Safety: `ptr` is still owned by this function. + free_func(ptr); + return None; + } + Some(ptr) +} + +/// Calls `func` with a `CBB` pointer and returns a [Buffer] of the ultimate +/// contents of that CBB. +#[allow(clippy::unwrap_used)] +fn cbb_to_buffer(initial_capacity: usize, func: F) -> Buffer { + // Safety: type checking ensures that `cbb` is the correct size. + let mut cbb = unsafe { + initialized_struct_fallible(|cbb| bssl_sys::CBB_init(cbb, initial_capacity) == 1) + } + // `CBB_init` only fails if out of memory, which isn't something that this crate handles. + .unwrap(); + func(&mut cbb); + + let mut ptr: *mut u8 = core::ptr::null_mut(); + let mut len: usize = 0; + // `CBB_finish` only fails on programming error, which we convert into a + // panic. + assert_eq!(1, unsafe { + bssl_sys::CBB_finish(&mut cbb, &mut ptr, &mut len) + }); + + // Safety: `ptr` is on the BoringSSL heap and ownership is returned by + // `CBB_finish`. + Buffer { ptr, len } +} + /// Used to prevent external implementations of internal traits. mod sealed { pub struct Sealed; diff --git a/src/rust/bssl-crypto/src/macros.rs b/src/rust/bssl-crypto/src/macros.rs index 79049d4a3..6ac3d37c3 100644 --- a/src/rust/bssl-crypto/src/macros.rs +++ b/src/rust/bssl-crypto/src/macros.rs @@ -31,6 +31,10 @@ macro_rules! unsafe_iuf_algo { // - this always returns a valid pointer to an EVP_MD. unsafe { MdRef::from_ptr(bssl_sys::$evp_md() as *mut _) } } + + fn hash_to_vec(input: &[u8]) -> Vec { + Self::hash(input).as_slice().to_vec() + } } impl $name { @@ -103,3 +107,47 @@ macro_rules! unsafe_iuf_algo { } }; } + +macro_rules! aead_algo { + ($name:ident, $evp_md:ident, $key_len:expr, $nonce_len:expr, $tag_len:expr) => { + impl $name { + /// Create a new AEAD context for the given key. + pub fn new(key: &[u8; $key_len]) -> Self { + // Safety: $evp_md is assumed to return a valid `EVP_MD`. + unsafe { $name(EvpAead::new(key, { bssl_sys::$evp_md() })) } + } + } + + impl Aead for $name { + type Tag = [u8; $tag_len]; + type Nonce = [u8; $nonce_len]; + + fn seal(&self, nonce: &Self::Nonce, plaintext: &[u8], ad: &[u8]) -> Vec { + self.0.seal(nonce, plaintext, ad) + } + + fn seal_in_place( + &self, + nonce: &Self::Nonce, + plaintext: &mut [u8], + ad: &[u8], + ) -> Self::Tag { + self.0.seal_in_place(nonce, plaintext, ad) + } + + fn open(&self, nonce: &Self::Nonce, ciphertext: &[u8], ad: &[u8]) -> Option> { + self.0.open(nonce, ciphertext, ad) + } + + fn open_in_place( + &self, + nonce: &Self::Nonce, + ciphertext: &mut [u8], + tag: &Self::Tag, + ad: &[u8], + ) -> Result<(), InvalidCiphertext> { + self.0.open_in_place(nonce, ciphertext, tag, ad) + } + } + }; +} diff --git a/src/rust/bssl-crypto/src/mem.rs b/src/rust/bssl-crypto/src/mem.rs index a9031c4e1..9426dff03 100644 --- a/src/rust/bssl-crypto/src/mem.rs +++ b/src/rust/bssl-crypto/src/mem.rs @@ -13,21 +13,23 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +use crate::FfiSlice; + /// Returns true iff `a` and `b` contain the same bytes. It takes an amount of time dependent on the /// lengths, but independent of the contents of the slices `a` and `b`. The return type is a `bool`, /// since unlike `memcmp` in C this function cannot be used to put elements into a defined order. -pub fn crypto_memcmp(a: &[u8], b: &[u8]) -> bool { +pub fn constant_time_compare(a: &[u8], b: &[u8]) -> bool { if a.len() != b.len() { return false; } - if a.is_empty() && b.is_empty() { + if a.is_empty() { // Avoid FFI issues with empty slices that may potentially cause UB return true; } // Safety: // - The lengths of a and b are checked above. let result = - unsafe { bssl_sys::CRYPTO_memcmp(a.as_ptr() as *const _, b.as_ptr() as *const _, a.len()) }; + unsafe { bssl_sys::CRYPTO_memcmp(a.as_ffi_void_ptr(), b.as_ffi_void_ptr(), a.len()) }; result == 0 } @@ -37,26 +39,26 @@ mod test { #[test] fn test_different_length() { - assert!(!crypto_memcmp(&[0, 1, 2], &[0])) + assert!(!constant_time_compare(&[0, 1, 2], &[0])) } #[test] fn test_same_length_different_content() { - assert!(!crypto_memcmp(&[0, 1, 2], &[1, 2, 3])) + assert!(!constant_time_compare(&[0, 1, 2], &[1, 2, 3])) } #[test] fn test_same_content() { - assert!(crypto_memcmp(&[0, 1, 2], &[0, 1, 2])) + assert!(constant_time_compare(&[0, 1, 2], &[0, 1, 2])) } #[test] fn test_empty_slices() { - assert!(crypto_memcmp(&[], &[])) + assert!(constant_time_compare(&[], &[])) } #[test] fn test_empty_slices_different() { - assert!(!crypto_memcmp(&[], &[0, 1, 2])) + assert!(!constant_time_compare(&[], &[0, 1, 2])) } } diff --git a/src/rust/bssl-crypto/src/pkey.rs b/src/rust/bssl-crypto/src/pkey.rs deleted file mode 100644 index aa60a9f8f..000000000 --- a/src/rust/bssl-crypto/src/pkey.rs +++ /dev/null @@ -1,102 +0,0 @@ -/* Copyright (c) 2023, Google Inc. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -//! `Pkey` and `PkeyCtx` classes for holding asymmetric keys. This module is intended for internal -//! use within this crate only, to create higher-level abstractions suitable to be exposed -//! externally. - -use crate::{ec::EcKey, CSliceMut, ForeignType}; -use alloc::{borrow::ToOwned, string::String}; - -pub(crate) struct Pkey { - ptr: *mut bssl_sys::EVP_PKEY, -} - -// Safety: Implementation ensures `from_ptr(x).as_ptr == x` -unsafe impl ForeignType for Pkey { - type CType = bssl_sys::EVP_PKEY; - - unsafe fn from_ptr(ptr: *mut Self::CType) -> Self { - Self { ptr } - } - - fn as_ptr(&self) -> *mut Self::CType { - self.ptr - } -} - -impl From<&EcKey> for Pkey { - fn from(eckey: &EcKey) -> Self { - // Safety: EVP_PKEY_new does not have any preconditions - let pkey = unsafe { bssl_sys::EVP_PKEY_new() }; - assert!(!pkey.is_null()); - // Safety: - // - pkey is just allocated and is null-checked - // - EcKey ensures eckey.ptr is valid during its lifetime - // - EVP_PKEY_set1_EC_KEY doesn't take ownership - let result = unsafe { bssl_sys::EVP_PKEY_set1_EC_KEY(pkey, eckey.as_ptr()) }; - assert_eq!(result, 1, "bssl_sys::EVP_PKEY_set1_EC_KEY failed"); - Self { ptr: pkey } - } -} - -impl Drop for Pkey { - fn drop(&mut self) { - // Safety: `self.ptr` is owned by this struct - unsafe { bssl_sys::EVP_PKEY_free(self.ptr) } - } -} - -pub(crate) struct PkeyCtx { - ptr: *mut bssl_sys::EVP_PKEY_CTX, -} - -impl PkeyCtx { - pub fn new(pkey: &Pkey) -> Self { - // Safety: - // - `Pkey` ensures `pkey.ptr` is valid, and EVP_PKEY_CTX_new does not take ownership. - let pkeyctx = unsafe { bssl_sys::EVP_PKEY_CTX_new(pkey.ptr, core::ptr::null_mut()) }; - assert!(!pkeyctx.is_null()); - Self { ptr: pkeyctx } - } - - #[allow(clippy::panic)] - pub(crate) fn diffie_hellman( - self, - other_public_key: &Pkey, - mut output: CSliceMut, - ) -> Result<(), String> { - let result = unsafe { bssl_sys::EVP_PKEY_derive_init(self.ptr) }; - assert_eq!(result, 1, "bssl_sys::EVP_PKEY_derive_init failed"); - - let result = unsafe { bssl_sys::EVP_PKEY_derive_set_peer(self.ptr, other_public_key.ptr) }; - assert_eq!(result, 1, "bssl_sys::EVP_PKEY_derive_set_peer failed"); - - let result = - unsafe { bssl_sys::EVP_PKEY_derive(self.ptr, output.as_mut_ptr(), &mut output.len()) }; - match result { - 0 => Err("bssl_sys::EVP_PKEY_derive failed".to_owned()), - 1 => Ok(()), - _ => panic!("Unexpected result {result:?} from bssl_sys::EVP_PKEY_derive"), - } - } -} - -impl Drop for PkeyCtx { - fn drop(&mut self) { - // Safety: self.ptr is owned by this struct - unsafe { bssl_sys::EVP_PKEY_CTX_free(self.ptr) } - } -} diff --git a/src/rust/bssl-crypto/src/rand.rs b/src/rust/bssl-crypto/src/rand.rs index 9fdbe0a7e..c6d419fb7 100644 --- a/src/rust/bssl-crypto/src/rand.rs +++ b/src/rust/bssl-crypto/src/rand.rs @@ -13,29 +13,56 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -use crate::CSliceMut; +//! Getting random bytes. -/// Fills buf with random bytes. In the event that sufficient random data can not be obtained, -/// BoringSSL will abort, so the assert will never be hit. +use crate::{with_output_array, FfiMutSlice}; + +/// Fills `buf` with random bytes. pub fn rand_bytes(buf: &mut [u8]) { - let mut ffi_buf = CSliceMut::from(buf); - let result = unsafe { bssl_sys::RAND_bytes(ffi_buf.as_mut_ptr(), ffi_buf.len()) }; - assert_eq!(result, 1, "BoringSSL RAND_bytes API failed unexpectedly"); + // Safety: `RAND_bytes` writes exactly `buf.len()` bytes. + let ret = unsafe { bssl_sys::RAND_bytes(buf.as_mut_ffi_ptr(), buf.len()) }; + + // BoringSSL's `RAND_bytes` always succeeds returning 1, or crashes the + // address space if the PRNG can not provide random data. + debug_assert!(ret == 1); +} + +/// Returns an array of random bytes. +pub fn rand_array() -> [u8; N] { + unsafe { + with_output_array(|out, out_len| { + // Safety: `RAND_bytes` writes exactly `out_len` bytes, as required. + let ret = bssl_sys::RAND_bytes(out, out_len); + // BoringSSL RAND_bytes always succeeds returning 1, or crashes the + // address space if the PRNG can not provide random data. + debug_assert!(ret == 1); + }) + } } #[cfg(test)] mod tests { - use super::rand_bytes; + use super::*; #[test] - fn test_rand_bytes() { + fn fill() { let mut buf = [0; 32]; rand_bytes(&mut buf); } #[test] - fn test_rand_bytes_empty() { + fn fill_empty() { let mut buf = []; rand_bytes(&mut buf); } + + #[test] + fn array() { + let _rand: [u8; 32] = rand_array(); + } + + #[test] + fn empty_array() { + let _rand: [u8; 0] = rand_array(); + } } diff --git a/src/rust/bssl-crypto/src/rsa.rs b/src/rust/bssl-crypto/src/rsa.rs new file mode 100644 index 000000000..cd67d7986 --- /dev/null +++ b/src/rust/bssl-crypto/src/rsa.rs @@ -0,0 +1,329 @@ +/* Copyright (c) 2024, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +//! RSA signatures. +//! +//! New protocols should not use RSA, but it's still often found in existing +//! protocols. This module implements PKCS#1 signatures (the most common type). +//! +//! Creating a signature: +//! +//! ``` +//! use bssl_crypto::{digest, rsa}; +//! # use bssl_crypto::rsa::TEST_PKCS8_BYTES; +//! +//! // Generating an RSA private key is slow, so this examples parses it from +//! // PKCS#8 DER. +//! let private_key = rsa::PrivateKey::from_der_private_key_info(TEST_PKCS8_BYTES).unwrap(); +//! let signed_msg = b"hello world"; +//! let sig = private_key.sign_pkcs1::(signed_msg); +//! ``` +//! +//! To verify a signature, publish your _public_ key: +//! +//! ``` +//! # use bssl_crypto::{rsa}; +//! # use bssl_crypto::rsa::TEST_PKCS8_BYTES; +//! # let private_key = rsa::PrivateKey::from_der_private_key_info(TEST_PKCS8_BYTES).unwrap(); +//! let public_key_bytes = private_key.as_public().to_der_subject_public_key_info(); +//! ``` +//! +//! Then verify the signature from above with it: +//! +//! ``` +//! # use bssl_crypto::{digest, rsa}; +//! # use bssl_crypto::rsa::TEST_PKCS8_BYTES; +//! # let private_key = rsa::PrivateKey::from_der_private_key_info(TEST_PKCS8_BYTES).unwrap(); +//! # let signed_msg = b"hello world"; +//! # let mut sig = private_key.sign_pkcs1::(signed_msg); +//! # let public_key_bytes = private_key.as_public().to_der_subject_public_key_info(); +//! let public_key = rsa::PublicKey::from_der_subject_public_key_info(public_key_bytes.as_ref()) +//! .unwrap(); +//! assert!(public_key.verify_pkcs1::(signed_msg, sig.as_slice()).is_ok()); +//! sig[0] ^= 1; +//! assert!(public_key.verify_pkcs1::(signed_msg, sig.as_slice()).is_err()); +//! ``` + +use crate::{ + cbb_to_buffer, digest, parse_with_cbs, scoped, sealed, with_output_vec, Buffer, FfiSlice, + ForeignTypeRef, InvalidSignatureError, +}; +use alloc::vec::Vec; +use core::ptr::null_mut; + +/// An RSA public key. +pub struct PublicKey(*mut bssl_sys::RSA); + +impl PublicKey { + /// Parse a DER-encoded RSAPublicKey structure (from RFC 8017). + pub fn from_der_rsa_public_key(der: &[u8]) -> Option { + Some(PublicKey(parse_with_cbs( + der, + // Safety: `ptr` is a non-null result from `RSA_parse_public_key` here. + |ptr| unsafe { bssl_sys::RSA_free(ptr) }, + // Safety: cbs is valid per `parse_with_cbs`. + |cbs| unsafe { bssl_sys::RSA_parse_public_key(cbs) }, + )?)) + } + + /// Serialize to a DER-encoded RSAPublicKey structure (from RFC 8017). + pub fn to_der_rsa_public_key(&self) -> Buffer { + cbb_to_buffer(/*initial_capacity=*/ 300, |cbb| unsafe { + // Safety: `self.0` is valid by construction. + assert_eq!(1, bssl_sys::RSA_marshal_public_key(cbb, self.0)) + }) + } + + /// Parse a DER-encoded SubjectPublicKeyInfo. This format is found in, + /// for example, X.509 certificates. + pub fn from_der_subject_public_key_info(spki: &[u8]) -> Option { + let mut pkey = scoped::EvpPkey::from_ptr(parse_with_cbs( + spki, + // Safety: `pkey` is a non-null result from `EVP_parse_public_key` here. + |pkey| unsafe { bssl_sys::EVP_PKEY_free(pkey) }, + // Safety: cbs is valid per `parse_with_cbs`. + |cbs| unsafe { bssl_sys::EVP_parse_public_key(cbs) }, + )?); + let rsa = unsafe { bssl_sys::EVP_PKEY_get1_RSA(pkey.as_ffi_ptr()) }; + if !rsa.is_null() { + // Safety: `EVP_PKEY_get1_RSA` adds a reference so we are not + // stealing ownership from `pkey`. + Some(PublicKey(rsa)) + } else { + None + } + } + + /// Serialize to a DER-encoded SubjectPublicKeyInfo. This format is found + /// in, for example, X.509 certificates. + pub fn to_der_subject_public_key_info(&self) -> Buffer { + let mut pkey = scoped::EvpPkey::new(); + // Safety: this takes a reference to `self.0` and so doesn't steal ownership. + assert_eq!(1, unsafe { + bssl_sys::EVP_PKEY_set1_RSA(pkey.as_ffi_ptr(), self.0) + }); + cbb_to_buffer(384, |cbb| unsafe { + // The arguments are valid so this will only fail if out of memory, + // which this crate doesn't handle. + assert_eq!(1, bssl_sys::EVP_marshal_public_key(cbb, pkey.as_ffi_ptr())); + }) + } + + /// Verify that `signature` is a valid signature of a digest of + /// `signed_msg`, by this public key. The digest of the message will be + /// computed with the specified hash function. + pub fn verify_pkcs1( + &self, + signed_msg: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignatureError> { + let digest = Hash::hash_to_vec(signed_msg); + // Safety: `get_md` always returns a valid pointer. + let hash_nid = unsafe { bssl_sys::EVP_MD_nid(Hash::get_md(sealed::Sealed).as_ptr()) }; + let result = unsafe { + // Safety: all buffers are valid and `self.0` is valid by construction. + bssl_sys::RSA_verify( + hash_nid, + digest.as_slice().as_ffi_ptr(), + digest.len(), + signature.as_ffi_ptr(), + signature.len(), + self.0, + ) + }; + if result == 1 { + Ok(()) + } else { + Err(InvalidSignatureError) + } + } +} + +// Safety: +// +// An `RSA` is safe to use from multiple threads so long as no mutating +// operations are performed. (Reference count changes don't count as mutating.) +// No mutating operations are performed. `RSA_verify` takes a non-const pointer +// but the BoringSSL docs specifically say that "these functions are considered +// non-mutating for thread-safety purposes and may be used concurrently." +unsafe impl Sync for PublicKey {} +unsafe impl Send for PublicKey {} + +impl Drop for PublicKey { + fn drop(&mut self) { + // Safety: this object owns `self.0`. + unsafe { bssl_sys::RSA_free(self.0) } + } +} + +/// The set of supported RSA key sizes for key generation. +#[allow(missing_docs)] +pub enum KeySize { + Rsa2048 = 2048, + Rsa3072 = 3072, + Rsa4096 = 4096, +} + +/// An RSA private key. +pub struct PrivateKey(*mut bssl_sys::RSA); + +impl PrivateKey { + /// Generate a fresh RSA private key of the given size. + pub fn generate(size: KeySize) -> Self { + let e = scoped::Bignum::from_u64(bssl_sys::RSA_F4 as u64); + let ptr = unsafe { bssl_sys::RSA_new() }; + assert!(!ptr.is_null()); + + let result = unsafe { + // Safety: `rsa` and `e` are valid and initialized, just above. + bssl_sys::RSA_generate_key_ex(ptr, size as core::ffi::c_int, e.as_ffi_ptr(), null_mut()) + }; + assert_eq!(1, result); + // Safety: this function owns `ptr` and thus can move ownership here. + Self(ptr) + } + + /// Parse a DER-encoded RSAPrivateKey structure (from RFC 8017). + pub fn from_der_rsa_private_key(der: &[u8]) -> Option { + Some(PrivateKey(parse_with_cbs( + der, + // Safety: `ptr` is a non-null result from `RSA_parse_private_key` here. + |ptr| unsafe { bssl_sys::RSA_free(ptr) }, + // Safety: `cbs` is valid per `parse_with_cbs`. + |cbs| unsafe { bssl_sys::RSA_parse_private_key(cbs) }, + )?)) + } + + /// Serialize to a DER-encoded RSAPrivateKey structure (from RFC 8017). + pub fn to_der_rsa_private_key(&self) -> Buffer { + cbb_to_buffer(/*initial_capacity=*/ 512, |cbb| unsafe { + // Safety: `self.0` is valid by construction. + assert_eq!(1, bssl_sys::RSA_marshal_private_key(cbb, self.0)) + }) + } + + /// Parse a DER-encrypted PrivateKeyInfo struct (from RFC 5208). This is often called "PKCS#8 format". + pub fn from_der_private_key_info(der: &[u8]) -> Option { + let mut pkey = scoped::EvpPkey::from_ptr(parse_with_cbs( + der, + // Safety: `ptr` is a non-null result from `EVP_parse_private_key` here. + |pkey| unsafe { bssl_sys::EVP_PKEY_free(pkey) }, + // Safety: `cbs` is valid per `parse_with_cbs`. + |cbs| unsafe { bssl_sys::EVP_parse_private_key(cbs) }, + )?); + // Safety: `pkey` is valid and was created just above. + let rsa = unsafe { bssl_sys::EVP_PKEY_get1_RSA(pkey.as_ffi_ptr()) }; + if rsa.is_null() { + return None; + } + Some(Self(rsa)) + } + + /// Serialize to a DER-encrypted PrivateKeyInfo struct (from RFC 5208). This is often called "PKCS#8 format". + pub fn to_der_private_key_info(&self) -> Buffer { + let mut pkey = scoped::EvpPkey::new(); + assert_eq!(1, unsafe { + // Safety: `pkey` was just constructed. This takes a reference and + // so doesn't steal ownership from `self`. + bssl_sys::EVP_PKEY_set1_RSA(pkey.as_ffi_ptr(), self.0) + }); + unsafe { + cbb_to_buffer(/*initial_capacity=*/ 384, |cbb| { + // Safety: `pkey` is valid and owned by this function. + assert_eq!(1, bssl_sys::EVP_marshal_private_key(cbb, pkey.as_ffi_ptr())); + }) + } + } + + /// Compute the signature of the digest of `to_be_signed` with PKCS#1 using + /// this private key. The specified hash function is used to compute the + // digest. + pub fn sign_pkcs1(&self, to_be_signed: &[u8]) -> Vec { + let digest = Hash::hash_to_vec(to_be_signed); + // Safety: `get_md` always returns a valid pointer. + let hash_nid = unsafe { bssl_sys::EVP_MD_nid(Hash::get_md(sealed::Sealed).as_ptr()) }; + let max_output = unsafe { bssl_sys::RSA_size(self.0) } as usize; + + unsafe { + with_output_vec(max_output, |out_buf| { + let mut out_len: core::ffi::c_uint = 0; + // Safety: `out_buf` points to at least `RSA_size` bytes, as + // required. `self.0` is valid by construction. + let result = bssl_sys::RSA_sign( + hash_nid, + digest.as_slice().as_ffi_ptr(), + digest.len(), + out_buf, + &mut out_len, + self.0, + ); + // `RSA_sign` should always be successful unless it's out of + // memory, which this crate doesn't handle. + assert_eq!(1, result); + let out_len = out_len as usize; + assert!(out_len <= max_output); + // Safety: `out_len` bytes have been written. + out_len + }) + } + } + + /// Return the public key corresponding to this private key. + pub fn as_public(&self) -> PublicKey { + // Safety: `self.0` is valid by construction and `RSA_up_ref` means + // we we can pass an ownership reference to `PublicKey`. + unsafe { bssl_sys::RSA_up_ref(self.0) }; + PublicKey(self.0) + } +} + +// Safety: +// +// An `RSA` is safe to use from multiple threads so long as no mutating +// operations are performed. (Reference count changes don't count as mutating.) +// No mutating operations are performed. `RSA_sign` takes a non-const pointer +// but the BoringSSL docs specifically say that "these functions are considered +// non-mutating for thread-safety purposes and may be used concurrently." +unsafe impl Sync for PrivateKey {} +unsafe impl Send for PrivateKey {} + +impl Drop for PrivateKey { + fn drop(&mut self) { + // Safety: `self.0` is always owned by this struct. + unsafe { bssl_sys::RSA_free(self.0) } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::digest; + + #[test] + fn sign_and_verify() { + let key = PrivateKey::from_der_private_key_info(TEST_PKCS8_BYTES).unwrap(); + let signed_msg = b"hello world"; + let sig = key.sign_pkcs1::(signed_msg); + assert!(key + .as_public() + .verify_pkcs1::(signed_msg, &sig) + .is_ok()); + } +} + +// RSA generation is slow, but serialized keys are large. In order to use this +// in doctests, it's included here and public, but undocumented. +#[doc(hidden)] +pub const TEST_PKCS8_BYTES: &[u8] = b"\x30\x82\x04\xbd\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x04\x82\x04\xa7\x30\x82\x04\xa3\x02\x01\x00\x02\x82\x01\x01\x00\x98\x3f\xf5\xc4\x89\xb7\x6f\x12\xc8\xb5\x82\xaa\x98\xe6\x75\x39\xc4\x44\x46\xa0\x45\x62\x42\x43\x21\x81\xa0\x53\x17\x47\xb3\xdc\xfc\x3b\x76\x03\xd6\xd4\xce\x5e\x9d\x22\xe5\xa3\x59\xa2\x47\x0c\xe4\x82\x33\x7a\x21\xa5\x61\x2a\x77\xa2\x6b\xfa\xa3\x45\x41\x50\xc2\xf7\x0d\xe1\xa6\x3a\x83\x5b\xe6\xb8\x1f\x24\x1e\x24\x89\xf8\x8d\xde\x5f\xf1\x50\x27\x0f\x2b\xbe\x58\xaa\x64\x67\xef\x22\x57\x1e\xf4\x3f\x2e\xba\x4b\x2f\xc3\x5e\x67\xcc\xc3\xf6\xdd\x6b\x31\x58\xb9\xbd\x7b\xf9\x23\xac\xf2\xa9\xb6\x8f\x88\x75\x0f\x73\xdf\xd2\x14\xaa\x41\x28\x5c\x9a\xd6\xc4\xab\x6f\xb0\x53\xb9\x0a\x2c\xfb\x56\x6e\x56\x94\xaa\x1a\x25\x29\x3b\x01\x0c\x7e\x44\x1b\xe1\x76\x12\x73\xc4\x16\x62\x64\x3d\xe6\xf7\x9f\x69\x3f\xc9\x3b\x75\xd6\x80\xee\x87\x68\x83\xde\x2d\x18\xe4\x26\xdd\x1a\x02\xd8\xd2\x1d\xb6\xf1\x71\xf5\x63\x62\x0c\xd7\x35\x21\xc6\x75\xb4\xd5\x0f\x89\x08\x17\x13\x24\x07\xc2\x7c\x73\xe2\x17\x00\x12\x8a\xc9\x39\xdb\xf0\xc8\x6f\x1f\xf7\x99\xed\x8c\x67\x9c\xf2\x30\x5c\xd0\xd0\x0d\xc1\x15\x07\xa3\x1d\xf5\xd4\x92\x82\xfd\x9c\x5a\x11\x69\x3b\x02\x03\x01\x00\x01\x02\x82\x01\x00\x44\xe1\x5a\xfd\x8a\x18\xd5\x45\xb8\x4c\x76\x4b\x5c\x55\x97\x5f\x85\x2e\x26\x8d\xc8\x16\x46\x48\x3c\xd6\x7a\x84\x5d\x19\xf1\x83\xdf\x11\xbf\xb8\xc8\xef\x0a\x56\xbf\xdc\xd3\xeb\xed\x57\x7f\xb1\x93\x88\x5c\x65\xba\xe7\x29\x68\x9f\x2b\x7a\x92\xb0\x5f\x5a\xc7\x81\x0d\x68\xd8\x57\xee\x4d\x13\xbc\xf4\x3c\x12\x89\x18\x9a\xdb\x3a\xc4\x0a\xc0\x10\x35\x3b\xa5\xdc\xbe\x1c\x88\xc4\x84\xea\x12\x64\x4c\xb8\x71\x19\x93\x7e\x8e\x73\x1d\x9f\x04\x61\xa1\x97\x27\x82\x2e\xb6\x4d\x6a\x4f\xfb\xa4\xe5\xa7\x54\x94\xb5\xf1\x41\xc8\xa4\x3d\xa1\xe6\x4a\xf0\xdb\xbb\xc2\x91\x26\x9a\x0f\xbf\xdd\x57\x1e\x83\x5c\x9a\x7b\x28\x53\x1d\x2d\x44\x91\x1f\x02\x81\x7b\x6f\xb5\xf7\x48\x7d\xa0\x12\x22\xdb\xbf\xd9\x04\x17\xe4\x97\xf2\xac\x32\xf8\x70\xfa\x75\xe3\x5a\xb0\xef\x1f\x2d\x24\xb9\x26\x83\x33\xe7\x3c\x3c\xfb\x0b\xd8\x70\x33\x76\xb1\x1c\x1d\x38\x06\x0a\xdb\xbd\xd2\x34\x5e\xe6\xb1\x6f\x5d\x8f\x18\xac\x94\xd2\x0d\xee\x39\x0b\xa3\xb4\xcf\xf1\xe1\x91\x30\xcb\xce\xa5\x2f\xa9\xcc\x4f\xee\xe4\xdd\xee\x8a\x77\x0e\xd1\xbd\xcc\xb0\x11\x55\x15\x5e\x99\xf1\x02\x81\x81\x00\xd1\x75\x33\xe4\x31\xc2\xfc\x09\x6c\xf6\x04\x97\xc7\xa3\xb1\x88\x36\x26\xd8\x4e\x86\x2d\xb8\x99\x68\x97\xd8\x0b\xc6\xc3\xe7\x58\x49\xc3\x41\xcd\xcd\x33\x09\xa0\x90\xb2\x77\xfa\xa3\xb6\x71\x09\x33\x43\x0a\x6a\xd8\xc3\x36\xaf\xa9\x11\x54\x64\x77\x82\xf4\xf1\xe0\x12\x5a\xb8\x9f\x5a\x04\xb3\x29\xd4\xc6\xba\x4c\xdc\x04\x97\xfb\xb6\x7e\x1b\x89\x09\x0c\x8a\xb8\x6c\x9f\x2b\x91\x0d\x34\x18\x39\xf3\x38\xf9\xe6\xed\x29\x48\x30\xe4\x3c\x09\x15\x33\xe0\xb8\x2f\xd8\xfa\xf2\x6d\x1f\xf1\xee\x02\xc2\xb4\xf9\xf4\x63\x4b\xa5\x02\x81\x81\x00\xba\x14\x89\xff\x65\xb5\xe6\x52\x45\x23\x37\x5e\x0c\x62\xde\xe9\x7f\xa9\x05\xee\x28\x0d\x91\xb1\x99\xd6\x8b\xf8\x58\x50\x8b\xb1\xee\x57\xbd\x2b\x7b\xf0\x25\x03\xeb\xbc\x87\x73\xc8\xbf\x57\x16\xda\x49\x7a\x79\x82\x25\x99\x46\x9c\xb3\xd2\xd5\xb0\xae\xec\xeb\xbc\xd2\x4b\xae\xd0\x0a\x54\xcd\xad\x44\x90\x74\x79\xa2\x34\x73\x8a\x3a\x6c\x0b\x13\x20\x5d\xa4\xcc\x7b\xb4\x64\xcf\x61\x6e\xdf\xc1\x8c\xd4\x84\x22\xf1\x19\x32\x6d\xf1\x6f\xe1\x1e\xa5\xf6\x20\x6c\xc6\xa8\x9c\x4d\x8d\x59\xdf\x90\x71\x67\x1a\x48\xa3\x4b\x5f\x02\x81\x81\x00\x97\x4d\x8f\x7f\x7e\x86\xb8\x23\x62\xe7\x50\x28\x07\xd9\x72\x4b\xcf\xba\x3d\xb4\x73\x6e\xa1\x93\x87\x9f\x70\x3c\x09\x87\xc8\x1c\xd9\xa3\xc7\x6c\x0f\x97\x97\x93\xba\x12\x81\x62\xb7\x51\xf9\xd3\x48\x89\x5c\x04\x14\xb2\xe7\x54\xfa\xce\xfe\xe4\x58\x04\x6c\x46\x30\xb3\x71\x7f\x3d\xf4\xfb\xc2\x24\x2c\x84\xa5\x5d\x11\xed\xeb\x8f\xb3\xa2\xe2\xe7\x19\x77\x4a\xd9\xaf\xf5\x46\xb6\x50\x10\x5a\x93\xb9\xe3\x65\x79\xef\xc5\x4b\x55\xad\xf8\xc4\x22\xe1\xc7\xa9\xa5\x3e\x9a\xff\xf5\xde\x06\x98\x04\xbc\x7b\x98\xb7\x75\xe6\xd5\x02\x81\x80\x0a\x7f\x38\x1d\xa9\x2e\x2e\xb4\xfb\x63\x76\x2f\x1f\x01\xc0\xd3\x69\x39\x2e\xb5\x75\x9a\xf6\x5a\x0f\x74\x93\xe6\xc9\x8c\x99\xa4\xca\xee\x36\x24\xaa\xd4\x2c\x32\x61\x6c\xfc\x33\x22\xe2\xf0\x55\xc0\xb0\x9e\x71\x16\x4f\x6a\xab\x1a\x11\xe6\xd5\xd9\x26\xb5\x04\xc3\x5d\x15\x99\xe1\xf0\x83\x42\x2b\x01\x10\x29\x11\xe7\x7d\x8f\xfa\xff\x3a\xb3\x11\x3c\x25\x2c\x33\xc0\xd2\xb7\x51\x1f\x8c\xf2\xa0\x67\x82\x61\x85\xdb\x15\xf1\xcb\x53\xf0\x5c\xc1\xae\xd9\x08\x91\x3a\x4f\xae\xa9\x8d\x4c\xc1\x98\xd3\x5c\xde\x95\xb4\x68\x7f\x02\x81\x80\x7d\x3e\x6b\x2c\x16\xe8\x17\x2c\x27\x9c\xc5\xc5\xfb\x30\x1a\xf7\x32\x53\x93\xfe\xc1\xa0\x5d\xac\x7d\x6f\xba\x1b\x56\x7e\x34\xf6\xa7\x91\x1f\x39\x84\x1c\x94\x58\x13\xe2\xb9\xec\xb6\x24\xfe\x76\x35\x1b\xcc\x4f\x8e\x0d\x88\x5b\x5a\x6f\xb6\xa2\x0b\xc3\xb6\x98\x2d\xca\xce\xce\x26\xb4\x36\x37\x42\xa4\xc0\xa9\x85\x57\x4b\x6b\xc2\xed\x14\x96\xe5\xbc\x2b\x83\x32\xe9\x83\x24\x7f\x85\x74\x09\x3c\xfa\x45\xfd\x21\xeb\xd8\xa3\x02\xd2\x70\x0a\x9a\x9d\x7d\xe4\x39\xc4\x59\xc8\x16\x6f\xce\xd5\x1d\xea\x91\x4d\x12\x78\xc3\x30"; diff --git a/src/rust/bssl-crypto/src/scoped.rs b/src/rust/bssl-crypto/src/scoped.rs new file mode 100644 index 000000000..391a0c6af --- /dev/null +++ b/src/rust/bssl-crypto/src/scoped.rs @@ -0,0 +1,89 @@ +/* Copyright (c) 2024, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +//! Helpers to ensure that some temporary objects are always freed. + +use crate::initialized_struct; + +/// A scoped `EC_KEY`. +pub struct EvpPkey(*mut bssl_sys::EVP_PKEY); + +impl EvpPkey { + pub fn new() -> Self { + let ptr = unsafe { bssl_sys::EVP_PKEY_new() }; + // `ptr` is only NULL if we're out of memory, which this crate + // doesn't handle. + assert!(!ptr.is_null()); + EvpPkey(ptr) + } + + pub fn from_ptr(ptr: *mut bssl_sys::EVP_PKEY) -> Self { + EvpPkey(ptr) + } + + pub fn as_ffi_ptr(&mut self) -> *mut bssl_sys::EVP_PKEY { + self.0 + } +} + +impl Drop for EvpPkey { + fn drop(&mut self) { + unsafe { bssl_sys::EVP_PKEY_free(self.0) } + } +} + +/// A scoped `EC_KEY`. +pub struct EcKey(*mut bssl_sys::EC_KEY); + +impl EcKey { + pub fn new() -> Self { + let ptr = unsafe { bssl_sys::EC_KEY_new() }; + // `ptr` is only NULL if we're out of memory, which this crate + // doesn't handle. + assert!(!ptr.is_null()); + EcKey(ptr) + } + + pub fn as_ffi_ptr(&mut self) -> *mut bssl_sys::EC_KEY { + self.0 + } +} + +impl Drop for EcKey { + fn drop(&mut self) { + unsafe { bssl_sys::EC_KEY_free(self.0) } + } +} + +/// A scoped `BIGNUM`. +pub struct Bignum(bssl_sys::BIGNUM); + +impl Bignum { + pub fn from_u64(value: u64) -> Self { + let mut ret = Bignum(unsafe { initialized_struct(|ptr| bssl_sys::BN_init(ptr)) }); + assert_eq!(1, unsafe { bssl_sys::BN_set_u64(&mut ret.0, value) }); + ret + } + + pub unsafe fn as_ffi_ptr(&self) -> *const bssl_sys::BIGNUM { + &self.0 + } +} + +impl Drop for Bignum { + fn drop(&mut self) { + unsafe { bssl_sys::BN_free(&mut self.0) } + } +} diff --git a/src/rust/bssl-crypto/src/x25519.rs b/src/rust/bssl-crypto/src/x25519.rs index b91af5460..26030b10f 100644 --- a/src/rust/bssl-crypto/src/x25519.rs +++ b/src/rust/bssl-crypto/src/x25519.rs @@ -76,7 +76,7 @@ impl PrivateKey { // Safety: `X25519` indeed writes `SHARED_KEY_LEN` bytes. unsafe { with_output_array_fallible(|out, _| { - bssl_sys::X25519(out, self.0.as_ffi_ptr(), other_public_key.as_ffi_ptr()) + bssl_sys::X25519(out, self.0.as_ffi_ptr(), other_public_key.as_ffi_ptr()) == 1 }) } } diff --git a/src/rust/bssl-sys/Cargo.toml b/src/rust/bssl-sys/Cargo.toml index b01979a3a..76087e562 100644 --- a/src/rust/bssl-sys/Cargo.toml +++ b/src/rust/bssl-sys/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Benjamin Brittain "] edition = "2018" publish = false -license = "MIT" +license = "ISC" # This exists to workaround a limitation in cargo: # https://github.com/rust-lang/cargo/issues/3544 diff --git a/win-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-win.asm b/win-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-win.asm index 285df4f54..6691a2ddf 100644 --- a/win-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-win.asm +++ b/win-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64-win.asm @@ -1287,14 +1287,15 @@ _CET_ENDBR $L$128_dec_start: vzeroupper vmovdqa xmm0,XMMWORD[rdx] + + + vmovdqu xmm15,XMMWORD[16+rdx] + vpor xmm15,xmm15,XMMWORD[OR_MASK] mov rax,rdx lea rax,[32+rax] lea rcx,[32+rcx] - - vmovdqu xmm15,XMMWORD[r9*1+rdi] - vpor xmm15,xmm15,XMMWORD[OR_MASK] and r9,~15 @@ -2583,14 +2584,15 @@ _CET_ENDBR $L$256_dec_start: vzeroupper vmovdqa xmm0,XMMWORD[rdx] + + + vmovdqu xmm15,XMMWORD[16+rdx] + vpor xmm15,xmm15,XMMWORD[OR_MASK] mov rax,rdx lea rax,[32+rax] lea rcx,[32+rcx] - - vmovdqu xmm15,XMMWORD[r9*1+rdi] - vpor xmm15,xmm15,XMMWORD[OR_MASK] and r9,~15