1 // Copyright 2023 Google LLC
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 //      http://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 //! Wrappers around NP's usage of ed25519 signatures.
16 //!
17 //! All of NP's usages of ed25519 signatures are performed
18 //! with "context" bytes prepended to the payload to be signed
19 //! or verified. These "context" bytes allow for usage of the
20 //! same base key-pair for different purposes in the protocol.
21 #![no_std]
22 
23 use array_view::ArrayView;
24 use crypto_provider::ed25519::{Ed25519Provider, PrivateKey, PublicKey, Signature, SignatureError};
25 use sink::{Sink, SinkWriter};
26 use tinyvec::ArrayVec;
27 
28 /// Maximum length of the combined (context len byte) + (context bytes) + (signing payload)
29 /// byte-array which an ed25519 signature will be computed over. This is deliberately
30 /// chosen to be large enough to incorporate an entire v1 adv as the signing payload.
31 pub const MAX_SIGNATURE_BUFFER_LEN: usize = 512;
32 
33 /// Sign the given message with the given context and
34 /// return a digital signature. The message is represented
35 /// using a [`SinkWriter`] to allow the caller to construct
36 /// the payload to sign without requiring a fully-assembled
37 /// payload available as a slice.
38 ///
39 /// If the message writer writes too much data (greater than 256 bytes),
40 /// this will return `None` instead of a valid signature,
41 /// and so uses in `np_adv` will use `.expect` on the returned value
42 /// to indicate that this length constraint has been considered.
sign_with_context<E: Ed25519Provider, W: SinkWriter<DataType = u8>>( private_key: &PrivateKey, context: &SignatureContext, msg_writer: W, ) -> Option<Signature>43 pub fn sign_with_context<E: Ed25519Provider, W: SinkWriter<DataType = u8>>(
44     private_key: &PrivateKey,
45     context: &SignatureContext,
46     msg_writer: W,
47 ) -> Option<Signature> {
48     let mut buffer = context.create_signature_buffer();
49     buffer.try_extend_from_writer(msg_writer).map(|_| private_key.sign::<E>(buffer.as_ref()))
50 }
51 
52 /// Errors yielded when attempting to verify an ed25519 signature.
53 #[derive(Debug, PartialEq, Eq)]
54 pub enum SignatureVerificationError {
55     /// The payload that we attempted to verify the signature of was too big
56     PayloadTooBig,
57     /// The signature we were checking was invalid for the given payload
58     SignatureInvalid,
59 }
60 
61 impl From<SignatureError> for SignatureVerificationError {
from(_: SignatureError) -> Self62     fn from(_: SignatureError) -> Self {
63         Self::SignatureInvalid
64     }
65 }
66 
67 /// Succeeds if the signature was a valid signature created via the corresponding
68 /// keypair to this public key using the given [`SignatureContext`] on the given
69 /// message payload. The message payload is represented
70 /// using a [`SinkWriter`] to allow the caller to construct
71 /// the payload to sign without requiring a fully-assembled
72 /// payload available as a slice.
73 ///
74 /// If the message writer writes too much data (greater than 256 bytes),
75 /// this will return `None` instead of a valid signature,
76 /// and so uses in `np_adv` will use `.expect` on the returned value
77 /// to indicate that this length constraint has been considered.
verify_signature_with_context<E: Ed25519Provider, W: SinkWriter<DataType = u8>>( public_key: &PublicKey, context: &SignatureContext, msg_writer: W, signature: Signature, ) -> Result<(), SignatureVerificationError>78 pub fn verify_signature_with_context<E: Ed25519Provider, W: SinkWriter<DataType = u8>>(
79     public_key: &PublicKey,
80     context: &SignatureContext,
81     msg_writer: W,
82     signature: Signature,
83 ) -> Result<(), SignatureVerificationError> {
84     let mut buffer = context.create_signature_buffer();
85     let maybe_write_success = buffer.try_extend_from_writer(msg_writer);
86     match maybe_write_success {
87         Some(_) => {
88             public_key.verify_strict::<E>(buffer.as_ref(), signature)?;
89             Ok(())
90         }
91         None => Err(SignatureVerificationError::PayloadTooBig),
92     }
93 }
94 
95 /// Minimum length (in bytes) for a [`SignatureContext`] (which cannot be empty).
96 pub const MIN_SIGNATURE_CONTEXT_LEN: usize = 1;
97 
98 /// Maximum length (in bytes) for a [`SignatureContext`] (which uses an 8-bit length field).
99 pub const MAX_SIGNATURE_CONTEXT_LEN: usize = 255;
100 
101 /// (Non-empty) context bytes to use in the construction of NP's
102 /// Ed25519 signatures. The context bytes should uniquely
103 /// identify the component of the protocol performing the
104 /// signature/verification (e.g: advertisement signing,
105 /// connection signing), and should be between 1 and
106 /// 255 bytes in length.
107 pub struct SignatureContext {
108     data: ArrayView<u8, MAX_SIGNATURE_CONTEXT_LEN>,
109 }
110 
111 impl SignatureContext {
112     /// Creates a signature buffer with size bounded by MAX_SIGNATURE_BUFFER_LEN
113     /// which is pre-populated with the contents yielded by
114     /// [`SignatureContext#write_length_prefixed`].
create_signature_buffer(&self) -> impl Sink<u8> + AsRef<[u8]>115     fn create_signature_buffer(&self) -> impl Sink<u8> + AsRef<[u8]> {
116         let mut buffer = ArrayVec::<[u8; MAX_SIGNATURE_BUFFER_LEN]>::new();
117         #[allow(clippy::expect_used)]
118         self.write_length_prefixed(&mut buffer).expect("Context should always fit into sig buffer");
119         buffer
120     }
121 
122     /// Writes the contents of this signature context, prefixed
123     /// by the length of the context payload to the given byte-sink.
124     /// If writing to the sink failed at some point during this operation,
125     /// `None` will be returned, and the data written to the sink should
126     /// be considered to be invalid.
write_length_prefixed<S: Sink<u8>>(&self, sink: &mut S) -> Option<()>127     fn write_length_prefixed<S: Sink<u8>>(&self, sink: &mut S) -> Option<()> {
128         let length_byte = self.data.len() as u8;
129         sink.try_push(length_byte)?;
130         sink.try_extend_from_slice(self.data.as_slice())
131     }
132 
133     /// Attempts to construct a signature context from the utf-8 bytes
134     /// of the given string. Returns `None` if the passed string
135     /// is invalid to use for signature context bytes.
from_string_bytes(data_str: &str) -> Result<Self, SignatureContextInvalidLength>136     pub const fn from_string_bytes(data_str: &str) -> Result<Self, SignatureContextInvalidLength> {
137         let data_bytes = data_str.as_bytes();
138         Self::from_bytes(data_bytes)
139     }
140 
141     #[allow(clippy::indexing_slicing)]
from_bytes(bytes: &[u8]) -> Result<Self, SignatureContextInvalidLength>142     const fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureContextInvalidLength> {
143         let num_bytes = bytes.len();
144         if num_bytes < MIN_SIGNATURE_CONTEXT_LEN || num_bytes > MAX_SIGNATURE_CONTEXT_LEN {
145             Err(SignatureContextInvalidLength)
146         } else {
147             let mut array = [0u8; MAX_SIGNATURE_CONTEXT_LEN];
148             let mut i = 0;
149             while i < num_bytes {
150                 array[i] = bytes[i];
151                 i += 1;
152             }
153             let data = ArrayView::const_from_array(array, bytes.len());
154             Ok(Self { data })
155         }
156     }
157 }
158 
159 /// Error raised when attempting to construct a
160 /// [`SignatureContext`] out of data with an
161 /// invalid length in bytes.
162 #[derive(Debug)]
163 pub struct SignatureContextInvalidLength;
164 
165 impl TryFrom<&[u8]> for SignatureContext {
166     type Error = SignatureContextInvalidLength;
167 
try_from(value: &[u8]) -> Result<Self, Self::Error>168     fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
169         Self::from_bytes(value)
170     }
171 }
172