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