cypheraddr/
tor.rs

1// Set of libraries for privacy-preserving networking apps
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2023 by
6//     Dr. Maxim Orlovsky <orlovsky@cyphernet.org>
7//
8// Copyright 2022-2023 Cyphernet DAO, Switzerland
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use std::fmt::{self, Display, Formatter};
23use std::str::FromStr;
24
25use base32::Alphabet;
26use cypher::ed25519::PublicKey;
27use cypher::{EcPk, EcPkInvalid};
28use sha3::Digest;
29
30const ALPHABET: Alphabet = Alphabet::RFC4648 { padding: false };
31pub const ONION_V3_RAW_LEN: usize = 35;
32
33#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
34#[cfg_attr(
35    feature = "serde",
36    derive(Serialize, Deserialize),
37    serde(into = "String", try_from = "String")
38)]
39pub struct OnionAddrV3 {
40    pk: PublicKey,
41    checksum: u16,
42}
43
44impl From<PublicKey> for OnionAddrV3 {
45    fn from(pk: PublicKey) -> Self {
46        let mut h = sha3::Sha3_256::default();
47        h.update(b".onion checksum");
48        h.update(&pk[..]);
49        h.update([3u8]);
50        let hash = h.finalize();
51        let checksum = u16::from_le_bytes([hash[0], hash[1]]);
52        Self { pk, checksum }
53    }
54}
55
56impl From<OnionAddrV3> for PublicKey {
57    fn from(onion: OnionAddrV3) -> Self { onion.pk }
58}
59
60#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
61#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
62#[display(doc_comments)]
63pub enum OnionAddrDecodeError {
64    /// onion address has a version number {0} which is not supported.
65    UnsupportedVersion(u8),
66
67    /// onion address contains invalid public key.
68    #[from(EcPkInvalid)]
69    InvalidKey,
70
71    /// onion address contains invalid checksum.
72    InvalidChecksum,
73}
74
75impl OnionAddrV3 {
76    pub fn into_public_key(self) -> PublicKey { self.pk }
77
78    pub fn from_raw_bytes(bytes: [u8; ONION_V3_RAW_LEN]) -> Result<Self, OnionAddrDecodeError> {
79        let mut pk = [0u8; 32];
80        let mut checksum = [0u8; 2];
81        let version = bytes[ONION_V3_RAW_LEN - 1];
82
83        if version != 3 {
84            return Err(OnionAddrDecodeError::UnsupportedVersion(version));
85        }
86
87        pk.copy_from_slice(&bytes[..32]);
88        checksum.copy_from_slice(&bytes[32..34]);
89
90        let pk = PublicKey::from_pk_compressed(pk)?;
91        let checksum = u16::from_le_bytes(checksum);
92        let addr = OnionAddrV3::from(pk);
93
94        if addr.checksum != checksum {
95            return Err(OnionAddrDecodeError::InvalidChecksum);
96        }
97
98        Ok(addr)
99    }
100
101    pub fn into_raw_bytes(self) -> [u8; ONION_V3_RAW_LEN] {
102        let mut data = [0u8; ONION_V3_RAW_LEN];
103        data[..32].copy_from_slice(self.pk.as_slice());
104        data[32..34].copy_from_slice(&self.checksum.to_le_bytes());
105        data[ONION_V3_RAW_LEN - 1] = 3;
106        data
107    }
108
109    pub fn checksum(self) -> u16 { self.checksum }
110}
111
112#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
113#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
114#[display(doc_comments)]
115pub enum OnionAddrParseError {
116    /// onion address {0} doesn't end with `.onion` suffix.
117    NoSuffix(String),
118
119    /// onion address {0} has an invalid base32 encoding.
120    InvalidBase32(String),
121
122    /// onion address {0} has an invalid length.
123    InvalidLen(String),
124
125    /// version {1} encoded in address {0} doesn't match V3.
126    VersionMismatch(String, u8),
127
128    /// onion address {addr} has an invalid checksum {found} instead of {expected}.
129    InvalidChecksum {
130        expected: u16,
131        found: u16,
132        addr: String,
133    },
134
135    /// address contains invalid public key
136    #[from(EcPkInvalid)]
137    InvalidKey,
138}
139
140impl FromStr for OnionAddrV3 {
141    type Err = OnionAddrParseError;
142
143    fn from_str(s: &str) -> Result<Self, Self::Err> {
144        let stripped =
145            s.strip_suffix(".onion").ok_or_else(|| OnionAddrParseError::NoSuffix(s.to_owned()))?;
146        let data: Vec<u8> = base32::decode(ALPHABET, stripped)
147            .ok_or_else(|| OnionAddrParseError::InvalidBase32(s.to_owned()))?;
148        if data.len() != ONION_V3_RAW_LEN {
149            return Err(OnionAddrParseError::InvalidLen(s.to_owned()));
150        }
151        let ver = data[ONION_V3_RAW_LEN - 1];
152        if ver != 3 {
153            return Err(OnionAddrParseError::VersionMismatch(s.to_owned(), ver));
154        }
155        let mut key = [0u8; 32];
156        key.copy_from_slice(&data[..32]);
157        let pk = OnionAddrV3::from(PublicKey::from_pk_compressed(key)?);
158        let checksum = u16::from_le_bytes([data[32], data[33]]);
159        if pk.checksum != checksum {
160            return Err(OnionAddrParseError::InvalidChecksum {
161                expected: pk.checksum,
162                found: checksum,
163                addr: s.to_owned(),
164            });
165        }
166        Ok(pk)
167    }
168}
169
170impl Display for OnionAddrV3 {
171    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
172        let mut b32 = base32::encode(ALPHABET, &self.into_raw_bytes());
173        if !f.alternate() {
174            b32 = b32.to_lowercase();
175        }
176        write!(f, "{}.onion", b32)
177    }
178}
179
180impl From<OnionAddrV3> for String {
181    fn from(other: OnionAddrV3) -> Self { other.to_string() }
182}
183
184impl TryFrom<String> for OnionAddrV3 {
185    type Error = OnionAddrParseError;
186
187    fn try_from(value: String) -> Result<Self, Self::Error> { Self::from_str(&value) }
188}
189
190#[cfg(test)]
191mod test {
192    use super::*;
193
194    #[test]
195    fn display_from_str() {
196        let onion_str = "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion";
197        let onion = OnionAddrV3::from_str(onion_str).unwrap();
198        assert_eq!(onion_str, onion.to_string());
199    }
200
201    #[test]
202    fn binary_code() {
203        let onion =
204            OnionAddrV3::from_str("vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion")
205                .unwrap();
206        assert_eq!(onion, OnionAddrV3::from_raw_bytes(onion.into_raw_bytes()).unwrap());
207    }
208}