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.
176 lines
6.4 KiB
176 lines
6.4 KiB
|
6 days ago
|
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) }
|