ic_core/sig.rs
1//! Signing through an interface, so a key need not be in memory.
2//!
3//! The signature schemes in this library take a private key as bytes. That is
4//! the wrong shape for a key held in an HSM, a TPM or a cloud key service,
5//! where the caller has a handle and the device does the arithmetic. [`Signer`]
6//! is the shape both fit: name the algorithm, pass the message, get a
7//! signature. A TLS handshake or a certificate issuer written against it does
8//! not know or care where the key lives; [`Signer::custody`] is there for the
9//! callers that must.
10//!
11//! This crate defines the interface and nothing that implements it. Software
12//! keys and the hardware backends are other crates'; verification, which needs
13//! no private key, is `ic_sig::verify`.
14//!
15//! # Encodings
16//!
17//! Public keys are DER `SubjectPublicKeyInfo`. Signatures are in the form
18//! X.509 and TLS 1.3 both carry: an ASN.1 `Ecdsa-Sig-Value` for ECDSA, and the
19//! algorithm's own bytes for Ed25519, RSA, ML-DSA, SLH-DSA and HSS/LMS. One
20//! encoding at the
21//! interface means a signer written for certificates serves a handshake
22//! unchanged.
23
24use crate::traits::RandomSource;
25use crate::Result;
26
27/// A signature algorithm: the key type and everything that goes with it.
28///
29/// Each names one entry in the ontology, by [`SignatureAlgorithm::id`] --
30/// except the twelve SLH-DSA parameter sets, which share the entry `slh-dsa`
31/// and are named as its sets are. RSA keys serve several of these; every other
32/// key serves exactly one.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[non_exhaustive]
35#[allow(non_camel_case_types)]
36pub enum SignatureAlgorithm {
37 /// ECDSA over P-256 with SHA-256.
38 EcdsaP256Sha256,
39 /// ECDSA over P-384 with SHA-384.
40 EcdsaP384Sha384,
41 /// ECDSA over P-521 with SHA-512.
42 EcdsaP521Sha512,
43 /// Ed25519.
44 Ed25519,
45 /// RSASSA-PKCS1-v1_5 with SHA-256.
46 RsaPkcs1Sha256,
47 /// RSASSA-PKCS1-v1_5 with SHA-384.
48 RsaPkcs1Sha384,
49 /// RSASSA-PKCS1-v1_5 with SHA-512.
50 RsaPkcs1Sha512,
51 /// RSASSA-PSS with SHA-256, MGF1-SHA-256 and a 32-byte salt.
52 RsaPssSha256,
53 /// RSASSA-PSS with SHA-384, MGF1-SHA-384 and a 48-byte salt.
54 RsaPssSha384,
55 /// RSASSA-PSS with SHA-512, MGF1-SHA-512 and a 64-byte salt.
56 RsaPssSha512,
57 /// ML-DSA-44 (FIPS 204), pure, with an empty context.
58 MlDsa44,
59 /// ML-DSA-65 (FIPS 204), pure, with an empty context.
60 MlDsa65,
61 /// ML-DSA-87 (FIPS 204), pure, with an empty context.
62 MlDsa87,
63 /// HSS/LMS (RFC 8554, SP 800-208). The key names its own parameters, and
64 /// the hash function with them.
65 HssLms,
66 /// SLH-DSA-SHA2-128s (FIPS 205), pure, with an empty context.
67 SlhDsaSha2_128s,
68 /// SLH-DSA-SHA2-128f (FIPS 205), pure, with an empty context.
69 SlhDsaSha2_128f,
70 /// SLH-DSA-SHA2-192s (FIPS 205), pure, with an empty context.
71 SlhDsaSha2_192s,
72 /// SLH-DSA-SHA2-192f (FIPS 205), pure, with an empty context.
73 SlhDsaSha2_192f,
74 /// SLH-DSA-SHA2-256s (FIPS 205), pure, with an empty context.
75 SlhDsaSha2_256s,
76 /// SLH-DSA-SHA2-256f (FIPS 205), pure, with an empty context.
77 SlhDsaSha2_256f,
78 /// SLH-DSA-SHAKE-128s (FIPS 205), pure, with an empty context.
79 SlhDsaShake_128s,
80 /// SLH-DSA-SHAKE-128f (FIPS 205), pure, with an empty context.
81 SlhDsaShake_128f,
82 /// SLH-DSA-SHAKE-192s (FIPS 205), pure, with an empty context.
83 SlhDsaShake_192s,
84 /// SLH-DSA-SHAKE-192f (FIPS 205), pure, with an empty context.
85 SlhDsaShake_192f,
86 /// SLH-DSA-SHAKE-256s (FIPS 205), pure, with an empty context.
87 SlhDsaShake_256s,
88 /// SLH-DSA-SHAKE-256f (FIPS 205), pure, with an empty context.
89 SlhDsaShake_256f,
90}
91
92impl SignatureAlgorithm {
93 /// Every algorithm, for callers that enumerate them.
94 pub const ALL: &'static [SignatureAlgorithm] = &[
95 Self::EcdsaP256Sha256,
96 Self::EcdsaP384Sha384,
97 Self::EcdsaP521Sha512,
98 Self::Ed25519,
99 Self::RsaPkcs1Sha256,
100 Self::RsaPkcs1Sha384,
101 Self::RsaPkcs1Sha512,
102 Self::RsaPssSha256,
103 Self::RsaPssSha384,
104 Self::RsaPssSha512,
105 Self::MlDsa44,
106 Self::MlDsa65,
107 Self::MlDsa87,
108 Self::HssLms,
109 Self::SlhDsaSha2_128s,
110 Self::SlhDsaSha2_128f,
111 Self::SlhDsaSha2_192s,
112 Self::SlhDsaSha2_192f,
113 Self::SlhDsaSha2_256s,
114 Self::SlhDsaSha2_256f,
115 Self::SlhDsaShake_128s,
116 Self::SlhDsaShake_128f,
117 Self::SlhDsaShake_192s,
118 Self::SlhDsaShake_192f,
119 Self::SlhDsaShake_256s,
120 Self::SlhDsaShake_256f,
121 ];
122
123 /// The ontology identifier of the algorithm.
124 pub const fn id(self) -> &'static str {
125 match self {
126 Self::EcdsaP256Sha256 => "ecdsa-p256-sha256",
127 Self::EcdsaP384Sha384 => "ecdsa-p384-sha384",
128 Self::EcdsaP521Sha512 => "ecdsa-p521-sha512",
129 Self::Ed25519 => "ed25519",
130 Self::RsaPkcs1Sha256 => "rsa-pkcs1-sha256",
131 Self::RsaPkcs1Sha384 => "rsa-pkcs1-sha384",
132 Self::RsaPkcs1Sha512 => "rsa-pkcs1-sha512",
133 Self::RsaPssSha256 => "rsa-pss-sha256",
134 Self::RsaPssSha384 => "rsa-pss-sha384",
135 Self::RsaPssSha512 => "rsa-pss-sha512",
136 Self::MlDsa44 => "ml-dsa-44",
137 Self::MlDsa65 => "ml-dsa-65",
138 Self::MlDsa87 => "ml-dsa-87",
139 Self::HssLms => "hss-lms",
140 Self::SlhDsaSha2_128s => "slh-dsa-sha2-128s",
141 Self::SlhDsaSha2_128f => "slh-dsa-sha2-128f",
142 Self::SlhDsaSha2_192s => "slh-dsa-sha2-192s",
143 Self::SlhDsaSha2_192f => "slh-dsa-sha2-192f",
144 Self::SlhDsaSha2_256s => "slh-dsa-sha2-256s",
145 Self::SlhDsaSha2_256f => "slh-dsa-sha2-256f",
146 Self::SlhDsaShake_128s => "slh-dsa-shake-128s",
147 Self::SlhDsaShake_128f => "slh-dsa-shake-128f",
148 Self::SlhDsaShake_192s => "slh-dsa-shake-192s",
149 Self::SlhDsaShake_192f => "slh-dsa-shake-192f",
150 Self::SlhDsaShake_256s => "slh-dsa-shake-256s",
151 Self::SlhDsaShake_256f => "slh-dsa-shake-256f",
152 }
153 }
154
155 /// The algorithm an ontology identifier names, if it is one of these.
156 pub fn from_id(id: &str) -> Option<Self> {
157 Self::ALL.iter().copied().find(|a| a.id() == id)
158 }
159
160 /// The longest signature the algorithm produces, in the encoding this
161 /// module's interface uses: a buffer this long always suffices.
162 ///
163 /// ECDSA is the DER `Ecdsa-Sig-Value`, whose length varies with the
164 /// leading bits of `r` and `s`. RSA is for the largest modulus this
165 /// library accepts, 4096 bits. HSS/LMS is for the largest parameters RFC
166 /// 8554 allows -- eight levels, each a tree of height 25 at Winternitz
167 /// width 1 with a 256-bit hash -- and most signatures are a small fraction
168 /// of it.
169 pub const fn max_signature_len(self) -> usize {
170 match self {
171 // SEQUENCE { INTEGER, INTEGER }, each integer one byte longer
172 // than the scalar when its top bit is set.
173 Self::EcdsaP256Sha256 => 72,
174 Self::EcdsaP384Sha384 => 104,
175 Self::EcdsaP521Sha512 => 139,
176 Self::Ed25519 => 64,
177 Self::RsaPkcs1Sha256
178 | Self::RsaPkcs1Sha384
179 | Self::RsaPkcs1Sha512
180 | Self::RsaPssSha256
181 | Self::RsaPssSha384
182 | Self::RsaPssSha512 => 512,
183 Self::MlDsa44 => 2420,
184 Self::MlDsa65 => 3309,
185 Self::MlDsa87 => 4627,
186 // u32(L - 1), then seven signed public keys of 56 bytes, then
187 // eight LMS signatures of 12 + 32 + 265 * 32 + 25 * 32 bytes.
188 Self::HssLms => 74988,
189 // FIPS 205 table 2.
190 Self::SlhDsaSha2_128s | Self::SlhDsaShake_128s => 7856,
191 Self::SlhDsaSha2_128f | Self::SlhDsaShake_128f => 17088,
192 Self::SlhDsaSha2_192s | Self::SlhDsaShake_192s => 16224,
193 Self::SlhDsaSha2_192f | Self::SlhDsaShake_192f => 35664,
194 Self::SlhDsaSha2_256s | Self::SlhDsaShake_256s => 29792,
195 Self::SlhDsaSha2_256f | Self::SlhDsaShake_256f => 49856,
196 }
197 }
198}
199
200/// Where a private key lives, as far as its holder can say.
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202#[non_exhaustive]
203pub enum Custody {
204 /// In this process's memory.
205 Software,
206 /// In a hardware device -- an HSM, a TPM, a smart card -- that performs the
207 /// operation itself.
208 Hardware,
209 /// In a remote service that performs the operation, such as a cloud key
210 /// management service.
211 Service,
212}
213
214impl Custody {
215 /// Stable identifier used in every serialized form.
216 pub const fn id(self) -> &'static str {
217 match self {
218 Self::Software => "software",
219 Self::Hardware => "hardware",
220 Self::Service => "service",
221 }
222 }
223}
224
225/// A private key that can sign, wherever it is held.
226///
227/// Object-safe, and `Send + Sync`: a server shares one key between the
228/// connections it is serving at once, so a signer that could not cross threads
229/// would be unusable exactly where a signer is most needed. Signing takes
230/// `&self` for the same reason; a signer with state to change guards it itself.
231///
232/// # What an implementation promises
233///
234/// - [`sign`](Signer::sign) signs `message` itself, not a digest of it: the
235/// algorithm names its own hash.
236/// - It writes nothing past the length it returns, and allocates only if the
237/// implementation must, so a software signer can serve a caller that may not
238/// allocate.
239/// - It refuses, rather than substitutes, an algorithm it did not list in
240/// [`algorithms`](Signer::algorithms).
241///
242/// # Blocking
243///
244/// `sign` returns when the signature exists. For a key in a remote service that
245/// is a network round trip made inside the call; a caller that cannot block
246/// needs to run it elsewhere and is not served by this interface alone.
247pub trait Signer: Send + Sync {
248 /// The algorithms this key signs with, most preferred first.
249 ///
250 /// The order is meaningful: a protocol negotiating an algorithm takes the
251 /// first one here that its peer and its policy also allow. Listing an
252 /// algorithm is a statement about the key, not about any protocol -- an
253 /// RSA key may list PKCS#1 v1.5 for the certificates it signs, and a TLS
254 /// 1.3 handshake still will not use it.
255 fn algorithms(&self) -> &[SignatureAlgorithm];
256
257 /// The public key, as a DER `SubjectPublicKeyInfo`.
258 ///
259 /// These must be the bytes the key's certificate carries, not a
260 /// re-encoding of the same key: callers compare the two byte for byte to
261 /// check that a certificate and a signer belong together. A signer for a
262 /// remote key reads this once, when it is made.
263 fn public_key(&self) -> &[u8];
264
265 /// Where the private key lives.
266 fn custody(&self) -> Custody;
267
268 /// Sign `message` with `algorithm`, writing the signature to the front of
269 /// `out` and returning its length.
270 ///
271 /// `rng` supplies randomness for the algorithms that use it; a device that
272 /// draws its own ignores it. `out` shorter than the signature is refused
273 /// with `InvalidLength`, with nothing written and nothing consumed;
274 /// [`SignatureAlgorithm::max_signature_len`] is always long enough. An
275 /// algorithm not in [`algorithms`](Signer::algorithms) is refused with
276 /// `InvalidParameter`.
277 fn sign(
278 &self,
279 algorithm: SignatureAlgorithm,
280 message: &[u8],
281 rng: &mut dyn RandomSource,
282 out: &mut [u8],
283 ) -> Result<usize>;
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn identifiers_are_unique_and_round_trip() {
292 for (i, a) in SignatureAlgorithm::ALL.iter().enumerate() {
293 assert_eq!(SignatureAlgorithm::from_id(a.id()), Some(*a));
294 assert!(SignatureAlgorithm::ALL[..i]
295 .iter()
296 .all(|b| b.id() != a.id()));
297 }
298 assert_eq!(SignatureAlgorithm::from_id("ecdsa-p256-sha1"), None);
299 }
300
301 /// The trait is object-safe, which is the property protocols rely on.
302 #[test]
303 fn a_signer_can_be_a_trait_object() {
304 struct Fixed;
305 impl Signer for Fixed {
306 fn algorithms(&self) -> &[SignatureAlgorithm] {
307 &[SignatureAlgorithm::Ed25519]
308 }
309 fn public_key(&self) -> &[u8] {
310 &[]
311 }
312 fn custody(&self) -> Custody {
313 Custody::Hardware
314 }
315 fn sign(
316 &self,
317 _: SignatureAlgorithm,
318 _: &[u8],
319 _: &mut dyn RandomSource,
320 out: &mut [u8],
321 ) -> Result<usize> {
322 out[0] = 7;
323 Ok(1)
324 }
325 }
326 struct NoRng;
327 impl RandomSource for NoRng {
328 fn fill(&mut self, _: &mut [u8]) -> Result<()> {
329 Ok(())
330 }
331 }
332 // Shared across threads, as a server shares its key.
333 fn shareable<T: Send + Sync + ?Sized>(_: &T) {}
334 let signer: &dyn Signer = &Fixed;
335 shareable(signer);
336 let mut out = [0u8; 4];
337 assert_eq!(
338 signer
339 .sign(SignatureAlgorithm::Ed25519, b"m", &mut NoRng, &mut out)
340 .unwrap(),
341 1
342 );
343 assert_eq!(signer.custody().id(), "hardware");
344 }
345}