Format 3, step 2: the codec of BODY, the security area and the head
body.ts, security.ts and head.ts port format3.go of the Go reference at
spec-v0.10:
- body.ts: the frame of BODY (spec 29.2), AREA_LEN, SECURITY_LEN and
HEAD_LEN with their limits, and the zeros of the security area, all
ERR_INTEGRITY.
- security.ts: the empty SECURITY_CBOR that writers write, and the
verdicts X, F0, F1, S0, S1 and S2 of spec 29.7 with the Spanish lines
of the official SDK. The evaluation never fails and carries no code.
- head.ts: HEAD_CBOR in the layers of spec 69.1: R1 and R8 in the CDDL,
R8 by UTF-8 bytes and not by the UTF-16 order of JavaScript strings;
then the comment, the declared author, the paths with pathrule.ts, the
layout of the files, R7 and R9, all ERR_HEAD_INVALID; and the critical
extensions of the new extension object "head". decodeWrittenHead
leaves the extensions to the caller, for the self-check of the writer.
ERR_HEAD_INVALID becomes the 19th normative code, with its gloss.
Tests: the cases of format3_test.go and a few more, with the error
texts and the verdicts of the reference byte for byte, taken from it at
spec-v0.10 with a scratch program. Coverage 100 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// Tests of security.ts: the empty area that writers write, the verdicts of
Documentation of v0.11: modules, verdicts, dependencies and the changelog
Fixes T14 of the review of the session of 1 and 2 October:
- README: the table of modules gains author.ts, ed25519strict.ts, der.ts,
cms.ts, securitycms.ts and note.ts, and names Go at spec-v0.11; the row
of the format 3 gives the verdicts of v0.11, X, F0 to F6 and S0 to S5,
where it said X to S2, and says that evaluateSecurity never throws; the
rows of the writers, lengths.ts, index.ts and testing/ say where the test
vectors come from now, the area of 32 KiB and the public note.
- README: the table of runtime dependencies says what noble does for the
signatures and the seals, and the guards list the importers of noble of
v0.11 and the new guards of testing/; the counts of the corpus of
mutations (210 cases, four that open) and of capsule-vectors.json are
those of today.
- security.test.ts no longer says that the library does not reach the
verdicts of v0.11, nor that its texts are those of spec-v0.10.
- CHANGELOG: an entry for the fixes of the review, and what waits for
v0.12: the reader of certificates (T2, T6, T7, T8), the text of an
issuer without a commonName and the test of cms.test.ts that compares it
with itself.
npm run verify passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
// the signature and of the seal without the context of a capsule, on the
// cases of TestSecurityVerdicts of the Go reference and a few more, and the
// Spanish lines of spec §29.7, as the reference gives them at spec-v0.11.
// The verdicts in the context of a capsule are tested by author.test.ts,
// securitycms.test.ts, security.failure.test.ts, fixtures.test.ts and
// vectors.test.ts.
Format 3, step 2: the codec of BODY, the security area and the head
body.ts, security.ts and head.ts port format3.go of the Go reference at
spec-v0.10:
- body.ts: the frame of BODY (spec 29.2), AREA_LEN, SECURITY_LEN and
HEAD_LEN with their limits, and the zeros of the security area, all
ERR_INTEGRITY.
- security.ts: the empty SECURITY_CBOR that writers write, and the
verdicts X, F0, F1, S0, S1 and S2 of spec 29.7 with the Spanish lines
of the official SDK. The evaluation never fails and carries no code.
- head.ts: HEAD_CBOR in the layers of spec 69.1: R1 and R8 in the CDDL,
R8 by UTF-8 bytes and not by the UTF-16 order of JavaScript strings;
then the comment, the declared author, the paths with pathrule.ts, the
layout of the files, R7 and R9, all ERR_HEAD_INVALID; and the critical
extensions of the new extension object "head". decodeWrittenHead
leaves the extensions to the caller, for the self-check of the writer.
ERR_HEAD_INVALID becomes the 19th normative code, with its gloss.
Tests: the cases of format3_test.go and a few more, with the error
texts and the verdicts of the reference byte for byte, taken from it at
spec-v0.10 with a scratch program. Coverage 100 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
import { describe , expect , it } from 'vitest' ;
import { h , hx } from './testing/testdata.ts' ;
import { arr , b , bn , map , t , u } from './testing/cborhex.ts' ;
import { encodeSecurity , evaluateSecurity , type Verdict , verdictLines , verdictText } from './security.ts' ;
const EMPTY = 'a20071646174656b6579732d73656375726974790101' ;
// An author-signature of alg 1 with a key of 32 zero bytes and a signature
// of 64: 105 bytes, as Go's EncodeAuthorSignature(1, ...) writes it.
const SIGNATURE = map ( [ 0 , u ( 1 ) ] , [ 1 , bn ( 32 ) ] , [ 2 , bn ( 64 ) ] ) ;
const SEAL = map ( [ 0 , u ( 1 ) ] , [ 1 , b ( '010203' ) ] ) ;
// SECURITY_CBOR with keys 2 and 3 when given, as hex.
function security ( signature? : string , seal? : string ) : Uint8Array {
const pairs : [ number , string ] [ ] = [
[ 0 , t ( 'datekeys-security' ) ] ,
[ 1 , u ( 1 ) ] ,
] ;
if ( signature !== undefined ) pairs . push ( [ 2 , b ( signature ) ] ) ;
if ( seal !== undefined ) pairs . push ( [ 3 , b ( seal ) ] ) ;
return h ( map ( . . . pairs ) ) ;
}
describe ( 'encodeSecurity' , ( ) = > {
it ( 'writes the empty area of 22 bytes, which yields F0 and S0' , ( ) = > {
expect ( hx ( encodeSecurity ( ) ) ) . toBe ( EMPTY ) ;
expect ( SIGNATURE . length / 2 ) . toBe ( 105 ) ;
expect ( security ( SIGNATURE ) . length ) . toBe ( 130 ) ;
} ) ;
} ) ;
describe ( 'evaluateSecurity' , ( ) = > {
it . each ( [
[ 'empty' , security ( ) , 'F0' , 'S0' ] ,
[ 'a signature of alg 1' , security ( SIGNATURE ) , 'F1' , 'S0' ] ,
[ 'a seal of seal_type 1' , security ( undefined , SEAL ) , 'F0' , 'S1' ] ,
[ 'both' , security ( SIGNATURE , SEAL ) , 'F1' , 'S1' ] ,
[ 'alg 0' , security ( map ( [ 0 , u ( 0 ) ] , [ 1 , b ( '' ) ] , [ 2 , b ( '' ) ] ) ) , 'F1' , 'S0' ] ,
[ 'a signature that is no map' , security ( '01' ) , 'F1' , 'S0' ] ,
// The first row that holds decides: a seal with an unknown key and an
// unknown seal_type breaks its schema, S2, before its type is read.
[ 'a seal with an unknown key' , security ( undefined , 'a300070141000200' ) , 'F0' , 'S2' ] ,
[ 'seal_type 0' , security ( undefined , map ( [ 0 , u ( 0 ) ] , [ 1 , b ( '01' ) ] ) ) , 'F0' , 'S2' ] ,
[ 'a seal that is not CBOR' , security ( SIGNATURE , 'ff' ) , 'F1' , 'S2' ] ,
[ 'a seal with seal_type 2^32' , security ( undefined , map ( [ 0 , u ( 2 * * 32 ) ] , [ 1 , b ( '' ) ] ) ) , 'F0' , 'S2' ] ,
[ 'a seal with a trailing byte' , security ( undefined , SEAL + '00' ) , 'F0' , 'S2' ] ,
[ 'a seal without its token' , security ( undefined , map ( [ 0 , u ( 1 ) ] ) ) , 'F0' , 'S2' ] ,
[ 'a seal with key 2 in place of its token' , security ( undefined , map ( [ 0 , u ( 1 ) ] , [ 2 , b ( '' ) ] ) ) , 'F0' , 'S2' ] ,
[ 'a seal with an empty token' , security ( undefined , map ( [ 0 , u ( 7 ) ] , [ 1 , b ( '' ) ] ) ) , 'F0' , 'S1' ] ,
[ 'version 2' , h ( map ( [ 0 , t ( 'datekeys-security' ) ] , [ 1 , u ( 2 ) ] ) ) , 'X' , 'X' ] ,
[ 'another type tag' , h ( map ( [ 0 , t ( 'datekeys-head' ) ] , [ 1 , u ( 1 ) ] ) ) , 'X' , 'X' ] ,
[ 'an unknown key 4' , h ( map ( [ 0 , t ( 'datekeys-security' ) ] , [ 1 , u ( 1 ) ] , [ 4 , b ( '01' ) ] ) ) , 'X' , 'X' ] ,
[ 'key 2 not a byte string' , h ( map ( [ 0 , t ( 'datekeys-security' ) ] , [ 1 , u ( 1 ) ] , [ 2 , u ( 1 ) ] ) ) , 'X' , 'X' ] ,
[ 'an empty key 2' , h ( map ( [ 0 , t ( 'datekeys-security' ) ] , [ 1 , u ( 1 ) ] , [ 2 , b ( '' ) ] ) ) , 'X' , 'X' ] ,
[ 'a key 2 of 65537 bytes' , h ( map ( [ 0 , t ( 'datekeys-security' ) ] , [ 1 , u ( 1 ) ] , [ 2 , bn ( 65537 , 1 ) ] ) ) , 'X' , 'X' ] ,
[ 'a byte more' , h ( EMPTY + '00' ) , 'X' , 'X' ] ,
[ 'not CBOR' , new TextEncoder ( ) . encode ( 'security' ) , 'X' , 'X' ] ,
[ 'no key 1' , h ( map ( [ 0 , t ( 'datekeys-security' ) ] ) ) , 'X' , 'X' ] ,
[ 'an array' , h ( arr ( t ( 'datekeys-security' ) , u ( 1 ) ) ) , 'X' , 'X' ] ,
] ) ( '%s: %s and %s' , ( _ , input , signature , seal ) = > {
expect ( evaluateSecurity ( input ) ) . toEqual ( { signature , seal } ) ;
} ) ;
} ) ;
describe ( 'verdictLines' , ( ) = > {
const F1 = 'No se ha comprobado ninguna firma: trátala como no firmada.' ;
const S1 = 'Lleva un sello de tiempo que esta versión no sabe comprobar: aquí no prueba nada.' ;
const S2 = 'El sello de tiempo es ilegible: no prueba nada.' ;
it ( 'shows X alone, and otherwise the signature and then the seal unless it is S0' , ( ) = > {
const lines = ( signature : Verdict , seal : Verdict ) : string [ ] = > verdictLines ( { signature , seal } ) ;
expect ( lines ( 'X' , 'X' ) ) . toEqual ( [ 'No se han podido comprobar la firma ni el sello: trátala como no firmada y sin fecha probada.' ] ) ;
expect ( lines ( 'F0' , 'S0' ) ) . toEqual ( [ 'Sin firma de autor.' ] ) ;
expect ( lines ( 'F0' , 'S1' ) ) . toEqual ( [ 'Sin firma de autor.' , S1 ] ) ;
expect ( lines ( 'F0' , 'S2' ) ) . toEqual ( [ 'Sin firma de autor.' , S2 ] ) ;
expect ( lines ( 'F1' , 'S0' ) ) . toEqual ( [ F1 ] ) ;
expect ( lines ( 'F1' , 'S1' ) ) . toEqual ( [ F1 , S1 ] ) ;
expect ( lines ( 'F1' , 'S2' ) ) . toEqual ( [ F1 , S2 ] ) ;
expect ( verdictText ( 'S0' ) ) . toBe ( '' ) ;
} ) ;
// F6 and S4 write the names that the reader found (spec §29.7, §29.10): a signer sealed before the round time or not, a signer who does not count.
it ( 'writes the lines of F6 and S4 from the signers and the authority that were found' , ( ) = > {
const t = { seconds : 1_790_000_000 , nanos : 0 } ;
const line = ( holder : string , before : boolean , result = 'valid' ) = > ( { holder , issuer : ` emisor de ${ holder } ` , result , sealTime : t , before } ) ;
const lines = verdictLines ( {
signature : 'F6' ,
seal : 'S4' ,
detail : { signers : [ line ( 'Ana' , true ) , line ( 'Luis' , false ) ] , foreign : [ line ( 'Otro' , true , 'invalid' ) ] , sealHolder : 'TSA' , sealTime : t } ,
} ) ;
expect ( lines ) . toEqual ( [
'Firmado con un certificado a nombre de Ana, Luis. DateKeys no comprueba quién lo emitió: para eso, exporta la firma a un validador oficial.' ,
' Ana (emisor según su certificado: emisor de Ana), sellado el 2026-09-21T14:13:20Z, antes de la fecha de apertura.' ,
' Luis (emisor según su certificado: emisor de Luis), sellado el 2026-09-21T14:13:20Z, no antes de la fecha de apertura.' ,
' Otro firmante, Otro: invalid. No cuenta.' ,
'Según un sello a nombre de TSA, existía el 2026-09-21T14:13:20Z, antes de que la cápsula pudiera abrirse. DateKeys no comprueba quién emitió el sello.' ,
] ) ;
} ) ;
Documentation of v0.11: modules, verdicts, dependencies and the changelog
Fixes T14 of the review of the session of 1 and 2 October:
- README: the table of modules gains author.ts, ed25519strict.ts, der.ts,
cms.ts, securitycms.ts and note.ts, and names Go at spec-v0.11; the row
of the format 3 gives the verdicts of v0.11, X, F0 to F6 and S0 to S5,
where it said X to S2, and says that evaluateSecurity never throws; the
rows of the writers, lengths.ts, index.ts and testing/ say where the test
vectors come from now, the area of 32 KiB and the public note.
- README: the table of runtime dependencies says what noble does for the
signatures and the seals, and the guards list the importers of noble of
v0.11 and the new guards of testing/; the counts of the corpus of
mutations (210 cases, four that open) and of capsule-vectors.json are
those of today.
- security.test.ts no longer says that the library does not reach the
verdicts of v0.11, nor that its texts are those of spec-v0.10.
- CHANGELOG: an entry for the fixes of the review, and what waits for
v0.12: the reader of certificates (T2, T6, T7, T8), the text of an
issuer without a commonName and the test of cms.test.ts that compares it
with itself.
npm run verify passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
// The verdicts of spec v0.11 (§29.7) whose text is fixed, and those whose text names a key, a holder or a time, F3, F4, F6
// and S4, which verdictLines writes from what the reader found and verdictText leaves empty.
it ( 'has the text of the verdicts of v0.11 that do not name anything, and none for those that do' , ( ) = > {
expect ( verdictText ( 'F2' ) ) . toBe ( 'La firma no corresponde a este contenido.' ) ;
expect ( verdictText ( 'F5' ) ) . toBe ( 'Faltan firmas o sellos que la propia cápsula exige: trátala como no firmada.' ) ;
expect ( verdictText ( 'S3' ) ) . toBe ( 'El sello no corresponde a este contenido.' ) ;
expect ( verdictText ( 'S5' ) ) . toBe ( 'Sellado después de la fecha de apertura: no prueba nada anterior.' ) ;
for ( const v of [ 'F3' , 'F4' , 'F6' , 'S4' ] as const ) expect ( verdictText ( v ) , v ) . toBe ( '' ) ;
} ) ;
Format 3, step 2: the codec of BODY, the security area and the head
body.ts, security.ts and head.ts port format3.go of the Go reference at
spec-v0.10:
- body.ts: the frame of BODY (spec 29.2), AREA_LEN, SECURITY_LEN and
HEAD_LEN with their limits, and the zeros of the security area, all
ERR_INTEGRITY.
- security.ts: the empty SECURITY_CBOR that writers write, and the
verdicts X, F0, F1, S0, S1 and S2 of spec 29.7 with the Spanish lines
of the official SDK. The evaluation never fails and carries no code.
- head.ts: HEAD_CBOR in the layers of spec 69.1: R1 and R8 in the CDDL,
R8 by UTF-8 bytes and not by the UTF-16 order of JavaScript strings;
then the comment, the declared author, the paths with pathrule.ts, the
layout of the files, R7 and R9, all ERR_HEAD_INVALID; and the critical
extensions of the new extension object "head". decodeWrittenHead
leaves the extensions to the caller, for the self-check of the writer.
ERR_HEAD_INVALID becomes the 19th normative code, with its gloss.
Tests: the cases of format3_test.go and a few more, with the error
texts and the verdicts of the reference byte for byte, taken from it at
spec-v0.10 with a scratch program. Coverage 100 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
} ) ;