Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

verify certificate #36

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.0
- **FEAT** add verify capability to certificate
## 0.2.4+3
- **FEAT** let any ASN1 object be exported by asn1ToPem
## 0.2.4+2

- **DOCS**: fix code sample (pull request [#29](https://github.com/appsup-dart/x509/issues/29) from bivens-dev/patch-1). ([14a3a777](https://github.com/appsup-dart/x509/commit/14a3a777afae604e293ee18eb34629c3ef875862))
Expand Down
77 changes: 74 additions & 3 deletions lib/src/certificate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,66 @@ class X509Certificate implements Certificate {
buffer.writeln(toHexString(toBigInt(signatureValue!), '$prefix\t\t', 18));
return buffer.toString();
}
/// return a signature from signatureValue
Signature get signature {
ObjectIdentifier name = signatureAlgorithm.algorithm;
if (name.parent == ObjectIdentifier([1,2,840,10045,4,3])) { // ECDSA with SHA*
var sig = ASN1Sequence.fromBytes(Uint8List.fromList(signatureValue!)); // x509 DER of r + s
var r = (sig.elements[0] as ASN1Integer);
var rb = r.valueBytes();
if (rb[0] == 0) {
rb = rb.sublist(1);
}
var s = (sig.elements[1] as ASN1Integer);
var sb = s.valueBytes();
if (sb[0] == 0) {
sb = sb.sublist(1);
}
Uint8List signature = Uint8List.fromList(rb+sb);
return Signature(signature);
} else {
return Signature(Uint8List.fromList(signatureValue!));
}
}

bool verify(PublicKey publicKey, {bool checkDates=true}) {
if(checkDates) {
var validity = tbsCertificate.validity;
if (validity?.notAfter.isBefore(DateTime.now()) == true) {
return false;
}
if (validity?.notBefore.isAfter(DateTime.now()) == true) {
return false;
}
}
final bytes = tbsCertificate.toAsn1().encodedBytes;
// signatureAlgorithm.algorithm.parent
final name = signatureAlgorithm.algorithm.name;
Identifier algo = _algorithmFromName(name);
final verifier = publicKey.createVerifier(algo);
return verifier.verify(bytes, signature);
}
}

/// try to get the Identifier from the given name
/// throws an Exception if name did not match a supported algorithm
Identifier _algorithmFromName(String name) {
switch (name) {
case 'ecdsa-with-SHA256':
return algorithms.signing.ecdsa.sha256;
case 'ecdsa-with-SHA384':
return algorithms.signing.ecdsa.sha384;
case 'ecdsa-with-SHA512':
return algorithms.signing.ecdsa.sha512;
case 'sha256WithRSAEncryption':
return algorithms.signing.rsa.sha256;
case 'sha384WithRSAEncryption':
return algorithms.signing.rsa.sha384;
case 'sha512WithRSAEncryption':
return algorithms.signing.rsa.sha512;
default:
throw Exception('unsupported signature algorithm $name');
}
}

/// An unsigned (To-Be-Signed) certificate.
Expand Down Expand Up @@ -155,6 +215,7 @@ class TbsCertificate {
.elements
.map((v) => Extension.fromAsn1(v as ASN1Sequence))
.toList();
break;
}
}
}
Expand Down Expand Up @@ -194,9 +255,19 @@ class TbsCertificate {
..add(subjectPublicKeyInfo!.toAsn1());
if (version! > 1) {
if (issuerUniqueID != null) {
// TODO
// var iuid = ASN1BitString.fromBytes(issuerUniqueID);
//ASN1Object.preEncoded(tag, valBytes)
var iuid = ASN1BitString.fromBytes(Uint8List.fromList(issuerUniqueID!));
seq.add(ASN1Object.preEncoded(0x1f | 1 << 6, iuid.encodedBytes));
}
if (subjectUniqueID != null) {
var suid = ASN1BitString.fromBytes(Uint8List.fromList(subjectUniqueID!));
seq.add(ASN1Object.preEncoded(0x1f | 2 << 6, suid.encodedBytes));
}
if (extensions != null) {
var exSeq = ASN1Sequence();
for (var ex in extensions!) {
exSeq.add(ex.toAsn1());
}
seq.add(ASN1Object.preEncoded(0xa3, exSeq.encodedBytes));
}
}
return seq;
Expand Down
27 changes: 23 additions & 4 deletions lib/src/extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ class Extension {

/// The extension's value.
final ExtensionValue extnValue;
ASN1Sequence? _backup;

const Extension(
{required this.extnId, this.isCritical = false, required this.extnValue});
Extension({
required this.extnId,
this.isCritical = false,
required this.extnValue,
ASN1Sequence? backup})
: _backup = backup;

/// Creates a Extension from an [ASN1Sequence].
///
Expand All @@ -40,7 +45,6 @@ class Extension {
critical = toDart(sequence.elements[1]);
octetIndex = 2;
}

var value = ExtensionValue.fromAsn1(
ASN1Parser(sequence.elements[octetIndex].contentBytes()).nextObject(),
id,
Expand All @@ -49,8 +53,19 @@ class Extension {
throw UnimplementedError(
'Cannot handle critical extension $id (${id.parent} ${id.nodes.last})');
}
return Extension(extnId: id, isCritical: critical, extnValue: value, backup: sequence);
}

return Extension(extnId: id, isCritical: critical, extnValue: value);
ASN1Sequence toAsn1() {
if (_backup != null) {
return _backup!;
}
var seq = ASN1Sequence();
seq.add(fromDart(extnId));
if (isCritical) {
seq.add(fromDart(isCritical));
}
return seq;
}

@override
Expand All @@ -60,6 +75,7 @@ class Extension {
buffer.writeln('$prefix\t$extnValue');
return buffer.toString();
}

}

/// The base class for extension values.
Expand Down Expand Up @@ -158,9 +174,12 @@ class AuthorityKeyIdentifier extends ExtensionValue {
issuer = GeneralNames.fromAsn1(o);
break;
case 2:
var old = o.encodedBytes[0];
number =
(ASN1Parser(o.encodedBytes..[0] = 2).nextObject() as ASN1Integer)
.valueAsBigInteger;
o.encodedBytes[0] = old;

}
}
return AuthorityKeyIdentifier(keyId, issuer, number);
Expand Down
55 changes: 21 additions & 34 deletions lib/src/objectidentifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,11 @@ class ObjectIdentifier {
: null;

factory ObjectIdentifier.fromAsn1(ASN1ObjectIdentifier id) {
var bytes = id.valueBytes();
var nodes = <int>[];
var v = bytes.first;
nodes.add(v ~/ 40);
nodes.add(v % 40);

var w = 0;
for (var v in bytes.skip(1)) {
if (v >= 128) {
w += v - 128;
w *= 128;
} else {
w += v;
nodes.add(w);
w = 0;
}
}

return ObjectIdentifier(nodes);
return ObjectIdentifier(id.oi);
}

ASN1ObjectIdentifier toAsn1() {
var bytes = <int>[];
bytes.add(nodes.first * 40 + nodes[1]);
for (var v in nodes.skip(2)) {
var w = [];
while (v > 128) {
var u = v % 128;
v -= u;
v ~/= 128;
w.add(u);
}
w.add(v);
bytes.addAll(w.skip(1).toList().reversed.map((v) => v + 128));
bytes.add(w.first);
}
return ASN1ObjectIdentifier(bytes);
return ASN1ObjectIdentifier.fromComponents(nodes);
}

@override
Expand Down Expand Up @@ -166,6 +134,16 @@ class ObjectIdentifier {
6: 'prime239v3',
7: 'prime256v1',
}
},
4: {
null: 'signatures',
3: {
null: 'ecdsa-with-SHA2',
1: 'ecdsa-with-SHA224',
2: 'ecdsa-with-SHA256',
3: 'ecdsa-with-SHA384',
4: 'ecdsa-with-SHA512',
}
}
},
113549: {
Expand Down Expand Up @@ -416,6 +394,15 @@ class ObjectIdentifier {
}
}
},
101: {
null: 'thawte',
110: 'id-X25519',
111: 'id-X448',
112: 'id-Ed25519',
113: 'id-Ed448',
114: 'id-EdDSA25519-ph',
115: 'id-EdDS448-ph',
},
132: {
null: 'certicom',
0: {
Expand Down
21 changes: 18 additions & 3 deletions lib/src/request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,22 @@ class CertificationRequest {
return CertificationRequest(
CertificationRequestInfo.fromAsn1(sequence.elements[0] as ASN1Sequence),
algorithm,
(sequence.elements[2] as ASN1BitString).contentBytes());
toDart(sequence.elements[2]));
}

ASN1Sequence toAsn1() {
return ASN1Sequence()
..add(certificationRequestInfo.toAsn1())
..add(signatureAlgorithm.toAsn1())
..add(fromDart(signature));
}
}

