Skip to content

Commit acdaedf

Browse files
authored
Merge pull request #107 from arj-singh/master
Basic Constraints extension
2 parents 6a50349 + b164851 commit acdaedf

File tree

4 files changed

+171
-3
lines changed

4 files changed

+171
-3
lines changed

lib/src/X509Utils.dart

+64-1
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ class X509Utils {
290290
/// * [sans] = Subject alternative names to place within the certificate
291291
/// * [keyUsage] = The key usage definition extension
292292
/// * [extKeyUsage] = The extended key usage definition
293+
/// * [cA] = The cA boolean of the basic constraints extension, which indicates whether the certificate is a CA
294+
/// * [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.
293295
/// * [serialNumber] = The serialnumber. If not set the default will be 1.
294296
/// * [issuer] = The issuer. If null, the issuer will be the subject of the given csr.
295297
/// * [notBefore] = The Timestamp after when the certificate is valid. If null, this will be [DateTime.now].
@@ -302,6 +304,8 @@ class X509Utils {
302304
List<String>? sans,
303305
List<KeyUsage>? keyUsage,
304306
List<ExtendedKeyUsage>? extKeyUsage,
307+
bool? cA,
308+
int? pathLenConstraint,
305309
String serialNumber = '1',
306310
Map<String, String>? issuer,
307311
DateTime? notBefore,
@@ -397,7 +401,8 @@ class X509Utils {
397401
// Add Extensions
398402
if (IterableUtils.isNotNullOrEmpty(sans) ||
399403
IterableUtils.isNotNullOrEmpty(keyUsage) ||
400-
IterableUtils.isNotNullOrEmpty(extKeyUsage)) {
404+
IterableUtils.isNotNullOrEmpty(extKeyUsage) ||
405+
cA != null) {
401406
var extensionTopSequence = ASN1Sequence();
402407

403408
if (IterableUtils.isNotNullOrEmpty(keyUsage)) {
@@ -484,6 +489,30 @@ class X509Utils {
484489
extensionTopSequence.add(sanSequence);
485490
}
486491

492+
if (cA != null) {
493+
var basicConstraintsSequence = ASN1Sequence();
494+
495+
basicConstraintsSequence
496+
.add(ASN1ObjectIdentifier.fromIdentifierString("2.5.29.19"));
497+
basicConstraintsSequence.add(ASN1Boolean(true));
498+
499+
var basicConstraintsList = ASN1Sequence();
500+
501+
if (cA) {
502+
basicConstraintsList.add(ASN1Boolean(cA));
503+
}
504+
505+
// check if CA to allow pathLenConstraint
506+
if (pathLenConstraint != null && cA && pathLenConstraint >= 0) {
507+
basicConstraintsList.add(ASN1Integer.fromtInt(pathLenConstraint));
508+
}
509+
510+
basicConstraintsSequence
511+
.add(ASN1OctetString(octets: basicConstraintsList.encode()));
512+
513+
extensionTopSequence.add(basicConstraintsSequence);
514+
}
515+
487516
var extObj = ASN1Object(tag: 0xA3);
488517
extObj.valueBytes = extensionTopSequence.encode();
489518

@@ -1417,6 +1446,28 @@ class X509Utils {
14171446
return extKeyUsage;
14181447
}
14191448

1449+
///
1450+
/// Parses the given ASN1Object to the two basic constraint
1451+
/// fields cA and pathLenConstraint. Returns a list of types [bool, int] if
1452+
/// cA is true and a valid pathLenConstraint is specified, else the
1453+
/// corresponding element will be null.
1454+
///
1455+
static List<dynamic> _fetchBasicConstraintsFromExtension(ASN1Object extData) {
1456+
var basicConstraints = <dynamic>[null, null];
1457+
var octet = extData as ASN1OctetString;
1458+
var constraintParser = ASN1Parser(octet.valueBytes);
1459+
var constraintSeq = constraintParser.nextObject() as ASN1Sequence;
1460+
constraintSeq.elements!.forEach((ASN1Object obj) {
1461+
if (obj is ASN1Boolean) {
1462+
basicConstraints[0] = obj.boolValue;
1463+
}
1464+
if (obj is ASN1Integer) {
1465+
basicConstraints[1] = obj.integer!.toInt();
1466+
}
1467+
});
1468+
return basicConstraints;
1469+
}
1470+
14201471
///
14211472
/// Parses the given object identifier values to the internal enum
14221473
///
@@ -1926,6 +1977,7 @@ class X509Utils {
19261977
List<String>? sans;
19271978
List<KeyUsage>? keyUsage;
19281979
List<ExtendedKeyUsage>? extKeyUsage;
1980+
List<dynamic> basicConstraints;
19291981
var extensions = X509CertificateDataExtensions();
19301982
extSequence.elements!.forEach(
19311983
(ASN1Object subseq) {
@@ -1962,6 +2014,17 @@ class X509Utils {
19622014
}
19632015
extensions.extKeyUsage = extKeyUsage;
19642016
}
2017+
if (oi.objectIdentifierAsString == '2.5.29.19') {
2018+
if (seq.elements!.length == 3) {
2019+
basicConstraints =
2020+
_fetchBasicConstraintsFromExtension(seq.elements!.elementAt(2));
2021+
} else {
2022+
basicConstraints = [null, null];
2023+
}
2024+
2025+
extensions.cA = basicConstraints[0];
2026+
extensions.pathLenConstraint = basicConstraints[1];
2027+
}
19652028
if (oi.objectIdentifierAsString == '1.3.6.1.5.5.7.1.12') {
19662029
var vmcData = _fetchVmcLogo(seq.elements!.elementAt(1));
19672030
extensions.vmc = vmcData;

lib/src/model/x509/X509CertificateDataExtensions.dart

+8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ class X509CertificateDataExtensions {
1919
/// The key usage extension
2020
List<KeyUsage>? keyUsage;
2121

22+
/// The cA field of the basic constraints extension
23+
bool? cA;
24+
25+
/// The pathLenConstraint field of the basic constraints extension
26+
int? pathLenConstraint;
27+
2228
/// The base64 encoded VMC logo
2329
VmcData? vmc;
2430

@@ -29,6 +35,8 @@ class X509CertificateDataExtensions {
2935
this.subjectAlternativNames,
3036
this.extKeyUsage,
3137
this.keyUsage,
38+
this.cA,
39+
this.pathLenConstraint,
3240
this.vmc,
3341
this.cRLDistributionPoints,
3442
});

lib/src/model/x509/X509CertificateDataExtensions.g.dart

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/x509_utils_test.dart

+95-2
Original file line numberDiff line numberDiff line change
@@ -1652,10 +1652,16 @@ SEQUENCE (1 elem)
16521652
notBefore: notBefore,
16531653
);
16541654
var x509 = X509Utils.x509CertificateFromPem(pem);
1655-
expect(x509.tbsCertificate?.validity.notBefore.toIso8601String().substring(0, 10),
1655+
expect(
1656+
x509.tbsCertificate?.validity.notBefore
1657+
.toIso8601String()
1658+
.substring(0, 10),
16561659
notBefore.toUtc().toIso8601String().substring(0, 10),
16571660
reason: "notBefore match except milliseconds as utc");
1658-
expect(x509.tbsCertificate?.validity.notAfter.toIso8601String().substring(0, 10),
1661+
expect(
1662+
x509.tbsCertificate?.validity.notAfter
1663+
.toIso8601String()
1664+
.substring(0, 10),
16591665
notAfter.toUtc().toIso8601String().substring(0, 10),
16601666
reason: "notAfter match except milliseconds as utc");
16611667
});
@@ -1707,6 +1713,93 @@ SEQUENCE (1 elem)
17071713
KeyUsage.DECIPHER_ONLY);
17081714
});
17091715

1716+
test('Test generateSelfSignedCertificate with cA true and valid pathlen', () {
1717+
var pair = CryptoUtils.generateEcKeyPair();
1718+
var dn = {
1719+
'CN': 'basic-utils.dev',
1720+
'O': 'Magic Company',
1721+
'L': 'Fakecity',
1722+
'S': 'FakeState',
1723+
'C': 'DE',
1724+
};
1725+
var csr = X509Utils.generateEccCsrPem(
1726+
dn, pair.privateKey as ECPrivateKey, pair.publicKey as ECPublicKey,
1727+
san: ['san1.basic-utils.dev', 'san2.basic-utils.dev']);
1728+
1729+
var pem = X509Utils.generateSelfSignedCertificate(pair.privateKey, csr, 365,
1730+
cA: true, pathLenConstraint: 10);
1731+
var x509 = X509Utils.x509CertificateFromPem(pem);
1732+
1733+
expect(x509.tbsCertificate?.extensions?.cA, true);
1734+
expect(x509.tbsCertificate?.extensions?.pathLenConstraint, 10);
1735+
});
1736+
1737+
test('Test generateSelfSignedCertificate with cA false and valid pathLen',
1738+
() {
1739+
var pair = CryptoUtils.generateEcKeyPair();
1740+
var dn = {
1741+
'CN': 'basic-utils.dev',
1742+
'O': 'Magic Company',
1743+
'L': 'Fakecity',
1744+
'S': 'FakeState',
1745+
'C': 'DE',
1746+
};
1747+
var csr = X509Utils.generateEccCsrPem(
1748+
dn, pair.privateKey as ECPrivateKey, pair.publicKey as ECPublicKey,
1749+
san: ['san1.basic-utils.dev', 'san2.basic-utils.dev']);
1750+
1751+
var pem = X509Utils.generateSelfSignedCertificate(pair.privateKey, csr, 365,
1752+
cA: false, pathLenConstraint: 10);
1753+
var x509 = X509Utils.x509CertificateFromPem(pem);
1754+
1755+
expect(x509.tbsCertificate?.extensions?.cA, null);
1756+
expect(x509.tbsCertificate?.extensions?.pathLenConstraint, null);
1757+
});
1758+
1759+
test('Test generateSelfSignedCertificate with cA true and invalid pathLen',
1760+
() {
1761+
var pair = CryptoUtils.generateEcKeyPair();
1762+
var dn = {
1763+
'CN': 'basic-utils.dev',
1764+
'O': 'Magic Company',
1765+
'L': 'Fakecity',
1766+
'S': 'FakeState',
1767+
'C': 'DE',
1768+
};
1769+
var csr = X509Utils.generateEccCsrPem(
1770+
dn, pair.privateKey as ECPrivateKey, pair.publicKey as ECPublicKey,
1771+
san: ['san1.basic-utils.dev', 'san2.basic-utils.dev']);
1772+
1773+
var pem = X509Utils.generateSelfSignedCertificate(pair.privateKey, csr, 365,
1774+
cA: true, pathLenConstraint: -10);
1775+
var x509 = X509Utils.x509CertificateFromPem(pem);
1776+
1777+
expect(x509.tbsCertificate?.extensions?.cA, true);
1778+
expect(x509.tbsCertificate?.extensions?.pathLenConstraint, null);
1779+
});
1780+
1781+
test('Test generateSelfSignedCertificate with cA false and invalid pathLen',
1782+
() {
1783+
var pair = CryptoUtils.generateEcKeyPair();
1784+
var dn = {
1785+
'CN': 'basic-utils.dev',
1786+
'O': 'Magic Company',
1787+
'L': 'Fakecity',
1788+
'S': 'FakeState',
1789+
'C': 'DE',
1790+
};
1791+
var csr = X509Utils.generateEccCsrPem(
1792+
dn, pair.privateKey as ECPrivateKey, pair.publicKey as ECPublicKey,
1793+
san: ['san1.basic-utils.dev', 'san2.basic-utils.dev']);
1794+
1795+
var pem = X509Utils.generateSelfSignedCertificate(pair.privateKey, csr, 365,
1796+
cA: false, pathLenConstraint: -10);
1797+
var x509 = X509Utils.x509CertificateFromPem(pem);
1798+
1799+
expect(x509.tbsCertificate?.extensions?.cA, null);
1800+
expect(x509.tbsCertificate?.extensions?.pathLenConstraint, null);
1801+
});
1802+
17101803
test('Test x509CertificateFromPem with vmc', () {
17111804
var data = X509Utils.x509CertificateFromPem(vmc);
17121805

0 commit comments

Comments
 (0)