use super::registry::RegistryIndex;
use anyhow::Error;
use base64::{engine::general_purpose::STANDARD, Engine};
use prost::Message;
use serde::{Deserialize, Serialize};
use serde_with::{base64::Base64, serde_as};
use std::fmt;
use thiserror::Error;
use warg_crypto::{hash::AnyHashError, signing, Decode, Signable};
use warg_protobuf::protocol as protobuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublishedProtoEnvelope<Contents> {
pub envelope: ProtoEnvelope<Contents>,
pub registry_index: RegistryIndex,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProtoEnvelope<Contents> {
contents: Contents,
content_bytes: Vec<u8>,
key_id: signing::KeyID,
signature: signing::Signature,
}
impl<Contents> ProtoEnvelope<Contents> {
pub fn signed_contents(
private_key: &signing::PrivateKey,
contents: Contents,
) -> Result<Self, signing::SignatureError>
where
Contents: Signable,
{
let content_bytes: Vec<u8> = contents.encode();
let key_id = private_key.public_key().fingerprint();
let signature = contents.sign(private_key)?;
Ok(ProtoEnvelope {
contents,
content_bytes,
key_id,
signature,
})
}
pub fn content_bytes(&self) -> &[u8] {
&self.content_bytes
}
pub fn key_id(&self) -> &signing::KeyID {
&self.key_id
}
pub fn signature(&self) -> &signing::Signature {
&self.signature
}
pub fn to_protobuf(&self) -> Vec<u8> {
let proto_envelope = protobuf::Envelope {
contents: self.content_bytes.clone(),
key_id: self.key_id.to_string(),
signature: self.signature.to_string(),
};
proto_envelope.encode_to_vec()
}
pub fn from_protobuf(bytes: &[u8]) -> Result<Self, ParseEnvelopeError>
where
Contents: Decode,
{
let envelope = protobuf::Envelope::decode(bytes)?;
let contents = Contents::decode(&envelope.contents)?;
let key_id = envelope.key_id.into();
let signature = envelope.signature.parse()?;
Ok(ProtoEnvelope {
contents,
content_bytes: envelope.contents,
key_id,
signature,
})
}
}
impl<Content> AsRef<Content> for ProtoEnvelope<Content> {
fn as_ref(&self) -> &Content {
&self.contents
}
}
#[derive(Error, Debug)]
pub enum ParseEnvelopeError {
#[error("failed to parse the outer envelope protobuf message")]
ProtobufEnvelope(#[from] prost::DecodeError),
#[error("failed to parse envelope contents from bytes")]
Contents(#[from] Error),
#[error("failed to parse envelope key id")]
KeyID(#[from] AnyHashError),
#[error("failed to parse envelope signature")]
Signature(#[from] signing::SignatureParseError),
}
#[serde_as]
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProtoEnvelopeBody {
#[serde_as(as = "Base64")]
content_bytes: Vec<u8>,
key_id: signing::KeyID,
signature: signing::Signature,
}
impl<Content> TryFrom<ProtoEnvelopeBody> for ProtoEnvelope<Content>
where
Content: Decode,
{
type Error = Error;
fn try_from(value: ProtoEnvelopeBody) -> Result<Self, Self::Error> {
let contents = Content::decode(&value.content_bytes)?;
let envelope = ProtoEnvelope {
contents,
content_bytes: value.content_bytes,
key_id: value.key_id,
signature: value.signature,
};
Ok(envelope)
}
}
impl<Content> From<ProtoEnvelope<Content>> for ProtoEnvelopeBody {
fn from(value: ProtoEnvelope<Content>) -> Self {
ProtoEnvelopeBody {
content_bytes: value.content_bytes,
key_id: value.key_id,
signature: value.signature,
}
}
}
impl fmt::Debug for ProtoEnvelopeBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ProtoEnvelopeBody")
.field("content_bytes", &STANDARD.encode(&self.content_bytes))
.field("key_id", &self.key_id)
.field("signature", &self.signature)
.finish()
}
}
#[serde_as]
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublishedProtoEnvelopeBody {
#[serde(flatten)]
pub envelope: ProtoEnvelopeBody,
pub registry_index: RegistryIndex,
}
impl<Content> TryFrom<PublishedProtoEnvelopeBody> for PublishedProtoEnvelope<Content>
where
Content: Decode,
{
type Error = Error;
fn try_from(value: PublishedProtoEnvelopeBody) -> Result<Self, Self::Error> {
Ok(PublishedProtoEnvelope {
envelope: ProtoEnvelope::<Content>::try_from(value.envelope)?,
registry_index: value.registry_index,
})
}
}
impl<Content> From<PublishedProtoEnvelope<Content>> for PublishedProtoEnvelopeBody {
fn from(value: PublishedProtoEnvelope<Content>) -> Self {
PublishedProtoEnvelopeBody {
envelope: ProtoEnvelopeBody::from(value.envelope),
registry_index: value.registry_index,
}
}
}
impl fmt::Debug for PublishedProtoEnvelopeBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PublishedProtoEnvelopeBody")
.field(
"content_bytes",
&STANDARD.encode(&self.envelope.content_bytes),
)
.field("key_id", &self.envelope.key_id)
.field("signature", &self.envelope.signature)
.field("registry_index", &self.registry_index)
.finish()
}
}