Skip to content

Commit e3e6ffe

Browse files
committed
Allow library users to bring their own crypto providers
1 parent c8c16a2 commit e3e6ffe

File tree

5 files changed

+381
-157
lines changed

5 files changed

+381
-157
lines changed

src/account.rs

Lines changed: 130 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use std::sync::Arc;
44
#[cfg(feature = "time")]
55
use std::time::{Duration, SystemTime};
66

7-
use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine};
87
use http::header::LOCATION;
98
#[cfg(feature = "time")]
109
use http::header::USER_AGENT;
@@ -22,17 +21,17 @@ use serde::{Deserialize, Serialize};
2221

2322
#[cfg(feature = "hyper-rustls")]
2423
use crate::DefaultClient;
24+
use crate::crypto::{CryptoProvider, key_thumbprint};
2525
use crate::order::Order;
2626
use crate::types::{
27-
AccountCredentials, AuthorizationStatus, Empty, Header, JoseJson, Jwk, KeyOrKeyId, NewAccount,
27+
AccountCredentials, AuthorizationStatus, Empty, Header, JoseJson, KeyOrKeyId, NewAccount,
2828
NewAccountPayload, NewOrder, OrderState, Problem, ProfileMeta, RevocationRequest, Signer,
29-
SigningAlgorithm,
3029
};
3130
#[cfg(feature = "time")]
3231
use crate::types::{CertificateIdentifier, RenewalInfo};
3332
#[cfg(feature = "time")]
3433
use crate::{BodyWrapper, CRATE_USER_AGENT, retry_after};
35-
use crate::{BytesResponse, Client, Error, HttpClient, crypto, nonce_from_response};
34+
use crate::{BytesResponse, Client, Error, HttpClient, nonce_from_response};
3635

