use openssl::ec::EcKey;
use openssl::ecdsa::EcdsaSig;
use openssl::error::ErrorStack;
use openssl::pkey::{Private, Public};
use protobuf::Message;
use sha2::{Digest, Sha256};
use thiserror::Error;
use url::Url;
pub mod proto;
pub trait Pubs {
fn get(&self, id: &Url) -> Option<EcKey<Public>>;
}
pub trait TrustChecker {
fn is_trusted(&self, id: &Url) -> bool;
}
pub struct CertificateBlueprint {
pub subject: Url,
pub capability: Vec<u8>,
pub exp: Option<u64>,
}
pub struct InvokeBlueprint {
pub cert: proto::Certificate,
pub action: Vec<u8>,
pub exp: Option<u64>,
}
#[derive(Error, Debug)]
pub enum ForgeError {
#[error("Unexpected OpenSSL error. Invalid private key?")]
CryptoError {
#[from]
source: ErrorStack,
},
}
#[derive(Error, Debug)]
pub enum DelegateError {
#[error("Unexpected OpenSSL error. Invalid private key?")]
CryptoError {
#[from]
source: ErrorStack,
},
}
#[derive(Error, Debug)]
pub enum InvokeError {
#[error("Unexpected OpenSSL error. Invalid private key?")]
CryptoError {
#[from]
source: ErrorStack,
},
}
pub struct Holder {
me: Url,
sk: EcKey<Private>,
}
impl Holder {
pub fn new(me: Url, sk: EcKey<Private>) -> Self {
Holder { me, sk }
}
pub fn forge(&self, options: CertificateBlueprint) -> Result<proto::Certificate, ForgeError> {
let mut proto_payload = proto::Certificate_Payload::new();
self.write_cert_payload(&mut proto_payload, options)?;
let proto = self.write_cert(proto_payload)?;
Ok(proto)
}
pub fn delegate(
&self,
cert: proto::Certificate,
options: CertificateBlueprint,
) -> Result<proto::Certificate, DelegateError> {
let mut proto_payload = proto::Certificate_Payload::new();
self.write_cert_payload(&mut proto_payload, options)?;
proto_payload.set_parent(cert);
let proto = self.write_cert(proto_payload)?;
Ok(proto)
}
pub fn invoke(&self, options: InvokeBlueprint) -> Result<proto::Invocation, InvokeError> {
let mut proto_payload = proto::Invocation_Payload::new();
self.write_invoke_payload(&mut proto_payload, options)?;
let proto = self.write_invocation(proto_payload)?;
Ok(proto)
}
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, ErrorStack> {
let mut hasher = Sha256::new();
hasher.input(msg);
let hash = hasher.result();
Ok(EcdsaSig::sign(&hash, &self.sk)?.to_der()?)
}
fn write_cert_payload(
&self,
proto_payload: &mut proto::Certificate_Payload,
options: CertificateBlueprint,
) -> Result<(), ErrorStack> {
proto_payload.set_capability(options.capability);
if let Some(x) = options.exp {
proto_payload.set_expiration(x)
}
proto_payload.set_issuer(self.me.clone().into_string());
proto_payload.set_subject(options.subject.to_string());
Ok(())
}
fn write_cert(
&self,
proto_payload: proto::Certificate_Payload,
) -> Result<proto::Certificate, ErrorStack> {
let bytes = proto_payload
.write_to_bytes()
.expect("Protobuf internal error");
let mut proto = proto::Certificate::new();
proto.set_signature(self.sign(&bytes)?);
proto.set_payload(bytes);
Ok(proto)
}
fn write_invoke_payload(
&self,
proto_payload: &mut proto::Invocation_Payload,
options: InvokeBlueprint,
) -> Result<(), ErrorStack> {
proto_payload.set_invoker(self.me.clone().into_string());
proto_payload.set_action(options.action);
proto_payload.set_certificate(options.cert);
if let Some(x) = options.exp {
proto_payload.set_expiration(x)
}
Ok(())
}
fn write_invocation(
&self,
proto_payload: proto::Invocation_Payload,
) -> Result<proto::Invocation, ErrorStack> {
let bytes = proto_payload
.write_to_bytes()
.expect("Protobuf internal error");
let mut proto = proto::Invocation::new();
proto.set_signature(self.sign(&bytes)?);
proto.set_payload(bytes);
Ok(proto)
}
}
#[derive(Error, Debug)]
pub enum ValidateError {
#[error("Malformed protobuf message")]
Malformed {
#[from]
source: protobuf::ProtobufError,
},
#[error("Untrusted issuer {issuer}")]
Untrusted { issuer: Url },
#[error("Can't parse URL {url}")]
BadURL { url: String },
#[error("Unknown pub key for {url}")]
UnknownPub { url: Url },
#[error("Issuer {issuer} doesn't match subject {subject}")]
BadIssuer { subject: Url, issuer: Url },
#[error("Invoker {invoker} doesn't match subject {subject}")]
BadInvoker { subject: Url, invoker: Url },
#[error("Expired item")]
Expired,
#[error("Bad signature")]
BadSign,
}
pub struct Validator<'a> {
trust_checker: &'a dyn TrustChecker,
pubs: &'a dyn Pubs,
}
impl<'a> Validator<'a> {
pub fn new(trust_checker: &'a dyn TrustChecker, pubs: &'a dyn Pubs) -> Self {
Validator {
trust_checker,
pubs,
}
}
pub fn validate_cert(&self, cert: &proto::Certificate, now: u64) -> Result<(), ValidateError> {
self.validate_cert2(None, cert, now)
}
pub fn validate_invocation(
&self,
invocation: &proto::Invocation,
now: u64,
) -> Result<(), ValidateError> {
let mut payload = proto::Invocation_Payload::new();
payload.merge_from_bytes(invocation.get_payload())?;
if payload.get_expiration() != 0 && payload.get_expiration() < now {
return Err(ValidateError::Expired);
}
let invoker = Url::parse(payload.get_invoker()).map_err(|_| ValidateError::BadURL {
url: payload.get_invoker().to_string(),
})?;
let invoker_pub = match self.pubs.get(&invoker) {
Some(x) => x,
None => return Err(ValidateError::UnknownPub { url: invoker }),
};
self.verify(
invocation.get_payload(),
invocation.get_signature(),
&invoker_pub,
)?;
let cert = payload.get_certificate();
let mut cert_payload = proto::Certificate_Payload::new();
cert_payload.merge_from_bytes(cert.get_payload())?;
let subject =
Url::parse(cert_payload.get_subject()).map_err(|_| ValidateError::BadURL {
url: cert_payload.get_subject().to_string(),
})?;
if cert_payload.get_subject() != payload.get_invoker() {
return Err(ValidateError::BadInvoker { invoker, subject });
};
self.validate_cert(cert, now)
}
fn validate_cert2(
&self,
next_issuer: Option<&Url>,
cert: &proto::Certificate,
now: u64,
) -> Result<(), ValidateError> {
let mut payload = proto::Certificate_Payload::new();
payload.merge_from_bytes(cert.get_payload())?;
if payload.get_expiration() != 0 && payload.get_expiration() < now {
return Err(ValidateError::Expired);
}
let issuer = Url::parse(payload.get_issuer()).map_err(|_| ValidateError::BadURL {
url: payload.get_issuer().to_string(),
})?;
let subject = Url::parse(payload.get_subject()).map_err(|_| ValidateError::BadURL {
url: payload.get_issuer().to_string(),
})?;
if let Some(issuer) = next_issuer {
if issuer != &subject {
return Err(ValidateError::BadIssuer {
issuer: issuer.clone(),
subject,
});
}
}
if payload.has_parent() {
self.validate_cert2(Some(&issuer), payload.get_parent(), now)?
} else if !self.trust_checker.is_trusted(&issuer) {
return Err(ValidateError::Untrusted { issuer });
}
let issuer_pub = match self.pubs.get(&issuer) {
Some(x) => x,
None => return Err(ValidateError::UnknownPub { url: issuer }),
};
self.verify(cert.get_payload(), cert.get_signature(), &issuer_pub)?;
Ok(())
}
fn verify(&self, msg: &[u8], sign: &[u8], pk: &EcKey<Public>) -> Result<(), ValidateError> {
let mut hasher = Sha256::new();
hasher.input(msg);
let hash = hasher.result();
if !EcdsaSig::from_der(sign)
.map_err(|_| ValidateError::BadSign)?
.verify(&hash, pk)
.map_err(|_| ValidateError::BadSign)?
{
return Err(ValidateError::BadSign);
}
Ok(())
}
}