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. 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}