use crate::{base64, error::TorError};
use base32::{self, Alphabet};
use curve25519_dalek::Scalar;
use ed25519_dalek::{
hazmat::{raw_sign, ExpandedSecretKey},
Signature, SignatureError, Signer, SigningKey, Verifier, VerifyingKey,
};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use sha2::Sha512;
use sha3::{Digest, Sha3_256};
use std::str::FromStr;
use zeroize::{Zeroize, ZeroizeOnDrop};
const TOR_VERSION: u8 = 3;
#[derive(
Clone,
Deserialize,
Serialize,
Debug,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
Zeroize,
ZeroizeOnDrop,
)]
pub struct TorServiceId(String);
impl From<TorServiceId> for String {
fn from(id: TorServiceId) -> String {
id.0.clone()
}
}
impl std::fmt::Display for TorServiceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::convert::From<VerifyingKey> for TorServiceId {
fn from(verifying_key: VerifyingKey) -> Self {
let version = &[TOR_VERSION];
let verifying_key_bytes = verifying_key.as_bytes();
let checksum = TorServiceId::calculate_checksum(verifying_key_bytes.as_ref());
let mut onion_bytes = verifying_key_bytes.to_vec();
onion_bytes.extend_from_slice(&checksum);
onion_bytes.extend_from_slice(version);
Self(base32::encode(Alphabet::RFC4648 { padding: false }, &onion_bytes).to_lowercase())
}
}
impl FromStr for TorServiceId {
type Err = TorError;
fn from_str(service_id: &str) -> Result<Self, Self::Err> {
let onion_bytes = match base32::decode(Alphabet::RFC4648 { padding: false }, service_id) {
Some(bytes) => bytes,
None => return Err(TorError::protocol_error("Error base32 decoding service ID")),
};
if onion_bytes.len() != 35 {
return Err(TorError::protocol_error("Service ID is of wrong length"));
}
let mut verifying_key_bytes = [0u8; 32];
verifying_key_bytes.copy_from_slice(&onion_bytes[..32]);
let mut checksum = [0u8; 2];
checksum.copy_from_slice(&onion_bytes[32..34]);
let verifying_checksum = Self::calculate_checksum(&verifying_key_bytes);
if checksum != verifying_checksum {
return Err(TorError::protocol_error("Invalid checksum"));
}
Ok(Self(service_id.to_string()))
}
}
impl TorServiceId {
pub fn generate() -> Self {
let signing_key = SigningKey::generate(&mut OsRng);
signing_key.verifying_key().into()
}
fn calculate_checksum(verifying_key_bytes: &[u8]) -> [u8; 2] {
let mut checksum_bytes = ".onion checksum".as_bytes().to_vec();
checksum_bytes.extend_from_slice(verifying_key_bytes);
checksum_bytes.extend_from_slice(&[TOR_VERSION]);
let mut checksum = [0u8; 2];
checksum
.copy_from_slice(&Sha3_256::default().chain_update(&checksum_bytes).finalize()[..2]);
checksum
}
pub fn verifying_key(&self) -> Result<VerifyingKey, TorError> {
let onion_bytes = match base32::decode(Alphabet::RFC4648 { padding: false }, &self.0) {
Some(bytes) => bytes,
None => return Err(TorError::protocol_error("Error base32 decoding service ID")),
};
let mut verifying_key_bytes = [0u8; 32];
verifying_key_bytes.copy_from_slice(&onion_bytes[..32]);
let verifying_key = match VerifyingKey::from_bytes(&verifying_key_bytes) {
Ok(key) => key,
Err(_) => {
return Err(TorError::protocol_error(
"Error parsing verifying key from bytes",
))
}
};
Ok(verifying_key)
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn short_id(&self) -> &str {
&self.0[..10]
}
pub fn onion_hostname(&self) -> String {
format!("{}.onion", self.0)
}
}
pub type TorBlob = [u8; 64];
#[serde_as]
#[derive(ZeroizeOnDrop)]
pub struct TorEd25519SigningKey {
scalar_bytes: [u8; 32],
expanded_secret_key: ExpandedSecretKey,
}
impl TorEd25519SigningKey {
fn expanded_secret_key(&self) -> &ExpandedSecretKey {
&self.expanded_secret_key
}
pub fn verifying_key(&self) -> VerifyingKey {
VerifyingKey::from(self.expanded_secret_key())
}
pub fn scalar(&self) -> Scalar {
self.expanded_secret_key.scalar
}
pub fn from_blob(blob: &str) -> Result<Self, TorError> {
let blob_bytes: [u8; 64] = match base64::decode(blob) {
Ok(bytes) => match bytes.try_into() {
Ok(bytes) => bytes,
Err(_) => Err(TorError::protocol_error("Wrong number of bytes in blob"))?,
},
Err(error) => Err(TorError::protocol_error(&format!(
"Error decoding blob: {error}"
)))?,
};
Ok(Self::from_bytes(blob_bytes))
}
pub fn from_bytes(bytes: [u8; 64]) -> Self {
let mut scalar_bytes = [0u8; 32];
scalar_bytes.copy_from_slice(&bytes[..32]);
Self {
scalar_bytes,
expanded_secret_key: ExpandedSecretKey::from_bytes(&bytes),
}
}
pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
self.verifying_key().verify(message, signature)
}
pub fn to_blob(&self) -> String {
let mut blob_bytes = [0u8; 64];
blob_bytes[..32].copy_from_slice(&self.scalar_bytes);
blob_bytes[32..].copy_from_slice(&self.expanded_secret_key.hash_prefix);
base64::encode(&blob_bytes)
}
}
impl std::str::FromStr for TorEd25519SigningKey {
type Err = TorError;
fn from_str(key: &str) -> Result<Self, Self::Err> {
Self::from_blob(key)
}
}
impl std::fmt::Display for TorEd25519SigningKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_blob())
}
}
impl From<TorBlob> for TorEd25519SigningKey {
fn from(blob: TorBlob) -> Self {
Self::from_bytes(blob)
}
}
impl From<SigningKey> for TorEd25519SigningKey {
fn from(signing_key: SigningKey) -> Self {
let expanded_secret_key = ExpandedSecretKey::from(signing_key.as_bytes());
Self {
scalar_bytes: *expanded_secret_key.scalar.as_bytes(),
expanded_secret_key,
}
}
}
impl Signer<Signature> for TorEd25519SigningKey {
fn try_sign(&self, message: &[u8]) -> Result<Signature, SignatureError> {
Ok(raw_sign::<Sha512>(
self.expanded_secret_key(),
message,
&self.verifying_key(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
use std::str::FromStr;
#[test]
fn test_ed25519v3_service_id() -> Result<(), anyhow::Error> {
let base64_blob = "0H/jnBeWzMoU1MGNRQPnmd8JqlpTNS3UeTiDOMyPTGGXXpLd0KinCtQbcgz2fCYjbzfK3ElJ7x3zGCkB1fAtAA==";
let signing_key = TorEd25519SigningKey::from_blob(base64_blob)?;
let public_key = signing_key.verifying_key();
assert_eq!(
"vvqbbaknxi6w44t6rplzh7nmesfzw3rjujdijpqsu5xl3nhlkdscgqad",
TorServiceId::from(public_key).as_str(),
);
Ok(())
}
#[test]
fn test_tor_generated_tor_ed25519v3_sign_verify() -> Result<(), anyhow::Error> {
let message = b"This is my very secret message";
let base64_blob = "0H/jnBeWzMoU1MGNRQPnmd8JqlpTNS3UeTiDOMyPTGGXXpLd0KinCtQbcgz2fCYjbzfK3ElJ7x3zGCkB1fAtAA==";
let signing_key = TorEd25519SigningKey::from_blob(base64_blob)?;
let signature = signing_key.sign(message);
assert!(signing_key.verify(message, &signature).is_ok());
Ok(())
}
#[test]
fn test_ed25519_tor_ed25519v3_expanded_keys() -> Result<(), anyhow::Error> {
let dalek_signing_key = SigningKey::generate(&mut OsRng);
let dalek_expanded_key = ExpandedSecretKey::from(dalek_signing_key.as_bytes());
let signing_key = TorEd25519SigningKey::from(dalek_signing_key);
let expanded_key = signing_key.expanded_secret_key();
assert_eq!(dalek_expanded_key.scalar, expanded_key.scalar);
assert_eq!(dalek_expanded_key.hash_prefix, expanded_key.hash_prefix);
Ok(())
}
#[test]
fn test_self_generated_tor_ed25519v3_sign_verify() -> Result<(), anyhow::Error> {
let message = b"This is my very secret message";
let dalek_signing_key = SigningKey::generate(&mut OsRng);
let signing_key = TorEd25519SigningKey::from(dalek_signing_key);
let signature = signing_key.sign(message);
assert!(signing_key.verify(message, &signature).is_ok());
Ok(())
}
#[test]
fn test_to_from_blob() -> Result<(), anyhow::Error> {
let blob_in = "0H/jnBeWzMoU1MGNRQPnmd8JqlpTNS3UeTiDOMyPTGGXXpLd0KinCtQbcgz2fCYjbzfK3ElJ7x3zGCkB1fAtAA==";
let signing_key = TorEd25519SigningKey::from_blob(blob_in)?;
let blob_out = signing_key.to_blob();
assert_eq!(blob_in, blob_out);
Ok(())
}
#[test]
fn test_blob_reduced() -> Result<(), anyhow::Error> {
let blob_in = "0H/jnBeWzMoU1MGNRQPnmd8JqlpTNS3UeTiDOMyPTGGXXpLd0KinCtQbcgz2fCYjbzfK3ElJ7x3zGCkB1fAtAA==";
let bytes = base64::decode(blob_in)?;
let scalar_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
let scalar = Scalar::from_bytes_mod_order(scalar_bytes);
let output = scalar.to_bytes();
let scalar2 = Scalar::from_bytes_mod_order(output);
let output2 = scalar2.to_bytes();
assert_eq!(output, output2);
Ok(())
}
#[test]
fn test_serialize_service_id() -> Result<(), anyhow::Error> {
let service_id =
TorServiceId::from_str("vvqbbaknxi6w44t6rplzh7nmesfzw3rjujdijpqsu5xl3nhlkdscgqad")?;
let expected = "\"vvqbbaknxi6w44t6rplzh7nmesfzw3rjujdijpqsu5xl3nhlkdscgqad\"";
let json_out = serde_json::to_string(&service_id)?;
assert_eq!(expected, json_out);
let deserialized_service_id: TorServiceId = serde_json::from_str(&json_out)?;
assert_eq!(service_id, deserialized_service_id);
Ok(())
}
#[test]
fn test_ed25519_signing_key_to_tor_signing_key() -> Result<(), Box<dyn std::error::Error>> {
let mut csprng = OsRng;
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
let tor_signing_key: TorEd25519SigningKey = signing_key.clone().into();
assert_eq!(signing_key.verifying_key(), tor_signing_key.verifying_key());
Ok(())
}
}