class CertificationRequestInfo {
final int? version;
final Name subject;
final SubjectPublicKeyInfo subjectPublicKeyInfo;
final Map<String, List<dynamic>>? attributes;
final ASN1Object attributes;

CertificationRequestInfo(
this.version, this.subject, this.subjectPublicKeyInfo, this.attributes);
Expand All @@ -43,6 +50,14 @@ class CertificationRequestInfo {
toDart(sequence.elements[0]).toInt() + 1,
Name.fromAsn1(sequence.elements[1] as ASN1Sequence),
SubjectPublicKeyInfo.fromAsn1(sequence.elements[2] as ASN1Sequence),
null /*TODO*/);
sequence.elements[3]);
}

ASN1Sequence toAsn1() {
return ASN1Sequence()
..add(fromDart((version ?? 1) - 1))
..add(subject.toAsn1())
..add(subjectPublicKeyInfo.toAsn1())
..add(attributes);
}
}
50 changes: 46 additions & 4 deletions lib/src/util.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
library x509.conversions;

import 'dart:convert';
import 'dart:typed_data';

import 'package:asn1lib/asn1lib.dart';

import '../x509.dart';

class TypedString {
final String data;
final int asn1Type;
const TypedString(this.data, this.asn1Type);

@override
String toString() {
return data;
}
ASN1Object toAsn1() {
if (asn1Type == UTF8_STRING_TYPE) {
return ASN1UTF8String(data);
} else if (asn1Type == PRINTABLE_STRING_TYPE){
return ASN1PrintableString(data);
} else {
throw Exception('unknown type ${asn1Type.toRadixString(16)}');
}
}
}

