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
+103
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());
}
}
}
+232
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
+217
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
}
}
}
}
}
}