aboutsummaryrefslogtreecommitdiff
path: root/nearby/presence/np_ed25519/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'nearby/presence/np_ed25519/src/lib.rs')
-rw-r--r--nearby/presence/np_ed25519/src/lib.rs286
1 files changed, 286 insertions, 0 deletions
diff --git a/nearby/presence/np_ed25519/src/lib.rs b/nearby/presence/np_ed25519/src/lib.rs
new file mode 100644
index 0000000..60c8302
--- /dev/null
+++ b/nearby/presence/np_ed25519/src/lib.rs
@@ -0,0 +1,286 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Wrappers around NP's usage of ed25519 signatures.
+//!
+//! All of NP's usages of ed25519 signatures are performed
+//! with "context" bytes prepended to the payload to be signed
+//! or verified. These "context" bytes allow for usage of the
+//! same base key-pair for different purposes in the protocol.
+#![no_std]
+#![forbid(unsafe_code)]
+#![deny(missing_docs, clippy::indexing_slicing)]
+
+extern crate core;
+
+use array_view::ArrayView;
+use crypto_provider::ed25519::{
+ Ed25519Provider, KeyPair as _, PublicKey as _, RawPrivateKey, RawPublicKey, RawSignature,
+ Signature as _, SignatureError,
+};
+use crypto_provider::CryptoProvider;
+use sink::{Sink, SinkWriter};
+use tinyvec::ArrayVec;
+
+/// Convenient type-alias for a crypto-provider's Ed25519 key-pair
+type CpKeyPair<C> = <<C as CryptoProvider>::Ed25519 as Ed25519Provider>::KeyPair;
+
+/// Type-alias for the Ed25519 public key type
+type CpPublicKey<C> = <<C as CryptoProvider>::Ed25519 as Ed25519Provider>::PublicKey;
+
+/// Type-alias for the Ed25519 signature type
+type CpSignature<C> = <<C as CryptoProvider>::Ed25519 as Ed25519Provider>::Signature;
+
+/// Maximum length of the combined (context len byte) + (context bytes) + (signing payload)
+/// byte-array which an ed25519 signature will be computed over. This is deliberately
+/// chosen to be large enough to incorporate an entire v1 adv as the signing payload.
+pub const MAX_SIGNATURE_BUFFER_LEN: usize = 512;
+
+/// Representation of an Ed25519 key-pair using the given
+/// [`CryptoProvider`] to back its implementation.
+/// Contains both the public and secret halves of an
+/// asymmetric key, and so it may be used to
+/// both sign and verify message signatures.
+pub struct KeyPair<C: CryptoProvider>(CpKeyPair<C>);
+
+impl<C: CryptoProvider> KeyPair<C> {
+ /// Returns the `KeyPair`'s private key bytes. This method should only ever be called by code
+ /// which securely stores private credentials.
+ pub fn private_key(&self) -> RawPrivateKey {
+ self.0.private_key()
+ }
+
+ /// Builds this key-pair from an array of its private key bytes in the yielded by `private_key`.
+ /// This method should only ever be called by code which securely stores private credentials.
+ pub fn from_private_key(private_key: &RawPrivateKey) -> Self {
+ Self(CpKeyPair::<C>::from_private_key(private_key))
+ }
+
+ /// Sign the given message with the given context and
+ /// return a digital signature. The message is represented
+ /// using a [`SinkWriter`] to allow the caller to construct
+ /// the payload to sign without requiring a fully-assembled
+ /// payload available as a slice.
+ ///
+ /// If the message writer writes too much data (greater than 256 bytes),
+ /// this will return `None` instead of a valid signature,
+ /// and so uses in `np_adv` will use `.expect` on the returned value
+ /// to indicate that this length constraint has been considered.
+ pub fn sign_with_context<W: SinkWriter<DataType = u8>>(
+ &self,
+ context: &SignatureContext,
+ msg_writer: W,
+ ) -> Option<Signature<C>> {
+ let mut buffer = context.create_signature_buffer();
+ buffer.try_extend_from_writer(msg_writer).map(|_| Signature(self.0.sign(buffer.as_ref())))
+ }
+
+ /// Gets the public key of this key-pair
+ pub fn public(&self) -> PublicKey<C> {
+ PublicKey { public_key: self.0.public() }
+ }
+
+ /// Generates an ed25519 keypair from a CSPRNG
+ /// generate is not available in `no-std`
+ #[cfg(feature = "std")]
+ pub fn generate() -> Self {
+ Self(CpKeyPair::<C>::generate())
+ }
+}
+
+/// Error raised when attempting to deserialize a key-pair
+/// from a byte-array, but the bytes do not represent a valid
+/// ed25519 key-pair
+#[derive(Debug)]
+pub struct InvalidKeyPairBytes;
+
+/// Errors yielded when attempting to verify an ed25519 signature.
+#[derive(Debug, PartialEq, Eq)]
+pub enum SignatureVerificationError {
+ /// The payload that we attempted to verify the signature of was too big
+ PayloadTooBig,
+ /// The signature we were checking was invalid for the given payload
+ SignatureInvalid,
+}
+
+impl From<SignatureError> for SignatureVerificationError {
+ fn from(_: SignatureError) -> Self {
+ Self::SignatureInvalid
+ }
+}
+
+/// Representation of an Ed25519 public key used for
+/// signature verification.
+pub struct PublicKey<C: CryptoProvider> {
+ public_key: CpPublicKey<C>,
+}
+
+impl<C: CryptoProvider> PublicKey<C> {
+ /// Succeeds if the signature was a valid signature created via the corresponding
+ /// keypair to this public key using the given [`SignatureContext`] on the given
+ /// message payload. The message payload is represented
+ /// using a [`SinkWriter`] to allow the caller to construct
+ /// the payload to sign without requiring a fully-assembled
+ /// payload available as a slice.
+ ///
+ /// If the message writer writes too much data (greater than 256 bytes),
+ /// this will return `None` instead of a valid signature,
+ /// and so uses in `np_adv` will use `.expect` on the returned value
+ /// to indicate that this length constraint has been considered.
+ pub fn verify_signature_with_context<W: SinkWriter<DataType = u8>>(
+ &self,
+ context: &SignatureContext,
+ msg_writer: W,
+ signature: &Signature<C>,
+ ) -> Result<(), SignatureVerificationError> {
+ let mut buffer = context.create_signature_buffer();
+ let maybe_write_success = buffer.try_extend_from_writer(msg_writer);
+ match maybe_write_success {
+ Some(_) => {
+ self.public_key.verify_strict(buffer.as_ref(), &signature.0)?;
+ Ok(())
+ }
+ None => Err(SignatureVerificationError::PayloadTooBig),
+ }
+ }
+
+ /// Builds an ed25519 public key from an array of bytes in
+ /// the format yielded by `to_bytes`.
+ pub fn from_bytes(bytes: &RawPublicKey) -> Result<Self, InvalidPublicKeyBytes> {
+ CpPublicKey::<C>::from_bytes(bytes)
+ .map(|public_key| Self { public_key })
+ .map_err(|_| InvalidPublicKeyBytes)
+ }
+
+ /// Yields the bytes of this ed25519 public key
+ pub fn to_bytes(&self) -> RawPublicKey {
+ self.public_key.to_bytes()
+ }
+}
+
+impl<C: CryptoProvider> Clone for PublicKey<C> {
+ fn clone(&self) -> Self {
+ Self::from_bytes(&self.to_bytes()).unwrap()
+ }
+}
+
+/// Error raised when attempting to deserialize a public key
+/// from a byte-array, but the bytes do not represent a valid
+/// ed25519 public key
+#[derive(Debug)]
+pub struct InvalidPublicKeyBytes;
+
+/// Representation of an Ed25519 message signature,
+/// which can be checked against a [`PublicKey`]
+/// for validity.
+pub struct Signature<C: CryptoProvider>(CpSignature<C>);
+
+impl<C: CryptoProvider> Signature<C> {
+ /// Returns a slice of the signature bytes
+ pub fn to_bytes(&self) -> RawSignature {
+ self.0.to_bytes()
+ }
+}
+
+/// Error raised when attempting to construct a [`Signature`]
+/// from a byte-array which is not of the proper length or format.
+#[derive(Debug)]
+pub struct InvalidSignatureBytes;
+
+impl<C: CryptoProvider> TryFrom<&[u8]> for Signature<C> {
+ type Error = InvalidSignatureBytes;
+ fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
+ bytes
+ .try_into()
+ .map(|sig| Self(CpSignature::<C>::from_bytes(sig)))
+ .map_err(|_| InvalidSignatureBytes)
+ }
+}
+
+/// Minimum length (in bytes) for a [`SignatureContext`] (which cannot be empty).
+pub const MIN_SIGNATURE_CONTEXT_LEN: usize = 1;
+
+/// Maximum length (in bytes) for a [`SignatureContext`] (which uses a 8-bit length field).
+pub const MAX_SIGNATURE_CONTEXT_LEN: usize = 255;
+
+/// (Non-empty) context bytes to use in the construction of NP's
+/// Ed25519 signatures. The context bytes should uniquely
+/// identify the component of the protocol performing the
+/// signature/verification (e.g: advertisement signing,
+/// connection signing), and should be between 1 and
+/// 255 bytes in length.
+pub struct SignatureContext {
+ data: ArrayView<u8, MAX_SIGNATURE_CONTEXT_LEN>,
+}
+
+impl SignatureContext {
+ /// Creates a signature buffer with size bounded by MAX_SIGNATURE_BUFFER_LEN
+ /// which is pre-populated with the contents yielded by
+ /// [`SignatureContext#write_length_prefixed`].
+ fn create_signature_buffer(&self) -> impl Sink<u8> + AsRef<[u8]> {
+ let mut buffer = ArrayVec::<[u8; MAX_SIGNATURE_BUFFER_LEN]>::new();
+ self.write_length_prefixed(&mut buffer).expect("Context should always fit into sig buffer");
+ buffer
+ }
+
+ /// Writes the contents of this signature context, prefixed
+ /// by the length of the context payload to the given byte-sink.
+ /// If writing to the sink failed at some point during this operation,
+ /// `None` will be returned, and the data written to the sink should
+ /// be considered to be invalid.
+ fn write_length_prefixed<S: Sink<u8>>(&self, sink: &mut S) -> Option<()> {
+ let length_byte = self.data.len() as u8;
+ sink.try_push(length_byte)?;
+ sink.try_extend_from_slice(self.data.as_slice())
+ }
+
+ /// Attempts to construct a signature context from the utf-8 bytes
+ /// of the given string. Returns `None` if the passed string
+ /// is invalid to use for signature context bytes.
+ pub const fn from_string_bytes(data_str: &str) -> Result<Self, SignatureContextInvalidLength> {
+ let data_bytes = data_str.as_bytes();
+ Self::from_bytes(data_bytes)
+ }
+
+ #[allow(clippy::indexing_slicing)]
+ const fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureContextInvalidLength> {
+ let num_bytes = bytes.len();
+ if num_bytes < MIN_SIGNATURE_CONTEXT_LEN || num_bytes > MAX_SIGNATURE_CONTEXT_LEN {
+ Err(SignatureContextInvalidLength)
+ } else {
+ let mut array = [0u8; MAX_SIGNATURE_CONTEXT_LEN];
+ let mut i = 0;
+ while i < num_bytes {
+ array[i] = bytes[i];
+ i += 1;
+ }
+ let data = ArrayView::const_from_array(array, bytes.len());
+ Ok(Self { data })
+ }
+ }
+}
+
+/// Error raised when attempting to construct a
+/// [`SignatureContext`] out of data with an
+/// invalid length in bytes.
+#[derive(Debug)]
+pub struct SignatureContextInvalidLength;
+
+impl TryFrom<&[u8]> for SignatureContext {
+ type Error = SignatureContextInvalidLength;
+
+ fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
+ Self::from_bytes(value)
+ }
+}