Initial commit

This commit is contained in:
Jean-Michel Picod
2020-01-28 15:09:10 +01:00
commit f91d2fd3db
90 changed files with 31123 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Test vectors for AES-ECB from NIST's validation suite.
///
/// See also https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/aes/AESAVS.pdf
#[macro_use]
extern crate arrayref;
extern crate hex;
extern crate regex;
use crypto::{aes256, Decrypt16BytesBlock, Encrypt16BytesBlock};
use regex::Regex;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
#[test]
fn aesavs() {
// These data files are taken from https://csrc.nist.gov/groups/STM/cavp/documents/aes/KAT_AES.zip.
test_aesavs_file("tests/data/ECBVarKey256.rsp");
test_aesavs_file("tests/data/ECBVarTxt256.rsp");
}
fn test_aesavs_file<P: AsRef<Path>>(path: P) {
// Implements some custom parsing for NIST's test vectors.
let re_count = Regex::new("^COUNT = ([0-9]+)$").unwrap();
let re_key = Regex::new("^KEY = ([0-9a-f]{64})$").unwrap();
let re_plaintext = Regex::new("^PLAINTEXT = ([0-9a-f]{32})$").unwrap();
let re_ciphertext = Regex::new("^CIPHERTEXT = ([0-9a-f]{32})$").unwrap();
let file = BufReader::new(File::open(path).unwrap());
let mut lines = file.lines();
loop {
let line = lines.next().unwrap().unwrap();
if line == "[ENCRYPT]" {
break;
}
}
for i in 0.. {
// empty line
let line = lines.next().unwrap().unwrap();
assert_eq!(line, "");
let line = lines.next().unwrap().unwrap();
if line == "[DECRYPT]" {
// Skip the decryption tests, they are the same as the encryption tests.
break;
}
// "COUNT = "
let captures = re_count.captures(&line).unwrap();
let count = captures.get(1).unwrap().as_str().parse::<usize>().unwrap();
assert_eq!(count, i);
// "KEY = "
let line = lines.next().unwrap().unwrap();
let captures = re_key.captures(&line).unwrap();
let key = hex::decode(captures.get(1).unwrap().as_str()).unwrap();
assert_eq!(key.len(), 32);
// "PLAINTEXT = "
let line = lines.next().unwrap().unwrap();
let captures = re_plaintext.captures(&line).unwrap();
let plaintext = hex::decode(captures.get(1).unwrap().as_str()).unwrap();
assert_eq!(plaintext.len(), 16);
// "CIPHERTEXT = "
let line = lines.next().unwrap().unwrap();
let captures = re_ciphertext.captures(&line).unwrap();
let ciphertext = hex::decode(captures.get(1).unwrap().as_str()).unwrap();
assert_eq!(ciphertext.len(), 16);
{
let encryption_key = aes256::EncryptionKey::new(array_ref![key, 0, 32]);
let mut block: [u8; 16] = [Default::default(); 16];
block.copy_from_slice(&plaintext);
encryption_key.encrypt_block(&mut block);
assert_eq!(&block, ciphertext.as_slice());
}
{
let encryption_key = aes256::EncryptionKey::new(array_ref![key, 0, 32]);
let decryption_key = aes256::DecryptionKey::new(&encryption_key);
let mut block: [u8; 16] = [Default::default(); 16];
block.copy_from_slice(&ciphertext);
decryption_key.decrypt_block(&mut block);
assert_eq!(&block, plaintext.as_slice());
}
}
}

View File

