1use crate::error::{Context, Error, ensure};
2use ring::signature::{
3 ECDSA_P256_SHA256_ASN1, ECDSA_P384_SHA384_ASN1, ED25519, RSA_PKCS1_2048_8192_SHA256,
4 RSA_PKCS1_2048_8192_SHA384, RSA_PKCS1_2048_8192_SHA512, RSA_PSS_2048_8192_SHA256,
5 RSA_PSS_2048_8192_SHA384, RSA_PSS_2048_8192_SHA512, UnparsedPublicKey, VerificationAlgorithm,
6};
7use rustls::{
8 Certificate, DigitallySignedStruct, ServerName, SignatureScheme,
9 client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
10};
11use sha2::{Digest, Sha256};
12use std::{
13 collections::HashMap,
14 path::{Path, PathBuf},
15 sync::{Arc, Mutex},
16 time::SystemTime,
17};
18
19pub struct Pins {
20 directory: PinDirectory,
21 memory: Mutex<HashMap<String, [u8; 32]>>,
22}
23
24enum PinDirectory {
25 Ephemeral,
26 Persistent(PathBuf),
27}
28
29impl Pins {
30 pub fn ephemeral() -> Self {
31 tracing::warn!(
32 "electrum certificate pins are not persisted; trust on first use lasts this session only"
33 );
34 Self {
35 directory: PinDirectory::Ephemeral,
36 memory: Mutex::new(HashMap::new()),
37 }
38 }
39
40 pub fn in_directory(cache_dir: &Path) -> Result<Self, Error> {
41 let directory = cache_dir.join("electrum-pins");
42 if !directory.try_exists()? {
43 std::fs::create_dir_all(cache_dir)?;
44 urma_io::create_private_directory(&directory)?;
45 }
46 Ok(Self {
47 directory: PinDirectory::Persistent(directory),
48 memory: Mutex::new(HashMap::new()),
49 })
50 }
51
52 pub fn check(&self, key: &str, digest: [u8; 32]) -> Result<(), Error> {
53 let mut memory = match self.memory.lock() {
54 Ok(memory) => memory,
55 Err(poisoned) => {
56 tracing::error!("electrum pin table lock poisoned");
57 poisoned.into_inner()
58 }
59 };
60 let known = match memory.get(key) {
61 Some(pinned) => Some(*pinned),
62 None => self.load(key)?,
63 };
64 match known {
65 Some(pinned) => {
66 ensure!(
67 pinned == digest,
68 "electrum server {key} presented a certificate that differs from its pinned certificate; refusing"
69 );
70 memory.insert(key.to_owned(), digest);
71 Ok(())
72 }
73 None => {
74 self.persist(key, digest)?;
75 memory.insert(key.to_owned(), digest);
76 tracing::warn!(
77 server = key,
78 pin = hex::encode(digest),
79 "electrum certificate pinned on first use"
80 );
81 Ok(())
82 }
83 }
84 }
85
86 fn load(&self, key: &str) -> Result<Option<[u8; 32]>, Error> {
87 let PinDirectory::Persistent(directory) = &self.directory else {
88 return Ok(None);
89 };
90 let path = directory.join(format!("{key}.sha256"));
91 if !path.try_exists()? {
92 return Ok(None);
93 }
94 let text = String::from_utf8(urma_io::read_bounded(&path, 128)?)?;
95 let bytes = hex::decode(text.trim())?;
96 Ok(Some(<[u8; 32]>::try_from(bytes.as_slice())?))
97 }
98
99 fn persist(&self, key: &str, digest: [u8; 32]) -> Result<(), Error> {
100 let PinDirectory::Persistent(directory) = &self.directory else {
101 return Ok(());
102 };
103 let path = directory.join(format!("{key}.sha256"));
104 urma_io::write_new(&path, format!("{}\n", hex::encode(digest)).as_bytes())?;
105 Ok(())
106 }
107}
108
109pub(crate) struct Pinned {
110 pub(crate) pins: Arc<Pins>,
111 pub(crate) key: String,
112}
113
114impl ServerCertVerifier for Pinned {
115 fn verify_server_cert(
116 &self,
117 end_entity: &Certificate,
118 _intermediates: &[Certificate],
119 _server_name: &ServerName,
120 _scts: &mut dyn Iterator<Item = &[u8]>,
121 _ocsp_response: &[u8],
122 _now: SystemTime,
123 ) -> Result<ServerCertVerified, rustls::Error> {
124 let digest: [u8; 32] = Sha256::digest(&end_entity.0).into();
125 match self.pins.check(&self.key, digest) {
126 Ok(()) => Ok(ServerCertVerified::assertion()),
127 Err(error) => {
128 tracing::error!(server = %self.key, %error, "electrum certificate rejected");
129 Err(rustls::Error::General(error.to_string()))
130 }
131 }
132 }
133
134 fn verify_tls12_signature(
135 &self,
136 message: &[u8],
137 cert: &Certificate,
138 dss: &DigitallySignedStruct,
139 ) -> Result<HandshakeSignatureValid, rustls::Error> {
140 self.verify_signature(message, cert, dss)
141 }
142
143 fn verify_tls13_signature(
144 &self,
145 message: &[u8],
146 cert: &Certificate,
147 dss: &DigitallySignedStruct,
148 ) -> Result<HandshakeSignatureValid, rustls::Error> {
149 self.verify_signature(message, cert, dss)
150 }
151
152 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
153 vec![
154 SignatureScheme::ECDSA_NISTP384_SHA384,
155 SignatureScheme::ECDSA_NISTP256_SHA256,
156 SignatureScheme::ED25519,
157 SignatureScheme::RSA_PSS_SHA512,
158 SignatureScheme::RSA_PSS_SHA384,
159 SignatureScheme::RSA_PSS_SHA256,
160 SignatureScheme::RSA_PKCS1_SHA512,
161 SignatureScheme::RSA_PKCS1_SHA384,
162 SignatureScheme::RSA_PKCS1_SHA256,
163 ]
164 }
165
166 fn request_scts(&self) -> bool {
167 false
168 }
169}
170
171impl Pinned {
172 fn verify_signature(
173 &self,
174 message: &[u8],
175 cert: &Certificate,
176 dss: &DigitallySignedStruct,
177 ) -> Result<HandshakeSignatureValid, rustls::Error> {
178 match verify_handshake(message, &cert.0, dss) {
179 Ok(()) => Ok(HandshakeSignatureValid::assertion()),
180 Err(error) => {
181 tracing::error!(server = %self.key, %error, "electrum handshake signature rejected");
182 Err(rustls::Error::General(error.to_string()))
183 }
184 }
185 }
186}
187
188enum PublicKey<'a> {
189 Rsa(&'a [u8]),
190 P256(&'a [u8]),
191 P384(&'a [u8]),
192 Ed25519(&'a [u8]),
193}
194
195fn verify_handshake(
196 message: &[u8],
197 certificate: &[u8],
198 dss: &DigitallySignedStruct,
199) -> Result<(), Error> {
200 let key = public_key(certificate)?;
201 let (algorithm, bytes): (&'static dyn VerificationAlgorithm, &[u8]) = match (dss.scheme, key) {
202 (SignatureScheme::RSA_PKCS1_SHA256, PublicKey::Rsa(k)) => (&RSA_PKCS1_2048_8192_SHA256, k),
203 (SignatureScheme::RSA_PKCS1_SHA384, PublicKey::Rsa(k)) => (&RSA_PKCS1_2048_8192_SHA384, k),
204 (SignatureScheme::RSA_PKCS1_SHA512, PublicKey::Rsa(k)) => (&RSA_PKCS1_2048_8192_SHA512, k),
205 (SignatureScheme::RSA_PSS_SHA256, PublicKey::Rsa(k)) => (&RSA_PSS_2048_8192_SHA256, k),
206 (SignatureScheme::RSA_PSS_SHA384, PublicKey::Rsa(k)) => (&RSA_PSS_2048_8192_SHA384, k),
207 (SignatureScheme::RSA_PSS_SHA512, PublicKey::Rsa(k)) => (&RSA_PSS_2048_8192_SHA512, k),
208 (SignatureScheme::ECDSA_NISTP256_SHA256, PublicKey::P256(k)) => {
209 (&ECDSA_P256_SHA256_ASN1, k)
210 }
211 (SignatureScheme::ECDSA_NISTP384_SHA384, PublicKey::P384(k)) => {
212 (&ECDSA_P384_SHA384_ASN1, k)
213 }
214 (SignatureScheme::ED25519, PublicKey::Ed25519(k)) => (&ED25519, k),
215 (scheme, _) => {
216 return Err(Error::Unsupported(format!(
217 "handshake signature scheme {scheme:?} does not match the pinned certificate key"
218 )));
219 }
220 };
221 UnparsedPublicKey::new(algorithm, bytes)
222 .verify(message, dss.signature())
223 .map_err(|cause| Error::Invalid(format!("handshake signature invalid: {cause}")))
224}
225
226fn element(bytes: &[u8]) -> Result<(u8, &[u8], &[u8]), Error> {
227 let tag = *bytes.first().context("certificate DER truncated")?;
228 let first = *bytes.get(1).context("certificate DER truncated")?;
229 let (length, header) = if first < 0x80 {
230 (usize::from(first), 2)
231 } else {
232 let count = usize::from(first & 0x7f);
233 ensure!(
234 (1..=3).contains(&count),
235 "certificate DER length form unsupported"
236 );
237 let mut length = 0_usize;
238 for offset in 0..count {
239 let byte = *bytes.get(2 + offset).context("certificate DER truncated")?;
240 length = (length << 8) | usize::from(byte);
241 }
242 (length, 2 + count)
243 };
244 let end = header
245 .checked_add(length)
246 .context("certificate DER length overflow")?;
247 let content = bytes
248 .get(header..end)
249 .context("certificate DER truncated")?;
250 Ok((tag, content, &bytes[end..]))
251}
252
253fn sequence(bytes: &[u8]) -> Result<(&[u8], &[u8]), Error> {
254 let (tag, content, rest) = element(bytes)?;
255 ensure!(tag == 0x30, "certificate DER expected a SEQUENCE");
256 Ok((content, rest))
257}
258
259fn public_key(certificate: &[u8]) -> Result<PublicKey<'_>, Error> {
260 let (body, _trailer) = sequence(certificate)?;
261 let (tbs, _signature) = sequence(body)?;
262 let (tag, _content, after_first) = element(tbs)?;
263 let mut rest = if tag == 0xA0 { after_first } else { tbs };
264 for _field in 0..5 {
265 let (_tag, _content, after) = element(rest)?;
266 rest = after;
267 }
268 let (spki, _extensions) = sequence(rest)?;
269 let (algorithm, key_rest) = sequence(spki)?;
270 let (tag, oid, params) = element(algorithm)?;
271 ensure!(tag == 0x06, "certificate key algorithm is not an OID");
272 let (tag, bits, _after_key) = element(key_rest)?;
273 ensure!(
274 tag == 0x03 && bits.first() == Some(&0),
275 "certificate key is not a byte-aligned BIT STRING"
276 );
277 let key = &bits[1..];
278 match oid {
279 [0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01] => Ok(PublicKey::Rsa(key)),
280 [0x2B, 0x65, 0x70] => Ok(PublicKey::Ed25519(key)),
281 [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01] => {
282 let (tag, curve, _after_curve) = element(params)?;
283 ensure!(tag == 0x06, "certificate EC curve is not an OID");
284 match curve {
285 [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07] => Ok(PublicKey::P256(key)),
286 [0x2B, 0x81, 0x04, 0x00, 0x22] => Ok(PublicKey::P384(key)),
287 other => Err(Error::Unsupported(format!(
288 "certificate EC curve {} unsupported",
289 hex::encode(other)
290 ))),
291 }
292 }
293 other => Err(Error::Unsupported(format!(
294 "certificate key algorithm {} unsupported",
295 hex::encode(other)
296 ))),
297 }
298}