Browse Source

Add tls-alpn-01 challenge support

Although the standardization is still a draft, this challenge is already
supported by Let's Encrypt.

https://datatracker.ietf.org/doc/draft-ietf-acme-tls-alpn/
pull/5/head
Rodolphe Breard 6 years ago
parent
commit
0a7deb4cdc
  1. 1
      CHANGELOG.md
  2. 6
      README.md
  3. 4
      acmed/src/acme_proto.rs
  4. 33
      acmed/src/acme_proto/structs/authorization.rs

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- An account object has been added in the configuration. - An account object has been added in the configuration.
- Failure recovery: HTTPS requests rejected by the server that are recoverable, like the badNonce error, are now retried several times before being considered a hard failure. - Failure recovery: HTTPS requests rejected by the server that are recoverable, like the badNonce error, are now retried several times before being considered a hard failure.
- The TLS-ALPN-01 challenge is now supported. The proof is a string representation of the acmeIdentifier extension. The self-signed certificate itself has to be built by a hook.
### Changed ### Changed
- In the configuration, the `email` certificate field has been replaced by the `account` field which matches an account object. - In the configuration, the `email` certificate field has been replaced by the `account` field which matches an account object.

6
README.md

@ -10,7 +10,7 @@ The Automatic Certificate Management Environment (ACME), is an internet standard
## Key features ## Key features
- HTTP-01 and DNS-01 challenges
- http-01, dns-01 and tls-alpn-01 challenges
- RSA 2048, RSA 4096, ECDSA P-256 and ECDSA P-384 certificates - RSA 2048, RSA 4096, ECDSA P-256 and ECDSA P-384 certificates
- Fully customizable challenge validation action - Fully customizable challenge validation action
- Fully customizable archiving method (yes, you can use git or anything else) - Fully customizable archiving method (yes, you can use git or anything else)
@ -22,8 +22,8 @@ The Automatic Certificate Management Environment (ACME), is an internet standard
## Planned features ## Planned features
- TLS-ALPN challenges
- daemon and certificates management via the `acmectl` tool
- A standalone server dedicated to the tls-alpn-01 challenge validation
- Daemon and certificates management via the `acmectl` tool
## Build from source ## Build from source

4
acmed/src/acme_proto.rs

@ -19,6 +19,7 @@ pub mod structs;
pub enum Challenge { pub enum Challenge {
Http01, Http01,
Dns01, Dns01,
TlsAlpn01,
} }
impl Challenge { impl Challenge {
@ -26,6 +27,7 @@ impl Challenge {
match s.to_lowercase().as_str() { match s.to_lowercase().as_str() {
"http-01" => Ok(Challenge::Http01), "http-01" => Ok(Challenge::Http01),
"dns-01" => Ok(Challenge::Dns01), "dns-01" => Ok(Challenge::Dns01),
"tls-alpn-01" => Ok(Challenge::TlsAlpn01),
_ => Err(format!("{}: unknown challenge.", s).into()), _ => Err(format!("{}: unknown challenge.", s).into()),
} }
} }
@ -36,6 +38,7 @@ impl fmt::Display for Challenge {
let s = match self { let s = match self {
Challenge::Http01 => "http-01", Challenge::Http01 => "http-01",
Challenge::Dns01 => "dns-01", Challenge::Dns01 => "dns-01",
Challenge::TlsAlpn01 => "tls-alpn-01",
}; };
write!(f, "{}", s) write!(f, "{}", s)
} }
@ -46,6 +49,7 @@ impl PartialEq<structs::Challenge> for Challenge {
match (self, other) { match (self, other) {
(Challenge::Http01, structs::Challenge::Http01(_)) => true, (Challenge::Http01, structs::Challenge::Http01(_)) => true,
(Challenge::Dns01, structs::Challenge::Dns01(_)) => true, (Challenge::Dns01, structs::Challenge::Dns01(_)) => true,
(Challenge::TlsAlpn01, structs::Challenge::TlsAlpn01(_)) => true,
_ => false, _ => false,
} }
} }

33
acmed/src/acme_proto/structs/authorization.rs

@ -8,6 +8,11 @@ use serde::Deserialize;
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
const ACME_OID: &str = "1.3.6.1.5.5.7.1";
const ID_PE_ACME_ID: usize = 31;
const DER_OCTET_STRING_ID: usize = 0x04;
const DER_STRUCT_NAME: &str = "DER";
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Authorization { pub struct Authorization {
pub identifier: Identifier, pub identifier: Identifier,
@ -59,7 +64,8 @@ pub enum Challenge {
Http01(TokenChallenge), Http01(TokenChallenge),
#[serde(rename = "dns-01")] #[serde(rename = "dns-01")]
Dns01(TokenChallenge), Dns01(TokenChallenge),
// TODO: tls-alpn-01
#[serde(rename = "tls-alpn-01")]
TlsAlpn01(TokenChallenge),
#[serde(other)] #[serde(other)]
Unknown, Unknown,
} }
@ -69,7 +75,9 @@ deserialize_from_str!(Challenge);
impl Challenge { impl Challenge {
pub fn get_url(&self) -> String { pub fn get_url(&self) -> String {
match self { match self {
Challenge::Http01(tc) | Challenge::Dns01(tc) => tc.url.to_owned(),
Challenge::Http01(tc) | Challenge::Dns01(tc) | Challenge::TlsAlpn01(tc) => {
tc.url.to_owned()
}
Challenge::Unknown => String::new(), Challenge::Unknown => String::new(),
} }
} }
@ -83,6 +91,25 @@ impl Challenge {
let a = b64_encode(&a); let a = b64_encode(&a);
Ok(a) Ok(a)
} }
Challenge::TlsAlpn01(tc) => {
let acme_ext_name = format!("{}.{}", ACME_OID, ID_PE_ACME_ID);
let ka = tc.key_authorization(private_key)?;
let proof = sha256(ka.as_bytes());
let proof_str = proof
.iter()
.map(|e| format!("{:02x}", e))
.collect::<Vec<String>>()
.join(":");
let value = format!(
"critical,{}:{:02x}:{:02x}:{}",
DER_STRUCT_NAME,
DER_OCTET_STRING_ID,
proof.len(),
proof_str
);
let acme_ext = format!("{}={}", acme_ext_name, value);
Ok(acme_ext)
}
Challenge::Unknown => Ok(String::new()), Challenge::Unknown => Ok(String::new()),
} }
} }
@ -90,7 +117,7 @@ impl Challenge {
pub fn get_file_name(&self) -> String { pub fn get_file_name(&self) -> String {
match self { match self {
Challenge::Http01(tc) => tc.token.to_owned(), Challenge::Http01(tc) => tc.token.to_owned(),
Challenge::Dns01(_) => String::new(),
Challenge::Dns01(_) | Challenge::TlsAlpn01(_) => String::new(),
Challenge::Unknown => String::new(), Challenge::Unknown => String::new(),
} }
} }

Loading…
Cancel
Save