Merge branch 'develop'
diff --git a/Cargo.toml b/Cargo.toml
index d1ffee2..1a0bbb9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tuf"
-version = "0.1.5"
+version = "0.1.6"
authors = [ "heartsucker <heartsucker@autistici.org>" ]
description = "Library for The Update Framework (TUF)"
homepage = "https://github.com/heartsucker/rust-tuf"
@@ -37,6 +37,7 @@
hyper = "0.10.10"
itoa = "0.3"
log = "0.3"
+pem = "0.4"
ring = "0.9.4"
serde = "1"
serde_derive = "1"
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..09f45e0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+.PHONY: help dev-docs
+.DEFAULT_GOAL := help
+
+dev-docs: ## Generate the documentation for all modules (dev friendly)
+ @cargo rustdoc --all-features -- --no-defaults --passes "collapse-docs" --passes "unindent-comments"
+
+help: ## Print this message
+ @awk 'BEGIN {FS = ":.*?## "} /^[0-9a-zA-Z_-]+:.*?## / {printf "\033[36m%16s\033[0m : %s\n", $$1, $$2}' $(MAKEFILE_LIST)
diff --git a/README.md b/README.md
index 4d6bf1a..79f836b 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# rust-tuf
-[![Travis build Status](https://travis-ci.org/heartsucker/rust-tuf.svg?branch=master)](https://travis-ci.org/heartsucker/rust-tuf) [![Appveyor build status](https://ci.appveyor.com/api/projects/status/kfyvpkdvn5ap7dqc/branch/master?svg=true)](https://ci.appveyor.com/project/heartsucker/rust-tuf/branch/master)[![codecov](https://codecov.io/gh/heartsucker/rust-tuf/branch/master/graph/badge.svg)](https://codecov.io/gh/heartsucker/rust-tuf)
+[![Travis build Status](https://travis-ci.org/heartsucker/rust-tuf.svg?branch=master)](https://travis-ci.org/heartsucker/rust-tuf) [![Appveyor build status](https://ci.appveyor.com/api/projects/status/kfyvpkdvn5ap7dqc/branch/master?svg=true)](https://ci.appveyor.com/project/heartsucker/rust-tuf/branch/master) [![codecov](https://codecov.io/gh/heartsucker/rust-tuf/branch/master/graph/badge.svg)](https://codecov.io/gh/heartsucker/rust-tuf)
A Rust implementation of [The Update Framework (TUF)](https://theupdateframework.github.io/).
diff --git a/src/cjson.rs b/src/cjson.rs
index 470ed70..b624980 100644
--- a/src/cjson.rs
+++ b/src/cjson.rs
@@ -3,7 +3,7 @@
use std::collections::BTreeMap;
use std::io;
-pub fn canonicalize(jsn: json::Value) -> Result<Vec<u8>, String> {
+pub fn canonicalize(jsn: &json::Value) -> Result<Vec<u8>, String> {
let converted = convert(jsn)?;
let mut buf = Vec::new();
let _ = converted.write(&mut buf); // Vec<u8> impl always succeeds (or panics).
@@ -74,32 +74,32 @@
U64(u64),
}
-fn convert(jsn: json::Value) -> Result<Value, String> {
+fn convert(jsn: &json::Value) -> Result<Value, String> {
match jsn {
- json::Value::Null => Ok(Value::Null),
- json::Value::Bool(b) => Ok(Value::Bool(b)),
- json::Value::Number(n) => {
+ &json::Value::Null => Ok(Value::Null),
+ &json::Value::Bool(b) => Ok(Value::Bool(b)),
+ &json::Value::Number(ref n) => {
n.as_i64()
.map(Number::I64)
.or(n.as_u64().map(Number::U64))
.map(Value::Number)
.ok_or_else(|| String::from("only i64 and u64 are supported"))
}
- json::Value::Array(arr) => {
+ &json::Value::Array(ref arr) => {
let mut out = Vec::new();
- for res in arr.iter().cloned().map(|v| convert(v)) {
+ for res in arr.iter().map(|v| convert(v)) {
out.push(res?)
}
Ok(Value::Array(out))
}
- json::Value::Object(obj) => {
+ &json::Value::Object(ref obj) => {
let mut out = BTreeMap::new();
for (k, v) in obj.iter() {
- let _ = out.insert(k.clone(), convert(v.clone())?);
+ let _ = out.insert(k.clone(), convert(v)?);
}
Ok(Value::Object(out))
}
- json::Value::String(s) => Ok(Value::String(s)),
+ &json::Value::String(ref s) => Ok(Value::String(s.clone())),
}
}
@@ -202,10 +202,17 @@
#[test]
fn root_json() {
let mut file = File::open("./tests/cjson/root.json").expect("couldn't open root.json");
- let mut buf = Vec::new();
- file.read_to_end(&mut buf).expect("couldn't read root.json");
- let jsn = json::from_slice(&buf).expect("not json");
+ let mut buf = String::new();
+ file.read_to_string(&mut buf).expect("couldn't read root.json");
+
+ let mut file = File::open("./tests/cjson/root.cjson").expect("couldn't open root.cjson");
+ let mut cjsn = String::new();
+ file.read_to_string(&mut cjsn).expect("couldn't read root.cjson");
+
+ let ref jsn = json::from_str(&buf).expect("not json");
let out = canonicalize(jsn).expect("couldn't canonicalize");
- assert_eq!(out, buf);
+ let out = ::std::str::from_utf8(&out).expect("not utf-8");
+
+ assert_eq!(out, cjsn);
}
}
diff --git a/src/error.rs b/src/error.rs
index f21873a..f2d0518 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -8,6 +8,8 @@
/// Error type for all TUF related errors.
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
+ /// ASN.1 parse errors.
+ Asn1,
/// Errors for converting JSON to canonical JSON.
CanonicalJsonError(String),
/// The metadata for the given role has expired.
diff --git a/src/lib.rs b/src/lib.rs
index 6b453cc..292ee33 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -143,11 +143,11 @@
extern crate itoa;
#[macro_use]
extern crate log;
+extern crate pem;
extern crate ring;
extern crate serde;
#[macro_use]
extern crate serde_derive;
-#[macro_use]
extern crate serde_json as json;
extern crate url;
extern crate untrusted;
@@ -157,9 +157,10 @@
mod util;
mod cjson;
+mod error;
mod http;
mod metadata;
-mod error;
+mod rsa;
mod tuf;
pub use tuf::*;
diff --git a/src/metadata.rs b/src/metadata.rs
index e90a983..7d77a93 100644
--- a/src/metadata.rs
+++ b/src/metadata.rs
@@ -1,9 +1,10 @@
use chrono::{DateTime, UTC};
use data_encoding::HEXLOWER;
use json;
+use pem;
use ring;
use ring::digest::{digest, SHA256};
-use ring::signature::ED25519;
+use ring::signature::{ED25519, RSA_PSS_2048_8192_SHA256, RSA_PSS_2048_8192_SHA512};
use serde::de::{Deserialize, DeserializeOwned, Deserializer, Error as DeserializeError};
use std::collections::HashMap;
use std::fmt::{self, Display, Formatter, Debug};
@@ -13,6 +14,7 @@
use cjson::canonicalize;
use error::Error;
+use rsa::convert_to_pkcs1;
static HASH_PREFERENCES: &'static [HashType] = &[HashType::Sha512, HashType::Sha256];
@@ -51,11 +53,11 @@
}
}
-pub trait RoleType: Debug {
+pub trait RoleType: Debug + Clone{
fn matches(role: &Role) -> bool;
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Root {}
impl RoleType for Root {
fn matches(role: &Role) -> bool {
@@ -66,7 +68,7 @@
}
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Targets {}
impl RoleType for Targets {
fn matches(role: &Role) -> bool {
@@ -77,7 +79,7 @@
}
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Timestamp {}
impl RoleType for Timestamp {
fn matches(role: &Role) -> bool {
@@ -88,7 +90,7 @@
}
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Snapshot {}
impl RoleType for Snapshot {
fn matches(role: &Role) -> bool {
@@ -99,8 +101,8 @@
}
}
-#[derive(Debug)]
-pub struct SignedMetadata<R: RoleType> {
+#[derive(Debug, Clone)]
+pub struct SignedMetadata<R: RoleType + Clone> {
pub signatures: Vec<Signature>,
pub signed: json::Value,
_role: PhantomData<R>,
@@ -498,6 +500,8 @@
pub enum KeyType {
/// [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) signature scheme.
Ed25519,
+ /// [RSA](https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29)
+ Rsa,
/// Internal representation of an unsupported key type.
Unsupported(String),
}
@@ -506,6 +510,8 @@
fn supports(&self, scheme: &SignatureScheme) -> bool {
match (self, scheme) {
(&KeyType::Ed25519, &SignatureScheme::Ed25519) => true,
+ (&KeyType::Rsa, &SignatureScheme::RsaSsaPssSha256) => true,
+ (&KeyType::Rsa, &SignatureScheme::RsaSsaPssSha512) => true,
_ => false,
}
}
@@ -517,6 +523,7 @@
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ed25519" => Ok(KeyType::Ed25519),
+ "rsa" => Ok(KeyType::Rsa),
typ => Ok(KeyType::Unsupported(typ.into())),
}
}
@@ -535,15 +542,25 @@
/// The raw bytes of a public key.
#[derive(Clone, PartialEq, Debug)]
-pub struct KeyValue(pub Vec<u8>);
+pub struct KeyValue {
+ /// The key's raw bytes.
+ pub value: Vec<u8>,
+ /// The key's original value, needed for ID calculation
+ pub original: String,
+ /// The key's type,
+ pub typ: KeyType,
+}
impl KeyValue {
/// Calculates the `KeyId` of the public key.
pub fn key_id(&self) -> KeyId {
- // TODO this only works because we're using ed25519
- // and this will break when we add RSA.
- let key_value_hex = canonicalize(json!(HEXLOWER.encode(&self.0))).unwrap(); // TODO unwrap
- KeyId(HEXLOWER.encode(digest(&SHA256, &key_value_hex).as_ref()))
+ match self.typ {
+ KeyType::Unsupported(_) => KeyId(String::from("error")), // TODO this feels wrong, but we check this everywhere else
+ _ => {
+ let key_value = canonicalize(&json::Value::String(self.original.clone())).unwrap(); // TODO unwrap
+ KeyId(HEXLOWER.encode(digest(&SHA256, &key_value).as_ref()))
+ }
+ }
}
}
@@ -551,12 +568,32 @@
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
match Deserialize::deserialize(de)? {
json::Value::String(ref s) => {
- // TODO this is shit because we can't tell what type of key it is
- // e.g., ed25519 => hex, rsa => PEM
- // need to add this into the type/struct so it can be accessed here
- HEXLOWER.decode(s.as_ref())
- .map(KeyValue)
- .map_err(|e| DeserializeError::custom(format!("Key value was not hex: {}", e)))
+ // TODO this is pretty shaky
+ if s.starts_with("-----") {
+ pem::parse(s)
+ .map(|p| {
+ KeyValue {
+ value: p.contents,
+ original: s.clone(),
+ typ: KeyType::Rsa,
+ }
+ })
+ .map_err(|e| {
+ DeserializeError::custom(format!("Key was not PEM encoded: {}", e))
+ })
+ } else {
+ HEXLOWER.decode(s.as_ref())
+ .map(|v| {
+ KeyValue {
+ value: v,
+ original: s.clone(),
+ typ: KeyType::Ed25519,
+ }
+ })
+ .map_err(|e| {
+ DeserializeError::custom(format!("Key value was not hex: {}", e))
+ })
+ }
}
json::Value::Object(mut object) => {
json::from_value::<KeyValue>(object.remove("public")
@@ -609,23 +646,25 @@
#[derive(Clone, PartialEq, Debug)]
pub enum SignatureScheme {
Ed25519,
+ RsaSsaPssSha256,
+ RsaSsaPssSha512,
Unsupported(String),
}
impl SignatureScheme {
fn verify(&self, pub_key: &KeyValue, msg: &[u8], sig: &SignatureValue) -> Result<(), Error> {
- match self {
- &SignatureScheme::Ed25519 => {
- ring::signature::verify(&ED25519,
- Input::from(&pub_key.0),
- Input::from(msg),
- Input::from(&sig.0))
- .map_err(|_| Error::VerificationFailure("Bad signature".into()))
- }
+ let alg: &ring::signature::VerificationAlgorithm = match self {
+ &SignatureScheme::Ed25519 => &ED25519,
+ &SignatureScheme::RsaSsaPssSha256 => &RSA_PSS_2048_8192_SHA256,
+ &SignatureScheme::RsaSsaPssSha512 => &RSA_PSS_2048_8192_SHA512,
&SignatureScheme::Unsupported(ref s) => {
- Err(Error::UnsupportedSignatureScheme(s.clone()))
+ return Err(Error::UnsupportedSignatureScheme(s.clone()));
}
- }
+ };
+
+ ring::signature::verify(alg, Input::from(&convert_to_pkcs1(&pub_key.value)),
+ Input::from(msg), Input::from(&sig.0))
+ .map_err(|_| Error::VerificationFailure("Bad signature".into()))
}
}
@@ -635,6 +674,8 @@
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ed25519" => Ok(SignatureScheme::Ed25519),
+ "rsassa-pss-sha256" => Ok(SignatureScheme::RsaSsaPssSha256),
+ "rsassa-pss-sha512" => Ok(SignatureScheme::RsaSsaPssSha512),
typ => Ok(SignatureScheme::Unsupported(typ.into())),
}
}
diff --git a/src/rsa/der.rs b/src/rsa/der.rs
new file mode 100644
index 0000000..6b465a6
--- /dev/null
+++ b/src/rsa/der.rs
@@ -0,0 +1,338 @@
+// Copyright 2015 Brian Smith.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+//! Building blocks for parsing DER-encoded ASN.1 structures.
+//!
+//! This module contains the foundational parts of an ASN.1 DER parser.
+
+use ring;
+use std::io::{self, Write};
+use untrusted;
+
+pub const CONSTRUCTED: u8 = 1 << 5;
+
+#[derive(Clone, Copy, PartialEq, Debug)]
+#[repr(u8)]
+pub enum Tag {
+ EOC = 0x00,
+ Integer = 0x02,
+ BitString = 0x03,
+ Null = 0x05,
+ OID = 0x06,
+ Sequence = CONSTRUCTED | 0x10, // 0x30
+}
+
+pub fn expect_tag_and_get_value<'a>(input: &mut untrusted::Reader<'a>,
+ tag: Tag)
+ -> Result<untrusted::Input<'a>, ring::error::Unspecified> {
+
+ let (actual_tag, inner) = read_tag_and_get_value(input)?;
+ if (tag as usize) != (actual_tag as usize) {
+ return Err(ring::error::Unspecified);
+ }
+ Ok(inner)
+}
+
+pub fn read_tag_and_get_value<'a>
+ (input: &mut untrusted::Reader<'a>)
+ -> Result<(u8, untrusted::Input<'a>), ring::error::Unspecified> {
+ let tag = input.read_byte()?;
+
+ if tag as usize == Tag::EOC as usize {
+ return Ok((tag, untrusted::Input::from(&[])))
+ }
+
+ if (tag & 0x1F) == 0x1F {
+ return Err(ring::error::Unspecified); // High tag number form is not allowed.
+ }
+
+ // If the high order bit of the first byte is set to zero then the length
+ // is encoded in the seven remaining bits of that byte. Otherwise, those
+ // seven bits represent the number of bytes used to encode the length.
+ let length = match input.read_byte()? {
+ n if (n & 0x80) == 0 => n as usize,
+ 0x81 => {
+ let second_byte = input.read_byte()?;
+ if second_byte < 128 {
+ return Err(ring::error::Unspecified); // Not the canonical encoding.
+ }
+ second_byte as usize
+ }
+ 0x82 => {
+ let second_byte = input.read_byte()? as usize;
+ let third_byte = input.read_byte()? as usize;
+ let combined = (second_byte << 8) | third_byte;
+
+ if combined < 256 {
+ return Err(ring::error::Unspecified); // Not the canonical encoding.
+ }
+ combined
+ }
+ _ => {
+ return Err(ring::error::Unspecified); // We don't support longer lengths.
+ }
+ };
+
+
+ let inner = input.skip_and_get_input(length)?;
+ Ok((tag, inner))
+}
+
+// TODO: investigate taking decoder as a reference to reduce generated code
+// size.
+pub fn nested<'a, F, R, E: Copy>(input: &mut untrusted::Reader<'a>,
+ tag: Tag,
+ error: E,
+ decoder: F)
+ -> Result<R, E>
+ where F: FnOnce(&mut untrusted::Reader<'a>) -> Result<R, E>
+{
+ let inner = expect_tag_and_get_value(input, tag).map_err(|_| error)?;
+ inner.read_all(error, decoder)
+}
+
+fn nonnegative_integer<'a>(input: &mut untrusted::Reader<'a>,
+ min_value: u8)
+ -> Result<untrusted::Input<'a>, ring::error::Unspecified> {
+ // Verify that |input|, which has had any leading zero stripped off, is the
+ // encoding of a value of at least |min_value|.
+ fn check_minimum(input: untrusted::Input,
+ min_value: u8)
+ -> Result<(), ring::error::Unspecified> {
+ input.read_all(ring::error::Unspecified, |input| {
+ let first_byte = input.read_byte()?;
+ if input.at_end() && first_byte < min_value {
+ return Err(ring::error::Unspecified);
+ }
+ let _ = input.skip_to_end();
+ Ok(())
+ })
+ }
+
+ let value = expect_tag_and_get_value(input, Tag::Integer)?;
+
+ value.read_all(ring::error::Unspecified, |input| {
+ // Empty encodings are not allowed.
+ let first_byte = input.read_byte()?;
+
+ if first_byte == 0 {
+ if input.at_end() {
+ // |value| is the legal encoding of zero.
+ if min_value > 0 {
+ return Err(ring::error::Unspecified);
+ }
+ return Ok(value);
+ }
+
+ let r = input.skip_to_end();
+ r.read_all(ring::error::Unspecified, |input| {
+ let second_byte = input.read_byte()?;
+ if (second_byte & 0x80) == 0 {
+ // A leading zero is only allowed when the value's high bit
+ // is set.
+ return Err(ring::error::Unspecified);
+ }
+ let _ = input.skip_to_end();
+ Ok(())
+ })?;
+ check_minimum(r, min_value)?;
+ return Ok(r);
+ }
+
+ // Negative values are not allowed.
+ if (first_byte & 0x80) != 0 {
+ return Err(ring::error::Unspecified);
+ }
+
+ let _ = input.skip_to_end();
+ check_minimum(value, min_value)?;
+ Ok(value)
+ })
+}
+
+/// Parses a positive DER integer, returning the big-endian-encoded value, sans
+/// any leading zero byte.
+#[inline]
+pub fn positive_integer<'a>(input: &mut untrusted::Reader<'a>)
+ -> Result<untrusted::Input<'a>, ring::error::Unspecified> {
+ nonnegative_integer(input, 1)
+}
+
+
+pub struct Der<'a, W: Write + 'a> {
+ writer: &'a mut W,
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct Error;
+
+impl From<ring::error::Unspecified> for Error {
+ fn from(_: ring::error::Unspecified) -> Error {
+ Error
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(_: io::Error) -> Error {
+ Error
+ }
+}
+
+
+impl<'a, W: Write> Der<'a, W> {
+ pub fn new(writer: &'a mut W) -> Self {
+ Der { writer: writer }
+ }
+
+ fn length_of_length(len: usize) -> u8 {
+ let mut i = len;
+ let mut num_bytes = 1;
+
+ while i > 255 {
+ num_bytes += 1;
+ i >>= 8;
+ }
+
+ num_bytes
+ }
+
+ fn write_len(&mut self, len: usize) -> Result<(), Error> {
+ if len >= 128 {
+ let n = Self::length_of_length(len);
+ self.writer.write_all(&[0x80 | n])?;
+
+ for i in (1..n + 1).rev() {
+ self.writer.write_all(&[(len >> ((i - 1) * 8)) as u8])?;
+ }
+ } else {
+ self.writer.write_all(&[len as u8])?;
+ }
+
+ Ok(())
+ }
+
+ pub fn write_integer(&mut self, input: untrusted::Input) -> Result<(), Error> {
+ self.writer.write_all(&[Tag::Integer as u8])?;
+ let mut buf = Vec::new();
+
+ input.read_all(Error, |read| {
+ while let Ok(byte) = read.read_byte() {
+ buf.push(byte);
+ }
+
+ Ok(())
+ })?;
+
+ self.write_len(buf.len())?;
+
+ Ok(self.writer.write_all(&mut buf)?)
+ }
+
+ pub fn write_sequence<F: FnOnce(&mut Der<Vec<u8>>) -> Result<(), Error>>
+ (&mut self,
+ func: F)
+ -> Result<(), Error> {
+ self.writer.write_all(&[Tag::Sequence as u8])?;
+ let mut buf = Vec::new();
+
+ {
+ let mut inner = Der::new(&mut buf);
+ func(&mut inner)?;
+ }
+
+ self.write_len(buf.len())?;
+ Ok(self.writer.write_all(&buf)?)
+ }
+}
+
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use untrusted;
+
+ fn with_good_i<F, R>(value: &[u8], f: F)
+ where F: FnOnce(&mut untrusted::Reader) -> Result<R, ring::error::Unspecified>
+ {
+ let r = untrusted::Input::from(value).read_all(ring::error::Unspecified, f);
+ assert!(r.is_ok());
+ }
+
+ fn with_bad_i<F, R>(value: &[u8], f: F)
+ where F: FnOnce(&mut untrusted::Reader) -> Result<R, ring::error::Unspecified>
+ {
+ let r = untrusted::Input::from(value).read_all(ring::error::Unspecified, f);
+ assert!(r.is_err());
+ }
+
+ static ZERO_INTEGER: &'static [u8] = &[0x02, 0x01, 0x00];
+
+ static GOOD_POSITIVE_INTEGERS: &'static [(&'static [u8], u8)] =
+ &[(&[0x02, 0x01, 0x01], 0x01),
+ (&[0x02, 0x01, 0x02], 0x02),
+ (&[0x02, 0x01, 0x7e], 0x7e),
+ (&[0x02, 0x01, 0x7f], 0x7f),
+
+ // Values that need to have an 0x00 prefix to disambiguate them from
+ // them from negative values.
+ (&[0x02, 0x02, 0x00, 0x80], 0x80),
+ (&[0x02, 0x02, 0x00, 0x81], 0x81),
+ (&[0x02, 0x02, 0x00, 0xfe], 0xfe),
+ (&[0x02, 0x02, 0x00, 0xff], 0xff)];
+
+ static BAD_NONNEGATIVE_INTEGERS: &'static [&'static [u8]] = &[&[], // At end of input
+ &[0x02], // Tag only
+ &[0x02, 0x00], // Empty value
+
+ // Length mismatch
+ &[0x02, 0x00, 0x01],
+ &[0x02, 0x01],
+ &[0x02, 0x01, 0x00, 0x01],
+ &[0x02, 0x01, 0x01, 0x00], // Would be valid if last byte is ignored.
+ &[0x02, 0x02, 0x01],
+
+ // Negative values
+ &[0x02, 0x01, 0x80],
+ &[0x02, 0x01, 0xfe],
+ &[0x02, 0x01, 0xff],
+
+ // Values that have an unnecessary leading 0x00
+ &[0x02, 0x02, 0x00, 0x00],
+ &[0x02, 0x02, 0x00, 0x01],
+ &[0x02, 0x02, 0x00, 0x02],
+ &[0x02, 0x02, 0x00, 0x7e],
+ &[0x02, 0x02, 0x00, 0x7f]];
+
+ #[test]
+ fn test_positive_integer() {
+ with_bad_i(ZERO_INTEGER, |input| {
+ let _ = positive_integer(input)?;
+ Ok(())
+ });
+ for &(ref test_in, test_out) in GOOD_POSITIVE_INTEGERS.iter() {
+ with_good_i(test_in, |input| {
+ let test_out = [test_out];
+ assert_eq!(positive_integer(input)?,
+ untrusted::Input::from(&test_out[..]));
+ Ok(())
+ });
+ }
+ for &test_in in BAD_NONNEGATIVE_INTEGERS.iter() {
+ with_bad_i(test_in, |input| {
+ let _ = positive_integer(input)?;
+ Ok(())
+ });
+ }
+ }
+}
diff --git a/src/rsa/mod.rs b/src/rsa/mod.rs
new file mode 100644
index 0000000..3a46bdb
--- /dev/null
+++ b/src/rsa/mod.rs
@@ -0,0 +1,165 @@
+//! Helper module for RSA key encoding / decoding.
+
+mod der;
+
+use untrusted::Input;
+
+use self::der::{Tag, Der};
+
+/// Corresponds to `1.2.840.113549.1.1.1 rsaEncryption(PKCS #1)`
+const RSA_PKCS1_OID: &'static [u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
+
+pub fn convert_to_pkcs1<'a>(input: &[u8]) -> Vec<u8> {
+ // if we ever move away from `ring`, this needs to do an explicit key size check (>= 2048)
+ from_pkcs1(input)
+ .or_else(|| from_spki(input))
+ .unwrap_or_else(|| input.to_vec())
+}
+
+fn from_pkcs1(input: &[u8]) -> Option<Vec<u8>> {
+ let _input = Input::from(&input);
+ _input.read_all(der::Error, |i| {
+ der::nested(i, Tag::Sequence, der::Error, |i| {
+ let _ = der::positive_integer(i)?;
+ let _ = der::positive_integer(i)?;
+ // if the input was already pkcs1, just return it
+ Ok(input.to_vec())
+ })
+ })
+ .ok()
+}
+
+fn from_spki(input: &[u8]) -> Option<Vec<u8>> {
+ let _input = Input::from(&input);
+ _input.read_all(der::Error, |i| {
+ der::nested(i, Tag::Sequence, der::Error, |i| {
+ der::nested(i, Tag::Sequence, der::Error, |i| {
+ let oid = der::expect_tag_and_get_value(i, Tag::OID)?;
+ if oid != Input::from(RSA_PKCS1_OID) {
+ return Err(der::Error);
+ }
+
+ let _ = der::expect_tag_and_get_value(i, Tag::Null)?;
+ Ok(())
+ })?;
+
+ der::nested(i, Tag::BitString, der::Error, |i| {
+ // wtf why
+ let _ = der::expect_tag_and_get_value(i, Tag::EOC)?;
+ Ok(i.skip_to_end().iter().cloned().collect())
+ //der::nested(i, Tag::Sequence, der::Error, |i| {
+ // let n = der::positive_integer(i)?;
+ // let e = der::positive_integer(i)?;
+ // write_pkcs1(n, e)
+ //})
+ })
+ })
+ })
+ .ok()
+}
+
+#[allow(dead_code)]
+fn write_pkcs1(n: Input, e: Input) -> Result<Vec<u8>, der::Error> {
+ let mut output = Vec::new();
+ {
+ let mut _der = Der::new(&mut output);
+ _der.write_sequence(|_der| {
+ _der.write_integer(n)?;
+ _der.write_integer(e)
+ })?;
+ }
+
+ Ok(output)
+}
+
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use pem;
+ use std::fs::File;
+ use std::io::Read;
+
+ fn read_file(path: &str) -> Vec<u8> {
+ let mut file = File::open(path).expect("couldn't open file");
+ let mut buf = Vec::new();
+ file.read_to_end(&mut buf).expect("couldn't read file");
+ buf
+ }
+
+ #[test]
+ fn test_write_pkcs1() {
+ let contents = read_file("./tests/rsa/pkcs1-1.pub");
+ let contents = pem::parse(&contents).expect("not PEM").contents;
+
+ let n = &[0x00, 0x9d, 0xda, 0x85, 0x17, 0x15, 0x67, 0xab, 0xb1, 0x63, 0x8a, 0x13, 0x01,
+ 0xee, 0xc0, 0x63, 0x7c, 0xc3, 0x08, 0x4b, 0x6d, 0x75, 0xd8, 0x70, 0x74, 0x3d,
+ 0xab, 0x98, 0xef, 0x00, 0xd0, 0xf2, 0x04, 0xe7, 0x7d, 0xb5, 0xa4, 0x08, 0xe3,
+ 0x90, 0xda, 0x4b, 0xe1, 0xd1, 0xff, 0x0f, 0x87, 0x8d, 0x6b, 0x43, 0x58, 0x99,
+ 0x88, 0xf6, 0x99, 0xab, 0xe7, 0x90, 0xfb, 0x2a, 0xa1, 0x3c, 0x2b, 0x0f, 0x24,
+ 0xa4, 0x9e, 0xab, 0xd1, 0xfc, 0xa0, 0xe0, 0xa8, 0x9f, 0x82, 0x48, 0xe5, 0xa7,
+ 0xd2, 0x4d, 0x44, 0xe4, 0x0b, 0x43, 0x66, 0x03, 0x54, 0x8d, 0xdd, 0xc3, 0x0c,
+ 0x26, 0xf5, 0x20, 0x36, 0xbf, 0xae, 0x05, 0x63, 0x9c, 0xf8, 0x81, 0xeb, 0xf7,
+ 0x4a, 0x3a, 0xc4, 0x14, 0xee, 0xce, 0x99, 0x66, 0x9f, 0x3c, 0xe3, 0x18, 0x21,
+ 0x8d, 0x68, 0xe3, 0x0b, 0xb7, 0xb3, 0xf7, 0xca, 0xe1, 0x7c, 0xab, 0xd5, 0x17,
+ 0x6f, 0x50, 0xc1, 0x38, 0x1b, 0xea, 0x62, 0xeb, 0x46, 0x07, 0x95, 0x01, 0xfb,
+ 0xd3, 0x1a, 0xd0, 0xae, 0x1c, 0xe6, 0x53, 0x27, 0x53, 0x2d, 0x08, 0x55, 0xbe,
+ 0xa3, 0xd6, 0xd1, 0x02, 0x14, 0xa4, 0xa2, 0xe1, 0x14, 0xde, 0xa4, 0x0e, 0x54,
+ 0x00, 0xe5, 0x79, 0x2c, 0x4d, 0x93, 0xe8, 0x6b, 0x3c, 0xf6, 0x44, 0x63, 0x85,
+ 0x3c, 0x6f, 0x56, 0xc2, 0x80, 0x02, 0x3f, 0x76, 0xcf, 0x75, 0x46, 0x5f, 0x9a,
+ 0x49, 0x47, 0xdc, 0xe6, 0xe9, 0x9a, 0xc0, 0x6e, 0x34, 0x9e, 0x9f, 0xd2, 0xdf,
+ 0xbc, 0x55, 0xa0, 0x77, 0x61, 0xf3, 0xd5, 0x0c, 0xb8, 0x77, 0xd2, 0x66, 0xd2,
+ 0x24, 0x9a, 0x25, 0xbe, 0x55, 0x1b, 0x4e, 0xbf, 0x3b, 0x82, 0x4c, 0x4f, 0x51,
+ 0x57, 0x7c, 0x8b, 0xf6, 0x38, 0xfe, 0x4d, 0x97, 0x32, 0xa8, 0xc8, 0x3c, 0x69,
+ 0xe5, 0x91, 0x15, 0x2c, 0x75, 0x8d, 0x92, 0xc1, 0xc7, 0x6b];
+
+ let e = &[0x01, 0x00, 0x01];
+ let bytes = write_pkcs1(Input::from(n), Input::from(e));
+
+ assert_eq!(bytes, Ok(contents));
+ }
+
+ #[test]
+ fn pkcs1_noop_conversion_1() {
+ let contents = read_file("./tests/rsa/pkcs1-1.pub");
+ let contents = pem::parse(&contents).expect("not PEM").contents;
+ assert_eq!(convert_to_pkcs1(&contents), contents);
+ }
+
+ #[test]
+ fn pkcs1_noop_conversion_2() {
+ let contents = read_file("./tests/rsa/pkcs1-2.pub");
+ let contents = pem::parse(&contents).expect("not PEM").contents;
+ assert_eq!(convert_to_pkcs1(&contents), contents);
+ }
+
+ #[test]
+ fn pkcs1_from_spki_conversion_1() {
+ let spki = read_file("./tests/rsa/spki-1.pub");
+ let spki = pem::parse(&spki).expect("not PEM").contents;
+
+ let pkcs1 = read_file("./tests/rsa/pkcs1-1.pub");
+ let pkcs1 = pem::parse(&pkcs1).expect("not PEM").contents;
+
+ for (i, (a, b)) in convert_to_pkcs1(&spki).iter().zip(pkcs1.iter()).enumerate() {
+ println!("{} {} {}", i, a, b);
+ }
+
+ assert!(convert_to_pkcs1(&spki) == pkcs1);
+ }
+
+ #[test]
+ fn pkcs1_from_spki_conversion_2() {
+ let spki = read_file("./tests/rsa/spki-2.pub");
+ let spki = pem::parse(&spki).expect("not PEM").contents;
+
+ let pkcs1 = read_file("./tests/rsa/pkcs1-2.pub");
+ let pkcs1 = pem::parse(&pkcs1).expect("not PEM").contents;
+
+ for (i, (a, b)) in convert_to_pkcs1(&spki).iter().zip(pkcs1.iter()).enumerate() {
+ println!("{} {} {}", i, a, b);
+ }
+
+ assert!(convert_to_pkcs1(&spki) == pkcs1);
+ }
+}
diff --git a/src/tuf.rs b/src/tuf.rs
index 0227057..8b666e4 100644
--- a/src/tuf.rs
+++ b/src/tuf.rs
@@ -392,7 +392,6 @@
Some(meta.length),
Some((&hash_alg, &expected_hash.0)),
&mut temp_file)?;
-
// TODO ? check downloaded version matches what was in the timestamp.json
match self.snapshot {
@@ -591,9 +590,10 @@
}
};
- let signed = json::from_slice(&buf)?;
- let safe_bytes = Self::verify_meta::<R>(signed, role, threshold, trusted_ids, available_keys)?;
- let meta: M = json::from_slice(&safe_bytes)?;
+ let signed: SignedMetadata<R> = json::from_slice(&buf)?;
+ // TODO clone
+ Self::verify_meta::<R>(signed.clone(), role, threshold, trusted_ids, available_keys)?;
+ let meta: M = json::from_value(signed.signed)?;
if !allow_expired && meta.expires() <= &UTC::now() {
return Err(Error::ExpiredMetadata(role.clone()));
@@ -721,7 +721,8 @@
if let Some(&mut json::Value::Object(ref mut root)) = roles.get_mut("root") {
if let Some(&mut json::Value::Array(ref mut key_ids)) = root.get_mut("keyids") {
key_ids.clear();
- key_ids.extend(root_keys.iter().map(|k| json!(k.value.key_id().0)));
+ key_ids.extend(root_keys.iter()
+ .map(|k| json::Value::String(k.value.key_id().0)));
}
}
}
@@ -735,9 +736,9 @@
threshold: i32,
trusted_ids: &[KeyId],
available_keys: &HashMap<KeyId, Key>)
- -> Result<Vec<u8>, Error> {
+ -> Result<(), Error> {
let bytes =
- cjson::canonicalize(signed.signed).map_err(|err| Error::CanonicalJsonError(err))?;
+ cjson::canonicalize(&signed.signed).map_err(|err| Error::CanonicalJsonError(err))?;
let unique_count = signed.signatures
.iter()
@@ -755,7 +756,7 @@
if let Some(key) = k {
m.insert(id, key);
} else {
- debug!("Unknown key ID: {:?}", id);
+ debug!("unknown key id: {:?}", id);
}
m
});
@@ -779,7 +780,7 @@
Err(e) => warn!("Failed to verify with key ID {:?}: {:?}", &sig.key_id, e),
}
if valid_sigs == threshold {
- return Ok(bytes);
+ return Ok(());
}
}
}
diff --git a/tests/cjson/root.cjson b/tests/cjson/root.cjson
new file mode 100644
index 0000000..e132582
--- /dev/null
+++ b/tests/cjson/root.cjson
@@ -0,0 +1,33 @@
+{"signatures":[{"keyid":"ed3c845525d34937bf0584989a76fa300c0a6072de714b11b8dca535e10b9088","method":"rsassa-pss-sha256","sig":"49bdc0586c83afad062c608a70a5cfd6dd32bf47eb7830960322723615f723fbf43c7d80dab455b08114624d89de740b2547bece8fda01a8acaecf651fcbe98f291971de695fb2f00889bd1ec076e9d431c4c004791dc5e223f023301eca865c0ede005952dfa4fc7b1a591f8875445872d6162039d5e59418998e0c99a15ad28689b82b72099926354ca253cffafe7a3f7ececb9ecddda35cecb05ea9ca184c7ea942ea8a1a49b7445803dd3a14d74697d220f59cef5d56bc9b640d8aca2328f0d6ea4aa25ba79164ecdf31904a176b6100188970d37860c2612cf4571613fdbaa29080be50cae2966dd46a677acaf1dd500d7a86d025877aab0fdaa72f1856"}],"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2038-01-19T03:14:06Z","keys":{"0bde74244f238cd6d4e3839d35a5b248d6cde5759b760ebdbee45a386109a41a":{"keytype":"rsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8QLzN27B3+DKhWR8nJZO
+ScZ6XS2mubhqcr3giT+WJejjVH4zIlaeC5tosaY96VKI5vRd4aFgQwq2XEx+YTJM
+dAaUGOUSvPYa+iv+O0ilPeYyXazoCaxEDj5c5EQCWJh5EJO+6fdnF7oqFrfGk2kx
+54kY8wBfYEr2psXLTuidKLmxu1gZLymRcH7oDHggFahii0HqXGUSrcTzsuqLI83/
+ulQ0VmJhkY9zooTgh+NhUvKKVGNy7O2rNQLzPXFyeun9bOeKhKiCnUIt0kxtFJPg
+l6u8X46qg4uKqVbDbv2oUV5jTfVDlGwJVAWvC8JPC8N1P0BRmGGqA3NGxpmMRBa6
+fQIDAQAB
+-----END PUBLIC KEY-----"}},"a4304752b0a81316df3d0d1c331e882fe284f1239fe394f31211a19da02e16bd":{"keytype":"rsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoeX9pEiyvBLIl46oLfUJ
+KphECc0sojOmVEtp9rAP/aChHdYHl3usyOexNyQNX7vucmk9rnK/aOFJwo5Mtf5r
+Yl4sIMK5jqI2mvjGjuminjBDGoLCRiK3mFxAGyr4t0AtxtpW8AR4tvgUfl3DS+aB
+XRYBhsx6zndQsPfrggakyv9xeA4uORDQ2ivrq+4BkDCXo65ZA61qFSsfPaShfij0
+klGPLVbyTMc+offlfiiL6ogd6MI49wNb+IDslAPfwrPZ4M3SLWVMmb6+JclFzwp0
+p+CURLWXCptDC9muFSMxZTwaBWBiresHEN36zBoU27ziZCNHpaIgGhjRaJVjzjBo
+MQIDAQAB
+-----END PUBLIC KEY-----"}},"dbfa99f76c6a89191a6747a5e6ed63da202ce77a1f32ae85e152f325ca9a4117":{"keytype":"rsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0weyzfZ42HVeafaN3WN
+Tz9PNsy/p2AqgPWYLD/lZRO0VwTRfQ/IM30NQ2DdK1Nu/uEkKTFs+Opx1sL+ezCJ
+M2BtqxZKzWlpdGR5Fu2VhXC+pd4FzSBPD3H70Ln85fsww2dnJOVZyxL++vgRKj/I
+QM26ind9pkDhdowSj9bu45Sn8M1BJ7WWoxj4JMficgeSQ4DhrP6Xx326hLLOp0uf
+LxJbNNCeBqlmpZR+wi5MO21L+1JLEhFxCpmrhii4IILi6zooi5Je8RLW544IkzQE
+5sCX/q9PdnEXc7yOKksja9J6tHcUR6cLWdbGAH4MpUGaT91eDKMh9nNNtwoDpDNV
+qwIDAQAB
+-----END PUBLIC KEY-----"}},"ed3c845525d34937bf0584989a76fa300c0a6072de714b11b8dca535e10b9088":{"keytype":"rsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAndqFFxVnq7FjihMB7sBj
+fMMIS2112HB0PauY7wDQ8gTnfbWkCOOQ2kvh0f8Ph41rQ1iZiPaZq+eQ+yqhPCsP
+JKSeq9H8oOCon4JI5afSTUTkC0NmA1SN3cMMJvUgNr+uBWOc+IHr90o6xBTuzplm
+nzzjGCGNaOMLt7P3yuF8q9UXb1DBOBvqYutGB5UB+9Ma0K4c5lMnUy0IVb6j1tEC
+FKSi4RTepA5UAOV5LE2T6Gs89kRjhTxvVsKAAj92z3VGX5pJR9zm6ZrAbjSen9Lf
+vFWgd2Hz1Qy4d9Jm0iSaJb5VG06/O4JMT1FXfIv2OP5NlzKoyDxp5ZEVLHWNksHH
+awIDAQAB
+-----END PUBLIC KEY-----"}}},"roles":{"root":{"keyids":["ed3c845525d34937bf0584989a76fa300c0a6072de714b11b8dca535e10b9088"],"threshold":1},"snapshot":{"keyids":["dbfa99f76c6a89191a6747a5e6ed63da202ce77a1f32ae85e152f325ca9a4117"],"threshold":1},"targets":{"keyids":["0bde74244f238cd6d4e3839d35a5b248d6cde5759b760ebdbee45a386109a41a"],"threshold":1},"timestamp":{"keyids":["a4304752b0a81316df3d0d1c331e882fe284f1239fe394f31211a19da02e16bd"],"threshold":1}},"version":1}}
\ No newline at end of file
diff --git a/tests/cjson/root.json b/tests/cjson/root.json
index 8417118..4aee181 100644
--- a/tests/cjson/root.json
+++ b/tests/cjson/root.json
@@ -1 +1,67 @@
-{"signatures":[{"keyid":"edbef910373211b9358743b82fdc93df5925ee374a032335605cc0ad88198be5","method":"ed25519","sig":"ebeb73585c1908d24d4a6fe8a71e47b6847ef8224d7d6256eb47e2a0961fb5f41277cdfbd8647048091f18672a130eb0630843359dcc2896741d9314bd1a3e02"}],"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2038-01-19T03:14:06Z","keys":{"86cde9bc080169837e7c25e136a197f9b572828c6d7358165a3316023066f955":{"keytype":"ed25519","keyval":{"public":"d28ff85e56a01a7fc545cccba7733f6bb97d736ebb80993f8198b3290edd4ba7"}},"bc7ba0c97704e55a3b7e24112f92bb4b56d27bea8beb669e50ffdbe17b6ae375":{"keytype":"ed25519","keyval":{"public":"200de6ac8ddcab44b9ed40ac71904d1c4c873cc1b20e183b6edaea6504657297"}},"c9952c718dd7942231929bd7a1f0559a27ad70c1a6e99eb7cefacb17fcce36d3":{"keytype":"ed25519","keyval":{"public":"04aee67fc4119ac01b8645400ff5ca7af4953eae27accf0cecfe9e22ff098d4d"}},"edbef910373211b9358743b82fdc93df5925ee374a032335605cc0ad88198be5":{"keytype":"ed25519","keyval":{"public":"2ca92b0dc29b78f64d28bcc2b1081025ea843a2ee88c3fe840fb9db85604ca98"}}},"roles":{"root":{"keyids":["edbef910373211b9358743b82fdc93df5925ee374a032335605cc0ad88198be5"],"threshold":1},"snapshot":{"keyids":["86cde9bc080169837e7c25e136a197f9b572828c6d7358165a3316023066f955"],"threshold":1},"targets":{"keyids":["c9952c718dd7942231929bd7a1f0559a27ad70c1a6e99eb7cefacb17fcce36d3"],"threshold":1},"timestamp":{"keyids":["bc7ba0c97704e55a3b7e24112f92bb4b56d27bea8beb669e50ffdbe17b6ae375"],"threshold":1}},"version":1}}
\ No newline at end of file
+{
+ "signatures": [
+ {
+ "keyid": "ed3c845525d34937bf0584989a76fa300c0a6072de714b11b8dca535e10b9088",
+ "method": "rsassa-pss-sha256",
+ "sig": "49bdc0586c83afad062c608a70a5cfd6dd32bf47eb7830960322723615f723fbf43c7d80dab455b08114624d89de740b2547bece8fda01a8acaecf651fcbe98f291971de695fb2f00889bd1ec076e9d431c4c004791dc5e223f023301eca865c0ede005952dfa4fc7b1a591f8875445872d6162039d5e59418998e0c99a15ad28689b82b72099926354ca253cffafe7a3f7ececb9ecddda35cecb05ea9ca184c7ea942ea8a1a49b7445803dd3a14d74697d220f59cef5d56bc9b640d8aca2328f0d6ea4aa25ba79164ecdf31904a176b6100188970d37860c2612cf4571613fdbaa29080be50cae2966dd46a677acaf1dd500d7a86d025877aab0fdaa72f1856"
+ }
+ ],
+ "signed": {
+ "_type": "Root",
+ "consistent_snapshot": false,
+ "expires": "2038-01-19T03:14:06Z",
+ "keys": {
+ "0bde74244f238cd6d4e3839d35a5b248d6cde5759b760ebdbee45a386109a41a": {
+ "keytype": "rsa",
+ "keyval": {
+ "public": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8QLzN27B3+DKhWR8nJZO\nScZ6XS2mubhqcr3giT+WJejjVH4zIlaeC5tosaY96VKI5vRd4aFgQwq2XEx+YTJM\ndAaUGOUSvPYa+iv+O0ilPeYyXazoCaxEDj5c5EQCWJh5EJO+6fdnF7oqFrfGk2kx\n54kY8wBfYEr2psXLTuidKLmxu1gZLymRcH7oDHggFahii0HqXGUSrcTzsuqLI83/\nulQ0VmJhkY9zooTgh+NhUvKKVGNy7O2rNQLzPXFyeun9bOeKhKiCnUIt0kxtFJPg\nl6u8X46qg4uKqVbDbv2oUV5jTfVDlGwJVAWvC8JPC8N1P0BRmGGqA3NGxpmMRBa6\nfQIDAQAB\n-----END PUBLIC KEY-----"
+ }
+ },
+ "a4304752b0a81316df3d0d1c331e882fe284f1239fe394f31211a19da02e16bd": {
+ "keytype": "rsa",
+ "keyval": {
+ "public": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoeX9pEiyvBLIl46oLfUJ\nKphECc0sojOmVEtp9rAP/aChHdYHl3usyOexNyQNX7vucmk9rnK/aOFJwo5Mtf5r\nYl4sIMK5jqI2mvjGjuminjBDGoLCRiK3mFxAGyr4t0AtxtpW8AR4tvgUfl3DS+aB\nXRYBhsx6zndQsPfrggakyv9xeA4uORDQ2ivrq+4BkDCXo65ZA61qFSsfPaShfij0\nklGPLVbyTMc+offlfiiL6ogd6MI49wNb+IDslAPfwrPZ4M3SLWVMmb6+JclFzwp0\np+CURLWXCptDC9muFSMxZTwaBWBiresHEN36zBoU27ziZCNHpaIgGhjRaJVjzjBo\nMQIDAQAB\n-----END PUBLIC KEY-----"
+ }
+ },
+ "dbfa99f76c6a89191a6747a5e6ed63da202ce77a1f32ae85e152f325ca9a4117": {
+ "keytype": "rsa",
+ "keyval": {
+ "public": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0weyzfZ42HVeafaN3WN\nTz9PNsy/p2AqgPWYLD/lZRO0VwTRfQ/IM30NQ2DdK1Nu/uEkKTFs+Opx1sL+ezCJ\nM2BtqxZKzWlpdGR5Fu2VhXC+pd4FzSBPD3H70Ln85fsww2dnJOVZyxL++vgRKj/I\nQM26ind9pkDhdowSj9bu45Sn8M1BJ7WWoxj4JMficgeSQ4DhrP6Xx326hLLOp0uf\nLxJbNNCeBqlmpZR+wi5MO21L+1JLEhFxCpmrhii4IILi6zooi5Je8RLW544IkzQE\n5sCX/q9PdnEXc7yOKksja9J6tHcUR6cLWdbGAH4MpUGaT91eDKMh9nNNtwoDpDNV\nqwIDAQAB\n-----END PUBLIC KEY-----"
+ }
+ },
+ "ed3c845525d34937bf0584989a76fa300c0a6072de714b11b8dca535e10b9088": {
+ "keytype": "rsa",
+ "keyval": {
+ "public": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAndqFFxVnq7FjihMB7sBj\nfMMIS2112HB0PauY7wDQ8gTnfbWkCOOQ2kvh0f8Ph41rQ1iZiPaZq+eQ+yqhPCsP\nJKSeq9H8oOCon4JI5afSTUTkC0NmA1SN3cMMJvUgNr+uBWOc+IHr90o6xBTuzplm\nnzzjGCGNaOMLt7P3yuF8q9UXb1DBOBvqYutGB5UB+9Ma0K4c5lMnUy0IVb6j1tEC\nFKSi4RTepA5UAOV5LE2T6Gs89kRjhTxvVsKAAj92z3VGX5pJR9zm6ZrAbjSen9Lf\nvFWgd2Hz1Qy4d9Jm0iSaJb5VG06/O4JMT1FXfIv2OP5NlzKoyDxp5ZEVLHWNksHH\nawIDAQAB\n-----END PUBLIC KEY-----"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "ed3c845525d34937bf0584989a76fa300c0a6072de714b11b8dca535e10b9088"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "dbfa99f76c6a89191a6747a5e6ed63da202ce77a1f32ae85e152f325ca9a4117"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "0bde74244f238cd6d4e3839d35a5b248d6cde5759b760ebdbee45a386109a41a"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "a4304752b0a81316df3d0d1c331e882fe284f1239fe394f31211a19da02e16bd"
+ ],
+ "threshold": 1
+ }
+ },
+ "version": 1
+ }
+}
diff --git a/tests/rsa/pkcs1-1.pub b/tests/rsa/pkcs1-1.pub
new file mode 100644
index 0000000..7ca5280
--- /dev/null
+++ b/tests/rsa/pkcs1-1.pub
@@ -0,0 +1,8 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAndqFFxVnq7FjihMB7sBjfMMIS2112HB0PauY7wDQ8gTnfbWkCOOQ
+2kvh0f8Ph41rQ1iZiPaZq+eQ+yqhPCsPJKSeq9H8oOCon4JI5afSTUTkC0NmA1SN
+3cMMJvUgNr+uBWOc+IHr90o6xBTuzplmnzzjGCGNaOMLt7P3yuF8q9UXb1DBOBvq
+YutGB5UB+9Ma0K4c5lMnUy0IVb6j1tECFKSi4RTepA5UAOV5LE2T6Gs89kRjhTxv
+VsKAAj92z3VGX5pJR9zm6ZrAbjSen9LfvFWgd2Hz1Qy4d9Jm0iSaJb5VG06/O4JM
+T1FXfIv2OP5NlzKoyDxp5ZEVLHWNksHHawIDAQAB
+-----END RSA PUBLIC KEY-----
diff --git a/tests/rsa/pkcs1-2.pub b/tests/rsa/pkcs1-2.pub
new file mode 100644
index 0000000..47701da
--- /dev/null
+++ b/tests/rsa/pkcs1-2.pub
@@ -0,0 +1,13 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIICCgKCAgEA6si2eKLhg5EwZajX8b4PpLmPnnhJElIEadkb7nJnW4kCk5QO5TwH
+tfRS7snPULxH6gDiTMKBMRMNOw9wXOwNZ/DTLeaKs7ScJS24tLnjwI8dEj5h0N8n
+eBvkFrStEFoNxy0unRJHWnnc+j3TdCWf5k1VbZWS5fREECtq767fDBs/l55iI176
+6x+vSPbdt0mXsTiFLf4eT1ISskvToG7/K3TkTyTUYLVdCfOYnWgsbgjiemjwzoyW
+CgxHQGSh6CaGWccvczp1WNL757VP/HzpsmVE/mr0T6V3k/lGAp0mEbjyJKcnk2E5
+LaLjSqfk+b4yJgmQXP68HOLRWL57bUgZnx85GpHpQfE0MX0OmuwQsbpQxxiErSax
+3Lrh3sWsa0dUIggb0/UWKir6omoMwjYcqR4Sfp2vI0yjusLah3nF6bV6b+zgA/Zy
++tffsW7ntxjOY/d8hQRJtKmEJ54XJSCEOay6E1NfCj/G9UGNuKt/rnhWghkxoOuG
+lDAo5O4UCjwyDavinAb/3E2Q6tSZJR0g6z7mNBDZewmViHB+5b9demT/xaloyxe0
+QrevtTlh/NbytEki0NQgXE/6sXHd/R0Y3wqDtggWZbeWTqSD+HiyglPzgLULpXMP
+NhO05bhlbHnP4srkacjBFSfQYcl7odkQZeqFbCk+k0/cYYQ+6bd2IpUCAwEAAQ==
+-----END RSA PUBLIC KEY-----
diff --git a/tests/rsa/spki-1.pub b/tests/rsa/spki-1.pub
new file mode 100644
index 0000000..44b0ef9
--- /dev/null
+++ b/tests/rsa/spki-1.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAndqFFxVnq7FjihMB7sBj
+fMMIS2112HB0PauY7wDQ8gTnfbWkCOOQ2kvh0f8Ph41rQ1iZiPaZq+eQ+yqhPCsP
+JKSeq9H8oOCon4JI5afSTUTkC0NmA1SN3cMMJvUgNr+uBWOc+IHr90o6xBTuzplm
+nzzjGCGNaOMLt7P3yuF8q9UXb1DBOBvqYutGB5UB+9Ma0K4c5lMnUy0IVb6j1tEC
+FKSi4RTepA5UAOV5LE2T6Gs89kRjhTxvVsKAAj92z3VGX5pJR9zm6ZrAbjSen9Lf
+vFWgd2Hz1Qy4d9Jm0iSaJb5VG06/O4JMT1FXfIv2OP5NlzKoyDxp5ZEVLHWNksHH
+awIDAQAB
+-----END PUBLIC KEY-----
diff --git a/tests/rsa/spki-2.pub b/tests/rsa/spki-2.pub
new file mode 100644
index 0000000..e4d767e
--- /dev/null
+++ b/tests/rsa/spki-2.pub
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6si2eKLhg5EwZajX8b4P
+pLmPnnhJElIEadkb7nJnW4kCk5QO5TwHtfRS7snPULxH6gDiTMKBMRMNOw9wXOwN
+Z/DTLeaKs7ScJS24tLnjwI8dEj5h0N8neBvkFrStEFoNxy0unRJHWnnc+j3TdCWf
+5k1VbZWS5fREECtq767fDBs/l55iI1766x+vSPbdt0mXsTiFLf4eT1ISskvToG7/
+K3TkTyTUYLVdCfOYnWgsbgjiemjwzoyWCgxHQGSh6CaGWccvczp1WNL757VP/Hzp
+smVE/mr0T6V3k/lGAp0mEbjyJKcnk2E5LaLjSqfk+b4yJgmQXP68HOLRWL57bUgZ
+nx85GpHpQfE0MX0OmuwQsbpQxxiErSax3Lrh3sWsa0dUIggb0/UWKir6omoMwjYc
+qR4Sfp2vI0yjusLah3nF6bV6b+zgA/Zy+tffsW7ntxjOY/d8hQRJtKmEJ54XJSCE
+Oay6E1NfCj/G9UGNuKt/rnhWghkxoOuGlDAo5O4UCjwyDavinAb/3E2Q6tSZJR0g
+6z7mNBDZewmViHB+5b9demT/xaloyxe0QrevtTlh/NbytEki0NQgXE/6sXHd/R0Y
+3wqDtggWZbeWTqSD+HiyglPzgLULpXMPNhO05bhlbHnP4srkacjBFSfQYcl7odkQ
+ZeqFbCk+k0/cYYQ+6bd2IpUCAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/tests/vectors.rs b/tests/vectors.rs
index 5acc59e..95a5215 100644
--- a/tests/vectors.rs
+++ b/tests/vectors.rs
@@ -1,4 +1,5 @@
extern crate data_encoding;
+extern crate pem;
extern crate serde;
#[macro_use]
extern crate serde_derive;
@@ -11,6 +12,7 @@
use std::fs::{self, File, DirEntry};
use std::io::{self, Read};
use std::path::{PathBuf, Path};
+use std::str;
use tempdir::TempDir;
use tuf::{Tuf, Config, Error, RemoteRepo};
use tuf::meta::{Key, KeyValue, KeyType};
@@ -68,7 +70,7 @@
}
}
-fn run_test_vector(test_path: &str, test_type: TestType) {
+fn run_test_vector(test_path: &str, test_type: TestType, pin_root_keys: bool) {
let temp_dir = TempDir::new("rust-tuf").expect("couldn't make temp dir");
let temp_path = temp_dir.into_path();
@@ -93,28 +95,6 @@
println!("The test vector path is: {}",
vector_path.to_string_lossy().into_owned());
- let root_keys = test_vector.root_keys
- .iter()
- .map(|k| {
- let file_path = vector_path.join("keys").join(k.path.clone());
- let mut file = File::open(file_path).expect("couldn't open file");
- let mut key = String::new();
- file.read_to_string(&mut key).expect("couldn't read key");
-
- match k.typ.as_ref() {
- "ed25519" => {
- let val = HEXLOWER.decode(key.replace("\n", "").as_ref())
- .expect("key value not hex");
- Key {
- typ: KeyType::Ed25519,
- value: KeyValue(val),
- }
- }
- x => panic!("unknown key type: {}", x),
- }
- })
- .collect();
-
let config = match test_type {
TestType::File => Config::build()
.remote(RemoteRepo::File(vector_path.join("repo"))),
@@ -125,7 +105,63 @@
.finish()
.expect("bad config");
- match (Tuf::from_root_keys(root_keys, config), &test_vector.error) {
+ let tuf = if pin_root_keys {
+ let root_keys = test_vector.root_keys
+ .iter()
+ .map(|k| {
+ let file_path = vector_path.join("keys").join(k.path.clone());
+ let mut file = File::open(file_path)
+ .expect("couldn't open file");
+ let mut buf = Vec::new();
+ file.read_to_end(&mut buf).expect("couldn't read key");
+
+ let len = buf.len();
+ if buf[len - 1] == b'\n' {
+ buf.truncate(len - 1)
+ }
+
+ let key = str::from_utf8(&buf).expect("not utf-8").to_string();
+
+ match k.typ.as_ref() {
+ "ed25519" => {
+ let val = HEXLOWER.decode(key.replace("\n", "").as_ref())
+ .expect("key value not hex");
+ Key {
+ typ: KeyType::Ed25519,
+ value: KeyValue {
+ typ: KeyType::Ed25519,
+ value: val,
+ original: key,
+ },
+ }
+ }
+ "rsa" => {
+ let val = pem::parse(key.clone())
+ .expect("key value not pem");
+ Key {
+ typ: KeyType::Rsa,
+ value: KeyValue {
+ typ: KeyType::Rsa,
+ value: val.contents,
+ original: key,
+ },
+ }
+ }
+ x => panic!("unknown key type: {}", x),
+ }
+ })
+ .collect();
+ Tuf::from_root_keys(root_keys, config)
+ } else {
+ Tuf::initialize(&temp_path)
+ .expect("failed to initialize");
+ fs::copy(vector_path.join("repo").join("1.root.json"),
+ temp_path.join("metadata").join("current").join("root.json"))
+ .expect("failed to copy root.json");
+ Tuf::new(config)
+ };
+
+ match (tuf, &test_vector.error) {
(Ok(ref tuf), &None) => {
// first time pulls remote
assert_eq!(tuf.fetch_target("targets/file.txt").map(|_| ()), Ok(()));
@@ -151,6 +187,11 @@
format!("Role: {}, err: {}", role, err))
}
+ (Err(Error::UnmetThreshold(_)), &Some(ref err))
+ if err == &"IllegalRsaKeySize".to_string() => {
+ ()
+ }
+
(Err(Error::UnmetThreshold(ref role)), &Some(ref err))
if err.starts_with("UnmetThreshold::") => {
assert!(err.to_lowercase()
@@ -215,15 +256,27 @@
use $crate::{run_test_vector, TestType};
#[test]
- fn file_test() {
- run_test_vector($name, TestType::File)
+ fn file_pinned() {
+ run_test_vector($name, TestType::File, true)
+ }
+
+ #[test]
+ fn file_unpinned() {
+ run_test_vector($name, TestType::File, false)
}
// TODO no idea how windows shell scipting works
#[cfg(not(windows))]
#[test]
- fn http_test() {
- run_test_vector($name, TestType::Http)
+ fn http_pinned() {
+ run_test_vector($name, TestType::Http, true)
+ }
+
+ // TODO no idea how windows shell scipting works
+ #[cfg(not(windows))]
+ #[test]
+ fn http_unpinned() {
+ run_test_vector($name, TestType::Http, false)
}
}
}
@@ -231,10 +284,10 @@
test_cases!("001", _001);
test_cases!("002", _002);
-// test_cases!("003", _003);
-// test_cases!("004", _004);
+test_cases!("003", _003);
+test_cases!("004", _004);
test_cases!("005", _005);
-// test_cases!("006", _006);
+test_cases!("006", _006);
test_cases!("007", _007);
test_cases!("008", _008);
test_cases!("009", _009);
@@ -255,24 +308,24 @@
test_cases!("024", _024);
test_cases!("025", _025);
test_cases!("026", _026);
-// test_cases!("027", _027);
-// test_cases!("028", _028);
+test_cases!("027", _027);
+test_cases!("028", _028);
test_cases!("029", _029);
test_cases!("030", _030);
test_cases!("031", _031);
test_cases!("032", _032);
test_cases!("033", _033);
test_cases!("034", _034);
-// test_cases!("035", _035);
-// test_cases!("036", _036);
+test_cases!("035", _035);
+test_cases!("036", _036);
test_cases!("037", _037);
test_cases!("038", _038);
test_cases!("039", _039);
test_cases!("040", _040);
-// test_cases!("041", _041);
-// test_cases!("042", _042);
-// test_cases!("043", _043);
-// test_cases!("044", _044);
+test_cases!("041", _041);
+test_cases!("042", _042);
+test_cases!("043", _043);
+test_cases!("044", _044);
test_cases!("045", _045);
test_cases!("046", _046);
test_cases!("047", _047);