use std::fmt;
use std::io;
use std::str::FromStr;
use std::sync::Arc;
use bytes::Bytes;
use log::error;
use serde::{
Deserialize, Deserializer, Serialize, Serializer
};
use crate::crypto::PublicKey;
use crate::repository::Cert;
use crate::repository::Crl;
use crate::repository::Manifest;
use crate::repository::Roa;
use crate::repository::aspa::Aspa;
use crate::crypto::Signer;
use crate::crypto::SigningError;
use crate::repository::error::ValidationError;
use crate::repository::x509::{Time, Validity};
use crate::rrdp;
use crate::uri;
use crate::util::base64;
use crate::xml;
use crate::xml::decode::{
Content, Error as XmlError
};
use crate::xml::encode;
use super::sigmsg::SignedMessage;
const VERSION: &str = "4";
const NS: &[u8] = b"http://www.hactrn.net/uris/rpki/publication-spec/";
const MSG: &[u8] = b"msg";
const LIST: &[u8] = b"list";
const SUCCESS: &[u8] = b"success";
const PUBLISH: &[u8] = b"publish";
const WITHDRAW: &[u8] = b"withdraw";
const REPORT_ERROR: &[u8] = b"report_error";
const ERROR_TEXT: &[u8] = b"error_text";
const FAILED_PDU: &[u8] = b"failed_pdu";
pub const CONTENT_TYPE: &str = "application/rpki-publication";
#[derive(Clone, Debug)]
pub struct PublicationCms {
signed_msg: SignedMessage,
message: Message,
}
impl PublicationCms {
pub fn create<S: Signer>(
message: Message,
issuing_key_id: &S::KeyId,
signer: &S,
) -> Result<Self, SigningError<S::Error>> {
let data = message.to_xml_bytes();
let validity = Validity::new(
Time::five_minutes_ago(),
Time::five_minutes_from_now()
);
let signed_msg = SignedMessage::create(
data,
validity,
issuing_key_id,
signer
)?;
Ok(PublicationCms { signed_msg, message})
}
pub fn unpack(self) -> (SignedMessage, Message) {
(self.signed_msg, self.message)
}
pub fn into_message(self) -> Message {
self.message
}
pub fn to_bytes(&self) -> Bytes {
self.signed_msg.to_captured().into_bytes()
}
pub fn decode(
bytes: &[u8]
) -> Result<Self, Error> {
let signed_msg = SignedMessage::decode(bytes, false)
.map_err(|e| Error::CmsDecode(e.to_string()))?;
let content = signed_msg.content().to_bytes();
let message = Message::decode(content.as_ref())?;
Ok(PublicationCms { signed_msg, message })
}
pub fn validate(&self, issuer_key: &PublicKey) -> Result<(), Error> {
self.signed_msg.validate(issuer_key).map_err(|e| e.into())
}
pub fn validate_at(
&self, issuer_key: &PublicKey, when: Time
) -> Result<(), Error> {
self.signed_msg.validate_at(issuer_key, when).map_err(|e| e.into())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Message {
Query(Query),
Reply(Reply),
}
impl Message {
pub fn list_query() -> Self {
Message::Query(Query::List)
}
pub fn list_reply(reply: ListReply) -> Self {
Message::Reply(Reply::List(reply))
}
pub fn delta(delta: PublishDelta) -> Self {
Message::Query(Query::Delta(delta))
}
pub fn success() -> Self {
Message::Reply(Reply::Success)
}
pub fn error(error: ErrorReply) -> Self {
Message::Reply(Reply::ErrorReply(error))
}
}
impl Message {
pub fn as_reply(self) -> Result<Reply, Error> {
match self {
Message::Query(_) => Err(Error::NotReply),
Message::Reply(reply) => Ok(reply)
}
}
pub fn as_query(self) -> Result<Query, Error> {
match self {
Message::Query(query) => Ok(query),
Message::Reply(_) => Err(Error::NotQuery),
}
}
}
impl Message {
pub fn write_xml(
&self, writer: &mut impl io::Write
) -> Result<(), io::Error> {
let mut writer = xml::encode::Writer::new(writer);
let type_value = match self {
Message::Query(_) => "query",
Message::Reply(_) => "reply",
};
writer.element(MSG.into())?
.attr("xmlns", NS)?
.attr("version", VERSION)?
.attr("type", type_value)?
.content(|content|{
match self {
Message::Query(msg) => msg.write_xml(content),
Message::Reply(msg) => msg.write_xml(content)
}
})?;
writer.done()
}
pub fn to_xml_string(&self) -> String {
let bytes = self.to_xml_bytes();
std::str::from_utf8(&bytes)
.unwrap() .to_string()
}
pub fn to_xml_bytes(&self) -> Bytes {
let mut vec = vec![];
self.write_xml(&mut vec).unwrap(); Bytes::from(vec)
}
}
impl Message {
pub fn decode<R: io::BufRead>(reader: R) -> Result<Self, Error> {
let mut reader = xml::decode::Reader::new(reader);
let mut kind: Option<MessageKind> = None;
let mut outer = reader.start(|element| {
if element.name().local() != MSG {
return Err(XmlError::Malformed)
}
element.attributes(|name, value| match name {
b"version" => {
if value.ascii_into::<String>()? != VERSION {
return Err(XmlError::Malformed)
}
Ok(())
}
b"type" => {
kind = Some(match value.ascii_into::<String>()?.as_str() {
"query" => Ok(MessageKind::Query),
"reply" => Ok(MessageKind::Reply),
_ => Err(XmlError::Malformed)
}?);
Ok(())
}
_ => Err(XmlError::Malformed)
})
})?;
let msg = match kind.ok_or(XmlError::Malformed)? {
MessageKind::Query => Message::Query(
Query::decode(&mut outer, &mut reader)?
),
MessageKind::Reply => Message::Reply(
Reply::decode(&mut outer, &mut reader)?
)
};
outer.take_end(&mut reader)?;
reader.end()?;
Ok(msg)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum MessageKind {
Query,
Reply
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Query {
List,
Delta(PublishDelta),
}
impl Query {
fn decode<R: io::BufRead>(
content: &mut Content,
reader: &mut xml::decode::Reader<R>,
) -> Result<Self, Error> {
let mut pdus: Vec<QueryPdu> = vec![];
loop {
match QueryPdu::decode_opt(content, reader)? {
None => break,
Some(pdu) => {
if !pdus.is_empty() && pdu == QueryPdu::List {
error!("Found list pdu in multi-element query");
return Err(Error::XmlError(XmlError::Malformed));
}
pdus.push(pdu);
}
}
}
if pdus.first() == Some(&QueryPdu::List) {
Ok(Query::List)
} else {
let mut delta = PublishDelta::default();
for pdu in pdus.into_iter() {
match pdu {
QueryPdu::List => {} QueryPdu::PublishDeltaElement(el) => delta.0.push(el)
}
}
Ok(Query::Delta(delta))
}
}
}
impl Query {
fn write_xml<W: io::Write>(
&self,
content: &mut encode::Content<W>
) -> Result<(), io::Error> {
match self {
Query::List => {
content.element(LIST.into())?;
},
Query::Delta(delta) => {
for el in &delta.0 {
el.write_xml(content)?;
}
}
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum QueryPduType {
List,
Publish,
Withdraw,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum QueryPdu {
List,
PublishDeltaElement(PublishDeltaElement)
}
impl QueryPdu {
fn decode_opt<R: io::BufRead>(
content: &mut Content,
reader: &mut xml::decode::Reader<R>,
) -> Result<Option<Self>, Error> {
let mut pdu_type = None;
let mut tag: Option<String> = None;
let mut uri: Option<uri::Rsync> = None;
let mut hash: Option<rrdp::Hash> = None;
let pdu_element = content.take_opt_element(reader, |element| {
pdu_type = Some(match element.name().local() {
LIST => Ok(QueryPduType::List),
PUBLISH => Ok(QueryPduType::Publish),
WITHDRAW => Ok(QueryPduType::Withdraw),
_ => Err(XmlError::Malformed)
}?);
element.attributes(|name, value| match name {
b"tag" => {
tag = Some(value.ascii_into()?);
Ok(())
}
b"hash" => {
let hex: String = value.ascii_into()?;
if let Ok(hash_value) =rrdp::Hash::from_str(&hex) {
hash = Some(hash_value);
Ok(())
} else {
Err(XmlError::Malformed)
}
}
b"uri" => {
uri = Some(value.ascii_into()?);
Ok(())
}
_ => {
Err(XmlError::Malformed)
}
})
})?;
let mut pdu_element = match pdu_element {
Some(inner) => inner,
None => return Ok(None)
};
let pdu_type = pdu_type.unwrap();
let pdu: Result<QueryPdu, Error> = match pdu_type {
QueryPduType::List => {
Ok(QueryPdu::List)
},
QueryPduType::Publish => {
let uri = uri.ok_or(XmlError::Malformed)?;
let bytes = pdu_element.take_text(reader, |text| {
text.base64_decode()
})?;
let content = Base64::from_content(&bytes);
match hash {
None => {
Ok(QueryPdu::PublishDeltaElement(
PublishDeltaElement::Publish(
Publish {
tag,
uri,
content,
}
)
))
},
Some(hash) => {
Ok(QueryPdu::PublishDeltaElement(
PublishDeltaElement::Update(
Update {
tag,
uri,
content,
hash,
}
)
))
}
}
}
QueryPduType::Withdraw => {
let uri = uri.ok_or(XmlError::Malformed)?;
let hash = hash.ok_or(XmlError::Malformed)?;
Ok(QueryPdu::PublishDeltaElement(
PublishDeltaElement::Withdraw(
Withdraw { tag, uri, hash }
)
))
}
};
let pdu = pdu?;
pdu_element.take_end(reader)?;
Ok(Some(pdu))
}
fn write_xml<W: io::Write>(
&self,
content: &mut encode::Content<W>
) -> Result<(), io::Error> {
match self {
QueryPdu::List => {
content.element(LIST.into())?;
Ok(())
}
QueryPdu::PublishDeltaElement(el) => el.write_xml(content)
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublishDelta(Vec<PublishDeltaElement>);
impl PublishDelta {
pub fn empty() -> Self {
Self::default()
}
pub fn add_publish(&mut self, publish: Publish) {
self.0.push(PublishDeltaElement::Publish(publish));
}
pub fn add_update(&mut self, update: Update) {
self.0.push(PublishDeltaElement::Update(update));
}
pub fn add_withdraw(&mut self, withdraw: Withdraw) {
self.0.push(PublishDeltaElement::Withdraw(withdraw));
}
pub fn into_elements(self) -> Vec<PublishDeltaElement> {
self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl std::ops::Add for PublishDelta {
type Output = PublishDelta;
fn add(mut self, mut other: Self) -> Self::Output {
self.0.append(&mut other.0);
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum PublishDeltaElement {
Publish(Publish),
Update(Update),
Withdraw(Withdraw),
}
impl PublishDeltaElement {
fn write_xml<W: io::Write>(
&self,
content: &mut encode::Content<W>
) -> Result<(), io::Error> {
match self {
PublishDeltaElement::Publish(p) => p.write_xml(content),
PublishDeltaElement::Update(u) => u.write_xml(content),
PublishDeltaElement::Withdraw(w) => w.write_xml(content)
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Publish {
tag: Option<String>,
uri: uri::Rsync,
content: Base64,
}
impl Publish {
pub fn new(
tag: Option<String>,
uri: uri::Rsync,
content: Base64
) -> Self {
Publish { tag, uri, content }
}
pub fn with_hash_tag(uri: uri::Rsync, content: Base64) -> Self {
let tag = Some(content.to_hash().to_string());
Publish { tag, uri, content }
}
pub fn tag(&self) -> Option<&String> {
self.tag.as_ref()
}
pub fn uri(&self) -> &uri::Rsync {
&self.uri
}
pub fn content(&self) -> &Base64 {
&self.content
}
pub fn unpack(self) -> (Option<String>, uri::Rsync, Base64) {
(self.tag, self.uri, self.content)
}
}
impl Publish {
fn write_xml<W: io::Write>(
&self,
content: &mut encode::Content<W>
) -> Result<(), io::Error> {
content
.element(PUBLISH.into())?
.attr("tag", self.tag_for_xml())?
.attr("uri", &self.uri)?
.content(|content| content.raw(&self.content))?;
Ok(())
}
fn tag_for_xml(&self) -> &str {
self.tag.as_deref().unwrap_or("")
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Update {
tag: Option<String>,
uri: uri::Rsync,
content: Base64,
hash: rrdp::Hash,
}
impl Update {
pub fn new(
tag: Option<String>,
uri: uri::Rsync,
content: Base64,
old_hash: rrdp::Hash
) -> Self {
Update {
tag,
uri,
content,
hash: old_hash,
}
}
pub fn with_hash_tag(
uri: uri::Rsync,
content: Base64,
old_hash: rrdp::Hash
) -> Self {
let tag = Some(content.to_hash().to_string());
Update {
tag,
uri,
content,
hash: old_hash,
}
}
pub fn tag(&self) -> Option<&String> {
self.tag.as_ref()
}
pub fn uri(&self) -> &uri::Rsync {
&self.uri
}
pub fn content(&self) -> &Base64 {
&self.content
}
pub fn hash(&self) -> &rrdp::Hash {
&self.hash
}
pub fn unpack(self) -> (Option<String>, uri::Rsync, Base64, rrdp::Hash) {
(self.tag, self.uri, self.content, self.hash)
}
}
impl Update {
fn write_xml<W: io::Write>(
&self,
content: &mut encode::Content<W>
) -> Result<(), io::Error> {
content
.element(PUBLISH.into())?
.attr("tag", self.tag_for_xml())?
.attr("uri", &self.uri)?
.attr("hash", &self.hash)?
.content(|content| content.raw(&self.content))?;
Ok(())
}
fn tag_for_xml(&self) -> &str {
self.tag.as_deref().unwrap_or("")
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Withdraw {
tag: Option<String>,
uri: uri::Rsync,
hash: rrdp::Hash,
}
impl Withdraw {
pub fn new(tag: Option<String>, uri: uri::Rsync, hash: rrdp::Hash) -> Self {
Withdraw { tag, uri, hash }
}
pub fn with_hash_tag(uri: uri::Rsync, hash: rrdp::Hash) -> Self {
let tag = Some(hash.to_string());
Withdraw { tag, uri, hash }
}
pub fn tag(&self) -> Option<&String> {
self.tag.as_ref()
}
pub fn uri(&self) -> &uri::Rsync {
&self.uri
}
pub fn hash(&self) -> &rrdp::Hash {
&self.hash
}
pub fn unpack(self) -> (Option<String>, uri::Rsync, rrdp::Hash) {
(self.tag, self.uri, self.hash)
}
}
impl Withdraw {
fn write_xml<W: io::Write>(
&self,
content: &mut encode::Content<W>
) -> Result<(), io::Error> {
content.element(WITHDRAW.into())?
.attr("tag", self.tag_for_xml())?
.attr("uri", &self.uri)?
.attr("hash", &self.hash)?;
Ok(())
}
fn tag_for_xml(&self) -> &str {
self.tag.as_deref().unwrap_or("")
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Reply {
List(ListReply),
Success,
ErrorReply(ErrorReply),
}
impl Reply {
fn decode<R: io::BufRead>(
content: &mut Content,
reader: &mut xml::decode::Reader<R>,
) -> Result<Self, Error> {
let mut pdus: Vec<ReplyPdu> = vec![];
loop {
let mut pdu_type = None;
let mut uri: Option<uri::Rsync> = None;
let mut hash: Option<rrdp::Hash> = None;
let mut tag: Option<String> = None;
let mut error_code: Option<ReportErrorCode> = None;
let pdu_element = content.take_opt_element(reader, |element| {
pdu_type = Some(match element.name().local() {
LIST => Ok(ReplyPduType::List),
SUCCESS => Ok(ReplyPduType::Success),
REPORT_ERROR => Ok(ReplyPduType::Error),
_ => Err(XmlError::Malformed)
}?);
element.attributes(|name, value| match name {
b"hash" => {
let hex: String = value.ascii_into()?;
if let Ok(hash_value) =rrdp::Hash::from_str(&hex) {
hash = Some(hash_value);
Ok(())
} else {
Err(XmlError::Malformed)
}
}
b"uri" => {
uri = Some(value.ascii_into()?);
Ok(())
}
b"tag" => {
tag = Some(value.ascii_into()?);
Ok(())
}
b"error_code" => {
error_code = Some(value.ascii_into()?);
Ok(())
}
_ => {
Err(XmlError::Malformed)
}
})
})?;
let mut pdu_element = match pdu_element {
Some(inner) => inner,
None => break
};
let pdu_type = pdu_type.unwrap();
match pdu_type {
ReplyPduType::List => {
let uri = uri.ok_or(XmlError::Malformed)?;
let hash = hash.ok_or(XmlError::Malformed)?;
pdus.push(ReplyPdu::List(ListElement { uri, hash } ));
}
ReplyPduType::Success => {
if pdus.is_empty() {
pdus.push(ReplyPdu::Success)
} else {
error!("Found success pdu in multi-element reply");
return Err(Error::XmlError(XmlError::Malformed))
}
}
ReplyPduType::Error => {
if pdus.iter().any(|existing| {
existing.kind() != ReplyPduType::Error
}) {
error!("Found error report in non-error reply");
return Err(Error::XmlError(XmlError::Malformed));
} else {
let error = ReportError::decode_inner(
error_code.ok_or(XmlError::Malformed)?,
tag,
&mut pdu_element,
reader
)?;
pdus.push(ReplyPdu::Error(error));
}
}
}
pdu_element.take_end(reader)?;
}
let reply_kind = match pdus.first() {
Some(el) => el.kind(),
None => ReplyPduType::List
};
match reply_kind {
ReplyPduType::Success => Ok(Reply::Success),
ReplyPduType::List => {
let mut list = ListReply::default();
for pdu in pdus.into_iter() {
if let ReplyPdu::List(el) = pdu {
list.elements.push(el);
}
}
Ok(Reply::List(list))
}
ReplyPduType::Error => {
let mut errors = ErrorReply::default();
for pdu in pdus.into_iter() {
if let ReplyPdu::Error(err) = pdu {
errors.errors.push(err);
}
}
Ok(Reply::ErrorReply(errors))
}
}
}
}
impl Reply {
fn write_xml<W: io::Write>(
&self,
content: &mut encode::Content<W>
) -> Result<(), io::Error> {
match self {
Reply::List(list) => {
for el in &list.elements {
el.write_xml(content)?;
}
}
Reply::Success => {
content.element(SUCCESS.into())?;
}
Reply::ErrorReply(errors) => {
for err in &errors.errors {
err.write_xml(content)?;
}
}
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ReplyPduType {
List,
Success,
Error,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ReplyPdu {
List(ListElement),
Success,
Error(ReportError),
}
impl ReplyPdu {
fn kind(&self) -> ReplyPduType {
match self {
ReplyPdu::List(_) => ReplyPduType::List,
ReplyPdu::Success => ReplyPduType::Success,
ReplyPdu::Error(_) => ReplyPduType::Error
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ListReply {
elements: Vec<ListElement>,
}
impl ListReply {
pub fn empty() -> Self {
Self::default()
}
pub fn new(elements: Vec<ListElement>) -> Self {
ListReply { elements }
}
pub fn add_element(&mut self, element: ListElement) {
self.elements.push(element);
}
pub fn elements(&self) -> &Vec<ListElement> {
&self.elements
}
pub fn into_elements(self) -> Vec<ListElement> {
self.elements
}
pub fn into_withdraw_delta(self) -> PublishDelta {
let mut delta = PublishDelta::empty();
for el in self.elements.into_iter() {
let (uri, hash) = el.unpack();
let withdraw = Withdraw::with_hash_tag(uri, hash);
delta.add_withdraw(withdraw);
}
delta
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ListElement {
uri: uri::Rsync,
hash: rrdp::Hash,
}
impl ListElement {
pub fn new(
uri: uri::Rsync,
hash: rrdp::Hash
) -> Self {
ListElement { uri, hash }
}
pub fn uri(&self) -> &uri::Rsync {
&self.uri
}
pub fn hash(&self) -> &rrdp::Hash {
&self.hash
}
pub fn unpack(self) -> (uri::Rsync, rrdp::Hash) {
(self.uri, self.hash)
}
}
impl ListElement {
fn write_xml<W: io::Write>(
&self,
content: &mut encode::Content<W>
) -> Result<(), io::Error> {
content
.element(LIST.into())?
.attr("uri", &self.uri)?
.attr("hash", &self.hash)?;
Ok(())
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ErrorReply {
errors: Vec<ReportError>,
}
impl ErrorReply {
pub fn empty() -> Self {
Self::default()
}
pub fn for_error(error: ReportError) -> Self {
ErrorReply { errors: vec![error] }
}
pub fn add_error(&mut self, error: ReportError) {
self.errors.push(error)
}
pub fn errors(&self) -> &Vec<ReportError> {
&self.errors
}
}
impl fmt::Display for ErrorReply {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "error reply including: ")?;
for err in &self.errors {
match &err.error_text {
None => write!(f, "error code: {} ", err.error_code)?,
Some(text) => write!(f, "error code: {}, text: {} ", err.error_code, text)?,
}
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReportError {
error_code: ReportErrorCode,
tag: Option<String>,
error_text: Option<String>,
failed_pdu: Option<QueryPdu>,
}
impl ReportError {
pub fn with_code(
error_code: ReportErrorCode,
) -> Self {
let error_text = Some(error_code.to_text().to_string());
ReportError {
error_code,
tag: None,
error_text,
failed_pdu: None,
}
}
}
impl ReportError {
fn write_xml<W: io::Write>(
&self,
content: &mut encode::Content<W>
) -> Result<(), io::Error> {
content
.element(REPORT_ERROR.into())?
.attr("error_code", &self.error_code)?
.attr_opt("tag", self.tag.as_ref())?
.content(|content| {
content
.element(ERROR_TEXT.into())?
.content(|error_text_content|
error_text_content.raw(self.error_text_or_default())
)?;
content
.element_opt(
self.failed_pdu.as_ref(),
FAILED_PDU.into(),
|pdu, el| {
el.content(|content| pdu.write_xml(content))?;
Ok(())
}
)?;
Ok(())
})?;
Ok(())
}
fn error_text_or_default(&self) -> &str {
self.error_text.as_deref()
.unwrap_or_else(|| self.error_code.to_text())
}
}
impl ReportError {
fn decode_inner<R: io::BufRead>(
error_code: ReportErrorCode,
tag: Option<String>,
report_error_element: &mut Content,
reader: &mut xml::decode::Reader<R>,
) -> Result<Self, Error> {
let mut error_text: Option<String> = None;
let mut failed_pdu: Option<QueryPdu> = None;
loop {
let mut error_text_found = false;
let mut failed_pdu_found = false;
let error_element = report_error_element.take_opt_element(
reader,
|error_element| {
match error_element.name().local() {
ERROR_TEXT => {
error_text_found = true;
Ok(())
}
FAILED_PDU => {
failed_pdu_found = true;
Ok(())
}
_ => {
Err(XmlError::Malformed)
}
}
}
)?;
let mut el = match error_element {
Some(el) => el,
None => break
};
if error_text_found {
let text = el.take_text( reader, |text| {
text.to_ascii().map(|t| t.to_string())
})?;
error_text = Some(text);
}
if failed_pdu_found {
failed_pdu = QueryPdu::decode_opt(&mut el, reader)?;
}
el.take_end(reader)?;
}
Ok(ReportError { error_code, tag, error_text, failed_pdu })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ReportErrorCode {
XmlError,
PermissionFailure,
BadCmsSignature,
ObjectAlreadyPresent,
NoObjectPresent,
NoObjectMatchingHash,
ConsistencyProblem,
OtherError,
}
impl fmt::Display for ReportErrorCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ReportErrorCode::XmlError => write!(f, "xml_error"),
ReportErrorCode::PermissionFailure => write!(f, "permission_failure"),
ReportErrorCode::BadCmsSignature => write!(f, "bad_cms_signature"),
ReportErrorCode::ObjectAlreadyPresent => write!(f, "object_already_present"),
ReportErrorCode::NoObjectPresent => write!(f, "no_object_present"),
ReportErrorCode::NoObjectMatchingHash => write!(f, "no_object_matching_hash"),
ReportErrorCode::ConsistencyProblem => write!(f, "consistency_problem"),
ReportErrorCode::OtherError => write!(f, "other_error"),
}
}
}
impl FromStr for ReportErrorCode {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"xml_error" => Ok(ReportErrorCode::XmlError),
"permission_failure" => Ok(ReportErrorCode::PermissionFailure),
"bad_cms_signature" => Ok(ReportErrorCode::BadCmsSignature),
"object_already_present" => Ok(ReportErrorCode::ObjectAlreadyPresent),
"no_object_present" => Ok(ReportErrorCode::NoObjectPresent),
"no_object_matching_hash" => Ok(ReportErrorCode::NoObjectMatchingHash),
"consistency_problem" => Ok(ReportErrorCode::ConsistencyProblem),
"other_error" => Ok(ReportErrorCode::OtherError),
_ => Err(Error::InvalidErrorCode(s.to_string())),
}
}
}
impl ReportErrorCode {
fn to_text(&self) -> &str {
match self {
ReportErrorCode::XmlError => "Encountered an XML problem.",
ReportErrorCode::PermissionFailure => "Client does not have permission to update this URI.",
ReportErrorCode::BadCmsSignature => "Encountered bad CMS signature.",
ReportErrorCode::ObjectAlreadyPresent => "An object is already present at this URI, yet a \"hash\" attribute was not specified.",
ReportErrorCode::NoObjectPresent => "There is no object present at this URI, yet a \"hash\" attribute was specified.",
ReportErrorCode::NoObjectMatchingHash => "The \"hash\" attribute supplied does not match the \"hash\" attribute of the object at this URI.",
ReportErrorCode::ConsistencyProblem => "Server detected an update that looks like it will cause a consistency problem (e.g., an object was deleted, but the manifest was not updated).",
ReportErrorCode::OtherError => "Found some other issue."
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Base64(Arc<str>);
impl Base64 {
pub fn from_content(content: &[u8]) -> Self {
Base64(base64::Xml.encode(content).into())
}
pub fn to_bytes(&self) -> Bytes {
Bytes::from(base64::Xml.decode(self.0.as_ref()).unwrap())
}
pub fn to_hash(&self) -> rrdp::Hash {
rrdp::Hash::from_data(self.to_bytes().as_ref())
}
pub fn as_str(&self) -> &str {
self.0.as_ref()
}
pub fn size_approx(&self) -> usize {
(self.as_str().len() >> 2) * 3
}
}
impl fmt::Display for Base64 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Serialize for Base64 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.as_str().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Base64 {
fn deserialize<D>(deserializer: D) -> Result<Base64, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
Ok(Base64(string.into()))
}
}
impl From<&Cert> for Base64 {
fn from(cert: &Cert) -> Self {
Base64::from_content(&cert.to_captured().into_bytes())
}
}
impl From<&Roa> for Base64 {
fn from(roa: &Roa) -> Self {
Base64::from_content(&roa.to_captured().into_bytes())
}
}
impl From<&Aspa> for Base64 {
fn from(aspa: &Aspa) -> Self {
Base64::from_content(&aspa.to_captured().into_bytes())
}
}
impl From<&Manifest> for Base64 {
fn from(mft: &Manifest) -> Self {
Base64::from_content(&mft.to_captured().into_bytes())
}
}
impl From<&Crl> for Base64 {
fn from(crl: &Crl) -> Self {
Base64::from_content(&crl.to_captured().into_bytes())
}
}
#[derive(Debug)]
pub enum Error {
InvalidVersion,
XmlError(XmlError),
InvalidErrorCode(String),
CmsDecode(String),
Validation(ValidationError),
NotQuery,
NotReply
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::InvalidVersion => write!(f, "Invalid version"),
Error::XmlError(e) => e.fmt(f),
Error::InvalidErrorCode(code) => {
write!(f, "Invalid error code: {}", code)
}
Error::CmsDecode(msg) => {
write!(f, "Could not decode CMS: {}", msg)
}
Error::Validation(e) => {
write!(f, "CMS is not valid: {}", e)
}
Error::NotQuery => {
write!(f, "was not a query message")
}
Error::NotReply => {
write!(f, "was not a reply message")
}
}
}
}
impl From<XmlError> for Error {
fn from(e: XmlError) -> Self {
Error::XmlError(e)
}
}
impl From<ValidationError> for Error {
fn from(e: ValidationError) -> Self {
Error::Validation(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_and_encode_list_query() {
let xml = include_bytes!("../../test-data/ca/rfc8181/list.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_encode_publish_multi_query() {
let xml = include_bytes!("../../test-data/ca/rfc8181/publish-multi.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_encode_publish_single_query() {
let xml = include_bytes!("../../test-data/ca/rfc8181/publish-single.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_encode_publish_empty_query() {
let xml = include_bytes!("../../test-data/ca/rfc8181/publish-empty.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_encode_publish_empty_short_query() {
let xml = include_bytes!("../../test-data/ca/rfc8181/publish-empty-short.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_list_reply() {
let xml = include_bytes!("../../test-data/ca/rfc8181/list-reply.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_list_reply_single() {
let xml = include_bytes!("../../test-data/ca/rfc8181/list-reply-single.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_list_reply_empty() {
let xml = include_bytes!("../../test-data/ca/rfc8181/list-reply-empty.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_list_reply_empty_short() {
let xml = include_bytes!("../../test-data/ca/rfc8181/list-reply-empty-short.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_success_reply() {
let xml = include_bytes!("../../test-data/ca/rfc8181/success-reply.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
#[test]
fn parse_and_error_reply() {
let xml = include_bytes!("../../test-data/ca/rfc8181/error-reply.xml");
let msg = Message::decode(xml.as_ref()).unwrap();
let re_encoded = msg.to_xml_string();
let re_decoded = Message::decode(re_encoded.as_bytes()).unwrap();
assert_eq!(msg, re_decoded);
}
}
#[cfg(all(test, feature="softkeys"))]
mod signer_test {
use super::*;
use crate::{
ca::idcert::IdCert,
crypto::{softsigner::{OpenSslSigner, KeyId}, PublicKeyFormat}
};
fn sign_and_validate_msg(
signer: &OpenSslSigner,
signing_key: KeyId,
validation_key: &PublicKey,
message: Message
) {
let cms = PublicationCms::create(
message.clone(),
&signing_key,
signer
).unwrap();
let bytes = cms.to_bytes();
let decoded = PublicationCms::decode(&bytes).unwrap();
decoded.validate(validation_key).unwrap();
let decoded_message = decoded.into_message();
assert_eq!(message, decoded_message);
}
fn element(uri: &str, content: &[u8]) -> ListElement {
let uri = uri::Rsync::from_str(uri).unwrap();
let hash = Base64::from_content(content).to_hash();
ListElement::new(uri, hash)
}
fn publish(uri: &str, content: &[u8]) -> Publish {
let uri = uri::Rsync::from_str(uri).unwrap();
let content = Base64::from_content(content);
Publish::with_hash_tag(uri, content)
}
fn update(uri: &str, content: &[u8], old_content: &[u8]) -> Update {
let uri = uri::Rsync::from_str(uri).unwrap();
let content = Base64::from_content(content);
let hash = Base64::from_content(old_content).to_hash();
Update::with_hash_tag(uri, content, hash)
}
fn withdraw(uri: &str, content: &[u8]) -> Withdraw {
let uri = uri::Rsync::from_str(uri).unwrap();
let hash = Base64::from_content(content).to_hash();
Withdraw::with_hash_tag(uri, hash)
}
#[test]
fn sign_and_validate() {
let signer = OpenSslSigner::new();
let key = signer.create_key(PublicKeyFormat::Rsa).unwrap();
let cert = IdCert::new_ta(
Validity::from_secs(60),
&key,
&signer
).unwrap();
sign_and_validate_msg(&signer, key, cert.public_key(), Message::list_query());
let mut rpl = ListReply::empty();
rpl.add_element(element("rsync://localhost/ca/f1.txt", b"a"));
rpl.add_element(element("rsync://localhost/ca/f2.txt", b"b"));
rpl.add_element(element("rsync://localhost/ca/f3.txt", b"c"));
sign_and_validate_msg(&signer, key, cert.public_key(), Message::list_reply(rpl));
let mut delta = PublishDelta::empty();
delta.add_publish(publish("rsync://localhost/ca/f1.txt", b"a"));
delta.add_update(update("rsync://localhost/ca/f2.txt", b"b", b"c"));
delta.add_withdraw(withdraw("rsync://localhost/ca/f3.txt", b"d"));
sign_and_validate_msg(&signer, key, cert.public_key(), Message::delta(delta));
sign_and_validate_msg(&signer, key, cert.public_key(), Message::success());
let mut error_reply = ErrorReply::empty();
let error = ReportError::with_code(ReportErrorCode::PermissionFailure);
error_reply.add_error(error);
sign_and_validate_msg(&signer, key, cert.public_key(), Message::error(error_reply));
}
#[test]
fn base_64_size() {
fn random_bytes(size: usize) -> Vec<u8> {
let mut bytes = [0; 65535];
openssl::rand::rand_bytes(&mut bytes).unwrap();
Vec::from(&bytes[0..size])
}
let sizes = &[0, 10, 16, 256, 1024, 1025, 12322];
for size in sizes {
let buf = random_bytes(*size);
let base64 = Base64::from_content(&buf);
assert!(base64.size_approx() - buf.len() < 4);
}
}
}