diff --git a/CHANGELOG.md b/CHANGELOG.md index 9acac50..75d2d3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to this project will be documented in this file. +--- +## [3.0.0] - 2022-10-04 +### Added +- `DhKeyPair` which represents an asymmetric key pair in a space wher the + Computational Diffie-Helman problem is intractable +### Changed +- use constant generics +- use `core` instead of `std` when possible +### Fixed +### Removed +- `NonceTrait::increment()` +- `SymmetricCrypto` trait +- `Block` +- `kdf` module +--- + --- ## [2.0.0] - 2022-08-22 ### Added diff --git a/Cargo.lock b/Cargo.lock index 0dbdc4e..1b77414 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,25 +4,14 @@ version = 3 [[package]] name = "aead" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" dependencies = [ + "crypto-common", "generic-array", ] -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "opaque-debug", -] - [[package]] name = "aes" version = "0.8.1" @@ -30,35 +19,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" dependencies = [ "cfg-if", - "cipher 0.4.3", + "cipher", "cpufeatures", ] [[package]] name = "aes-gcm" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" dependencies = [ "aead", - "aes 0.7.5", - "cipher 0.3.0", + "aes", + "cipher", "ctr", "ghash", "subtle", ] -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] @@ -81,15 +64,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array", -] - [[package]] name = "cipher" version = "0.4.3" @@ -102,18 +76,14 @@ dependencies = [ [[package]] name = "cosmian_crypto_core" -version = "2.0.0" +version = "3.0.0" dependencies = [ - "aes 0.8.1", "aes-gcm", "curve25519-dalek", - "generic-array", "getrandom 0.2.7", "hex", "hkdf", - "num-bigint", - "rand", - "rand_core 0.6.3", + "rand_core 0.6.4", "rand_hc", "serde", "sha2", @@ -123,9 +93,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1079fb8528d9f9c888b1e8aa651e6e079ade467323d58f75faf1d30b1808f540" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -137,16 +107,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] [[package]] name = "ctr" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.3.0", + "cipher", ] [[package]] @@ -173,9 +144,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer", "crypto-common", @@ -188,7 +159,6 @@ version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ - "serde", "typenum", "version_check", ] @@ -219,9 +189,9 @@ dependencies = [ [[package]] name = "ghash" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ "opaque-debug", "polyval", @@ -248,7 +218,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -262,18 +232,18 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.132" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "log" @@ -284,43 +254,11 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "num-bigint" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", - "rand", - "serde", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - [[package]] name = "once_cell" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -330,9 +268,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "polyval" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" dependencies = [ "cfg-if", "cpufeatures", @@ -340,17 +278,11 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] @@ -364,27 +296,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -396,9 +307,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.7", ] @@ -409,23 +320,23 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] name = "serde" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -434,13 +345,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -451,9 +362,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", @@ -462,18 +373,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -488,17 +399,17 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ - "generic-array", + "crypto-common", "subtle", ] @@ -522,9 +433,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -532,9 +443,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", "log", @@ -547,9 +458,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -557,9 +468,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -570,9 +481,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index 31f20d7..49eb29b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = "Crypto lib for pure crypto primitives" edition = "2021" license = "MIT/Apache-2.0" name = "cosmian_crypto_core" -version = "2.0.0" +version = "3.0.0" [lib] crate-type = ["cdylib", "rlib"] @@ -12,17 +12,13 @@ name = "cosmian_crypto_core" path = "src/lib.rs" [dependencies] -aes = "0.8" -aes-gcm = "0.9" +aes-gcm = "0.10" curve25519-dalek = "3.2" -generic-array = { version = "0.14.5", features = ["serde"] } # specify the js feature for the WASM target getrandom = { version = "0.2", features = ["js"] } hex = "0.4" hkdf = "0.12" -num-bigint = { version = "0.4", features = ["rand", "serde"] } -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom"] } +rand_core = { version = "0.6", features = ["std", "getrandom"] } rand_hc = "0.3" serde = { version = "1.0", features = ["derive"] } sha2 = "0.10" diff --git a/README.md b/README.md index 8ff76c3..c6fb4dc 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Cosmian cryptographic resources: - symmetric cryptography primitives can be found in the `symmetric_crypto` module; - asymmetric cryptography primitives can be found in the `asymmetric_crypto` module; -- a Key Derivation Function (KDF) can be found in the `kdf` module; - a Random Number Generator (RNG) can be found in the `entropy` module. The crate also defines `CryptoCoreError`, the error type, and a few traits. @@ -23,28 +22,20 @@ GCM algorithm, as described in the [ISO 2004](https://www.shoup.net/iso/std6.pdf This implementation is 128-bits secure. It uses the [`aes_gcm`](https://docs.rs/aes-gcm/latest/aes_gcm/index.html) -implementation of the AES GCM algorithm. This implementation make use of the -AES instruction when available, which allows a high encryption speed. +implementation of the AES GCM algorithm. This implementation makes use of the +AES instruction set when available, which allows for a high encryption speed. ## Asymmetric Crypto This crate implements a public and a private key objects based on Curve25519. -This is the fastest elliptic curve known when implementing these objects. Its -security level is also 128 bits. +This one of the fastest elliptic curves known when implementing these objects. +Its security level is also 128 bits. It uses the [Dalek](https://github.com/dalek-cryptography/curve25519-dalek) -implementation, which also offers an implementation of the Ristretto technique -to construct a prime order group on the curve. This group is used to implement +implementation, which offers an implementation of the Ristretto technique to +construct a prime order group on the curve. This group is used to implement the public key. -## Key Derivation Function (KDF) - -This crate uses the [`hkdf`](https://docs.rs/hkdf/latest/hkdf/) implementation -of the HKDF algorithm, along with the Sha256 implementation of the Rust standard -library in order to implement a KDF. - -Since Sha256 is 128-bits secure, this makes this KDF 128-bits secure too. - ## Random Number Generator (RNG) This crate uses the implementation of the HC128 algorithm from the diff --git a/src/asymmetric_crypto.rs b/src/asymmetric_crypto/curve25519.rs similarity index 62% rename from src/asymmetric_crypto.rs rename to src/asymmetric_crypto/curve25519.rs index 32c03b0..0a88fbe 100644 --- a/src/asymmetric_crypto.rs +++ b/src/asymmetric_crypto/curve25519.rs @@ -5,22 +5,27 @@ //! Its security level is 128-bits. It is the fastest curve available at the //! time of this implementation. -use crate::{CryptoCoreError, KeyTrait}; +use crate::{asymmetric_crypto::DhKeyPair, CryptoCoreError, KeyTrait}; +use core::{ + convert::TryFrom, + fmt::Display, + ops::{Add, Div, Mul, Sub}, +}; use curve25519_dalek::{ constants, ristretto::{CompressedRistretto, RistrettoPoint}, scalar::Scalar, }; -use generic_array::{typenum::U32, GenericArray}; use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use std::{ - convert::TryFrom, - fmt::Display, - ops::{Add, Mul, Sub}, -}; use zeroize::{Zeroize, ZeroizeOnDrop}; +/// X25519 secret key length +pub const X25519_SK_LENGTH: usize = 32; + +/// X25519 public key length +pub const X25519_PK_LENGTH: usize = 32; + /// Asymmetric private key based on Curve25519. /// /// Internally, a curve scalar is used. It is 128-bits long. @@ -29,48 +34,40 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; pub struct X25519PrivateKey(Scalar); impl X25519PrivateKey { - /// Generate a new private key. - #[must_use] - pub fn new(rng: &mut R) -> Self { - let mut bytes = [0; 64]; - rng.fill_bytes(&mut bytes); - Self(Scalar::from_bytes_mod_order_wide(&bytes)) - } - - /// Convert to bytes without copy. + /// Converts to bytes without copy. #[inline] #[must_use] pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } +} - /// Invert the given key for the multiplicative law +impl KeyTrait for X25519PrivateKey { + /// Generates a new random key. #[inline] - #[must_use] - pub fn invert(&self) -> Self { - Self(self.0.invert()) + fn new(rng: &mut R) -> Self { + let mut bytes = [0; 64]; + rng.fill_bytes(&mut bytes); + Self(Scalar::from_bytes_mod_order_wide(&bytes)) } -} -impl KeyTrait for X25519PrivateKey { - type Length = U32; - - /// Convert the given private key into bytes. + /// Converts the given key into bytes. #[inline] - #[must_use] - fn to_bytes(&self) -> GenericArray { - GenericArray::::from(self.0.to_bytes()) + fn to_bytes(&self) -> [u8; Self::LENGTH] { + self.0.to_bytes() } + /// Converts the given bytes into key. + #[inline] fn try_from_bytes(bytes: &[u8]) -> Result { Self::try_from(bytes) } } -impl TryFrom<[u8; 32]> for X25519PrivateKey { +impl TryFrom<[u8; Self::LENGTH]> for X25519PrivateKey { type Error = CryptoCoreError; - fn try_from(bytes: [u8; 32]) -> Result { + fn try_from(bytes: [u8; Self::LENGTH]) -> Result { let scalar = Scalar::from_canonical_bytes(bytes).ok_or_else(|| { Self::Error::ConversionError( "Given bytes do not represent a canonical Scalar!".to_string(), @@ -84,7 +81,7 @@ impl TryFrom<&[u8]> for X25519PrivateKey { type Error = CryptoCoreError; fn try_from(bytes: &[u8]) -> Result { - let bytes: [u8; 32] = bytes.try_into().map_err(|e| { + let bytes: [u8; Self::LENGTH] = bytes.try_into().map_err(|e| { Self::Error::ConversionError(format!( "Error while converting slice of size {} to `X25519PublicKey`: {}", bytes.len(), @@ -95,17 +92,12 @@ impl TryFrom<&[u8]> for X25519PrivateKey { } } -impl From<&X25519PrivateKey> for [u8; 32] { - fn from(key: &X25519PrivateKey) -> Self { - key.0.to_bytes() - } -} - // Needed by serde to derive `Deserialize`. Do not use otherwise since there // is a copy anyway -impl From for [u8; 32] { +impl From for [u8; X25519_SK_LENGTH] { + #[inline] fn from(key: X25519PrivateKey) -> Self { - key.0.to_bytes() + key.to_bytes() } } @@ -121,51 +113,11 @@ impl TryFrom<&str> for X25519PrivateKey { /// Display the hex encoded value of the key impl Display for X25519PrivateKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", hex::encode(self.0.as_bytes())) } } -impl<'a> Mul<&'a Self> for X25519PrivateKey { - type Output = Self; - - fn mul(self, rhs: &Self) -> Self::Output { - Self(self.0 * rhs.0) - } -} - -impl<'a> Mul<&'a X25519PrivateKey> for &X25519PrivateKey { - type Output = X25519PrivateKey; - - fn mul(self, rhs: &X25519PrivateKey) -> Self::Output { - X25519PrivateKey(self.0 * rhs.0) - } -} - -impl Add for X25519PrivateKey { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl<'a> Add<&'a Self> for X25519PrivateKey { - type Output = Self; - - fn add(self, rhs: &Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl<'a> Add for &'a X25519PrivateKey { - type Output = X25519PrivateKey; - - fn add(self, rhs: X25519PrivateKey) -> Self::Output { - X25519PrivateKey(self.0 + rhs.0) - } -} - impl<'a> Add<&'a X25519PrivateKey> for &X25519PrivateKey { type Output = X25519PrivateKey; @@ -174,35 +126,28 @@ impl<'a> Add<&'a X25519PrivateKey> for &X25519PrivateKey { } } -impl Sub for X25519PrivateKey { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl<'a> Sub<&'a Self> for X25519PrivateKey { - type Output = Self; +impl<'a> Sub<&'a X25519PrivateKey> for &X25519PrivateKey { + type Output = X25519PrivateKey; - fn sub(self, rhs: &Self) -> Self::Output { - Self(self.0 - rhs.0) + fn sub(self, rhs: &X25519PrivateKey) -> Self::Output { + X25519PrivateKey(self.0 - rhs.0) } } -impl Sub for &X25519PrivateKey { +impl<'a> Mul<&'a X25519PrivateKey> for &X25519PrivateKey { type Output = X25519PrivateKey; - fn sub(self, rhs: X25519PrivateKey) -> Self::Output { - X25519PrivateKey(self.0 - rhs.0) + fn mul(self, rhs: &X25519PrivateKey) -> Self::Output { + X25519PrivateKey(self.0 * rhs.0) } } -impl<'a> Sub<&'a X25519PrivateKey> for &X25519PrivateKey { +impl<'a> Div<&'a X25519PrivateKey> for &X25519PrivateKey { type Output = X25519PrivateKey; - fn sub(self, rhs: &X25519PrivateKey) -> Self::Output { - X25519PrivateKey(self.0 - rhs.0) + fn div(self, rhs: &X25519PrivateKey) -> Self::Output { + #[allow(clippy::suspicious_arithmetic_impl)] + X25519PrivateKey(self.0 * rhs.0.invert()) } } @@ -212,7 +157,7 @@ impl Zeroize for X25519PrivateKey { } } -// Implement `Drop` trait to follow R23. +// Implements `Drop` trait to follow R23. impl Drop for X25519PrivateKey { fn drop(&mut self) { self.zeroize(); @@ -229,42 +174,44 @@ impl ZeroizeOnDrop for X25519PrivateKey {} #[serde(try_from = "&[u8]", into = "[u8; 32]")] pub struct X25519PublicKey(RistrettoPoint); -impl X25519PublicKey { - /// Generate a new random public key. +impl KeyTrait for X25519PublicKey { + /// Generates a new random public key. #[inline] - #[must_use] - pub fn new(rng: &mut R) -> Self { + fn new(rng: &mut R) -> Self { let mut uniform_bytes = [0u8; 64]; rng.fill_bytes(&mut uniform_bytes); Self(RistrettoPoint::from_uniform_bytes(&uniform_bytes)) } -} - -impl KeyTrait for X25519PublicKey { - type Length = U32; - /// Convert the given public key into an array of bytes. + /// Converts the given public key into an array of bytes. #[inline] - #[must_use] - fn to_bytes(&self) -> GenericArray { - GenericArray::::from(self.0.compress().to_bytes()) + fn to_bytes(&self) -> [u8; Self::LENGTH] { + self.0.compress().to_bytes() } + /// Converts the given bytes into key. + #[inline] fn try_from_bytes(bytes: &[u8]) -> Result { Self::try_from(bytes) } } +impl From for X25519PublicKey { + fn from(private_key: X25519PrivateKey) -> Self { + Self(&private_key.0 * &constants::RISTRETTO_BASEPOINT_TABLE) + } +} + impl From<&X25519PrivateKey> for X25519PublicKey { fn from(private_key: &X25519PrivateKey) -> Self { Self(&private_key.0 * &constants::RISTRETTO_BASEPOINT_TABLE) } } -impl TryFrom<[u8; 32]> for X25519PublicKey { +impl TryFrom<[u8; Self::LENGTH]> for X25519PublicKey { type Error = CryptoCoreError; - fn try_from(bytes: [u8; 32]) -> Result { + fn try_from(bytes: [u8; Self::LENGTH]) -> Result { Ok(Self(CompressedRistretto(bytes).decompress().ok_or_else( || { CryptoCoreError::ConversionError( @@ -279,7 +226,7 @@ impl TryFrom<&[u8]> for X25519PublicKey { type Error = CryptoCoreError; fn try_from(bytes: &[u8]) -> Result { - let bytes: [u8; 32] = bytes.try_into().map_err(|e| { + let bytes: [u8; Self::LENGTH] = bytes.try_into().map_err(|e| { Self::Error::ConversionError(format!( "Error while converting slice of size {} to `X25519PublicKey`: {}", bytes.len(), @@ -292,19 +239,14 @@ impl TryFrom<&[u8]> for X25519PublicKey { // Needed by serde to derive `Deserialize`. Do not use otherwise since there // is a copy anyway. -impl From for [u8; 32] { +impl From for [u8; X25519_PK_LENGTH] { + #[inline] fn from(key: X25519PublicKey) -> Self { - key.0.compress().to_bytes() - } -} - -impl From<&X25519PublicKey> for [u8; 32] { - fn from(key: &X25519PublicKey) -> Self { - key.0.compress().to_bytes() + key.to_bytes() } } -/// Parse from an hex encoded String +/// Parses an hex encoded String impl TryFrom<&str> for X25519PublicKey { type Error = CryptoCoreError; @@ -314,26 +256,18 @@ impl TryFrom<&str> for X25519PublicKey { } } -/// Display the hex encoded value of the key +/// Displays the hex encoded value of the key impl Display for X25519PublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", hex::encode(self.0.compress().to_bytes())) } } -impl Add for X25519PublicKey { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl<'a> Add<&'a Self> for X25519PublicKey { - type Output = Self; +impl<'a> Sub<&'a X25519PublicKey> for &X25519PublicKey { + type Output = X25519PublicKey; - fn add(self, rhs: &Self) -> Self::Output { - Self(self.0 + rhs.0) + fn sub(self, rhs: &X25519PublicKey) -> Self::Output { + X25519PublicKey(self.0 - rhs.0) } } @@ -353,21 +287,13 @@ impl<'a> Mul<&'a X25519PrivateKey> for &X25519PublicKey { } } -impl<'a> Mul<&'a X25519PrivateKey> for X25519PublicKey { - type Output = Self; - - fn mul(self, rhs: &X25519PrivateKey) -> Self::Output { - Self(self.0 * rhs.0) - } -} - impl Zeroize for X25519PublicKey { fn zeroize(&mut self) { self.0.zeroize() } } -// Implement `Drop` trait to follow R23. +// Implements `Drop` trait to follow R23. impl Drop for X25519PublicKey { fn drop(&mut self) { self.zeroize(); @@ -376,18 +302,60 @@ impl Drop for X25519PublicKey { impl ZeroizeOnDrop for X25519PublicKey {} +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct X25519KeyPair { + pk: X25519PublicKey, + sk: X25519PrivateKey, +} + +impl DhKeyPair for X25519KeyPair { + type PublicKey = X25519PublicKey; + + type PrivateKey = X25519PrivateKey; + + #[inline] + fn new(rng: &mut R) -> Self { + let sk = X25519PrivateKey::new(rng); + let pk = X25519PublicKey::from(&sk); + Self { sk, pk } + } + + #[inline] + fn public_key(&self) -> &Self::PublicKey { + &self.pk + } + + #[inline] + fn private_key(&self) -> &Self::PrivateKey { + &self.sk + } +} + +impl Zeroize for X25519KeyPair { + fn zeroize(&mut self) { + self.pk.zeroize(); + self.sk.zeroize(); + } +} + +// Implements `Drop` trait to follow R23. +impl Drop for X25519KeyPair { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl ZeroizeOnDrop for X25519KeyPair {} + #[cfg(test)] mod test { - use crate::{ - asymmetric_crypto::{X25519PrivateKey, X25519PublicKey}, - entropy::CsRng, - }; + use crate::{asymmetric_crypto::curve25519::*, entropy::CsRng, KeyTrait}; #[test] fn test_private_key_serialization() { let mut rng = CsRng::new(); let sk = X25519PrivateKey::new(&mut rng); - let bytes: [u8; 32] = (&sk).into(); + let bytes: [u8; X25519_SK_LENGTH] = sk.to_bytes(); let recovered = X25519PrivateKey::try_from(bytes).unwrap(); assert_eq!(sk, recovered); } @@ -396,8 +364,22 @@ mod test { fn test_public_key_serialization() { let mut rng = CsRng::new(); let pk = X25519PublicKey::new(&mut rng); - let bytes: [u8; 32] = (&pk).into(); + let bytes: [u8; X25519_PK_LENGTH] = pk.to_bytes(); let recovered = super::X25519PublicKey::try_from(bytes).unwrap(); assert_eq!(pk, recovered); } + + #[test] + fn test_dh_key_pair() { + let mut rng = CsRng::new(); + let kp1 = X25519KeyPair::new(&mut rng); + let kp2 = X25519KeyPair::new(&mut rng); + // check the keys are randomly generated + assert_ne!(kp1, kp2); + // check DH Key exchange is possible + assert_eq!( + kp1.public_key() * kp2.private_key(), + kp2.public_key() * kp1.private_key() + ); + } } diff --git a/src/asymmetric_crypto/mod.rs b/src/asymmetric_crypto/mod.rs new file mode 100644 index 0000000..5184b23 --- /dev/null +++ b/src/asymmetric_crypto/mod.rs @@ -0,0 +1,45 @@ +use crate::KeyTrait; +use core::{ + fmt::Debug, + ops::{Add, Div, Mul, Sub}, +}; +use rand_core::{CryptoRng, RngCore}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +pub mod curve25519; + +pub trait DhKeyPair: + Debug + PartialEq + Eq + Send + Sync + Sized + Clone + Zeroize + ZeroizeOnDrop +where + Self::PublicKey: From, + for<'a, 'b> &'a Self::PublicKey: Add<&'b Self::PublicKey, Output = Self::PublicKey> + + Mul<&'b Self::PrivateKey, Output = Self::PublicKey>, + for<'a, 'b> &'a Self::PrivateKey: Add<&'b Self::PrivateKey, Output = Self::PrivateKey> + + Sub<&'b Self::PrivateKey, Output = Self::PrivateKey> + + Mul<&'b Self::PrivateKey, Output = Self::PrivateKey> + + Div<&'b Self::PrivateKey, Output = Self::PrivateKey>, +{ + /// This is needed to be able to use `{ MyKeyPair::PK_LENGTH }` + /// as associated constant + const PK_LENGTH: usize = PK_LENGTH; + + /// This is needed to be able to use `{ MyKeyPair::SK_LENGTH }` + /// as associated constant + const SK_LENGTH: usize = SK_LENGTH; + + /// Public key + type PublicKey: KeyTrait; + + /// Secret key + type PrivateKey: KeyTrait; + + /// Creates a new key pair + #[must_use] + fn new(rng: &mut R) -> Self; + + /// Returns a reference to the public key. + fn public_key(&self) -> &Self::PublicKey; + + /// Returns a reference to the secret key. + fn private_key(&self) -> &Self::PrivateKey; +} diff --git a/src/entropy.rs b/src/entropy.rs index 4719618..0ebf062 100644 --- a/src/entropy.rs +++ b/src/entropy.rs @@ -1,4 +1,3 @@ -use generic_array::{ArrayLength, GenericArray}; use rand_core::{CryptoRng, RngCore, SeedableRng}; use rand_hc::Hc128Rng; @@ -10,7 +9,8 @@ pub struct CsRng { } impl CsRng { - /// Generate a new random number generator. + /// Generates a new random number generator. + #[inline] #[must_use] pub fn new() -> Self { Self { @@ -18,35 +18,41 @@ impl CsRng { } } - /// Generate a vector of random bytes with the given length. - /// - /// - `len` : number of random bytes to generate - pub fn generate_random_bytes>(&mut self) -> GenericArray { - let mut bytes = GenericArray::::default(); + /// Generates a vector of random bytes with the given length. + #[inline] + #[must_use] + pub fn generate_random_bytes(&mut self) -> [u8; LENGTH] { + let mut bytes = [0; LENGTH]; self.rng.fill_bytes(&mut bytes); bytes } } impl Default for CsRng { + #[must_use] + #[inline] fn default() -> Self { Self::new() } } impl RngCore for CsRng { + #[inline] fn next_u32(&mut self) -> u32 { self.rng.next_u32() } + #[inline] fn next_u64(&mut self) -> u64 { self.rng.next_u64() } + #[inline] fn fill_bytes(&mut self, dest: &mut [u8]) { self.rng.fill_bytes(dest); } + #[inline] fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { self.rng.try_fill_bytes(dest).map_err(rand_core::Error::new) } @@ -58,16 +64,15 @@ impl CryptoRng for CsRng {} mod test { use crate::entropy::CsRng; - use generic_array::typenum::{Unsigned, U1024}; #[test] fn test_random_bytes() { let mut cs_rng = CsRng::default(); - type N = U1024; + const N: usize = 1024; let random_bytes_1 = cs_rng.generate_random_bytes::(); - assert_eq!(::to_usize(), random_bytes_1.len()); + assert_eq!(N, random_bytes_1.len()); let random_bytes_2 = cs_rng.generate_random_bytes(); - assert_eq!(::to_usize(), random_bytes_1.len()); + assert_eq!(N, random_bytes_1.len()); assert_ne!(random_bytes_1, random_bytes_2); } } diff --git a/src/error.rs b/src/error.rs index 77525ec..8102da3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ use thiserror::Error; -/// Error type for this module. +/// Error type for this crate. #[derive(Debug, Error, PartialEq)] pub enum CryptoCoreError { #[error("Wrong size: {given} given should be {expected}")] diff --git a/src/kdf.rs b/src/kdf.rs deleted file mode 100644 index 577c2f8..0000000 --- a/src/kdf.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::CryptoCoreError; -use generic_array::{ArrayLength, GenericArray}; -use hkdf::Hkdf; -use sha2::Sha256; - -/// Derive a key of `KEY_LENGTH` bytes using a HMAC-based Extract-and-Expand -/// Key Derivation Function (HKDF) supplying a `bytes` and some `info` context -/// `String`. The hash function used is Sha256. -/// -/// - `bytes` : input bytes to hash, should be at least 32-bytes long -/// - `info` : some optional additional information to use in the hash -pub fn hkdf_256>( - bytes: &[u8], - info: &[u8], -) -> Result, CryptoCoreError> { - if bytes.len() < 32 { - return Err(CryptoCoreError::InvalidSize( - "Input `bytes` size should be at least 32".to_string(), - )); - } - let h = Hkdf::::new(None, bytes); - let mut out = GenericArray::::default(); - h.expand(info, &mut out) - .map_err(|_| CryptoCoreError::KdfError(KeyLength::to_usize()))?; - Ok(out) -} diff --git a/src/lib.rs b/src/lib.rs index 85a612f..b5d7df1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,29 +15,34 @@ mod error; pub mod asymmetric_crypto; pub mod entropy; -pub mod kdf; pub mod symmetric_crypto; +use rand_core::{CryptoRng, RngCore}; use zeroize::{Zeroize, ZeroizeOnDrop}; pub use crate::error::CryptoCoreError; pub mod reexport { - pub use generic_array; - // reexport `rand_core` so that the PRNG implement the correct version of the traits + // reexport `rand_core` so that the PRNGs implement the correct version of + // the traits pub use rand_core; } -/// Trait defining a cryptographic key. -pub trait KeyTrait: PartialEq + Eq + Send + Sync + Sized + Clone + Zeroize + ZeroizeOnDrop { - /// Number of bytes in the serialized key. - type Length: Eq + generic_array::ArrayLength; +/// Cryptographic key. +pub trait KeyTrait: + Clone + Eq + PartialEq + Send + Sized + Sync + Zeroize + ZeroizeOnDrop +{ + /// Key length + const LENGTH: usize = LENGTH; - /// Convert the given key into a vector of bytes. + /// Generates a new random key. #[must_use] - fn to_bytes(&self) -> generic_array::GenericArray; + fn new(rng: &mut R) -> Self; - /// Convert the given bytes into a key. An error is returned in case the - /// conversion fails. + /// Converts the given key into a vector of bytes. + #[must_use] + fn to_bytes(&self) -> [u8; LENGTH]; + + /// Tries to convert the given bytes into a key. fn try_from_bytes(bytes: &[u8]) -> Result; } diff --git a/src/symmetric_crypto/aes_256_gcm_pure/dem.rs b/src/symmetric_crypto/aes_256_gcm_pure/dem.rs deleted file mode 100644 index 519850b..0000000 --- a/src/symmetric_crypto/aes_256_gcm_pure/dem.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Implement the `DEM` trait for `Aes256GcmCrypto`. - -use crate::{ - symmetric_crypto::{ - aes_256_gcm_pure::Aes256GcmCrypto, nonce::NonceTrait, Dem, SymmetricCrypto, - }, - CryptoCoreError, KeyTrait, -}; -use generic_array::typenum::Unsigned; -use rand_core::{CryptoRng, RngCore}; -use std::convert::TryFrom; - -impl Dem for Aes256GcmCrypto { - fn encaps( - rng: &mut R, - secret_key: &[u8], - additional_data: Option<&[u8]>, - message: &[u8], - ) -> Result, CryptoCoreError> { - let key_length = <::Length as Unsigned>::to_usize(); - if secret_key.len() < key_length { - return Err(CryptoCoreError::SizeError { - given: secret_key.len(), - expected: key_length, - }); - } - // AES GCM includes an authentication method - // there is no need for parsing a MAC key - let key = Self::Key::try_from(&secret_key[..key_length])?; - let nonce = Self::Nonce::new(rng); - let mut c = Self::encrypt(&key, message, &nonce, additional_data) - .map_err(|err| CryptoCoreError::EncryptionError(err.to_string()))?; - // allocate correct byte number - let mut res: Vec = Vec::with_capacity(message.len() + Self::ENCAPSULATION_OVERHEAD); - res.append(&mut nonce.into()); - res.append(&mut c); - Ok(res) - } - - fn decaps( - secret_key: &[u8], - additional_data: Option<&[u8]>, - encapsulation: &[u8], - ) -> Result, CryptoCoreError> { - let key_length = <::Length as Unsigned>::to_usize(); - if secret_key.len() < key_length { - return Err(CryptoCoreError::SizeError { - given: secret_key.len(), - expected: key_length, - }); - } - // AES GCM includes an authentication method - // there is no need for parsing a MAC key - let key = Self::Key::try_from(&secret_key[..key_length])?; - let nonce = Self::Nonce::try_from(&encapsulation[..Self::Nonce::LENGTH])?; - Self::decrypt( - &key, - &encapsulation[Self::Nonce::LENGTH..], - &nonce, - additional_data, - ) - .map_err(|err| CryptoCoreError::EncryptionError(err.to_string())) - } -} - -#[cfg(test)] -mod tests { - - use crate::{ - entropy::CsRng, - symmetric_crypto::{aes_256_gcm_pure::Aes256GcmCrypto, Dem}, - CryptoCoreError, - }; - use generic_array::typenum::U256; - - #[test] - fn test_dem() -> Result<(), CryptoCoreError> { - let m = b"my secret message"; - let additional_data = Some(b"public tag".as_slice()); - let mut rng = CsRng::new(); - let secret_key = rng.generate_random_bytes::(); - let c = Aes256GcmCrypto::encaps(&mut rng, &secret_key, additional_data, m)?; - let res = Aes256GcmCrypto::decaps(&secret_key, additional_data, &c)?; - if res != m { - return Err(CryptoCoreError::DecryptionError( - "Decaps failed".to_string(), - )); - } - Ok(()) - } -} diff --git a/src/symmetric_crypto/aes_256_gcm_pure/mod.rs b/src/symmetric_crypto/aes_256_gcm_pure/mod.rs index d0a4a0f..6c2a643 100644 --- a/src/symmetric_crypto/aes_256_gcm_pure/mod.rs +++ b/src/symmetric_crypto/aes_256_gcm_pure/mod.rs @@ -3,73 +3,97 @@ //! //! It will use the AES native interface on the CPU if available. use crate::{ - symmetric_crypto::{nonce::NonceTrait, SymmetricCrypto}, + symmetric_crypto::{nonce::NonceTrait, Dem, SymKey}, CryptoCoreError, }; -use aes::cipher::Unsigned; use aes_gcm::{ - aead::{Aead, NewAead, Payload}, - AeadInPlace, Aes256Gcm, + aead::{Aead, Payload}, + aes::cipher::generic_array::GenericArray, + AeadInPlace, Aes256Gcm, KeyInit, }; -use generic_array::{typenum, GenericArray}; -use std::fmt::Display; +use rand_core::{CryptoRng, RngCore}; -pub mod dem; +use super::{key::Key, nonce::Nonce}; -/// Use a 256-bit AES key -pub type KeyLength = typenum::U32; +/// Use a 256-bit AES key. +const KEY_LENGTH: usize = 32; -/// Use a 96-bit nonce -pub const NONCE_LENGTH: usize = 12; +/// Use a 96-bit nonce. +const NONCE_LENGTH: usize = 12; -/// Use a 128-bit MAC tag -pub const MAC_LENGTH: usize = 16; +/// Use a 128-bit MAC tag. +const MAC_LENGTH: usize = 16; -pub type Key = crate::symmetric_crypto::key::Key; - -pub type Nonce = crate::symmetric_crypto::nonce::Nonce; +/// Plaintext size (in bytes) restriction from the NIST +/// https://csrc.nist.gov/publications/detail/sp/800-38d/final +const MAX_PLAINTEXT_LENGTH: u64 = 68_719_476_704; // (2 ^ 39 - 256) / 8 /// Structure implementing `SymmetricCrypto` and the `DEM` interfaces based on /// AES 256 GCM. +#[derive(Debug, PartialEq, Eq)] pub struct Aes256GcmCrypto; -impl Display for Aes256GcmCrypto { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", Self::description()) - } -} - -impl SymmetricCrypto for Aes256GcmCrypto { - type Key = Key; - type Nonce = Nonce; +impl Dem for Aes256GcmCrypto { + const ENCRYPTION_OVERHEAD: usize = Self::Nonce::LENGTH + Self::MAC_LENGTH; const MAC_LENGTH: usize = MAC_LENGTH; - fn description() -> String { - format!( - "AES 256 GCM pure Rust (key bits: {}, nonce bits: {}, tag bits: {})", - KeyLength::to_usize() * 8, - NONCE_LENGTH * 8, - MAC_LENGTH * 8 - ) - } + type Key = Key; - fn encrypt( - key: &Self::Key, - bytes: &[u8], - nonce: &Self::Nonce, + type Nonce = Nonce; + + fn encrypt( + rng: &mut R, + secret_key: &Self::Key, + plaintext: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoCoreError> { - encrypt_combined(key, bytes, nonce, additional_data) + if plaintext.len() as u64 > MAX_PLAINTEXT_LENGTH { + return Err(CryptoCoreError::InvalidSize(format!( + "Plaintext is too large ({} bytes), max size: {} ", + plaintext.len(), + MAX_PLAINTEXT_LENGTH + ))); + } + let nonce = Self::Nonce::new(rng); + // allocate correct byte number + let mut res: Vec = Vec::with_capacity(plaintext.len() + Self::ENCRYPTION_OVERHEAD); + res.extend_from_slice(nonce.as_bytes()); + res.append(&mut encrypt_combined( + secret_key.as_bytes(), + plaintext, + nonce.as_bytes(), + additional_data, + )?); + Ok(res) } fn decrypt( - key: &Self::Key, - bytes: &[u8], - nonce: &Self::Nonce, + secret_key: &Self::Key, + ciphertext: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoCoreError> { - decrypt_combined(key, bytes, nonce, additional_data) + if ciphertext.len() < Self::ENCRYPTION_OVERHEAD { + return Err(CryptoCoreError::InvalidSize(format!( + "Ciphertext is too small ({} bytes), min size: {}", + ciphertext.len(), + Self::ENCRYPTION_OVERHEAD + ))); + } + if ciphertext.len() as u64 > MAX_PLAINTEXT_LENGTH + Self::ENCRYPTION_OVERHEAD as u64 { + return Err(CryptoCoreError::InvalidSize(format!( + "Ciphertext is too large ({} bytes), max size: {} ", + ciphertext.len(), + MAX_PLAINTEXT_LENGTH + Self::ENCRYPTION_OVERHEAD as u64 + ))); + } + // The ciphertext is of the form: nonce || AEAD ciphertext + decrypt_combined( + secret_key.as_bytes(), + &ciphertext[Self::Nonce::LENGTH..], + &ciphertext[..Self::Nonce::LENGTH], + additional_data, + ) } } @@ -79,15 +103,15 @@ impl SymmetricCrypto for Aes256GcmCrypto { /// /// The total length of the encrypted data is the message length + `MAC_LENGTH` pub fn encrypt_combined( - key: &Key, + key: &[u8], bytes: &[u8], - nonce: &Nonce, + nonce: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoCoreError> { let payload = additional_data.map_or_else(|| Payload::from(bytes), |aad| Payload { msg: bytes, aad }); - Aes256Gcm::new(key) - .encrypt(GenericArray::from_slice(nonce.as_slice()), payload) + Aes256Gcm::new(GenericArray::from_slice(key)) + .encrypt(GenericArray::from_slice(nonce), payload) .map_err(|e| CryptoCoreError::EncryptionError(e.to_string())) } @@ -97,14 +121,14 @@ pub fn encrypt_combined( /// /// The tag length is `MAC_LENGTH` pub fn encrypt_in_place_detached( - key: &Key, + key: &[u8], bytes: &mut [u8], - nonce: &Nonce, + nonce: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoCoreError> { - Aes256Gcm::new(key) + Aes256Gcm::new(GenericArray::from_slice(key)) .encrypt_in_place_detached( - GenericArray::from_slice(nonce.as_slice()), + GenericArray::from_slice(nonce), additional_data.unwrap_or_default(), bytes, ) @@ -119,14 +143,14 @@ pub fn encrypt_in_place_detached( /// /// Decryption will never be performed, even partially, before verification. pub fn decrypt_combined( - key: &Key, + key: &[u8], msg: &[u8], - nonce: &Nonce, + nonce: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoCoreError> { let payload = additional_data.map_or_else(|| Payload::from(msg), |aad| Payload { msg, aad }); - Aes256Gcm::new(key) - .decrypt(GenericArray::from_slice(nonce.as_slice()), payload) + Aes256Gcm::new(GenericArray::from_slice(key)) + .decrypt(GenericArray::from_slice(nonce), payload) .map_err(|e| CryptoCoreError::DecryptionError(e.to_string())) } @@ -138,15 +162,15 @@ pub fn decrypt_combined( /// /// Decryption will never be performed, even partially, before verification. pub fn decrypt_in_place_detached( - key: &Key, + key: &[u8], bytes: &mut [u8], tag: &[u8], - nonce: &Nonce, + nonce: &[u8], additional_data: Option<&[u8]>, ) -> Result<(), CryptoCoreError> { - Aes256Gcm::new(key) + Aes256Gcm::new(GenericArray::from_slice(key)) .decrypt_in_place_detached( - GenericArray::from_slice(nonce.as_slice()), + GenericArray::from_slice(nonce), additional_data.unwrap_or_default(), bytes, GenericArray::from_slice(tag), @@ -162,40 +186,42 @@ mod tests { symmetric_crypto::{ aes_256_gcm_pure::{ decrypt_combined, decrypt_in_place_detached, encrypt_combined, - encrypt_in_place_detached, Key, Nonce, MAC_LENGTH, + encrypt_in_place_detached, Aes256GcmCrypto, Nonce, KEY_LENGTH, MAC_LENGTH, + NONCE_LENGTH, }, + key::Key, nonce::NonceTrait, + Dem, SymKey, }, - CryptoCoreError, + CryptoCoreError, KeyTrait, }; - use generic_array::typenum::{U42, U8192}; #[test] fn test_encryption_decryption_combined() -> Result<(), CryptoCoreError> { let mut cs_rng = CsRng::new(); - let key = Key::new(&mut cs_rng); - let bytes = cs_rng.generate_random_bytes::(); - let iv = Nonce::new(&mut cs_rng); + let key = Key::::new(&mut cs_rng); + let bytes = cs_rng.generate_random_bytes::<8192>(); + let iv = Nonce::::new(&mut cs_rng); // no additional data - let encrypted_result = encrypt_combined(&key, &bytes, &iv, None)?; + let encrypted_result = encrypt_combined(&key, &bytes, iv.as_bytes(), None)?; assert_ne!(encrypted_result, bytes.to_vec()); assert_eq!(bytes.len() + MAC_LENGTH, encrypted_result.len()); - let recovered = decrypt_combined(&key, &encrypted_result, &iv, None)?; + let recovered = decrypt_combined(&key, &encrypted_result, iv.as_bytes(), None)?; assert_eq!(bytes.to_vec(), recovered); // additional data - let aad = cs_rng.generate_random_bytes::(); - let encrypted_result = encrypt_combined(&key, &bytes, &iv, Some(&aad))?; + let aad = cs_rng.generate_random_bytes::<42>(); + let encrypted_result = encrypt_combined(&key, &bytes, iv.as_bytes(), Some(&aad))?; assert_ne!(encrypted_result, bytes.to_vec()); assert_eq!(bytes.len() + MAC_LENGTH, encrypted_result.len()); - let recovered = decrypt_combined(&key, &encrypted_result, &iv, Some(&aad))?; + let recovered = decrypt_combined(&key, &encrypted_result, iv.as_bytes(), Some(&aad))?; assert_eq!(bytes.to_vec(), recovered); // data should not be recovered if the AAD is modified - let aad = cs_rng.generate_random_bytes::(); - let recovered = decrypt_combined(&key, &encrypted_result, &iv, Some(&aad)); + let aad = cs_rng.generate_random_bytes::<42>(); + let recovered = decrypt_combined(&key, &encrypted_result, iv.as_bytes(), Some(&aad)); assert_ne!(Ok(bytes.to_vec()), recovered); // data should not be recovered if the key is modified - let new_key = Key::new(&mut cs_rng); - let recovered = decrypt_combined(&new_key, &encrypted_result, &iv, Some(&aad)); + let new_key = Key::::new(&mut cs_rng); + let recovered = decrypt_combined(&new_key, &encrypted_result, iv.as_bytes(), Some(&aad)); assert_ne!(Ok(bytes.to_vec()), recovered); Ok(()) } @@ -203,26 +229,42 @@ mod tests { #[test] fn test_encryption_decryption_detached() -> Result<(), CryptoCoreError> { let mut cs_rng = CsRng::new(); - let key = Key::new(&mut cs_rng); - let bytes = cs_rng.generate_random_bytes::(); - let iv = Nonce::new(&mut cs_rng); + let key = Key::::new(&mut cs_rng); + let bytes = cs_rng.generate_random_bytes::<1024>(); + let iv = Nonce::::new(&mut cs_rng); // no additional data let mut data = bytes; - let tag = encrypt_in_place_detached(&key, &mut data, &iv, None)?; + let tag = encrypt_in_place_detached(&key, &mut data, iv.as_bytes(), None)?; assert_ne!(bytes, data); assert_eq!(bytes.len(), data.len()); assert_eq!(MAC_LENGTH, tag.len()); - decrypt_in_place_detached(&key, &mut data, &tag, &iv, None)?; + decrypt_in_place_detached(&key, &mut data, &tag, iv.as_bytes(), None)?; assert_eq!(bytes, data); // // additional data - let ad = cs_rng.generate_random_bytes::(); + let ad = cs_rng.generate_random_bytes::<42>(); let mut data = bytes; - let tag = encrypt_in_place_detached(&key, &mut data, &iv, Some(&ad))?; + let tag = encrypt_in_place_detached(&key, &mut data, iv.as_bytes(), Some(&ad))?; assert_ne!(bytes, data); assert_eq!(bytes.len(), data.len()); assert_eq!(MAC_LENGTH, tag.len()); - decrypt_in_place_detached(&key, &mut data, &tag, &iv, Some(&ad))?; + decrypt_in_place_detached(key.as_bytes(), &mut data, &tag, iv.as_bytes(), Some(&ad))?; assert_eq!(bytes, data); Ok(()) } + + #[test] + fn test_dem() -> Result<(), CryptoCoreError> { + let m = b"my secret message"; + let additional_data = Some(b"public tag".as_slice()); + let mut rng = CsRng::new(); + let secret_key = Key::new(&mut rng); + let c = Aes256GcmCrypto::encrypt(&mut rng, &secret_key, m, additional_data)?; + let res = Aes256GcmCrypto::decrypt(&secret_key, &c, additional_data)?; + if res != m { + return Err(CryptoCoreError::DecryptionError( + "Decaps failed".to_string(), + )); + } + Ok(()) + } } diff --git a/src/symmetric_crypto/block.rs b/src/symmetric_crypto/block.rs deleted file mode 100644 index bfb43f5..0000000 --- a/src/symmetric_crypto/block.rs +++ /dev/null @@ -1,305 +0,0 @@ -use crate::{ - symmetric_crypto::{nonce::NonceTrait, SymmetricCrypto}, - CryptoCoreError, -}; -use rand_core::{CryptoRng, RngCore}; - -/// Block holding clear text data that needs to be encrypted. -/// -/// The max fixed length of clear text is `MAX_CLEAR_TEXT_LENGTH`. -/// The max block encrypted length is `Block::MAX_ENCRYPTED_LENGTH`. -/// -/// When calling `to_encrypted_bytes(...)` an array of bytes is generated that -/// is made of a `BlockHeader` containing the nonce, the cipher text and - -/// depending on the symmetric scheme - an authentication MAC. The nonce is -/// refreshed on each call to `to_encrypted_bytes(...)` -pub struct Block -where - S: SymmetricCrypto, -{ - clear_text: Vec, //padded with zeroes if need be - phantom_data: std::marker::PhantomData, -} - -impl Block -where - S: SymmetricCrypto, -{ - /// Number of bytes added to the size of the input data in the output ciphertext. - pub const ENCRYPTION_OVERHEAD: usize = - BlockHeader::::LENGTH + ::MAC_LENGTH; - - /// Maximum number of bytes for an output ciphertext. - pub const MAX_ENCRYPTED_LENGTH: usize = MAX_CLEAR_TEXT_LENGTH + Self::ENCRYPTION_OVERHEAD; - - /// Creates a new empty block - #[must_use] - pub fn new() -> Self { - Self { - clear_text: vec![], - phantom_data: std::marker::PhantomData::default(), - } - } - - /// Parses a block of encrypted data to a `Block`. - /// The resource `uid` and `block_number` are part of the - /// authentication scheme amd must be re-supplied with the - /// same values used to encrypt the block - pub fn from_encrypted_bytes( - encrypted_bytes: &[u8], - symmetric_key: &::Key, - uid: &[u8], - block_number: usize, - ) -> Result { - // The block header is always present - if encrypted_bytes.len() < Self::ENCRYPTION_OVERHEAD { - return Err(CryptoCoreError::InvalidSize(format!( - "array of encrypted data bytes of length {} is too small", - encrypted_bytes.len(), - ))); - } - if encrypted_bytes.len() > Self::MAX_ENCRYPTED_LENGTH { - return Err(CryptoCoreError::InvalidSize(format!( - "array of encrypted data bytes of length {} is too large", - encrypted_bytes.len(), - ))); - } - let block_header_len = BlockHeader::::LENGTH; - // recover the block header and regenerate the IV - let block_header = BlockHeader::::parse(&encrypted_bytes[0..block_header_len])?; - let mut ad = uid.to_vec(); - // Warning: `usize` can be interpreted as `u32` on 32-bits CPU-architecture. - // The `u64`-cast prevents build on those 32-bits machine or on - // `wasm32-unknown-unknown` builds. - ad.extend(&(block_number as u64).to_le_bytes()); - // decrypt - let clear_text = S::decrypt( - symmetric_key, - &encrypted_bytes[block_header_len..], - &block_header.nonce, - Some(&ad), - )?; - Ok(Self { - clear_text, - phantom_data: std::marker::PhantomData::default(), - }) - } - - /// Generates the encrypted data block. The nonce is refreshed on each - /// call. The resource `uid` and `block_number` are part of the AEAD and - /// must be re-supplied to decrypt the bytes. They are used to guarantee - /// that a block cannot be moved within and between resources - pub fn to_encrypted_bytes( - &self, - rng: &mut R, - symmetric_key: &::Key, - uid: &[u8], - block_number: usize, - ) -> Result, CryptoCoreError> { - // refresh the nonce - let nonce = S::Nonce::new(rng); - - // get the associated data ready - let mut ad = Vec::with_capacity(uid.len() + 8); - ad.extend(uid); - // Warning: `usize` can be interpreted as `u32` on 32-bits CPU-architecture. - // The `u64`-cast prevents build on those 32-bits machine or on - // `wasm32-unknown-unknown` builds. - ad.extend(&(block_number as u64).to_le_bytes()); - - // allocate correct number of bytes - let mut bytes = Vec::with_capacity( - S::Nonce::LENGTH + S::MAC_LENGTH + self.clear_text().len() + ad.len(), - ); - // write the header - bytes.append( - &mut BlockHeader:: { - nonce: nonce.clone(), - } - .to_bytes(), - ); - // write encrypted data - bytes.extend(S::encrypt( - symmetric_key, - &self.clear_text, - &nonce, - Some(&ad), - )?); - Ok(bytes) - } - - /// Returns a reference to the clear text - #[must_use] - pub fn clear_text(&self) -> &[u8] { - &self.clear_text - } - - /// Moves clear text data out of the block - #[must_use] - pub fn clear_text_owned(self) -> Vec { - self.clear_text - } - - /// Writes the given clear text data in the block. Pad the block with - /// zeroes if the offset is beyond the current end of the block. - /// - /// Return the length of the data written - pub fn write(&mut self, start_offset: usize, data: &[u8]) -> Result { - if start_offset >= MAX_CLEAR_TEXT_LENGTH { - return Err(CryptoCoreError::InvalidSize(format!( - "write in block: start offset: {} is greater than max block clear text len {}", - start_offset, MAX_CLEAR_TEXT_LENGTH - ))); - } - // pad if need be - let num_to_pad = start_offset - self.clear_text.len(); - if num_to_pad > 0 { - self.clear_text.append(&mut vec![0; num_to_pad]); - } - // see what space is available - let space_left = MAX_CLEAR_TEXT_LENGTH - start_offset; - if space_left == 0 { - return Ok(0); - } - if data.len() <= space_left { - self.clear_text.extend_from_slice(data); - return Ok(data.len()); - } - self.clear_text.extend_from_slice(&data[0..space_left]); - Ok(space_left) - } -} - -impl Default for Block -where - S: SymmetricCrypto, -{ - fn default() -> Self { - Self::new() - } -} - -/// The `BlockHeader` contains the nonce/IV of an encrypted `Block` -pub struct BlockHeader -where - S: SymmetricCrypto, -{ - nonce: ::Nonce, -} - -impl BlockHeader -where - S: SymmetricCrypto, -{ - pub const LENGTH: usize = ::Nonce::LENGTH; - - pub fn parse(bytes: &[u8]) -> Result { - //TODO: use transmute to make this faster ? - Ok(Self { - nonce: <::Nonce>::try_from_bytes(bytes)?, - }) - } - - /// Convert the block header into a slice of bytes. - pub fn as_bytes(&self) -> &[u8] { - self.nonce.as_slice() - } - - /// Convert the block header into a vector of bytes. - pub fn to_bytes(&self) -> Vec { - self.as_bytes().to_vec() - } -} - -#[cfg(test)] -mod tests { - - use super::Block; - use crate::{ - entropy::CsRng, - symmetric_crypto::aes_256_gcm_pure::{Aes256GcmCrypto, Key}, - }; - use generic_array::typenum::{U100, U16384}; - - const MAX_CLEAR_TEXT_LENGTH: usize = 4096; - type Bl = Block; - - #[test] - fn test_empty_block() { - let b = Bl::new(); - assert!(b.clear_text().is_empty()); - - let mut cs_rng = CsRng::new(); - let symmetric_key = Key::new(&mut cs_rng); - let uid = [1_u8; 32]; - // let iv = cs_rng.generate_nonce(); - let encrypted_bytes = b - .to_encrypted_bytes(&mut cs_rng, &symmetric_key, &uid, 1) - .expect("failed to encrypted bytes"); - assert_eq!(Bl::ENCRYPTION_OVERHEAD, encrypted_bytes.len()); - let c = Bl::from_encrypted_bytes(&encrypted_bytes, &symmetric_key, &uid, 1) - .expect("failed from encrypted bytes"); - assert!(c.clear_text().is_empty()); - } - - #[test] - fn test_full_block() { - let mut cs_rng = CsRng::new(); - let symmetric_key = Key::new(&mut cs_rng); - let uid = [1_u8; 32]; - - let mut b = Bl::new(); - assert!(b.clear_text().is_empty()); - let data = cs_rng.generate_random_bytes::(); - let written = b.write(0, &data).expect("failed writing data"); - assert_eq!(MAX_CLEAR_TEXT_LENGTH, written); - - // let iv = cs_rng.generate_nonce(); - let encrypted_bytes = b - .to_encrypted_bytes(&mut cs_rng, &symmetric_key, &uid, 1) - .expect("failed to encrypted bytes"); - assert_eq!( - Bl::ENCRYPTION_OVERHEAD + MAX_CLEAR_TEXT_LENGTH, - encrypted_bytes.len() - ); - assert_eq!( - Bl::ENCRYPTION_OVERHEAD + MAX_CLEAR_TEXT_LENGTH, - Bl::MAX_ENCRYPTED_LENGTH - ); - let c = Bl::from_encrypted_bytes(&encrypted_bytes, &symmetric_key, &uid, 1) - .expect("failed from encrypted bytes"); - assert_eq!(&data[0..MAX_CLEAR_TEXT_LENGTH], c.clear_text()); - } - - #[test] - fn test_partial_block() { - let mut cs_rng = CsRng::new(); - let symmetric_key = Key::new(&mut cs_rng); - let uid = [1_u8; 32]; - - let mut b = Bl::new(); - assert!(b.clear_text().is_empty()); - - let data1 = cs_rng.generate_random_bytes::(); - let written = b.write(0, &data1).expect("failed writing data"); - assert_eq!(100, written); - - let data2 = cs_rng.generate_random_bytes::(); - let written = b.write(200, &data2).expect("failed writing data"); - assert_eq!(100, written); - - // let iv = cs_rng.generate_nonce(); - let encrypted_bytes = b - .to_encrypted_bytes(&mut cs_rng, &symmetric_key, &uid, 1) - .expect("failed to encrypted bytes"); - assert_eq!(300 + Bl::ENCRYPTION_OVERHEAD, encrypted_bytes.len()); - let c = Bl::from_encrypted_bytes(&encrypted_bytes, &symmetric_key, &uid, 1) - .expect("failed from encrypted bytes"); - let mut data: Vec = vec![]; - data.extend(&data1); - data.extend(&[0_u8; 100]); - data.extend(&data2); - - assert_eq!(&data, c.clear_text()); - } -} diff --git a/src/symmetric_crypto/key.rs b/src/symmetric_crypto/key.rs index 8ef84e0..b66e3ca 100644 --- a/src/symmetric_crypto/key.rs +++ b/src/symmetric_crypto/key.rs @@ -1,101 +1,83 @@ //! Define a symmetric key object of variable size. use crate::{symmetric_crypto::SymKey, CryptoCoreError, KeyTrait}; -use aes::cipher::generic_array::{ArrayLength, GenericArray}; +use core::{convert::TryFrom, fmt::Display, hash::Hash, ops::Deref}; use rand_core::{CryptoRng, RngCore}; -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, fmt::Display, ops::Deref}; use zeroize::{Zeroize, ZeroizeOnDrop}; /// Symmetric key of a given size. /// /// It is internally built using an array of bytes of the given length. -#[derive(Debug, Default, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Key>(GenericArray); - -impl> Key { - /// Generate a new symmetric random `Key` - pub fn new(rng: &mut R) -> Self { - let mut key = Self(GenericArray::::default()); - rng.fill_bytes(&mut key.0); - key +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub struct Key([u8; LENGTH]); + +impl KeyTrait for Key { + /// Generates a new symmetric random `Key`. + #[inline] + fn new(rng: &mut R) -> Self { + let mut key = [0; LENGTH]; + rng.fill_bytes(&mut key); + Self(key) } -} - -impl> KeyTrait for Key { - type Length = KeyLength; - /// Convert the given key into bytes, with copy. - fn to_bytes(&self) -> generic_array::GenericArray { - self.0.clone() + /// Converts the given key into bytes. + #[inline] + fn to_bytes(&self) -> [u8; LENGTH] { + self.0.to_owned() } - /// Try to convert the given bytes into a key. Size must be correct. + /// Tries to convert the given bytes into a key. Size must be correct. + #[inline] fn try_from_bytes(bytes: &[u8]) -> Result { - Self::try_from(bytes) + let bytes = <[u8; LENGTH]>::try_from(bytes) + .map_err(|e| CryptoCoreError::ConversionError(e.to_string()))?; + Ok(Self(bytes)) } } -impl> SymKey for Key { - /// Convert the given key into a byte slice, without copy. +impl SymKey for Key { + /// Converts the given key into a byte slice. + #[inline] fn as_bytes(&self) -> &[u8] { &self.0 } - fn from_bytes(bytes: GenericArray) -> Self { - Self(bytes) - } -} - -impl> From> for GenericArray { - fn from(k: Key) -> Self { - k.to_bytes() - } -} - -impl> From> for Key { - fn from(b: GenericArray) -> Self { - Self(b) + /// Consumes the key to return underlying bytes. + #[inline] + fn into_bytes(self) -> [u8; LENGTH] { + self.0 } -} -impl<'a, KeyLength: ArrayLength> TryFrom<&'a [u8]> for Key { - type Error = CryptoCoreError; - - fn try_from(bytes: &'a [u8]) -> Result { - if bytes.len() != KeyLength::to_usize() { - return Err(Self::Error::SizeError { - given: bytes.len(), - expected: KeyLength::to_usize(), - }); - } - Ok(Self(GenericArray::::clone_from_slice(bytes))) + /// Converts the given bytes with correct size into a key. + #[inline] + fn from_bytes(bytes: [u8; LENGTH]) -> Self { + Self(bytes) } } -impl> Display for Key { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(self.as_slice())) +impl Display for Key { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", hex::encode(self.as_bytes())) } } -impl> Zeroize for Key { +impl Zeroize for Key { fn zeroize(&mut self) { self.0.zeroize(); } } -// Implement `Drop` trait to follow R23. -impl> Drop for Key { +// Implements `Drop` trait to follow R23. +impl Drop for Key { fn drop(&mut self) { self.zeroize(); } } -impl> ZeroizeOnDrop for Key {} +impl ZeroizeOnDrop for Key {} -impl> Deref for Key { - type Target = GenericArray; +impl Deref for Key { + type Target = [u8]; fn deref(&self) -> &Self::Target { &self.0 @@ -106,24 +88,16 @@ impl> Deref for Key { mod tests { use crate::{entropy::CsRng, symmetric_crypto::key::Key, KeyTrait}; - use generic_array::typenum::{ToInt, U32}; + + const KEY_LENGTH: usize = 32; #[test] fn test_key() { let mut cs_rng = CsRng::new(); - let key_1 = Key::::new(&mut cs_rng); - assert_eq!(>::to_int(), key_1.len()); + let key_1 = Key::::new(&mut cs_rng); + assert_eq!(KEY_LENGTH, key_1.len()); let key_2 = Key::new(&mut cs_rng); - assert_eq!(>::to_int(), key_2.len()); + assert_eq!(KEY_LENGTH, key_2.len()); assert_ne!(key_1, key_2); } - - #[test] - fn test_key_serialization() { - let mut cs_rng = CsRng::new(); - let key = Key::::new(&mut cs_rng); - let bytes = key.to_bytes(); - let res = Key::try_from(bytes).unwrap(); - assert_eq!(key, res); - } } diff --git a/src/symmetric_crypto/metadata.rs b/src/symmetric_crypto/metadata.rs index 81e7082..4412da6 100644 --- a/src/symmetric_crypto/metadata.rs +++ b/src/symmetric_crypto/metadata.rs @@ -1,6 +1,6 @@ use crate::CryptoCoreError; +use core::convert::TryInto; use serde::{Deserialize, Serialize}; -use std::convert::TryInto; /// Attempts to get the length of this slice as an `u32` in 4 endian bytes and /// returns an error if it overflows @@ -23,12 +23,13 @@ pub struct BytesScanner<'a> { impl<'a> BytesScanner<'a> { /// Returns a new byte scanner object. #[must_use] + #[inline] pub const fn new(bytes: &'a [u8]) -> Self { BytesScanner { bytes, start: 0 } } /// Returns a slice of the next `size` bytes or an error if less is - /// available + /// available. pub fn next(&mut self, size: usize) -> Result<&'a [u8], CryptoCoreError> { let end = self.start + size; if self.bytes.len() < end { @@ -42,7 +43,7 @@ impl<'a> BytesScanner<'a> { Ok(chunk) } - /// Reads the next 4 big endian bytes to return an u32 + /// Reads the next 4 big endian bytes to return an u32. pub fn read_u32(&mut self) -> Result { Ok(u32::from_be_bytes(self.next(4)?.try_into().map_err( |_e| CryptoCoreError::ConversionError("invalid u32".to_string()), @@ -60,23 +61,24 @@ impl<'a> BytesScanner<'a> { } } - /// Return `true` if there still are some bytes to read. + /// Returns `true` if there still are some bytes to read. pub fn has_more(&self) -> bool { self.start < self.bytes.len() } } -/// Metadata encrypted as part of the header +/// Metadata encrypted as part of the header. /// /// The `uid` is a security parameter: -/// - when using a stream cipher such as AES, it uniquely -/// identifies a resource, such as a file, and is part of the AEAD of every -/// block when symmetrically encrypting data. It prevents an attacker from -/// moving blocks between resources. -/// - when using FPE, it is the "tweak" ([see Appendix C](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38G.pdf)) +/// - when using a stream cipher such as AES, it uniquely identifies a +/// resource, such as a file, and is part of the AEAD of every block when +/// symmetrically encrypting data. It prevents an attacker from moving +/// blocks between resources. +/// - when using FPE, it is the "tweak" +/// ([see Appendix C](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38G.pdf)) /// -/// The `additional_data` is not used as a security parameter. It is optional -/// data (such as index tags) symmetrically encrypted as part of the header. +/// The `additional_data` is *not* used as a security parameter, it is an +/// optional data (such as index tags). #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] pub struct Metadata { pub uid: Vec, @@ -84,7 +86,7 @@ pub struct Metadata { } impl Metadata { - /// The length in bytes of this meta data + /// Returns the length in bytes of this metadata. pub fn len(&self) -> usize { self.uid.len() + self.additional_data.as_ref().unwrap_or(&vec![]).len() } @@ -95,23 +97,30 @@ impl Metadata { self.len() == 0 } - /// Encode the metadata as a byte array + /// Encodes the metadata as a byte array. /// /// The first 4 bytes is the `u32` length of the UID as big endian bytes + /// + /// TODO: when LEB128 parsing is moved from CoverCrypt to an independent + /// crate, we can implement `read()` and `write()` for this object. This + /// will allows reducing the size of the serialized length saving up to 3 + /// bytes when the UID is small. pub fn try_to_bytes(&self) -> Result, CryptoCoreError> { if self.is_empty() { return Ok(vec![]); } let mut bytes = Vec::with_capacity(4 + self.len()); bytes.append(&mut get_u32_len(&self.uid)?.to_vec()); - bytes.extend(&self.uid); + bytes.extend_from_slice(&self.uid); if let Some(ad) = &self.additional_data { - bytes.extend(ad); + bytes.extend_from_slice(ad); } Ok(bytes) } - /// Decode the metadata from a byte array + /// Decodes the metadata from a byte array. + /// + /// TODO: see `try_to_bytes()` documentation. pub fn try_from_bytes(bytes: &[u8]) -> Result { if bytes.is_empty() { return Ok(Self::default()); diff --git a/src/symmetric_crypto/mod.rs b/src/symmetric_crypto/mod.rs index 9833e48..bccbba2 100644 --- a/src/symmetric_crypto/mod.rs +++ b/src/symmetric_crypto/mod.rs @@ -6,113 +6,73 @@ pub mod aes_256_gcm_pure; pub mod key; pub mod nonce; -mod block; mod metadata; -pub use block::Block; pub use metadata::BytesScanner; pub use metadata::Metadata; use crate::{CryptoCoreError, KeyTrait}; -use generic_array::GenericArray; +use core::{fmt::Debug, hash::Hash}; use nonce::NonceTrait; use rand_core::{CryptoRng, RngCore}; use std::vec::Vec; /// Defines a symmetric encryption key. -pub trait SymKey: KeyTrait { - /// Convert the given key into a byte slice. +pub trait SymKey: KeyTrait + Hash { + /// Converts the given key into a byte slice. + #[must_use] fn as_bytes(&self) -> &[u8]; - /// Convert the given bytes into a key. + /// Consumes the key to return the underlying bytes. #[must_use] - fn from_bytes(bytes: GenericArray) -> Self; -} - -/// Defines a symmetric encryption scheme. If this scheme is authenticated, -/// the `MAC_LENGTH` will be greater than `0`. -pub trait SymmetricCrypto: Send + Sync { - const MAC_LENGTH: usize; - type Key: SymKey; - type Nonce: NonceTrait; - - /// A short description of the scheme - fn description() -> String; - - /// Encrypts a message using a secret key and a public nonce in combined - /// mode. - /// - /// Append the MAC tag authenticating both the confidential and non - /// confidential data (aad) to the ciphertext. Thus, the length of the - /// encrypted data is the message length + `MAC_LENGTH`. - /// - /// It can also be used as a pur MAC by providing an empty message. - /// - /// # Parameters - /// - /// - `key` : symmetric key to use for encryption - /// - `bytes` : message bytes to encrypt - /// - `nonce` : nonce to use - /// - `aad` : additional associated data, the same data must be used - /// for decryption - fn encrypt( - key: &Self::Key, - bytes: &[u8], - nonce: &Self::Nonce, - aad: Option<&[u8]>, - ) -> Result, CryptoCoreError>; + fn into_bytes(self) -> [u8; LENGTH]; - /// Decrypts a message in combined mode. - /// - /// Attempts to read a MAC appended to the ciphertext. The provided - /// additional data must match those provided during encryption for the - /// MAC to verify. Decryption will never be performed, even partially, - /// before verification. - /// - /// # Parameters - /// - /// - `key` : symmetric key to use - /// - `bytes` : encrypted bytes to decrypt (the MAC must be appended) - /// - `nonce` : nonce to use for decryption - /// - `aad` : additional associated data, the same data must be used - /// for encryption - fn decrypt( - key: &Self::Key, - bytes: &[u8], - nonce: &Self::Nonce, - aad: Option<&[u8]>, - ) -> Result, CryptoCoreError>; + /// Converts the given bytes into a key. + #[must_use] + fn from_bytes(bytes: [u8; LENGTH]) -> Self; } /// Defines a DEM based on a symmetric scheme as defined in section 9.1 of the /// [ISO 2004](https://www.shoup.net/iso/std6.pdf). -pub trait Dem: SymmetricCrypto { +pub trait Dem: Debug + PartialEq { /// Number of bytes added to the message length in the encapsulation. - const ENCAPSULATION_OVERHEAD: usize = Self::Nonce::LENGTH + Self::MAC_LENGTH; + const ENCRYPTION_OVERHEAD: usize = Self::Nonce::LENGTH + Self::MAC_LENGTH; + + /// MAC tag length + const MAC_LENGTH: usize; + + /// Symmetric key length + const KEY_LENGTH: usize = KEY_LENGTH; + + /// Associated nonce type + type Nonce: NonceTrait; - /// Encapsulate data using the given symmetric key. + /// Associated key type + type Key: SymKey; + + /// Encrypts data using the given symmetric key. /// /// - `rng` : secure random number generator /// - `secret_key` : secret symmetric key - /// - `message` : message to encapsulate + /// - `plaintext` : plaintext message /// - `aad` : optional data to use in the authentication method, /// must use the same for decryption - fn encaps( + fn encrypt( rng: &mut R, - secret_key: &[u8], + secret_key: &Self::Key, + plaintext: &[u8], aad: Option<&[u8]>, - message: &[u8], ) -> Result, CryptoCoreError>; - /// Decapsulate data using the given symmetric key. + /// Decrypts data using the given symmetric key. /// - /// - `secret_key` : secret symmetric key - /// - `encapsulation` : message encapsulation - /// - `aad` : optional data to use in the authentication method, + /// - `secret_key` : symmetric key + /// - `ciphertext` : ciphertext message + /// - `aad` : optional data to use in the authentication method, /// must use the same for encyption - fn decaps( - secret_key: &[u8], + fn decrypt( + secret_key: &Self::Key, + ciphertext: &[u8], aad: Option<&[u8]>, - encapsulation: &[u8], ) -> Result, CryptoCoreError>; } diff --git a/src/symmetric_crypto/nonce.rs b/src/symmetric_crypto/nonce.rs index 29a9d6c..4941394 100644 --- a/src/symmetric_crypto/nonce.rs +++ b/src/symmetric_crypto/nonce.rs @@ -1,41 +1,34 @@ -//! Define a nonce object, for use in symmetric encryption. +//! Defines a nonce object, for use in symmetric encryption. //! //! A nonce, for Number used ONCE, is a randomly generated number used to //! ensure a ciphertext cannot be reused, hence avoiding replay attacks. use crate::CryptoCoreError; -use num_bigint::BigUint; -use rand_core::{CryptoRng, RngCore}; -use std::{ - cmp::min, +use core::{ convert::{TryFrom, TryInto}, fmt::{Debug, Display}, - vec::Vec, }; +use rand_core::{CryptoRng, RngCore}; -/// Trait defining a nonce for use in a symmetric encryption scheme. +/// Defines a nonce to use in a symmetric encryption scheme. pub trait NonceTrait: Send + Sync + Sized + Clone { /// Size of the nonce in bytes. const LENGTH: usize; - /// Generate a new nonce object. + /// Generates a new nonce object. #[must_use] fn new(rng: &mut R) -> Self; - /// Try to deserialize the given `bytes` into a nonce object. The number of - /// `bytes` must be equal to `Self::LENGTH`. + /// Tries to deserialize the given `bytes` into a nonce object. The number + /// of `bytes` must be equal to `Self::LENGTH`. fn try_from_bytes(bytes: &[u8]) -> Result; - /// Increment the nonce by the given value. - #[must_use] - fn increment(&self, increment: usize) -> Self; - /// Xor the nonce with the given value. #[must_use] fn xor(&self, b2: &[u8]) -> Self; - /// Serialize the nonce. - fn as_slice(&self) -> &[u8]; + /// Serializes the nonce. + fn as_bytes(&self) -> &[u8]; } /// Nonce object of the given size. @@ -47,12 +40,14 @@ pub struct Nonce([u8; NONCE_LENGTH]); impl NonceTrait for Nonce { const LENGTH: usize = NONCE_LENGTH; + #[inline] fn new(rng: &mut R) -> Self { - let mut bytes = [0_u8; NONCE_LENGTH]; + let mut bytes = [0; NONCE_LENGTH]; rng.fill_bytes(&mut bytes); Self(bytes) } + #[inline] fn try_from_bytes(bytes: &[u8]) -> Result { let b: [u8; NONCE_LENGTH] = bytes.try_into().map_err(|_| CryptoCoreError::SizeError { given: bytes.len(), @@ -61,35 +56,21 @@ impl NonceTrait for Nonce { Ok(Self(b)) } - fn increment(&self, increment: usize) -> Self { - let mut bi = BigUint::from_bytes_le(&self.0); - bi += BigUint::from(increment); - let mut bi_bytes = bi.to_bytes_le(); - bi_bytes.resize(NONCE_LENGTH, 0); - Self(bi_bytes.try_into().expect("This should never happen")) - } - + #[inline] fn xor(&self, b2: &[u8]) -> Self { let mut n = self.0; - for i in 0..min(b2.len(), NONCE_LENGTH) { - n[i] ^= b2[i]; + for (ni, bi) in n.iter_mut().zip(b2) { + *ni ^= bi } Self(n) } - fn as_slice(&self) -> &[u8] { + #[inline] + fn as_bytes(&self) -> &[u8] { &self.0 } } -impl TryFrom> for Nonce { - type Error = CryptoCoreError; - - fn try_from(bytes: Vec) -> Result { - Self::try_from_bytes(&bytes) - } -} - impl<'a, const NONCE_LENGTH: usize> TryFrom<&'a [u8]> for Nonce { type Error = CryptoCoreError; @@ -104,14 +85,8 @@ impl From<[u8; NONCE_LENGTH]> for Nonce } } -impl From> for Vec { - fn from(n: Nonce) -> Self { - n.0.to_vec() - } -} - impl Display for Nonce { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", hex::encode(self.0)) } } @@ -123,25 +98,15 @@ mod tests { symmetric_crypto::nonce::{Nonce, NonceTrait}, }; - const NONCE_LENGTH: usize = 128; + const NONCE_LENGTH: usize = 12; #[test] fn test_nonce() { let mut cs_rng = CsRng::new(); let nonce_1 = Nonce::::new(&mut cs_rng); - assert_eq!(NONCE_LENGTH, nonce_1.as_slice().len()); + assert_eq!(NONCE_LENGTH, nonce_1.as_bytes().len()); let nonce_2 = Nonce::::new(&mut cs_rng); - assert_eq!(NONCE_LENGTH, nonce_2.as_slice().len()); + assert_eq!(NONCE_LENGTH, nonce_2.as_bytes().len()); assert_ne!(nonce_1, nonce_2); } - - #[test] - fn test_increment_nonce() { - const NONCE_LENGTH: usize = 12; - let mut nonce: Nonce = Nonce::from([0_u8; NONCE_LENGTH]); - let inc = 1_usize << 10; - nonce = nonce.increment(inc); - println!("{}", hex::encode(nonce.0)); - assert_eq!("000400000000000000000000", hex::encode(nonce.0)); - } }