Skip to main content

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}