ObjectIdentifier? _ecParametersFromAsn1(ASN1Object object) {
// https://tools.ietf.org/html/rfc5480#section-2.1.1
// ECParameters ::= CHOICE {
Expand Down Expand Up @@ -159,7 +180,7 @@ PublicKey publicKeyFromAsn1(ASN1BitString data, AlgorithmIdentifier algorithm) {
curve: _curveObjectIdentifierToIdentifier(algorithm.parameters));
case 'sha1WithRSAEncryption':
}
throw UnimplementedError('Unknown algoritmh $algorithm');
throw UnimplementedError('Unknown algorithm $algorithm');
}

String keyToString(Key key, [String prefix = '']) {
Expand All @@ -174,12 +195,28 @@ String keyToString(Key key, [String prefix = '']) {
return '$prefix$key';
}

void _addBigIntToList(BigInt bInt, List<int> list) {
var bIntList = ASN1Integer.encodeBigInt(bInt);

if (bIntList.first == 0) {
list.addAll(Uint8List.fromList(bIntList.sublist(1)));
} else {
list.addAll(Uint8List.fromList(bIntList));
}
}

ASN1BitString keyToAsn1(Key key) {
var s = ASN1Sequence();
if (key is RsaPublicKey) {
s
..add(ASN1Integer(key.modulus))
..add(ASN1Integer(key.exponent));
} else if (key is EcPublicKey) {
var list = <int>[];
list.add(4); //uncompressed
_addBigIntToList(key.xCoordinate, list);
_addBigIntToList(key.yCoordinate, list);
return ASN1BitString(Uint8List.fromList(list));
}
return ASN1BitString(s.encodedBytes);
}
Expand Down Expand Up @@ -230,7 +267,8 @@ ASN1Object fromDart(dynamic obj) {
if (obj is int) return ASN1Integer(BigInt.from(obj));
if (obj is ObjectIdentifier) return obj.toAsn1();
if (obj is bool) return ASN1Boolean(obj);
if (obj is String) return ASN1PrintableString(obj);
if (obj is String) return ASN1UTF8String(obj);
if (obj is TypedString) return obj.toAsn1();
if (obj is DateTime) return ASN1UtcTime(obj);

throw ArgumentError.value(obj, 'obj', 'cannot be encoded as ASN1Object');
Expand All @@ -245,11 +283,15 @@ dynamic toDart(ASN1Object obj) {
if (obj is ASN1BitString) return obj.stringValue;
if (obj is ASN1Boolean) return obj.booleanValue;
if (obj is ASN1OctetString) return obj.stringValue;
if (obj is ASN1PrintableString) return obj.stringValue;
if (obj is ASN1PrintableString) {
return TypedString(obj.stringValue, PRINTABLE_STRING_TYPE);
}
if (obj is ASN1UtcTime) return obj.dateTimeValue;
if (obj is ASN1GeneralizedTime) return obj.dateTimeValue;
if (obj is ASN1IA5String) return obj.stringValue;
if (obj is ASN1UTF8String) return obj.utf8StringValue;
if (obj is ASN1UTF8String) {
return TypedString(obj.utf8StringValue, UTF8_STRING_TYPE);
}

// ASN.1 Identifier format is below:
// | 7 | 6 | 5 | 4| 3| 2|1|0|
Expand Down
Loading