Skip to content

Commit

Permalink
Merge pull request #107 from arj-singh/master
Browse files Browse the repository at this point in the history
Basic Constraints extension
  • Loading branch information
Ephenodrom authored Nov 23, 2023
2 parents 6a50349 + b164851 commit acdaedf
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 3 deletions.
65 changes: 64 additions & 1 deletion lib/src/X509Utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ class X509Utils {
/// * [sans] = Subject alternative names to place within the certificate
/// * [keyUsage] = The key usage definition extension
/// * [extKeyUsage] = The extended key usage definition
/// * [cA] = The cA boolean of the basic constraints extension, which indicates whether the certificate is a CA
/// * [pathLenConstraint] = The pathLenConstraint field of the basic constraints extension. This is ignored if cA is null or false, or if pathLenConstraint is less than 0.
/// * [serialNumber] = The serialnumber. If not set the default will be 1.
/// * [issuer] = The issuer. If null, the issuer will be the subject of the given csr.
/// * [notBefore] = The Timestamp after when the certificate is valid. If null, this will be [DateTime.now].
Expand All @@ -302,6 +304,8 @@ class X509Utils {
List<String>? sans,
List<KeyUsage>? keyUsage,
List<ExtendedKeyUsage>? extKeyUsage,
bool? cA,
int? pathLenConstraint,
String serialNumber = '1',
Map<String, String>? issuer,
DateTime? notBefore,
Expand Down Expand Up @@ -397,7 +401,8 @@ class X509Utils {
// Add Extensions
if (IterableUtils.isNotNullOrEmpty(sans) ||
IterableUtils.isNotNullOrEmpty(keyUsage) ||
IterableUtils.isNotNullOrEmpty(extKeyUsage)) {
IterableUtils.isNotNullOrEmpty(extKeyUsage) ||
cA != null) {
var extensionTopSequence = ASN1Sequence();

if (IterableUtils.isNotNullOrEmpty(keyUsage)) {
Expand Down Expand Up @@ -484,6 +489,30 @@ class X509Utils {
extensionTopSequence.add(sanSequence);
}

if (cA != null) {
var basicConstraintsSequence = ASN1Sequence();

basicConstraintsSequence
.add(ASN1ObjectIdentifier.fromIdentifierString("2.5.29.19"));
basicConstraintsSequence.add(ASN1Boolean(true));

var basicConstraintsList = ASN1Sequence();

if (cA) {
basicConstraintsList.add(ASN1Boolean(cA));
}

// check if CA to allow pathLenConstraint
if (pathLenConstraint != null && cA && pathLenConstraint >= 0) {
basicConstraintsList.add(ASN1Integer.fromtInt(pathLenConstraint));
}

basicConstraintsSequence
.add(ASN1OctetString(octets: basicConstraintsList.encode()));

extensionTopSequence.add(basicConstraintsSequence);
}

var extObj = ASN1Object(tag: 0xA3);
extObj.valueBytes = extensionTopSequence.encode();

Expand Down Expand Up @@ -1417,6 +1446,28 @@ class X509Utils {
return extKeyUsage;
}

///
/// Parses the given ASN1Object to the two basic constraint
/// fields cA and pathLenConstraint. Returns a list of types [bool, int] if
/// cA is true and a valid pathLenConstraint is specified, else the
/// corresponding element will be null.
///
static List<dynamic> _fetchBasicConstraintsFromExtension(ASN1Object extData) {
var basicConstraints = <dynamic>[null, null];
var octet = extData as ASN1OctetString;
var constraintParser = ASN1Parser(octet.valueBytes);
var constraintSeq = constraintParser.nextObject() as ASN1Sequence;
constraintSeq.elements!.forEach((ASN1Object obj) {
if (obj is ASN1Boolean) {
basicConstraints[0] = obj.boolValue;
}
if (obj is ASN1Integer) {
basicConstraints[1] = obj.integer!.toInt();
}
});
return basicConstraints;
}

///
/// Parses the given object identifier values to the internal enum
///
Expand Down Expand Up @@ -1926,6 +1977,7 @@ class X509Utils {
List<String>? sans;
List<KeyUsage>? keyUsage;
List<ExtendedKeyUsage>? extKeyUsage;
List<dynamic> basicConstraints;
var extensions = X509CertificateDataExtensions();
extSequence.elements!.forEach(
(ASN1Object subseq) {
Expand Down Expand Up @@ -1962,6 +2014,17 @@ class X509Utils {
}
extensions.extKeyUsage = extKeyUsage;
}
if (oi.objectIdentifierAsString == '2.5.29.19') {
if (seq.elements!.length == 3) {
basicConstraints =
_fetchBasicConstraintsFromExtension(seq.elements!.elementAt(2));
} else {
basicConstraints = [null, null];
}

extensions.cA = basicConstraints[0];
extensions.pathLenConstraint = basicConstraints[1];
}
if (oi.objectIdentifierAsString == '1.3.6.1.5.5.7.1.12') {
var vmcData = _fetchVmcLogo(seq.elements!.elementAt(1));
extensions.vmc = vmcData;
Expand Down
8 changes: 8 additions & 0 deletions lib/src/model/x509/X509CertificateDataExtensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ class X509CertificateDataExtensions {
/// The key usage extension
List<KeyUsage>? keyUsage;

/// The cA field of the basic constraints extension
bool? cA;

/// The pathLenConstraint field of the basic constraints extension
int? pathLenConstraint;

/// The base64 encoded VMC logo
VmcData? vmc;

Expand All @@ -29,6 +35,8 @@ class X509CertificateDataExtensions {
this.subjectAlternativNames,
this.extKeyUsage,
this.keyUsage,
this.cA,
this.pathLenConstraint,
this.vmc,
this.cRLDistributionPoints,
});
Expand Down
4 changes: 4 additions & 0 deletions lib/src/model/x509/X509CertificateDataExtensions.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 95 additions & 2 deletions test/x509_utils_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1652,10 +1652,16 @@ SEQUENCE (1 elem)
notBefore: notBefore,
);
var x509 = X509Utils.x509CertificateFromPem(pem);
expect(x509.tbsCertificate?.validity.notBefore.toIso8601String().substring(0, 10),
expect(
x509.tbsCertificate?.validity.notBefore
.toIso8601String()
.substring(0, 10),
notBefore.toUtc().toIso8601String().substring(0, 10),
reason: "notBefore match except milliseconds as utc");
expect(x509.tbsCertificate?.validity.notAfter.toIso8601String().substring(0, 10),
expect(
x509.tbsCertificate?.validity.notAfter
.toIso8601String()
.substring(0, 10),
notAfter.toUtc().toIso8601String().substring(0, 10),
reason: "notAfter match except milliseconds as utc");
});
Expand Down Expand Up @@ -1707,6 +1713,93 @@ SEQUENCE (1 elem)
KeyUsage.DECIPHER_ONLY);
});

test('Test generateSelfSignedCertificate with cA true and valid pathlen', () {
var pair = CryptoUtils.generateEcKeyPair();
var dn = {
'CN': 'basic-utils.dev',
'O': 'Magic Company',
'L': 'Fakecity',
'S': 'FakeState',
'C': 'DE',
};
var csr = X509Utils.generateEccCsrPem(
dn, pair.privateKey as ECPrivateKey, pair.publicKey as ECPublicKey,
san: ['san1.basic-utils.dev', 'san2.basic-utils.dev']);

var pem = X509Utils.generateSelfSignedCertificate(pair.privateKey, csr, 365,
cA: true, pathLenConstraint: 10);
var x509 = X509Utils.x509CertificateFromPem(pem);

expect(x509.tbsCertificate?.extensions?.cA, true);
expect(x509.tbsCertificate?.extensions?.pathLenConstraint, 10);
});

test('Test generateSelfSignedCertificate with cA false and valid pathLen',
() {
var pair = CryptoUtils.generateEcKeyPair();
var dn = {
'CN': 'basic-utils.dev',
'O': 'Magic Company',
'L': 'Fakecity',
'S': 'FakeState',
'C': 'DE',
};
var csr = X509Utils.generateEccCsrPem(
dn, pair.privateKey as ECPrivateKey, pair.publicKey as ECPublicKey,
san: ['san1.basic-utils.dev', 'san2.basic-utils.dev']);

var pem = X509Utils.generateSelfSignedCertificate(pair.privateKey, csr, 365,
cA: false, pathLenConstraint: 10);
var x509 = X509Utils.x509CertificateFromPem(pem);

expect(x509.tbsCertificate?.extensions?.cA, null);
expect(x509.tbsCertificate?.extensions?.pathLenConstraint, null);
});

test('Test generateSelfSignedCertificate with cA true and invalid pathLen',
() {
var pair = CryptoUtils.generateEcKeyPair();
var dn = {
'CN': 'basic-utils.dev',
'O': 'Magic Company',
'L': 'Fakecity',
'S': 'FakeState',
'C': 'DE',
};
var csr = X509Utils.generateEccCsrPem(
dn, pair.privateKey as ECPrivateKey, pair.publicKey as ECPublicKey,
san: ['san1.basic-utils.dev', 'san2.basic-utils.dev']);

var pem = X509Utils.generateSelfSignedCertificate(pair.privateKey, csr, 365,
cA: true, pathLenConstraint: -10);
var x509 = X509Utils.x509CertificateFromPem(pem);

expect(x509.tbsCertificate?.extensions?.cA, true);
expect(x509.tbsCertificate?.extensions?.pathLenConstraint, null);
});

test('Test generateSelfSignedCertificate with cA false and invalid pathLen',
() {
var pair = CryptoUtils.generateEcKeyPair();
var dn = {
'CN': 'basic-utils.dev',
'O': 'Magic Company',
'L': 'Fakecity',
'S': 'FakeState',
'C': 'DE',
};
var csr = X509Utils.generateEccCsrPem(
dn, pair.privateKey as ECPrivateKey, pair.publicKey as ECPublicKey,
san: ['san1.basic-utils.dev', 'san2.basic-utils.dev']);

var pem = X509Utils.generateSelfSignedCertificate(pair.privateKey, csr, 365,
cA: false, pathLenConstraint: -10);
var x509 = X509Utils.x509CertificateFromPem(pem);

expect(x509.tbsCertificate?.extensions?.cA, null);
expect(x509.tbsCertificate?.extensions?.pathLenConstraint, null);
});

test('Test x509CertificateFromPem with vmc', () {
var data = X509Utils.x509CertificateFromPem(vmc);

Expand Down

0 comments on commit acdaedf

Please sign in to comment.