mirror of https://github.com/breard-r/acmed.git
Browse Source
Create an abstraction around the certificate
Create an abstraction around the certificate
As for public and private keys, the certificate should also be abstracted. rel #2pull/5/head
Rodolphe Breard
6 years ago
10 changed files with 190 additions and 210 deletions
-
1acme_common/Cargo.toml
-
3acme_common/src/crypto.rs
-
163acme_common/src/crypto/openssl_certificate.rs
-
6acmed/src/acme_proto.rs
-
30acmed/src/acme_proto/certificate.rs
-
102acmed/src/certificate.rs
-
11acmed/src/storage.rs
-
68tacd/src/certificate.rs
-
5tacd/src/main.rs
-
11tacd/src/server.rs
@ -1,3 +1,6 @@ |
|||||
mod openssl_keys;
|
mod openssl_keys;
|
||||
pub use openssl_keys::{gen_keypair, KeyType, PrivateKey, PublicKey};
|
pub use openssl_keys::{gen_keypair, KeyType, PrivateKey, PublicKey};
|
||||
pub const DEFAULT_ALGO: &str = "rsa2048";
|
pub const DEFAULT_ALGO: &str = "rsa2048";
|
||||
|
|
||||
|
mod openssl_certificate;
|
||||
|
pub use openssl_certificate::{Csr, X509Certificate};
|
@ -0,0 +1,163 @@ |
|||||
|
use super::{gen_keypair, KeyType, PrivateKey, PublicKey};
|
||||
|
use crate::b64_encode;
|
||||
|
use crate::error::Error;
|
||||
|
use openssl::asn1::Asn1Time;
|
||||
|
use openssl::bn::{BigNum, MsbOption};
|
||||
|
use openssl::hash::MessageDigest;
|
||||
|
use openssl::stack::Stack;
|
||||
|
use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName};
|
||||
|
use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder, X509};
|
||||
|
use std::collections::HashSet;
|
||||
|
use time::strptime;
|
||||
|
|
||||
|
const APP_ORG: &str = "ACMEd";
|
||||
|
const APP_NAME: &str = "ACMEd";
|
||||
|
const X509_VERSION: i32 = 0x02;
|
||||
|
const CRT_SERIAL_NB_BITS: i32 = 32;
|
||||
|
const CRT_NB_DAYS_VALIDITY: u32 = 7;
|
||||
|
const INVALID_EXT_MSG: &str = "Invalid acmeIdentifier extension.";
|
||||
|
|
||||
|
pub struct Csr {
|
||||
|
inner_csr: X509Req,
|
||||
|
}
|
||||
|
|
||||
|
impl Csr {
|
||||
|
pub fn new(
|
||||
|
pub_key: &PublicKey,
|
||||
|
priv_key: &PrivateKey,
|
||||
|
domains: &[String],
|
||||
|
) -> Result<Self, Error> {
|
||||
|
let mut builder = X509ReqBuilder::new()?;
|
||||
|
builder.set_pubkey(&pub_key.inner_key)?;
|
||||
|
let ctx = builder.x509v3_context(None);
|
||||
|
let mut san = SubjectAlternativeName::new();
|
||||
|
for dns in domains.iter() {
|
||||
|
san.dns(&dns);
|
||||
|
}
|
||||
|
let san = san.build(&ctx)?;
|
||||
|
let mut ext_stack = Stack::new()?;
|
||||
|
ext_stack.push(san)?;
|
||||
|
builder.add_extensions(&ext_stack)?;
|
||||
|
builder.sign(&priv_key.inner_key, MessageDigest::sha256())?;
|
||||
|
Ok(Csr {
|
||||
|
inner_csr: builder.build(),
|
||||
|
})
|
||||
|
}
|
||||
|
|
||||
|
pub fn to_der_base64(&self) -> Result<String, Error> {
|
||||
|
let csr = self.inner_csr.to_der()?;
|
||||
|
let csr = b64_encode(&csr);
|
||||
|
Ok(csr)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
pub struct X509Certificate {
|
||||
|
pub inner_cert: X509,
|
||||
|
}
|
||||
|
|
||||
|
impl X509Certificate {
|
||||
|
pub fn from_pem(pem_data: &[u8]) -> Result<Self, Error> {
|
||||
|
Ok(X509Certificate {
|
||||
|
inner_cert: X509::from_pem(pem_data)?,
|
||||
|
})
|
||||
|
}
|
||||
|
|
||||
|
pub fn from_acme_ext(domain: &str, acme_ext: &str) -> Result<(PrivateKey, Self), Error> {
|
||||
|
let (pub_key, priv_key) = gen_keypair(KeyType::EcdsaP256)?;
|
||||
|
let inner_cert = gen_certificate(domain, &pub_key, &priv_key, acme_ext)?;
|
||||
|
let cert = X509Certificate { inner_cert };
|
||||
|
Ok((priv_key, cert))
|
||||
|
}
|
||||
|
|
||||
|
pub fn public_key(&self) -> Result<PublicKey, Error> {
|
||||
|
let raw_key = self.inner_cert.public_key()?.public_key_to_pem()?;
|
||||
|
let pub_key = PublicKey::from_pem(&raw_key)?;
|
||||
|
Ok(pub_key)
|
||||
|
}
|
||||
|
|
||||
|
// OpenSSL ASN1_TIME_print madness
|
||||
|
// The real fix would be to add Asn1TimeRef access in the openssl crate.
|
||||
|
//
|
||||
|
// https://github.com/sfackler/rust-openssl/issues/687
|
||||
|
// https://github.com/sfackler/rust-openssl/pull/673
|
||||
|
pub fn not_after(&self) -> Result<time::Tm, Error> {
|
||||
|
let formats = [
|
||||
|
"%b %d %T %Y %Z",
|
||||
|
"%b %d %T %Y %Z",
|
||||
|
"%b %d %T %Y",
|
||||
|
"%b %d %T %Y",
|
||||
|
"%b %d %T.%f %Y %Z",
|
||||
|
"%b %d %T.%f %Y %Z",
|
||||
|
"%b %d %T.%f %Y",
|
||||
|
"%b %d %T.%f %Y",
|
||||
|
];
|
||||
|
let not_after = self.inner_cert.not_after().to_string();
|
||||
|
for fmt in formats.iter() {
|
||||
|
if let Ok(t) = strptime(¬_after, fmt) {
|
||||
|
return Ok(t);
|
||||
|
}
|
||||
|
}
|
||||
|
Err(format!("invalid time string: {}", ¬_after).into())
|
||||
|
}
|
||||
|
|
||||
|
pub fn subject_alt_names(&self) -> HashSet<String> {
|
||||
|
match self.inner_cert.subject_alt_names() {
|
||||
|
Some(s) => s
|
||||
|
.iter()
|
||||
|
.filter(|v| v.dnsname().is_some())
|
||||
|
.map(|v| v.dnsname().unwrap().to_string())
|
||||
|
.collect(),
|
||||
|
None => HashSet::new(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
fn gen_certificate(
|
||||
|
domain: &str,
|
||||
|
public_key: &PublicKey,
|
||||
|
private_key: &PrivateKey,
|
||||
|
acme_ext: &str,
|
||||
|
) -> Result<X509, Error> {
|
||||
|
let mut x509_name = X509NameBuilder::new()?;
|
||||
|
x509_name.append_entry_by_text("O", APP_ORG)?;
|
||||
|
let ca_name = format!("{} TLS-ALPN-01 Authority", APP_NAME);
|
||||
|
x509_name.append_entry_by_text("CN", &ca_name)?;
|
||||
|
let x509_name = x509_name.build();
|
||||
|
|
||||
|
let mut builder = X509Builder::new()?;
|
||||
|
builder.set_version(X509_VERSION)?;
|
||||
|
let serial_number = {
|
||||
|
let mut serial = BigNum::new()?;
|
||||
|
serial.rand(CRT_SERIAL_NB_BITS - 1, MsbOption::MAYBE_ZERO, false)?;
|
||||
|
serial.to_asn1_integer()?
|
||||
|
};
|
||||
|
builder.set_serial_number(&serial_number)?;
|
||||
|
builder.set_subject_name(&x509_name)?;
|
||||
|
builder.set_issuer_name(&x509_name)?;
|
||||
|
builder.set_pubkey(&public_key.inner_key)?;
|
||||
|
let not_before = Asn1Time::days_from_now(0)?;
|
||||
|
builder.set_not_before(¬_before)?;
|
||||
|
let not_after = Asn1Time::days_from_now(CRT_NB_DAYS_VALIDITY)?;
|
||||
|
builder.set_not_after(¬_after)?;
|
||||
|
|
||||
|
builder.append_extension(BasicConstraints::new().build()?)?;
|
||||
|
let ctx = builder.x509v3_context(None, None);
|
||||
|
let san_ext = SubjectAlternativeName::new().dns(domain).build(&ctx)?;
|
||||
|
builder.append_extension(san_ext)?;
|
||||
|
|
||||
|
let ctx = builder.x509v3_context(None, None);
|
||||
|
let mut v: Vec<&str> = acme_ext.split('=').collect();
|
||||
|
let value = v.pop().ok_or_else(|| Error::from(INVALID_EXT_MSG))?;
|
||||
|
let acme_ext_name = v.pop().ok_or_else(|| Error::from(INVALID_EXT_MSG))?;
|
||||
|
if !v.is_empty() {
|
||||
|
return Err(Error::from(INVALID_EXT_MSG));
|
||||
|
}
|
||||
|
let acme_ext = X509Extension::new(None, Some(&ctx), &acme_ext_name, &value)
|
||||
|
.map_err(|_| Error::from(INVALID_EXT_MSG))?;
|
||||
|
builder
|
||||
|
.append_extension(acme_ext)
|
||||
|
.map_err(|_| Error::from(INVALID_EXT_MSG))?;
|
||||
|
builder.sign(&private_key.inner_key, MessageDigest::sha256())?;
|
||||
|
let cert = builder.build();
|
||||
|
Ok(cert)
|
||||
|
}
|
@ -1,68 +0,0 @@ |
|||||
use acme_common::crypto::{gen_keypair, KeyType, PrivateKey, PublicKey};
|
|
||||
use acme_common::error::Error;
|
|
||||
use openssl::asn1::Asn1Time;
|
|
||||
use openssl::bn::{BigNum, MsbOption};
|
|
||||
use openssl::hash::MessageDigest;
|
|
||||
use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName};
|
|
||||
use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509};
|
|
||||
|
|
||||
const X509_VERSION: i32 = 0x02;
|
|
||||
const CRT_SERIAL_NB_BITS: i32 = 32;
|
|
||||
const CRT_NB_DAYS_VALIDITY: u32 = 7;
|
|
||||
const INVALID_EXT_MSG: &str = "Invalid acmeIdentifier extension.";
|
|
||||
|
|
||||
fn get_certificate(
|
|
||||
domain: &str,
|
|
||||
public_key: &PublicKey,
|
|
||||
private_key: &PrivateKey,
|
|
||||
acme_ext: &str,
|
|
||||
) -> Result<X509, Error> {
|
|
||||
let mut x509_name = X509NameBuilder::new()?;
|
|
||||
x509_name.append_entry_by_text("O", crate::APP_ORG)?;
|
|
||||
let ca_name = format!("{} TLS-ALPN-01 Authority", crate::APP_NAME);
|
|
||||
x509_name.append_entry_by_text("CN", &ca_name)?;
|
|
||||
let x509_name = x509_name.build();
|
|
||||
|
|
||||
let mut builder = X509Builder::new()?;
|
|
||||
builder.set_version(X509_VERSION)?;
|
|
||||
let serial_number = {
|
|
||||
let mut serial = BigNum::new()?;
|
|
||||
serial.rand(CRT_SERIAL_NB_BITS - 1, MsbOption::MAYBE_ZERO, false)?;
|
|
||||
serial.to_asn1_integer()?
|
|
||||
};
|
|
||||
builder.set_serial_number(&serial_number)?;
|
|
||||
builder.set_subject_name(&x509_name)?;
|
|
||||
builder.set_issuer_name(&x509_name)?;
|
|
||||
builder.set_pubkey(&public_key.inner_key)?;
|
|
||||
let not_before = Asn1Time::days_from_now(0)?;
|
|
||||
builder.set_not_before(¬_before)?;
|
|
||||
let not_after = Asn1Time::days_from_now(CRT_NB_DAYS_VALIDITY)?;
|
|
||||
builder.set_not_after(¬_after)?;
|
|
||||
|
|
||||
builder.append_extension(BasicConstraints::new().build()?)?;
|
|
||||
let ctx = builder.x509v3_context(None, None);
|
|
||||
let san_ext = SubjectAlternativeName::new().dns(domain).build(&ctx)?;
|
|
||||
builder.append_extension(san_ext)?;
|
|
||||
|
|
||||
let ctx = builder.x509v3_context(None, None);
|
|
||||
let mut v: Vec<&str> = acme_ext.split('=').collect();
|
|
||||
let value = v.pop().ok_or_else(|| Error::from(INVALID_EXT_MSG))?;
|
|
||||
let acme_ext_name = v.pop().ok_or_else(|| Error::from(INVALID_EXT_MSG))?;
|
|
||||
if !v.is_empty() {
|
|
||||
return Err(Error::from(INVALID_EXT_MSG));
|
|
||||
}
|
|
||||
let acme_ext = X509Extension::new(None, Some(&ctx), &acme_ext_name, &value)
|
|
||||
.map_err(|_| Error::from(INVALID_EXT_MSG))?;
|
|
||||
builder
|
|
||||
.append_extension(acme_ext)
|
|
||||
.map_err(|_| Error::from(INVALID_EXT_MSG))?;
|
|
||||
builder.sign(&private_key.inner_key, MessageDigest::sha256())?;
|
|
||||
let cert = builder.build();
|
|
||||
Ok(cert)
|
|
||||
}
|
|
||||
|
|
||||
pub fn gen_certificate(domain: &str, acme_ext: &str) -> Result<(PrivateKey, X509), Error> {
|
|
||||
let (pub_key, priv_key) = gen_keypair(KeyType::EcdsaP256)?;
|
|
||||
let cert = get_certificate(domain, &pub_key, &priv_key, acme_ext)?;
|
|
||||
Ok((priv_key, cert))
|
|
||||
}
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue