//! Key type wrappers of various kinds used in onion services. //! //! (We define wrappers here as a safety net against confusing one kind of //! key for another: without a system like this, it can get pretty hard making //! sure that each key is used only in the right way.) // TODO hs: Maybe we want to remove some of these types as we build the // implementation; for example, if we find that a single key type is visible // only in a single module somewhere else, it would make sense to just use the // underlying type. use digest::Digest; use tor_llcrypto::d::Sha3_256; use tor_llcrypto::pk::{curve25519, ed25519, keymanip}; use crate::macros::{define_bytes, define_pk_keypair}; use crate::time::TimePeriod; define_bytes! { /// The identity of a v3 onion service. (KP_hs_id) /// /// This is the decoded and validated ed25519 public key that is encoded as a /// `${base32}.onion` address. When expanded, it is a public key whose /// corresponding secret key is controlled by the onion service. /// /// Note: This is a separate type from [`OnionIdKey`] because it is about 6x /// smaller. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct OnionId([u8; 32]); } define_pk_keypair! { /// The identity of a v3 onion service, expanded into a public key. (KP_hs_id) /// /// This is the decoded and validated ed25519 public key that is encoded as /// a `${base32}.onion` address. /// /// This key is not used to sign or validate anything on its own; instead, it is /// used to derive a `BlindedOnionIdKey`. /// /// Note: This is a separate type from [`OnionId`] because it is about 6x /// larger. It is an expanded form, used for doing actual cryptography. // // NOTE: This is called the "master" key in rend-spec-v3, but we're deprecating // that vocabulary generally. pub struct OnionIdKey(ed25519::PublicKey) / /// /// This is stored as an expanded secret key, for compatibility with the C /// tor implementation, and in order to support custom-generated addresses. /// /// (About custom generated addresses: When making a vanity onion address, /// it is inefficient to search for a compact secret key `s` and compute /// `SHA512(s)=(a,r)` and `A=aB` until you find an `s` that produces an `A` /// that you like. Instead, most folks use the algorithm of /// rend-spec-v3.txt appendix C, wherein you search for a good `a` directly /// by repeatedly adding `8B` to A until you find an `A` you like. The only /// major drawback is that once you have found a good `a`, you can't get an /// `s` for it, since you presumably can't find SHA512 preimages. And that /// is why we store the private key in (a,r) form.) OnionIdSecretKey(ed25519::ExpandedSecretKey); } impl OnionIdKey { /// Return a representation of this key as an [`OnionId`]. /// /// ([`OnionId`] is much smaller, and easier to store.) pub fn id(&self) -> OnionId { OnionId(self.0.to_bytes().into()) } } impl TryFrom for OnionIdKey { type Error = signature::Error; fn try_from(value: OnionId) -> Result { ed25519::PublicKey::from_bytes(value.0.as_ref()).map(OnionIdKey) } } impl From for OnionId { fn from(value: OnionIdKey) -> Self { value.id() } } impl OnionIdKey { /// Derive the blinded key and subcredential for this identity during `cur_period`. pub fn compute_blinded_key( &self, cur_period: TimePeriod, ) -> Result<(BlindedOnionIdKey, crate::Subcredential), keymanip::BlindingError> { // TODO hs: decide whether we want to support this kind of shared secret; C Tor does not. let secret = b""; let param = self.blinding_parameter(secret, cur_period); let blinded_key = keymanip::blind_pubkey(&self.0, param)?; // rend-spec-v3 section 2.1 let subcredential_bytes: [u8; 32] = { // N_hs_subcred = H("subcredential" | N_hs_cred | blinded-public-key). // where // N_hs_cred = H("credential" | public-identity-key) let n_hs_cred: [u8; 32] = { let mut h = Sha3_256::new(); h.update(b"credential"); h.update(self.0.as_bytes()); h.finalize().into() }; let mut h = Sha3_256::new(); h.update(b"subcredential"); h.update(n_hs_cred); h.update(blinded_key.as_bytes()); h.finalize().into() }; Ok((blinded_key.into(), subcredential_bytes.into())) } /// Compute the 32-byte "blinding parameters" used to compute blinded public /// (and secret) keys. fn blinding_parameter(&self, secret: &[u8], cur_period: TimePeriod) -> [u8; 32] { // rend-spec-v3 appendix A.2 // We generate our key blinding parameter as // h = H(BLIND_STRING | A | s | B | N) // Where: // H is SHA3-256. // A is this public key. // BLIND_STRING = "Derive temporary signing key" | INT_1(0) // s is an optional secret (not implemented here.) // B is the ed25519 basepoint. // N = "key-blind" || INT_8(period_num) || INT_8(period_length). /// String used as part of input to blinding hash. const BLIND_STRING: &[u8] = b"Derive temporary signing key\0"; /// String representation of our Ed25519 basepoint. const ED25519_BASEPOINT: &[u8] = b"(15112221349535400772501151409588531511454012693041857206046113283949847762202, \ 46316835694926478169428394003475163141307993866256225615783033603165251855960)"; let mut h = Sha3_256::new(); h.update(BLIND_STRING); h.update(self.0.as_bytes()); h.update(secret); h.update(ED25519_BASEPOINT); h.update(b"key-blind"); h.update(cur_period.interval_num.to_be_bytes()); h.update(u64::from(cur_period.length_in_sec).to_be_bytes()); h.finalize().into() } } impl OnionIdSecretKey { /// Derive the blinded key and subcredential for this identity during `cur_period`. pub fn compute_blinded_key( &self, cur_period: TimePeriod, ) -> Result< ( BlindedOnionIdKey, BlindedOnionIdSecretKey, crate::Subcredential, ), keymanip::BlindingError, > { // TODO hs: as above, decide if we want this. let secret = b""; // Note: This implementation is somewhat inefficient, as it recomputes // the PublicKey, and computes our blinding parameters twice. But we // only do this on an onion service once per time period: the // performance does not matter. let public_key: OnionIdKey = ed25519::PublicKey::from(&self.0).into(); let (blinded_public_key, subcredential) = public_key.compute_blinded_key(cur_period)?; let param = public_key.blinding_parameter(secret, cur_period); let blinded_secret_key = keymanip::blind_seckey(&self.0, param)?; Ok((blinded_public_key, blinded_secret_key.into(), subcredential)) } } define_pk_keypair! { /// The "blinded" identity of a v3 onion service. (`KP_hs_blind_id`) /// /// This key is derived via a one-way transformation from an /// `OnionIdKey` and the current time period. /// /// It is used for two purposes: first, to compute an index into the HSDir /// ring, and second, to sign a `DescSigningKey`. /// /// Note: This is a separate type from [`BlindedOnionId`] because it is about 6x /// larger. It is an expanded form, used for doing actual cryptography. pub struct BlindedOnionIdKey(ed25519::PublicKey) / BlindedOnionIdSecretKey(ed25519::ExpandedSecretKey); } define_bytes! { /// A blinded onion service identity, represented in a compact format. (`KP_hs_blind_id`) /// /// Note: This is a separate type from [`BlindedOnionIdKey`] because it is about /// 6x smaller. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct BlindedOnionId([u8; 32]); } impl BlindedOnionIdKey { /// Return a representation of this key as a [`BlindedOnionId`]. /// /// ([`BlindedOnionId`] is much smaller, and easier to store.) pub fn id(&self) -> BlindedOnionId { BlindedOnionId(self.0.to_bytes().into()) } } impl TryFrom for BlindedOnionIdKey { type Error = signature::Error; fn try_from(value: BlindedOnionId) -> Result { ed25519::PublicKey::from_bytes(value.0.as_ref()).map(BlindedOnionIdKey) } } impl From for BlindedOnionId { fn from(value: BlindedOnionIdKey) -> Self { value.id() } } impl BlindedOnionIdSecretKey { /// Compute a signature of `message` with this key, using the corresponding `public_key`. pub fn sign(&self, message: &[u8], public_key: &BlindedOnionIdKey) -> ed25519::Signature { self.0.sign(message, &public_key.0) } } define_pk_keypair! { /// A key used to sign onion service descriptors. (`KP_desc_sign`) /// /// It is authenticated with a `BlindedOnionIdKeys` to prove that it belongs to /// the right onion service, and is used in turn to sign the descriptor that /// tells clients what they need to know about contacting an onion service. /// /// Onion services create a new `DescSigningKey` every time the /// `BlindedOnionIdKeys` rotates, to prevent descriptors made in one time period /// from being linkable to those made in another. /// /// Note: we use a separate signing key here, rather than using the /// BlindedOnionIdKey directly, so that the secret key for the BlindedOnionIdKey /// can be kept offline. pub struct DescSigningKey(ed25519::PublicKey) / DescSigningSecretKey(ed25519::SecretKey); } define_pk_keypair! { /// A key used to identify and authenticate an onion service at a single /// introduction point. (`KP_hs_intro_tid`) /// /// This key is included in the onion service's descriptor; a different one is /// used at each introduction point. Introduction points don't know the /// relation of this key to the onion service: they only recognize the same key /// when they see it again. pub struct IntroPtAuthKey(ed25519::PublicKey) / IntroPtAuthSecretKey(ed25519::SecretKey); } define_pk_keypair! { /// A key used in the HsNtor handshake between the client and the onion service. /// (`KP_hs_into_ntor`) /// /// The onion service chooses a different one of these to use with each /// introduction point, though it does not need to tell the introduction points /// about these keys. pub struct IntroPtEncKey(curve25519::PublicKey) / IntroPtEncSecretKey(curve25519::StaticSecret); } define_pk_keypair! { /// First type of client authorization key, used for the introduction protocol. /// (`KP_hsc_intro_auth`) /// /// This is used to sign a nonce included in an extension in the encrypted /// portion of an introduce cell. pub struct ClientIntroAuthKey(ed25519::PublicKey) / ClientIntroAuthSecretKey(ed25519::SecretKey); } define_pk_keypair! { /// Second type of client authorization key, used for onion descriptor /// decryption. (`KP_hsc_desc_enc`) /// /// Any client who knows the secret key corresponding to this key can decrypt /// the inner layer of the onion service descriptor. pub struct ClientDescAuthKey(curve25519::PublicKey) / ClientDescAuthSecretKey(curve25519::StaticSecret); } /// A set of keys to tell the client to use when connecting to an onion service. // // TODO hs pub struct ClientSecretKeys { /// Possibly, a key that is used to decrypt a descriptor. desc_auth: Option, /// Possibly, a key that is used to authenticate while /// introducing. intro_auth: Option, } #[cfg(test)] mod test { // @@ begin test lint list maintained by maint/add_warning @@ #![allow(clippy::bool_assert_comparison)] #![allow(clippy::clone_on_copy)] #![allow(clippy::dbg_macro)] #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] #![allow(clippy::single_char_pattern)] #![allow(clippy::unwrap_used)] #![allow(clippy::unchecked_duration_subtraction)] //! use hex_literal::hex; use signature::Verifier; use std::time::{Duration, SystemTime}; use tor_basic_utils::test_rng::testing_rng; use tor_llcrypto::util::rand_compat::RngCompatExt as _; use super::*; #[test] fn key_blinding_blackbox() { let mut rng = testing_rng().rng_compat(); let offset = Duration::new(12 * 60 * 60, 0); let when = TimePeriod::new(Duration::from_secs(3600), SystemTime::now(), offset).unwrap(); let keypair = ed25519::Keypair::generate(&mut rng); let id_pub = OnionIdKey::from(keypair.public); let id_sec = OnionIdSecretKey::from(ed25519::ExpandedSecretKey::from(&keypair.secret)); let (blinded_pub, subcred1) = id_pub.compute_blinded_key(when).unwrap(); let (blinded_pub2, blinded_sec, subcred2) = id_sec.compute_blinded_key(when).unwrap(); assert_eq!(subcred1.as_ref(), subcred2.as_ref()); assert_eq!(blinded_pub.0.to_bytes(), blinded_pub2.0.to_bytes()); assert_eq!(blinded_pub.id(), blinded_pub2.id()); let message = b"Here is a terribly important string to authenticate."; let other_message = b"Hey, that is not what I signed!"; let sign = blinded_sec.sign(message, &blinded_pub2); assert!(blinded_pub.as_ref().verify(message, &sign).is_ok()); assert!(blinded_pub.as_ref().verify(other_message, &sign).is_err()); } #[test] fn key_blinding_testvec() { // Test vectors generated with C tor. let id = OnionId::from(hex!( "833990B085C1A688C1D4C8B1F6B56AFAF5A2ECA674449E1D704F83765CCB7BC6" )); let id_pubkey = OnionIdKey::try_from(id).unwrap(); let id_seckey = OnionIdSecretKey::from( ed25519::ExpandedSecretKey::from_bytes(&hex!( "D8C7FF0E31295B66540D789AF3E3DF992038A9592EEA01D8B7CBA06D6E66D159 4D6167696320576F7264733A20737065697373636F62616C742062697669756D" )) .unwrap(), ); let offset = Duration::new(12 * 60 * 60, 0); let time_period = TimePeriod::new( Duration::from_secs(1440), humantime::parse_rfc3339("1970-01-22T01:50:33Z").unwrap(), offset, ) .unwrap(); assert_eq!(time_period.interval_num, 1234); let param = id_pubkey.blinding_parameter(b"", time_period); assert_eq!( param, hex!("379E50DB31FEE6775ABD0AF6FB7C371E060308F4F847DB09FE4CFE13AF602287") ); let (blinded_pub1, subcred1) = id_pubkey.compute_blinded_key(time_period).unwrap(); assert_eq!( blinded_pub1.0.to_bytes(), hex!("3A50BF210E8F9EE955AE0014F7A6917FB65EBF098A86305ABB508D1A7291B6D5") ); assert_eq!( subcred1.as_ref(), &hex!("635D55907816E8D76398A675A50B1C2F3E36B42A5CA77BA3A0441285161AE07D") ); let (blinded_pub2, blinded_sec, subcred2) = id_seckey.compute_blinded_key(time_period).unwrap(); assert_eq!(blinded_pub1.0.to_bytes(), blinded_pub2.0.to_bytes()); assert_eq!(subcred1.as_ref(), subcred2.as_ref()); assert_eq!( blinded_sec.0.to_bytes(), hex!( "A958DC83AC885F6814C67035DE817A2C604D5D2F715282079448F789B656350B 4540FE1F80AA3F7E91306B7BF7A8E367293352B14A29FDCC8C19F3558075524B" ) ); } }