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.
DateKeys/internal/cms/hostile_test.go

201 lines
11 KiB

package cms_test
import (
"bytes"
"crypto/elliptic"
"crypto/sha256"
"encoding/asn1"
"errors"
"testing"
"time"
"g.activething.com/go/DateKeys/internal/cms"
"g.activething.com/go/DateKeys/internal/cms/cmstest"
"g.activething.com/go/DateKeys/internal/der"
)
var oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
// tstInfo builds a TSTInfo of RFC 3161 over subject, with the fields after
// genTime that the test gives.
func tstInfo(subject []byte, genTime []byte, after ...[]byte) []byte {
return cmstest.Seq(append(tstFields(subject, genTime), after...)...)
}
// tstFields are the five required fields of a TSTInfo.
func tstFields(subject []byte, genTime []byte) [][]byte {
h := sha256.Sum256(subject)
return [][]byte{
cmstest.Int(1), cmstest.OID(asn1.ObjectIdentifier{1, 2, 3, 4}),
cmstest.Seq(cmstest.Seq(cmstest.OID(oidSHA256)), cmstest.Octets(h[:])),
cmstest.Int(42), genTime,
}
}
func parses(t *testing.T, tsa cmstest.Signer, info []byte, subject []byte) (*cms.Token, error) {
t.Helper()
tok, err := cms.ParseToken(cmstest.TokenRaw(info, tsa))
if err == nil && !tok.Check(subject) {
t.Fatal("a token that parses does not verify")
}
return tok, err
}
// Review of the CMS reader: the TSTInfo is an OCTET STRING, so it is checked
// field by field, and what DER forbids, or what would make a seal after the
// opening date look before it, is a form error (spec §29.11, S2).
func TestTSTInfoStrict(t *testing.T) {
tsa := cmstest.NewECDSA("TSA", elliptic.P256(), from, to)
subject := []byte("seal subject")
good := cmstest.GeneralizedTime("20260930120000Z")
if tok, err := parses(t, tsa, tstInfo(subject, good), subject); err != nil || !tok.GenTime.Equal(now) || tok.Accuracy != 0 {
t.Fatalf("the baseline: %v", err)
}
if tok, err := parses(t, tsa, tstInfo(subject, cmstest.GeneralizedTime("20260930120000.5Z"), cmstest.Seq(cmstest.Int(2), cmstest.TLV(0x80, []byte{5}))), subject); err != nil ||
tok.GenTime.Sub(now) != 500*time.Millisecond || tok.Accuracy != 2*time.Second+5*time.Millisecond {
t.Fatalf("a fraction and an accuracy: %v", err)
}
// The optional fields, in their order, each once.
tsaName := cmstest.TLV(0xa0, cmstest.TLV(0xa4, cmstest.Seq()))
exts := cmstest.TLV(0xa1, cmstest.Seq(cmstest.OID(asn1.ObjectIdentifier{1, 2, 3}), cmstest.Octets(nil)))
ms := func(c ...byte) []byte { return cmstest.TLV(0x80, c) }
us := func(c ...byte) []byte { return cmstest.TLV(0x81, c) }
for name, tc := range map[string]struct {
after [][]byte
accuracy time.Duration
}{
"an empty accuracy": {[][]byte{cmstest.Seq()}, 0},
"only millis": {[][]byte{cmstest.Seq(ms(5))}, 5 * time.Millisecond},
"only micros": {[][]byte{cmstest.Seq(us(7))}, 7 * time.Microsecond},
"millis and micros of 999": {[][]byte{cmstest.Seq(ms(0x03, 0xe7), us(0x03, 0xe7))}, 999*time.Millisecond + 999*time.Microsecond},
"millis of 128": {[][]byte{cmstest.Seq(ms(0, 0x80))}, 128 * time.Millisecond},
"millis of 300": {[][]byte{cmstest.Seq(ms(0x01, 0x2c))}, 300 * time.Millisecond},
"seconds, millis and micros": {[][]byte{cmstest.Seq(cmstest.Int(1), ms(5), us(7))}, time.Second + 5*time.Millisecond + 7*time.Microsecond},
"2^31 - 1 seconds": {[][]byte{cmstest.Seq(cmstest.Int(1<<31 - 1))}, (1<<31 - 1) * time.Second},
"ordering TRUE": {[][]byte{cmstest.Bool(true)}, 0},
"an accuracy and ordering TRUE": {[][]byte{cmstest.Seq(cmstest.Int(1)), cmstest.Bool(true)}, time.Second},
"a nonce": {[][]byte{cmstest.Int(99)}, 0},
"ordering TRUE and a nonce": {[][]byte{cmstest.Bool(true), cmstest.Int(99)}, 0},
"a tsa": {[][]byte{tsaName}, 0},
"extensions": {[][]byte{exts}, 0},
"every field": {[][]byte{cmstest.Seq(cmstest.Int(3)), cmstest.Bool(true), cmstest.Int(99), tsaName, exts}, 3 * time.Second},
} {
tok, err := parses(t, tsa, tstInfo(subject, good, tc.after...), subject)
if err != nil || tok.Accuracy != tc.accuracy {
t.Errorf("%s: %v", name, err)
}
}
fields := tstFields(subject, good)
with := func(i int, v []byte) []byte {
f := append([][]byte(nil), fields...)
f[i] = v
return cmstest.Seq(f...)
}
h := sha256.Sum256(subject)
big := []byte{1, 0, 0, 0, 0, 0, 0, 0, 5} // 2^64 + 5, which wraps around to 5 in 64 bits
for name, info := range map[string][]byte{
"a negative accuracy": tstInfo(subject, good, cmstest.Seq(cmstest.Int(-31536000))),
"an accuracy of -1": tstInfo(subject, good, cmstest.Seq(cmstest.Int(-1))),
"an accuracy that overflows": tstInfo(subject, good, cmstest.Seq(cmstest.Int(9223372037))),
"an accuracy of 2^31 seconds": tstInfo(subject, good, cmstest.Seq(cmstest.Int(1<<31))),
"an accuracy of 2^64 + 5 seconds": tstInfo(subject, good, cmstest.Seq(cmstest.IntBytes(big))),
"millis of 0": tstInfo(subject, good, cmstest.Seq(ms(0))),
"millis of 1000": tstInfo(subject, good, cmstest.Seq(ms(0x03, 0xe8))),
"millis of 5000": tstInfo(subject, good, cmstest.Seq(ms(0x13, 0x88))),
"millis of -1": tstInfo(subject, good, cmstest.Seq(ms(0xff))),
"millis of 5 in two bytes": tstInfo(subject, good, cmstest.Seq(ms(0, 5))),
"millis of 2^64 + 5": tstInfo(subject, good, cmstest.Seq(ms(big...))),
"empty millis": tstInfo(subject, good, cmstest.Seq(ms())),
"millis constructed": tstInfo(subject, good, cmstest.Seq(cmstest.TLV(0xa0, cmstest.Int(5)))),
"micros of 0": tstInfo(subject, good, cmstest.Seq(us(0))),
"micros of 1000": tstInfo(subject, good, cmstest.Seq(us(0x03, 0xe8))),
"micros before millis": tstInfo(subject, good, cmstest.Seq(us(1), ms(1))),
"a field after the micros": tstInfo(subject, good, cmstest.Seq(us(1), cmstest.TLV(0x82, []byte{1}))),
"millis as an INTEGER": tstInfo(subject, good, cmstest.Seq(cmstest.Int(1), cmstest.Int(5))),
"genTime with an offset": tstInfo(subject, cmstest.GeneralizedTime("20260930130000+0100")),
"genTime with a trailing zero": tstInfo(subject, cmstest.GeneralizedTime("20260930120000.50Z")),
"genTime without seconds": tstInfo(subject, cmstest.GeneralizedTime("202609301200Z")),
"genTime as a UTCTime": tstInfo(subject, cmstest.UTCTime("260930120000Z")),
"ordering FALSE written": tstInfo(subject, good, cmstest.Bool(false)),
"ordering of two bytes": tstInfo(subject, good, cmstest.TLV(0x01, []byte{0xff, 0xff})),
"two accuracies": tstInfo(subject, good, cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.Int(1))),
"two orderings": tstInfo(subject, good, cmstest.Bool(true), cmstest.Bool(true)),
"an extra INTEGER at the end": tstInfo(subject, good, cmstest.Int(7), cmstest.Int(8), cmstest.Int(9)),
"two tsa fields": tstInfo(subject, good, tsaName, tsaName),
"two extensions": tstInfo(subject, good, exts, exts),
"a field after the extensions": tstInfo(subject, good, exts, cmstest.Int(1)),
"a field out of order": tstInfo(subject, good, cmstest.Int(7), cmstest.Seq(cmstest.Int(1))),
"a reserved tag in the extensions": tstInfo(subject, good, cmstest.TLV(0xa1, cmstest.TLV(0x0e, []byte{0x41}))),
"an unused-bits BIT STRING inside": tstInfo(subject, good, cmstest.TLV(0xa1, cmstest.TLV(0x03, []byte{7, 0xff}))),
"version 2": cmstest.Seq(cmstest.Int(2)),
"version 2, the fields complete": with(0, cmstest.Int(2)),
"version 2^64 + 1": with(0, cmstest.IntBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 1})),
"the version as an ENUMERATED": with(0, cmstest.TLV(0x0a, []byte{1})),
"the policy as an INTEGER": with(1, cmstest.Int(1)),
"the serialNumber as OCTETS": with(3, cmstest.Octets([]byte{42})),
"the messageImprint as a SET": with(2, cmstest.TLV(0x31, cmstest.Seq(cmstest.OID(oidSHA256)), cmstest.Octets(h[:]))),
"a messageImprint of three fields": with(2, cmstest.Seq(cmstest.Seq(cmstest.OID(oidSHA256)), cmstest.Octets(h[:]), cmstest.Null())),
"a messageImprint of one field": with(2, cmstest.Seq(cmstest.Seq(cmstest.OID(oidSHA256)))),
"a messageImprint algorithm as a SET": with(2, cmstest.Seq(cmstest.TLV(0x31, cmstest.OID(oidSHA256)), cmstest.Octets(h[:]))),
"a hashedMessage as a BIT STRING": with(2, cmstest.Seq(cmstest.Seq(cmstest.OID(oidSHA256)), cmstest.BitString(h[:]))),
"four fields": cmstest.Seq(fields[:4]...),
"a SET": cmstest.TLV(0x31, fields...),
"not a SEQUENCE": cmstest.Int(1),
"not DER": append(tstInfo(subject, good), 0),
} {
if _, err := cms.ParseToken(cmstest.TokenRaw(info, tsa)); !errors.Is(err, cms.ErrForm) {
t.Errorf("%s: %v", name, err)
}
}
}
// retag changes the identifier octet of the first element of the content of
// the last constructed child of der, a SET OF SignerInfo.
func signerInfoTag(t *testing.T, b []byte, tag byte) []byte {
t.Helper()
_, ci, _ := der.Split(b)
_, content, _ := der.Split(ci[1])
_, sd, _ := der.Split(content[0])
signerInfos := sd[len(sd)-1]
_, infos, _ := der.Split(signerInfos)
at := bytes.Index(b, infos[0])
if at < 0 {
t.Fatal("no SignerInfo")
}
out := bytes.Clone(b)
out[at] = tag
return out
}
func TestSignatureStrictness(t *testing.T) {
a := cmstest.NewECDSA("Ana", elliptic.P256(), from, to)
good := cmstest.Signature(msg, cmstest.Options{}, a)
for name, b := range map[string][]byte{
"ContentInfo as a SET": append([]byte{0x31}, good[1:]...),
"ContentInfo as [3]": append([]byte{0xa3}, good[1:]...),
"SignerInfo as a SET": signerInfoTag(t, good, 0x31),
"SignerInfo as [5]": signerInfoTag(t, good, 0xa5),
"an attribute without a value": cmstest.Signature(msg, cmstest.Options{ExtraAttrs: [][]byte{cmstest.Seq(cmstest.OID(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}), cmstest.Set(0x31))}}, a),
"a second content-type": cmstest.Signature(msg, cmstest.Options{ExtraAttrs: [][]byte{cmstest.Seq(cmstest.OID(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}), cmstest.Set(0x31, cmstest.OID(cmstest.OIDData)))}}, a),
} {
if _, err := cms.ParseSignature(b); !errors.Is(err, cms.ErrForm) {
t.Errorf("%s: %v", name, err)
}
}
// Both signing-certificate attributes: the v2 counts, the other decides nothing.
both := cmstest.Signature(msg, cmstest.Options{SigCertV1: true}, a)
if sd, err := cms.ParseSignature(both); err != nil || sd.Signers[0].Check(msg) != cms.Valid {
t.Errorf("signing-certificate beside the v2: %v", err)
}
// PSS parameters that write their default are not accepted as verifiable.
rsa := cmstest.NewRSA("Luis", 2048, from, to)
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{PSS: true, PSSTrailer: true}, rsa))
if err != nil || sd.Signers[0].Check(msg) != cms.NotVerifiable {
t.Errorf("a PSS trailerField written: %v", err)
}
sd, err = cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{PSS: true}, rsa))
if err != nil || sd.Signers[0].Check(msg) != cms.Valid {
t.Errorf("PSS without it: %v", err)
}
}

Powered by TurnKey Linux.