#![recursion_limit = "1024"]
#![deny(missing_docs,
missing_debug_implementations, missing_copy_implementations,
trivial_casts, trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces, unused_qualifications)]
extern crate base64;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate lazy_static;
extern crate regex;
pub mod errors;
pub use errors::*;
use regex::bytes::{Captures, Regex};
const REGEX_STR: &'static str =
r"(?s)-----BEGIN (?P<begin>.*?)-----\s*(?P<data>.*?)-----END (?P<end>.*?)-----\s*";
lazy_static! {
static ref ASCII_ARMOR: Regex = Regex::new(REGEX_STR).unwrap();
}
#[derive(PartialEq,Debug)]
pub struct Pem {
pub tag: String,
pub contents: Vec<u8>,
}
impl Pem {
fn new_from_captures(caps: Captures) -> Result<Pem> {
fn as_utf8<'a>(bytes: &'a [u8]) -> Result<&'a str> {
std::str::from_utf8(bytes).map_err(|e| ErrorKind::NotUtf8(e).into())
}
let tag = as_utf8(caps.name("begin")
.ok_or_else(|| ErrorKind::MissingBeginTag)?
.as_bytes())?;
ensure!(!tag.is_empty(), ErrorKind::MissingBeginTag);
let tag_end = as_utf8(caps.name("end")
.ok_or_else(|| ErrorKind::MissingEndTag)?
.as_bytes())?;
ensure!(!tag_end.is_empty(), ErrorKind::MissingEndTag);
ensure!(tag == tag_end,
ErrorKind::MismatchedTags(tag.into(), tag_end.into()));
let data = as_utf8(caps.name("data")
.ok_or_else(|| ErrorKind::MissingData)?
.as_bytes())?;
let contents = try!(base64::decode_config(&data, base64::MIME)
.map_err(ErrorKind::InvalidData));
Ok(Pem {
tag: tag.to_owned(),
contents: contents,
})
}
}
pub fn parse<B: AsRef<[u8]>>(input: B) -> Result<Pem> {
ASCII_ARMOR.captures(&input.as_ref())
.ok_or_else(|| ErrorKind::MalformedFraming.into())
.and_then(Pem::new_from_captures)
}
pub fn parse_many<B: AsRef<[u8]>>(input: B) -> Vec<Pem> {
ASCII_ARMOR.captures_iter(&input.as_ref())
.filter_map(|caps| Pem::new_from_captures(caps).ok())
.collect()
}
pub fn encode(pem: &Pem) -> String {
let mut output = String::new();
let contents;
if pem.contents.is_empty() {
contents = String::from("");
} else {
contents = base64::encode_config(&pem.contents, base64::Config::new(
base64::CharacterSet::Standard,
true,
true,
base64::LineWrap::Wrap(64, base64::LineEnding::CRLF)
));
}
output.push_str(&format!("-----BEGIN {}-----\r\n", pem.tag));
output.push_str(&format!("{}\r\n", contents));
output.push_str(&format!("-----END {}-----\r\n", pem.tag));
output
}
pub fn encode_many(pems: &[Pem]) -> String {
pems.iter()
.map(encode)
.collect::<Vec<String>>()
.join("\r\n")
}
#[cfg(test)]
mod test {
use super::*;
const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY-----\r
MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r
-----END RSA PRIVATE KEY-----\r
\r
-----BEGIN RSA PUBLIC KEY-----\r
MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r
-----END RSA PUBLIC KEY-----\r
";
#[test]
fn test_parse_works() {
let pem = parse(SAMPLE).unwrap();
assert_eq!(pem.tag, "RSA PRIVATE KEY");
}
#[test]
fn test_parse_invalid_framing() {
let input = "--BEGIN data-----
-----END data-----";
match parse(&input) {
Err(Error(ErrorKind::MalformedFraming, _)) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn test_parse_invalid_begin() {
let input = "-----BEGIN -----
MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
-----END RSA PUBLIC KEY-----";
match parse(&input) {
Err(Error(ErrorKind::MissingBeginTag, _)) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn test_parse_invalid_end() {
let input = "-----BEGIN DATA-----
MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
-----END -----";
match parse(&input) {
Err(Error(ErrorKind::MissingEndTag, _)) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn test_parse_invalid_data() {
let input = "-----BEGIN DATA-----
MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oY?
QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
-----END DATA-----";
match parse(&input) {
Err(Error(ErrorKind::InvalidData(_), _)) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn test_parse_empty_data() {
let input = "-----BEGIN DATA-----
-----END DATA-----";
let pem = parse(&input).unwrap();
assert_eq!(pem.contents.len(), 0);
}
#[test]
fn test_parse_many_works() {
let pems = parse_many(SAMPLE);
assert_eq!(pems.len(), 2);
assert_eq!(pems[0].tag, "RSA PRIVATE KEY");
assert_eq!(pems[1].tag, "RSA PUBLIC KEY");
}
#[test]
fn test_encode_empty_contents() {
let pem = Pem {
tag: String::from("FOO"),
contents: vec![],
};
let encoded = encode(&pem);
assert!(encoded != "");
let pem_out = parse(&encoded).unwrap();
assert_eq!(&pem, &pem_out);
}
#[test]
fn test_encode_contents() {
let pem = Pem {
tag: String::from("FOO"),
contents: vec![1, 2, 3, 4],
};
let encoded = encode(&pem);
assert!(encoded != "");
let pem_out = parse(&encoded).unwrap();
assert_eq!(&pem, &pem_out);
}
#[test]
fn test_encode_many() {
let pems = parse_many(SAMPLE);
let encoded = encode_many(&pems);
assert_eq!(SAMPLE, encoded);
}
}