Skip to main content

yacme_key/
lib.rs

1//! # Encryption keys for ACME protocol certificate issuance.
2//!
3//! ACME requires that accounts be identified by an asymmetric public/private key pair
4//! used for signing all requests. This crate implements the types which handle those
5//! keypairs, using RustCrypto libraries under the hood. The types here are not directly
6//! the RustCrypto primatives, as they abstract over the actual algorithm in use without
7//! polluting the entire ACME interface with generics.
8
9#![deny(unsafe_code)]
10#![deny(missing_docs)]
11
12use std::fmt;
13
14pub mod cert;
15mod ecdsa;
16pub mod jwk;
17
18use crate::ecdsa::EcdsaSigningKey;
19
20pub use crate::ecdsa::EcdsaAlgorithm;
21
22pub use p256;
23pub use pkcs8;
24pub use signature;
25
26/// A signature, produced by signing a key over a message.
27///
28/// Interally, just bytes which must be encoded in some fashion
29/// for use.
30pub struct Signature(Vec<u8>);
31
32impl fmt::Debug for Signature {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.debug_tuple("Signature").field(&self.0).finish()
35    }
36}
37
38impl AsRef<[u8]> for Signature {
39    fn as_ref(&self) -> &[u8] {
40        self.0.as_slice()
41    }
42}
43
44impl From<Vec<u8>> for Signature {
45    fn from(value: Vec<u8>) -> Self {
46        Signature(value)
47    }
48}
49
50/// Error returned when signatures failed
51pub struct SigningError;
52
53/// The public half of a signing key
54pub struct PublicKey(Box<dyn PublicKeyAlgorithm>);
55
56impl PublicKey {
57    /// A JSON web key type suitable for use in a JWS header
58    /// and for use when creating a key thumbprint for authentication
59    /// challenges.
60    pub fn to_jwk(&self) -> crate::jwk::Jwk {
61        self.0.as_jwk()
62    }
63
64    /// The pkcs8 algorithm identifier for this key.
65    pub fn algorithm(&self) -> pkcs8::AlgorithmIdentifier {
66        self.0.algorithm()
67    }
68
69    /// The key, as raw bytes.
70    pub fn as_bytes(&self) -> Vec<u8> {
71        self.0.as_bytes()
72    }
73}
74
75impl PartialEq for PublicKey {
76    fn eq(&self, other: &Self) -> bool {
77        self.to_jwk() == other.to_jwk()
78    }
79}
80
81impl Eq for PublicKey {}
82
83impl<T> From<Box<T>> for PublicKey
84where
85    T: PublicKeyAlgorithm + 'static,
86{
87    fn from(value: Box<T>) -> Self {
88        PublicKey(value as _)
89    }
90}
91
92impl fmt::Debug for PublicKey {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.debug_tuple("PublicKey").finish()
95    }
96}
97
98impl AsRef<[u8]> for PublicKey {
99    fn as_ref(&self) -> &[u8] {
100        todo!("PublicKey to bytes")
101    }
102}
103
104/// Supported signing key types for YACME
105///
106/// Currently, only ECDSA P256 is supported.
107///
108/// ```
109/// # use yacme_key::SignatureKind;
110/// # use yacme_key::EcdsaAlgorithm;
111/// let algorithm = SignatureKind::Ecdsa(EcdsaAlgorithm::P256);
112/// ```
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum SignatureKind {
115    /// ECDSA using a variety of potential curves
116    Ecdsa(EcdsaAlgorithm),
117}
118
119impl SignatureKind {
120    /// Create a new, secure random signing key for this signature kind.
121    pub fn random(&self) -> SigningKey {
122        match self {
123            SignatureKind::Ecdsa(ecdsa) => SigningKey(ecdsa.random().into()),
124        }
125    }
126}
127
128/// Signing key to authenticate an ACME account
129///
130/// Read a signing key from a PKCS#8 PEM-encoded file:
131///
132/// ```
133/// # use yacme_key::{SignatureKind, SigningKey};
134/// # use yacme_key::EcdsaAlgorithm;
135/// let private = "-----BEGIN PRIVATE KEY-----
136/// MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm1tOPOUt86+QgoiJ
137/// kirpEl69+tUxLP848nPw9BbyW1ShRANCAASGWHBM2Lj7uUA4i9/jKSDp1vw4+iyu
138/// hxVHBELXhxaD/LOQKtQAOhumi1uCTg8mMTrFrUM1VOtF8R0+rjrB3UXd
139/// -----END PRIVATE KEY-----";
140/// let key = SigningKey::from_pkcs8_pem(private,
141///    SignatureKind::Ecdsa(yacme_key::EcdsaAlgorithm::P256))
142/// .unwrap();
143/// ```
144///
145/// ⚠️ *Do not use this key, it is an example used for testing only*!
146#[derive(Debug, PartialEq, Eq)]
147pub struct SigningKey(InnerSigningKey);
148
149impl SigningKey {
150    /// Read a private key from a PEM-encoded PKCS#8 format key (usually the kind produced by
151    /// OpenSSL) into this signing key document. You must provide the signature kind, as the code
152    /// does not currently infer the type of key in use.
153    pub fn from_pkcs8_pem(data: &str, signature: SignatureKind) -> Result<Self, pkcs8::Error> {
154        match signature {
155            SignatureKind::Ecdsa(algorithm) => Ok(SigningKey(InnerSigningKey::Ecdsa(Box::new(
156                EcdsaSigningKey::from_pkcs8_pem(data, algorithm)?,
157            )))),
158        }
159    }
160
161    /// Return the signature kind, used to recover the key type from a PEM file.
162    pub fn kind(&self) -> SignatureKind {
163        match &self.0 {
164            InnerSigningKey::Ecdsa(key) => key.kind(),
165        }
166    }
167}
168
169impl SigningKey {
170    /// The public key half of this signing key.
171    pub fn public_key(&self) -> PublicKey {
172        self.0.public_key()
173    }
174
175    /// A JSON web key type suitable for use in a JWS header
176    /// and for use when creating a key thumbprint for authentication
177    /// challenges.
178    pub fn as_jwk(&self) -> crate::jwk::Jwk {
179        self.0.as_jwk()
180    }
181
182    /// The pkcs8 algorithm identifier for this key.
183    pub fn algorithm(&self) -> pkcs8::AlgorithmIdentifier {
184        self.0.algorithm()
185    }
186}
187
188impl pkcs8::EncodePrivateKey for SigningKey {
189    fn to_pkcs8_der(&self) -> pkcs8::Result<der::SecretDocument> {
190        self.0.to_pkcs8_der()
191    }
192}
193
194impl signature::Signer<Signature> for SigningKey {
195    fn try_sign(&self, msg: &[u8]) -> Result<Signature, ::ecdsa::Error> {
196        self.0.try_sign(msg)
197    }
198}
199
200impl signature::DigestSigner<sha2::Sha256, Signature> for SigningKey {
201    fn try_sign_digest(&self, digest: sha2::Sha256) -> Result<Signature, ::ecdsa::Error> {
202        self.0.try_sign_digest(digest)
203    }
204}
205
206pub(crate) enum InnerSigningKey {
207    Ecdsa(Box<dyn SigningKeyAlgorithm>),
208    //TODO: Consider supporting other algorithms?
209}
210
211impl InnerSigningKey {
212    pub(crate) fn as_jwk(&self) -> crate::jwk::Jwk {
213        match self {
214            InnerSigningKey::Ecdsa(ecdsa) => ecdsa.as_jwk(),
215        }
216    }
217
218    pub(crate) fn public_key(&self) -> PublicKey {
219        match self {
220            InnerSigningKey::Ecdsa(ecdsa) => ecdsa.public_key(),
221        }
222    }
223
224    pub(crate) fn algorithm(&self) -> pkcs8::AlgorithmIdentifier {
225        match self {
226            InnerSigningKey::Ecdsa(ecdsa) => ecdsa.algorithm(),
227        }
228    }
229}
230
231impl From<EcdsaSigningKey> for InnerSigningKey {
232    fn from(value: EcdsaSigningKey) -> Self {
233        InnerSigningKey::Ecdsa(Box::new(value) as _)
234    }
235}
236
237impl PartialEq for InnerSigningKey {
238    fn eq(&self, other: &Self) -> bool {
239        self.public_key() == other.public_key()
240    }
241}
242
243impl Eq for InnerSigningKey {}
244
245impl pkcs8::EncodePrivateKey for InnerSigningKey {
246    fn to_pkcs8_der(&self) -> pkcs8::Result<der::SecretDocument> {
247        match self {
248            InnerSigningKey::Ecdsa(key) => key.to_pkcs8_der(),
249        }
250    }
251}
252
253impl signature::Signer<Signature> for InnerSigningKey {
254    fn try_sign(&self, msg: &[u8]) -> Result<Signature, ::ecdsa::Error> {
255        match self {
256            InnerSigningKey::Ecdsa(key) => key.try_sign(msg),
257        }
258    }
259}
260
261impl signature::DigestSigner<sha2::Sha256, Signature> for InnerSigningKey {
262    fn try_sign_digest(&self, digest: sha2::Sha256) -> Result<Signature, ::ecdsa::Error> {
263        match self {
264            InnerSigningKey::Ecdsa(key) => key.try_sign_digest(digest),
265        }
266    }
267}
268
269impl fmt::Debug for InnerSigningKey {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        match self {
272            InnerSigningKey::Ecdsa(_) => f.write_str("ECDSA-Key"),
273        }
274    }
275}
276
277pub(crate) trait SigningKeyAlgorithm:
278    signature::Signer<Signature> + pkcs8::EncodePrivateKey
279{
280    fn as_jwk(&self) -> crate::jwk::Jwk;
281    fn public_key(&self) -> PublicKey;
282    fn try_sign_digest(&self, digest: sha2::Sha256) -> Result<Signature, ::ecdsa::Error>;
283    fn algorithm(&self) -> pkcs8::AlgorithmIdentifier;
284    fn kind(&self) -> SignatureKind;
285}
286
287pub(crate) trait PublicKeyAlgorithm {
288    fn as_jwk(&self) -> crate::jwk::Jwk;
289    fn algorithm(&self) -> pkcs8::AlgorithmIdentifier;
290    fn as_bytes(&self) -> Vec<u8>;
291}
292
293#[cfg(test)]
294mod test {
295    use std::sync::Arc;
296
297    use base64ct::LineEnding;
298    use pkcs8::EncodePrivateKey;
299
300    #[macro_export]
301    macro_rules! key {
302        ($name:tt) => {
303            $crate::test::key(include_str!(concat!(
304                env!("CARGO_MANIFEST_DIR"),
305                "/../reference-keys/",
306                $name,
307                ".pem"
308            )))
309        };
310    }
311
312    pub(crate) fn key(private: &str) -> Arc<crate::SigningKey> {
313        let key = crate::SigningKey::from_pkcs8_pem(
314            private,
315            crate::SignatureKind::Ecdsa(crate::EcdsaAlgorithm::P256),
316        )
317        .unwrap();
318
319        Arc::new(key)
320    }
321
322    #[test]
323    fn roundtrip_key_through_pkcs8() {
324        let key = key!("ec-p255");
325        let pkcs8 = key.to_pkcs8_pem(LineEnding::default()).unwrap();
326        let key2 = crate::SigningKey::from_pkcs8_pem(&pkcs8, key.kind()).unwrap();
327
328        assert_eq!(key.as_ref(), &key2);
329    }
330}