Add support for EAB in newAccount requests
This commit is contained in:
parent
c4f715216f
commit
b4ca9c11be
|
@ -17,6 +17,7 @@ specification.
|
|||
* Store/recover your account credentials by serializing/deserializing
|
||||
* Fully async implementation with tracing support
|
||||
* Support for processing multiple orders concurrently
|
||||
* Support for external account binding
|
||||
* Uses hyper with rustls and Tokio for HTTP requests
|
||||
* Uses *ring* for ECDSA signing
|
||||
* Minimum supported Rust version: 1.64
|
||||
|
|
|
@ -26,6 +26,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
only_return_existing: false,
|
||||
},
|
||||
LetsEncrypt::Staging.url(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
70
src/lib.rs
70
src/lib.rs
|
@ -12,6 +12,7 @@ use hyper::client::HttpConnector;
|
|||
use hyper::header::{CONTENT_TYPE, LOCATION};
|
||||
use hyper::{Body, Method, Request, Response};
|
||||
use ring::digest::{digest, SHA256};
|
||||
use ring::hmac;
|
||||
use ring::rand::SystemRandom;
|
||||
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
@ -23,8 +24,8 @@ pub use types::{
|
|||
Identifier, LetsEncrypt, NewAccount, NewOrder, OrderState, OrderStatus, Problem,
|
||||
};
|
||||
use types::{
|
||||
DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, Signer,
|
||||
SigningAlgorithm,
|
||||
DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, NewAccountPayload,
|
||||
Signer, SigningAlgorithm,
|
||||
};
|
||||
|
||||
/// An ACME order as described in RFC 8555 (section 7.1.3)
|
||||
|
@ -204,11 +205,28 @@ impl Account {
|
|||
}
|
||||
|
||||
/// Create a new account on the `server_url` with the information in [`NewAccount`]
|
||||
pub async fn create(account: &NewAccount<'_>, server_url: &str) -> Result<Account, Error> {
|
||||
pub async fn create(
|
||||
account: &NewAccount<'_>,
|
||||
server_url: &str,
|
||||
external_account: Option<&ExternalAccountKey>,
|
||||
) -> Result<Account, Error> {
|
||||
let client = Client::new(server_url).await?;
|
||||
let key = Key::generate()?;
|
||||
let payload = NewAccountPayload {
|
||||
new_account: account,
|
||||
external_account_binding: external_account
|
||||
.map(|eak| {
|
||||
JoseJson::new(
|
||||
Some(&Jwk::new(&key.inner)),
|
||||
eak.header(None, &client.urls.new_account),
|
||||
eak,
|
||||
)
|
||||
})
|
||||
.transpose()?,
|
||||
};
|
||||
|
||||
let rsp = client
|
||||
.post(Some(account), None, &key, &client.urls.new_account)
|
||||
.post(Some(&payload), None, &key, &client.urls.new_account)
|
||||
.await?;
|
||||
|
||||
let account_url = rsp
|
||||
|
@ -312,7 +330,8 @@ impl AccountInner {
|
|||
impl Signer for AccountInner {
|
||||
type Signature = <Key as Signer>::Signature;
|
||||
|
||||
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: &'n str, url: &'u str) -> Header<'n> {
|
||||
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
|
||||
debug_assert!(nonce.is_some());
|
||||
Header {
|
||||
alg: self.key.signing_algorithm,
|
||||
key: KeyOrKeyId::KeyId(&self.id),
|
||||
|
@ -362,7 +381,7 @@ impl Client {
|
|||
};
|
||||
|
||||
let nonce = nonce.ok_or("no nonce found")?;
|
||||
let body = JoseJson::new(payload, signer.header(&nonce, url), signer)?;
|
||||
let body = JoseJson::new(payload, signer.header(Some(&nonce), url), signer)?;
|
||||
let request = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(url)
|
||||
|
@ -415,7 +434,8 @@ impl Key {
|
|||
impl Signer for Key {
|
||||
type Signature = ring::signature::Signature;
|
||||
|
||||
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: &'n str, url: &'u str) -> Header<'n> {
|
||||
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
|
||||
debug_assert!(nonce.is_some());
|
||||
Header {
|
||||
alg: self.signing_algorithm,
|
||||
key: KeyOrKeyId::from_key(&self.inner),
|
||||
|
@ -471,6 +491,42 @@ impl fmt::Debug for KeyAuthorization {
|
|||
}
|
||||
}
|
||||
|
||||
/// A HMAC key used to link account creation requests to an external account
|
||||
///
|
||||
/// See RFC 8555 section 7.3.4 for more information.
|
||||
pub struct ExternalAccountKey {
|
||||
id: String,
|
||||
key: hmac::Key,
|
||||
}
|
||||
|
||||
impl ExternalAccountKey {
|
||||
/// Create a new external account key
|
||||
pub fn new(id: String, key_value: &[u8]) -> Self {
|
||||
Self {
|
||||
id,
|
||||
key: hmac::Key::new(hmac::HMAC_SHA256, key_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Signer for ExternalAccountKey {
|
||||
type Signature = hmac::Tag;
|
||||
|
||||
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
|
||||
debug_assert_eq!(nonce, None);
|
||||
Header {
|
||||
alg: SigningAlgorithm::Hs256,
|
||||
key: KeyOrKeyId::KeyId(&self.id),
|
||||
nonce,
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
|
||||
Ok(hmac::sign(&self.key, payload))
|
||||
}
|
||||
}
|
||||
|
||||
fn nonce_from_response(rsp: &Response<Body>) -> Option<String> {
|
||||
rsp.headers()
|
||||
.get(REPLAY_NONCE)
|
||||
|
|
16
src/types.rs
16
src/types.rs
|
@ -118,7 +118,8 @@ pub(crate) struct Header<'a> {
|
|||
pub(crate) alg: SigningAlgorithm,
|
||||
#[serde(flatten)]
|
||||
pub(crate) key: KeyOrKeyId<'a>,
|
||||
pub(crate) nonce: &'a str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) nonce: Option<&'a str>,
|
||||
pub(crate) url: &'a str,
|
||||
}
|
||||
|
||||
|
@ -230,6 +231,15 @@ pub struct NewOrder<'a> {
|
|||
pub identifiers: &'a [Identifier],
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct NewAccountPayload<'a> {
|
||||
#[serde(flatten)]
|
||||
pub(crate) new_account: &'a NewAccount<'a>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) external_account_binding: Option<JoseJson>,
|
||||
}
|
||||
|
||||
/// Input data for [Account](crate::Account) creation
|
||||
///
|
||||
/// To be passed into [Account::create()](crate::Account::create()).
|
||||
|
@ -286,7 +296,7 @@ impl JoseJson {
|
|||
pub(crate) trait Signer {
|
||||
type Signature: AsRef<[u8]>;
|
||||
|
||||
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: &'n str, url: &'u str) -> Header<'n>;
|
||||
fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n>;
|
||||
|
||||
fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error>;
|
||||
}
|
||||
|
@ -383,6 +393,8 @@ impl LetsEncrypt {
|
|||
pub(crate) enum SigningAlgorithm {
|
||||
/// ECDSA using P-256 and SHA-256
|
||||
Es256,
|
||||
/// HMAC with SHA-256,
|
||||
Hs256,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
Loading…
Reference in New Issue