You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
656 lines
19 KiB
656 lines
19 KiB
|
2 days ago
|
// The strict DER of X.690 that spec §29.10 asks of a CMS signature, as
|
||
|
|
// der_test.go of datekeys-go at 601e6d2 (the draft of v0.12). Every error text
|
||
|
|
// is the one that the Go reference prints for the same input; the fuzz target
|
||
|
|
// is a property over seeded mutations of its seeds.
|
||
|
|
|
||
|
|
import 'dart:convert';
|
||
|
|
import 'dart:math';
|
||
|
|
import 'dart:typed_data';
|
||
|
|
|
||
|
|
import 'package:datekeys/datekeys.dart' show fromHex, toHex;
|
||
|
|
import 'package:datekeys/src/der.dart' as der;
|
||
|
|
import 'package:test/test.dart';
|
||
|
|
|
||
|
|
import 'der_support.dart';
|
||
|
|
|
||
|
|
Uint8List h(String hex) => fromHex(hex);
|
||
|
|
|
||
|
|
String zeros(int n) => '00' * n;
|
||
|
|
|
||
|
|
String hexOf(String s) => toHex(utf8.encode(s));
|
||
|
|
|
||
|
|
/// The message of the DerException that [f] throws.
|
||
|
|
String derError(void Function() f) {
|
||
|
|
try {
|
||
|
|
f();
|
||
|
|
} on der.DerException catch (e) {
|
||
|
|
return e.message;
|
||
|
|
}
|
||
|
|
fail('no DerException');
|
||
|
|
}
|
||
|
|
|
||
|
|
void main() {
|
||
|
|
test('check accepts exactly the DER of the profile', () {
|
||
|
|
// The cases of TestCheck: null for DER, and the text of the reference
|
||
|
|
// for the rest.
|
||
|
|
final cases = <(String, String, String?)>[
|
||
|
|
(
|
||
|
|
'sequence of an integer and a null',
|
||
|
|
'3005020101'
|
||
|
|
'0500',
|
||
|
|
null,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a long length',
|
||
|
|
'04'
|
||
|
|
'8180${zeros(128)}',
|
||
|
|
null,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a long form under 128',
|
||
|
|
'0481'
|
||
|
|
'01'
|
||
|
|
'00',
|
||
|
|
'der: a long form for a length under 128',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a length with a leading zero',
|
||
|
|
'04820001'
|
||
|
|
'00',
|
||
|
|
'der: a length with a leading zero',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a length of 128 with a leading zero',
|
||
|
|
'04820080${zeros(128)}',
|
||
|
|
'der: a length with a leading zero',
|
||
|
|
),
|
||
|
|
('an indefinite length', '30800000', 'der: an indefinite length'),
|
||
|
|
('a length of 0xff', '04ff${zeros(127)}', 'der: a length of 0xff'),
|
||
|
|
(
|
||
|
|
'a length of five bytes',
|
||
|
|
'0485'
|
||
|
|
'0100000000'
|
||
|
|
'00',
|
||
|
|
'der: a length that does not fit',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a length of nine bytes that wraps around to 128',
|
||
|
|
'0489'
|
||
|
|
'010000000000000080${zeros(128)}',
|
||
|
|
'der: a length that does not fit',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a length whose bytes are missing',
|
||
|
|
'0482'
|
||
|
|
'01',
|
||
|
|
'der: a length that does not fit',
|
||
|
|
),
|
||
|
|
('a lone identifier octet', '04', 'der: truncated element'),
|
||
|
|
('nothing', '', 'der: truncated element'),
|
||
|
|
('a high tag number', '1f0100', 'der: a tag number of 31 or more'),
|
||
|
|
('truncated', '0402aa', 'der: an element longer than its container'),
|
||
|
|
(
|
||
|
|
'trailing bytes',
|
||
|
|
'0500'
|
||
|
|
'00',
|
||
|
|
'der: 1 bytes after the element',
|
||
|
|
),
|
||
|
|
('BOOLEAN 01', '010101', 'der: a BOOLEAN that is not 00 or FF'),
|
||
|
|
('BOOLEAN 00', '010100', null),
|
||
|
|
('BOOLEAN FF', '0101ff', null),
|
||
|
|
(
|
||
|
|
'BOOLEAN of two bytes',
|
||
|
|
'0102ffff',
|
||
|
|
'der: a BOOLEAN that is not 00 or FF',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'INTEGER with a leading zero',
|
||
|
|
'02020001',
|
||
|
|
'der: an INTEGER that is not minimal',
|
||
|
|
),
|
||
|
|
('INTEGER 0x80 with its zero', '02020080', null),
|
||
|
|
(
|
||
|
|
'INTEGER with a leading FF',
|
||
|
|
'0202ff80',
|
||
|
|
'der: an INTEGER that is not minimal',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'INTEGER -1 in two bytes',
|
||
|
|
'0202ffff',
|
||
|
|
'der: an INTEGER that is not minimal',
|
||
|
|
),
|
||
|
|
('INTEGER -129', '0202ff7f', null),
|
||
|
|
('empty INTEGER', '0200', 'der: an empty INTEGER'),
|
||
|
|
(
|
||
|
|
'ENUMERATED with a leading zero',
|
||
|
|
'0a020001',
|
||
|
|
'der: an INTEGER that is not minimal',
|
||
|
|
),
|
||
|
|
('NULL with content', '050100', 'der: a NULL with content'),
|
||
|
|
(
|
||
|
|
'constructed OCTET STRING',
|
||
|
|
'2404'
|
||
|
|
'0402aabb',
|
||
|
|
'der: constructed form of the universal type 4',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'constructed INTEGER',
|
||
|
|
'2203'
|
||
|
|
'020101',
|
||
|
|
'der: constructed form of the universal type 2',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'BIT STRING with unused bits set',
|
||
|
|
'03020701',
|
||
|
|
'der: a BIT STRING with unused bits that are not zero',
|
||
|
|
),
|
||
|
|
('BIT STRING with unused bits clear', '03020780', null),
|
||
|
|
('BIT STRING of 4 bits', '030204f0', null),
|
||
|
|
('an empty BIT STRING', '030100', null),
|
||
|
|
(
|
||
|
|
'a BIT STRING without its first octet',
|
||
|
|
'0300',
|
||
|
|
'der: a malformed BIT STRING',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a BIT STRING of eight unused bits',
|
||
|
|
'03020800',
|
||
|
|
'der: a malformed BIT STRING',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a BIT STRING of no bits with unused bits',
|
||
|
|
'030101',
|
||
|
|
'der: a malformed BIT STRING',
|
||
|
|
),
|
||
|
|
('OID', '06032a0304', null),
|
||
|
|
(
|
||
|
|
'OID with 0x80 inside a subidentifier',
|
||
|
|
'0604'
|
||
|
|
'2a818001',
|
||
|
|
null,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'OID with a leading 0x80',
|
||
|
|
'06038001'
|
||
|
|
'02',
|
||
|
|
'der: an OBJECT IDENTIFIER with a leading 0x80 in a subidentifier',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'OID that does not end',
|
||
|
|
'06022a83',
|
||
|
|
'der: a malformed OBJECT IDENTIFIER',
|
||
|
|
),
|
||
|
|
('an empty OID', '0600', 'der: a malformed OBJECT IDENTIFIER'),
|
||
|
|
('context tag, constructed', 'a003020101', null),
|
||
|
|
('context tag, primitive, any content', '8003000000', null),
|
||
|
|
(
|
||
|
|
'SET in primitive form',
|
||
|
|
'1100',
|
||
|
|
'der: the universal type 17 in primitive form',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'SEQUENCE in primitive form',
|
||
|
|
'1000',
|
||
|
|
'der: the universal type 16 in primitive form',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'the end of contents',
|
||
|
|
'0000',
|
||
|
|
'der: the universal type 0, which the profile does not use',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a reserved universal tag',
|
||
|
|
'0e0141',
|
||
|
|
'der: the universal type 14, which the profile does not use',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a SEQUENCE of the end of contents',
|
||
|
|
'30020000',
|
||
|
|
'der: the universal type 0, which the profile does not use',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a SEQUENCE with a bad child',
|
||
|
|
'3003'
|
||
|
|
'020000',
|
||
|
|
'der: an empty INTEGER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a SEQUENCE whose child does not fit',
|
||
|
|
'3003'
|
||
|
|
'040500',
|
||
|
|
'der: an element longer than its container',
|
||
|
|
),
|
||
|
|
('UTF8String', '0c026162', null),
|
||
|
|
('UTF8String that is not UTF-8', '0c01ff', null),
|
||
|
|
('PrintableString with an underscore', '13015f', null),
|
||
|
|
('TeletexString', '1401e9', null),
|
||
|
|
('IA5String', '160161', null),
|
||
|
|
('VisibleString', '1a0161', null),
|
||
|
|
('UniversalString', '1c0400000061', null),
|
||
|
|
('BMPString', '1e020061', null),
|
||
|
|
// The other string types are DER too, whatever their content: a name
|
||
|
|
// may hold a NumericString, as the INN of a Russian certificate.
|
||
|
|
('NumericString', '1204${hexOf('1234')}', null),
|
||
|
|
('NumericString with a letter', '1201${hexOf('A')}', null),
|
||
|
|
('VideotexString', '150141', null),
|
||
|
|
('GraphicString', '190141', null),
|
||
|
|
('GeneralString', '1b0141', null),
|
||
|
|
('ObjectDescriptor', '070141', null),
|
||
|
|
(
|
||
|
|
'NumericString in constructed form',
|
||
|
|
'3203'
|
||
|
|
'120131',
|
||
|
|
'der: constructed form of the universal type 18',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'REAL',
|
||
|
|
'0900',
|
||
|
|
'der: the universal type 9, which the profile does not use',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'RELATIVE-OID',
|
||
|
|
'0d0101',
|
||
|
|
'der: the universal type 13, which the profile does not use',
|
||
|
|
),
|
||
|
|
// Times in the forms of DER (X.690 11.7, 11.8).
|
||
|
|
('UTCTime', '170d${hexOf('250101120000Z')}', null),
|
||
|
|
(
|
||
|
|
'an empty UTCTime',
|
||
|
|
'1700',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'UTCTime without seconds',
|
||
|
|
'170b${hexOf('2501011200Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'UTCTime with a digit more',
|
||
|
|
'170e${hexOf('2501011200001Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'UTCTime with an offset',
|
||
|
|
'1711${hexOf('250101120000+0100')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'UTCTime of 30 February',
|
||
|
|
'170d${hexOf('250230120000Z')}',
|
||
|
|
'der: a date or a time that does not exist',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'UTCTime with second 60',
|
||
|
|
'170d${hexOf('250101235960Z')}',
|
||
|
|
'der: a date or a time that does not exist',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a UTCTime that is not a time',
|
||
|
|
'170a${hexOf('not a time')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'UTCTime with a slash in its seconds',
|
||
|
|
'170d${hexOf('2501011200/0Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'UTCTime with a colon in its day',
|
||
|
|
'170d${hexOf('25010:120000Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
('GeneralizedTime', '180f${hexOf('20250101120000Z')}', null),
|
||
|
|
(
|
||
|
|
'GeneralizedTime with a fraction',
|
||
|
|
'1812${hexOf('20250101120000.25Z')}',
|
||
|
|
null,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime with a trailing zero',
|
||
|
|
'1813${hexOf('20250101120000.250Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime with an empty fraction',
|
||
|
|
'1810${hexOf('20250101120000.Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime with a letter in its fraction',
|
||
|
|
'1812${hexOf('20250101120000.2aZ')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime without Z',
|
||
|
|
'180e${hexOf('20250101120000')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime with a comma',
|
||
|
|
'1812${hexOf('20250101120000,25Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime without seconds',
|
||
|
|
'180d${hexOf('202501011200Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime with a slash in its seconds',
|
||
|
|
'180f${hexOf('202501011200/0Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime of month 0',
|
||
|
|
'180f${hexOf('20250001120000Z')}',
|
||
|
|
'der: a date or a time that does not exist',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime of month 13',
|
||
|
|
'180f${hexOf('20251301120000Z')}',
|
||
|
|
'der: a date or a time that does not exist',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime of day 0',
|
||
|
|
'180f${hexOf('20250100120000Z')}',
|
||
|
|
'der: a date or a time that does not exist',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime of 31 April',
|
||
|
|
'180f${hexOf('20250431120000Z')}',
|
||
|
|
'der: a date or a time that does not exist',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime of hour 24',
|
||
|
|
'180f${hexOf('20250101240000Z')}',
|
||
|
|
'der: a date or a time that does not exist',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime of minute 60',
|
||
|
|
'180f${hexOf('20250101126000Z')}',
|
||
|
|
'der: a date or a time that does not exist',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime of 29 February 2024',
|
||
|
|
'180f${hexOf('20240229235959Z')}',
|
||
|
|
null,
|
||
|
|
),
|
||
|
|
// What a check that is missing would let through: the last byte read
|
||
|
|
// as Z, a colon read as the digit 10, a slash read as a year, a tag of
|
||
|
|
// a high number read as one byte, and an indefinite length read as a
|
||
|
|
// long form.
|
||
|
|
(
|
||
|
|
'UTCTime that ends in another letter',
|
||
|
|
'170d${hexOf('250101120000X')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime with a digit in the place of Z',
|
||
|
|
'180f${hexOf('202501011200000')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime with a colon in its day',
|
||
|
|
'180f${hexOf('2025010:120000Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'GeneralizedTime with a slash in its year',
|
||
|
|
'180f${hexOf('/0250101120000Z')}',
|
||
|
|
'der: a time that is not in the form of DER',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'a context tag of a high number',
|
||
|
|
'9f0100',
|
||
|
|
'der: a tag number of 31 or more',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'an indefinite length and nothing after it',
|
||
|
|
'3080',
|
||
|
|
'der: an indefinite length',
|
||
|
|
),
|
||
|
|
];
|
||
|
|
for (final (name, hex, text) in cases) {
|
||
|
|
final b = h(hex);
|
||
|
|
if (text == null) {
|
||
|
|
expect(() => der.check(b), returnsNormally, reason: name);
|
||
|
|
} else {
|
||
|
|
expect(derError(() => der.check(b)), text, reason: name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test(
|
||
|
|
'the elements of a SET OF go in ascending order, equal ones may repeat',
|
||
|
|
() {
|
||
|
|
final a = [0x02, 0x01, 0x01];
|
||
|
|
final b = [0x02, 0x01, 0x02];
|
||
|
|
expect(der.setOfSorted([a, b]), isTrue);
|
||
|
|
expect(der.setOfSorted([b, a]), isFalse);
|
||
|
|
expect(der.setOfSorted([a, a, b]), isTrue);
|
||
|
|
expect(der.setOfSorted([a, b, a]), isFalse);
|
||
|
|
expect(der.setOfSorted([]), isTrue);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
test('elements nested 32 levels below the outer one are DER, 33 are not', () {
|
||
|
|
Uint8List nested(int n) {
|
||
|
|
var b = [0x05, 0x00];
|
||
|
|
for (var i = 0; i < n; i++) {
|
||
|
|
b = [0x30, b.length, ...b];
|
||
|
|
}
|
||
|
|
return Uint8List.fromList(b);
|
||
|
|
}
|
||
|
|
|
||
|
|
der.check(nested(32));
|
||
|
|
expect(derError(() => der.check(nested(33))), 'der: nested too deep');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('parseTime reads the times of DER exactly to the nanosecond', () {
|
||
|
|
// Seconds since the epoch, nanoseconds and the form of RFC 3339, as Go's
|
||
|
|
// Time.Unix, Time.Nanosecond and RFC3339Nano print them.
|
||
|
|
final cases = <(String, int, int, bool, String)>[
|
||
|
|
(
|
||
|
|
'\x17\x0d'
|
||
|
|
'491231235959Z',
|
||
|
|
2524607999,
|
||
|
|
0,
|
||
|
|
false,
|
||
|
|
'2049-12-31T23:59:59Z',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'\x17\x0d'
|
||
|
|
'500101000000Z',
|
||
|
|
-631152000,
|
||
|
|
0,
|
||
|
|
false,
|
||
|
|
'1950-01-01T00:00:00Z',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'\x18\x13'
|
||
|
|
'20240229120000.125Z',
|
||
|
|
1709208000,
|
||
|
|
125000000,
|
||
|
|
true,
|
||
|
|
'2024-02-29T12:00:00.125Z',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'\x18\x19'
|
||
|
|
'20240229120000.123456789Z',
|
||
|
|
1709208000,
|
||
|
|
123456789,
|
||
|
|
true,
|
||
|
|
'2024-02-29T12:00:00.123456789Z',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'\x18\x0f'
|
||
|
|
'19490101000000Z',
|
||
|
|
-662688000,
|
||
|
|
0,
|
||
|
|
false,
|
||
|
|
'1949-01-01T00:00:00Z',
|
||
|
|
),
|
||
|
|
// Digits beyond the nanosecond are dropped, as Go drops them.
|
||
|
|
(
|
||
|
|
'\x18\x1b'
|
||
|
|
'20240229120000.12345678912Z',
|
||
|
|
1709208000,
|
||
|
|
123456789,
|
||
|
|
true,
|
||
|
|
'2024-02-29T12:00:00.123456789Z',
|
||
|
|
),
|
||
|
|
// The proleptic Gregorian calendar of Go: the year 0 is a leap year.
|
||
|
|
(
|
||
|
|
'\x18\x0f'
|
||
|
|
'00000229120000Z',
|
||
|
|
-62162078400,
|
||
|
|
0,
|
||
|
|
false,
|
||
|
|
'0000-02-29T12:00:00Z',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'\x18\x0f'
|
||
|
|
'99991231235959Z',
|
||
|
|
253402300799,
|
||
|
|
0,
|
||
|
|
false,
|
||
|
|
'9999-12-31T23:59:59Z',
|
||
|
|
),
|
||
|
|
];
|
||
|
|
for (final (el, seconds, nanos, fraction, text) in cases) {
|
||
|
|
final t = der.parseTime(Uint8List.fromList(el.codeUnits));
|
||
|
|
expect(t.time.unixSeconds, seconds, reason: text);
|
||
|
|
expect(t.time.nanosecond, nanos, reason: text);
|
||
|
|
expect(t.fraction, fraction, reason: text);
|
||
|
|
expect('${t.time}', text);
|
||
|
|
}
|
||
|
|
final bad = <(String, String)>[
|
||
|
|
(
|
||
|
|
'\x18\x0f'
|
||
|
|
'20230229120000Z',
|
||
|
|
'der: a date or a time that does not exist',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'\x04\x0d'
|
||
|
|
'491231235959Z',
|
||
|
|
'der: not a UTCTime or a GeneralizedTime',
|
||
|
|
),
|
||
|
|
// A GeneralizedTime in an OCTET STRING, a UTCTime in a UTF8String.
|
||
|
|
(
|
||
|
|
'\x04\x0f'
|
||
|
|
'20230228120000Z',
|
||
|
|
'der: not a UTCTime or a GeneralizedTime',
|
||
|
|
),
|
||
|
|
(
|
||
|
|
'\x0c\x0d'
|
||
|
|
'491231235959Z',
|
||
|
|
'der: not a UTCTime or a GeneralizedTime',
|
||
|
|
),
|
||
|
|
('\x17', 'der: not a UTCTime or a GeneralizedTime'),
|
||
|
|
// Its content does not fit.
|
||
|
|
(
|
||
|
|
'\x17\x0d'
|
||
|
|
'4912',
|
||
|
|
'der: an element longer than its container',
|
||
|
|
),
|
||
|
|
('', 'der: not a UTCTime or a GeneralizedTime'),
|
||
|
|
];
|
||
|
|
for (final (el, text) in bad) {
|
||
|
|
expect(
|
||
|
|
derError(() => der.parseTime(Uint8List.fromList(el.codeUnits))),
|
||
|
|
text,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('times compare in order, to the nanosecond', () {
|
||
|
|
der.DerTime t(String s) => der
|
||
|
|
.parseTime(Uint8List.fromList([0x18, s.length, ...s.codeUnits]))
|
||
|
|
.time;
|
||
|
|
final a = t('20240229120000.1Z');
|
||
|
|
final b = t('20240229120000.100000001Z');
|
||
|
|
final c = t('20240229120001Z');
|
||
|
|
expect(a.isBefore(b), isTrue);
|
||
|
|
expect(b.isBefore(c), isTrue);
|
||
|
|
expect(c.isAfter(a), isTrue);
|
||
|
|
expect(a.compareTo(a), 0);
|
||
|
|
expect(a, t('20240229120000.1Z'));
|
||
|
|
expect(a, isNot(b));
|
||
|
|
// A UTCTime and a GeneralizedTime of the same instant are the same time.
|
||
|
|
final utc = der.parseTime(h('170d${hexOf('491231235959Z')}')).time;
|
||
|
|
expect(utc, t('20491231235959Z'));
|
||
|
|
expect(utc.hashCode, t('20491231235959Z').hashCode);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('split and content read the children and the content', () {
|
||
|
|
final b = h('30050201010500');
|
||
|
|
der.check(b);
|
||
|
|
final s = der.split(b);
|
||
|
|
expect(s.id, 0x30);
|
||
|
|
expect(s.children.map(toHex), ['020101', '0500']);
|
||
|
|
expect(toHex(der.content(b)), '0201010500');
|
||
|
|
// Nothing, a primitive element, and constructed ones whose content, or a
|
||
|
|
// child, does not fit.
|
||
|
|
final bad = <(String, String)>[
|
||
|
|
('', 'der: not a constructed element'),
|
||
|
|
('0400', 'der: not a constructed element'),
|
||
|
|
('3005', 'der: an element longer than its container'),
|
||
|
|
('a005', 'der: an element longer than its container'),
|
||
|
|
('300304050000', 'der: an element longer than its container'),
|
||
|
|
];
|
||
|
|
for (final (hex, text) in bad) {
|
||
|
|
expect(derError(() => der.split(h(hex))), text, reason: hex);
|
||
|
|
}
|
||
|
|
expect(
|
||
|
|
derError(() => der.content(h('040500'))),
|
||
|
|
'der: an element longer than its container',
|
||
|
|
);
|
||
|
|
expect(derError(() => der.content(h('04'))), 'der: truncated element');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a child that does not fit is an error of split, which returns', () {
|
||
|
|
// The loop over the children advances by each header: a child that does
|
||
|
|
// not fit must end it, or it would never end.
|
||
|
|
expect(
|
||
|
|
derError(() => der.split(h('3003040500'))),
|
||
|
|
'der: an element longer than its container',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('what check accepts, split, content and parseTime read', () {
|
||
|
|
// FuzzDERCheck over seeded mutations of its seeds: the children of a
|
||
|
|
// constructed element are its content exactly, a primitive one is not
|
||
|
|
// split, and a time of the universal class is read by parseTime.
|
||
|
|
final seeds = [
|
||
|
|
for (final s in [
|
||
|
|
'30050201010500',
|
||
|
|
'a003020101',
|
||
|
|
'0603'
|
||
|
|
'2a0304',
|
||
|
|
'170d${hexOf('250101120000Z')}',
|
||
|
|
'1812${hexOf('20250101120000.25Z')}',
|
||
|
|
'30800000',
|
||
|
|
'0489'
|
||
|
|
'010000000000000080',
|
||
|
|
'3010'
|
||
|
|
'170d${hexOf('491231235959Z')}'
|
||
|
|
'0101ff',
|
||
|
|
])
|
||
|
|
h(s),
|
||
|
|
];
|
||
|
|
final r = Random(7);
|
||
|
|
var accepted = 0;
|
||
|
|
for (var i = 0; i < 20000; i++) {
|
||
|
|
final b = mutateDer(r, seeds[r.nextInt(seeds.length)]);
|
||
|
|
try {
|
||
|
|
der.check(b);
|
||
|
|
} on der.DerException {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
accepted++;
|
||
|
|
walkAccepted(b);
|
||
|
|
}
|
||
|
|
expect(accepted, greaterThan(100));
|
||
|
|
});
|
||
|
|
}
|