Stage 5a: the CMS reader with the certificate profile of the draft v0.12
cms.dart ports internal/cms of datekeys-go at c531e93, with its order of
checks and its texts: parseCert reads a certificate field by field with
the profile of spec v0.12 §29.10, with the holder from givenName and
surname before the commonName and the issuer from organizationName when
there is no commonName with text; parseSignature reads a detached
SignedData, its certificates, OCSP responses, signers, signed and
unsigned attributes, and SignerInfo.check gives valid, invalid or not
verifiable with the closed table of algorithms, a key of another scheme
invalid; parseToken reads an RFC 3161 token and its TSTInfo field by
field, form errors before algorithm errors, and Token.check verifies it
over a subject. Object identifiers are compared by their bytes, and a
SET OF may repeat an element.
The tests run every case of the vectors on the VM, the fixtures
format3_signed_cms and format3_sealed included, and the part that
cms_vectors.g.dart holds compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// The differential of the CMS reader against the Go reference: the cases of
|
|
|
|
|
// test/vectors/cms_*.json, which tool/cms_go_vectors_test.go writes with
|
|
|
|
|
// internal/cms of datekeys-go and the ECDSA and RSA of Go, run through
|
|
|
|
|
// cms.dart, ecdsa.dart, rsa.dart and nist_curves.dart, with the same
|
|
|
|
|
// results and the same texts. It reads no file itself, so that the tests
|
|
|
|
|
// compiled to JavaScript run it too, on the part of the cases that
|
|
|
|
|
// cms_vectors.g.dart holds; the cases of the fixtures run only when a
|
|
|
|
|
// reader of fixtures is given.
|
Stage 5a: the vectors of Go for the CMS reader, ECDSA and RSA
tool/cms_go_vectors_test.go records what internal/cms of datekeys-go at
c531e93 (the draft v0.12) and the ECDSA and RSA of Go give: the curves,
points and signatures of ecdsa.VerifyASN1; RSA keys of 2048 to 4096 bits
and signatures of rsa.VerifyPKCS1v15 and VerifyPSS, with encodings built
by hand, each with one defect of its padding; ParseCert, ParseSignature,
SignerInfo.Check, ParseToken and Token.Check on the cases of the tests of
internal/cms and others (identifiers whose arcs wrap around in 32 or 64
bits, SET OF with an element repeated, the limits of the accuracy and of
the imprint, the ends of the validity); signatures, tokens and
certificates edited node by node and bit by bit; and every signature and
token of security_cms.json and of the two CMS fixtures, signer by signer.
It runs as a test in an export of datekeys-go, so that it can import
internal/cms and make keys and signatures deterministic with
testing/cryptotest: every run writes the same bytes. Each file stays
under 560 KB; repeated certificates are written once per file.
cms_vectors.g.dart holds a part of each file for the tests compiled to
JavaScript, and a test on the VM checks that it is that part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
library;
|
|
|
|
|
|
Stage 5a: ECDSA on P-256, P-384 and P-521, and RSA
nist_curves.dart reads an uncompressed point as Go's
ecdsa.ParseUncompressedPublicKey does, and computes u1 G + u2 Q in
Jacobian coordinates with Shamir's trick, the infinity included.
ecdsa.dart is VerifyASN1: the encoding as cryptobyte reads it, r and s
in [1, n - 1] and never reduced, s above n/2 accepted, the hash cut to
the bits of the order. rsa.dart is VerifyPKCS1v15, which rebuilds the
encoding and compares it whole, and VerifyPSS with a salt of the length
of the hash, with the bytes before emLen that must be zero; and the
hashes of the table, SHA-256, SHA-384 and SHA-512 of package:crypto.
All on BigInt, exact on the VM and compiled to JavaScript, and not
constant-time: a verification sees public values only. The curve
constants are those that the vectors record from Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
import 'dart:typed_data';
|
|
|
|
|
|
|
|
|
|
import 'package:datekeys/src/bytes.dart';
|
Stage 5a: the CMS reader with the certificate profile of the draft v0.12
cms.dart ports internal/cms of datekeys-go at c531e93, with its order of
checks and its texts: parseCert reads a certificate field by field with
the profile of spec v0.12 §29.10, with the holder from givenName and
surname before the commonName and the issuer from organizationName when
there is no commonName with text; parseSignature reads a detached
SignedData, its certificates, OCSP responses, signers, signed and
unsigned attributes, and SignerInfo.check gives valid, invalid or not
verifiable with the closed table of algorithms, a key of another scheme
invalid; parseToken reads an RFC 3161 token and its TSTInfo field by
field, form errors before algorithm errors, and Token.check verifies it
over a subject. Object identifiers are compared by their bytes, and a
SET OF may repeat an element.
The tests run every case of the vectors on the VM, the fixtures
format3_signed_cms and format3_sealed included, and the part that
cms_vectors.g.dart holds compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
import 'package:datekeys/src/cms.dart';
|
|
|
|
|
import 'package:datekeys/src/datekey.dart';
|
|
|
|
|
import 'package:datekeys/src/der.dart' as der;
|
Stage 5a: ECDSA on P-256, P-384 and P-521, and RSA
nist_curves.dart reads an uncompressed point as Go's
ecdsa.ParseUncompressedPublicKey does, and computes u1 G + u2 Q in
Jacobian coordinates with Shamir's trick, the infinity included.
ecdsa.dart is VerifyASN1: the encoding as cryptobyte reads it, r and s
in [1, n - 1] and never reduced, s above n/2 accepted, the hash cut to
the bits of the order. rsa.dart is VerifyPKCS1v15, which rebuilds the
encoding and compares it whole, and VerifyPSS with a salt of the length
of the hash, with the bytes before emLen that must be zero; and the
hashes of the table, SHA-256, SHA-384 and SHA-512 of package:crypto.
All on BigInt, exact on the VM and compiled to JavaScript, and not
constant-time: a verification sees public values only. The curve
constants are those that the vectors record from Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
import 'package:datekeys/src/ecdsa.dart';
|
|
|
|
|
import 'package:datekeys/src/nist_curves.dart';
|
|
|
|
|
import 'package:datekeys/src/rsa.dart';
|
Stage 5a: the CMS reader with the certificate profile of the draft v0.12
cms.dart ports internal/cms of datekeys-go at c531e93, with its order of
checks and its texts: parseCert reads a certificate field by field with
the profile of spec v0.12 §29.10, with the holder from givenName and
surname before the commonName and the issuer from organizationName when
there is no commonName with text; parseSignature reads a detached
SignedData, its certificates, OCSP responses, signers, signed and
unsigned attributes, and SignerInfo.check gives valid, invalid or not
verifiable with the closed table of algorithms, a key of another scheme
invalid; parseToken reads an RFC 3161 token and its TSTInfo field by
field, form errors before algorithm errors, and Token.check verifies it
over a subject. Object identifiers are compared by their bytes, and a
SET OF may repeat an element.
The tests run every case of the vectors on the VM, the fixtures
format3_signed_cms and format3_sealed included, and the part that
cms_vectors.g.dart holds compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
import 'package:datekeys/src/sha256.dart';
|
Stage 5a: ECDSA on P-256, P-384 and P-521, and RSA
nist_curves.dart reads an uncompressed point as Go's
ecdsa.ParseUncompressedPublicKey does, and computes u1 G + u2 Q in
Jacobian coordinates with Shamir's trick, the infinity included.
ecdsa.dart is VerifyASN1: the encoding as cryptobyte reads it, r and s
in [1, n - 1] and never reduced, s above n/2 accepted, the hash cut to
the bits of the order. rsa.dart is VerifyPKCS1v15, which rebuilds the
encoding and compares it whole, and VerifyPSS with a salt of the length
of the hash, with the bytes before emLen that must be zero; and the
hashes of the table, SHA-256, SHA-384 and SHA-512 of package:crypto.
All on BigInt, exact on the VM and compiled to JavaScript, and not
constant-time: a verification sees public values only. The curve
constants are those that the vectors record from Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
import 'package:test/test.dart';
|
|
|
|
|
|
Stage 5a: the vectors of Go for the CMS reader, ECDSA and RSA
tool/cms_go_vectors_test.go records what internal/cms of datekeys-go at
c531e93 (the draft v0.12) and the ECDSA and RSA of Go give: the curves,
points and signatures of ecdsa.VerifyASN1; RSA keys of 2048 to 4096 bits
and signatures of rsa.VerifyPKCS1v15 and VerifyPSS, with encodings built
by hand, each with one defect of its padding; ParseCert, ParseSignature,
SignerInfo.Check, ParseToken and Token.Check on the cases of the tests of
internal/cms and others (identifiers whose arcs wrap around in 32 or 64
bits, SET OF with an element repeated, the limits of the accuracy and of
the imprint, the ends of the validity); signatures, tokens and
certificates edited node by node and bit by bit; and every signature and
token of security_cms.json and of the two CMS fixtures, signer by signer.
It runs as a test in an export of datekeys-go, so that it can import
internal/cms and make keys and signatures deterministic with
testing/cryptotest: every run writes the same bytes. Each file stays
under 560 KB; repeated certificates are written once per file.
cms_vectors.g.dart holds a part of each file for the tests compiled to
JavaScript, and a test on the VM checks that it is that part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
typedef Json = Map<String, Object?>;
|
|
|
|
|
|
|
|
|
|
/// The names of the vector files, cms_<name>.json.
|
|
|
|
|
const cmsFiles = [
|
|
|
|
|
'ecdsa',
|
|
|
|
|
'rsa',
|
|
|
|
|
'certs',
|
|
|
|
|
'signatures',
|
|
|
|
|
'algorithms',
|
|
|
|
|
'tokens',
|
|
|
|
|
'mutations',
|
|
|
|
|
'corpus',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// The cases of [section] of [file].
|
|
|
|
|
List<Json> cases(Json file, String section) =>
|
|
|
|
|
(file[section]! as List).cast<Json>();
|
Stage 5a: ECDSA on P-256, P-384 and P-521, and RSA
nist_curves.dart reads an uncompressed point as Go's
ecdsa.ParseUncompressedPublicKey does, and computes u1 G + u2 Q in
Jacobian coordinates with Shamir's trick, the infinity included.
ecdsa.dart is VerifyASN1: the encoding as cryptobyte reads it, r and s
in [1, n - 1] and never reduced, s above n/2 accepted, the hash cut to
the bits of the order. rsa.dart is VerifyPKCS1v15, which rebuilds the
encoding and compares it whole, and VerifyPSS with a salt of the length
of the hash, with the bytes before emLen that must be zero; and the
hashes of the table, SHA-256, SHA-384 and SHA-512 of package:crypto.
All on BigInt, exact on the VM and compiled to JavaScript, and not
constant-time: a verification sees public values only. The curve
constants are those that the vectors record from Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
|
|
|
|
|
Uint8List hexOf(Object? v) => fromHex(v! as String);
|
|
|
|
|
|
Stage 5a: the CMS reader with the certificate profile of the draft v0.12
cms.dart ports internal/cms of datekeys-go at c531e93, with its order of
checks and its texts: parseCert reads a certificate field by field with
the profile of spec v0.12 §29.10, with the holder from givenName and
surname before the commonName and the issuer from organizationName when
there is no commonName with text; parseSignature reads a detached
SignedData, its certificates, OCSP responses, signers, signed and
unsigned attributes, and SignerInfo.check gives valid, invalid or not
verifiable with the closed table of algorithms, a key of another scheme
invalid; parseToken reads an RFC 3161 token and its TSTInfo field by
field, form errors before algorithm errors, and Token.check verifies it
over a subject. Object identifiers are compared by their bytes, and a
SET OF may repeat an element.
The tests run every case of the vectors on the VM, the fixtures
format3_signed_cms and format3_sealed included, and the part that
cms_vectors.g.dart holds compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// The bytes of a case
|
|
|
|
|
|
|
|
|
|
/// An element of [tag] with the encodings of [content], its length in the
|
|
|
|
|
/// shortest form, as tlv of Go's cmstest.
|
|
|
|
|
Uint8List tlv(int tag, List<List<int>> content) {
|
|
|
|
|
final c = concatBytes(content);
|
|
|
|
|
final n = c.length;
|
|
|
|
|
final List<int> header;
|
|
|
|
|
if (n < 0x80) {
|
|
|
|
|
header = [tag, n];
|
|
|
|
|
} else if (n < 0x100) {
|
|
|
|
|
header = [tag, 0x81, n];
|
|
|
|
|
} else if (n < 0x10000) {
|
|
|
|
|
header = [tag, 0x82, n >> 8, n & 0xff];
|
|
|
|
|
} else {
|
|
|
|
|
header = [tag, 0x83, n >> 16, n >> 8 & 0xff, n & 0xff];
|
|
|
|
|
}
|
|
|
|
|
return concatBytes([header, c]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<Uint8List> _children(Uint8List b) => der.split(b).children;
|
|
|
|
|
|
|
|
|
|
// cmstest.Edit of Go: b with the element at path replaced by what f
|
|
|
|
|
// returns for it, and the lengths of its ancestors written again.
|
|
|
|
|
Uint8List _edit(Uint8List b, List<int> path, Uint8List Function(Uint8List) f) {
|
|
|
|
|
if (path.isEmpty) return f(b);
|
|
|
|
|
final kids = [..._children(b)];
|
|
|
|
|
kids[path[0]] = _edit(kids[path[0]], path.sublist(1), f);
|
|
|
|
|
return tlv(b[0], kids);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The edit of [c] applied to [base], as tool/cms_go_vectors_test.go does
|
|
|
|
|
/// it.
|
|
|
|
|
Uint8List applyEdit(Uint8List base, Json c) {
|
|
|
|
|
final path = [for (final i in (c['path'] as List?) ?? const []) i as int];
|
|
|
|
|
final op = c['op']! as String;
|
|
|
|
|
final arg = c['arg'] as int?;
|
|
|
|
|
if (op == 'raw') {
|
|
|
|
|
return Uint8List.fromList(base)..[arg!] ^= 1 << (c['bit']! as int);
|
|
|
|
|
}
|
|
|
|
|
return _edit(base, path, (old) {
|
|
|
|
|
switch (op) {
|
|
|
|
|
case 'retag':
|
|
|
|
|
return concatBytes([
|
|
|
|
|
[arg!],
|
|
|
|
|
old.sublist(1),
|
|
|
|
|
]);
|
|
|
|
|
case 'remove':
|
|
|
|
|
return Uint8List(0);
|
|
|
|
|
case 'dup':
|
|
|
|
|
return concatBytes([old, old]);
|
|
|
|
|
case 'null':
|
|
|
|
|
return Uint8List.fromList(const [5, 0]);
|
|
|
|
|
case 'append':
|
|
|
|
|
return tlv(old[0], [
|
|
|
|
|
..._children(old),
|
|
|
|
|
const [5, 0],
|
|
|
|
|
]);
|
|
|
|
|
case 'reverse':
|
|
|
|
|
return tlv(old[0], _children(old).reversed.toList());
|
|
|
|
|
}
|
|
|
|
|
var content = Uint8List.fromList(der.content(old));
|
|
|
|
|
switch (op) {
|
|
|
|
|
case 'flip':
|
|
|
|
|
content[arg!] ^= 1 << (c['bit']! as int);
|
|
|
|
|
case 'trunc':
|
|
|
|
|
content = content.sublist(0, content.length - 1);
|
|
|
|
|
case 'extend':
|
|
|
|
|
content = concatBytes([
|
|
|
|
|
content,
|
|
|
|
|
[arg!],
|
|
|
|
|
]);
|
|
|
|
|
case 'empty':
|
|
|
|
|
content = Uint8List(0);
|
|
|
|
|
default:
|
|
|
|
|
throw StateError('an edit $op');
|
|
|
|
|
}
|
|
|
|
|
return tlv(old[0], [content]);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The bytes of the case [c] of [file]: its der, in hex or in pieces of hex
|
|
|
|
|
/// and certificates of the file, or the edit of one of [bases]; checked
|
|
|
|
|
/// against its SHA-256 when it has one.
|
|
|
|
|
Uint8List caseBytes(Json file, Json c, {Map<String, Uint8List>? bases}) {
|
|
|
|
|
final Uint8List b;
|
|
|
|
|
final der = c['der'];
|
|
|
|
|
if (c['base'] != null) {
|
|
|
|
|
b = applyEdit(bases![c['base']! as String]!, c);
|
|
|
|
|
} else if (der is List) {
|
|
|
|
|
final certs = [for (final h in file['certificates']! as List) hexOf(h)];
|
|
|
|
|
b = concatBytes([for (final p in der) p is int ? certs[p] : hexOf(p)]);
|
|
|
|
|
} else {
|
|
|
|
|
b = der == null ? Uint8List(0) : hexOf(der);
|
|
|
|
|
}
|
|
|
|
|
final sum = c['sha256'];
|
|
|
|
|
if (sum != null) expect(toHex(sha256(b)), sum, reason: 'the bytes of $c');
|
|
|
|
|
return b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The bases of [section] of [file], by their names.
|
|
|
|
|
Map<String, Uint8List> basesOf(Json file, String section) => {
|
|
|
|
|
for (final c in cases(file, section))
|
|
|
|
|
c['name']! as String: caseBytes(file, c),
|
|
|
|
|
};
|
|
|
|
|
|
Stage 5a: ECDSA on P-256, P-384 and P-521, and RSA
nist_curves.dart reads an uncompressed point as Go's
ecdsa.ParseUncompressedPublicKey does, and computes u1 G + u2 Q in
Jacobian coordinates with Shamir's trick, the infinity included.
ecdsa.dart is VerifyASN1: the encoding as cryptobyte reads it, r and s
in [1, n - 1] and never reduced, s above n/2 accepted, the hash cut to
the bits of the order. rsa.dart is VerifyPKCS1v15, which rebuilds the
encoding and compares it whole, and VerifyPSS with a salt of the length
of the hash, with the bytes before emLen that must be zero; and the
hashes of the table, SHA-256, SHA-384 and SHA-512 of package:crypto.
All on BigInt, exact on the VM and compiled to JavaScript, and not
constant-time: a verification sees public values only. The curve
constants are those that the vectors record from Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
/// A short name of the case [c], for the reasons of the expectations.
|
|
|
|
|
String caseName(Json c) =>
|
|
|
|
|
(c['name'] ??
|
|
|
|
|
(c['names'] as List?)?.first ??
|
|
|
|
|
c['fixture'] ??
|
|
|
|
|
'${c['base']} ${c['path']} ${c['op']} ${c['arg'] ?? ''}')
|
|
|
|
|
.toString();
|
|
|
|
|
|
Stage 5a: the CMS reader with the certificate profile of the draft v0.12
cms.dart ports internal/cms of datekeys-go at c531e93, with its order of
checks and its texts: parseCert reads a certificate field by field with
the profile of spec v0.12 §29.10, with the holder from givenName and
surname before the commonName and the issuer from organizationName when
there is no commonName with text; parseSignature reads a detached
SignedData, its certificates, OCSP responses, signers, signed and
unsigned attributes, and SignerInfo.check gives valid, invalid or not
verifiable with the closed table of algorithms, a key of another scheme
invalid; parseToken reads an RFC 3161 token and its TSTInfo field by
field, form errors before algorithm errors, and Token.check verifies it
over a subject. Object identifiers are compared by their bytes, and a
SET OF may repeat an element.
The tests run every case of the vectors on the VM, the fixtures
format3_signed_cms and format3_sealed included, and the part that
cms_vectors.g.dart holds compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Spans
|
|
|
|
|
|
|
|
|
|
/// [view], a view of the bytes of [base], as the vectors place it:
|
|
|
|
|
/// [offset, length].
|
|
|
|
|
List<int> spanOf(Uint8List base, Uint8List view) {
|
|
|
|
|
expect(view.buffer == base.buffer, isTrue, reason: 'not a view');
|
|
|
|
|
final off = view.offsetInBytes - base.offsetInBytes;
|
|
|
|
|
expect(off >= 0 && off + view.length <= base.length, isTrue);
|
|
|
|
|
return [off, view.length];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<int>? spanOrNull(Uint8List base, Uint8List? view) =>
|
|
|
|
|
view == null ? null : spanOf(base, view);
|
|
|
|
|
|
|
|
|
|
List<Object?> algOf(Uint8List base, AlgorithmIdentifier a) => [
|
|
|
|
|
spanOf(base, a.oid),
|
|
|
|
|
spanOrNull(base, a.params),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
List<Object?> algJson(Object? v) {
|
|
|
|
|
final a = v! as Json;
|
|
|
|
|
return [a['oid'], a['params']];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<int> timeOf(Instant t) => [t.seconds, t.nanos];
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// The outcomes
|
|
|
|
|
|
|
|
|
|
/// Expects cms.dart to read the certificate [b] as Go read it: the error of
|
|
|
|
|
/// [c], or its fields, names and validity.
|
|
|
|
|
void expectCert(Json c, Uint8List b) {
|
|
|
|
|
final Cert cert;
|
|
|
|
|
try {
|
|
|
|
|
cert = parseCert(b);
|
|
|
|
|
} on CertificateException catch (e) {
|
|
|
|
|
expect(e.message, c['error'], reason: caseName(c));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final name = caseName(c);
|
|
|
|
|
expect(c['error'], isNull, reason: name);
|
|
|
|
|
expect(cert.raw, b, reason: name);
|
|
|
|
|
expect(cert.hash, sha256(b), reason: name);
|
|
|
|
|
expect(spanOf(b, cert.serial), c['serial'], reason: name);
|
|
|
|
|
expect(spanOf(b, cert.rawIssuer), c['issuer'], reason: name);
|
|
|
|
|
expect(spanOf(b, cert.rawSubject), c['subject'], reason: name);
|
|
|
|
|
expect(spanOrNull(b, cert.ski), c['ski'], reason: name);
|
|
|
|
|
expect(spanOf(b, cert.spki), c['spki'], reason: name);
|
|
|
|
|
expect(timeOf(cert.notBefore), c['not_before'], reason: name);
|
|
|
|
|
expect(timeOf(cert.notAfter), c['not_after'], reason: name);
|
|
|
|
|
expect(cert.holder, c['holder'], reason: name);
|
|
|
|
|
expect(cert.issuerName, c['issuer_name'], reason: name);
|
|
|
|
|
for (final v in (c['valid_at'] as List?) ?? const []) {
|
|
|
|
|
final at = v! as List;
|
|
|
|
|
expect(
|
|
|
|
|
cert.validAt(Instant(at[0]! as int, at[1]! as int)),
|
|
|
|
|
at[2],
|
|
|
|
|
reason: '$name: valid at $at',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String resultName(CmsResult r) => switch (r) {
|
|
|
|
|
CmsResult.valid => 'valid',
|
|
|
|
|
CmsResult.invalid => 'invalid',
|
|
|
|
|
CmsResult.notVerifiable => 'not verifiable',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Expects cms.dart to read the signature [b] as Go read it: the error of
|
|
|
|
|
/// [c], or its certificates, OCSP responses and signers, each with the
|
|
|
|
|
/// result of its check over [message] and [other]; and in a case of the
|
|
|
|
|
/// corpus the names of each certificate and the token of each signer.
|
|
|
|
|
void expectSignature(Json c, Uint8List b, Uint8List message, Uint8List other) {
|
|
|
|
|
final name = caseName(c);
|
|
|
|
|
final SignedData sd;
|
|
|
|
|
try {
|
|
|
|
|
sd = parseSignature(b);
|
|
|
|
|
} on CmsFormException catch (e) {
|
|
|
|
|
expect(e.message, c['error'], reason: name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
expect(c['error'], isNull, reason: name);
|
|
|
|
|
expect(sd.eContent, isNull, reason: name);
|
|
|
|
|
expect(
|
|
|
|
|
[for (final x in sd.certs) spanOf(b, x.raw)],
|
|
|
|
|
c['certs'],
|
|
|
|
|
reason: name,
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
[for (final x in sd.ocsp) spanOf(b, x)],
|
|
|
|
|
c['ocsp'] ?? const [],
|
|
|
|
|
reason: name,
|
|
|
|
|
);
|
|
|
|
|
final signers = cases(c, 'signers');
|
|
|
|
|
expect(sd.signers, hasLength(signers.length), reason: name);
|
|
|
|
|
for (var i = 0; i < signers.length; i++) {
|
|
|
|
|
final s = sd.signers[i];
|
|
|
|
|
final w = signers[i];
|
|
|
|
|
final where = '$name, signer $i';
|
|
|
|
|
expect(sd.certs.indexWhere((x) => identical(x, s.cert)), w['cert']);
|
|
|
|
|
expect(algOf(b, s.digestAlg), algJson(w['digest_alg']), reason: where);
|
|
|
|
|
expect(algOf(b, s.sigAlg), algJson(w['sig_alg']), reason: where);
|
|
|
|
|
expect(spanOf(b, s.signedAttrs), w['signed_attrs'], reason: where);
|
|
|
|
|
expect(spanOf(b, s.messageDigest), w['message_digest'], reason: where);
|
|
|
|
|
expect(spanOf(b, s.signature), w['signature'], reason: where);
|
|
|
|
|
expect(spanOrNull(b, s.token), w['token'], reason: where);
|
|
|
|
|
expect(resultName(s.check(message)), w['result'], reason: where);
|
|
|
|
|
expect(resultName(s.check(other)), w['other_result'], reason: where);
|
|
|
|
|
if (w.containsKey('holder')) {
|
|
|
|
|
expect(s.cert.holder, w['holder'], reason: where);
|
|
|
|
|
expect(s.cert.issuerName, w['issuer'], reason: where);
|
|
|
|
|
final seal = w['seal'] as Json?;
|
|
|
|
|
expect(seal == null, s.token == null, reason: where);
|
|
|
|
|
if (seal != null) {
|
|
|
|
|
final t = expectToken(seal, s.token!, s.signature, corpus: true);
|
|
|
|
|
if (t != null) {
|
|
|
|
|
expect(
|
|
|
|
|
s.cert.validAt(t.genTime),
|
|
|
|
|
seal['signer_valid_at_gen_time'],
|
|
|
|
|
reason: where,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Expects cms.dart to read the token [b] as Go read it: the error of [c]
|
|
|
|
|
/// and its kind, or its fields and its check over [subject]. Returns the
|
|
|
|
|
/// token when it reads.
|
|
|
|
|
Token? expectToken(
|
|
|
|
|
Json c,
|
|
|
|
|
Uint8List b,
|
|
|
|
|
Uint8List subject, {
|
|
|
|
|
bool corpus = false,
|
|
|
|
|
}) {
|
|
|
|
|
final name = caseName(c);
|
|
|
|
|
final Token t;
|
|
|
|
|
try {
|
|
|
|
|
t = parseToken(b);
|
|
|
|
|
} on CmsException catch (e) {
|
|
|
|
|
expect(e.message, c['error'], reason: name);
|
|
|
|
|
expect(
|
|
|
|
|
switch (e) {
|
|
|
|
|
CmsFormException() => 'form',
|
|
|
|
|
CmsAlgorithmException() => 'algorithm',
|
|
|
|
|
},
|
|
|
|
|
c['kind'],
|
|
|
|
|
reason: name,
|
|
|
|
|
);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
expect(c['error'], isNull, reason: name);
|
|
|
|
|
expect(timeOf(t.genTime), c['gen_time'], reason: name);
|
|
|
|
|
expect(t.accuracy.inMicroseconds, c['accuracy_us'], reason: name);
|
|
|
|
|
expect(
|
|
|
|
|
timeOf(addDuration(t.genTime, t.accuracy)),
|
|
|
|
|
c['gen_time_plus_accuracy'],
|
|
|
|
|
reason: name,
|
|
|
|
|
);
|
|
|
|
|
expect(algOf(b, t.imprintAlg), algJson(c['imprint_alg']), reason: name);
|
|
|
|
|
expect(spanOf(b, t.imprint), c['imprint'], reason: name);
|
|
|
|
|
expect(spanOf(b, t.tsa.raw), c['tsa'], reason: name);
|
|
|
|
|
expect(t.imprintIsSha256, c['imprint_sha256'], reason: name);
|
|
|
|
|
expect(t.check(subject), c['check'], reason: name);
|
|
|
|
|
if (corpus) expect(t.tsa.holder, c['tsa_holder'], reason: name);
|
|
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
|
Stage 5a: ECDSA on P-256, P-384 and P-521, and RSA
nist_curves.dart reads an uncompressed point as Go's
ecdsa.ParseUncompressedPublicKey does, and computes u1 G + u2 Q in
Jacobian coordinates with Shamir's trick, the infinity included.
ecdsa.dart is VerifyASN1: the encoding as cryptobyte reads it, r and s
in [1, n - 1] and never reduced, s above n/2 accepted, the hash cut to
the bits of the order. rsa.dart is VerifyPKCS1v15, which rebuilds the
encoding and compares it whole, and VerifyPSS with a salt of the length
of the hash, with the bytes before emLen that must be zero; and the
hashes of the table, SHA-256, SHA-384 and SHA-512 of package:crypto.
All on BigInt, exact on the VM and compiled to JavaScript, and not
constant-time: a verification sees public values only. The curve
constants are those that the vectors record from Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// The tests of each file
|
|
|
|
|
|
|
|
|
|
final _curves = {'P-256': p256, 'P-384': p384, 'P-521': p521};
|
|
|
|
|
|
|
|
|
|
final _hashes = {
|
|
|
|
|
'SHA-256': Sha2.sha256,
|
|
|
|
|
'SHA-384': Sha2.sha384,
|
|
|
|
|
'SHA-512': Sha2.sha512,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// The tests of cms_ecdsa.json.
|
|
|
|
|
void ecdsaTests(Json file) {
|
|
|
|
|
test('the curves are those of Go', () {
|
|
|
|
|
for (final c in cases(file, 'curves')) {
|
|
|
|
|
final curve = _curves[c['name']]!;
|
|
|
|
|
expect(curve.p, BigInt.parse(c['p']! as String, radix: 16));
|
|
|
|
|
expect(curve.n, BigInt.parse(c['n']! as String, radix: 16));
|
|
|
|
|
expect(curve.b, BigInt.parse(c['b']! as String, radix: 16));
|
|
|
|
|
expect(curve.gx, BigInt.parse(c['gx']! as String, radix: 16));
|
|
|
|
|
expect(curve.gy, BigInt.parse(c['gy']! as String, radix: 16));
|
|
|
|
|
expect(curve.p.bitLength, c['bits']);
|
|
|
|
|
expect(curve.byteLength, ((c['bits']! as int) + 7) ~/ 8);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
test('a point reads as ecdsa.ParseUncompressedPublicKey reads it', () {
|
|
|
|
|
for (final c in cases(file, 'points')) {
|
|
|
|
|
final p = decodeUncompressed(_curves[c['curve']]!, hexOf(c['point']));
|
|
|
|
|
expect(p != null, c['ok'], reason: caseName(c));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
test('a signature verifies as ecdsa.VerifyASN1 verifies it', () {
|
|
|
|
|
final keys = [
|
|
|
|
|
for (final k in cases(file, 'keys'))
|
|
|
|
|
decodeUncompressed(_curves[k['curve']]!, hexOf(k['point']))!,
|
|
|
|
|
];
|
|
|
|
|
for (final c in cases(file, 'verify')) {
|
|
|
|
|
expect(
|
|
|
|
|
verifyEcdsaAsn1(
|
|
|
|
|
keys[c['key']! as int],
|
|
|
|
|
hexOf(c['hash']),
|
|
|
|
|
hexOf(c['sig']),
|
|
|
|
|
),
|
|
|
|
|
c['valid'],
|
|
|
|
|
reason: caseName(c),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The tests of cms_rsa.json.
|
|
|
|
|
void rsaTests(Json file) {
|
|
|
|
|
test('the DigestInfo of each hash is that of Go', () {
|
|
|
|
|
final info = file['digest_info']! as Json;
|
|
|
|
|
for (final MapEntry(:key, :value) in _hashes.entries) {
|
|
|
|
|
expect(toHex(value.digestInfoPrefix), info[key], reason: key);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
test(
|
|
|
|
|
'a signature verifies as rsa.VerifyPKCS1v15 and VerifyPSS verify it',
|
|
|
|
|
() {
|
|
|
|
|
final keys = [
|
|
|
|
|
for (final k in cases(file, 'keys'))
|
|
|
|
|
RsaPublicKey(
|
|
|
|
|
BigInt.parse(k['n']! as String, radix: 16),
|
|
|
|
|
k['e']! as int,
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
for (final (i, k) in cases(file, 'keys').indexed) {
|
|
|
|
|
expect(keys[i].n.bitLength, k['bits'], reason: caseName(k));
|
|
|
|
|
}
|
|
|
|
|
for (final c in [...cases(file, 'valid'), ...cases(file, 'verify')]) {
|
|
|
|
|
final key = keys[c['key']! as int];
|
|
|
|
|
final hash = _hashes[c['hash']]!;
|
|
|
|
|
final digest = hexOf(c['digest']);
|
|
|
|
|
final sig = hexOf(c['sig']);
|
|
|
|
|
final valid = c['scheme'] == 'pss'
|
|
|
|
|
? verifyPss(key, hash, digest, sig)
|
|
|
|
|
: verifyPkcs1v15(key, hash, digest, sig);
|
|
|
|
|
expect(valid, c['valid'], reason: caseName(c));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
Stage 5a: the CMS reader with the certificate profile of the draft v0.12
cms.dart ports internal/cms of datekeys-go at c531e93, with its order of
checks and its texts: parseCert reads a certificate field by field with
the profile of spec v0.12 §29.10, with the holder from givenName and
surname before the commonName and the issuer from organizationName when
there is no commonName with text; parseSignature reads a detached
SignedData, its certificates, OCSP responses, signers, signed and
unsigned attributes, and SignerInfo.check gives valid, invalid or not
verifiable with the closed table of algorithms, a key of another scheme
invalid; parseToken reads an RFC 3161 token and its TSTInfo field by
field, form errors before algorithm errors, and Token.check verifies it
over a subject. Object identifiers are compared by their bytes, and a
SET OF may repeat an element.
The tests run every case of the vectors on the VM, the fixtures
format3_signed_cms and format3_sealed included, and the part that
cms_vectors.g.dart holds compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
|
|
|
|
|
/// The tests of cms_certs.json.
|
|
|
|
|
void certTests(Json file) {
|
|
|
|
|
test('a certificate reads as cms.ParseCert reads it', () {
|
|
|
|
|
for (final c in cases(file, 'named')) {
|
|
|
|
|
expectCert(c, caseBytes(file, c));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
test('each certificate edited node by node reads as in Go', () {
|
|
|
|
|
final bases = basesOf(file, 'bases');
|
|
|
|
|
for (final c in cases(file, 'bases')) {
|
|
|
|
|
expectCert(c, bases[c['name']]!);
|
|
|
|
|
}
|
|
|
|
|
for (final c in cases(file, 'edits')) {
|
|
|
|
|
expectCert(c, caseBytes(file, c, bases: bases));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The tests of cms_signatures.json and cms_algorithms.json, whose cases
|
|
|
|
|
/// are in [sections].
|
|
|
|
|
void signatureTests(Json file, List<String> sections) {
|
|
|
|
|
final message = hexOf(file['message']);
|
|
|
|
|
final other = hexOf(file['other_message']);
|
|
|
|
|
for (final section in sections) {
|
|
|
|
|
test('$section: a signature reads and checks as in Go', () {
|
|
|
|
|
for (final c in cases(file, section)) {
|
|
|
|
|
expectSignature(c, caseBytes(file, c), message, other);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (file.containsKey('oids')) {
|
|
|
|
|
test('the object identifiers are those of internal/cms', () {
|
|
|
|
|
final oids = file['oids']! as Json;
|
|
|
|
|
expect(profileOids.keys.toSet(), oids.keys.toSet());
|
|
|
|
|
for (final MapEntry(:key, :value) in profileOids.entries) {
|
|
|
|
|
expect(toHex(value), oids[key], reason: key);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The tests of cms_tokens.json.
|
|
|
|
|
void tokenTests(Json file) {
|
|
|
|
|
final subject = hexOf(file['subject']);
|
|
|
|
|
for (final section in const ['tokens', 'tstinfo']) {
|
|
|
|
|
test('$section: a token reads and checks as in Go', () {
|
|
|
|
|
for (final c in cases(file, section)) {
|
|
|
|
|
expectToken(c, caseBytes(file, c), subject);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The tests of cms_mutations.json.
|
|
|
|
|
void mutationTests(Json file) {
|
|
|
|
|
final message = hexOf(file['message']);
|
|
|
|
|
final other = hexOf(file['other_message']);
|
|
|
|
|
final subject = hexOf(file['subject']);
|
|
|
|
|
test('each signature edited node by node reads and checks as in Go', () {
|
|
|
|
|
final bases = basesOf(file, 'signature_bases');
|
|
|
|
|
for (final c in cases(file, 'signature_bases')) {
|
|
|
|
|
expectSignature(c, bases[c['name']]!, message, other);
|
|
|
|
|
}
|
|
|
|
|
for (final c in cases(file, 'signatures')) {
|
|
|
|
|
expectSignature(c, caseBytes(file, c, bases: bases), message, other);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
test('each token edited node by node reads and checks as in Go', () {
|
|
|
|
|
final bases = basesOf(file, 'token_bases');
|
|
|
|
|
for (final c in cases(file, 'token_bases')) {
|
|
|
|
|
expectToken(c, bases[c['name']]!, subject);
|
|
|
|
|
}
|
|
|
|
|
for (final c in cases(file, 'tokens')) {
|
|
|
|
|
expectToken(c, caseBytes(file, c, bases: bases), subject);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The tests of cms_corpus.json; those of the fixtures need [fixture], the
|
|
|
|
|
/// record of a fixture of testdata/ by its name.
|
|
|
|
|
void corpusTests(Json file, {Json Function(String name)? fixture}) {
|
|
|
|
|
final other = hexOf(file['other_message']);
|
|
|
|
|
test('the signatures of security_cms.json read and check as in Go', () {
|
|
|
|
|
for (final c in cases(file, 'signatures')) {
|
|
|
|
|
final b = caseBytes(file, c);
|
|
|
|
|
expectSignature(c, b, hexOf(c['message']), other);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
test('the seals of security_cms.json read and check as in Go', () {
|
|
|
|
|
for (final c in cases(file, 'seals')) {
|
|
|
|
|
expectToken(c, caseBytes(file, c), hexOf(c['subject']), corpus: true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (fixture == null) return;
|
|
|
|
|
test('the signature and the seal of the fixtures read as in Go', () {
|
|
|
|
|
for (final c in cases(file, 'fixtures')) {
|
|
|
|
|
final area = hexOf(fixture(c['fixture']! as String)['security_cbor']);
|
|
|
|
|
final span = c['span']! as List;
|
|
|
|
|
final start = span[0]! as int;
|
|
|
|
|
final b = Uint8List.sublistView(area, start, start + (span[1]! as int));
|
|
|
|
|
expect(toHex(sha256(b)), c['sha256']);
|
|
|
|
|
final name = {'name': c['fixture']};
|
|
|
|
|
if (c['kind'] == 'signature') {
|
|
|
|
|
expectSignature(
|
|
|
|
|
{...c['signature']! as Json, ...name},
|
|
|
|
|
b,
|
|
|
|
|
hexOf(c['message']),
|
|
|
|
|
other,
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
expectToken(
|
|
|
|
|
{...c['seal']! as Json, ...name},
|
|
|
|
|
b,
|
|
|
|
|
hexOf(c['subject']),
|
|
|
|
|
corpus: true,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|