The builder of the tests, cmstest: - NewCert writes a certificate from the DER of its tbsCertificate, field by field: names of any string type with any bytes (UTF8String, PrintableString with an underscore or an at sign, IA5String, TeletexString, BMPString of odd length or with a surrogate, VisibleString, NumericString), an attribute twice or none, no version, times with a fraction, an extension twice, a compressed EC key, an even modulus, and any signature. A Signer made so serves Signature and Token. - Options for the version of a SignerInfo, the hashAlgorithm and the certHash of an ESSCertIDv2, a signatureAlgorithm other than the one of the key, a certificate twice, two content-type attributes, an attribute with an arc of 2^31, a SignerInfo twice, BER, signerInfos out of order, two signature-time-stamp attributes, and edits of the SignedData and of each SignerInfo. - Token options for any accuracy, a genTime of free text, ordering FALSE, a field after the last, an imprint of any length, no message-digest, a CRL in crls and the certificate of the authority twice. - Edits of the DER after signing: Edit, Retag, Withdraw (a SignerInfo removed), WithoutTimeStamp (a CAdES-T removed), Merge (a co-signature) and Indefinite. The tests of internal/cms and internal/der fail for each check of cms.go, cert.go, verify.go and der.go. A mutation run, which replaces each leaf of each condition by false and by true, one at a time, kills every mutant that is not equivalent to the code it mutates. FuzzParseSignature, FuzzParseToken, FuzzParseCert and FuzzDERCheck, seeded with security_cms.json and with what cmstest builds: no panic, what Check accepts Split reads, and the parsers fail only with ErrForm or ErrAlgorithm. capsule: the case of a seal outside the validity of the certificate gave an invalid seal; it now tests a certificate that expired before a valid seal (out of validity) apart from an authority that was not valid at its time (invalid seal). SIGNERS out of order, empty, too long, with 31 bytes or with a hash twice are F1 beside a CMS signature that is valid for the AUTHOR_MESSAGE of those SIGNERS, and the names of certificates show as spec v0.12 §29.7 says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>v0.12
parent
b06ab4ffa2
commit
4ce4d59c8d
@ -0,0 +1,287 @@
|
||||
package cmstest
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"math/big"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
// CertSpec describes a certificate that NewCert writes field by field, from
|
||||
// the DER of its tbsCertificate, to write what crypto/x509 does not let one
|
||||
// write: names of any string type with any bytes, an attribute twice or none,
|
||||
// no version, times with a fraction, an extension twice, a compressed key or
|
||||
// an even modulus, and any signature, which DateKeys does not check (spec
|
||||
// §29.10). Each field holds the DER of its element as it goes in the
|
||||
// certificate; nil takes the default of a certificate of version 3 of the key.
|
||||
type CertSpec struct {
|
||||
// CN names the subject, CN = CN in a UTF8String, when Subject is nil.
|
||||
CN string
|
||||
// From and To are the validity, 2020-01-01 to 2040-01-01 by default,
|
||||
// written as RFC 5280 does when NotBefore and NotAfter are nil.
|
||||
From, To time.Time
|
||||
// Version is the field [0] EXPLICIT of the version, INTEGER 2 (version
|
||||
// 3) by default; NoVersion leaves it out, as a version 1 certificate.
|
||||
Version []byte
|
||||
NoVersion bool
|
||||
// Serial is the serialNumber, a random positive INTEGER by default.
|
||||
Serial []byte
|
||||
// SigAlg is the AlgorithmIdentifier of the signature, in tbsCertificate
|
||||
// and after it: that of the key with SHA-256 by default.
|
||||
SigAlg []byte
|
||||
// Issuer and Subject are the names; the issuer is the subject by default,
|
||||
// as in a self-signed certificate.
|
||||
Issuer, Subject []byte
|
||||
// NotBefore and NotAfter are the times of validity as written.
|
||||
NotBefore, NotAfter []byte
|
||||
// SPKI is the SubjectPublicKeyInfo, that of the key by default.
|
||||
SPKI []byte
|
||||
// UniqueIDs are written after the SPKI: [1] issuerUniqueID and [2]
|
||||
// subjectUniqueID.
|
||||
UniqueIDs [][]byte
|
||||
// SKI is the keyIdentifier of the extension subjectKeyIdentifier of the
|
||||
// default extensions, and what a sid by subjectKeyIdentifier names: 20
|
||||
// bytes of the hash of the SPKI by default.
|
||||
SKI []byte
|
||||
// Extensions are the Extension elements of [3]: subjectKeyIdentifier and
|
||||
// keyUsage by default. An empty, non-nil slice writes [3] with an empty
|
||||
// SEQUENCE, and NoExtensions writes no [3].
|
||||
Extensions [][]byte
|
||||
NoExtensions bool
|
||||
// After are elements written at the end of tbsCertificate.
|
||||
After [][]byte
|
||||
// Signature is the BIT STRING of the signature as written; by default
|
||||
// the key signs tbsCertificate.
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// NewCert returns a signer of key whose certificate is the one that spec
|
||||
// describes.
|
||||
func NewCert(spec CertSpec, key crypto.Signer) Signer {
|
||||
from, to := spec.From, spec.To
|
||||
if from.IsZero() {
|
||||
from = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
if to.IsZero() {
|
||||
to = time.Date(2040, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
serial := spec.Serial
|
||||
if serial == nil {
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(1<<62))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
serial = BigInt(n.Add(n, big.NewInt(1)))
|
||||
}
|
||||
sigAlg := spec.SigAlg
|
||||
if sigAlg == nil {
|
||||
sigAlg = AlgID(OIDECDSA256)
|
||||
if _, ok := key.(*rsa.PrivateKey); ok {
|
||||
sigAlg = AlgID(OIDSHA256RSA, Null())
|
||||
}
|
||||
}
|
||||
subject := spec.Subject
|
||||
if subject == nil {
|
||||
cn := spec.CN
|
||||
if cn == "" {
|
||||
cn = "DateKeys test"
|
||||
}
|
||||
subject = Name(ATV(OIDCommonName, UTF8(cn)))
|
||||
}
|
||||
issuer := spec.Issuer
|
||||
if issuer == nil {
|
||||
issuer = subject
|
||||
}
|
||||
notBefore, notAfter := spec.NotBefore, spec.NotAfter
|
||||
if notBefore == nil {
|
||||
notBefore = CertTimeOf(from)
|
||||
}
|
||||
if notAfter == nil {
|
||||
notAfter = CertTimeOf(to)
|
||||
}
|
||||
spki := spec.SPKI
|
||||
if spki == nil {
|
||||
spki = SPKI(key.Public())
|
||||
}
|
||||
ski := spec.SKI
|
||||
if ski == nil {
|
||||
h := sha256.Sum256(spki)
|
||||
ski = h[:20]
|
||||
}
|
||||
var tbs [][]byte
|
||||
if !spec.NoVersion {
|
||||
version := spec.Version
|
||||
if version == nil {
|
||||
version = tlv(0xa0, Int(2))
|
||||
}
|
||||
tbs = append(tbs, version)
|
||||
}
|
||||
tbs = append(tbs, serial, sigAlg, issuer, Seq(notBefore, notAfter), subject, spki)
|
||||
tbs = append(tbs, spec.UniqueIDs...)
|
||||
if !spec.NoExtensions {
|
||||
exts := spec.Extensions
|
||||
if exts == nil {
|
||||
exts = [][]byte{ExtSKI(ski), ExtKeyUsage()}
|
||||
}
|
||||
tbs = append(tbs, tlv(0xa3, Seq(exts...)))
|
||||
}
|
||||
tbsDER := Seq(append(tbs, spec.After...)...)
|
||||
signature := spec.Signature
|
||||
if signature == nil {
|
||||
_, sig := sign(key, Options{Hash: crypto.SHA256}, sum(crypto.SHA256, tbsDER))
|
||||
signature = BitString(sig)
|
||||
}
|
||||
c := &Cert{Raw: Seq(tbsDER, sigAlg, signature), RawIssuer: issuer, Serial: serial}
|
||||
if spec.Extensions == nil && !spec.NoExtensions || spec.SKI != nil {
|
||||
c.SubjectKeyId = ski
|
||||
}
|
||||
return Signer{Cert: c, Key: key}
|
||||
}
|
||||
|
||||
// The attribute types of a name.
|
||||
var (
|
||||
OIDCommonName = asn1.ObjectIdentifier{2, 5, 4, 3}
|
||||
OIDSurname = asn1.ObjectIdentifier{2, 5, 4, 4}
|
||||
OIDSerialNumber = asn1.ObjectIdentifier{2, 5, 4, 5}
|
||||
OIDCountry = asn1.ObjectIdentifier{2, 5, 4, 6}
|
||||
OIDOrganization = asn1.ObjectIdentifier{2, 5, 4, 10}
|
||||
OIDOrgUnit = asn1.ObjectIdentifier{2, 5, 4, 11}
|
||||
OIDGivenName = asn1.ObjectIdentifier{2, 5, 4, 42}
|
||||
|
||||
OIDSKI = asn1.ObjectIdentifier{2, 5, 29, 14}
|
||||
OIDKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 15}
|
||||
)
|
||||
|
||||
// Name is a Name with one RelativeDistinguishedName for each attribute, in
|
||||
// the order given.
|
||||
func Name(atvs ...[]byte) []byte {
|
||||
rdns := make([][]byte, len(atvs))
|
||||
for i, a := range atvs {
|
||||
rdns[i] = tlv(0x31, a)
|
||||
}
|
||||
return Seq(rdns...)
|
||||
}
|
||||
|
||||
// RDN is a RelativeDistinguishedName of several attributes, in the order
|
||||
// given, for NameOf.
|
||||
func RDN(atvs ...[]byte) []byte { return tlv(0x31, atvs...) }
|
||||
|
||||
// NameOf is a Name of the RelativeDistinguishedName elements given.
|
||||
func NameOf(rdns ...[]byte) []byte { return Seq(rdns...) }
|
||||
|
||||
// ATV is an AttributeTypeAndValue.
|
||||
func ATV(oid asn1.ObjectIdentifier, value []byte) []byte { return Seq(OID(oid), value) }
|
||||
|
||||
// The string types of a name, with the bytes as given: those that break
|
||||
// their type too.
|
||||
|
||||
// UTF8 is a UTF8String.
|
||||
func UTF8(s string) []byte { return tlv(0x0c, []byte(s)) }
|
||||
|
||||
// Numeric is a NumericString.
|
||||
func Numeric(s string) []byte { return tlv(0x12, []byte(s)) }
|
||||
|
||||
// Printable is a PrintableString.
|
||||
func Printable(s string) []byte { return tlv(0x13, []byte(s)) }
|
||||
|
||||
// Teletex is a TeletexString.
|
||||
func Teletex(s string) []byte { return tlv(0x14, []byte(s)) }
|
||||
|
||||
// IA5 is an IA5String.
|
||||
func IA5(s string) []byte { return tlv(0x16, []byte(s)) }
|
||||
|
||||
// Visible is a VisibleString.
|
||||
func Visible(s string) []byte { return tlv(0x1a, []byte(s)) }
|
||||
|
||||
// BMP is a BMPString with the bytes as given: of odd length, or with a
|
||||
// surrogate.
|
||||
func BMP(b []byte) []byte { return tlv(0x1e, b) }
|
||||
|
||||
// BMPText is the BMPString of s in UTF-16BE; a code point outside the BMP
|
||||
// becomes a surrogate pair.
|
||||
func BMPText(s string) []byte {
|
||||
var b []byte
|
||||
for _, u := range utf16.Encode([]rune(s)) {
|
||||
b = append(b, byte(u>>8), byte(u))
|
||||
}
|
||||
return BMP(b)
|
||||
}
|
||||
|
||||
// Extension is an Extension, with critical only when it is true, as DER
|
||||
// writes it.
|
||||
func Extension(oid asn1.ObjectIdentifier, critical bool, value []byte) []byte {
|
||||
f := [][]byte{OID(oid)}
|
||||
if critical {
|
||||
f = append(f, Bool(true))
|
||||
}
|
||||
return Seq(append(f, Octets(value))...)
|
||||
}
|
||||
|
||||
// ExtSKI is the extension subjectKeyIdentifier with the keyIdentifier id.
|
||||
func ExtSKI(id []byte) []byte { return Extension(OIDSKI, false, Octets(id)) }
|
||||
|
||||
// ExtKeyUsage is the extension keyUsage with digitalSignature.
|
||||
func ExtKeyUsage() []byte { return Extension(OIDKeyUsage, true, []byte{0x03, 0x02, 0x07, 0x80}) }
|
||||
|
||||
// SPKI is the SubjectPublicKeyInfo of a public key, as crypto/x509 writes it:
|
||||
// an EC point uncompressed, and RSA with NULL parameters.
|
||||
func SPKI(pub crypto.PublicKey) []byte {
|
||||
b, err := x509.MarshalPKIXPublicKey(pub)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// CurveOID returns the named curve of curve: P-256, P-384 or P-521.
|
||||
func CurveOID(curve elliptic.Curve) asn1.ObjectIdentifier {
|
||||
switch curve {
|
||||
case elliptic.P384():
|
||||
return OIDP384
|
||||
case elliptic.P521():
|
||||
return OIDP521
|
||||
}
|
||||
return OIDP256
|
||||
}
|
||||
|
||||
// Uncompressed returns the point of pub, uncompressed.
|
||||
func Uncompressed(pub *ecdsa.PublicKey) []byte {
|
||||
b, err := pub.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Compressed returns the point of pub, compressed (SEC 1 2.3.3).
|
||||
func Compressed(pub *ecdsa.PublicKey) []byte {
|
||||
u := Uncompressed(pub)
|
||||
n := (len(u) - 1) / 2
|
||||
return append([]byte{2 | u[len(u)-1]&1}, u[1:1+n]...)
|
||||
}
|
||||
|
||||
// SPKIEC is the SubjectPublicKeyInfo of id-ecPublicKey with the parameters
|
||||
// and the point given.
|
||||
func SPKIEC(params, point []byte) []byte {
|
||||
return Seq(AlgID(OIDECPublicKey, params), BitString(point))
|
||||
}
|
||||
|
||||
// SPKICompressed is the SubjectPublicKeyInfo of pub with its point
|
||||
// compressed, which the table of spec §29.10 does not have.
|
||||
func SPKICompressed(pub *ecdsa.PublicKey) []byte {
|
||||
return SPKIEC(OID(CurveOID(pub.Curve)), Compressed(pub))
|
||||
}
|
||||
|
||||
// SPKIRSA is the SubjectPublicKeyInfo of rsaEncryption with NULL parameters
|
||||
// and the modulus and exponent given: an even modulus, or a size outside the
|
||||
// table.
|
||||
func SPKIRSA(n, e *big.Int) []byte {
|
||||
return Seq(AlgID(OIDRSA, Null()), BitString(Seq(BigInt(n), BigInt(e))))
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
package cmstest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"slices"
|
||||
|
||||
"g.activething.com/go/DateKeys/internal/der"
|
||||
)
|
||||
|
||||
// The edits of the DER of a signature, a token or a certificate: what an
|
||||
// attacker, or a signing application of another country, does to the bytes
|
||||
// after they are signed. A path lists the index of a child at each level,
|
||||
// from the element given: in a signature or a token, 1, 0 is the SignedData.
|
||||
|
||||
// Children returns the encodings of the children of the constructed element b.
|
||||
func Children(b []byte) [][]byte {
|
||||
_, kids, err := der.Split(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return kids
|
||||
}
|
||||
|
||||
// At returns the element at path in b.
|
||||
func At(b []byte, path ...int) []byte {
|
||||
for _, i := range path {
|
||||
b = Children(b)[i]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Edit returns b with the element at path replaced by what f returns for it,
|
||||
// and the lengths of its ancestors written again. f may return nil, to remove
|
||||
// the element, or several elements one after the other.
|
||||
func Edit(b []byte, f func(old []byte) []byte, path ...int) []byte {
|
||||
if len(path) == 0 {
|
||||
return f(b)
|
||||
}
|
||||
kids := slices.Clone(Children(b))
|
||||
kids[path[0]] = Edit(kids[path[0]], f, path[1:]...)
|
||||
return tlv(b[0], kids...)
|
||||
}
|
||||
|
||||
// Retag returns an edit that changes the identifier octet of an element.
|
||||
func Retag(tag byte) func([]byte) []byte {
|
||||
return func(b []byte) []byte { return append([]byte{tag}, b[1:]...) }
|
||||
}
|
||||
|
||||
// Replace returns an edit that puts elems in the place of an element.
|
||||
func Replace(elems ...[]byte) func([]byte) []byte {
|
||||
return func([]byte) []byte { return bytes.Join(elems, nil) }
|
||||
}
|
||||
|
||||
// Append returns an edit that adds elems at the end of the children of a
|
||||
// constructed element.
|
||||
func Append(elems ...[]byte) func([]byte) []byte {
|
||||
return func(b []byte) []byte { return tlv(b[0], append(slices.Clone(Children(b)), elems...)...) }
|
||||
}
|
||||
|
||||
// Indefinite returns the constructed element b with an indefinite length:
|
||||
// BER, which DER forbids (X.690 10.1).
|
||||
func Indefinite(b []byte) []byte {
|
||||
c, err := der.Content(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
out := append([]byte{b[0], 0x80}, c...)
|
||||
return append(out, 0, 0)
|
||||
}
|
||||
|
||||
// SignedDataPath is the path of the SignedData in a signature or a token.
|
||||
var SignedDataPath = []int{1, 0}
|
||||
|
||||
// SignerInfosPath returns the path of the signerInfos of the signature or the
|
||||
// token b: the last field of its SignedData.
|
||||
func SignerInfosPath(b []byte) []int {
|
||||
return append(slices.Clone(SignedDataPath), len(Children(At(b, SignedDataPath...)))-1)
|
||||
}
|
||||
|
||||
// sid returns the two forms of the SignerIdentifier of s.
|
||||
func sid(s Signer) (issuerAndSerial, ski []byte) {
|
||||
return Seq(s.Cert.RawIssuer, s.Cert.Serial), tlv(0x80, s.Cert.SubjectKeyId)
|
||||
}
|
||||
|
||||
// editSigner applies f to the SignerInfo of s in the signature b, and removes
|
||||
// it when f returns nil.
|
||||
func editSigner(b []byte, s Signer, f func(info []byte) []byte) []byte {
|
||||
ias, ski := sid(s)
|
||||
return Edit(b, func(set []byte) []byte {
|
||||
var out [][]byte
|
||||
for _, info := range Children(set) {
|
||||
if id := Children(info)[1]; bytes.Equal(id, ias) || s.Cert.SubjectKeyId != nil && bytes.Equal(id, ski) {
|
||||
if info = f(info); info == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
out = append(out, info)
|
||||
}
|
||||
return Set(0x31, out...)
|
||||
}, SignerInfosPath(b)...)
|
||||
}
|
||||
|
||||
// Withdraw returns the signature b without the SignerInfo of s: a signature
|
||||
// withdrawn. Its certificate stays.
|
||||
func Withdraw(b []byte, s Signer) []byte {
|
||||
return editSigner(b, s, func([]byte) []byte { return nil })
|
||||
}
|
||||
|
||||
// WithoutTimeStamp returns the signature b with the unsigned attributes of
|
||||
// the SignerInfo of s removed: its CAdES-T withdrawn. What the SignerInfo
|
||||
// signs does not change.
|
||||
func WithoutTimeStamp(b []byte, s Signer) []byte {
|
||||
return editSigner(b, s, func(info []byte) []byte {
|
||||
f := Children(info)
|
||||
if n := len(f); n > 0 && f[n-1][0] == 0xa1 {
|
||||
f = f[:n-1]
|
||||
}
|
||||
return Seq(f...)
|
||||
})
|
||||
}
|
||||
|
||||
// Merge returns the co-signature that joins the signatures sigs of one
|
||||
// message, as a signing application adds a SignerInfo to a signature: the
|
||||
// digest algorithms and the certificates of all, each once, and their
|
||||
// SignerInfo, each in DER order.
|
||||
func Merge(sigs ...[]byte) []byte {
|
||||
var algs, certs, infos [][]byte
|
||||
var version, encap []byte
|
||||
for _, s := range sigs {
|
||||
f := Children(At(s, SignedDataPath...))
|
||||
version, encap = f[0], f[2]
|
||||
algs = append(algs, Children(f[1])...)
|
||||
for _, x := range f[3:] {
|
||||
switch x[0] {
|
||||
case 0xa0:
|
||||
certs = append(certs, Children(x)...)
|
||||
case 0x31:
|
||||
infos = append(infos, Children(x)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
once := func(e [][]byte) [][]byte {
|
||||
slices.SortFunc(e, bytes.Compare)
|
||||
return slices.CompactFunc(e, bytes.Equal)
|
||||
}
|
||||
fields := [][]byte{version, Set(0x31, once(algs)...), encap}
|
||||
if len(certs) > 0 {
|
||||
fields = append(fields, Set(0xa0, once(certs)...))
|
||||
}
|
||||
fields = append(fields, Set(0x31, infos...))
|
||||
return Seq(OID(OIDSignedData), tlv(0xa0, Seq(fields...)))
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
package cms_test
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"g.activething.com/go/DateKeys/internal/cms"
|
||||
"g.activething.com/go/DateKeys/internal/cms/cmstest"
|
||||
"g.activething.com/go/DateKeys/internal/der"
|
||||
)
|
||||
|
||||
// The keys and the certificates of the tests of the reader, made once: an
|
||||
// RSA key takes time to generate. The certificates are made field by field,
|
||||
// with the profile of spec §29.10, and are valid from 2020 to 2040.
|
||||
var (
|
||||
ecKey = cmstest.ECKey(elliptic.P256())
|
||||
ecKey2 = cmstest.ECKey(elliptic.P256())
|
||||
rsaKey = cmstest.RSAKey(2048)
|
||||
|
||||
ana = cmstest.NewCert(cmstest.CertSpec{CN: "Ana López"}, ecKey)
|
||||
luis = cmstest.NewCert(cmstest.CertSpec{CN: "Luis Gómez"}, rsaKey)
|
||||
tsa = cmstest.NewCert(cmstest.CertSpec{CN: "TSA de prueba"}, ecKey2)
|
||||
)
|
||||
|
||||
// path joins paths of cmstest.Edit.
|
||||
func path(parts ...any) []int {
|
||||
var out []int
|
||||
for _, p := range parts {
|
||||
switch x := p.(type) {
|
||||
case int:
|
||||
out = append(out, x)
|
||||
case []int:
|
||||
out = append(out, x...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// firstSignerInfo is the path of the first SignerInfo of a signature.
|
||||
func firstSignerInfo(b []byte) []int { return path(cmstest.SignerInfosPath(b), 0) }
|
||||
|
||||
// wantForm checks that the signature b breaks the profile (F1).
|
||||
func wantForm(t *testing.T, name string, b []byte) {
|
||||
t.Helper()
|
||||
if _, err := cms.ParseSignature(b); !errors.Is(err, cms.ErrForm) {
|
||||
t.Errorf("%s: %v, want a form error", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// signerOf parses the signature b, which must meet the profile, and returns
|
||||
// its only SignerInfo.
|
||||
func signerOf(t *testing.T, name string, b []byte) *cms.SignerInfo {
|
||||
t.Helper()
|
||||
sd, err := cms.ParseSignature(b)
|
||||
if err != nil || len(sd.Signers) != 1 {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
return sd.Signers[0]
|
||||
}
|
||||
|
||||
// resultOf is the result of the only SignerInfo of the signature b over msg.
|
||||
func resultOf(t *testing.T, name string, b []byte) cms.Result {
|
||||
t.Helper()
|
||||
return signerOf(t, name, b).Check(msg)
|
||||
}
|
||||
|
||||
func isForm(err error) bool { return errors.Is(err, cms.ErrForm) }
|
||||
|
||||
func isAlgorithm(err error) bool { return errors.Is(err, cms.ErrAlgorithm) }
|
||||
|
||||
// contentOf returns the content octets of the DER element b.
|
||||
func contentOf(b []byte) []byte {
|
||||
c, err := der.Content(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func sha1Sum(b []byte) []byte {
|
||||
s := sha1.Sum(b)
|
||||
return s[:]
|
||||
}
|
||||
@ -0,0 +1,348 @@
|
||||
package cms_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"encoding/asn1"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"g.activething.com/go/DateKeys/internal/cms"
|
||||
"g.activething.com/go/DateKeys/internal/cms/cmstest"
|
||||
)
|
||||
|
||||
// Spec §29.10, "Forma", rules 1 to 4, and the rules that follow them: each
|
||||
// case breaks one check of the reader, and the signature is F1.
|
||||
func TestSignatureFormRules(t *testing.T) {
|
||||
good := cmstest.Signature(msg, cmstest.Options{}, ana)
|
||||
both := cmstest.Signature(msg, cmstest.Options{}, ana, luis)
|
||||
tok := func(sig []byte) []byte { return cmstest.Token(sig, now, cmstest.TokenOptions{}, tsa) }
|
||||
sealed := cmstest.Signature(msg, cmstest.Options{Token: tok}, ana)
|
||||
sd := cmstest.SignedDataPath
|
||||
si := firstSignerInfo(good)
|
||||
attrsOf := func(mutate func(attrs [][]byte) [][]byte) []byte {
|
||||
return cmstest.Signature(msg, cmstest.Options{Mutate: mutate}, ana)
|
||||
}
|
||||
extra := func(attrs ...[]byte) []byte { return cmstest.Signature(msg, cmstest.Options{ExtraAttrs: attrs}, ana) }
|
||||
h := sha256.Sum256(ana.Cert.Raw)
|
||||
v2 := func(value []byte) func([][]byte) [][]byte {
|
||||
return func(a [][]byte) [][]byte {
|
||||
a[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV2), cmstest.Set(0x31, value))
|
||||
return a
|
||||
}
|
||||
}
|
||||
unknown := asn1.ObjectIdentifier{1, 2, 3, 4}
|
||||
reversed := func(b []byte) []byte {
|
||||
k := slices.Clone(cmstest.Children(b))
|
||||
slices.Reverse(k)
|
||||
return cmstest.TLV(b[0], k...)
|
||||
}
|
||||
same := func(c1, c2 cmstest.CertSpec) []byte {
|
||||
a, b := cmstest.NewCert(c1, ecKey), cmstest.NewCert(c2, ecKey2)
|
||||
return cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{b.Cert.Raw}}, a)
|
||||
}
|
||||
ocsp := func(n int64) []byte {
|
||||
return cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(n)))
|
||||
}
|
||||
twoOCSP := cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{ocsp(1), ocsp(2)}}, ana)
|
||||
|
||||
for name, b := range map[string][]byte{
|
||||
// Rule 1: DER, the ContentInfo and the SignedData.
|
||||
"BER": cmstest.Signature(msg, cmstest.Options{BER: true}, ana),
|
||||
"a ContentInfo with a third element": cmstest.Edit(good, cmstest.Append(cmstest.Null())),
|
||||
"a ContentInfo of only its type": cmstest.Seq(cmstest.OID(cmstest.OIDSignedData)),
|
||||
"the content as [1]": cmstest.Edit(good, cmstest.Retag(0xa1), 1),
|
||||
"the content type id-data": cmstest.Edit(good, cmstest.Replace(cmstest.OID(cmstest.OIDData)), 0),
|
||||
"the content type an INTEGER": cmstest.Edit(good, cmstest.Replace(cmstest.Int(1)), 0),
|
||||
"[0] with an element more": cmstest.Edit(good, cmstest.Append(cmstest.Null()), 1),
|
||||
"[0] empty": cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0xa0)), 1),
|
||||
"[0] holding a SET": cmstest.Edit(good, cmstest.Retag(0x31), sd...),
|
||||
"a SignedData of two fields": cmstest.Edit(good, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[:2]...) }, sd...),
|
||||
"a SignedData without signerInfos": cmstest.Edit(good, func(b []byte) []byte { k := cmstest.Children(b); return cmstest.Seq(k[:len(k)-1]...) }, sd...),
|
||||
"a field after signerInfos": cmstest.Edit(good, cmstest.Append(cmstest.Set(0x31)), sd...),
|
||||
"the version as an OCTET STRING": cmstest.Edit(good, cmstest.Retag(0x04), path(sd, 0)...),
|
||||
"digestAlgorithms as a SEQUENCE": cmstest.Edit(good, cmstest.Retag(0x30), path(sd, 1)...),
|
||||
"digestAlgorithms out of order": cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0x31, cmstest.HashAlg(crypto.SHA512), cmstest.HashAlg(crypto.SHA256))), path(sd, 1)...),
|
||||
"a digestAlgorithm that is an INTEGER": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.Int(1))), path(sd, 1)...),
|
||||
"a digestAlgorithm that is a SET": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.TLV(0x31, cmstest.OID(cmstest.OIDSHA256)))), path(sd, 1)...),
|
||||
"a digestAlgorithm without an OID": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.Seq(cmstest.Int(1)))), path(sd, 1)...),
|
||||
"an empty digestAlgorithm": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.Seq())), path(sd, 1)...),
|
||||
"a digestAlgorithm of three fields": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.AlgID(cmstest.OIDSHA256, cmstest.Null(), cmstest.Null()))), path(sd, 1)...),
|
||||
"encapContentInfo as a SET": cmstest.Edit(good, cmstest.Retag(0x31), path(sd, 2)...),
|
||||
"an empty encapContentInfo": cmstest.Edit(good, cmstest.Replace(cmstest.Seq()), path(sd, 2)...),
|
||||
"encapContentInfo of three fields": cmstest.Edit(good, cmstest.Append(cmstest.TLV(0xa0, cmstest.Octets(msg)), cmstest.Null()), path(sd, 2)...),
|
||||
"eContentType an INTEGER": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.Int(1))), path(sd, 2)...),
|
||||
"eContentType id-ct-TSTInfo": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo))), path(sd, 2)...),
|
||||
"rule 2: an eContent in the signature": cmstest.Edit(good, cmstest.Append(cmstest.TLV(0xa0, cmstest.Octets(msg))), path(sd, 2)...),
|
||||
"certificates out of order": cmstest.Edit(both, reversed, path(sd, 3)...),
|
||||
"a CertificateChoice [4]": cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{cmstest.TLV(0xa4, cmstest.Null())}}, ana),
|
||||
"a CertificateChoice that is a SET": cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{cmstest.Set(0x31, cmstest.Null())}}, ana),
|
||||
"rule 3: a CRL in crls": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1))}}, ana),
|
||||
"rule 3: a CRL of the shape of an OCSP": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(0)))}}, ana),
|
||||
"rule 3: crls out of order": cmstest.Edit(twoOCSP, reversed, path(sd, 4)...),
|
||||
"rule 3: another revocation format": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(unknown), cmstest.Null())}}, ana),
|
||||
"rule 3: a revocation format of one": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP))}}, ana),
|
||||
"rule 3: a revocation format by number": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.Int(2), cmstest.Null())}}, ana),
|
||||
"signerInfos as a SEQUENCE": cmstest.Edit(good, cmstest.Retag(0x30), cmstest.SignerInfosPath(good)...),
|
||||
"no SignerInfo": cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0x31)), cmstest.SignerInfosPath(good)...),
|
||||
"signerInfos out of order": cmstest.Signature(msg, cmstest.Options{Unsorted: true}, ana, luis),
|
||||
|
||||
// Rule 3: the SignerInfo and its sid.
|
||||
"a SignerInfo without signedAttrs": cmstest.Edit(good, func(b []byte) []byte { k := cmstest.Children(b); return cmstest.Seq(k[0], k[1], k[2], k[4], k[5]) }, si...),
|
||||
"signedAttrs as [1]": cmstest.Edit(good, cmstest.Retag(0xa1), path(si, 3)...),
|
||||
"a SignerInfo without its signature": cmstest.Edit(good, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[:5]...) }, si...),
|
||||
"the version of a SignerInfo as OCTETS": cmstest.Edit(good, cmstest.Retag(0x04), path(si, 0)...),
|
||||
"the version 2": cmstest.Signature(msg, cmstest.Options{Version: 2}, ana),
|
||||
"the version 3 with issuerAndSerialNumber": cmstest.Signature(msg, cmstest.Options{Version: 3}, ana),
|
||||
"the version 1 with subjectKeyIdentifier": cmstest.Signature(msg, cmstest.Options{Version: 1, SKI: true}, ana),
|
||||
"the version 4 with subjectKeyIdentifier": cmstest.Signature(msg, cmstest.Options{Version: 4, SKI: true}, ana),
|
||||
"the version 257": cmstest.Edit(good, cmstest.Replace(cmstest.Int(257)), path(si, 0)...),
|
||||
"the digestAlgorithm as a SET": cmstest.Edit(good, cmstest.Retag(0x31), path(si, 2)...),
|
||||
"the signatureAlgorithm as a SET": cmstest.Edit(good, cmstest.Retag(0x31), path(si, 4)...),
|
||||
"the digestAlgorithm of a SignerInfo, no OID": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.Int(1))), path(si, 2)...),
|
||||
"a signatureAlgorithm without an OID": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.Int(1))), path(si, 4)...),
|
||||
"an empty signatureAlgorithm": cmstest.Edit(good, cmstest.Replace(cmstest.Seq()), path(si, 4)...),
|
||||
"the signature as a BIT STRING": cmstest.Edit(good, func(b []byte) []byte { return cmstest.BitString(contentOf(b)) }, path(si, 5)...),
|
||||
"a field after the unsigned attributes": cmstest.Signature(msg, cmstest.Options{Token: tok, EditSignerInfo: func(f [][]byte) [][]byte { return append(f, cmstest.Null()) }}, ana),
|
||||
"a field [2] after the signature": cmstest.Signature(msg, cmstest.Options{EditSignerInfo: func(f [][]byte) [][]byte { return append(f, cmstest.TLV(0xa2, cmstest.BigArcAttr())) }}, ana),
|
||||
"a sid of three elements": cmstest.Edit(good, cmstest.Append(cmstest.Null()), path(si, 1)...),
|
||||
"a sid of one element": cmstest.Edit(good, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[0]) }, path(si, 1)...),
|
||||
"the issuer of the sid as a SET": cmstest.Edit(good, cmstest.Retag(0x31), path(si, 1, 0)...),
|
||||
"the serial of the sid as an OCTET STRING": cmstest.Edit(good, cmstest.Retag(0x04), path(si, 1, 1)...),
|
||||
"the serial of another certificate": cmstest.Edit(good, cmstest.Replace(cmstest.Int(12345)), path(si, 1, 1)...),
|
||||
"a sid of another choice": cmstest.Edit(good, cmstest.Retag(0x81), path(si, 1)...),
|
||||
"a subjectKeyIdentifier of no certificate": cmstest.Edit(cmstest.Signature(msg, cmstest.Options{SKI: true}, ana), cmstest.Replace(cmstest.TLV(0x80, []byte("other"))), path(si, 1)...),
|
||||
"no certificate of the signer": cmstest.Signature(msg, cmstest.Options{OmitCert: true}, ana),
|
||||
"two certificates of one issuer and serial": same(cmstest.CertSpec{CN: "A", Serial: cmstest.Int(7)}, cmstest.CertSpec{CN: "B", Serial: cmstest.Int(7), Issuer: cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("A")))}),
|
||||
"two certificates of one keyIdentifier": cmstest.Signature(msg, cmstest.Options{SKI: true, ExtraCerts: [][]byte{cmstest.NewCert(cmstest.CertSpec{CN: "B", SKI: ana.Cert.SubjectKeyId}, ecKey2).Cert.Raw}}, ana),
|
||||
"an empty keyIdentifier, a certificate of none": cmstest.Signature(msg, cmstest.Options{SKI: true}, cmstest.NewCert(cmstest.CertSpec{CN: "Sin SKI", NoExtensions: true}, ecKey)),
|
||||
"two SignerInfo of one certificate": cmstest.Signature(msg, cmstest.Options{}, ana, ana),
|
||||
"one SignerInfo twice": cmstest.Signature(msg, cmstest.Options{SignerInfoTwice: true}, ana),
|
||||
"the signer's certificate breaks the profile": cmstest.Signature(msg, cmstest.Options{}, cmstest.NewCert(cmstest.CertSpec{CN: "Ana", NoVersion: true}, ecKey)),
|
||||
|
||||
// Rule 4 and the rules after it: the attributes.
|
||||
"signedAttrs out of order": cmstest.Signature(msg, cmstest.Options{UnsortedAttrs: true}, ana),
|
||||
"an attribute as a SET": extra(cmstest.TLV(0x31, cmstest.OID(unknown), cmstest.Set(0x31, cmstest.Null()))),
|
||||
"an attribute of three fields": extra(cmstest.Seq(cmstest.OID(unknown), cmstest.Set(0x31, cmstest.Null()), cmstest.Null())),
|
||||
"an attribute of one field": extra(cmstest.Seq(cmstest.OID(unknown))),
|
||||
"an attribute that is an INTEGER": extra(cmstest.Int(5)),
|
||||
"the values of an attribute as a SEQUENCE": extra(cmstest.Seq(cmstest.OID(unknown), cmstest.Seq(cmstest.Null()))),
|
||||
"an attribute whose type is an INTEGER": extra(cmstest.Seq(cmstest.Int(1), cmstest.Set(0x31, cmstest.Null()))),
|
||||
"an unknown attribute without a value": extra(cmstest.Seq(cmstest.OID(unknown), cmstest.Set(0x31))),
|
||||
"values of an attribute out of order": extra(cmstest.Seq(cmstest.OID(unknown), cmstest.TLV(0x31, cmstest.Int(2), cmstest.Int(1)))),
|
||||
"two content-type attributes": cmstest.Signature(msg, cmstest.Options{ContentType2: true}, ana),
|
||||
// Two values, id-data first in DER order: one value is required, not
|
||||
// the first of several.
|
||||
"a content-type of two values": attrsOf(func(a [][]byte) [][]byte {
|
||||
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDData), cmstest.OID(cmstest.OIDTSTInfo))
|
||||
return a
|
||||
}),
|
||||
"a message-digest of two values": attrsOf(func(a [][]byte) [][]byte {
|
||||
s := sha256.Sum256(msg)
|
||||
a[1] = cmstest.Attr(cmstest.OIDMessageDigest, cmstest.Octets(s[:]), cmstest.Octets(append(s[:], 0)))
|
||||
return a
|
||||
}),
|
||||
"a second content-type without a value": extra(cmstest.Seq(cmstest.OID(cmstest.OIDContentType), cmstest.Set(0x31))),
|
||||
"no content-type": attrsOf(func(a [][]byte) [][]byte { return a[1:] }),
|
||||
"the content-type id-signedData": attrsOf(func(a [][]byte) [][]byte {
|
||||
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDSignedData))
|
||||
return a
|
||||
}),
|
||||
"the content-type id-ct-TSTInfo": attrsOf(func(a [][]byte) [][]byte {
|
||||
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDTSTInfo))
|
||||
return a
|
||||
}),
|
||||
"a content-type that is OCTETS": attrsOf(func(a [][]byte) [][]byte {
|
||||
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.Octets([]byte("data")))
|
||||
return a
|
||||
}),
|
||||
"no message-digest": cmstest.Signature(msg, cmstest.Options{NoMessageDigest: true}, ana),
|
||||
"two message-digest attributes": attrsOf(func(a [][]byte) [][]byte { return append(a, a[1]) }),
|
||||
"a message-digest that is [0]": attrsOf(func(a [][]byte) [][]byte {
|
||||
s := sha256.Sum256(msg)
|
||||
a[1] = cmstest.Attr(cmstest.OIDMessageDigest, cmstest.TLV(0x80, s[:]))
|
||||
return a
|
||||
}),
|
||||
"no signing-certificate-v2": cmstest.Signature(msg, cmstest.Options{NoSigCertV2: true}, ana),
|
||||
"only a signing-certificate": cmstest.Signature(msg, cmstest.Options{NoSigCertV2: true, SigCertV1: true}, ana),
|
||||
"two signing-certificate-v2 attributes": attrsOf(func(a [][]byte) [][]byte { return append(a, a[2]) }),
|
||||
"a signing-certificate-v2 of two values": v2Values(h[:]),
|
||||
"a SigningCertificateV2 as a SET": attrsOf(v2(cmstest.TLV(0x31, cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:])))))),
|
||||
"an empty SigningCertificateV2": attrsOf(v2(cmstest.Seq())),
|
||||
"its certs as a SET": attrsOf(v2(cmstest.Seq(cmstest.TLV(0x31, cmstest.Seq(cmstest.Octets(h[:])))))),
|
||||
"its certs empty": attrsOf(v2(cmstest.Seq(cmstest.Seq()))),
|
||||
"an ESSCertIDv2 as a SET": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.TLV(0x31, cmstest.Octets(h[:])))))),
|
||||
"an empty ESSCertIDv2": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq())))),
|
||||
"an ESSCertIDv2 of only its algorithm": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.HashAlg(crypto.SHA256)))))),
|
||||
"a certHash as [0]": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.TLV(0x80, h[:])))))),
|
||||
"a hashAlgorithm without an OID": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Int(1)), cmstest.Octets(h[:])))))),
|
||||
"an ESSCertIDv2 of SHA-1": cmstest.Signature(msg, cmstest.Options{ESSHashAlg: cmstest.HashAlg(crypto.SHA1)}, ana),
|
||||
"an ESSCertIDv2 of SHA-256 with an INTEGER": cmstest.Signature(msg, cmstest.Options{ESSHashAlg: cmstest.AlgID(cmstest.OIDSHA256, cmstest.Int(0))}, ana),
|
||||
"the hash of another certificate": cmstest.Signature(msg, cmstest.Options{ESSCert: luis.Cert.Raw}, ana),
|
||||
"two signature-time-stamp attributes": cmstest.Signature(msg, cmstest.Options{Token: tok, TimeStamps2: true}, ana),
|
||||
"a signature-time-stamp of two values": cmstest.Signature(msg, cmstest.Options{Token: tok, Token2: true}, ana),
|
||||
"unsigned attributes out of order": cmstest.Edit(sealed, func(b []byte) []byte {
|
||||
k := append(slices.Clone(cmstest.Children(b)), cmstest.Attr(asn1.ObjectIdentifier{1, 2, 3, 4}, cmstest.Null()))
|
||||
slices.SortFunc(k, func(x, y []byte) int { return bytes.Compare(y, x) })
|
||||
return cmstest.TLV(0xa1, k...)
|
||||
}, path(firstSignerInfo(sealed), 6)...),
|
||||
"an unsigned attribute without a value": cmstest.Signature(msg, cmstest.Options{ExtraUnsigned: [][]byte{cmstest.Seq(cmstest.OID(unknown), cmstest.Set(0x31))}}, ana),
|
||||
} {
|
||||
wantForm(t, name, b)
|
||||
}
|
||||
}
|
||||
|
||||
// v2Values is a signature whose signing-certificate-v2 has two values, the
|
||||
// first of them in DER order the right one: a longer certHash sorts after.
|
||||
func v2Values(h []byte) []byte {
|
||||
return cmstest.Signature(msg, cmstest.Options{Mutate: func(a [][]byte) [][]byte {
|
||||
a[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV2), cmstest.Set(0x31, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(h)))), cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 33)))))))
|
||||
return a
|
||||
}}, ana)
|
||||
}
|
||||
|
||||
// What decides nothing (spec §29.10): another choice of CertificateChoices, a
|
||||
// certificate twice or one that breaks the profile, OCSP responses, the other
|
||||
// attributes, signed or not, a signing-certificate beside the v2, and an
|
||||
// ESSCertIDv2 with its hashAlgorithm written.
|
||||
func TestSignatureDecidesNothing(t *testing.T) {
|
||||
tok := func(sig []byte) []byte { return cmstest.Token(sig, now, cmstest.TokenOptions{}, tsa) }
|
||||
v1 := cmstest.NewCert(cmstest.CertSpec{CN: "Intermedia v1", NoVersion: true}, ecKey2)
|
||||
for name, o := range map[string]cmstest.Options{
|
||||
"an attribute certificate [1]": {ExtraCerts: [][]byte{cmstest.TLV(0xa1, cmstest.Seq(cmstest.Int(1)))}},
|
||||
"the other choices [0], [2] and [3]": {ExtraCerts: [][]byte{cmstest.TLV(0xa0, cmstest.Null()), cmstest.TLV(0xa2, cmstest.Null()), cmstest.TLV(0xa3, cmstest.Null())}},
|
||||
"the certificate twice": {ExtraCerts: [][]byte{ana.Cert.Raw}},
|
||||
"a certificate of version 1": {ExtraCerts: [][]byte{v1.Cert.Raw}},
|
||||
"a certificate that is not one": {ExtraCerts: [][]byte{cmstest.Seq(cmstest.Int(1))}},
|
||||
"two OCSP responses": {OCSP: cmstest.Seq(cmstest.Int(0)), CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(9)))}},
|
||||
"an attribute with an arc of 2^31": {ExtraAttrs: [][]byte{cmstest.BigArcAttr()}},
|
||||
"a signing-time": {ExtraAttrs: [][]byte{cmstest.Attr(cmstest.OIDSigningTime, cmstest.UTCTime("260930120000Z"))}},
|
||||
"an unknown attribute of two values": {ExtraAttrs: [][]byte{cmstest.Attr(asn1.ObjectIdentifier{1, 2, 3, 4}, cmstest.Int(1), cmstest.Int(2))}},
|
||||
"a signing-certificate beside the v2": {SigCertV1: true},
|
||||
"a wrong signing-certificate, and v2": {ExtraAttrs: [][]byte{cmstest.Attr(cmstest.OIDSigCertV1, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 20))))))}},
|
||||
"an ESSCertIDv2 of SHA-256 written": {ESSHashAlg: cmstest.HashAlg(crypto.SHA256)},
|
||||
"an ESSCertIDv2 of SHA-256 with NULL": {ESSHashAlg: cmstest.AlgID(cmstest.OIDSHA256, cmstest.Null())},
|
||||
"an ESSCertIDv2 of SHA-384": {ESSHashAlg: cmstest.HashAlg(crypto.SHA384)},
|
||||
"an ESSCertIDv2 of SHA-512": {ESSHashAlg: cmstest.HashAlg(crypto.SHA512)},
|
||||
"an unknown unsigned attribute": {Token: tok, ExtraUnsigned: [][]byte{cmstest.BigArcAttr()}},
|
||||
"unsigned attributes, no time-stamp": {ExtraUnsigned: [][]byte{cmstest.BigArcAttr()}},
|
||||
"a sid by subjectKeyIdentifier": {SKI: true},
|
||||
"the version written 1, as by default": {Version: 1},
|
||||
} {
|
||||
sd, err := cms.ParseSignature(cmstest.Signature(msg, o, ana))
|
||||
if err != nil || len(sd.Signers) != 1 || sd.Signers[0].Cert.Hash != sha256.Sum256(ana.Cert.Raw) || sd.Signers[0].Check(msg) != cms.Valid {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
continue
|
||||
}
|
||||
if o.Token != nil && sd.Signers[0].Token == nil {
|
||||
t.Errorf("%s: the token is lost", name)
|
||||
}
|
||||
}
|
||||
// The certificates of the profile, each once, and the OCSP responses.
|
||||
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{OCSP: cmstest.Seq(cmstest.Int(0)), ExtraCerts: [][]byte{ana.Cert.Raw, v1.Cert.Raw}}, ana, luis))
|
||||
if err != nil || len(sd.Certs) != 2 || len(sd.OCSP) != 1 || !bytes.Equal(sd.OCSP[0], cmstest.Seq(cmstest.Int(0))) || sd.EContent != nil {
|
||||
t.Errorf("certificates and OCSP: %v %+v", err, sd)
|
||||
}
|
||||
}
|
||||
|
||||
// A co-signature finds each certificate by the bytes of its issuer and its
|
||||
// serial, both, or by its keyIdentifier: two certificates that share one of
|
||||
// them are two signers, not an ambiguity.
|
||||
func TestCoSignatureIdentifiers(t *testing.T) {
|
||||
issuer := cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("CA de prueba")))
|
||||
for name, pair := range map[string][2]cmstest.CertSpec{
|
||||
"one issuer, two serials": {{CN: "A", Issuer: issuer, Serial: cmstest.Int(1)}, {CN: "B", Issuer: issuer, Serial: cmstest.Int(2)}},
|
||||
"one serial, two issuers": {{CN: "A", Serial: cmstest.Int(5)}, {CN: "B", Serial: cmstest.Int(5)}},
|
||||
"one serial, two issuers by SKI": {{CN: "A", Serial: cmstest.Int(5)}, {CN: "B", Serial: cmstest.Int(5)}},
|
||||
} {
|
||||
a, b := cmstest.NewCert(pair[0], ecKey), cmstest.NewCert(pair[1], ecKey2)
|
||||
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{SKI: name == "one serial, two issuers by SKI"}, a, b))
|
||||
if err != nil || len(sd.Signers) != 2 || sd.Signers[0].Cert == sd.Signers[1].Cert {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
continue
|
||||
}
|
||||
for _, s := range sd.Signers {
|
||||
if s.Check(msg) != cms.Valid {
|
||||
t.Errorf("%s: %s does not verify", name, s.Cert.Holder())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The tokens have the form of §29.11, step 1: one SignerInfo, the TSTInfo in
|
||||
// its eContent, its content-type, its message-digest and the certificate of
|
||||
// the authority named by signing-certificate or signing-certificate-v2.
|
||||
func TestTokenFormRules(t *testing.T) {
|
||||
subject := []byte("seal subject")
|
||||
good := cmstest.Token(subject, now, cmstest.TokenOptions{}, tsa)
|
||||
info := cmstest.TSTInfo(subject, now, cmstest.TokenOptions{})
|
||||
encap := path(cmstest.SignedDataPath, 2)
|
||||
other := cmstest.NewCert(cmstest.CertSpec{CN: "Otra TSA"}, ecKey)
|
||||
withAttrs := func(mutate func(a [][]byte) [][]byte) []byte {
|
||||
return cmstest.Token(subject, now, cmstest.TokenOptions{CMS: cmstest.Options{Mutate: mutate}}, tsa)
|
||||
}
|
||||
ess1 := func(c []byte) []byte {
|
||||
return cmstest.Attr(cmstest.OIDSigCertV1, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(sha1Sum(c))))))
|
||||
}
|
||||
ess2 := func(c []byte) []byte {
|
||||
h := sha256.Sum256(c)
|
||||
return cmstest.Attr(cmstest.OIDSigCertV2, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:])))))
|
||||
}
|
||||
twoCRLs := cmstest.Token(subject, now, cmstest.TokenOptions{CMS: cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.Int(2))}}}, tsa)
|
||||
for name, b := range map[string][]byte{
|
||||
"two SignerInfo": cmstest.Merge(cmstest.TokenRaw(info, tsa), cmstest.TokenRaw(info, other)),
|
||||
"BER": cmstest.Token(subject, now, cmstest.TokenOptions{CMS: cmstest.Options{BER: true}}, tsa),
|
||||
"id-data": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDData), cmstest.TLV(0xa0, cmstest.Octets(info)))), encap...),
|
||||
"no eContent": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo))), encap...),
|
||||
"an eContent [1]": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa1, cmstest.Octets(info)))), encap...),
|
||||
"an eContent of two OCTET STRINGs": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0, cmstest.Octets(info), cmstest.Octets(info)))), encap...),
|
||||
"an empty eContent": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0))), encap...),
|
||||
"an eContent that is a SEQUENCE": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0, cmstest.Seq(info)))), encap...),
|
||||
"the content-type id-data": withAttrs(func(a [][]byte) [][]byte {
|
||||
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDData))
|
||||
return a
|
||||
}),
|
||||
"no message-digest": cmstest.Token(subject, now, cmstest.TokenOptions{NoMessageDigest: true}, tsa),
|
||||
"no signing certificate": withAttrs(func(a [][]byte) [][]byte { return a[:2] }),
|
||||
"two signing-certificate attributes": withAttrs(func(a [][]byte) [][]byte { return append(a, a[2]) }),
|
||||
"a signing-certificate of two values": withAttrs(func(a [][]byte) [][]byte {
|
||||
// The right value first in DER order, and a longer one after it.
|
||||
a[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV1), cmstest.Set(0x31, cmstest.Children(cmstest.Children(a[2])[1])[0], cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 21)))))))
|
||||
return a
|
||||
}),
|
||||
"two signing-certificate-v2 and a v1": withAttrs(func(a [][]byte) [][]byte { return append(a, ess2(tsa.Cert.Raw), ess2(tsa.Cert.Raw)) }),
|
||||
"a signing-certificate of another TSA": withAttrs(func(a [][]byte) [][]byte { a[2] = ess1(other.Cert.Raw); return a }),
|
||||
"a signing-certificate-v2 of another": cmstest.Token(subject, now, cmstest.TokenOptions{SigCertV2: true, CMS: cmstest.Options{ESSCert: other.Cert.Raw}}, tsa),
|
||||
"no certificate of the authority": cmstest.Token(subject, now, cmstest.TokenOptions{CMS: cmstest.Options{OmitCert: true}}, tsa),
|
||||
"crls out of order": cmstest.Edit(twoCRLs, func(b []byte) []byte {
|
||||
k := slices.Clone(cmstest.Children(b))
|
||||
slices.Reverse(k)
|
||||
return cmstest.TLV(0xa1, k...)
|
||||
}, path(cmstest.SignedDataPath, 4)...),
|
||||
} {
|
||||
if _, err := cms.ParseToken(b); err == nil || !isForm(err) {
|
||||
t.Errorf("%s: %v, want a form error", name, err)
|
||||
}
|
||||
}
|
||||
// What decides nothing in a token: crls, its certificate twice, and the
|
||||
// signing-certificate-v2 in the place of signing-certificate.
|
||||
for name, o := range map[string]cmstest.TokenOptions{
|
||||
"a CRL": {CRL: cmstest.Seq(cmstest.Int(1))},
|
||||
"two CRLs": {CMS: cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.Int(2))}}},
|
||||
"the certificate twice": {TSATwice: true},
|
||||
"signing-certificate-v2": {SigCertV2: true},
|
||||
"v2 beside a wrong v1": {SigCertV2: true, CMS: cmstest.Options{ExtraAttrs: [][]byte{ess1(other.Cert.Raw)}}},
|
||||
"a sid by keyIdentifier": {CMS: cmstest.Options{SKI: true}},
|
||||
"a signature of SHA-512": {CMS: cmstest.Options{Hash: crypto.SHA512}},
|
||||
"an imprint of SHA-512": {Hash: crypto.SHA512},
|
||||
"an accuracy of 1.5 s": {Accuracy: 1500 * time.Millisecond},
|
||||
"an accuracy of 2 s and 3µs": {Accuracy: 2*time.Second + 3*time.Microsecond},
|
||||
} {
|
||||
tok, err := cms.ParseToken(cmstest.Token(subject, now, o, tsa))
|
||||
if err != nil || !tok.Check(subject) || tok.Accuracy != o.Accuracy || tok.TSA.Hash != sha256.Sum256(tsa.Cert.Raw) {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,186 @@
|
||||
package cms_test
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"g.activething.com/go/DateKeys/internal/cms"
|
||||
"g.activething.com/go/DateKeys/internal/cms/cmstest"
|
||||
"g.activething.com/go/DateKeys/internal/der"
|
||||
)
|
||||
|
||||
// The fuzz targets of the reader. Each one is seeded with the signatures and
|
||||
// the tokens of testdata/vectors/security_cms.json and with what cmstest
|
||||
// builds, and checks that the reader never panics and fails only with the
|
||||
// errors of its verdicts: ErrForm (F1, S2) or ErrAlgorithm (S1).
|
||||
|
||||
// FuzzParseSignature reads any bytes as the CMS signature of alg 2 (spec
|
||||
// §29.10). What it accepts has a certificate for each SignerInfo, and its
|
||||
// checks, and the token of each, run without a panic.
|
||||
func FuzzParseSignature(f *testing.F) {
|
||||
for _, b := range fuzzSeeds(f) {
|
||||
f.Add(b)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, b []byte) {
|
||||
sd, err := cms.ParseSignature(b)
|
||||
if err != nil {
|
||||
if sd != nil || !errors.Is(err, cms.ErrForm) && !errors.Is(err, cms.ErrAlgorithm) {
|
||||
t.Fatalf("%v, with a result %v", err, sd != nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
if der.Check(b) != nil || len(sd.Signers) == 0 {
|
||||
t.Fatal("a signature that is not DER, or without a SignerInfo")
|
||||
}
|
||||
for _, s := range sd.Signers {
|
||||
if s.Cert == nil || s.Cert.Hash != sha256.Sum256(s.Cert.Raw) {
|
||||
t.Fatal("a SignerInfo without its certificate")
|
||||
}
|
||||
switch s.Check(msg) {
|
||||
case cms.Valid, cms.Invalid, cms.NotVerifiable:
|
||||
default:
|
||||
t.Fatal("a result outside the three")
|
||||
}
|
||||
s.Cert.Holder()
|
||||
s.Cert.IssuerName()
|
||||
if s.Token != nil {
|
||||
checkToken(t, s.Token, s.Signature)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzParseToken reads any bytes as an RFC 3161 token (spec §29.11).
|
||||
func FuzzParseToken(f *testing.F) {
|
||||
for _, b := range fuzzSeeds(f) {
|
||||
f.Add(b)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, b []byte) { checkToken(t, b, []byte("seal subject")) })
|
||||
}
|
||||
|
||||
// maxAccuracy is the largest precision of a token: 2^31 - 1 seconds, 999
|
||||
// milliseconds and 999 microseconds.
|
||||
const maxAccuracy = (1<<31-1)*time.Second + 999*time.Millisecond + 999*time.Microsecond
|
||||
|
||||
func checkToken(t *testing.T, b, subject []byte) {
|
||||
t.Helper()
|
||||
tok, err := cms.ParseToken(b)
|
||||
if err != nil {
|
||||
if tok != nil || !errors.Is(err, cms.ErrForm) && !errors.Is(err, cms.ErrAlgorithm) {
|
||||
t.Fatalf("%v, with a result %v", err, tok != nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
if tok.TSA == nil || tok.GenTime.IsZero() || tok.Accuracy < 0 || tok.Accuracy > maxAccuracy {
|
||||
t.Fatalf("a token of %+v", tok)
|
||||
}
|
||||
tok.Check(subject)
|
||||
tok.ImprintIsSHA256()
|
||||
tok.TSA.Holder()
|
||||
}
|
||||
|
||||
// FuzzParseCert reads any bytes as a certificate with the profile of spec
|
||||
// §29.10. What it accepts names its holder and its issuer without a panic,
|
||||
// and has a valid period that it contains.
|
||||
func FuzzParseCert(f *testing.F) {
|
||||
for _, b := range fuzzSeeds(f) {
|
||||
if sd, err := cms.ParseSignature(b); err == nil {
|
||||
for _, c := range sd.Certs {
|
||||
f.Add(c.Raw)
|
||||
}
|
||||
}
|
||||
if tok, err := cms.ParseToken(b); err == nil {
|
||||
f.Add(tok.TSA.Raw)
|
||||
}
|
||||
}
|
||||
for _, spec := range []cmstest.CertSpec{
|
||||
{CN: "Ana López"},
|
||||
{Subject: cmstest.Name(cn(cmstest.BMPText("Ana")), given(cmstest.Printable("Ana")), surname(cmstest.Teletex("Lopez")), org(cmstest.IA5("Banco")))},
|
||||
{NoVersion: true},
|
||||
{NotAfter: cmstest.GeneralizedTime("20501231235959Z"), UniqueIDs: [][]byte{cmstest.TLV(0x81, []byte{0, 1})}},
|
||||
{Extensions: [][]byte{cmstest.ExtSKI([]byte{1}), cmstest.ExtSKI([]byte{1})}},
|
||||
} {
|
||||
f.Add(cert(spec))
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, b []byte) {
|
||||
c, err := cms.ParseCert(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.Hash != sha256.Sum256(b) || c.NotBefore.IsZero() || c.NotAfter.IsZero() {
|
||||
t.Fatalf("a certificate of %+v", c)
|
||||
}
|
||||
if !c.NotAfter.Before(c.NotBefore) && (!c.ValidAt(c.NotBefore) || !c.ValidAt(c.NotAfter)) {
|
||||
t.Fatal("a period that does not contain its ends")
|
||||
}
|
||||
c.Holder()
|
||||
c.IssuerName()
|
||||
})
|
||||
}
|
||||
|
||||
// fuzzSeeds returns the signatures and the tokens of security_cms.json, and
|
||||
// some that cmstest builds.
|
||||
func fuzzSeeds(f *testing.F) [][]byte {
|
||||
raw, err := os.ReadFile(filepath.Join("..", "..", "testdata", "vectors", "security_cms.json"))
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
var file struct {
|
||||
Cases []struct {
|
||||
Area string `json:"security_cbor"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &file); err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
var out [][]byte
|
||||
for _, c := range file.Cases {
|
||||
area, err := hex.DecodeString(c.Area)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
out = append(out, contentInfos(area)...)
|
||||
}
|
||||
tok := func(sig []byte) []byte {
|
||||
return cmstest.Token(sig, now, cmstest.TokenOptions{Accuracy: 1500 * time.Millisecond, After: [][]byte{cmstest.Bool(true), cmstest.Int(7)}}, tsa)
|
||||
}
|
||||
return append(out,
|
||||
cmstest.Signature(msg, cmstest.Options{Token: tok, OCSP: cmstest.Seq(cmstest.Int(0))}, ana, luis),
|
||||
cmstest.Signature(msg, cmstest.Options{PSS: true, SKI: true, Hash: crypto.SHA384}, luis),
|
||||
cmstest.Signature(msg, cmstest.Options{SigCertV1: true, ESSHashAlg: cmstest.HashAlg(crypto.SHA512), ExtraAttrs: [][]byte{cmstest.BigArcAttr()}}, ana),
|
||||
cmstest.Token([]byte("seal subject"), now, cmstest.TokenOptions{SigCertV2: true, TSATwice: true, CRL: cmstest.Seq(cmstest.Int(1))}, tsa),
|
||||
)
|
||||
}
|
||||
|
||||
// contentInfos returns the outermost runs of bytes of b that are one DER
|
||||
// SEQUENCE of more than 127 bytes: the signatures and the tokens of an area.
|
||||
func contentInfos(b []byte) [][]byte {
|
||||
var out [][]byte
|
||||
for i := 0; i+4 < len(b); i++ {
|
||||
if b[i] != 0x30 || b[i+1] < 0x81 || b[i+1] > 0x83 {
|
||||
continue
|
||||
}
|
||||
n := int(b[i+1] & 0x7f)
|
||||
if i+2+n > len(b) {
|
||||
continue
|
||||
}
|
||||
l := 0
|
||||
for _, c := range b[i+2 : i+2+n] {
|
||||
l = l<<8 | int(c)
|
||||
}
|
||||
end := i + 2 + n + l
|
||||
if end > len(b) || der.Check(b[i:end]) != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, b[i:end])
|
||||
i = end - 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
@ -0,0 +1,266 @@
|
||||
package cms_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"encoding/asn1"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"g.activething.com/go/DateKeys/internal/cms"
|
||||
"g.activething.com/go/DateKeys/internal/cms/cmstest"
|
||||
)
|
||||
|
||||
var (
|
||||
p384Key = cmstest.ECKey(elliptic.P384())
|
||||
p521Key = cmstest.ECKey(elliptic.P521())
|
||||
)
|
||||
|
||||
// pss is the AlgorithmIdentifier of RSASSA-PSS with the fields of its
|
||||
// parameters given.
|
||||
func pss(fields ...[]byte) []byte { return cmstest.AlgID(cmstest.OIDPSS, cmstest.Seq(fields...)) }
|
||||
|
||||
// Spec §29.10, "Algoritmos" and step 2 of "Verificación": the closed table of
|
||||
// algorithms, and a key of another scheme than its algorithm, which is
|
||||
// invalid and not outside the table (step 3).
|
||||
func TestAlgorithmTable(t *testing.T) {
|
||||
alg, null := cmstest.AlgID, cmstest.Null
|
||||
h0 := cmstest.TLV(0xa0, cmstest.HashAlg(crypto.SHA256))
|
||||
m1 := cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, cmstest.HashAlg(crypto.SHA256)))
|
||||
s2 := cmstest.TLV(0xa2, cmstest.Int(32))
|
||||
p384 := cmstest.NewCert(cmstest.CertSpec{CN: "P-384"}, p384Key)
|
||||
p521 := cmstest.NewCert(cmstest.CertSpec{CN: "P-521"}, p521Key)
|
||||
for name, tc := range map[string]struct {
|
||||
s cmstest.Signer
|
||||
o cmstest.Options
|
||||
want cms.Result
|
||||
}{
|
||||
"SHA-1, ECDSA": {ana, cmstest.Options{Hash: crypto.SHA1}, cms.NotVerifiable},
|
||||
"SHA-1, RSA": {luis, cmstest.Options{Hash: crypto.SHA1}, cms.NotVerifiable},
|
||||
"SHA-1, and another message": {ana, cmstest.Options{Hash: crypto.SHA1, Message: []byte("other")}, cms.NotVerifiable},
|
||||
"SHA-256 with NULL": {ana, cmstest.Options{DigestAlg: alg(cmstest.OIDSHA256, null())}, cms.Valid},
|
||||
"SHA-256 with an INTEGER": {ana, cmstest.Options{DigestAlg: alg(cmstest.OIDSHA256, cmstest.Int(0))}, cms.NotVerifiable},
|
||||
"P-256 with SHA-384": {ana, cmstest.Options{Hash: crypto.SHA384}, cms.Valid},
|
||||
"P-256 with SHA-512": {ana, cmstest.Options{Hash: crypto.SHA512}, cms.Valid},
|
||||
"P-384 with SHA-384": {p384, cmstest.Options{Hash: crypto.SHA384}, cms.Valid},
|
||||
"P-521 with SHA-512": {p521, cmstest.Options{Hash: crypto.SHA512}, cms.Valid},
|
||||
"P-521 with SHA-256": {p521, cmstest.Options{}, cms.Valid},
|
||||
"rsaEncryption": {luis, cmstest.Options{}, cms.Valid},
|
||||
"rsaEncryption without parameters": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDRSA)}, cms.Valid},
|
||||
"rsaEncryption with an INTEGER": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDRSA, cmstest.Int(0))}, cms.NotVerifiable},
|
||||
"rsaEncryption with SHA-512": {luis, cmstest.Options{Hash: crypto.SHA512}, cms.Valid},
|
||||
"sha256WithRSAEncryption": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA, null())}, cms.Valid},
|
||||
"sha256WithRSAEncryption without NULL": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA)}, cms.Valid},
|
||||
"sha256WithRSAEncryption, INTEGER": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA, cmstest.Int(0))}, cms.NotVerifiable},
|
||||
"sha256WithRSAEncryption, SHA-384": {luis, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDSHA256RSA, null())}, cms.NotVerifiable},
|
||||
"sha384WithRSAEncryption": {luis, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDSHA384RSA, null())}, cms.Valid},
|
||||
"sha384WithRSAEncryption, SHA-256": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA384RSA, null())}, cms.NotVerifiable},
|
||||
"sha384WithRSAEncryption, INTEGER": {luis, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDSHA384RSA, cmstest.Int(0))}, cms.NotVerifiable},
|
||||
"sha512WithRSAEncryption": {luis, cmstest.Options{Hash: crypto.SHA512, SigAlg: alg(cmstest.OIDSHA512RSA, null())}, cms.Valid},
|
||||
"sha512WithRSAEncryption, SHA-256": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA512RSA, null())}, cms.NotVerifiable},
|
||||
"sha512WithRSAEncryption, INTEGER": {luis, cmstest.Options{Hash: crypto.SHA512, SigAlg: alg(cmstest.OIDSHA512RSA, cmstest.Int(0))}, cms.NotVerifiable},
|
||||
"ecdsa-with-SHA256 with NULL": {ana, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA256, null())}, cms.NotVerifiable},
|
||||
"ecdsa-with-SHA256, SHA-384": {ana, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDECDSA256)}, cms.NotVerifiable},
|
||||
"ecdsa-with-SHA384, SHA-256": {ana, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA384)}, cms.NotVerifiable},
|
||||
"ecdsa-with-SHA384 with NULL": {ana, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDECDSA384, null())}, cms.NotVerifiable},
|
||||
"ecdsa-with-SHA512, SHA-256": {ana, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA512)}, cms.NotVerifiable},
|
||||
"ecdsa-with-SHA512 with NULL": {ana, cmstest.Options{Hash: crypto.SHA512, SigAlg: alg(cmstest.OIDECDSA512, null())}, cms.NotVerifiable},
|
||||
"an algorithm outside the table": {ana, cmstest.Options{SigAlg: alg(asn1.ObjectIdentifier{1, 3, 101, 112})}, cms.NotVerifiable},
|
||||
"another algorithm, an RSA key": {luis, cmstest.Options{SigAlg: alg(asn1.ObjectIdentifier{1, 3, 101, 112})}, cms.NotVerifiable},
|
||||
"another algorithm with PSS parameters": {luis, cmstest.Options{PSS: true, SigAlg: alg(asn1.ObjectIdentifier{1, 2, 3, 4}, cmstest.Seq(h0, m1, s2))}, cms.NotVerifiable},
|
||||
"PSS": {luis, cmstest.Options{PSS: true}, cms.Valid},
|
||||
"PSS with SHA-384": {luis, cmstest.Options{PSS: true, Hash: crypto.SHA384}, cms.Valid},
|
||||
"PSS, its fields written again": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, s2)}, cms.Valid},
|
||||
"PSS with NULL in its hashes": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, alg(cmstest.OIDSHA256, null())), cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, alg(cmstest.OIDSHA256, null()))), s2)}, cms.Valid},
|
||||
"PSS without parameters": {luis, cmstest.Options{PSS: true, SigAlg: alg(cmstest.OIDPSS)}, cms.NotVerifiable},
|
||||
"PSS with NULL parameters": {luis, cmstest.Options{PSS: true, SigAlg: alg(cmstest.OIDPSS, null())}, cms.NotVerifiable},
|
||||
"PSS parameters as a SET": {luis, cmstest.Options{PSS: true, SigAlg: alg(cmstest.OIDPSS, cmstest.TLV(0x31, h0, m1, s2))}, cms.NotVerifiable},
|
||||
"PSS with [0] of two elements": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, cmstest.HashAlg(crypto.SHA256), null()), m1, s2)}, cms.NotVerifiable},
|
||||
"PSS with [0] primitive": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0x80, cmstest.HashAlg(crypto.SHA256)), m1, s2)}, cms.NotVerifiable},
|
||||
"PSS with [1] before [0]": {luis, cmstest.Options{PSS: true, SigAlg: pss(m1, h0, s2)}, cms.NotVerifiable},
|
||||
"PSS with [0] twice": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, h0, m1, s2)}, cms.NotVerifiable},
|
||||
"PSS of SHA-512 with SHA-256": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, cmstest.HashAlg(crypto.SHA512)), m1, s2)}, cms.NotVerifiable},
|
||||
"PSS with a hash of an INTEGER": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, alg(cmstest.OIDSHA256, cmstest.Int(0))), m1, s2)}, cms.NotVerifiable},
|
||||
"PSS with a hash without an OID": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, cmstest.Seq(cmstest.Int(0))), m1, s2)}, cms.NotVerifiable},
|
||||
"PSS with another mask": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 9}, cmstest.HashAlg(crypto.SHA256))), s2)}, cms.NotVerifiable},
|
||||
"PSS with MGF1 without its hash": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1)), s2)}, cms.NotVerifiable},
|
||||
"PSS with MGF1 not an algorithm": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, cmstest.Seq(cmstest.Int(1))), s2)}, cms.NotVerifiable},
|
||||
"PSS with MGF1 of SHA-512": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, cmstest.HashAlg(crypto.SHA512))), s2)}, cms.NotVerifiable},
|
||||
"PSS with MGF1 of a hash with INTEGER": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, alg(cmstest.OIDSHA256, cmstest.Int(0)))), s2)}, cms.NotVerifiable},
|
||||
"PSS with MGF1 of a hash without OID": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, cmstest.Seq(cmstest.Int(0)))), s2)}, cms.NotVerifiable},
|
||||
"PSS with a salt of 20": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.Int(20)))}, cms.NotVerifiable},
|
||||
"PSS with a salt in OCTETS": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.Octets([]byte{32})))}, cms.NotVerifiable},
|
||||
"PSS with a salt of 2^64 + 32": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.IntBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 32})))}, cms.NotVerifiable},
|
||||
"PSS with a negative salt": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.Int(-224)))}, cms.NotVerifiable},
|
||||
"PSS with trailerField": {luis, cmstest.Options{PSS: true, PSSTrailer: true}, cms.NotVerifiable},
|
||||
"PSS with a field [4]": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, s2, cmstest.TLV(0xa4, cmstest.Int(1)))}, cms.NotVerifiable},
|
||||
"PSS without [0]": {luis, cmstest.Options{PSS: true, SigAlg: pss(m1, s2)}, cms.NotVerifiable},
|
||||
"PSS without [1]": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, s2)}, cms.NotVerifiable},
|
||||
"PSS without [2]": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1)}, cms.NotVerifiable},
|
||||
"step 3: an RSA key with ECDSA": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA256)}, cms.Invalid},
|
||||
"step 3: an EC key with PKCS #1": {ana, cmstest.Options{SigAlg: alg(cmstest.OIDRSA, null())}, cms.Invalid},
|
||||
"step 3: an EC key with PSS": {ana, cmstest.Options{SigAlg: pss(h0, m1, s2)}, cms.Invalid},
|
||||
"step 3: PKCS #1, a bit flipped": {luis, cmstest.Options{CorruptSignature: true}, cms.Invalid},
|
||||
"step 3: PSS, a bit flipped": {luis, cmstest.Options{PSS: true, CorruptSignature: true}, cms.Invalid},
|
||||
"step 3: ECDSA, a bit flipped": {ana, cmstest.Options{CorruptSignature: true}, cms.Invalid},
|
||||
"step 3: the digest of another message": {ana, cmstest.Options{Message: []byte("other")}, cms.Invalid},
|
||||
} {
|
||||
if got := resultOf(t, name, cmstest.Signature(msg, tc.o, tc.s)); got != tc.want {
|
||||
t.Errorf("%s: %v, want %v", name, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The keys of the table (spec §29.10): RSA with NULL parameters, exactly a
|
||||
// modulus and an exponent, the modulus odd of 2048 to 4096 bits and the
|
||||
// exponent odd from 3 to 2^31 - 1; EC on P-256, P-384 or P-521, the point
|
||||
// uncompressed and on the curve. Any other is not verifiable; a key of the
|
||||
// table that does not verify the signature is invalid.
|
||||
func TestKeyTable(t *testing.T) {
|
||||
n, e := rsaKey.N, big.NewInt(65537)
|
||||
two := func(bits uint) *big.Int {
|
||||
return new(big.Int).Add(new(big.Int).Lsh(big.NewInt(1), bits), big.NewInt(1))
|
||||
}
|
||||
key := func(fields ...[]byte) []byte { return cmstest.BitString(cmstest.Seq(fields...)) }
|
||||
rsaAlg := cmstest.AlgID(cmstest.OIDRSA, cmstest.Null())
|
||||
ecAlg := cmstest.AlgID(cmstest.OIDECPublicKey, cmstest.OID(cmstest.OIDP256))
|
||||
point := cmstest.Uncompressed(&ecKey.PublicKey)
|
||||
off := bytes.Clone(point)
|
||||
off[len(off)-1] ^= 1
|
||||
even := evenPointKey()
|
||||
evenPoint := cmstest.Uncompressed(&even.PublicKey)
|
||||
for name, tc := range map[string]struct {
|
||||
spki []byte
|
||||
key crypto.Signer
|
||||
want cms.Result
|
||||
}{
|
||||
"RSA of 2048 bits": {cmstest.SPKIRSA(n, e), rsaKey, cms.Valid},
|
||||
"RSA of 2047 bits": {cmstest.SPKIRSA(two(2046), e), rsaKey, cms.NotVerifiable},
|
||||
"RSA of 4096 bits that is another": {cmstest.SPKIRSA(two(4095), e), rsaKey, cms.Invalid},
|
||||
"RSA of 4097 bits": {cmstest.SPKIRSA(two(4096), e), rsaKey, cms.NotVerifiable},
|
||||
"an even modulus": {cmstest.SPKIRSA(new(big.Int).Add(n, big.NewInt(1)), e), rsaKey, cms.NotVerifiable},
|
||||
"a negative modulus": {cmstest.SPKIRSA(new(big.Int).Neg(n), e), rsaKey, cms.NotVerifiable},
|
||||
"an exponent of 1": {cmstest.SPKIRSA(n, big.NewInt(1)), rsaKey, cms.NotVerifiable},
|
||||
"an exponent of 3": {cmstest.SPKIRSA(n, big.NewInt(3)), rsaKey, cms.Invalid},
|
||||
"an even exponent": {cmstest.SPKIRSA(n, big.NewInt(65536)), rsaKey, cms.NotVerifiable},
|
||||
"an exponent of 2^31 - 1": {cmstest.SPKIRSA(n, big.NewInt(1<<31-1)), rsaKey, cms.Invalid},
|
||||
"an exponent of 2^64 + 65537": {cmstest.SPKIRSA(n, new(big.Int).Add(new(big.Int).Lsh(big.NewInt(1), 64), e)), rsaKey, cms.NotVerifiable},
|
||||
"a negative exponent": {cmstest.SPKIRSA(n, big.NewInt(-1)), rsaKey, cms.NotVerifiable},
|
||||
"an RSAPublicKey with a byte more": {cmstest.Seq(rsaAlg, cmstest.BitString(append(cmstest.Seq(cmstest.BigInt(n), cmstest.BigInt(e)), 0))), rsaKey, cms.NotVerifiable},
|
||||
"an RSAPublicKey as a SET": {cmstest.Seq(rsaAlg, cmstest.BitString(cmstest.TLV(0x31, cmstest.BigInt(n), cmstest.BigInt(e)))), rsaKey, cms.NotVerifiable},
|
||||
"an RSAPublicKey of three INTEGERs": {cmstest.Seq(rsaAlg, key(cmstest.BigInt(n), cmstest.BigInt(e), cmstest.Int(1))), rsaKey, cms.NotVerifiable},
|
||||
"an RSAPublicKey that is an INTEGER": {cmstest.Seq(rsaAlg, cmstest.BitString(cmstest.BigInt(n))), rsaKey, cms.NotVerifiable},
|
||||
"a modulus in OCTETS": {cmstest.Seq(rsaAlg, key(cmstest.Octets(append([]byte{0}, n.Bytes()...)), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
|
||||
"an exponent in OCTETS": {cmstest.Seq(rsaAlg, key(cmstest.BigInt(n), cmstest.Octets(e.Bytes()))), rsaKey, cms.NotVerifiable},
|
||||
"rsaEncryption without NULL": {cmstest.Seq(cmstest.AlgID(cmstest.OIDRSA), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
|
||||
"an RSA key of id-RSASSA-PSS": {cmstest.Seq(cmstest.AlgID(cmstest.OIDPSS, cmstest.Null()), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
|
||||
"an SPKI of three elements": {cmstest.Seq(rsaAlg, key(cmstest.BigInt(n), cmstest.BigInt(e)), cmstest.Null()), rsaKey, cms.NotVerifiable},
|
||||
"an SPKI whose key is OCTETS": {cmstest.Seq(rsaAlg, cmstest.Octets(append([]byte{0}, cmstest.Seq(cmstest.BigInt(n), cmstest.BigInt(e))...))), rsaKey, cms.NotVerifiable},
|
||||
"an SPKI whose algorithm is a SET": {cmstest.Seq(cmstest.TLV(0x31, cmstest.OID(cmstest.OIDRSA), cmstest.Null()), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
|
||||
"an SPKI of no algorithm": {cmstest.Seq(cmstest.Seq(cmstest.Int(1)), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
|
||||
"P-256": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDP256), point), ecKey, cms.Valid},
|
||||
"a compressed point": {cmstest.SPKICompressed(&ecKey.PublicKey), ecKey, cms.NotVerifiable},
|
||||
"a point off the curve": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDP256), off), ecKey, cms.NotVerifiable},
|
||||
"a point of P-256 said P-384": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDP384), point), ecKey, cms.NotVerifiable},
|
||||
"brainpoolP256r1": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDBrainpoolP256), point), ecKey, cms.NotVerifiable},
|
||||
"a point of P-521, another curve": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDBrainpoolP256), cmstest.Uncompressed(&p521Key.PublicKey)), p521Key, cms.NotVerifiable},
|
||||
"id-ecPublicKey without a curve": {cmstest.Seq(cmstest.AlgID(cmstest.OIDECPublicKey), cmstest.BitString(point)), ecKey, cms.NotVerifiable},
|
||||
"id-ecPublicKey with NULL": {cmstest.SPKIEC(cmstest.Null(), point), ecKey, cms.NotVerifiable},
|
||||
"an EC key of id-ecDH": {cmstest.Seq(cmstest.AlgID(asn1.ObjectIdentifier{1, 3, 132, 1, 12}, cmstest.OID(cmstest.OIDP256)), cmstest.BitString(point)), ecKey, cms.NotVerifiable},
|
||||
"a BIT STRING of 1 unused bit": {cmstest.Seq(ecAlg, cmstest.TLV(0x03, append([]byte{1}, evenPoint...))), even, cms.NotVerifiable},
|
||||
"an empty BIT STRING": {cmstest.Seq(ecAlg, cmstest.TLV(0x03, []byte{0})), ecKey, cms.NotVerifiable},
|
||||
} {
|
||||
s := cmstest.NewCert(cmstest.CertSpec{CN: "Clave", SPKI: tc.spki}, tc.key)
|
||||
if got := resultOf(t, name, cmstest.Signature(msg, cmstest.Options{}, s)); got != tc.want {
|
||||
t.Errorf("%s: %v, want %v", name, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// evenPointKey returns a P-256 key whose point ends in an even byte: a BIT
|
||||
// STRING with one unused bit holds it in DER.
|
||||
func evenPointKey() *ecdsa.PrivateKey {
|
||||
for {
|
||||
k := cmstest.ECKey(elliptic.P256())
|
||||
if p := cmstest.Uncompressed(&k.PublicKey); p[len(p)-1]&1 == 0 {
|
||||
return k
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spec §29.11: the token in the order of its profile, form (S2), algorithms
|
||||
// (S1) and verification (S3).
|
||||
func TestTokenProfile(t *testing.T) {
|
||||
subject := []byte("seal subject")
|
||||
tok := func(o cmstest.TokenOptions, s cmstest.Signer) []byte { return cmstest.Token(subject, now, o, s) }
|
||||
small := cmstest.NewCert(cmstest.CertSpec{CN: "TSA 1024"}, cmstest.RSAKey(1024))
|
||||
compressed := cmstest.NewCert(cmstest.CertSpec{CN: "TSA comprimida", SPKI: cmstest.SPKICompressed(&ecKey2.PublicKey)}, ecKey2)
|
||||
notYet := cmstest.NewCert(cmstest.CertSpec{CN: "TSA futura", From: now.Add(time.Second)}, ecKey2)
|
||||
expired := cmstest.NewCert(cmstest.CertSpec{CN: "TSA caducada", To: now.Add(-time.Second)}, ecKey2)
|
||||
exact := cmstest.NewCert(cmstest.CertSpec{CN: "TSA justa", From: now, To: now}, ecKey2)
|
||||
badImprintAlg := func() []byte {
|
||||
info := cmstest.Seq(cmstest.Int(1), cmstest.OID(asn1.ObjectIdentifier{1, 2, 3, 4}), cmstest.Seq(cmstest.Seq(cmstest.Int(1)), cmstest.Octets(make([]byte, 32))), cmstest.Int(42), cmstest.GeneralizedTimeOf(now))
|
||||
return cmstest.TokenRaw(info, tsa)
|
||||
}
|
||||
for name, tc := range map[string]struct {
|
||||
token []byte
|
||||
form bool // S2, else S1
|
||||
}{
|
||||
"S1: an imprint of SHA-1": {tok(cmstest.TokenOptions{Hash: crypto.SHA1}, tsa), false},
|
||||
"S1: a signature of SHA-1": {tok(cmstest.TokenOptions{CMS: cmstest.Options{Hash: crypto.SHA1}}, tsa), false},
|
||||
"S1: a key of 1024 bits": {tok(cmstest.TokenOptions{}, small), false},
|
||||
"S1: a compressed key": {tok(cmstest.TokenOptions{}, compressed), false},
|
||||
"S1: PSS with trailerField": {tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, PSSTrailer: true}}, luis), false},
|
||||
"S2 before S1: SHA-1 and version 2": {tok(cmstest.TokenOptions{Hash: crypto.SHA1, Version: 2}, tsa), true},
|
||||
"S2 before S1: SHA-1 and no digest": {tok(cmstest.TokenOptions{Hash: crypto.SHA1, NoMessageDigest: true}, tsa), true},
|
||||
"S2: an imprint algorithm that is no one": {badImprintAlg(), true},
|
||||
} {
|
||||
_, err := cms.ParseToken(tc.token)
|
||||
if tc.form && !isForm(err) || !tc.form && !isAlgorithm(err) {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
// S3: it reads, and does not verify.
|
||||
for name, b := range map[string][]byte{
|
||||
"the signature of the authority, a bit flipped": tok(cmstest.TokenOptions{CMS: cmstest.Options{CorruptSignature: true}}, tsa),
|
||||
"the message-digest of another TSTInfo": tok(cmstest.TokenOptions{CMS: cmstest.Options{Message: []byte("other")}}, tsa),
|
||||
"an imprint of 33 bytes": tok(cmstest.TokenOptions{Imprint: append(sha256Of(subject), 0)}, tsa),
|
||||
"an imprint of 31 bytes": tok(cmstest.TokenOptions{Imprint: sha256Of(subject)[:31]}, tsa),
|
||||
"another imprint": tok(cmstest.TokenOptions{Imprint: make([]byte, 32)}, tsa),
|
||||
"an authority not yet valid": tok(cmstest.TokenOptions{}, notYet),
|
||||
"an authority expired": tok(cmstest.TokenOptions{}, expired),
|
||||
"an authority of PSS, a bit flipped": tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, CorruptSignature: true}}, luis),
|
||||
} {
|
||||
token, err := cms.ParseToken(b)
|
||||
if err != nil || token.Check(subject) {
|
||||
t.Errorf("%s: %v, valid %v", name, err, err == nil && token.Check(subject))
|
||||
}
|
||||
}
|
||||
// Valid: at the first and the last instant of the validity of the
|
||||
// authority, with RSA, PSS and SHA-512, and with the imprint of SHA-384,
|
||||
// which only seal_type 2 refuses.
|
||||
for name, b := range map[string][]byte{
|
||||
"an authority valid exactly then": tok(cmstest.TokenOptions{}, exact),
|
||||
"an authority of RSA": tok(cmstest.TokenOptions{}, luis),
|
||||
"an authority of PSS": tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true}}, luis),
|
||||
"a signature of SHA-512": tok(cmstest.TokenOptions{CMS: cmstest.Options{Hash: crypto.SHA512}}, tsa),
|
||||
} {
|
||||
token, err := cms.ParseToken(b)
|
||||
if err != nil || !token.Check(subject) || !token.ImprintIsSHA256() {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
token, err := cms.ParseToken(tok(cmstest.TokenOptions{Hash: crypto.SHA384}, tsa))
|
||||
if err != nil || !token.Check(subject) || token.ImprintIsSHA256() {
|
||||
t.Errorf("an imprint of SHA-384: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func sha256Of(b []byte) []byte {
|
||||
h := crypto.SHA256.New()
|
||||
h.Write(b)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
Loading…
Reference in new issue