aboutsummaryrefslogtreecommitdiff
path: root/rust
diff options
context:
space:
mode:
authorNabil Wadih <nwadih@google.com>2023-05-12 09:30:10 -0700
committerBoringssl LUCI CQ <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-05-26 20:55:21 +0000
commitb0a026f8541c551854efd617021bb276f1fe5c23 (patch)
tree44f517b64f29cee60b79f1e1b515a580c0b676df /rust
parente30750c9f46058c8247ad1652fe3af917c162661 (diff)
downloadboringssl-b0a026f8541c551854efd617021bb276f1fe5c23.zip
boringssl-b0a026f8541c551854efd617021bb276f1fe5c23.tar.gz
boringssl-b0a026f8541c551854efd617021bb276f1fe5c23.tar.bz2
add rust bindings for ed25519
Change-Id: I7458b1d7aa1736d586dc80660d59c07fa2ac1c8a Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/59805 Reviewed-by: Bob Beck <bbe@google.com> Reviewed-by: David Benjamin <davidben@google.com> Commit-Queue: David Benjamin <davidben@google.com>
Diffstat (limited to 'rust')
-rw-r--r--rust/bssl-crypto/src/ed25519.rs219
-rw-r--r--rust/bssl-crypto/src/lib.rs7
2 files changed, 226 insertions, 0 deletions
diff --git a/rust/bssl-crypto/src/ed25519.rs b/rust/bssl-crypto/src/ed25519.rs
new file mode 100644
index 0000000..1fa5b05
--- /dev/null
+++ b/rust/bssl-crypto/src/ed25519.rs
@@ -0,0 +1,219 @@
+/* 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;
+
+/// The length in bytes of an Ed25519 public key.
+pub const PUBLIC_KEY_LENGTH: 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 =
+ (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;
+
+// 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;
+
+/// 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]);
+
+/// An Ed25519 public key used to verify a signature + message.
+pub struct PublicKey([u8; PUBLIC_KEY_LENGTH]);
+
+/// Error returned if the verification on the signature + message fails.
+#[derive(Debug)]
+pub struct SignatureError;
+
+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];
+
+ // 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()) }
+
+ 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] {
+ // 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",
+ )
+ }
+
+ /// Builds this 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];
+
+ // 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(),
+ )
+ }
+ PrivateKey(private_key)
+ }
+
+ /// Signs the given message and returns a digital signature.
+ pub fn sign(&self, msg: &[u8]) -> Signature {
+ let mut sig_bytes = [0u8; SIGNATURE_LENGTH];
+
+ // 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.as_ptr(),
+ msg.len(),
+ self.0.as_ptr(),
+ )
+ };
+ assert_eq!(result, 1, "allocation failure in bssl_sys::ED25519_sign");
+
+ Signature(sig_bytes)
+ }
+
+ /// Returns the PublicKey of the KeyPair.
+ pub fn 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..]
+ .try_into()
+ .expect("The slice is always the correct size for a public key"),
+ )
+ }
+}
+
+impl PublicKey {
+ /// Builds the public key from an array of bytes.
+ pub fn from_bytes(bytes: [u8; PUBLIC_KEY_LENGTH]) -> Self {
+ PublicKey(bytes)
+ }
+
+ /// Returns the bytes of the public key.
+ pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] {
+ 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);
+ let ret = unsafe {
+ bssl_sys::ED25519_verify(
+ message_cslice.as_ptr(),
+ message_cslice.len(),
+ signature.0.as_ptr(),
+ self.0.as_ptr(),
+ )
+ };
+ if ret == 1 {
+ Ok(())
+ } else {
+ Err(SignatureError)
+ }
+ }
+}
+
+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() {
+ 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);
+ assert_eq!(private_key.0, new_private_key.0);
+ }
+
+ #[test]
+ fn ed25519_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(
+ "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
+ );
+ let msg = [0u8; 0];
+ let sig_expected = test_helpers::decode_hex("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b");
+ let kp = PrivateKey::new_from_seed(&sk);
+ let sig = kp.sign(&msg);
+ assert_eq!(sig_expected, sig.0);
+
+ 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());
+ }
+
+ #[test]
+ fn ed25519_sign_and_verify() {
+ // Test Case 15 from RFC test vectors: https://www.rfc-editor.org/rfc/rfc8032#section-7.1
+ let pk = test_helpers::decode_hex(
+ "cf3af898467a5b7a52d33d53bc037e2642a8da996903fc252217e9c033e2f291",
+ );
+ let sk = test_helpers::decode_hex(
+ "9acad959d216212d789a119252ebfe0c96512a23c73bd9f3b202292d6916a738",
+ );
+ 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 sig = kp.sign(&msg);
+ assert_eq!(sig_expected, sig.0);
+
+ 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());
+ }
+}
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs
index 6ccf09c..2c80ae8 100644
--- a/rust/bssl-crypto/src/lib.rs
+++ b/rust/bssl-crypto/src/lib.rs
@@ -32,6 +32,9 @@ pub mod aes;
/// BoringSSL implemented hash functions.
pub mod digest;
+/// BoringSSL implemented Ed25519 operations.
+pub mod ed25519;
+
/// BoringSSL implemented hkdf operations.
pub mod hkdf;
@@ -62,6 +65,10 @@ impl CSlice<'_> {
self.0.as_ptr() as *const T
}
}
+
+ pub fn len(&self) -> usize {
+ self.0.len()
+ }
}
/// This is a helper struct which provides functions for passing mutable slices over FFI.