use std::{
cmp::Ordering,
convert::TryInto,
fmt::{self, Debug, Display, Formatter},
hash::Hash,
marker::PhantomData,
path::Path,
str,
time::{SystemTime, UNIX_EPOCH},
};
use casper_types::file_utils::{read_file, ReadFileError};
use datasize::DataSize;
use hex_fmt::HexFmt;
use nid::Nid;
use openssl::{
asn1::{Asn1Integer, Asn1IntegerRef, Asn1Time},
bn::{BigNum, BigNumContext},
ec::{self, EcKey},
error::ErrorStack,
hash::{DigestBytes, MessageDigest},
nid,
pkey::{PKey, PKeyRef, Private, Public},
sha,
ssl::{SslAcceptor, SslConnector, SslContextBuilder, SslMethod, SslVerifyMode, SslVersion},
x509::{X509Builder, X509Name, X509NameBuilder, X509NameRef, X509Ref, X509},
};
#[cfg(test)]
use rand::{
distributions::{Distribution, Standard},
Rng,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
mod big_array {
use serde_big_array::big_array;
big_array! { BigArray; }
}
const SIGNATURE_ALGORITHM: Nid = Nid::ECDSA_WITH_SHA512;
const SIGNATURE_CURVE: Nid = Nid::SECP521R1;
const SIGNATURE_DIGEST: Nid = Nid::SHA512;
type SslResult<T> = Result<T, ErrorStack>;
#[derive(Copy, Clone, DataSize, Deserialize, Serialize)]
pub struct Sha512(#[serde(with = "big_array::BigArray")] [u8; Sha512::SIZE]);
impl Sha512 {
const SIZE: usize = 64;
const NID: Nid = Nid::SHA512;
pub fn new<B: AsRef<[u8]>>(data: B) -> Self {
let mut openssl_sha = sha::Sha512::new();
openssl_sha.update(data.as_ref());
Sha512(openssl_sha.finish())
}
fn bytes(&self) -> &[u8] {
let bs = &self.0[..];
debug_assert_eq!(bs.len(), Self::SIZE);
bs
}
fn from_openssl_digest(digest: &DigestBytes) -> Self {
let digest_bytes = digest.as_ref();
debug_assert_eq!(
digest_bytes.len(),
Self::SIZE,
"digest is not the right size - check constants in `tls.rs`"
);
let mut buf = [0; Self::SIZE];
buf.copy_from_slice(&digest_bytes[0..Self::SIZE]);
Sha512(buf)
}
fn create_message_digest() -> MessageDigest {
MessageDigest::from_nid(Self::NID).expect("Sha512::NID is invalid")
}
}
#[derive(Copy, Clone, DataSize, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub(crate) struct CertFingerprint(Sha512);
impl Debug for CertFingerprint {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "CertFingerprint({:10})", HexFmt(self.0.bytes()))
}
}
#[derive(Copy, Clone, DataSize, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct KeyFingerprint(Sha512);
impl KeyFingerprint {
pub const LENGTH: usize = Sha512::SIZE;
}
impl AsRef<[u8]> for KeyFingerprint {
fn as_ref(&self) -> &[u8] {
self.0.bytes()
}
}
impl Debug for KeyFingerprint {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "KeyFingerprint({:10})", HexFmt(self.0.bytes()))
}
}
impl From<[u8; KeyFingerprint::LENGTH]> for KeyFingerprint {
fn from(raw_bytes: [u8; KeyFingerprint::LENGTH]) -> Self {
KeyFingerprint(Sha512(raw_bytes))
}
}
impl From<Sha512> for KeyFingerprint {
fn from(hash: Sha512) -> Self {
Self(hash)
}
}
#[cfg(test)]
impl Distribution<KeyFingerprint> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> KeyFingerprint {
let mut bytes = [0u8; Sha512::SIZE];
rng.fill(&mut bytes[..]);
bytes.into()
}
}
#[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)]
struct Signature(Vec<u8>);
impl Debug for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Signature({:10})", HexFmt(&self.0))
}
}
#[derive(Clone, DataSize)]
pub struct TlsCert {
#[data_size(skip)] x509: X509,
cert_fingerprint: CertFingerprint,
key_fingerprint: KeyFingerprint,
}
impl TlsCert {
pub(crate) fn fingerprint(&self) -> CertFingerprint {
self.cert_fingerprint
}
pub(crate) fn public_key_fingerprint(&self) -> KeyFingerprint {
self.key_fingerprint
}
pub(crate) fn as_x509(&self) -> &X509 {
&self.x509
}
}
impl Debug for TlsCert {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "TlsCert({:?})", self.fingerprint())
}
}
impl Hash for TlsCert {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.fingerprint().hash(state);
}
}
impl PartialEq for TlsCert {
fn eq(&self, other: &Self) -> bool {
self.fingerprint() == other.fingerprint()
}
}
impl Eq for TlsCert {}
#[derive(Debug, Error, Serialize)]
pub enum LoadCertError {
#[error("could not load certificate file: {0}")]
ReadFile(
#[serde(skip_serializing)]
#[source]
ReadFileError,
),
#[error("unable to load x509 certificate {0:?}")]
X509CertFromPem(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
}
pub(crate) fn load_cert<P: AsRef<Path>>(src: P) -> Result<X509, LoadCertError> {
let pem = read_file(src.as_ref()).map_err(LoadCertError::ReadFile)?;
X509::from_pem(&pem).map_err(LoadCertError::X509CertFromPem)
}
#[derive(Debug, Error, Serialize)]
pub(crate) enum LoadSecretKeyError {
#[error("could not load secret key file: {0}")]
ReadFile(
#[serde(skip_serializing)]
#[source]
ReadFileError,
),
#[error("unable to load private key from pem {0:?}")]
PrivateKeyFromPem(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
}
pub(crate) fn load_secret_key<P: AsRef<Path>>(src: P) -> Result<PKey<Private>, LoadSecretKeyError> {
let pem = read_file(src.as_ref()).map_err(LoadSecretKeyError::ReadFile)?;
PKey::private_key_from_pem(&pem).map_err(LoadSecretKeyError::PrivateKeyFromPem)
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Signed<V> {
data: Vec<u8>,
signature: Signature,
_phantom: PhantomData<V>,
}
pub fn generate_node_cert() -> SslResult<(X509, PKey<Private>)> {
let private_key = generate_private_key()?;
let cert = generate_cert(&private_key, "casper-node")?;
Ok((cert, private_key))
}
pub(crate) fn create_tls_acceptor(
cert: &X509Ref,
private_key: &PKeyRef<Private>,
) -> SslResult<SslAcceptor> {
let mut builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls_server())?;
set_context_options(&mut builder, cert, private_key)?;
Ok(builder.build())
}
pub(crate) fn create_tls_connector(
cert: &X509Ref,
private_key: &PKeyRef<Private>,
) -> SslResult<SslConnector> {
let mut builder = SslConnector::builder(SslMethod::tls_client())?;
set_context_options(&mut builder, cert, private_key)?;
Ok(builder.build())
}
fn set_context_options(
ctx: &mut SslContextBuilder,
cert: &X509Ref,
private_key: &PKeyRef<Private>,
) -> SslResult<()> {
ctx.set_min_proto_version(Some(SslVersion::TLS1_3))?;
ctx.set_certificate(cert)?;
ctx.set_private_key(private_key)?;
ctx.check_private_key()?;
ctx.set_verify_callback(SslVerifyMode::PEER, |_, _| true);
Ok(())
}
#[derive(Debug, Error, Serialize)]
pub enum ValidationError {
#[error("error reading public key from certificate: {0:?}")]
CannotReadPublicKey(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("error reading subject or issuer name: {0:?}")]
CorruptSubjectOrIssuer(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("wrong signature scheme")]
WrongSignatureAlgorithm,
#[error("there was an issue reading or converting times: {0:?}")]
TimeIssue(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("the certificate is not yet valid")]
NotYetValid,
#[error("the certificate expired")]
Expired,
#[error("the serial number could not be compared to the reference: {0:?}")]
InvalidSerialNumber(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("wrong serial number")]
WrongSerialNumber,
#[error("no valid elliptic curve key could be extracted from certificate: {0:?}")]
CouldNotExtractEcKey(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("the given public key fails basic sanity checks: {0:?}")]
KeyFailsCheck(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("underlying elliptic curve is wrong")]
WrongCurve,
#[error("certificate is not self-signed")]
NotSelfSigned,
#[error("the signature could not be validated")]
FailedToValidateSignature(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("the signature is invalid")]
InvalidSignature,
#[error("failed to read fingerprint")]
InvalidFingerprint(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("could not create a big num context")]
BigNumContextNotAvailable(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("could not encode public key as bytes")]
PublicKeyEncodingFailed(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
#[error("the certificate is not signed by provided certificate authority")]
WrongCertificateAuthority,
#[error("error reading public key from ca certificate: {0:?}")]
CannotReadCAPublicKey(
#[serde(skip_serializing)]
#[source]
ErrorStack,
),
}
pub(crate) fn validate_cert_with_authority(
cert: X509,
ca: &X509,
) -> Result<TlsCert, ValidationError> {
let authority_key = ca
.public_key()
.map_err(ValidationError::CannotReadCAPublicKey)?;
validate_cert_expiration_date(&cert)?;
if !cert
.verify(authority_key.as_ref())
.map_err(ValidationError::FailedToValidateSignature)?
{
return Err(ValidationError::WrongCertificateAuthority);
}
tls_cert_from_x509(cert)
}
pub(crate) fn validate_self_signed_cert(cert: X509) -> Result<TlsCert, ValidationError> {
if cert.signature_algorithm().object().nid() != SIGNATURE_ALGORITHM {
return Err(ValidationError::WrongSignatureAlgorithm);
}
let subject =
name_to_string(cert.subject_name()).map_err(ValidationError::CorruptSubjectOrIssuer)?;
let issuer =
name_to_string(cert.issuer_name()).map_err(ValidationError::CorruptSubjectOrIssuer)?;
if subject != issuer {
return Err(ValidationError::NotSelfSigned);
}
if !num_eq(cert.serial_number(), 1).map_err(ValidationError::InvalidSerialNumber)? {
return Err(ValidationError::WrongSerialNumber);
}
validate_cert_expiration_date(&cert)?;
let (public_key, ec_key) = validate_cert_ec_key(&cert)?;
if ec_key.group().curve_name() != Some(SIGNATURE_CURVE) {
return Err(ValidationError::WrongCurve);
}
if !cert
.verify(&public_key)
.map_err(ValidationError::FailedToValidateSignature)?
{
return Err(ValidationError::InvalidSignature);
}
tls_cert_from_x509_and_key(cert, ec_key)
}
pub(crate) fn tls_cert_from_x509(cert: X509) -> Result<TlsCert, ValidationError> {
let (_public_key, ec_key) = validate_cert_ec_key(&cert)?;
tls_cert_from_x509_and_key(cert, ec_key)
}
fn tls_cert_from_x509_and_key(
cert: X509,
ec_key: EcKey<Public>,
) -> Result<TlsCert, ValidationError> {
let cert_fingerprint = cert_fingerprint(&cert)?;
let key_fingerprint = key_fingerprint(&ec_key)?;
Ok(TlsCert {
x509: cert,
cert_fingerprint,
key_fingerprint,
})
}
pub(crate) fn cert_fingerprint(cert: &X509) -> Result<CertFingerprint, ValidationError> {
assert_eq!(Sha512::NID, SIGNATURE_DIGEST);
let digest = &cert
.digest(Sha512::create_message_digest())
.map_err(ValidationError::InvalidFingerprint)?;
let cert_fingerprint = CertFingerprint(Sha512::from_openssl_digest(digest));
Ok(cert_fingerprint)
}
pub(crate) fn key_fingerprint(ec_key: &EcKey<Public>) -> Result<KeyFingerprint, ValidationError> {
let mut big_num_context =
BigNumContext::new().map_err(ValidationError::BigNumContextNotAvailable)?;
let buf = ec_key
.public_key()
.to_bytes(
ec::EcGroup::from_curve_name(SIGNATURE_CURVE)
.expect("broken constant SIGNATURE_CURVE")
.as_ref(),
ec::PointConversionForm::COMPRESSED,
&mut big_num_context,
)
.map_err(ValidationError::PublicKeyEncodingFailed)?;
let key_fingerprint = KeyFingerprint(Sha512::new(buf));
Ok(key_fingerprint)
}
fn validate_cert_ec_key(cert: &X509) -> Result<(PKey<Public>, EcKey<Public>), ValidationError> {
let public_key = cert
.public_key()
.map_err(ValidationError::CannotReadPublicKey)?;
let ec_key = public_key
.ec_key()
.map_err(ValidationError::CouldNotExtractEcKey)?;
ec_key.check_key().map_err(ValidationError::KeyFailsCheck)?;
Ok((public_key, ec_key))
}
fn validate_cert_expiration_date(cert: &X509) -> Result<(), ValidationError> {
let asn1_now = Asn1Time::from_unix(now()).map_err(ValidationError::TimeIssue)?;
if asn1_now
.compare(cert.not_before())
.map_err(ValidationError::TimeIssue)?
!= Ordering::Greater
{
return Err(ValidationError::NotYetValid);
}
if asn1_now
.compare(cert.not_after())
.map_err(ValidationError::TimeIssue)?
!= Ordering::Less
{
return Err(ValidationError::Expired);
}
Ok(())
}
fn now() -> i64 {
let now = SystemTime::now();
let ts: i64 = now
.duration_since(UNIX_EPOCH)
.expect("Great Scott! Your clock is horribly broken, Marty.")
.as_secs()
.try_into()
.expect("32-bit systems and far future are not supported");
ts
}
fn mknum(n: u32) -> Result<Asn1Integer, ErrorStack> {
let bn = BigNum::from_u32(n)?;
bn.to_asn1_integer()
}
fn mkname(c: &str, o: &str, cn: &str) -> Result<X509Name, ErrorStack> {
let mut builder = X509NameBuilder::new()?;
if !c.is_empty() {
builder.append_entry_by_text("C", c)?;
}
if !o.is_empty() {
builder.append_entry_by_text("O", o)?;
}
builder.append_entry_by_text("CN", cn)?;
Ok(builder.build())
}
fn name_to_string(name: &X509NameRef) -> SslResult<String> {
let mut output = String::new();
for entry in name.entries() {
output.push_str(entry.object().nid().long_name()?);
output.push('=');
output.push_str(entry.data().as_utf8()?.as_ref());
output.push(' ');
}
Ok(output)
}
fn num_eq(num: &Asn1IntegerRef, other: u32) -> SslResult<bool> {
let l = num.to_bn()?;
let r = BigNum::from_u32(other)?;
Ok(l.is_negative() == r.is_negative() && l.ucmp(r.as_ref()) == Ordering::Equal)
}
fn generate_private_key() -> SslResult<PKey<Private>> {
let ec_group = ec::EcGroup::from_curve_name(SIGNATURE_CURVE)?;
let ec_key = EcKey::generate(ec_group.as_ref())?;
PKey::from_ec_key(ec_key)
}
fn generate_cert(private_key: &PKey<Private>, cn: &str) -> SslResult<X509> {
let mut builder = X509Builder::new()?;
builder.set_version(2)?;
builder.set_serial_number(mknum(1)?.as_ref())?;
let issuer = mkname("US", "Casper Blockchain", cn)?;
builder.set_issuer_name(issuer.as_ref())?;
builder.set_subject_name(issuer.as_ref())?;
let ts = now();
builder.set_not_before(Asn1Time::from_unix(ts - 60)?.as_ref())?;
builder.set_not_after(Asn1Time::from_unix(ts + 10 * 365 * 24 * 60 * 60)?.as_ref())?;
builder.set_pubkey(private_key.as_ref())?;
assert_eq!(Sha512::NID, SIGNATURE_DIGEST);
builder.sign(private_key.as_ref(), Sha512::create_message_digest())?;
let cert = builder.build();
assert!(
validate_self_signed_cert(cert.clone()).is_ok(),
"newly generated cert does not pass our own validity check"
);
Ok(cert)
}
impl PartialEq for Sha512 {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.bytes() == other.bytes()
}
}
impl Eq for Sha512 {}
impl Ord for Sha512 {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self.bytes(), other.bytes())
}
}
impl PartialOrd for Sha512 {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(Ord::cmp(self, other))
}
}
impl Debug for Sha512 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", base16::encode_lower(&self.0[..]))
}
}
impl Display for Sha512 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:10}", HexFmt(&self.0[..]))
}
}
impl Display for CertFingerprint {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Display for KeyFingerprint {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:10}", HexFmt(self.0.bytes()))
}
}
impl Display for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:10}", HexFmt(&self.0[..]))
}
}
impl<T> Display for Signed<T>
where
T: Display + for<'de> Deserialize<'de>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match bincode::deserialize::<T>(self.data.as_slice()) {
Ok(item) => write!(f, "signed[{}]<{} bytes>", self.signature, item),
Err(_err) => write!(f, "signed[{}]<CORRUPT>", self.signature),
}
}
}
impl Hash for Sha512 {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let mut chunk = [0u8; 8];
chunk.copy_from_slice(&self.bytes()[0..8]);
state.write_u64(u64::from_le_bytes(chunk));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_name_to_string() {
let name = mkname("sc", "some_org", "some_cn").expect("could not create name");
assert_eq!(
name_to_string(name.as_ref()).expect("name to string failed"),
"countryName=sc organizationName=some_org commonName=some_cn "
);
}
#[test]
fn test_validate_self_signed_cert() {
let (cert, private_key) = generate_node_cert().expect("failed to generate key, cert pair");
let _tls_cert =
validate_self_signed_cert(cert).expect("generated self signed cert is not valid");
let ca_private_key = generate_private_key().expect("failed to generate private key");
let ca_signed_cert = make_ca_signed_cert(private_key, ca_private_key);
let error = validate_self_signed_cert(ca_signed_cert)
.expect_err("should not validate ca signed cert as self signed");
assert!(
matches!(error, ValidationError::InvalidSignature),
"{:?}",
error
);
}
#[test]
fn test_validate_cert_with_authority() {
let (ca_cert, ca_private_key) =
generate_node_cert().expect("failed to generate key, cert pair");
let (different_ca_cert, _ca_private_key) =
generate_node_cert().expect("failed to generate key, cert pair");
let node_private_key = generate_private_key().expect("failed to generate private key");
let node_cert = make_ca_signed_cert(node_private_key, ca_private_key);
validate_self_signed_cert(node_cert.clone())
.expect_err("should not validate CA signed cert as self signed");
let _node_tls_cert = validate_cert_with_authority(node_cert.clone(), &ca_cert)
.expect("should validate with ca cert");
let validation_error = validate_cert_with_authority(node_cert, &different_ca_cert)
.expect_err("should not validate cert against different CA");
assert!(
matches!(validation_error, ValidationError::WrongCertificateAuthority),
"{:?}",
validation_error
);
}
fn make_ca_signed_cert(private_key: PKey<Private>, ca_private_key: PKey<Private>) -> X509 {
let mut builder = X509Builder::new().unwrap();
builder.set_version(2).unwrap();
builder
.set_serial_number(mknum(1).unwrap().as_ref())
.unwrap();
let issuer = mkname("US", "Casper Blockchain", "Casper Network").unwrap();
builder.set_issuer_name(issuer.as_ref()).unwrap();
builder.set_subject_name(issuer.as_ref()).unwrap();
let ts = now();
builder
.set_not_before(Asn1Time::from_unix(ts - 60).unwrap().as_ref())
.unwrap();
builder
.set_not_after(
Asn1Time::from_unix(ts + 10 * 365 * 24 * 60 * 60)
.unwrap()
.as_ref(),
)
.unwrap();
builder.set_pubkey(private_key.as_ref()).unwrap();
assert_eq!(Sha512::NID, SIGNATURE_DIGEST);
builder
.sign(ca_private_key.as_ref(), Sha512::create_message_digest())
.unwrap();
builder.build()
}
}