Skip to main content

bssl_crypto/
ed25519.rs

1// Copyright 2023 The BoringSSL Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Ed25519, a signature scheme.
16//!
17//! Ed25519 builds a signature scheme over a curve that is isogenous to
18//! curve25519. This module provides the "pure" signature scheme described in
19//! <https://datatracker.ietf.org/doc/html/rfc8032>.
20//!
21//! ```
22//! use bssl_crypto::ed25519;
23//!
24//! let key = ed25519::PrivateKey::generate();
25//! // Publish your public key.
26//! let public_key_bytes = *key.to_public().as_bytes();
27//!
28//! // Sign and publish some message.
29//! let signed_message = b"hello world";
30//! let mut sig = key.sign(signed_message);
31//!
32//! // Anyone with the public key can verify it.
33//! let public_key = ed25519::PublicKey::from_bytes(&public_key_bytes);
34//! assert!(public_key.verify(signed_message, &sig).is_ok());
35//! ```
36
37use crate::{
38    cbb_to_buffer, scoped, with_output_array, Buffer, FfiMutSlice, FfiSlice, InvalidSignatureError,
39};
40
41/// The length in bytes of an Ed25519 public key.
42pub const PUBLIC_KEY_LEN: usize = bssl_sys::ED25519_PUBLIC_KEY_LEN as usize;
43
44/// The length in bytes of an Ed25519 seed which is the 32-byte private key
45/// representation defined in RFC 8032.
46pub const SEED_LEN: usize =
47    (bssl_sys::ED25519_PRIVATE_KEY_LEN - bssl_sys::ED25519_PUBLIC_KEY_LEN) as usize;
48
49/// The length in bytes of an Ed25519 signature.
50pub const SIGNATURE_LEN: usize = bssl_sys::ED25519_SIGNATURE_LEN as usize;
51
52// The length in bytes of an Ed25519 keypair. In BoringSSL, the private key is suffixed with the
53// public key, so the keypair length is the same as the private key length.
54const KEYPAIR_LEN: usize = bssl_sys::ED25519_PRIVATE_KEY_LEN as usize;
55
56// The longest Ed25519ph context permitted by RFC 8032.
57const MAX_CONTEXT_LEN: usize = 255;
58
59/// An Ed25519 private key.
60#[derive(Clone)]
61pub struct PrivateKey([u8; KEYPAIR_LEN]);
62
63/// An Ed25519 public key used to verify a signature + message.
64pub struct PublicKey([u8; PUBLIC_KEY_LEN]);
65
66/// An Ed25519 signature created by signing a message with a private key.
67pub type Signature = [u8; SIGNATURE_LEN];
68
69impl PrivateKey {
70    /// Generates a new Ed25519 keypair.
71    pub fn generate() -> Self {
72        let mut public_key = [0u8; PUBLIC_KEY_LEN];
73        let mut private_key = [0u8; KEYPAIR_LEN];
74
75        // Safety:
76        // - Public key and private key are the correct length.
77        unsafe {
78            bssl_sys::ED25519_keypair(public_key.as_mut_ffi_ptr(), private_key.as_mut_ffi_ptr())
79        }
80
81        PrivateKey(private_key)
82    }
83
84    /// Returns the "seed" of this private key, as defined in RFC 8032.
85    pub fn to_seed(&self) -> [u8; SEED_LEN] {
86        // This code will never panic because a length 32 slice will always fit into a
87        // size 32 byte array. The private key is the first 32 bytes of the keypair.
88        #[allow(clippy::expect_used)]
89        self.0[..SEED_LEN]
90            .try_into()
91            .expect("A slice of length SEED_LEN will always fit into an array of length SEED_LEN")
92    }
93
94    /// Derives a key-pair from `seed`, which is the 32-byte private key representation defined
95    /// in RFC 8032.
96    pub fn from_seed(seed: &[u8; SEED_LEN]) -> Self {
97        let mut public_key = [0u8; PUBLIC_KEY_LEN];
98        let mut private_key = [0u8; KEYPAIR_LEN];
99
100        // Safety:
101        // - Public key, private key, and seed are the correct lengths.
102        unsafe {
103            bssl_sys::ED25519_keypair_from_seed(
104                public_key.as_mut_ffi_ptr(),
105                private_key.as_mut_ffi_ptr(),
106                seed.as_ffi_ptr(),
107            )
108        }
109        PrivateKey(private_key)
110    }
111
112    /// Signs the given message and returns the signature.
113    pub fn sign(&self, msg: &[u8]) -> Signature {
114        let mut sig_bytes = [0u8; SIGNATURE_LEN];
115
116        // Safety:
117        // - On allocation failure we panic.
118        // - Signature and private keys are always the correct length.
119        let result = unsafe {
120            bssl_sys::ED25519_sign(
121                sig_bytes.as_mut_ffi_ptr(),
122                msg.as_ffi_ptr(),
123                msg.len(),
124                self.0.as_ffi_ptr(),
125            )
126        };
127        assert_eq!(result, 1, "allocation failure in bssl_sys::ED25519_sign");
128
129        sig_bytes
130    }
131
132    /// Signs the given `sha512_digest` with the given `context` using Ed25519ph.
133    ///
134    /// Note that Ed25519ph (pre-hashed) and "pure" Ed25519 are different algorithms
135    /// defined in RFC 8032. Calling `sign` with the digest of a message will *not*
136    /// return the same output as `sign_prehashed`.
137    ///
138    /// This function returns None if `context` is longer than 255 bytes.
139    pub fn sign_prehashed(&self, context: &[u8], sha512_digest: &[u8; 64]) -> Option<Signature> {
140        if context.len() > MAX_CONTEXT_LEN {
141            return None;
142        }
143
144        let mut sig_bytes = [0u8; SIGNATURE_LEN];
145
146        // Safety:
147        // - On allocation failure we panic.
148        // - The context length is checked above.
149        // - Signature and private keys are always the correct length.
150        let result = unsafe {
151            bssl_sys::ED25519_sign_prehashed(
152                sig_bytes.as_mut_ffi_ptr(),
153                context.as_ffi_ptr(),
154                context.len(),
155                sha512_digest.as_ffi_ptr(),
156                self.0.as_ffi_ptr(),
157            )
158        };
159        assert_eq!(
160            result, 1,
161            "allocation failure in bssl_sys::ED25519_sign_prehashed"
162        );
163
164        Some(sig_bytes)
165    }
166
167    /// Returns the [`PublicKey`] corresponding to this private key.
168    pub fn to_public(&self) -> PublicKey {
169        let keypair_bytes = &self.0;
170
171        // This code will never panic because a length 32 slice will always fit into a
172        // size 32 byte array. The public key is the last 32 bytes of the keypair.
173        #[allow(clippy::expect_used)]
174        PublicKey(
175            keypair_bytes[PUBLIC_KEY_LEN..]
176                .try_into()
177                .expect("The slice is always the correct size for a public key"),
178        )
179    }
180
181    // Safety: caller must make sure that the key type is ED25519
182    pub(crate) unsafe fn from_evp_pkey(mut pkey: scoped::EvpPkey) -> Self {
183        let mut seed = [0; SEED_LEN];
184        let len = &mut { SEED_LEN };
185        // Safety: pkey is now owned and len is set
186        let ret = unsafe {
187            bssl_sys::EVP_PKEY_get_raw_private_key(
188                pkey.as_ffi_ptr(),
189                seed.as_mut_ptr(),
190                len as *mut _,
191            )
192        };
193        // Sanity check, in case the seed is not as long as expected.
194        assert_eq!(ret, 1);
195        assert_eq!(*len, SEED_LEN);
196        Self::from_seed(&seed)
197    }
198}
199
200impl PublicKey {
201    /// Builds the public key from an array of bytes.
202    pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_LEN]) -> Self {
203        PublicKey(*bytes)
204    }
205
206    /// Returns the bytes of the public key.
207    pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_LEN] {
208        &self.0
209    }
210
211    /// Parse a public key in SubjectPublicKeyInfo format.
212    pub fn from_der_subject_public_key_info(spki: &[u8]) -> Option<Self> {
213        // Safety: `EVP_pkey_ed25519` is always safe to call.
214        let alg = unsafe { bssl_sys::EVP_pkey_ed25519() };
215        let mut pkey =
216            scoped::EvpPkey::from_der_subject_public_key_info(spki, core::slice::from_ref(&alg))?;
217        let raw_pkey: [u8; PUBLIC_KEY_LEN] = unsafe {
218            with_output_array(|out, mut out_len| {
219                // We only passed one key type, so `pkey` must be an Ed25519
220                // key. The raw public key then must be available, and must be
221                // `PUBLIC_KEY_LEN` bytes.
222                assert_eq!(
223                    1,
224                    bssl_sys::EVP_PKEY_get_raw_public_key(pkey.as_ffi_ptr(), out, &mut out_len)
225                );
226                assert_eq!(out_len, PUBLIC_KEY_LEN);
227            })
228        };
229        Some(PublicKey(raw_pkey))
230    }
231
232    /// Serialize this key in SubjectPublicKeyInfo format.
233    pub fn to_der_subject_public_key_info(&self) -> Buffer {
234        // Safety: this only copies from the `self.0` buffer.
235        let mut pkey = scoped::EvpPkey::from_ptr(unsafe {
236            bssl_sys::EVP_PKEY_from_raw_public_key(
237                bssl_sys::EVP_pkey_ed25519(),
238                self.0.as_ffi_ptr(),
239                PUBLIC_KEY_LEN,
240            )
241        });
242        // Safety: we are only testing pointer nullness, we do not mutate the data
243        assert!(!pkey.as_ffi_ptr().is_null());
244
245        cbb_to_buffer(PUBLIC_KEY_LEN + 32, |cbb| unsafe {
246            // The arguments are valid so this will only fail if out of memory,
247            // which this crate doesn't handle.
248            assert_eq!(1, bssl_sys::EVP_marshal_public_key(cbb, pkey.as_ffi_ptr()));
249        })
250    }
251
252    /// Verifies that `signature` is a valid signature, by this key, of `msg`.
253    pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), InvalidSignatureError> {
254        let ret = unsafe {
255            // Safety: `self.0` is the correct length and other buffers are valid.
256            bssl_sys::ED25519_verify(
257                msg.as_ffi_ptr(),
258                msg.len(),
259                signature.as_ffi_ptr(),
260                self.0.as_ffi_ptr(),
261            )
262        };
263        if ret == 1 {
264            Ok(())
265        } else {
266            Err(InvalidSignatureError)
267        }
268    }
269
270    /// Verifies that `signature` is a valid signature under the given `context`
271    /// of `sha512_digest` using Ed25519ph.
272    ///
273    /// Note that Ed25519ph (pre-hashed) and "pure" Ed25519 are different algorithms
274    /// defined in RFC 8032. Calling `verify` with the digest of a message will *not*
275    /// return the same output as `verify_prehashed`.
276    ///
277    /// A `context` longer than 255 bytes, the maximum permitted by RFC 8032, is
278    /// reported as an invalid signature.
279    pub fn verify_prehashed(
280        &self,
281        context: &[u8],
282        sha512_digest: &[u8; 64],
283        signature: &Signature,
284    ) -> Result<(), InvalidSignatureError> {
285        let ret = unsafe {
286            // Safety: `self.0` is the correct length and other buffers are valid.
287            bssl_sys::ED25519_verify_prehashed(
288                context.as_ffi_ptr(),
289                context.len(),
290                sha512_digest.as_ffi_ptr(),
291                signature.as_ffi_ptr(),
292                self.0.as_ffi_ptr(),
293            )
294        };
295        if ret == 1 {
296            Ok(())
297        } else {
298            Err(InvalidSignatureError)
299        }
300    }
301}
302
303#[cfg(test)]
304mod test {
305    use super::*;
306    use crate::digest::Sha512;
307    use crate::test_helpers;
308
309    #[test]
310    fn gen_roundtrip() {
311        let private_key = PrivateKey::generate();
312        assert_ne!([0u8; 64], private_key.0);
313        let seed = private_key.to_seed();
314        let new_private_key = PrivateKey::from_seed(&seed);
315        assert_eq!(private_key.0, new_private_key.0);
316    }
317
318    #[test]
319    fn der_subject_public_key_info() {
320        let priv_key = PrivateKey::generate();
321        let msg = [0u8; 0];
322        let sig = priv_key.sign(&msg);
323
324        let pub_key = priv_key.to_public();
325        assert!(pub_key.verify(&msg, &sig).is_ok());
326
327        let pub_key_der = pub_key.to_der_subject_public_key_info();
328        let pub_key_from_der =
329            PublicKey::from_der_subject_public_key_info(pub_key_der.as_ref()).unwrap();
330        assert_eq!(pub_key.as_bytes(), pub_key_from_der.as_bytes());
331        assert!(pub_key_from_der.verify(&msg, &sig).is_ok());
332
333        assert!(PublicKey::from_der_subject_public_key_info(
334            &pub_key_from_der.as_bytes()[0..PUBLIC_KEY_LEN / 2]
335        )
336        .is_none());
337
338        assert!(PublicKey::from_der_subject_public_key_info(b"").is_none());
339    }
340
341    #[test]
342    fn der_subject_public_key_info_wrong_type() {
343        // This is an X25519 key, not an Ed25519 key.
344        let spki = test_helpers::decode_hex_into_vec("302a300506032b656e032100e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c");
345        // `from_der_subject_public_key_info` should reject it.
346        assert!(PublicKey::from_der_subject_public_key_info(&spki).is_none());
347    }
348
349    #[test]
350    fn empty_msg() {
351        // Test Case 1 from RFC test vectors: https://www.rfc-editor.org/rfc/rfc8032#section-7.1
352        let pk = test_helpers::decode_hex(
353            "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
354        );
355        let seed = test_helpers::decode_hex(
356            "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
357        );
358        let msg = [0u8; 0];
359        let sig_expected  = test_helpers::decode_hex("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b");
360        let kp = PrivateKey::from_seed(&seed);
361        let sig = kp.sign(&msg);
362        assert_eq!(sig_expected, sig);
363
364        let pub_key = PublicKey::from_bytes(&pk);
365        assert_eq!(pub_key.as_bytes(), kp.to_public().as_bytes());
366        assert!(pub_key.verify(&msg, &sig).is_ok());
367    }
368
369    #[test]
370    fn ed25519_sign_and_verify() {
371        // Test Case 15 from RFC test vectors: https://www.rfc-editor.org/rfc/rfc8032#section-7.1
372        let pk = test_helpers::decode_hex(
373            "cf3af898467a5b7a52d33d53bc037e2642a8da996903fc252217e9c033e2f291",
374        );
375        let sk = test_helpers::decode_hex(
376            "9acad959d216212d789a119252ebfe0c96512a23c73bd9f3b202292d6916a738",
377        );
378        let msg: [u8; 14] = test_helpers::decode_hex("55c7fa434f5ed8cdec2b7aeac173");
379        let sig_expected  = test_helpers::decode_hex("6ee3fe81e23c60eb2312b2006b3b25e6838e02106623f844c44edb8dafd66ab0671087fd195df5b8f58a1d6e52af42908053d55c7321010092748795ef94cf06");
380        let kp = PrivateKey::from_seed(&sk);
381
382        let sig = kp.sign(&msg);
383        assert_eq!(sig_expected, sig);
384
385        let pub_key = PublicKey::from_bytes(&pk);
386        assert_eq!(pub_key.as_bytes(), kp.to_public().as_bytes());
387        assert!(pub_key.verify(&msg, &sig).is_ok());
388    }
389
390    #[test]
391    fn ed25519ph_sign_and_verify() {
392        // Test Case 15 from RFC test vectors: https://www.rfc-editor.org/rfc/rfc8032#section-7.3
393        let pk = test_helpers::decode_hex(
394            "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf",
395        );
396        let sk = test_helpers::decode_hex(
397            "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42",
398        );
399        let msg: [u8; 3] = test_helpers::decode_hex("616263");
400        let mut ctx = Sha512::new();
401        ctx.update(&msg);
402        let sha512_digest = ctx.digest();
403        let sig_expected = test_helpers::decode_hex("98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406");
404        let kp = PrivateKey::from_seed(&sk);
405
406        let sig = kp.sign_prehashed(&[], &sha512_digest).unwrap();
407        assert_eq!(sig_expected, sig);
408
409        let pub_key = PublicKey::from_bytes(&pk);
410        assert_eq!(pub_key.as_bytes(), kp.to_public().as_bytes());
411        assert!(pub_key.verify_prehashed(&[], &sha512_digest, &sig).is_ok());
412    }
413
414    #[test]
415    fn ed25519ph_max_context_length() {
416        let kp = PrivateKey::generate();
417        let pub_key = kp.to_public();
418        let mut ctx = Sha512::new();
419        ctx.update(b"test message");
420        let sha512_digest = ctx.digest();
421
422        let context = [0u8; 255]; // Maximum allowed context length.
423        let sig = kp.sign_prehashed(&context, &sha512_digest).unwrap();
424        assert!(pub_key
425            .verify_prehashed(&context, &sha512_digest, &sig)
426            .is_ok());
427
428        let too_long_context = [0u8; 256];
429        assert!(kp
430            .sign_prehashed(&too_long_context, &sha512_digest)
431            .is_none());
432        assert!(pub_key
433            .verify_prehashed(&too_long_context, &sha512_digest, &sig)
434            .is_err());
435    }
436}