3736
/// An ACME account as described in RFC 8555 (section 7.1.2)
3837
///
@@ -55,6 +54,7 @@ impl Account {
5554
pub fn builder() -> Result<AccountBuilder, Error> {
5655
Ok(AccountBuilder {
5756
http: Box::new(DefaultClient::try_new()?),
57+
crypto: CryptoProvider::builtin(),
5858
})
5959
}
6060

@@ -73,14 +73,43 @@ impl Account {
7373
match roots.add(root_der) {
7474
Ok(()) => Ok(AccountBuilder {
7575
http: Box::new(DefaultClient::with_roots(roots)?),
76+
crypto: CryptoProvider::builtin(),
7677
}),
7778
Err(err) => Err(Error::Other(err.into())),
7879
}
7980
}
8081

8182
/// Create an account builder with the given HTTP client
83+
#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
8284
pub fn builder_with_http(http: Box<dyn HttpClient>) -> AccountBuilder {
83-
AccountBuilder { http }
85+
AccountBuilder {
86+
http,
87+
crypto: CryptoProvider::builtin(),
88+
}
89+
}
90+
91+
/// Create an account builder with the given [`CryptoProvider`] and default HTTP client
92+
///
93+
/// Use this when you need a custom crypto backend but are fine with the default
94+
/// HTTP client. For a custom HTTP client too, use
95+
/// [`Account::builder_with_crypto_and_http()`] instead.
96+
#[cfg(feature = "hyper-rustls")]
97+
pub fn builder_with_crypto(crypto: &'static CryptoProvider) -> Result<AccountBuilder, Error> {
98+
Ok(AccountBuilder {
99+
http: Box::new(DefaultClient::try_new()?),
100+
crypto,
101+
})
102+
}
103+
104+
/// Create an account builder with the given [`CryptoProvider`] and HTTP client
105+
///
106+
/// Use this when you need both a custom crypto backend and a custom HTTP client.
107+
/// For the default HTTP client, use [`Account::builder_with_crypto()`] instead.
108+
pub fn builder_with_crypto_and_http(
109+
crypto: &'static CryptoProvider,
110+
http: Box<dyn HttpClient>,
111+
) -> AccountBuilder {
112+
AccountBuilder { http, crypto }
84113
}
85114

86115
/// Create a new order based on the given [`NewOrder`]
@@ -227,15 +256,15 @@ impl Account {
227256
struct NewKey<'a> {
228257
account: &'a str,
229258
#[serde(rename = "oldKey")]
230-
old_key: Jwk,
259+
old_key: &'a serde_json::Value,
231260
}
232261

233-
let (new_key, new_key_pkcs8) = Key::generate_pkcs8()?;
262+
let (new_key, new_key_pkcs8) = Key::generate_pkcs8_with(self.inner.key.provider)?;
234263
let mut header = new_key.header(Some("nonce"), new_key_url);
235264
header.nonce = None;
236265
let payload = NewKey {
237266
account: &self.inner.id,
238-
old_key: Jwk::new(&self.inner.key.inner),
267+
old_key: &self.inner.key.jwk,
239268
};
240269

241270
let body = JoseJson::new(Some(&payload), header, &new_key)?;
@@ -353,10 +382,11 @@ impl AccountInner {
353382
async fn from_credentials(
354383
credentials: AccountCredentials,
355384
http: Box<dyn HttpClient>,
385+
crypto: &'static CryptoProvider,
356386
) -> Result<Self, Error> {
357387
Ok(Self {
358388
id: credentials.id,
359-
key: Key::from_pkcs8_der(credentials.key_pkcs8)?,
389+
key: Key::from_pkcs8_der_with(credentials.key_pkcs8, crypto)?,
360390
client: Arc::new(match (credentials.directory, credentials.urls) {
361391
(Some(directory_url), _) => Client::new(directory_url, http).await?,
362392
(None, Some(directory)) => Client {
@@ -390,20 +420,20 @@ impl AccountInner {
390420
}
391421

392422
impl Signer for AccountInner {
393-
type Signature = <Key as Signer>::Signature;
423+
type Signature = Vec<u8>;
394424

395425
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
396426
debug_assert!(nonce.is_some());
397427
Header {
398-
alg: self.key.signing_algorithm,
428+
alg: &self.key.alg,
399429
key: KeyOrKeyId::KeyId(&self.id),
400430
nonce,
401431
url,
402432
}
403433
}
404434

405-
fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
406-
self.key.sign(payload)
435+
fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, Error> {
436+
self.key.inner.sign(payload)
407437
}
408438
}
409439

@@ -412,16 +442,27 @@ impl Signer for AccountInner {
412442
/// Create one via [`Account::builder()`] or [`Account::builder_with_http()`].
413443
pub struct AccountBuilder {
414444
http: Box<dyn HttpClient>,
445+
crypto: &'static CryptoProvider,
415446
}
416447

417448
impl AccountBuilder {
449+
/// Override the [`CryptoProvider`] for this builder
450+
///
451+
/// By default, the builder uses [`CryptoProvider::builtin()`].
452+
pub fn crypto_provider(mut self, provider: &'static CryptoProvider) -> Self {
453+
self.crypto = provider;
454+
self
455+
}
456+
418457
/// Restore an existing account from the given credentials
419458
///
420459
/// The [`AccountCredentials`] type is opaque, but supports deserialization.
421460
#[allow(clippy::wrong_self_convention)]
422461
pub async fn from_credentials(self, credentials: AccountCredentials) -> Result<Account, Error> {
423462
Ok(Account {
424-
inner: Arc::new(AccountInner::from_credentials(credentials, self.http).await?),
463+
inner: Arc::new(
464+
AccountInner::from_credentials(credentials, self.http, self.crypto).await?,
465+
),
425466
})
426467
}
427468

@@ -435,7 +476,7 @@ impl AccountBuilder {
435476
directory_url: String,
436477
external_account: Option<&ExternalAccountKey>,
437478
) -> Result<(Account, AccountCredentials), Error> {
438-
let (key, key_pkcs8) = Key::generate_pkcs8()?;
479+
let (key, key_pkcs8) = Key::generate_pkcs8_with(self.crypto)?;
439480
Self::create_inner(
440481
account,
441482
(key, key_pkcs8),
@@ -512,7 +553,7 @@ impl AccountBuilder {
512553
Ok(Account {
513554
inner: Arc::new(AccountInner {
514555
id,
515-
key: Key::from_pkcs8_der(key_pkcs8_der)?,
556+
key: Key::from_pkcs8_der_with(key_pkcs8_der, self.crypto)?,
516557
client: Arc::new(Client::new(directory_url, self.http).await?),
517558
}),
518559
})
@@ -529,7 +570,7 @@ impl AccountBuilder {
529570
external_account_binding: external_account
530571
.map(|eak| {
531572
JoseJson::new(
532-
Some(&Jwk::new(&key.inner)),
573+
Some(&key.jwk),
533574
eak.header(None, &client.directory.new_account),
534575
eak,
535576
)
@@ -577,68 +618,93 @@ impl AccountBuilder {
577618

578619
/// Private account key used to sign requests
579620
pub struct Key {
580-
rng: crypto::SystemRandom,
581-
signing_algorithm: SigningAlgorithm,
582-
inner: crypto::EcdsaKeyPair,
621+
inner: Arc<dyn crate::crypto::AccountKey>,
622+
pub(crate) provider: &'static CryptoProvider,
623+
jwk: serde_json::Value,
624+
alg: String,
583625
pub(crate) thumb: String,
584626
}
585627

586628
impl Key {
587-
/// Generate a new ECDSA P-256 key pair
629+
/// Generate a new key pair using the built-in [`CryptoProvider`]
630+
#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
588631
#[deprecated(since = "0.8.3", note = "use `generate_pkcs8()` instead")]
589632
pub fn generate() -> Result<(Self, PrivateKeyDer<'static>), Error> {
590633
let (key, pkcs8) = Self::generate_pkcs8()?;
591634
Ok((key, PrivateKeyDer::Pkcs8(pkcs8)))
592635
}
593636

594-
/// Generate a new ECDSA P-256 key pair
637+
/// Generate a new key pair using the built-in [`CryptoProvider`]
638+
#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
595639
pub fn generate_pkcs8() -> Result<(Self, PrivatePkcs8KeyDer<'static>), Error> {
596-
let rng = crypto::SystemRandom::new();
597-
let pkcs8 =
598-
crypto::EcdsaKeyPair::generate_pkcs8(&crypto::ECDSA_P256_SHA256_FIXED_SIGNING, &rng)
599-
.map_err(|_| Error::Crypto)?;
640+
Self::generate_pkcs8_with(CryptoProvider::builtin())
641+
}
642+
643+
/// Generate a new key pair using the given [`CryptoProvider`]
644+
///
645+
/// The key type depends on the provider. The built-in providers use ECDSA P-256.
646+
pub fn generate_pkcs8_with(
647+
provider: &'static CryptoProvider,
648+
) -> Result<(Self, PrivatePkcs8KeyDer<'static>), Error> {
649+
let (key, pkcs8) = provider.account_key.generate_key()?;
650+
let thumb = key_thumbprint(key.as_ref(), provider.sha256)?;
651+
let jwk = key.to_jwk()?;
652+
let alg = key.jws_algorithm().to_owned();
600653
Ok((
601-
Self::new(pkcs8.as_ref(), rng)?,
602-
PrivatePkcs8KeyDer::from(pkcs8.as_ref().to_vec()),
654+
Self {
655+
inner: key,
656+
provider,
657+
jwk,
658+
alg,
659+
thumb,
660+
},
661+
pkcs8,
603662
))
604663
}
605664

606-
/// Create a new key from the given PKCS#8 DER-encoded private key
607-
///
608-
/// Currently, only ECDSA P-256 keys are supported.
665+
/// Create a key from the given PKCS#8 DER-encoded private key using the built-in
666+
/// [`CryptoProvider`]
667+
#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
609668
pub fn from_pkcs8_der(pkcs8_der: PrivatePkcs8KeyDer<'_>) -> Result<Self, Error> {
610-
Self::new(pkcs8_der.secret_pkcs8_der(), crypto::SystemRandom::new())
669+
Self::from_pkcs8_der_with(pkcs8_der, CryptoProvider::builtin())
611670
}
612671

613-
fn new(pkcs8_der: &[u8], rng: crypto::SystemRandom) -> Result<Self, Error> {
614-
let inner = crypto::p256_key_pair_from_pkcs8(pkcs8_der, &rng)?;
615-
let thumb = BASE64_URL_SAFE_NO_PAD.encode(Jwk::thumb_sha256(&inner)?);
672+
/// Create a key from the given PKCS#8 DER-encoded private key using the given
673+
/// [`CryptoProvider`]
674+
pub fn from_pkcs8_der_with(
675+
pkcs8_der: PrivatePkcs8KeyDer<'_>,
676+
provider: &'static CryptoProvider,
677+
) -> Result<Self, Error> {
678+
let owned = PrivatePkcs8KeyDer::from(pkcs8_der.secret_pkcs8_der().to_vec());
679+
let key = provider.account_key.load_key(owned)?;
680+
let thumb = key_thumbprint(key.as_ref(), provider.sha256)?;
681+
let jwk = key.to_jwk()?;
682+
let alg = key.jws_algorithm().to_owned();
616683
Ok(Self {
617-
rng,
618-
signing_algorithm: SigningAlgorithm::Es256,
619-
inner,
684+
inner: key,
685+
provider,
686+
jwk,
687+
alg,
620688
thumb,
621689
})
622690
}
623691
}
624692

625693
impl Signer for Key {
626-
type Signature = crypto::Signature;
694+
type Signature = Vec<u8>;
627695

628696
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
629697
debug_assert!(nonce.is_some());
630698
Header {
631-
alg: self.signing_algorithm,
632-
key: KeyOrKeyId::from_key(&self.inner),
699+
alg: &self.alg,
700+
key: KeyOrKeyId::Key(&self.jwk),
633701
nonce,
634702
url,
635703
}
636704
}
637705

638-
fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
639-
self.inner
640-
.sign(&self.rng, payload)
641-
.map_err(|_| Error::Crypto)
706+
fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, Error> {
707+
self.inner.sign(payload)
642708
}
643709
}
644710

@@ -647,36 +713,48 @@ impl Signer for Key {
647713
/// See RFC 8555 section 7.3.4 for more information.
648714
pub struct ExternalAccountKey {
649715
id: String,
650-
key: crypto::hmac::Key,
716+
key_value: Vec<u8>,
717+
provider: &'static CryptoProvider,
651718
}
652719

653720
impl ExternalAccountKey {
654-
/// Create a new external account key
721+
/// Create a new external account key using the built-in [`CryptoProvider`]
655722
///
656723
/// Note that the `key_value` argument represents the raw key value, so if the caller holds
657724
/// an encoded key value (for example, using base64), decode it before passing it in.
725+
#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
658726
pub fn new(id: String, key_value: &[u8]) -> Self {
727+
Self::new_with(id, key_value, CryptoProvider::builtin())
728+
}
729+
730+
/// Create a new external account key using the given [`CryptoProvider`]
731+
///
732+
/// Note that the `key_value` argument represents the raw key value, so if the caller holds
733+
/// an encoded key value (for example, using base64), decode it before passing it in.
734+
pub fn new_with(id: String, key_value: &[u8], provider: &'static CryptoProvider) -> Self {
659735
Self {
660736
id,
661-
key: crypto::hmac::Key::new(crypto::hmac::HMAC_SHA256, key_value),
737+
key_value: key_value.to_vec(),
738+
provider,
662739
}
663740
}
664741
}
665742

666743
impl Signer for ExternalAccountKey {
667-
type Signature = crypto::hmac::Tag;
744+
type Signature = Vec<u8>;
668745

669746
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
670747
debug_assert_eq!(nonce, None);
671748
Header {
672-
alg: SigningAlgorithm::Hs256,
749+
alg: "HS256",
673750
key: KeyOrKeyId::KeyId(&self.id),
674751
nonce,
675752
url,
676753
}
677754
}
678755

679-
fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
680-
Ok(crypto::hmac::sign(&self.key, payload))
756+
fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, Error> {
757+
let tag = self.provider.hmac_sha256.sign(&self.key_value, payload);
758+
Ok((*tag).as_ref().to_vec())
681759
}
682760
}

0 commit comments

Comments
 (0)