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/cms_test.go

176 lines
6.4 KiB

package cms_test
import (
"bytes"
"crypto"
"crypto/elliptic"
"crypto/sha256"
"errors"
"testing"
"time"
"g.activething.com/go/DateKeys/internal/cms"
"g.activething.com/go/DateKeys/internal/cms/cmstest"
)
var (
from = time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
to = time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)
now = time.Date(2026, 9, 30, 12, 0, 0, 0, time.UTC)
msg = []byte("datekeys:dkc3:author-signature:v1\n00\n")
)
func TestSignatureAlgorithms(t *testing.T) {
rsa2048 := cmstest.NewRSA("Ana López", 2048, from, to)
p256 := cmstest.NewECDSA("Luis", elliptic.P256(), from, to)
p384 := cmstest.NewECDSA("Eva", elliptic.P384(), from, to)
p521 := cmstest.NewECDSA("Raúl", elliptic.P521(), from, to)
for _, tc := range []struct {
name string
opts cmstest.Options
s cmstest.Signer
}{
{"RSA PKCS1 SHA-256", cmstest.Options{}, rsa2048},
{"RSA PKCS1 SHA-384", cmstest.Options{Hash: crypto.SHA384}, rsa2048},
{"RSA PKCS1 SHA-512", cmstest.Options{Hash: crypto.SHA512}, rsa2048},
{"RSA PSS SHA-256", cmstest.Options{PSS: true}, rsa2048},
{"RSA PSS SHA-512", cmstest.Options{PSS: true, Hash: crypto.SHA512}, rsa2048},
{"RSA by subjectKeyIdentifier", cmstest.Options{SKI: true}, rsa2048},
{"ECDSA P-256", cmstest.Options{}, p256},
{"ECDSA P-384 SHA-384", cmstest.Options{Hash: crypto.SHA384}, p384},
{"ECDSA P-521 SHA-512", cmstest.Options{Hash: crypto.SHA512}, p521},
} {
sd, err := cms.ParseSignature(cmstest.Signature(msg, tc.opts, tc.s))
if err != nil || len(sd.Signers) != 1 {
t.Errorf("%s: %v", tc.name, err)
continue
}
si := sd.Signers[0]
if si.Cert.Hash != [32]byte(sha(tc.s.Cert.Raw)) || si.Check(msg) != cms.Valid {
t.Errorf("%s: result %v", tc.name, si.Check(msg))
}
if si.Check([]byte("another message")) != cms.Invalid {
t.Errorf("%s: another message verifies", tc.name)
}
}
}
func TestCoSignature(t *testing.T) {
a := cmstest.NewECDSA("Ana", elliptic.P256(), from, to)
b := cmstest.NewRSA("Banco S.A.", 2048, from, to)
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{}, a, b))
if err != nil || len(sd.Signers) != 2 || len(sd.Certs) != 2 {
t.Fatalf("%v", err)
}
for _, s := range sd.Signers {
if s.Check(msg) != cms.Valid {
t.Errorf("%s does not verify", s.Cert.Holder())
}
}
if sd.Signers[0].Cert.Holder() == "" || sd.Signers[0].Cert.IssuerName() == "" {
t.Error("no holder or issuer")
}
}
func TestTokenOverSignature(t *testing.T) {
a := cmstest.NewECDSA("Ana", elliptic.P256(), from, to)
tsa := cmstest.NewRSA("TSA de prueba", 2048, from, to)
tok := func(sig []byte) []byte {
return cmstest.Token(sig, now, cmstest.TokenOptions{Accuracy: 2 * time.Second}, tsa)
}
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{Token: tok}, a))
if err != nil || sd.Signers[0].Token == nil {
t.Fatalf("%v", err)
}
token, err := cms.ParseToken(sd.Signers[0].Token)
if err != nil {
t.Fatal(err)
}
if !token.GenTime.Equal(now) || token.Accuracy != 2*time.Second || token.TSA.Holder() != "TSA de prueba" || !token.ImprintIsSHA256() {
t.Errorf("token %+v", token)
}
if !token.Check(sd.Signers[0].Signature) || token.Check([]byte("other")) {
t.Error("the token seals the signature value and nothing else")
}
}
func TestTokenFailures(t *testing.T) {
tsa := cmstest.NewECDSA("TSA", elliptic.P256(), from, to)
old := cmstest.NewECDSA("TSA caducada", elliptic.P256(), from, time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
subject := []byte("seal subject")
if tok, err := cms.ParseToken(cmstest.Token(subject, now, cmstest.TokenOptions{}, tsa)); err != nil || !tok.Check(subject) {
t.Fatalf("a good token: %v", err)
}
// S3: it verifies as a token, but not for this content or this date.
for name, der := range map[string][]byte{
"another imprint": cmstest.Token(subject, now, cmstest.TokenOptions{Imprint: bytes.Repeat([]byte{1}, 32)}, tsa),
"the TSA had expired": cmstest.Token(subject, now, cmstest.TokenOptions{}, old),
} {
tok, err := cms.ParseToken(der)
if err != nil || tok.Check(subject) {
t.Errorf("%s: %v, valid %v", name, err, err == nil && tok.Check(subject))
}
}
// S2: the form.
for name, der := range map[string][]byte{
"a TSTInfo of version 2": cmstest.Token(subject, now, cmstest.TokenOptions{Version: 2}, tsa),
"not DER": {0x30, 0x80, 0x00, 0x00},
"empty": nil,
} {
if _, err := cms.ParseToken(der); !errors.Is(err, cms.ErrForm) {
t.Errorf("%s: %v", name, err)
}
}
// S1: an algorithm outside the table.
small := cmstest.NewRSA("TSA 1024", 1024, from, to)
if _, err := cms.ParseToken(cmstest.Token(subject, now, cmstest.TokenOptions{}, small)); !errors.Is(err, cms.ErrAlgorithm) {
t.Errorf("a key of 1024 bits: %v", err)
}
}
func TestSignatureNotVerifiable(t *testing.T) {
small := cmstest.NewRSA("Chica", 1024, from, to)
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{}, small))
if err != nil {
t.Fatal(err)
}
if r := sd.Signers[0].Check(msg); r != cms.NotVerifiable {
t.Errorf("RSA of 1024 bits: %v", r)
}
}
func TestSignatureForm(t *testing.T) {
a := cmstest.NewECDSA("Ana", elliptic.P256(), from, to)
good := cmstest.Signature(msg, cmstest.Options{}, a)
if _, err := cms.ParseSignature(good); err != nil {
t.Fatal(err)
}
bad := map[string][]byte{
"a byte after it": append(bytes.Clone(good), 0),
"truncated": good[:len(good)-1],
"not a SignedData": {0x30, 0x03, 0x02, 0x01, 0x00},
"no certificate of the signer": cmstest.Signature(msg, cmstest.Options{OmitCert: true}, a),
"two timestamp attributes": cmstest.Signature(msg, cmstest.Options{Token2: true, Token: func(s []byte) []byte {
return cmstest.Seq(cmstest.OID(cmstest.OIDData))
}}, a),
"a signing-certificate with another hash": cmstest.Signature(msg, cmstest.Options{Mutate: func(attrs [][]byte) [][]byte {
attrs[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV2), cmstest.Set(0x31, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 32)))))))
return attrs
}}, a),
"no message-digest": cmstest.Signature(msg, cmstest.Options{Mutate: func(attrs [][]byte) [][]byte { return append(attrs[:1], attrs[2:]...) }}, a),
}
for name, der := range bad {
if _, err := cms.ParseSignature(der); !errors.Is(err, cms.ErrForm) {
t.Errorf("%s: %v", name, err)
}
}
}
func sha(b []byte) []byte {
h := cmstestSHA(b)
return h[:]
}
func cmstestSHA(b []byte) [32]byte { return sha256.Sum256(b) }

Powered by TurnKey Linux.