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) }