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