From 57f65b261ee82a3124ce6a9facfa3c9cd17dc3e2 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 21 May 2021 11:27:41 -0400 Subject: [PATCH] tor-llcrypto: Improve documentation and move keyblinding. --- tor-llcrypto/src/cipher.rs | 8 +- tor-llcrypto/src/d.rs | 8 +- tor-llcrypto/src/lib.rs | 61 +++++++++-- tor-llcrypto/src/pk.rs | 26 +++-- tor-llcrypto/src/pk/ed25519.rs | 142 ++++++------------------- tor-llcrypto/src/pk/keymanip.rs | 148 +++++++++++++++++++++++++-- tor-llcrypto/src/pk/rsa.rs | 30 ++++-- tor-llcrypto/src/util.rs | 6 +- tor-llcrypto/src/util/rand_compat.rs | 50 +++++++-- 9 files changed, 317 insertions(+), 162 deletions(-) diff --git a/tor-llcrypto/src/cipher.rs b/tor-llcrypto/src/cipher.rs index 54fb698ce..bbd102ed0 100644 --- a/tor-llcrypto/src/cipher.rs +++ b/tor-llcrypto/src/cipher.rs @@ -3,12 +3,10 @@ //! Fortunately, Tor has managed not to proliferate ciphers. It only //! uses AES, and (so far) only uses AES in counter mode. -/// Re-exports implementations of counter-mode AES +/// Re-exports implementations of counter-mode AES. /// -/// These ciphers implement the -/// [StreamCipher](https://docs.rs/cipher/0.2.1/cipher/stream/trait.StreamCipher.html) -/// trait, so use the -/// [cipher](https://docs.rs/cipher/0.2.1/cipher/) crate to access them. +/// These ciphers implement the `cipher::StreamCipher` trait, so use +/// the [`cipher`](https://docs.rs/cipher) crate to access them. pub mod aes { // These implement StreamCipher. pub use ::aes::{Aes128Ctr, Aes256Ctr}; diff --git a/tor-llcrypto/src/d.rs b/tor-llcrypto/src/d.rs index e0c278ef6..0c6dda072 100644 --- a/tor-llcrypto/src/d.rs +++ b/tor-llcrypto/src/d.rs @@ -1,11 +1,11 @@ //! Digests and XOFs used to implement the Tor protocol. //! -//! In various places, for legacy reasons, Tor uses SHA1, SHA2, -//! SHA3, and SHAKE. We re-export them all here, implementing -//! the Digest trait. +//! In various places, for legacy reasons, Tor uses SHA1, SHA2, SHA3, +//! and SHAKE. We re-export them all here, in forms implementing the +//! the [`digest::Digest`] traits. //! //! Other code should access these digests via the traits in the -//! [digest](https://docs.rs/digest/0.8.1/digest/) crate. +//! [`digest`] crate. pub use sha1::Sha1; pub use sha2::{Sha256, Sha512}; diff --git a/tor-llcrypto/src/lib.rs b/tor-llcrypto/src/lib.rs index ff0a0f5f1..c14580bbd 100644 --- a/tor-llcrypto/src/lib.rs +++ b/tor-llcrypto/src/lib.rs @@ -1,15 +1,58 @@ -//! Low-level crypto implementations for Tor. +//! # `tor-llcrypto`: Low-level cryptographic implementations for Tor. //! -//! This crate doesn't have much of interest: for the most part it -//! just wraps other crates that implement lower-level cryptographic -//! functionality. In some cases the functionality is just -//! re-exported; in others, it is wrapped to present a conseistent -//! interface. +//! ## Overview //! -//! Encryption is implemented in `cipher`, digests are in `d`, and -//! public key cryptography (including signatures, encryption, and key -//! agreement) are in `pk`. +//! The `tor-llcrypto` crate wraps lower-level cryptographic +//! primitives that Tor needs, and provides a few smaller pieces of +//! cryptographic functionality that are commonly required to +//! implement Tor correctly. //! +//! This crate is part of +//! [Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to +//! implement Tor in Rust. Many other crates in Arti depend on it. +//! +//! You probably wouldn't want to use this crate for implementing +//! non-Tor-based protocols; instead you should probably use the other +//! crates that it depends on if you have a low-level protocol to +//! implement, or a higher-level cryptographic system if you want to +//! add security to something else. It is easy to accidentally put +//! these functions together in ways that are unsafe. +//! +//! ### Why a separate crate? +//! +//! Why do we collect and re-export our cryptography here in +//! `tor-llcrypto`, instead of having the different crates in Arti use +//! underlying cryptographic crates directly? +//! +//! By wrapping our cryptography in this crate, we. +//! +//! ### Adding to `tor-llcrypto` +//! +//! Any low-level cryptographic algorithm that is used by at least two +//! other crates in Arti is a candidate for inclusion in +//! `tor-llcrypto`, especially if that algorithm's purpose is not +//! specific to any single piece of the Tor algorithm. +//! +//! Cryptographic _traits_ (like those from RustCrypto) don't have to +//! go in `tor-llcrypto`, since they are interfaces rather than +//! implementations. +//! +//! ## Contents +//! +//! Encryption is implemented in [`cipher`]: Currently only AES is +//! exposed or needed. +//! +//! Cryptographic digests are in [`d`]: The Tor protocol uses several +//! digests in different places, and these are all collected here. +//! +//! Public key cryptography (including signatures, encryption, and key +//! agreement) are in [`pk`]. Older parts of the Tor protocol require +//! RSA; newer parts are based on Curve25519 and Ed25519. There is +//! also functionality here for _key manipulation_ for the keys used +//! in these symmetric algorithms. +//! +//! The [`util`] module has some miscellaneous compatibility utilities +//! for manipulating cryptography-related objects and code. #![deny(missing_docs)] #![deny(unreachable_pub)] diff --git a/tor-llcrypto/src/pk.rs b/tor-llcrypto/src/pk.rs index 0ed7dd0c1..e0e0a584e 100644 --- a/tor-llcrypto/src/pk.rs +++ b/tor-llcrypto/src/pk.rs @@ -1,7 +1,7 @@ //! Public-key cryptography for Tor. //! //! In old places, Tor uses RSA; newer Tor public-key cryptography is -//! basd on curve25519 and ed25519. +//! based on curve25519 and ed25519. pub mod ed25519; pub mod keymanip; @@ -9,17 +9,23 @@ pub mod rsa; /// Re-exporting Curve25519 implementations. /// -/// Eventually there should probably be a key-agreement trait or two -/// that this implements, but for now I'm just re-using the API from -/// x25519-dalek. +/// *TODO*: Eventually we should probably recommend using is code via some +/// key-agreement trait, but for now we are just re-using the APIs from +/// [`x25519_dalek`]. pub mod curve25519 { pub use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret, StaticSecret}; } -/// Type for a validatable signature. +/// A type for a validatable signature. /// /// It necessarily includes the signature, the public key, and (a hash /// of?) the document being checked. +/// +/// Having this trait enables us to write code for checking a large number +/// of validatable signatures in a way that permits batch signatures for +/// Ed25519. +/// +/// To be used with [`validate_all_sigs`]. pub trait ValidatableSignature { /// Check whether this signature is a correct signature for the document. fn is_valid(&self) -> bool; @@ -32,8 +38,14 @@ pub trait ValidatableSignature { /// Check whether all of the signatures in this Vec are valid. /// -/// (Having a separate implementation here enables us to use -/// batch-verification when available.) +/// Return `true` if every signature is valid; return `false` if even +/// one is invalid. +/// +/// This function should typically give the same result as just +/// calling `v.iter().all(ValidatableSignature::is_valid))`, while taking +/// advantage of batch verification to whatever extent possible. +/// +/// (See [`ed25519::validate_batch`] for caveats.) pub fn validate_all_sigs(v: &[Box]) -> bool { // First we break out the ed25519 signatures (if any) so we can do // a batch-verification on them. diff --git a/tor-llcrypto/src/pk/ed25519.rs b/tor-llcrypto/src/pk/ed25519.rs index d5a6e27f2..d0d283c6d 100644 --- a/tor-llcrypto/src/pk/ed25519.rs +++ b/tor-llcrypto/src/pk/ed25519.rs @@ -1,20 +1,29 @@ -//! Re-exporting Ed25519 implementations. +//! Re-exporting Ed25519 implementations, and related utilities. //! -//! Eventually this should probably be replaced with a wrapper that -//! uses the ed25519 trait and the Signature trait. +//! Here we re-export types from [`ed25519_dalek`] that implement the +//! Ed25519 signature algorithm. (TODO: Eventually, this module +//! should probably be replaced with a wrapper that uses the ed25519 +//! trait and the Signature trait.) +//! +//! We additionally provide an `Ed25519Identity` type to represent the +//! unvalidated Ed25519 "identity keys" that we use throughout the Tor +//! protocol to uniquely identify a relay. use arrayref::array_ref; use std::convert::{TryFrom, TryInto}; use std::fmt::{self, Debug, Display, Formatter}; -use subtle::*; -use thiserror::Error; +use subtle::*; // for ct_eq pub use ed25519_dalek::{ExpandedSecretKey, Keypair, PublicKey, SecretKey, Signature}; -use curve25519_dalek::edwards::CompressedEdwardsY; -use curve25519_dalek::scalar::Scalar; - /// A relay's identity, as an unchecked, unvalidated Ed25519 key. +/// +/// This type is distinct from an Ed25519 [`PublicKey`] for several reasons: +/// * We're storing it in a compact format, whereas the public key +/// implementation might want an expanded form for more efficient key +/// validation. +/// * This type hasn't checked whether the bytes herre actually _are_ a +/// valid Ed25519 public key. #[derive(Clone, Copy, Hash)] #[allow(clippy::derive_hash_xor_eq)] pub struct Ed25519Identity { @@ -222,11 +231,24 @@ impl super::ValidatableSignature for ValidatableEd25519Signature { } /// Perform a batch verification operation on the provided signatures +/// +/// Return `true` if _every_ signature is valid; otherwise return `false`. +/// +/// Note that the mathematics for batch validation are slightly +/// different than those for normal one-signature validation. Because +/// of this, it is possible for an ostensible signature that passes +/// one validation algorithm might fail the other. (Well-formed +/// signatures generated by a correct Ed25519 implementation will +/// always pass both kinds of validation, and an attacker should not +/// be able to forge a signature that passes either kind.) pub fn validate_batch(sigs: &[&ValidatableEd25519Signature]) -> bool { use crate::pk::ValidatableSignature; if sigs.is_empty() { + // ed25519_dalek has nonzero cost for a batch-verification of + // zero sigs. true } else if sigs.len() == 1 { + // Validating one signature in the traditional way is faster. sigs[0].is_valid() } else { let mut ed_msgs = Vec::new(); @@ -241,107 +263,3 @@ pub fn validate_batch(sigs: &[&ValidatableEd25519Signature]) -> bool { ed25519_dalek::verify_batch(&ed_msgs[..], &ed_sigs[..], &ed_pks[..]).is_ok() } } - -/// An error during our blinding operation -#[derive(Error, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum BlindingError { - /// A bad public key was provided for blinding - #[error("Bad pubkey provided")] - BadPubkey, - /// Dalek failed the scalar multiplication - #[error("Key blinding Failed")] - BlindingFailed, -} - -// Convert this dalek error to a Blinding Error -impl From for BlindingError { - fn from(_: ed25519_dalek::SignatureError) -> BlindingError { - BlindingError::BlindingFailed - } -} - -/// Blind the ed25519 public key 'pk' using the blinding parameter 'param' and -/// return the blinded public key. -pub fn blind_pubkey(pk: &PublicKey, mut param: [u8; 32]) -> Result { - // Clamp the blinding parameter - param[0] &= 248; - param[31] &= 63; - param[31] |= 64; - - // Transform it into a scalar so that we can do scalar mult - let blinding_factor = Scalar::from_bytes_mod_order(param); - - // Convert the public key to a point on the curve - let pubkey_point = CompressedEdwardsY(pk.to_bytes()) - .decompress() - .ok_or(BlindingError::BadPubkey)?; - - // Do the scalar multiplication and get a point back - let blinded_pubkey_point = (blinding_factor * pubkey_point).compress(); - // Turn the point back into bytes and return it - Ok(PublicKey::from_bytes(&blinded_pubkey_point.0)?) -} - -#[cfg(test)] -mod test { - use super::*; - use std::convert::TryInto; - - #[test] - fn blinding() { - // Test the ed25519 blinding function. - // - // These test vectors are from our ed25519 implementation and related - // functions. These were automatically generated by the - // ed25519_exts_ref.py script in little-t-tor and they are also used by - // little-t-tor and onionbalance: - let pubkeys = vec![ - b"c2247870536a192d142d056abefca68d6193158e7c1a59c1654c954eccaff894", - b"1519a3b15816a1aafab0b213892026ebf5c0dc232c58b21088d88cb90e9b940d", - b"081faa81992e360ea22c06af1aba096e7a73f1c665bc8b3e4e531c46455fd1dd", - b"73cfa1189a723aad7966137cbffa35140bb40d7e16eae4c40b79b5f0360dd65a", - b"66c1a77104d86461b6f98f73acf3cd229c80624495d2d74d6fda1e940080a96b", - b"d21c294db0e64cb2d8976625786ede1d9754186ae8197a64d72f68c792eecc19", - b"c4d58b4cf85a348ff3d410dd936fa460c4f18da962c01b1963792b9dcc8a6ea6", - b"95126f14d86494020665face03f2d42ee2b312a85bc729903eb17522954a1c4a", - b"95126f14d86494020665face03f2d42ee2b312a85bc729903eb17522954a1c4a", - b"95126f14d86494020665face03f2d42ee2b312a85bc729903eb17522954a1c4a", - ]; - let params = vec![ - "54a513898b471d1d448a2f3c55c1de2c0ef718c447b04497eeb999ed32027823", - "831e9b5325b5d31b7ae6197e9c7a7baf2ec361e08248bce055908971047a2347", - "ac78a1d46faf3bfbbdc5af5f053dc6dc9023ed78236bec1760dadfd0b2603760", - "f9c84dc0ac31571507993df94da1b3d28684a12ad14e67d0a068aba5c53019fc", - "b1fe79d1dec9bc108df69f6612c72812755751f21ecc5af99663b30be8b9081f", - "81f1512b63ab5fb5c1711a4ec83d379c420574aedffa8c3368e1c3989a3a0084", - "97f45142597c473a4b0e9a12d64561133ad9e1155fe5a9807fe6af8a93557818", - "3f44f6a5a92cde816635dfc12ade70539871078d2ff097278be2a555c9859cd0", - "0000000000000000000000000000000000000000000000000000000000000000", - "1111111111111111111111111111111111111111111111111111111111111111", - ]; - let blinded_pubkeys = vec![ - "1fc1fa4465bd9d4956fdbdc9d3acb3c7019bb8d5606b951c2e1dfe0b42eaeb41", - "1cbbd4a88ce8f165447f159d9f628ada18674158c4f7c5ead44ce8eb0fa6eb7e", - "c5419ad133ffde7e0ac882055d942f582054132b092de377d587435722deb028", - "3e08d0dc291066272e313014bfac4d39ad84aa93c038478a58011f431648105f", - "59381f06acb6bf1389ba305f70874eed3e0f2ab57cdb7bc69ed59a9b8899ff4d", - "2b946a484344eb1c17c89dd8b04196a84f3b7222c876a07a4cece85f676f87d9", - "c6b585129b135f8769df2eba987e76e089e80ba3a2a6729134d3b28008ac098e", - "0eefdc795b59cabbc194c6174e34ba9451e8355108520554ec285acabebb34ac", - "312404d06a0a9de489904b18d5233e83a50b225977fa8734f2c897a73c067952", - "952a908a4a9e0e5176a2549f8f328955aca6817a9fdc59e3acec5dec50838108", - ]; - - for i in 0..pubkeys.len() { - let pk = PublicKey::from_bytes(&hex::decode(pubkeys[i]).unwrap()).unwrap(); - - let blinded_pk = blind_pubkey(&pk, hex::decode(params[i]).unwrap().try_into().unwrap()); - - assert_eq!( - hex::encode(blinded_pk.unwrap().to_bytes()), - blinded_pubkeys[i] - ); - } - } -} diff --git a/tor-llcrypto/src/pk/keymanip.rs b/tor-llcrypto/src/pk/keymanip.rs index 0cfb56b44..fcf86900d 100644 --- a/tor-llcrypto/src/pk/keymanip.rs +++ b/tor-llcrypto/src/pk/keymanip.rs @@ -1,25 +1,40 @@ //! Key manipulation functions for use with public keys. //! -//! Tor does some interesting and not-really-standard things with its +//! Tor does some interesting and not-standard things with its //! curve25519 and ed25519 keys, for several reasons. //! //! In order to prove ownership of a curve25519 private key, Tor //! converts it into an ed25519 key, and then uses that ed25519 key to -//! sign its identity key. +//! sign its identity key. We implement this conversion with +//! [`convert_curve25519_to_ed25519_public`] and +//! [`convert_curve25519_to_ed25519_private`]. //! -//! TODO: This is also where we would put the key-derivation code that -//! Tor uses in the hsv3 onion services protocol. +//! In Tor's v3 onion service design, Tor uses a _key blinding_ +//! algorithm to derive a publicly known Ed25519 key from a different +//! Ed25519 key used as the .onion address. This algorithm allows +//! directories to validate the signatures on onion service +//! descriptors, without knowing which services they represent. We +//! implement this blinding operation via [`blind_pubkey`]. +//! +//! ## TODO +//! +//! Recommend more standardized ways to do these things. use crate::pk; use digest::Digest; +use thiserror::Error; use zeroize::Zeroizing; +pub use ed25519_dalek::{ExpandedSecretKey, Keypair, PublicKey, SecretKey, Signature}; + +use curve25519_dalek::edwards::CompressedEdwardsY; +use curve25519_dalek::scalar::Scalar; + /// Convert a curve25519 public key (with sign bit) to an ed25519 /// public key, for use in ntor key cross-certification. /// -/// Note that this formula is not terribly standardized; don't use +/// Note that this formula is not standardized; don't use /// it for anything besides cross-certification. -/// pub fn convert_curve25519_to_ed25519_public( pubkey: &pk::curve25519::PublicKey, signbit: u8, @@ -40,8 +55,11 @@ pub fn convert_curve25519_to_ed25519_public( /// Convert a curve25519 private key to an ed25519 public key (and /// give a sign bit) to use with it, for use in ntor key cross-certification. /// -/// Note that this formula is not terribly standardized; don't use +/// Note that this formula is not standardized; don't use /// it for anything besides cross-certification. +/// +/// *NEVER* use these keys to sign inputs that may be generated by an +/// attacker. pub fn convert_curve25519_to_ed25519_private( privkey: &pk::curve25519::StaticSecret, ) -> Option<(pk::ed25519::ExpandedSecretKey, u8)> { @@ -71,8 +89,67 @@ pub fn convert_curve25519_to_ed25519_private( Some((result, signbit)) } +/// An error occurred during a key-blinding operation. +#[derive(Error, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum BlindingError { + /// A bad public key was provided for blinding + #[error("Bad pubkey provided")] + BadPubkey, + /// Dalek failed the scalar multiplication + #[error("Key blinding Failed")] + BlindingFailed, +} + +// Convert this dalek error to a BlindingError +impl From for BlindingError { + fn from(_: ed25519_dalek::SignatureError) -> BlindingError { + BlindingError::BlindingFailed + } +} + +/// Blind the ed25519 public key `pk` using the blinding parameter +/// `param`, and return the blinded public key. +/// +/// This algorithm is described in `rend-spec-v3.txt`, section A.2. +/// In the terminology of that section, the value `pk` corresponds to +/// `A`, and the value `param` corresponds to `h`. +/// +/// Note that the approach used to clamp `param` to a scalar means +/// that different possible values for `param` may yield the same +/// output for a given `pk`. This and other limitations make this +/// function unsuitable for use outside the context of +/// `rend-spec-v3.txt` without careful analysis. +/// +/// # Errors +/// +/// This function can fail if the input is not actually a valid +/// Ed25519 public key. +pub fn blind_pubkey(pk: &PublicKey, mut param: [u8; 32]) -> Result { + // Clamp the blinding parameter + param[0] &= 248; + param[31] &= 63; + param[31] |= 64; + + // Transform it into a scalar so that we can do scalar mult + let blinding_factor = Scalar::from_bytes_mod_order(param); + + // Convert the public key to a point on the curve + let pubkey_point = CompressedEdwardsY(pk.to_bytes()) + .decompress() + .ok_or(BlindingError::BadPubkey)?; + + // Do the scalar multiplication and get a point back + let blinded_pubkey_point = (blinding_factor * pubkey_point).compress(); + // Turn the point back into bytes and return it + Ok(PublicKey::from_bytes(&blinded_pubkey_point.0)?) +} + #[cfg(test)] mod tests { + use super::*; + use std::convert::TryInto; + #[test] fn curve_to_ed_compatible() { use super::*; @@ -97,4 +174,61 @@ mod tests { assert_eq!(ed_pk1, ed_pk2); } + + #[test] + fn blinding() { + // Test the ed25519 blinding function. + // + // These test vectors are from our ed25519 implementation and related + // functions. These were automatically generated by the + // ed25519_exts_ref.py script in little-t-tor and they are also used by + // little-t-tor and onionbalance: + let pubkeys = vec![ + b"c2247870536a192d142d056abefca68d6193158e7c1a59c1654c954eccaff894", + b"1519a3b15816a1aafab0b213892026ebf5c0dc232c58b21088d88cb90e9b940d", + b"081faa81992e360ea22c06af1aba096e7a73f1c665bc8b3e4e531c46455fd1dd", + b"73cfa1189a723aad7966137cbffa35140bb40d7e16eae4c40b79b5f0360dd65a", + b"66c1a77104d86461b6f98f73acf3cd229c80624495d2d74d6fda1e940080a96b", + b"d21c294db0e64cb2d8976625786ede1d9754186ae8197a64d72f68c792eecc19", + b"c4d58b4cf85a348ff3d410dd936fa460c4f18da962c01b1963792b9dcc8a6ea6", + b"95126f14d86494020665face03f2d42ee2b312a85bc729903eb17522954a1c4a", + b"95126f14d86494020665face03f2d42ee2b312a85bc729903eb17522954a1c4a", + b"95126f14d86494020665face03f2d42ee2b312a85bc729903eb17522954a1c4a", + ]; + let params = vec![ + "54a513898b471d1d448a2f3c55c1de2c0ef718c447b04497eeb999ed32027823", + "831e9b5325b5d31b7ae6197e9c7a7baf2ec361e08248bce055908971047a2347", + "ac78a1d46faf3bfbbdc5af5f053dc6dc9023ed78236bec1760dadfd0b2603760", + "f9c84dc0ac31571507993df94da1b3d28684a12ad14e67d0a068aba5c53019fc", + "b1fe79d1dec9bc108df69f6612c72812755751f21ecc5af99663b30be8b9081f", + "81f1512b63ab5fb5c1711a4ec83d379c420574aedffa8c3368e1c3989a3a0084", + "97f45142597c473a4b0e9a12d64561133ad9e1155fe5a9807fe6af8a93557818", + "3f44f6a5a92cde816635dfc12ade70539871078d2ff097278be2a555c9859cd0", + "0000000000000000000000000000000000000000000000000000000000000000", + "1111111111111111111111111111111111111111111111111111111111111111", + ]; + let blinded_pubkeys = vec![ + "1fc1fa4465bd9d4956fdbdc9d3acb3c7019bb8d5606b951c2e1dfe0b42eaeb41", + "1cbbd4a88ce8f165447f159d9f628ada18674158c4f7c5ead44ce8eb0fa6eb7e", + "c5419ad133ffde7e0ac882055d942f582054132b092de377d587435722deb028", + "3e08d0dc291066272e313014bfac4d39ad84aa93c038478a58011f431648105f", + "59381f06acb6bf1389ba305f70874eed3e0f2ab57cdb7bc69ed59a9b8899ff4d", + "2b946a484344eb1c17c89dd8b04196a84f3b7222c876a07a4cece85f676f87d9", + "c6b585129b135f8769df2eba987e76e089e80ba3a2a6729134d3b28008ac098e", + "0eefdc795b59cabbc194c6174e34ba9451e8355108520554ec285acabebb34ac", + "312404d06a0a9de489904b18d5233e83a50b225977fa8734f2c897a73c067952", + "952a908a4a9e0e5176a2549f8f328955aca6817a9fdc59e3acec5dec50838108", + ]; + + for i in 0..pubkeys.len() { + let pk = PublicKey::from_bytes(&hex::decode(pubkeys[i]).unwrap()).unwrap(); + + let blinded_pk = blind_pubkey(&pk, hex::decode(params[i]).unwrap().try_into().unwrap()); + + assert_eq!( + hex::encode(blinded_pk.unwrap().to_bytes()), + blinded_pubkeys[i] + ); + } + } } diff --git a/tor-llcrypto/src/pk/rsa.rs b/tor-llcrypto/src/pk/rsa.rs index 0754d0320..da7aa5d77 100644 --- a/tor-llcrypto/src/pk/rsa.rs +++ b/tor-llcrypto/src/pk/rsa.rs @@ -4,15 +4,18 @@ //! verification used in the Tor directory protocol and //! similar places. //! -//! Currently, that means supporting validating PKCSv1 -//! signatures, and encoding and decoding keys from DER. -//! -//! Currently missing is signing and RSA-OEAP. +//! Currently, that means validating PKCSv1 signatures, and encoding +//! and decoding RSA public keys from DER. //! //! # Limitations: //! +//! Currently missing are support for signing and RSA-OEAP. In Tor, +//! RSA signing is only needed for relays and authorities, and +//! RSA-OAEP padding is only needed for the (obsolete) TAP protocol. +//! +//! //! XXXX This module should expose RustCrypto trait-based wrappers, -//! but the rsa crate didn't support them as of initial writing. +//! but the [`rsa`] crate didn't support them as of initial writing. use arrayref::array_ref; use std::fmt; use subtle::*; @@ -20,11 +23,15 @@ use zeroize::Zeroize; /// How many bytes are in an "RSA ID"? (This is a legacy tor /// concept, and refers to identifying a relay by a SHA1 digest -/// of its public key.) +/// of its RSA public identity key.) pub const RSA_ID_LEN: usize = 20; -/// An identifier for a Tor relay, based on its legacy RSA -/// identity key. These are used all over the Tor protocol. +/// An identifier for a Tor relay, based on its legacy RSA identity +/// key. These are used all over the Tor protocol. +/// +/// Note that for modern purposes, you should almost always identify a +/// relay by its [`crate::pk::ed25519::Ed25519Identity`] instead of +/// by this kind of identity key. #[derive(Clone, Copy, Hash, Zeroize, Ord, PartialOrd)] #[allow(clippy::derive_hash_xor_eq)] pub struct RsaIdentity { @@ -110,7 +117,7 @@ impl<'de> serde::Deserialize<'de> for RsaIdentity { } impl RsaIdentity { - /// Expose and RsaIdentity as a slice of bytes. + /// Expose an RsaIdentity as a slice of bytes. pub fn as_bytes(&self) -> &[u8] { &self.id[..] } @@ -152,7 +159,12 @@ impl From<[u8; 20]> for RsaIdentity { /// methods and traits on the type. #[derive(Clone, Debug)] pub struct PublicKey(rsa::RSAPublicKey); + /// An RSA private key. +/// +/// This is not so useful at present, since Arti currently only has +/// client support, and Tor clients never actually need RSA private +/// keys. pub struct PrivateKey(rsa::RSAPrivateKey); impl PrivateKey { diff --git a/tor-llcrypto/src/util.rs b/tor-llcrypto/src/util.rs index dc8dfbd37..2205acc6e 100644 --- a/tor-llcrypto/src/util.rs +++ b/tor-llcrypto/src/util.rs @@ -1,12 +1,12 @@ //! Utilities for cryptographic purposes //! -//! For now, this just has a workaround for some other libraries' -//! lack of full x509 support +//! For now, this module is just contains a couple of workarounds for the +//! limitations of other libraries. use simple_asn1::{oid, ASN1Block, BigUint, OID}; pub mod rand_compat; -/// Given an X.509 certificate, return its SubjectPublicKey if that key +/// Given an X.509 certificate in DER, return its SubjectPublicKey if that key /// is an RSA key. /// /// WARNING: Does not validate the X.509 certificate at all! diff --git a/tor-llcrypto/src/util/rand_compat.rs b/tor-llcrypto/src/util/rand_compat.rs index 0ad0adbf7..14b750ad1 100644 --- a/tor-llcrypto/src/util/rand_compat.rs +++ b/tor-llcrypto/src/util/rand_compat.rs @@ -1,22 +1,50 @@ //! Compatibility utilities for working with libraries that consume //! older versions of rand_core. //! -//! The dalek-crypto libraries are currently stuck on rand_core 0.5.1, -//! but everywhere else we want to use the latest rand_core (0.6.2 as -//! of this writing). +//! The dalek-crypto libraries are currently stuck on [`rand_core`] +//! 0.5.1, but everywhere else in Arti we want to use the latest +//! [`rand_core`] (0.6.2 as of this writing). The extension trait in this +//! module lets us do so. +//! +//! # Example: +//! +//! As of May 2021, if you're using the current version of +//! [`x25519-dalek`], and the latest [`rand_core`], then you can't use +//! this code, because of the compatibility issue mentioned above. +//! +//! ```compile_fail +//! use rand_core::OsRng; +//! use x25519_dalek::EphemeralSecret; +//! +//! let my_secret = EphemeralSecret::new(OsRng); +//! ``` +//! +//! But instead, you can wrap the random number generator using the +//! [`RngCompatExt`] extension trait. +//! +//! ``` +//! use tor_llcrypto::util::rand_compat::RngCompatExt; +//! use rand_core::OsRng; +//! use x25519_dalek::EphemeralSecret; +//! +//! let my_secret = EphemeralSecret::new(OsRng.rng_compat()); +//! ``` +//! +//! The wrapped RNG can be used with the old version of the RngCode +//! trait, as well as the new one. use old_rand_core::{CryptoRng as OldCryptoRng, Error as OldError, RngCore as OldRngCore}; use rand_core::{CryptoRng, Error, RngCore}; use std::convert::TryInto; -/// Extension trait for current versions of RngCore; adds a +/// Extension trait for the _current_ versions of [`RngCore`]; adds a /// compatibility-wrappper function. pub trait RngCompatExt: RngCore { /// Wrapper type returned by this trait. type Wrapper: RngCore + OldRngCore; /// Return a version of this Rng that can be used with older versions - /// of the rand_core and rand libraries. + /// of the rand_core and rand libraries, as well as the current version. fn rng_compat(self) -> Self::Wrapper; } @@ -28,6 +56,16 @@ impl RngCompatExt for T { } /// A new-style Rng, wrapped for backward compatibility. +/// +/// This object implements both the current (0.6.2) version of [`RngCore`], +/// as well as the version from 0.5.1 that the dalek-crypto functions expect. +/// +/// To get an RngWrapper, use the [`RngCompatExt`] extension trait: +/// ``` +/// use tor_llcrypto::util::rand_compat::RngCompatExt; +/// +/// let mut wrapped_rng = rand::thread_rng().rng_compat(); +/// ``` pub struct RngWrapper(T); impl From for RngWrapper { @@ -69,7 +107,7 @@ impl RngCore for RngWrapper { impl OldCryptoRng for RngWrapper {} impl CryptoRng for RngWrapper {} -/// Convert a new-ish Rng error into the type that rng_core 0.5.1 +/// Convert a new-ish Rng error into the error type that rng_core 0.5.1 /// would deliver. fn err_to_old(e: Error) -> OldError { use std::num::NonZeroU32;