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
|
* Store/recover your account credentials by serializing/deserializing
|
||||||
* Fully async implementation with tracing support
|
* Fully async implementation with tracing support
|
||||||
* Support for processing multiple orders concurrently
|
* Support for processing multiple orders concurrently
|
||||||
|
* Support for external account binding
|
||||||
* Uses hyper with rustls and Tokio for HTTP requests
|
* Uses hyper with rustls and Tokio for HTTP requests
|
||||||
* Uses *ring* for ECDSA signing
|
* Uses *ring* for ECDSA signing
|
||||||
* Minimum supported Rust version: 1.64
|
* Minimum supported Rust version: 1.64
|
||||||
|
|
|
@ -26,6 +26,7 @@ async fn main() -> anyhow::Result<()> {
|
||||||
only_return_existing: false,
|
only_return_existing: false,
|
||||||
},
|
},
|
||||||
LetsEncrypt::Staging.url(),
|
LetsEncrypt::Staging.url(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.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::header::{CONTENT_TYPE, LOCATION};
|
||||||
use hyper::{Body, Method, Request, Response};
|
use hyper::{Body, Method, Request, Response};
|
||||||
use ring::digest::{digest, SHA256};
|
use ring::digest::{digest, SHA256};
|
||||||
|
use ring::hmac;
|
||||||
use ring::rand::SystemRandom;
|
use ring::rand::SystemRandom;
|
||||||
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
|
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
@ -23,8 +24,8 @@ pub use types::{
|
||||||
Identifier, LetsEncrypt, NewAccount, NewOrder, OrderState, OrderStatus, Problem,
|
Identifier, LetsEncrypt, NewAccount, NewOrder, OrderState, OrderStatus, Problem,
|
||||||
};
|
};
|
||||||
use types::{
|
use types::{
|
||||||
DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, Signer,
|
DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, NewAccountPayload,
|
||||||
SigningAlgorithm,
|
Signer, SigningAlgorithm,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An ACME order as described in RFC 8555 (section 7.1.3)
|
/// 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`]
|
/// 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 client = Client::new(server_url).await?;
|
||||||
let key = Key::generate()?;
|
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
|
let rsp = client
|
||||||
.post(Some(account), None, &key, &client.urls.new_account)
|
.post(Some(&payload), None, &key, &client.urls.new_account)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let account_url = rsp
|
let account_url = rsp
|
||||||
|
@ -312,7 +330,8 @@ impl AccountInner {
|
||||||
impl Signer for AccountInner {
|
impl Signer for AccountInner {
|
||||||
type Signature = <Key as Signer>::Signature;
|
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 {
|
Header {
|
||||||
alg: self.key.signing_algorithm,
|
alg: self.key.signing_algorithm,
|
||||||
key: KeyOrKeyId::KeyId(&self.id),
|
key: KeyOrKeyId::KeyId(&self.id),
|
||||||
|
@ -362,7 +381,7 @@ impl Client {
|
||||||
};
|
};
|
||||||
|
|
||||||
let nonce = nonce.ok_or("no nonce found")?;
|
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()
|
let request = Request::builder()
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.uri(url)
|
.uri(url)
|
||||||
|
@ -415,7 +434,8 @@ impl Key {
|
||||||
impl Signer for Key {
|
impl Signer for Key {
|
||||||
type Signature = ring::signature::Signature;
|
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 {
|
Header {
|
||||||
alg: self.signing_algorithm,
|
alg: self.signing_algorithm,
|
||||||
key: KeyOrKeyId::from_key(&self.inner),
|
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> {
|
fn nonce_from_response(rsp: &Response<Body>) -> Option<String> {
|
||||||
rsp.headers()
|
rsp.headers()
|
||||||
.get(REPLAY_NONCE)
|
.get(REPLAY_NONCE)
|
||||||
|
|
16
src/types.rs
16
src/types.rs
|
@ -118,7 +118,8 @@ pub(crate) struct Header<'a> {
|
||||||
pub(crate) alg: SigningAlgorithm,
|
pub(crate) alg: SigningAlgorithm,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub(crate) key: KeyOrKeyId<'a>,
|
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,
|
pub(crate) url: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,6 +231,15 @@ pub struct NewOrder<'a> {
|
||||||
pub identifiers: &'a [Identifier],
|
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
|
/// Input data for [Account](crate::Account) creation
|
||||||
///
|
///
|
||||||
/// To be passed into [Account::create()](crate::Account::create()).
|
/// To be passed into [Account::create()](crate::Account::create()).
|
||||||
|
@ -286,7 +296,7 @@ impl JoseJson {
|
||||||
pub(crate) trait Signer {
|
pub(crate) trait Signer {
|
||||||
type Signature: AsRef<[u8]>;
|
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>;
|
fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error>;
|
||||||
}
|
}
|
||||||
|
@ -383,6 +393,8 @@ impl LetsEncrypt {
|
||||||
pub(crate) enum SigningAlgorithm {
|
pub(crate) enum SigningAlgorithm {
|
||||||
/// ECDSA using P-256 and SHA-256
|
/// ECDSA using P-256 and SHA-256
|
||||||
Es256,
|
Es256,
|
||||||
|
/// HMAC with SHA-256,
|
||||||
|
Hs256,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
|
Loading…
Reference in New Issue