Initial commit
This commit is contained in:
103
libraries/crypto/tests/aesavs.rs
Normal file
103
libraries/crypto/tests/aesavs.rs
Normal 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
libraries/crypto/tests/asn1.rs
Normal file
232
libraries/crypto/tests/asn1.rs
Normal 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)
|
||||
}
|
||||
2571
libraries/crypto/tests/data/ECBVarKey256.rsp
Normal file
2571
libraries/crypto/tests/data/ECBVarKey256.rsp
Normal file
File diff suppressed because it is too large
Load Diff
1291
libraries/crypto/tests/data/ECBVarTxt256.rsp
Normal file
1291
libraries/crypto/tests/data/ECBVarTxt256.rsp
Normal file
File diff suppressed because it is too large
Load Diff
4578
libraries/crypto/tests/data/ecdsa_secp256r1_sha256_test.json
Normal file
4578
libraries/crypto/tests/data/ecdsa_secp256r1_sha256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
217
libraries/crypto/tests/wycheproof.rs
Normal file
217
libraries/crypto/tests/wycheproof.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user