Refactor, still broken, but moving towards not broken.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
/target/
|
/target/
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
*.pem
|
||||||
|
|||||||
8
.travis.yml
Normal file
8
.travis.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
language: rust
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
- nightly
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- rust: nightly
|
||||||
@@ -9,6 +9,7 @@ num = "^0.1.40"
|
|||||||
sha-1 = "^0.7.0"
|
sha-1 = "^0.7.0"
|
||||||
sha2 = "^0.7.0"
|
sha2 = "^0.7.0"
|
||||||
simple_asn1 = "^0.1.0"
|
simple_asn1 = "^0.1.0"
|
||||||
|
simple_dsa = { path = "../simple_dsa" }
|
||||||
simple_rsa = { path = "../simple_rsa" }
|
simple_rsa = { path = "../simple_rsa" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
354
src/algident.rs
Normal file
354
src/algident.rs
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
use error::X509ParseError;
|
||||||
|
use num::BigUint;
|
||||||
|
use num::bigint::ToBigInt;
|
||||||
|
use simple_asn1::{ASN1Block,ASN1Class,ASN1EncodeErr,FromASN1,OID,ToASN1};
|
||||||
|
|
||||||
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
|
enum HashAlgorithm { SHA1, SHA224, SHA256, SHA384, SHA512 }
|
||||||
|
|
||||||
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
|
enum PublicKeyInfo { RSA, DSA, EC }
|
||||||
|
|
||||||
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
|
pub struct AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm,
|
||||||
|
algo: PublicKeyInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromASN1 for AlgorithmIdentifier {
|
||||||
|
type Error = X509ParseError;
|
||||||
|
|
||||||
|
fn from_asn1(v: &[ASN1Block])
|
||||||
|
-> Result<(AlgorithmIdentifier,&[ASN1Block]),X509ParseError>
|
||||||
|
{
|
||||||
|
match v.split_first() {
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::NotEnoughData),
|
||||||
|
Some((x, rest)) => {
|
||||||
|
let v = decode_algorithm_ident(&x)?;
|
||||||
|
Ok((v, rest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_algorithm_ident(x: &ASN1Block)
|
||||||
|
-> Result<AlgorithmIdentifier,X509ParseError>
|
||||||
|
{
|
||||||
|
// AlgorithmIdentifier ::= SEQUENCE {
|
||||||
|
// algorithm OBJECT IDENTIFIER,
|
||||||
|
// parameters ANY DEFINED BY algorithm OPTIONAL }
|
||||||
|
match x {
|
||||||
|
&ASN1Block::Sequence(_, _, ref v) if v.len() >= 1 => {
|
||||||
|
match v[0] {
|
||||||
|
ASN1Block::ObjectIdentifier(_, _, ref oid) => {
|
||||||
|
if oid == oid!(1,2,840,113549,1,1,5) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA1,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,113549,1,1,11) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA256,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,113549,1,1,12) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA384,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,113549,1,1,13) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA512,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,113549,1,1,14) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA224,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,10040,4,3) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA1,
|
||||||
|
algo: PublicKeyInfo::DSA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,10045,4,1) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA1,
|
||||||
|
algo: PublicKeyInfo::EC
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,10045,4,3,1) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA224,
|
||||||
|
algo: PublicKeyInfo::EC
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,10045,4,3,2) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA256,
|
||||||
|
algo: PublicKeyInfo::EC
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,10045,4,3,3) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA384,
|
||||||
|
algo: PublicKeyInfo::EC
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,10045,4,3,4) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA512,
|
||||||
|
algo: PublicKeyInfo::EC
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// if oid == oid!(2,16,840,1,101,3,4,2,1) {
|
||||||
|
// return Ok(AlgorithmIdentifier {
|
||||||
|
// hash: HashAlgorithm::SHA256,
|
||||||
|
// algo: PublicKeyInfo::RSAPSS
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// if oid == oid!(2,16,840,1,101,3,4,2,2) {
|
||||||
|
// return Ok(AlgorithmIdentifier {
|
||||||
|
// hash: HashAlgorithm::SHA384,
|
||||||
|
// algo: PublicKeyInfo::RSAPSS
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// if oid == oid!(2,16,840,1,101,3,4,2,3) {
|
||||||
|
// return Ok(AlgorithmIdentifier {
|
||||||
|
// hash: HashAlgorithm::SHA512,
|
||||||
|
// algo: PublicKeyInfo::RSAPSS
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// if oid == oid!(2,16,840,1,101,3,4,2,4) {
|
||||||
|
// return Ok(AlgorithmIdentifier {
|
||||||
|
// hash: HashAlgorithm::SHA224,
|
||||||
|
// algo: PublicKeyInfo::RSAPSS
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
if oid == oid!(2,16,840,1,101,3,4,3,1) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA224,
|
||||||
|
algo: PublicKeyInfo::DSA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if oid == oid!(2,16,840,1,101,3,4,3,2) {
|
||||||
|
return Ok(AlgorithmIdentifier {
|
||||||
|
hash: HashAlgorithm::SHA256,
|
||||||
|
algo: PublicKeyInfo::DSA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(X509ParseError::UnknownAlgorithm)
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::UnknownAlgorithm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllFormedAlgoInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub enum SigAlgEncodeError {
|
||||||
|
ASN1Error(ASN1EncodeErr),
|
||||||
|
InvalidDSAValue, InvalidHash
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ASN1EncodeErr> for SigAlgEncodeError {
|
||||||
|
fn from(e: ASN1EncodeErr) -> SigAlgEncodeError {
|
||||||
|
SigAlgEncodeError::ASN1Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ToASN1 for AlgorithmIdentifier {
|
||||||
|
type Error = SigAlgEncodeError;
|
||||||
|
|
||||||
|
fn to_asn1_class(&self, c: ASN1Class)
|
||||||
|
-> Result<Vec<ASN1Block>,SigAlgEncodeError>
|
||||||
|
{
|
||||||
|
let block = encode_algorithm_ident(c, self)?;
|
||||||
|
Ok(vec![block])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_algorithm_ident(c: ASN1Class, x: &AlgorithmIdentifier)
|
||||||
|
-> Result<ASN1Block,SigAlgEncodeError>
|
||||||
|
{
|
||||||
|
match x.algo {
|
||||||
|
PublicKeyInfo::RSA => {
|
||||||
|
match x.hash {
|
||||||
|
HashAlgorithm::SHA1 => {
|
||||||
|
let o = oid!(1,2,840,113549,1,1,5);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA224 => {
|
||||||
|
let o = oid!(1,2,840,113549,1,1,14);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA256 => {
|
||||||
|
let o = oid!(1,2,840,113549,1,1,11);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA384 => {
|
||||||
|
let o = oid!(1,2,840,113549,1,1,12);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA512 => {
|
||||||
|
let o = oid!(1,2,840,113549,1,1,13);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PublicKeyInfo::DSA => {
|
||||||
|
match x.hash {
|
||||||
|
HashAlgorithm::SHA1 => {
|
||||||
|
let o = oid!(1,2,840,10040,4,3);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA224 => {
|
||||||
|
let o = oid!(2,16,840,1,101,3,4,3,1);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA256 => {
|
||||||
|
let o = oid!(2,16,840,1,101,3,4,3,2);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(SigAlgEncodeError::InvalidHash),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PublicKeyInfo::EC => {
|
||||||
|
match x.hash {
|
||||||
|
HashAlgorithm::SHA1 => {
|
||||||
|
let o = oid!(1,2,840,10045,4,1);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA224 => {
|
||||||
|
let o = oid!(1,2,840,10045,4,3,1);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA256 => {
|
||||||
|
let o = oid!(1,2,840,10045,4,3,2);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA384 => {
|
||||||
|
let o = oid!(1,2,840,10045,4,3,3);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
HashAlgorithm::SHA512 => {
|
||||||
|
let o = oid!(1,2,840,10045,4,3,4);
|
||||||
|
let obj = ASN1Block::ObjectIdentifier(c, 0, o);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![obj]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use quickcheck::{Arbitrary,Gen};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn rsa1<G: Gen>(g: &mut G) -> AlgorithmIdentifier {
|
||||||
|
AlgorithmIdentifier{
|
||||||
|
hash: HashAlgorithm::SHA1,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rsa224<G: Gen>(g: &mut G) -> AlgorithmIdentifier {
|
||||||
|
AlgorithmIdentifier{
|
||||||
|
hash: HashAlgorithm::SHA224,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rsa256<G: Gen>(g: &mut G) -> AlgorithmIdentifier {
|
||||||
|
AlgorithmIdentifier{
|
||||||
|
hash: HashAlgorithm::SHA256,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rsa384<G: Gen>(g: &mut G) -> AlgorithmIdentifier {
|
||||||
|
AlgorithmIdentifier{
|
||||||
|
hash: HashAlgorithm::SHA384,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rsa512<G: Gen>(g: &mut G) -> AlgorithmIdentifier {
|
||||||
|
AlgorithmIdentifier{
|
||||||
|
hash: HashAlgorithm::SHA512,
|
||||||
|
algo: PublicKeyInfo::RSA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dsa1<G: Gen>(g: &mut G) -> AlgorithmIdentifier {
|
||||||
|
AlgorithmIdentifier{
|
||||||
|
hash: HashAlgorithm::SHA1,
|
||||||
|
algo: PublicKeyInfo::DSA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dsa224<G: Gen>(g: &mut G) -> AlgorithmIdentifier {
|
||||||
|
AlgorithmIdentifier{
|
||||||
|
hash: HashAlgorithm::SHA224,
|
||||||
|
algo: PublicKeyInfo::DSA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dsa256<G: Gen>(g: &mut G) -> AlgorithmIdentifier {
|
||||||
|
AlgorithmIdentifier{
|
||||||
|
hash: HashAlgorithm::SHA256,
|
||||||
|
algo: PublicKeyInfo::DSA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for AlgorithmIdentifier {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> AlgorithmIdentifier {
|
||||||
|
let opts = [rsa1,rsa224,rsa256,rsa384,rsa512,dsa1,dsa224,dsa256];
|
||||||
|
let f = g.choose(&opts).unwrap();
|
||||||
|
f(g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quickcheck!{
|
||||||
|
fn algident_roundtrips(v: AlgorithmIdentifier) -> bool {
|
||||||
|
match encode_algorithm_ident(ASN1Class::Universal, &v) {
|
||||||
|
Err(_) =>
|
||||||
|
false,
|
||||||
|
Ok(block) => {
|
||||||
|
match decode_algorithm_ident(&block) {
|
||||||
|
Err(_) =>
|
||||||
|
false,
|
||||||
|
Ok(v2) =>
|
||||||
|
v == v2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
366
src/atv.rs
Normal file
366
src/atv.rs
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
use error::X509ParseError;
|
||||||
|
use name::X520Name;
|
||||||
|
use simple_asn1::{ASN1Block,ASN1Class,ASN1EncodeErr,FromASN1,ToASN1};
|
||||||
|
use std::ops::Index;
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub struct InfoBlock {
|
||||||
|
fields: Vec<AttributeTypeValue>
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_STRING: &'static str = "";
|
||||||
|
|
||||||
|
impl Index<X520Name> for InfoBlock {
|
||||||
|
type Output = str;
|
||||||
|
|
||||||
|
fn index(&self, name: X520Name) -> &str {
|
||||||
|
for atv in self.fields.iter() {
|
||||||
|
if name == atv.attrtype {
|
||||||
|
return &atv.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&EMPTY_STRING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for InfoBlock {
|
||||||
|
fn eq(&self, other: &InfoBlock) -> bool {
|
||||||
|
for x in self.fields.iter() {
|
||||||
|
if !other.fields.contains(x) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for x in other.fields.iter() {
|
||||||
|
if !self.fields.contains(x) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_info_block(x: &ASN1Block)
|
||||||
|
-> Result<InfoBlock,X509ParseError>
|
||||||
|
{
|
||||||
|
// Name ::= CHOICE { -- only one possibility for now --
|
||||||
|
// rdnSequence RDNSequence }
|
||||||
|
//
|
||||||
|
// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
||||||
|
//
|
||||||
|
// RelativeDistinguishedName ::=
|
||||||
|
// SET SIZE (1..MAX) OF AttributeTypeAndValue
|
||||||
|
match x {
|
||||||
|
&ASN1Block::Sequence(_, _, ref items) => {
|
||||||
|
let mut atvs = Vec::new();
|
||||||
|
|
||||||
|
for set in items.iter() {
|
||||||
|
match set {
|
||||||
|
&ASN1Block::Set(_, _, ref setitems) => {
|
||||||
|
for atv in setitems.iter() {
|
||||||
|
let v = decode_attribute_type_value(atv)?;
|
||||||
|
atvs.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
return Err(X509ParseError::IllFormedInfoBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(InfoBlock{ fields: atvs })
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllFormedInfoBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromASN1 for InfoBlock {
|
||||||
|
type Error = X509ParseError;
|
||||||
|
|
||||||
|
fn from_asn1(v: &[ASN1Block])
|
||||||
|
-> Result<(InfoBlock,&[ASN1Block]),X509ParseError>
|
||||||
|
{
|
||||||
|
match v.split_first() {
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::NotEnoughData),
|
||||||
|
Some((x, rest)) => {
|
||||||
|
let v = decode_info_block(&x)?;
|
||||||
|
Ok((v, rest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_info_block(c: ASN1Class, b: &InfoBlock)
|
||||||
|
-> Result<ASN1Block,ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let mut encoded_fields = Vec::with_capacity(b.fields.len());
|
||||||
|
|
||||||
|
for fld in b.fields.iter() {
|
||||||
|
let val = encode_attribute_type_value(c, fld)?;
|
||||||
|
encoded_fields.push(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
let set = ASN1Block::Set(c, 0, encoded_fields);
|
||||||
|
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![set]))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToASN1 for InfoBlock {
|
||||||
|
type Error = ASN1EncodeErr;
|
||||||
|
|
||||||
|
fn to_asn1_class(&self, c: ASN1Class)
|
||||||
|
-> Result<Vec<ASN1Block>,ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let block = encode_info_block(c, self)?;
|
||||||
|
Ok(vec![block])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
|
struct AttributeTypeValue {
|
||||||
|
attrtype: X520Name,
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_attribute_type_value(x: &ASN1Block)
|
||||||
|
-> Result<AttributeTypeValue,X509ParseError>
|
||||||
|
{
|
||||||
|
// AttributeTypeAndValue ::= SEQUENCE {
|
||||||
|
// type AttributeType,
|
||||||
|
// value AttributeValue }
|
||||||
|
match x {
|
||||||
|
&ASN1Block::Sequence(_, _, ref xs) => {
|
||||||
|
let (name, rest) = X520Name::from_asn1(xs)?;
|
||||||
|
match rest.first() {
|
||||||
|
None => Err(X509ParseError::NotEnoughData),
|
||||||
|
Some(ref x) => {
|
||||||
|
let atvstr = get_atv_string(name, x)?;
|
||||||
|
Ok(AttributeTypeValue{
|
||||||
|
attrtype: name,
|
||||||
|
value: atvstr
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllFormedAttrTypeValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromASN1 for AttributeTypeValue {
|
||||||
|
type Error = X509ParseError;
|
||||||
|
|
||||||
|
fn from_asn1(v: &[ASN1Block])
|
||||||
|
-> Result<(AttributeTypeValue,&[ASN1Block]),X509ParseError>
|
||||||
|
{
|
||||||
|
match v.split_first() {
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::NotEnoughData),
|
||||||
|
Some((x, rest)) => {
|
||||||
|
let v = decode_attribute_type_value(&x)?;
|
||||||
|
Ok((v, rest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_attribute_type_value(c: ASN1Class, x: &AttributeTypeValue)
|
||||||
|
-> Result<ASN1Block,ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let mut resvec = x.attrtype.to_asn1_class(c)?;
|
||||||
|
let value = match x.attrtype {
|
||||||
|
X520Name::CountryName =>
|
||||||
|
ASN1Block::PrintableString(c,0,x.value.clone()),
|
||||||
|
X520Name::SerialNumber =>
|
||||||
|
ASN1Block::PrintableString(c,0,x.value.clone()),
|
||||||
|
X520Name::DomainComponent =>
|
||||||
|
ASN1Block::IA5String(c,0,x.value.clone()),
|
||||||
|
X520Name::EmailAddress =>
|
||||||
|
ASN1Block::IA5String(c,0,x.value.clone()),
|
||||||
|
_ =>
|
||||||
|
ASN1Block::UTF8String(c,0,x.value.clone())
|
||||||
|
};
|
||||||
|
resvec.push(value);
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, resvec))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToASN1 for AttributeTypeValue {
|
||||||
|
type Error = ASN1EncodeErr;
|
||||||
|
|
||||||
|
fn to_asn1_class(&self, c: ASN1Class)
|
||||||
|
-> Result<Vec<ASN1Block>,ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let block = encode_attribute_type_value(c, self)?;
|
||||||
|
Ok(vec![block])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_atv_string(n: X520Name, x: &ASN1Block)
|
||||||
|
-> Result<String,X509ParseError>
|
||||||
|
{
|
||||||
|
match n {
|
||||||
|
X520Name::CountryName => {
|
||||||
|
let res = get_printable_val(x)?;
|
||||||
|
if res.len() != 2 {
|
||||||
|
return Err(X509ParseError::IllegalStringValue);
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
X520Name::SerialNumber => get_printable_val(x),
|
||||||
|
X520Name::DomainComponent => get_ia5_val(x),
|
||||||
|
X520Name::EmailAddress => get_ia5_val(x),
|
||||||
|
_ => get_string_val(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_string_val(a: &ASN1Block) -> Result<String,X509ParseError>
|
||||||
|
{
|
||||||
|
match a {
|
||||||
|
&ASN1Block::TeletexString(_,_,ref v) => Ok(v.clone()),
|
||||||
|
&ASN1Block::PrintableString(_,_,ref v) => Ok(v.clone()),
|
||||||
|
&ASN1Block::UniversalString(_,_,ref v) => Ok(v.clone()),
|
||||||
|
&ASN1Block::UTF8String(_,_,ref v) => Ok(v.clone()),
|
||||||
|
&ASN1Block::BMPString(_,_,ref v) => Ok(v.clone()),
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllegalStringValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_printable_val(a: &ASN1Block) -> Result<String,X509ParseError>
|
||||||
|
{
|
||||||
|
match a {
|
||||||
|
&ASN1Block::PrintableString(_,_,ref v) => Ok(v.clone()),
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllegalStringValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ia5_val(a: &ASN1Block) -> Result<String,X509ParseError>
|
||||||
|
{
|
||||||
|
match a {
|
||||||
|
&ASN1Block::IA5String(_,_,ref v) => Ok(v.clone()),
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllegalStringValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use quickcheck::{Arbitrary,Gen};
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl Arbitrary for X520Name {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> X520Name {
|
||||||
|
let names = vec![X520Name::Name,
|
||||||
|
X520Name::Surname,
|
||||||
|
X520Name::GivenName,
|
||||||
|
X520Name::Initials,
|
||||||
|
X520Name::GenerationQualifier,
|
||||||
|
X520Name::CommonName,
|
||||||
|
X520Name::LocalityName,
|
||||||
|
X520Name::StateOrProvinceName,
|
||||||
|
X520Name::OrganizationName,
|
||||||
|
X520Name::OrganizationalUnit,
|
||||||
|
X520Name::Title,
|
||||||
|
X520Name::DNQualifier,
|
||||||
|
X520Name::CountryName,
|
||||||
|
X520Name::SerialNumber,
|
||||||
|
X520Name::Pseudonym,
|
||||||
|
X520Name::DomainComponent,
|
||||||
|
X520Name::EmailAddress];
|
||||||
|
g.choose(&names).unwrap().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for AttributeTypeValue {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> AttributeTypeValue {
|
||||||
|
let name = X520Name::arbitrary(g);
|
||||||
|
let val = match name {
|
||||||
|
X520Name::CountryName => {
|
||||||
|
let mut base = gen_printable(g);
|
||||||
|
base.push('U');
|
||||||
|
base.push('S');
|
||||||
|
base.truncate(2);
|
||||||
|
base
|
||||||
|
}
|
||||||
|
X520Name::SerialNumber => gen_printable(g),
|
||||||
|
X520Name::DomainComponent => gen_ia5(g),
|
||||||
|
X520Name::EmailAddress => gen_ia5(g),
|
||||||
|
_ => gen_utf8(g)
|
||||||
|
};
|
||||||
|
AttributeTypeValue{ attrtype: name, value: val }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRINTABLE_CHARS: &'static str =
|
||||||
|
"ABCDEFGHIJKLMOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'()+,-./:=? ";
|
||||||
|
|
||||||
|
fn gen_printable<G: Gen>(g: &mut G) -> String {
|
||||||
|
let count = g.gen_range::<usize>(0, 384);
|
||||||
|
let mut items = Vec::with_capacity(count);
|
||||||
|
|
||||||
|
for _ in 0..count {
|
||||||
|
let v = g.choose(PRINTABLE_CHARS.as_bytes()).unwrap();
|
||||||
|
items.push(*v as char);
|
||||||
|
}
|
||||||
|
String::from_iter(items.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_ia5<G: Gen>(g: &mut G) -> String {
|
||||||
|
let count = g.gen_range::<usize>(0, 384);
|
||||||
|
let mut items = Vec::with_capacity(count);
|
||||||
|
|
||||||
|
for _ in 0..count {
|
||||||
|
items.push(g.gen::<u8>() as char);
|
||||||
|
}
|
||||||
|
String::from_iter(items.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_utf8<G: Gen>(g: &mut G) -> String {
|
||||||
|
String::arbitrary(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for InfoBlock {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> InfoBlock {
|
||||||
|
let count = g.gen_range::<usize>(0,12);
|
||||||
|
let mut items = Vec::with_capacity(count);
|
||||||
|
let mut names = Vec::with_capacity(count);
|
||||||
|
|
||||||
|
while items.len() < count {
|
||||||
|
let atv = AttributeTypeValue::arbitrary(g);
|
||||||
|
if !names.contains(&atv.attrtype) {
|
||||||
|
names.push(atv.attrtype);
|
||||||
|
items.push(atv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoBlock{ fields: items }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quickcheck! {
|
||||||
|
fn attrtypeval_roundtrips(v: AttributeTypeValue) -> bool {
|
||||||
|
match encode_attribute_type_value(ASN1Class::Universal, &v) {
|
||||||
|
Err(_) => false,
|
||||||
|
Ok(bstr) =>
|
||||||
|
match decode_attribute_type_value(&bstr) {
|
||||||
|
Err(_) => false,
|
||||||
|
Ok(v2) => v == v2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infoblock_roundtrips(v: InfoBlock) -> bool {
|
||||||
|
match encode_info_block(ASN1Class::Universal, &v) {
|
||||||
|
Err(_) => false,
|
||||||
|
Ok(bstr) =>
|
||||||
|
match decode_info_block(&bstr) {
|
||||||
|
Err(_) => false,
|
||||||
|
Ok(v2) => v == v2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/error.rs
Normal file
36
src/error.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use simple_asn1::ASN1DecodeErr;
|
||||||
|
use simple_dsa::DSAError;
|
||||||
|
use simple_rsa::RSAError;
|
||||||
|
|
||||||
|
/// The error type for parsing and validating an X.509 certificate.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum X509ParseError {
|
||||||
|
ASN1DecodeError(ASN1DecodeErr),
|
||||||
|
RSAError(RSAError), DSAError(DSAError),
|
||||||
|
NotEnoughData,
|
||||||
|
IllFormedName, IllFormedAttrTypeValue, IllFormedInfoBlock,
|
||||||
|
IllFormedValidity, IllFormedCertificateInfo, IllFormedSerialNumber,
|
||||||
|
IllFormedAlgoInfo, IllFormedKey, IllFormedEverything,
|
||||||
|
IllegalStringValue, NoSerialNumber, InvalidDSAInfo, ItemNotFound,
|
||||||
|
UnknownAlgorithm, InvalidRSAKey, InvalidDSAKey, KeyNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ASN1DecodeErr> for X509ParseError {
|
||||||
|
fn from(e: ASN1DecodeErr) -> X509ParseError {
|
||||||
|
X509ParseError::ASN1DecodeError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RSAError> for X509ParseError {
|
||||||
|
fn from(e: RSAError) -> X509ParseError {
|
||||||
|
X509ParseError::RSAError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DSAError> for X509ParseError {
|
||||||
|
fn from(e: DSAError) -> X509ParseError {
|
||||||
|
X509ParseError::DSAError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
744
src/lib.rs
744
src/lib.rs
@@ -5,12 +5,173 @@ extern crate num;
|
|||||||
extern crate quickcheck;
|
extern crate quickcheck;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate simple_asn1;
|
extern crate simple_asn1;
|
||||||
|
extern crate simple_dsa;
|
||||||
extern crate simple_rsa;
|
extern crate simple_rsa;
|
||||||
|
|
||||||
|
mod algident;
|
||||||
|
mod atv;
|
||||||
|
mod error;
|
||||||
|
mod misc;
|
||||||
|
mod name;
|
||||||
|
mod publickey;
|
||||||
|
mod validity;
|
||||||
|
|
||||||
|
use algident::AlgorithmIdentifier;
|
||||||
|
use atv::InfoBlock;
|
||||||
|
use error::X509ParseError;
|
||||||
|
use misc::{X509Serial,X509Version};
|
||||||
|
use publickey::X509PublicKey;
|
||||||
|
use simple_asn1::{ASN1Block,FromASN1};
|
||||||
|
use validity::Validity;
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* The actual certificate data type and methods
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
/// The type of an X.509 certificate.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Certificate {
|
||||||
|
pub version: X509Version,
|
||||||
|
pub serial: X509Serial,
|
||||||
|
pub signature_alg: AlgorithmIdentifier,
|
||||||
|
pub issuer: InfoBlock,
|
||||||
|
pub subject: InfoBlock,
|
||||||
|
pub validity: Validity,
|
||||||
|
pub subject_key: X509PublicKey,
|
||||||
|
pub extensions: Vec<()>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_certificate(x: &ASN1Block)
|
||||||
|
-> Result<Certificate,X509ParseError>
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// TBSCertificate ::= SEQUENCE {
|
||||||
|
// version [0] Version DEFAULT v1,
|
||||||
|
// serialNumber CertificateSerialNumber,
|
||||||
|
// signature AlgorithmIdentifier,
|
||||||
|
// issuer Name,
|
||||||
|
// validity Validity,
|
||||||
|
// subject Name,
|
||||||
|
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||||
|
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||||
|
// -- If present, version MUST be v2 or v3
|
||||||
|
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||||
|
// -- If present, version MUST be v2 or v3
|
||||||
|
// extensions [3] Extensions OPTIONAL
|
||||||
|
// -- If present, version MUST be v3 -- }
|
||||||
|
//
|
||||||
|
println!("x: {:?}", x);
|
||||||
|
match x {
|
||||||
|
&ASN1Block::Sequence(_, _, ref b0) => {
|
||||||
|
let (version, b1) = X509Version::from_asn1(b0)?;
|
||||||
|
println!("version: {:?}", version);
|
||||||
|
let (serial, b2) = X509Serial::from_asn1(b1)?;
|
||||||
|
println!("serial: {:?}", serial);
|
||||||
|
let (ident, b3) = AlgorithmIdentifier::from_asn1(b2)?;
|
||||||
|
println!("ident: {:?}", ident);
|
||||||
|
let (issuer, b4) = InfoBlock::from_asn1(b3)?;
|
||||||
|
println!("issuer: {:?}", issuer);
|
||||||
|
let (validity, b5) = Validity::from_asn1(b4)?;
|
||||||
|
println!("validity: {:?}", validity);
|
||||||
|
let (subject, b6) = InfoBlock::from_asn1(b5)?;
|
||||||
|
println!("subject: {:?}", subject);
|
||||||
|
let (subkey, b7) = X509PublicKey::from_asn1(b6)?;
|
||||||
|
println!("subkey: {:?}", subkey);
|
||||||
|
println!("REMAINDER: {:?}", b7);
|
||||||
|
Ok(Certificate {
|
||||||
|
version: version,
|
||||||
|
serial: serial,
|
||||||
|
signature_alg: ident,
|
||||||
|
issuer: issuer,
|
||||||
|
subject: subject,
|
||||||
|
validity: validity,
|
||||||
|
subject_key: subkey,
|
||||||
|
extensions: vec![]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllFormedCertificateInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* X.509 parsing routines
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
fn parse_x509(blocks: &[ASN1Block], buffer: &[u8])
|
||||||
|
-> Result<Certificate,X509ParseError>
|
||||||
|
{
|
||||||
|
match blocks.first() {
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::NotEnoughData),
|
||||||
|
Some(&ASN1Block::Sequence(_, _, ref x)) => {
|
||||||
|
let cert = decode_certificate(&x[0])?;
|
||||||
|
println!("cert: {:?}", cert);
|
||||||
|
Ok(cert)
|
||||||
|
}
|
||||||
|
Some(_) =>
|
||||||
|
Err(X509ParseError::IllFormedEverything)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* Testing is for winners!
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use simple_asn1::from_der;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn can_parse(f: &str) -> Result<Certificate,X509ParseError> {
|
||||||
|
let mut fd = File::open(f).unwrap();
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
let _amt = fd.read_to_end(&mut buffer);
|
||||||
|
println!("_amt: {:?}", _amt);
|
||||||
|
let asn1: Vec<ASN1Block> = from_der(&buffer[..])?;
|
||||||
|
parse_x509(&asn1, &buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rsa_tests() {
|
||||||
|
assert!(can_parse("test/rsa2048-1.der").is_ok());
|
||||||
|
assert!(can_parse("test/rsa2048-2.der").is_ok());
|
||||||
|
assert!(can_parse("test/rsa4096-1.der").is_ok());
|
||||||
|
assert!(can_parse("test/rsa4096-2.der").is_ok());
|
||||||
|
assert!(can_parse("test/rsa4096-3.der").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dsa_tests() {
|
||||||
|
assert!(can_parse("test/dsa2048-1.der").is_ok());
|
||||||
|
assert!(can_parse("test/dsa2048-2.der").is_ok());
|
||||||
|
assert!(can_parse("test/dsa3072-1.der").is_ok());
|
||||||
|
assert!(can_parse("test/dsa3072-2.der").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
fn ecc_tests() {
|
||||||
|
assert!(can_parse("test/ec384-1.der").is_ok());
|
||||||
|
assert!(can_parse("test/ec384-2.der").is_ok());
|
||||||
|
assert!(can_parse("test/ec384-3.der").is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
use chrono::{DateTime,Utc};
|
use chrono::{DateTime,Utc};
|
||||||
use num::{BigUint,ToPrimitive};
|
use num::{BigUint,ToPrimitive};
|
||||||
use simple_asn1::{ASN1Block,ASN1Class,FromASN1,FromASN1WithBody,OID,ToASN1};
|
use simple_asn1::{ASN1Block,ASN1Class,FromASN1,FromASN1WithBody,OID,ToASN1};
|
||||||
use simple_asn1::{ASN1DecodeErr,ASN1EncodeErr,der_decode};
|
use simple_asn1::{ASN1DecodeErr,ASN1EncodeErr,der_decode};
|
||||||
|
use simple_dsa::{DSAPublicKey};
|
||||||
use simple_rsa::{RSAPublicKey,RSAError,SigningHash,
|
use simple_rsa::{RSAPublicKey,RSAError,SigningHash,
|
||||||
SIGNING_HASH_SHA1, SIGNING_HASH_SHA224, SIGNING_HASH_SHA256,
|
SIGNING_HASH_SHA1, SIGNING_HASH_SHA224, SIGNING_HASH_SHA256,
|
||||||
SIGNING_HASH_SHA384, SIGNING_HASH_SHA512};
|
SIGNING_HASH_SHA384, SIGNING_HASH_SHA512};
|
||||||
@@ -19,7 +180,14 @@ use simple_rsa::{RSAPublicKey,RSAError,SigningHash,
|
|||||||
enum HashAlgorithm { None, MD2, MD5, SHA1, SHA224, SHA256, SHA384, SHA512 }
|
enum HashAlgorithm { None, MD2, MD5, SHA1, SHA224, SHA256, SHA384, SHA512 }
|
||||||
|
|
||||||
#[derive(Clone,Debug,PartialEq)]
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
enum PubKeyAlgorithm { RSA, RSAPSS, DSA, EC, DH, Unknown(OID) }
|
enum PubKeyAlgorithm {
|
||||||
|
RSA,
|
||||||
|
RSAPSS,
|
||||||
|
DSA(BigUint,BigUint,BigUint),
|
||||||
|
EC,
|
||||||
|
DH,
|
||||||
|
Unknown(OID)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum X509ParseError {
|
enum X509ParseError {
|
||||||
@@ -28,14 +196,15 @@ enum X509ParseError {
|
|||||||
NoSignatureAlgorithm, NoNameInformation, IllFormedNameInformation,
|
NoSignatureAlgorithm, NoNameInformation, IllFormedNameInformation,
|
||||||
NoValueForName, UnknownAttrTypeValue, IllegalStringValue, NoValidityInfo,
|
NoValueForName, UnknownAttrTypeValue, IllegalStringValue, NoValidityInfo,
|
||||||
ImproperValidityInfo, NoSubjectPublicKeyInfo, ImproperSubjectPublicKeyInfo,
|
ImproperValidityInfo, NoSubjectPublicKeyInfo, ImproperSubjectPublicKeyInfo,
|
||||||
BadPublicKeyAlgorithm, UnsupportedPublicKey, InvalidRSAKey,
|
BadPublicKeyAlgorithm, UnsupportedPublicKey, InvalidRSAKey, InvalidDSAInfo,
|
||||||
UnsupportedExtension, UnexpectedNegativeNumber, MissingNumber,
|
UnsupportedExtension, UnexpectedNegativeNumber, MissingNumber,
|
||||||
NoSignatureFound, UnsupportedSignature, SignatureFailed
|
NoSignatureFound, UnsupportedSignature, SignatureFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug,PartialEq)]
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
enum X509PublicKey {
|
enum X509PublicKey {
|
||||||
RSA(RSAPublicKey)
|
DSA(DSAPublicKey),
|
||||||
|
RSA(RSAPublicKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ASN1DecodeErr> for X509ParseError {
|
impl From<ASN1DecodeErr> for X509ParseError {
|
||||||
@@ -126,151 +295,6 @@ impl FromASN1 for SignatureAlgorithm {
|
|||||||
Some((x, rest)) => {
|
Some((x, rest)) => {
|
||||||
match x {
|
match x {
|
||||||
&ASN1Block::ObjectIdentifier(_, _, ref oid) => {
|
&ASN1Block::ObjectIdentifier(_, _, ref oid) => {
|
||||||
if oid == oid!(1,2,840,113549,1,1,1) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::None,
|
|
||||||
key_alg: PubKeyAlgorithm::RSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,113549,1,1,5) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA1,
|
|
||||||
key_alg: PubKeyAlgorithm::RSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,113549,1,1,4) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::MD5,
|
|
||||||
key_alg: PubKeyAlgorithm::RSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,113549,1,1,2) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::MD2,
|
|
||||||
key_alg: PubKeyAlgorithm::RSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,113549,1,1,11) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA256,
|
|
||||||
key_alg: PubKeyAlgorithm::RSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,113549,1,1,12) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA384,
|
|
||||||
key_alg: PubKeyAlgorithm::RSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,113549,1,1,13) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA512,
|
|
||||||
key_alg: PubKeyAlgorithm::RSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,113549,1,1,14) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA224,
|
|
||||||
key_alg: PubKeyAlgorithm::RSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,10040,4,1) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::None,
|
|
||||||
key_alg: PubKeyAlgorithm::DSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,10040,4,3) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA1,
|
|
||||||
key_alg: PubKeyAlgorithm::DSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,10045,2,1) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::None,
|
|
||||||
key_alg: PubKeyAlgorithm::EC
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,10045,4,1) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA1,
|
|
||||||
key_alg: PubKeyAlgorithm::EC
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,10045,4,3,1) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA224,
|
|
||||||
key_alg: PubKeyAlgorithm::EC
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,10045,4,3,2) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA256,
|
|
||||||
key_alg: PubKeyAlgorithm::EC
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,10045,4,3,3) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA384,
|
|
||||||
key_alg: PubKeyAlgorithm::EC
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,10045,4,3,4) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA512,
|
|
||||||
key_alg: PubKeyAlgorithm::EC
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,113549,1,1,10) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::None,
|
|
||||||
key_alg: PubKeyAlgorithm::RSAPSS
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(2,16,840,1,101,3,4,2,1) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA256,
|
|
||||||
key_alg: PubKeyAlgorithm::RSAPSS
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(2,16,840,1,101,3,4,2,2) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA384,
|
|
||||||
key_alg: PubKeyAlgorithm::RSAPSS
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(2,16,840,1,101,3,4,2,3) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA512,
|
|
||||||
key_alg: PubKeyAlgorithm::RSAPSS
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(2,16,840,1,101,3,4,2,4) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA224,
|
|
||||||
key_alg: PubKeyAlgorithm::RSAPSS
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(2,16,840,1,101,3,4,3,1) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA224,
|
|
||||||
key_alg: PubKeyAlgorithm::DSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(2,16,840,1,101,3,4,3,2) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::SHA256,
|
|
||||||
key_alg: PubKeyAlgorithm::DSA
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
if oid == oid!(1,2,840,10046,2,1) {
|
|
||||||
return Ok((SignatureAlgorithm {
|
|
||||||
hash_alg: HashAlgorithm::None,
|
|
||||||
key_alg: PubKeyAlgorithm::DH
|
|
||||||
}, rest));
|
|
||||||
}
|
|
||||||
Err(X509ParseError::ItemNotFound)
|
|
||||||
}
|
}
|
||||||
_ =>
|
_ =>
|
||||||
Err(X509ParseError::ItemNotFound)
|
Err(X509ParseError::ItemNotFound)
|
||||||
@@ -280,6 +304,21 @@ impl FromASN1 for SignatureAlgorithm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_dsa_info(vs: &[ASN1Block])
|
||||||
|
-> Result<(BigUint, BigUint, BigUint), X509ParseError>
|
||||||
|
{
|
||||||
|
match vs.split_first() {
|
||||||
|
Some((&ASN1Block::Sequence(_, _, ref info), rest)) => {
|
||||||
|
let p = decode_biguint(&info[0])?;
|
||||||
|
let q = decode_biguint(&info[1])?;
|
||||||
|
let g = decode_biguint(&info[2])?;
|
||||||
|
Ok((p, q, g))
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::InvalidDSAInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug,PartialEq)]
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
pub enum SigAlgEncodeErr {
|
pub enum SigAlgEncodeErr {
|
||||||
ASN1Problem(ASN1EncodeErr),
|
ASN1Problem(ASN1EncodeErr),
|
||||||
@@ -441,74 +480,27 @@ fn signing_hash(a: HashAlgorithm)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tbs_certificate(x: &ASN1Block)
|
|
||||||
-> Result<Certificate,X509ParseError>
|
|
||||||
{
|
|
||||||
match x {
|
|
||||||
&ASN1Block::Sequence(_, _, ref v0) => {
|
|
||||||
// TBSCertificate ::= SEQUENCE {
|
|
||||||
// version [0] Version DEFAULT v1,
|
|
||||||
let (version, v1) = get_version(v0)?;
|
|
||||||
// serialNumber CertificateSerialNumber,
|
|
||||||
let (serial, v2) = get_serial(v1)?;
|
|
||||||
// signature AlgorithmIdentifier,
|
|
||||||
let (algo, v3) = get_signature_info(v2)?;
|
|
||||||
// issuer Name,
|
|
||||||
let (issuer, v4) = get_name_data(v3)?;
|
|
||||||
// validity Validity,
|
|
||||||
let (validity, v5) = get_validity_data(v4)?;
|
|
||||||
// subject Name,
|
|
||||||
let (subject, v6) = get_name_data(v5)?;
|
|
||||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
|
||||||
let (subpki, v7) = get_subject_pki(v6)?;
|
|
||||||
|
|
||||||
if (version < 3) && !v7.is_empty() {
|
|
||||||
return Err(X509ParseError::UnsupportedExtension)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Support v3 extensions
|
|
||||||
if !v7.is_empty() {
|
|
||||||
return Err(X509ParseError::UnsupportedExtension)
|
|
||||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
||||||
// -- If present, version MUST be v2 or v3
|
|
||||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
||||||
// -- If present, version MUST be v2 or v3
|
|
||||||
// extensions [3] Extensions OPTIONAL
|
|
||||||
// -- If present, version MUST be v3 -- }
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Certificate{
|
|
||||||
version: version,
|
|
||||||
serial: serial,
|
|
||||||
signature_alg: algo,
|
|
||||||
issuer: issuer,
|
|
||||||
subject: subject,
|
|
||||||
validity: validity,
|
|
||||||
subject_key: subpki,
|
|
||||||
extensions: vec![]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::IllegalFormat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_signature_alg(x: &ASN1Block)
|
fn get_signature_alg(x: &ASN1Block)
|
||||||
-> Result<SignatureAlgorithm,X509ParseError>
|
-> Result<SignatureAlgorithm,X509ParseError>
|
||||||
{
|
{
|
||||||
// AlgorithmIdentifier ::= SEQUENCE {
|
// AlgorithmIdentifier ::= SEQUENCE {
|
||||||
// algorithm OBJECT IDENTIFIER,
|
// algorithm OBJECT IDENTIFIER,
|
||||||
// parameters ANY DEFINED BY algorithm OPTIONAL }
|
// parameters ANY DEFINED BY algorithm OPTIONAL }
|
||||||
|
println!("get_signature_alg {:?}", x);
|
||||||
match x {
|
match x {
|
||||||
&ASN1Block::Sequence(_, _, ref v) if v.len() == 2 => {
|
&ASN1Block::Sequence(_, _, ref v) => {
|
||||||
|
// initially there was a length check on v as a side condition
|
||||||
|
// for this case, but it caused unexpected problems and I took
|
||||||
|
// it out.
|
||||||
let (alg, _) = SignatureAlgorithm::from_asn1(v)?;
|
let (alg, _) = SignatureAlgorithm::from_asn1(v)?;
|
||||||
Ok(alg)
|
Ok(alg)
|
||||||
}
|
}
|
||||||
_ =>
|
_ => {
|
||||||
|
println!("Pattern match failed?!");
|
||||||
Err(X509ParseError::IllegalFormat)
|
Err(X509ParseError::IllegalFormat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_version(bs: &[ASN1Block])
|
fn get_version(bs: &[ASN1Block])
|
||||||
-> Result<(u32, &[ASN1Block]),X509ParseError>
|
-> Result<(u32, &[ASN1Block]),X509ParseError>
|
||||||
@@ -558,6 +550,7 @@ fn get_signature_info(bs: &[ASN1Block])
|
|||||||
{
|
{
|
||||||
match bs.split_first() {
|
match bs.split_first() {
|
||||||
Some((x, rest)) => {
|
Some((x, rest)) => {
|
||||||
|
println!("x: {:?}", x);
|
||||||
let alg = get_signature_alg(&x)?;
|
let alg = get_signature_alg(&x)?;
|
||||||
Ok((alg, rest))
|
Ok((alg, rest))
|
||||||
}
|
}
|
||||||
@@ -566,322 +559,6 @@ fn get_signature_info(bs: &[ASN1Block])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug,PartialEq)]
|
|
||||||
struct InfoBlock {
|
|
||||||
name: String,
|
|
||||||
surname: String,
|
|
||||||
given_name: String,
|
|
||||||
initials: String,
|
|
||||||
generation_qualifier: String,
|
|
||||||
common_name: String,
|
|
||||||
locality: String,
|
|
||||||
state_province: String,
|
|
||||||
organization: String,
|
|
||||||
unit: String,
|
|
||||||
title: String,
|
|
||||||
dn_qualifier: String,
|
|
||||||
country: String,
|
|
||||||
serial_number: String,
|
|
||||||
pseudonym: String,
|
|
||||||
domain_component: String,
|
|
||||||
email: String
|
|
||||||
}
|
|
||||||
|
|
||||||
fn empty_block() -> InfoBlock {
|
|
||||||
InfoBlock {
|
|
||||||
name: "".to_string(),
|
|
||||||
surname: "".to_string(),
|
|
||||||
given_name: "".to_string(),
|
|
||||||
initials: "".to_string(),
|
|
||||||
generation_qualifier: "".to_string(),
|
|
||||||
common_name: "".to_string(),
|
|
||||||
locality: "".to_string(),
|
|
||||||
state_province: "".to_string(),
|
|
||||||
organization: "".to_string(),
|
|
||||||
unit: "".to_string(),
|
|
||||||
title: "".to_string(),
|
|
||||||
dn_qualifier: "".to_string(),
|
|
||||||
country: "".to_string(),
|
|
||||||
serial_number: "".to_string(),
|
|
||||||
pseudonym: "".to_string(),
|
|
||||||
domain_component: "".to_string(),
|
|
||||||
email: "".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_name_data(bs: &[ASN1Block])
|
|
||||||
-> Result<(InfoBlock,&[ASN1Block]),X509ParseError>
|
|
||||||
{
|
|
||||||
match bs.split_first() {
|
|
||||||
Some((x,rest)) => {
|
|
||||||
match x {
|
|
||||||
// Name ::= CHOICE { -- only one possibility for now --
|
|
||||||
// rdnSequence RDNSequence }
|
|
||||||
//
|
|
||||||
// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
|
||||||
&ASN1Block::Sequence(_, _, ref items) => {
|
|
||||||
// RelativeDistinguishedName ::=
|
|
||||||
// SET SIZE (1..MAX) OF AttributeTypeAndValue
|
|
||||||
let mut iblock = empty_block();
|
|
||||||
|
|
||||||
for item in items.iter() {
|
|
||||||
match item {
|
|
||||||
&ASN1Block::Set(_, _, ref info) => {
|
|
||||||
for atv in info.iter() {
|
|
||||||
parse_attr_type_val(&atv, &mut iblock)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ =>
|
|
||||||
return Err(X509ParseError::IllFormedNameInformation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((iblock, rest))
|
|
||||||
}
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::NoNameInformation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::NoNameInformation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_attr_type_val(val: &ASN1Block, iblock: &mut InfoBlock)
|
|
||||||
-> Result<(),X509ParseError>
|
|
||||||
{
|
|
||||||
match val {
|
|
||||||
// AttributeTypeAndValue ::= SEQUENCE {
|
|
||||||
// type AttributeType,
|
|
||||||
// value AttributeValue }
|
|
||||||
&ASN1Block::Sequence(_, _, ref oidval) => {
|
|
||||||
match oidval.split_first() {
|
|
||||||
// AttributeType ::= OBJECT IDENTIFIER
|
|
||||||
Some((&ASN1Block::ObjectIdentifier(_, _, ref oid), rest)) => {
|
|
||||||
match rest.first() {
|
|
||||||
// AttributeValue ::= ANY -- DEFINED BY AttributeType
|
|
||||||
Some(val) =>
|
|
||||||
process_atv(oid, val, iblock),
|
|
||||||
None =>
|
|
||||||
Err(X509ParseError::NoValueForName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::IllFormedNameInformation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::IllFormedNameInformation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_atv(oid: &OID, val: &ASN1Block, iblock: &mut InfoBlock)
|
|
||||||
-> Result<(),X509ParseError>
|
|
||||||
{
|
|
||||||
//-- Arc for standard naming attributes
|
|
||||||
//
|
|
||||||
//id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 }
|
|
||||||
//
|
|
||||||
//-- Naming attributes of type X520name
|
|
||||||
//
|
|
||||||
//id-at-name AttributeType ::= { id-at 41 }
|
|
||||||
if oid == oid!(2,5,4,41) {
|
|
||||||
iblock.name = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//id-at-surname AttributeType ::= { id-at 4 }
|
|
||||||
if oid == oid!(2,5,4,4) {
|
|
||||||
iblock.surname = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//id-at-givenName AttributeType ::= { id-at 42 }
|
|
||||||
if oid == oid!(2,5,4,42) {
|
|
||||||
iblock.given_name = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//id-at-initials AttributeType ::= { id-at 43 }
|
|
||||||
if oid == oid!(2,5,4,43) {
|
|
||||||
iblock.initials = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//id-at-generationQualifier AttributeType ::= { id-at 44 }
|
|
||||||
if oid == oid!(2,5,4,44) {
|
|
||||||
iblock.generation_qualifier = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//
|
|
||||||
//-- Naming attributes of type X520CommonName
|
|
||||||
//
|
|
||||||
//id-at-commonName AttributeType ::= { id-at 3 }
|
|
||||||
if oid == oid!(2,5,4,3) {
|
|
||||||
iblock.common_name = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//-- Naming attributes of type X520LocalityName
|
|
||||||
//
|
|
||||||
//id-at-localityName AttributeType ::= { id-at 7 }
|
|
||||||
if oid == oid!(2,5,4,7) {
|
|
||||||
iblock.locality = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//-- Naming attributes of type X520StateOrProvinceName
|
|
||||||
//
|
|
||||||
//id-at-stateOrProvinceName AttributeType ::= { id-at 8 }
|
|
||||||
if oid == oid!(2,5,4,8) {
|
|
||||||
iblock.state_province = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//-- Naming attributes of type X520OrganizationName
|
|
||||||
//
|
|
||||||
//id-at-organizationName AttributeType ::= { id-at 10 }
|
|
||||||
if oid == oid!(2,5,4,10) {
|
|
||||||
iblock.organization = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//-- Naming attributes of type X520OrganizationalUnitName
|
|
||||||
//
|
|
||||||
//id-at-organizationalUnitName AttributeType ::= { id-at 11 }
|
|
||||||
if oid == oid!(2,5,4,11) {
|
|
||||||
iblock.unit = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//-- Naming attributes of type X520Title
|
|
||||||
//
|
|
||||||
//id-at-title AttributeType ::= { id-at 12 }
|
|
||||||
if oid == oid!(2,5,4,12) {
|
|
||||||
iblock.title = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//-- Naming attributes of type X520dnQualifier
|
|
||||||
//
|
|
||||||
//id-at-dnQualifier AttributeType ::= { id-at 46 }
|
|
||||||
//
|
|
||||||
//X520dnQualifier ::= PrintableString
|
|
||||||
if oid == oid!(2,5,4,46) {
|
|
||||||
iblock.dn_qualifier = get_printable_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//
|
|
||||||
//-- Naming attributes of type X520countryName (digraph from IS 3166)
|
|
||||||
//
|
|
||||||
//id-at-countryName AttributeType ::= { id-at 6 }
|
|
||||||
//
|
|
||||||
//X520countryName ::= PrintableString (SIZE (2))
|
|
||||||
if oid == oid!(2,5,4,6) {
|
|
||||||
iblock.country = get_printable_string_value(val)?;
|
|
||||||
if iblock.country.len() != 2 {
|
|
||||||
return Err(X509ParseError::IllegalStringValue);
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//
|
|
||||||
//-- Naming attributes of type X520SerialNumber
|
|
||||||
//
|
|
||||||
//id-at-serialNumber AttributeType ::= { id-at 5 }
|
|
||||||
//
|
|
||||||
//X520SerialNumber ::= PrintableString (SIZE (1..ub-serial-number))
|
|
||||||
if oid == oid!(2,5,4,5) {
|
|
||||||
iblock.serial_number = get_printable_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//
|
|
||||||
//-- Naming attributes of type X520Pseudonym
|
|
||||||
//
|
|
||||||
//id-at-pseudonym AttributeType ::= { id-at 65 }
|
|
||||||
if oid == oid!(2,5,4,65) {
|
|
||||||
iblock.pseudonym = get_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//-- Naming attributes of type DomainComponent (from RFC 4519)
|
|
||||||
//
|
|
||||||
//id-domainComponent AttributeType ::= { 0 9 2342 19200300 100 1 25 }
|
|
||||||
//
|
|
||||||
//DomainComponent ::= IA5String
|
|
||||||
if oid == oid!(0,9,2342,19200300,100,1,25) {
|
|
||||||
iblock.domain_component = get_ia5_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
//-- Legacy attributes
|
|
||||||
//
|
|
||||||
//pkcs-9 OBJECT IDENTIFIER ::=
|
|
||||||
// { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 }
|
|
||||||
//
|
|
||||||
//id-emailAddress AttributeType ::= { pkcs-9 1 }
|
|
||||||
//
|
|
||||||
//EmailAddress ::= IA5String (SIZE (1..ub-emailaddress-length))
|
|
||||||
if oid == oid!(1,2,840,113549,1,9,1) {
|
|
||||||
iblock.email = get_ia5_string_value(val)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(X509ParseError::UnknownAttrTypeValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_string_value(a: &ASN1Block) -> Result<String,X509ParseError>
|
|
||||||
{
|
|
||||||
match a {
|
|
||||||
&ASN1Block::TeletexString(_,_,ref v) => Ok(v.clone()),
|
|
||||||
&ASN1Block::PrintableString(_,_,ref v) => Ok(v.clone()),
|
|
||||||
&ASN1Block::UniversalString(_,_,ref v) => Ok(v.clone()),
|
|
||||||
&ASN1Block::UTF8String(_,_,ref v) => Ok(v.clone()),
|
|
||||||
&ASN1Block::BMPString(_,_,ref v) => Ok(v.clone()),
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::IllegalStringValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_printable_string_value(a: &ASN1Block) -> Result<String,X509ParseError>
|
|
||||||
{
|
|
||||||
match a {
|
|
||||||
&ASN1Block::PrintableString(_,_,ref v) => Ok(v.clone()),
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::IllegalStringValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_ia5_string_value(a: &ASN1Block) -> Result<String,X509ParseError>
|
|
||||||
{
|
|
||||||
match a {
|
|
||||||
&ASN1Block::IA5String(_,_,ref v) => Ok(v.clone()),
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::IllegalStringValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Debug,PartialEq)]
|
|
||||||
struct Validity {
|
|
||||||
not_before: DateTime<Utc>,
|
|
||||||
not_after: DateTime<Utc>
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_validity_data(bs: &[ASN1Block])
|
|
||||||
-> Result<(Validity,&[ASN1Block]),X509ParseError>
|
|
||||||
{
|
|
||||||
match bs.split_first() {
|
|
||||||
// Validity ::= SEQUENCE {
|
|
||||||
// notBefore Time,
|
|
||||||
// notAfter Time }
|
|
||||||
Some((&ASN1Block::Sequence(_, _, ref valxs), rest)) => {
|
|
||||||
if valxs.len() != 2 {
|
|
||||||
return Err(X509ParseError::ImproperValidityInfo);
|
|
||||||
}
|
|
||||||
let nb = get_time(&valxs[0])?;
|
|
||||||
let na = get_time(&valxs[1])?;
|
|
||||||
Ok((Validity{ not_before: nb, not_after: na }, rest))
|
|
||||||
}
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::NoValidityInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_time(b: &ASN1Block) -> Result<DateTime<Utc>, X509ParseError> {
|
|
||||||
match b {
|
|
||||||
&ASN1Block::UTCTime(_, _, v) => Ok(v.clone()),
|
|
||||||
&ASN1Block::GeneralizedTime(_, _, v) => Ok(v.clone()),
|
|
||||||
_ =>
|
|
||||||
Err(X509ParseError::ImproperValidityInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_subject_pki(b: &[ASN1Block])
|
fn get_subject_pki(b: &[ASN1Block])
|
||||||
-> Result<(X509PublicKey, &[ASN1Block]), X509ParseError>
|
-> Result<(X509PublicKey, &[ASN1Block]), X509ParseError>
|
||||||
{
|
{
|
||||||
@@ -890,6 +567,7 @@ fn get_subject_pki(b: &[ASN1Block])
|
|||||||
// algorithm AlgorithmIdentifier,
|
// algorithm AlgorithmIdentifier,
|
||||||
// subjectPublicKey BIT STRING }
|
// subjectPublicKey BIT STRING }
|
||||||
Some((&ASN1Block::Sequence(_, _, ref info), rest)) => {
|
Some((&ASN1Block::Sequence(_, _, ref info), rest)) => {
|
||||||
|
println!("get_subject_pki {:?}", info);
|
||||||
if info.len() != 2 {
|
if info.len() != 2 {
|
||||||
return Err(X509ParseError::ImproperSubjectPublicKeyInfo)
|
return Err(X509ParseError::ImproperSubjectPublicKeyInfo)
|
||||||
}
|
}
|
||||||
@@ -907,6 +585,9 @@ fn get_subject_pki(b: &[ASN1Block])
|
|||||||
Ok((X509PublicKey::RSA(key), rest))
|
Ok((X509PublicKey::RSA(key), rest))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
let key = get_dsa_public_key(&info[1])?;
|
||||||
|
println!("key alg: {:?}", alginfo.key_alg);
|
||||||
|
println!("info: {:?}", info);
|
||||||
Err(X509ParseError::UnsupportedPublicKey)
|
Err(X509ParseError::UnsupportedPublicKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -928,9 +609,20 @@ fn get_rsa_public_key(b: &ASN1Block)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_dsa_public_key(b: &ASN1Block)
|
||||||
|
-> Result<DSAPublicKey, X509ParseError>
|
||||||
|
{
|
||||||
|
match b {
|
||||||
|
&ASN1Block::BitString(_, _, size, ref vec) if size % 8 == 0 => {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::InvalidRSAKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use quickcheck::{Arbitrary,Gen};
|
|
||||||
use simple_asn1::{der_decode,der_encode};
|
use simple_asn1::{der_decode,der_encode};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
@@ -941,7 +633,9 @@ mod tests {
|
|||||||
match g.gen::<u8>() % 6 {
|
match g.gen::<u8>() % 6 {
|
||||||
0 => PubKeyAlgorithm::RSA,
|
0 => PubKeyAlgorithm::RSA,
|
||||||
1 => PubKeyAlgorithm::RSAPSS,
|
1 => PubKeyAlgorithm::RSAPSS,
|
||||||
2 => PubKeyAlgorithm::DSA,
|
2 => {
|
||||||
|
PubKeyAlgorithm::DSA,
|
||||||
|
}
|
||||||
3 => PubKeyAlgorithm::EC,
|
3 => PubKeyAlgorithm::EC,
|
||||||
4 => PubKeyAlgorithm::DH,
|
4 => PubKeyAlgorithm::DH,
|
||||||
5 => {
|
5 => {
|
||||||
@@ -1099,5 +793,13 @@ mod tests {
|
|||||||
assert!(can_parse("test/rsa4096-1.der").is_ok());
|
assert!(can_parse("test/rsa4096-1.der").is_ok());
|
||||||
assert!(can_parse("test/rsa4096-2.der").is_ok());
|
assert!(can_parse("test/rsa4096-2.der").is_ok());
|
||||||
assert!(can_parse("test/rsa4096-3.der").is_ok());
|
assert!(can_parse("test/rsa4096-3.der").is_ok());
|
||||||
|
assert!(can_parse("test/dsa2048-1.der").is_ok());
|
||||||
|
assert!(can_parse("test/dsa2048-2.der").is_ok());
|
||||||
|
assert!(can_parse("test/dsa3072-1.der").is_ok());
|
||||||
|
assert!(can_parse("test/dsa3072-2.der").is_ok());
|
||||||
|
assert!(can_parse("test/ec384-1.der").is_ok());
|
||||||
|
assert!(can_parse("test/ec384-2.der").is_ok());
|
||||||
|
assert!(can_parse("test/ec384-3.der").is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
181
src/misc.rs
Normal file
181
src/misc.rs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
use error::X509ParseError;
|
||||||
|
use num::{BigInt,BigUint,One,ToPrimitive,Zero};
|
||||||
|
use num::bigint::ToBigInt;
|
||||||
|
use simple_asn1::{ASN1Block,ASN1Class,ASN1EncodeErr,FromASN1,ToASN1};
|
||||||
|
|
||||||
|
#[derive(Clone,Copy,Debug,PartialEq)]
|
||||||
|
pub enum X509Version { V1, V2, V3 }
|
||||||
|
|
||||||
|
fn decode_version(bs: &[ASN1Block])
|
||||||
|
-> Result<(X509Version,&[ASN1Block]),X509ParseError>
|
||||||
|
{
|
||||||
|
match bs.split_first() {
|
||||||
|
Some((&ASN1Block::Integer(_, _, ref v), rest)) => {
|
||||||
|
match v.to_u8() {
|
||||||
|
Some(0) => Ok((X509Version::V1, rest)),
|
||||||
|
Some(1) => Ok((X509Version::V2, rest)),
|
||||||
|
Some(2) => Ok((X509Version::V3, rest)),
|
||||||
|
_ => Ok((X509Version::V1, &bs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::NotEnoughData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromASN1 for X509Version {
|
||||||
|
type Error = X509ParseError;
|
||||||
|
|
||||||
|
fn from_asn1(v: &[ASN1Block])
|
||||||
|
-> Result<(X509Version,&[ASN1Block]),X509ParseError>
|
||||||
|
{
|
||||||
|
decode_version(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_version(c: ASN1Class, v: X509Version) -> Vec<ASN1Block> {
|
||||||
|
match v {
|
||||||
|
X509Version::V1 => {
|
||||||
|
let zero: BigInt = Zero::zero();
|
||||||
|
let block = ASN1Block::Integer(c, 0, zero);
|
||||||
|
vec![block]
|
||||||
|
}
|
||||||
|
X509Version::V2 => {
|
||||||
|
let one: BigInt = One::one();
|
||||||
|
let block = ASN1Block::Integer(c, 0, one);
|
||||||
|
vec![block]
|
||||||
|
}
|
||||||
|
X509Version::V3 => {
|
||||||
|
let two: BigInt = BigInt::from(2 as u64);
|
||||||
|
let block = ASN1Block::Integer(c, 0, two);
|
||||||
|
vec![block]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToASN1 for X509Version {
|
||||||
|
type Error = ASN1EncodeErr;
|
||||||
|
|
||||||
|
fn to_asn1_class(&self, c: ASN1Class)
|
||||||
|
-> Result<Vec<ASN1Block>,ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
Ok(encode_version(c, *self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
|
pub struct X509Serial {
|
||||||
|
num: BigUint
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_serial(x: &ASN1Block)
|
||||||
|
-> Result<X509Serial,X509ParseError>
|
||||||
|
{
|
||||||
|
match x {
|
||||||
|
&ASN1Block::Integer(_, _, ref v) => {
|
||||||
|
match v.to_biguint() {
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::IllFormedSerialNumber),
|
||||||
|
Some(n) =>
|
||||||
|
Ok(X509Serial{ num: n })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::NoSerialNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromASN1 for X509Serial {
|
||||||
|
type Error = X509ParseError;
|
||||||
|
|
||||||
|
fn from_asn1(v: &[ASN1Block])
|
||||||
|
-> Result<(X509Serial,&[ASN1Block]),X509ParseError>
|
||||||
|
{
|
||||||
|
match v.split_first() {
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::NoSerialNumber),
|
||||||
|
Some((x, rest)) => {
|
||||||
|
let v = decode_serial(x)?;
|
||||||
|
Ok((v, rest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SerialEncodeErr { ASN1Error(ASN1EncodeErr), InvalidSerialNumber }
|
||||||
|
|
||||||
|
impl From<ASN1EncodeErr> for SerialEncodeErr {
|
||||||
|
fn from(e: ASN1EncodeErr) -> SerialEncodeErr {
|
||||||
|
SerialEncodeErr::ASN1Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_serial(c: ASN1Class, serial: &X509Serial)
|
||||||
|
-> Result<ASN1Block,SerialEncodeErr>
|
||||||
|
{
|
||||||
|
match serial.num.to_bigint() {
|
||||||
|
None =>
|
||||||
|
Err(SerialEncodeErr::InvalidSerialNumber),
|
||||||
|
Some(n) =>
|
||||||
|
Ok(ASN1Block::Integer(c, 0, n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToASN1 for X509Serial {
|
||||||
|
type Error = SerialEncodeErr;
|
||||||
|
|
||||||
|
fn to_asn1_class(&self, c: ASN1Class)
|
||||||
|
-> Result<Vec<ASN1Block>,SerialEncodeErr>
|
||||||
|
{
|
||||||
|
let v = encode_serial(c, self)?;
|
||||||
|
Ok(vec![v])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use quickcheck::{Arbitrary,Gen};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn check_version_roundtrip(v: X509Version) {
|
||||||
|
let blocks = encode_version(ASN1Class::Universal, v);
|
||||||
|
match decode_version(&blocks) {
|
||||||
|
Err(_) =>
|
||||||
|
assert!(false),
|
||||||
|
Ok((v2,_)) =>
|
||||||
|
assert_eq!(v, v2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn versions_roundtrip() {
|
||||||
|
check_version_roundtrip(X509Version::V1);
|
||||||
|
check_version_roundtrip(X509Version::V2);
|
||||||
|
check_version_roundtrip(X509Version::V3);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for X509Serial {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> X509Serial {
|
||||||
|
let count = g.gen_range::<usize>(0,16);
|
||||||
|
let bits = g.gen_iter::<u32>().take(count).collect();
|
||||||
|
let val = BigUint::new(bits);
|
||||||
|
X509Serial{ num: val }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quickcheck! {
|
||||||
|
fn serial_roundtrips(s: X509Serial) -> bool {
|
||||||
|
match encode_serial(ASN1Class::Universal, &s) {
|
||||||
|
Err(_) => false,
|
||||||
|
Ok(block) =>
|
||||||
|
match decode_serial(&block) {
|
||||||
|
Err(_) => false,
|
||||||
|
Ok(s2) => s == s2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/name.rs
Normal file
137
src/name.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
use num::BigUint;
|
||||||
|
use simple_asn1::{ASN1Block,ASN1Class,ASN1EncodeErr,FromASN1,OID,ToASN1};
|
||||||
|
|
||||||
|
use error::X509ParseError;
|
||||||
|
|
||||||
|
#[derive(Copy,Clone,Debug,Eq,Hash,PartialEq)]
|
||||||
|
pub enum X520Name {
|
||||||
|
Name, Surname, GivenName, Initials, GenerationQualifier, CommonName,
|
||||||
|
LocalityName, StateOrProvinceName, OrganizationName, OrganizationalUnit,
|
||||||
|
Title, DNQualifier, CountryName, SerialNumber, Pseudonym, DomainComponent,
|
||||||
|
EmailAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromASN1 for X520Name {
|
||||||
|
type Error = X509ParseError;
|
||||||
|
|
||||||
|
fn from_asn1(v: &[ASN1Block])
|
||||||
|
-> Result<(X520Name,&[ASN1Block]),X509ParseError>
|
||||||
|
{
|
||||||
|
match v.split_first() {
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::NotEnoughData),
|
||||||
|
Some((x,rest)) => {
|
||||||
|
let name = decode_name(&x)?;
|
||||||
|
Ok((name,rest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_name(val: &ASN1Block)
|
||||||
|
-> Result<X520Name,X509ParseError>
|
||||||
|
{
|
||||||
|
match val {
|
||||||
|
&ASN1Block::ObjectIdentifier(_, _, ref oid) => {
|
||||||
|
if oid == oid!(2,5,4,41) {return Ok(X520Name::Name) }
|
||||||
|
if oid == oid!(2,5,4,4) {return Ok(X520Name::Surname) }
|
||||||
|
if oid == oid!(2,5,4,42) {return Ok(X520Name::GivenName) }
|
||||||
|
if oid == oid!(2,5,4,43) {return Ok(X520Name::Initials) }
|
||||||
|
if oid == oid!(2,5,4,44) {return Ok(X520Name::GenerationQualifier)}
|
||||||
|
if oid == oid!(2,5,4,3) {return Ok(X520Name::CommonName) }
|
||||||
|
if oid == oid!(2,5,4,7) {return Ok(X520Name::LocalityName) }
|
||||||
|
if oid == oid!(2,5,4,8) {return Ok(X520Name::StateOrProvinceName)}
|
||||||
|
if oid == oid!(2,5,4,10) {return Ok(X520Name::OrganizationName) }
|
||||||
|
if oid == oid!(2,5,4,11) {return Ok(X520Name::OrganizationalUnit) }
|
||||||
|
if oid == oid!(2,5,4,12) {return Ok(X520Name::Title) }
|
||||||
|
if oid == oid!(2,5,4,46) {return Ok(X520Name::DNQualifier) }
|
||||||
|
if oid == oid!(2,5,4,6) {return Ok(X520Name::CountryName) }
|
||||||
|
if oid == oid!(2,5,4,5) {return Ok(X520Name::SerialNumber) }
|
||||||
|
if oid == oid!(2,5,4,65) {return Ok(X520Name::Pseudonym) }
|
||||||
|
if oid == oid!(0,9,2342,19200300,100,1,25) {
|
||||||
|
return Ok(X520Name::DomainComponent);
|
||||||
|
}
|
||||||
|
if oid == oid!(1,2,840,113549,1,9,1) {
|
||||||
|
return Ok(X520Name::EmailAddress);
|
||||||
|
}
|
||||||
|
Err(X509ParseError::IllFormedName)
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllFormedName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToASN1 for X520Name {
|
||||||
|
type Error = ASN1EncodeErr;
|
||||||
|
|
||||||
|
fn to_asn1_class(&self, c: ASN1Class)
|
||||||
|
-> Result<Vec<ASN1Block>,ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let block = encode_name(c, *self);
|
||||||
|
Ok(vec![block])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_name(class: ASN1Class, name: X520Name)
|
||||||
|
-> ASN1Block
|
||||||
|
{
|
||||||
|
let oid = match name {
|
||||||
|
X520Name::Name => oid!(2,5,4,41),
|
||||||
|
X520Name::Surname => oid!(2,5,4,4),
|
||||||
|
X520Name::GivenName => oid!(2,5,4,42),
|
||||||
|
X520Name::Initials => oid!(2,5,4,43),
|
||||||
|
X520Name::GenerationQualifier => oid!(2,5,4,44),
|
||||||
|
X520Name::CommonName => oid!(2,5,4,3),
|
||||||
|
X520Name::LocalityName => oid!(2,5,4,7),
|
||||||
|
X520Name::StateOrProvinceName => oid!(2,5,4,8),
|
||||||
|
X520Name::OrganizationName => oid!(2,5,4,10),
|
||||||
|
X520Name::OrganizationalUnit => oid!(2,5,4,11),
|
||||||
|
X520Name::Title => oid!(2,5,4,12),
|
||||||
|
X520Name::DNQualifier => oid!(2,5,4,46),
|
||||||
|
X520Name::CountryName => oid!(2,5,4,6),
|
||||||
|
X520Name::SerialNumber => oid!(2,5,4,5),
|
||||||
|
X520Name::Pseudonym => oid!(2,5,4,65),
|
||||||
|
X520Name::DomainComponent => oid!(0,9,2342,19200300,100,1,25),
|
||||||
|
X520Name::EmailAddress => oid!(1,2,840,113549,1,9,1)
|
||||||
|
};
|
||||||
|
|
||||||
|
ASN1Block::ObjectIdentifier(class, 0, oid)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn encdec_test(n: X520Name) {
|
||||||
|
let block = encode_name(ASN1Class::Universal, n);
|
||||||
|
let vec = vec![block];
|
||||||
|
match X520Name::from_asn1(&vec) {
|
||||||
|
Err(_) =>
|
||||||
|
assert!(false),
|
||||||
|
Ok((m, _)) =>
|
||||||
|
assert_eq!(n,m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn name_encoding_roundtrips() {
|
||||||
|
encdec_test(X520Name::Name);
|
||||||
|
encdec_test(X520Name::Surname);
|
||||||
|
encdec_test(X520Name::GivenName);
|
||||||
|
encdec_test(X520Name::Initials);
|
||||||
|
encdec_test(X520Name::GenerationQualifier);
|
||||||
|
encdec_test(X520Name::CommonName);
|
||||||
|
encdec_test(X520Name::LocalityName);
|
||||||
|
encdec_test(X520Name::StateOrProvinceName);
|
||||||
|
encdec_test(X520Name::OrganizationName);
|
||||||
|
encdec_test(X520Name::OrganizationalUnit);
|
||||||
|
encdec_test(X520Name::Title);
|
||||||
|
encdec_test(X520Name::DNQualifier);
|
||||||
|
encdec_test(X520Name::CountryName);
|
||||||
|
encdec_test(X520Name::SerialNumber);
|
||||||
|
encdec_test(X520Name::Pseudonym);
|
||||||
|
encdec_test(X520Name::DomainComponent);
|
||||||
|
encdec_test(X520Name::EmailAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
245
src/publickey.rs
Normal file
245
src/publickey.rs
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
use algident::SigAlgEncodeError;
|
||||||
|
use error::X509ParseError;
|
||||||
|
use num::BigUint;
|
||||||
|
use num::bigint::ToBigInt;
|
||||||
|
use simple_asn1::{ASN1Block,ASN1Class,ASN1EncodeErr,FromASN1,OID,ToASN1,
|
||||||
|
der_decode,der_encode,from_der,to_der};
|
||||||
|
use simple_dsa::{DSAParameterSize,DSAParameters,DSAPublicKey};
|
||||||
|
use simple_rsa::RSAPublicKey;
|
||||||
|
|
||||||
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
|
pub enum X509PublicKey {
|
||||||
|
DSA(DSAPublicKey),
|
||||||
|
RSA(RSAPublicKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromASN1 for X509PublicKey {
|
||||||
|
type Error = X509ParseError;
|
||||||
|
|
||||||
|
fn from_asn1(bs: &[ASN1Block])
|
||||||
|
-> Result<(X509PublicKey, &[ASN1Block]),X509ParseError>
|
||||||
|
{
|
||||||
|
match bs.split_first() {
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::KeyNotFound),
|
||||||
|
Some((x, rest)) => {
|
||||||
|
println!("key info: {:?}", x);
|
||||||
|
let v = decode_public_key(&x)?;
|
||||||
|
Ok((v, rest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToASN1 for X509PublicKey {
|
||||||
|
type Error = ASN1EncodeErr;
|
||||||
|
|
||||||
|
fn to_asn1_class(&self, c: ASN1Class)
|
||||||
|
-> Result<Vec<ASN1Block>,ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let block = encode_public_key(c, self)?;
|
||||||
|
Ok(vec![block])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_public_key(block: &ASN1Block)
|
||||||
|
-> Result<X509PublicKey,X509ParseError>
|
||||||
|
{
|
||||||
|
// SubjectPublicKeyInfo ::= SEQUENCE {
|
||||||
|
// algorithm AlgorithmIdentifier,
|
||||||
|
// subjectPublicKey BIT STRING }
|
||||||
|
match block {
|
||||||
|
&ASN1Block::Sequence(_, _, ref info) => {
|
||||||
|
let (id, alginfo) = strip_algident(&info[0])?;
|
||||||
|
|
||||||
|
if id == oid!(1,2,840,113549,1,1,1) {
|
||||||
|
let key = decode_rsa_key(&info[1])?;
|
||||||
|
return Ok(X509PublicKey::RSA(key));
|
||||||
|
}
|
||||||
|
if id == oid!(1,2,840,10040,4,1) {
|
||||||
|
let params = decode_dsa_info(&alginfo)?;
|
||||||
|
let key = decode_dsa_key(&info[1], ¶ms)?;
|
||||||
|
return Ok(X509PublicKey::DSA(key));
|
||||||
|
}
|
||||||
|
Err(X509ParseError::IllFormedKey)
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllFormedKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_algident(block: &ASN1Block)
|
||||||
|
-> Result<(OID,ASN1Block),X509ParseError>
|
||||||
|
{
|
||||||
|
match block {
|
||||||
|
&ASN1Block::Sequence(_, _, ref v) if v.len() == 2 => {
|
||||||
|
match v[0] {
|
||||||
|
ASN1Block::ObjectIdentifier(_, _, ref oid) => {
|
||||||
|
Ok((oid.clone(), v[1].clone()))
|
||||||
|
}
|
||||||
|
_ => Err(X509ParseError::IllFormedAlgoInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllFormedAlgoInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_public_key(c: ASN1Class, key: &X509PublicKey)
|
||||||
|
-> Result<ASN1Block, ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
match key {
|
||||||
|
&X509PublicKey::RSA(ref rsa) => encode_rsa_pubkey(c, rsa),
|
||||||
|
&X509PublicKey::DSA(ref dsa) => encode_dsa_pubkey(c, dsa)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_rsa_pubkey(c: ASN1Class, key: &RSAPublicKey)
|
||||||
|
-> Result<ASN1Block, ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let objoid = ASN1Block::ObjectIdentifier(c, 0, oid!(1,2,840,113549,1,1,1));
|
||||||
|
let objkey = encode_rsa_key(c, key)?;
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![objoid, objkey]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_dsa_pubkey(c: ASN1Class, key: &DSAPublicKey)
|
||||||
|
-> Result<ASN1Block, ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let objoid = ASN1Block::ObjectIdentifier(c, 0, oid!(1,2,840,10040,4,1));
|
||||||
|
let objkey = encode_dsa_key(c, key)?;
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vec![objoid, objkey]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_rsa_key(c: ASN1Class, k: &RSAPublicKey)
|
||||||
|
-> Result<ASN1Block, ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let bstr = der_encode(k)?;
|
||||||
|
Ok(ASN1Block::BitString(c, 0, bstr.len() * 8, bstr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_rsa_key(b: &ASN1Block) -> Result<RSAPublicKey, X509ParseError> {
|
||||||
|
match b {
|
||||||
|
&ASN1Block::BitString(_, _, size, ref vec) if size % 8 == 0 => {
|
||||||
|
der_decode(vec).map_err(|x| X509ParseError::from(x))
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::InvalidRSAKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_dsa_key(c: ASN1Class, k: &DSAPublicKey)
|
||||||
|
-> Result<ASN1Block, ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_dsa_key(b: &ASN1Block, params: &DSAParameters)
|
||||||
|
-> Result<DSAPublicKey, X509ParseError>
|
||||||
|
{
|
||||||
|
match b {
|
||||||
|
&ASN1Block::BitString(_, _, size, ref vec) if size % 8 == 0 => {
|
||||||
|
let vals = from_der(&vec)?;
|
||||||
|
match vals.first() {
|
||||||
|
Some(&ASN1Block::Integer(_, _, ref val)) => {
|
||||||
|
match val.to_biguint() {
|
||||||
|
Some(y) => {
|
||||||
|
Ok(DSAPublicKey::new(params, y))
|
||||||
|
}
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::InvalidDSAKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::InvalidDSAKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::InvalidRSAKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_dsa_info(v: &ASN1Block)
|
||||||
|
-> Result<DSAParameters, X509ParseError>
|
||||||
|
{
|
||||||
|
match v {
|
||||||
|
&ASN1Block::Sequence(_, _, ref info) => {
|
||||||
|
let p = decode_biguint(&info[0])?;
|
||||||
|
let q = decode_biguint(&info[1])?;
|
||||||
|
let g = decode_biguint(&info[2])?;
|
||||||
|
DSAParameters::new(p, g, q).map_err(|x| X509ParseError::from(x))
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::InvalidDSAInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_dsa_info(c: ASN1Class, params: &DSAParameters)
|
||||||
|
-> Result<ASN1Block,SigAlgEncodeError>
|
||||||
|
{
|
||||||
|
match (params.p.to_bigint(), params.q.to_bigint(), params.g.to_bigint()) {
|
||||||
|
(Some(pbs), Some(qbs), Some(gbs)) => {
|
||||||
|
let pb = ASN1Block::Integer(c, 0, pbs);
|
||||||
|
let qb = ASN1Block::Integer(c, 0, qbs);
|
||||||
|
let gb = ASN1Block::Integer(c, 0, gbs);
|
||||||
|
let vs = vec![pb, qb, gb];
|
||||||
|
Ok(ASN1Block::Sequence(c, 0, vs))
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(SigAlgEncodeError::InvalidDSAValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_biguint(b: &ASN1Block) -> Result<BigUint,X509ParseError> {
|
||||||
|
match b {
|
||||||
|
&ASN1Block::Integer(_, _, ref v) => {
|
||||||
|
match v.to_biguint() {
|
||||||
|
Some(sn) => Ok(sn),
|
||||||
|
_ => Err(X509ParseError::InvalidDSAInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::InvalidDSAInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use simple_dsa::DSAKeyPair;
|
||||||
|
use simple_rsa::RSAKeyPair;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const NUM_TESTS: usize = 1;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rsa_public_key_tests() {
|
||||||
|
for _ in 0..NUM_TESTS {
|
||||||
|
let pair = RSAKeyPair::generate(2048).unwrap();
|
||||||
|
let public = pair.public;
|
||||||
|
let block = encode_rsa_key(ASN1Class::Universal, &public).unwrap();
|
||||||
|
let public2 = decode_rsa_key(&block).unwrap();
|
||||||
|
assert_eq!(public, public2);
|
||||||
|
let x509public = X509PublicKey::RSA(public);
|
||||||
|
let block2 = encode_public_key(ASN1Class::Universal, &x509public).unwrap();
|
||||||
|
let x509public2 = decode_public_key(&block2).unwrap();
|
||||||
|
assert_eq!(x509public, x509public2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dsa_public_key_tests() {
|
||||||
|
for _ in 0..NUM_TESTS {
|
||||||
|
let params = DSAParameters::generate(DSAParameterSize::L1024N160).unwrap();
|
||||||
|
let pair = DSAKeyPair::generate_w_params(¶ms).unwrap();
|
||||||
|
let public = pair.public;
|
||||||
|
let block = encode_dsa_key(ASN1Class::Universal, &public).unwrap();
|
||||||
|
let public2 = decode_dsa_key(&block, ¶ms).unwrap();
|
||||||
|
assert_eq!(public, public2);
|
||||||
|
let x509public = X509PublicKey::DSA(public);
|
||||||
|
let block2 = encode_public_key(ASN1Class::Universal, &x509public).unwrap();
|
||||||
|
let x509public2 = decode_public_key(&block2).unwrap();
|
||||||
|
assert_eq!(x509public, x509public2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
src/validity.rs
Normal file
117
src/validity.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use chrono::{DateTime,Utc};
|
||||||
|
use error::X509ParseError;
|
||||||
|
use simple_asn1::{ASN1Block,ASN1Class,ASN1EncodeErr,FromASN1,ToASN1};
|
||||||
|
|
||||||
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
|
pub struct Validity {
|
||||||
|
not_before: DateTime<Utc>,
|
||||||
|
not_after: DateTime<Utc>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_validity_data(bs: &ASN1Block) -> Result<Validity,X509ParseError> {
|
||||||
|
// Validity ::= SEQUENCE {
|
||||||
|
// notBefore Time,
|
||||||
|
// notAfter Time }
|
||||||
|
match bs {
|
||||||
|
&ASN1Block::Sequence(_, _, ref valxs) => {
|
||||||
|
if valxs.len() != 2 {
|
||||||
|
return Err(X509ParseError::IllFormedValidity);
|
||||||
|
}
|
||||||
|
let nb = get_time(&valxs[0])?;
|
||||||
|
let na = get_time(&valxs[1])?;
|
||||||
|
Ok(Validity{ not_before: nb, not_after: na })
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllFormedValidity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromASN1 for Validity {
|
||||||
|
type Error = X509ParseError;
|
||||||
|
|
||||||
|
fn from_asn1(v: &[ASN1Block])
|
||||||
|
-> Result<(Validity,&[ASN1Block]),X509ParseError>
|
||||||
|
{
|
||||||
|
match v.split_first() {
|
||||||
|
None =>
|
||||||
|
Err(X509ParseError::NotEnoughData),
|
||||||
|
Some((x, rest)) => {
|
||||||
|
let v = decode_validity_data(&x)?;
|
||||||
|
Ok((v, rest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_validity_data(c: ASN1Class, v: &Validity) -> ASN1Block {
|
||||||
|
let mut vs = Vec::with_capacity(2);
|
||||||
|
vs.push(ASN1Block::GeneralizedTime(c, 0, v.not_before));
|
||||||
|
vs.push(ASN1Block::GeneralizedTime(c, 0, v.not_after));
|
||||||
|
ASN1Block::Sequence(c, 0, vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToASN1 for Validity {
|
||||||
|
type Error = ASN1EncodeErr;
|
||||||
|
|
||||||
|
fn to_asn1_class(&self, c: ASN1Class)
|
||||||
|
-> Result<Vec<ASN1Block>,ASN1EncodeErr>
|
||||||
|
{
|
||||||
|
let block = encode_validity_data(c, self);
|
||||||
|
Ok(vec![block])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_time(b: &ASN1Block) -> Result<DateTime<Utc>, X509ParseError> {
|
||||||
|
match b {
|
||||||
|
&ASN1Block::UTCTime(_, _, v) => Ok(v.clone()),
|
||||||
|
&ASN1Block::GeneralizedTime(_, _, v) => Ok(v.clone()),
|
||||||
|
_ =>
|
||||||
|
Err(X509ParseError::IllFormedValidity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use chrono::TimeZone;
|
||||||
|
use chrono::offset::LocalResult;
|
||||||
|
use quickcheck::{Arbitrary,Gen};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn arbitrary_date<G: Gen>(g: &mut G) -> DateTime<Utc> {
|
||||||
|
loop {
|
||||||
|
let y = g.gen_range::<i32>(1900,3000);
|
||||||
|
let m = g.gen_range::<u32>(0,12);
|
||||||
|
let d = g.gen_range::<u32>(0,31);
|
||||||
|
let h = g.gen_range::<u32>(0,24);
|
||||||
|
let m = g.gen_range::<u32>(0,60);
|
||||||
|
let s = g.gen_range::<u32>(0,60);
|
||||||
|
match Utc.ymd_opt(y,m,d).and_hms_opt(h,m,s) {
|
||||||
|
LocalResult::None =>
|
||||||
|
continue,
|
||||||
|
LocalResult::Single(x) =>
|
||||||
|
return x,
|
||||||
|
LocalResult::Ambiguous(x,_) =>
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for Validity {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> Validity {
|
||||||
|
Validity {
|
||||||
|
not_before: arbitrary_date(g),
|
||||||
|
not_after: arbitrary_date(g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quickcheck! {
|
||||||
|
fn validity_roundtrips(v: Validity) -> bool {
|
||||||
|
let bstr = encode_validity_data(ASN1Class::Universal, &v);
|
||||||
|
match decode_validity_data(&bstr) {
|
||||||
|
Err(_) => false,
|
||||||
|
Ok(v2) => v == v2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
test/dsa2048-1.der
Normal file
BIN
test/dsa2048-1.der
Normal file
Binary file not shown.
BIN
test/dsa2048-2.der
Normal file
BIN
test/dsa2048-2.der
Normal file
Binary file not shown.
BIN
test/dsa3072-1.der
Normal file
BIN
test/dsa3072-1.der
Normal file
Binary file not shown.
BIN
test/dsa3072-2.der
Normal file
BIN
test/dsa3072-2.der
Normal file
Binary file not shown.
BIN
test/ec384-1.der
Normal file
BIN
test/ec384-1.der
Normal file
Binary file not shown.
BIN
test/ec384-2.der
Normal file
BIN
test/ec384-2.der
Normal file
Binary file not shown.
BIN
test/ec384-3.der
Normal file
BIN
test/ec384-3.der
Normal file
Binary file not shown.
Reference in New Issue
Block a user