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.
125 lines
5.7 KiB
125 lines
5.7 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 {
|
|
h := sha256.Sum256(subject)
|
|
fields := [][]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,
|
|
}
|
|
return cmstest.Seq(append(fields, after...)...)
|
|
}
|
|
|
|
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) {
|
|
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)
|
|
}
|
|
for name, info := range map[string][]byte{
|
|
"a negative accuracy": tstInfo(subject, good, cmstest.Seq(cmstest.Int(-31536000))),
|
|
"an accuracy that overflows": tstInfo(subject, good, cmstest.Seq(cmstest.Int(9223372037))),
|
|
"millis of 0": tstInfo(subject, good, cmstest.Seq(cmstest.TLV(0x80, []byte{0}))),
|
|
"millis of 5000": tstInfo(subject, good, cmstest.Seq(cmstest.TLV(0x80, []byte{0x13, 0x88}))),
|
|
"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")),
|
|
"ordering FALSE written": tstInfo(subject, good, cmstest.TLV(0x01, []byte{0})),
|
|
"an extra INTEGER at the end": tstInfo(subject, good, cmstest.Int(7), cmstest.Int(8), cmstest.Int(9)),
|
|
"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)),
|
|
"not a SEQUENCE": cmstest.Int(1),
|
|
} {
|
|
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)
|
|
}
|
|
}
|