@@ -0,0 +1,232 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// A minimalist parser for ASN.1 encoded ECDSA signatures in DER form.
use arrayref::mut_array_refs;
use crypto::ecdsa;
use std::convert::TryFrom;
use std::io::Read;
#[derive(Debug)]
pub enum Asn1Error {
IoError(std::io::Error),
InvalidTagClass,
InvalidLongFormEncoding,
ArithmeticOverflow,
ExpectedSequenceTag(Tag),
ExpectedIntegerTag(Tag),
InvalidSequenceLen(usize),
InvalidIntegerLen(usize),
UnexpectedTrailingBytes,
InvalidSignature,
}
impl From<std::io::Error> for Asn1Error {
fn from(e: std::io::Error) -> Asn1Error {
Asn1Error::IoError(e)
}
}
#[derive(PartialEq, Debug)]
pub enum TagClass {
Universal = 0,
Application = 1,
ContextSpecific = 2,
Private = 3,
}
impl TryFrom<u8> for TagClass {
type Error = Asn1Error;
fn try_from(x: u8) -> Result<TagClass, Asn1Error> {
match x {
0 => Ok(TagClass::Universal),
1 => Ok(TagClass::Application),
2 => Ok(TagClass::ContextSpecific),
3 => Ok(TagClass::Private),
_ => Err(Asn1Error::InvalidTagClass),
}
}
}
#[allow(dead_code)]
enum UniversalTag {
Boolean = 1,
Integer = 2,
BitString = 3,
OctetString = 4,
Null = 5,
ObjectIdentifier = 6,
Utf8String = 12,
Sequence = 16,
Set = 17,
PrintableString = 19,
UtcTime = 23,
GeneralizedTime = 24,
}
#[derive(Debug)]
pub struct Tag {
class: TagClass,
constructed: bool,
number: u64,
}
impl Tag {
fn is_sequence(&self) -> bool {
self.class == TagClass::Universal
&& self.constructed
&& self.number == UniversalTag::Sequence as u64
}
fn is_number(&self) -> bool {
self.class == TagClass::Universal
&& !self.constructed
&& self.number == UniversalTag::Integer as u64
}
// Parse an ASN.1 tag encoded in DER form.
fn parse<R: Read>(input: &mut R) -> Result<Tag, Asn1Error> {
let mut buf = [0u8; 1];
input.read_exact(&mut buf)?;
let mut tag = buf[0];
let class = TagClass::try_from(tag >> 6)?;
let constructed = tag & 0x20 != 0;
tag &= 0x1F;
if tag < 31 {
// Short tag number
let number = tag as u64;
Ok(Tag {
class,
constructed,
number,
})
} else {
// Long tag number
let mut number: u64 = 0;
loop {
input.read_exact(&mut buf)?;
let x = buf[0];
if number == 0 && x == 0 {
return Err(Asn1Error::InvalidLongFormEncoding);
}
if number >> ((8 * std::mem::size_of::<u64>()) - 7) != 0 {
return Err(Asn1Error::ArithmeticOverflow);
}
number = (number << 7) | (x & 0x7F) as u64;
if (x & 0x80) == 0 {
if number < 31 {
return Err(Asn1Error::InvalidLongFormEncoding);
}
return Ok(Tag {
class,
constructed,
number,
});
}
}
}
}
}
// Parse an ASN.1 length encoded in DER form.
fn parse_len<R: Read>(input: &mut R) -> Result<usize, Asn1Error> {
let mut buf = [0u8; 1];
input.read_exact(&mut buf)?;
let first_byte = buf[0];
if (first_byte & 0x80) == 0 {
// Short form
Ok(first_byte as usize)
} else {
// Long form
let nbytes = (first_byte & 0x7F) as usize;
let mut length: usize = 0;
for _ in 0..nbytes {
input.read_exact(&mut buf)?;
let x = buf[0];
if length == 0 && x == 0 {
return Err(Asn1Error::InvalidLongFormEncoding);
}
if length >> (8 * (std::mem::size_of::<usize>() - 1)) != 0 {
return Err(Asn1Error::ArithmeticOverflow);
}
length = (length << 8) | x as usize;
}
if length < 0x80 {
return Err(Asn1Error::InvalidLongFormEncoding);
}
Ok(length)
}
}
fn parse_coordinate<R: Read>(mut input: R, bytes: &mut [u8; 32]) -> Result<usize, Asn1Error> {
let tag = Tag::parse(&mut input)?;
if !tag.is_number() {
return Err(Asn1Error::ExpectedIntegerTag(tag));
}
let len = parse_len(&mut input)?;
if len > 33 {
return Err(Asn1Error::InvalidIntegerLen(len));
}
let mut buf = vec![0; len];
input.read_exact(&mut buf)?;
if len == 33 {
if buf.remove(0) != 0 {
return Err(Asn1Error::InvalidIntegerLen(len));
}
}
bytes[(32 - buf.len())..].copy_from_slice(&buf);
Ok(len)
}
pub fn parse_signature<R: Read>(mut input: R) -> Result<ecdsa::Signature, Asn1Error> {
let tag = Tag::parse(&mut input)?;
if !tag.is_sequence() {
return Err(Asn1Error::ExpectedSequenceTag(tag));
}
let len = parse_len(&mut input)?;
let mut bytes = [0; 64];
let (xbytes, ybytes) = mut_array_refs![&mut bytes, 32, 32];
let xlen = parse_coordinate(&mut input, xbytes)?;
let ylen = parse_coordinate(&mut input, ybytes)?;
// Each coordinate has, besides (x|y)len bytes of integer, one byte for the tag and one
// byte for the length (the length is at most 33 and therefore encoded on one byte).
if len != xlen + ylen + 4 {
return Err(Asn1Error::InvalidSequenceLen(len));
}
// Check for unexpected bytes at the end.
let is_eof = {
let mut buf = [0u8; 1];
match input.read_exact(&mut buf) {
Ok(_) => false,
Err(e) => e.kind() == std::io::ErrorKind::UnexpectedEof,
}
};
if !is_eof {
return Err(Asn1Error::UnexpectedTrailingBytes);
}
ecdsa::Signature::from_bytes(&bytes).ok_or(Asn1Error::InvalidSignature)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,217 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crypto::ecdsa;
use serde::Deserialize;
use std::collections::HashMap;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
mod asn1;
#[test]
fn wycheproof() {
let wycheproof = load_tests("tests/data/ecdsa_secp256r1_sha256_test.json").unwrap();
wycheproof.type_check();
assert!(wycheproof.run_tests());
}
fn load_tests<P: AsRef<Path>>(path: P) -> Result<Wycheproof, Box<dyn Error>> {
let file = File::open(path)?;
let wycheproof = serde_json::from_reader(BufReader::new(file))?;
Ok(wycheproof)
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct Wycheproof {
algorithm: String,
#[allow(dead_code)]
generatorVersion: String,
#[allow(dead_code)]
numberOfTests: u32,
#[allow(dead_code)]
header: Vec<String>,
notes: HashMap<String, String>,
schema: String,
testGroups: Vec<TestGroup>,
}
impl Wycheproof {
fn type_check(&self) {
assert_eq!(self.algorithm, "ECDSA");
assert_eq!(self.schema, "ecdsa_verify_schema.json");
for group in &self.testGroups {
group.type_check();
}
}
fn run_tests(&self) -> bool {
let mut result = true;
for group in &self.testGroups {
result &= group.run_tests(&self.notes);
}
result
}
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct TestGroup {
key: Key,
#[allow(dead_code)]
keyDer: String,
#[allow(dead_code)]
keyPem: String,
sha: String,
r#type: String,
tests: Vec<TestCase>,
}
impl TestGroup {
fn type_check(&self) {
self.key.type_check();
assert_eq!(self.sha, "SHA-256");
assert_eq!(self.r#type, "EcdsaVerify");
for test in &self.tests {
test.type_check();
}
}
fn run_tests(&self, notes: &HashMap<String, String>) -> bool {
let key = self.key.get_key();
let mut result = true;
for test in &self.tests {
result &= test.run_test(&key, notes);
}
result
}
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct Key {
curve: String,
keySize: u32,
r#type: String,
uncompressed: String,
#[allow(dead_code)]
wx: String,
#[allow(dead_code)]
wy: String,
}
impl Key {
fn type_check(&self) {
assert_eq!(self.curve, "secp256r1");
assert_eq!(self.keySize, 256);
assert_eq!(self.r#type, "EcPublicKey");
assert_eq!(self.uncompressed.len(), 130);
}
fn get_key(&self) -> Option<ecdsa::PubKey> {
let bytes = hex::decode(&self.uncompressed).unwrap();
ecdsa::PubKey::from_bytes_uncompressed(&bytes)
}
}
#[derive(Deserialize, Debug)]
#[allow(non_camel_case_types)]
enum TestResult {
valid,
invalid,
acceptable,
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct TestCase {
tcId: u32,
comment: String,
msg: String,
sig: String,
result: TestResult,
flags: Vec<String>,
}
impl TestCase {
fn type_check(&self) {
// Nothing to do.
}
fn print(&self, notes: &HashMap<String, String>, error_msg: &str) {
println!("Test case #{} => {}", self.tcId, error_msg);
println!(" {}", self.comment);
println!(" result = {:?}", self.result);
for f in &self.flags {
println!(
" flag {} = {}",
f,
notes.get(f).map_or("unknown flag", |x| &x)
);
}
}
fn run_test(&self, key: &Option<ecdsa::PubKey>, notes: &HashMap<String, String>) -> bool {
match key {
None => {
let pass = match self.result {
TestResult::invalid | TestResult::acceptable => true,
TestResult::valid => false,
};
if !pass {
self.print(notes, "Invalid public key");
}
pass
}
Some(k) => {
let msg = hex::decode(&self.msg).unwrap();
let sig = hex::decode(&self.sig).unwrap();
match asn1::parse_signature(sig.as_slice()) {
Err(e) => {
let pass = match self.result {
TestResult::invalid | TestResult::acceptable => true,
TestResult::valid => false,
};
if !pass {
self.print(notes, "Invalid ASN.1 encoding for the signature");
println!(" {:?}", e);
}
pass
}
Ok(signature) => {
let verified = k.verify_vartime::<crypto::sha256::Sha256>(&msg, &signature);
let pass = match self.result {
TestResult::acceptable => true,
TestResult::valid => verified,
TestResult::invalid => !verified,
};
if !pass {
self.print(
notes,
&format!(
"Expected {:?} result, but the signature verification was {}",
self.result, verified
),
);
}
pass
}
}
}
}
}
}