diff --git a/internal/cms/cert.go b/internal/cms/cert.go new file mode 100644 index 0000000..98b3e65 --- /dev/null +++ b/internal/cms/cert.go @@ -0,0 +1,145 @@ +package cms + +import ( + "bytes" + "crypto/sha256" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "math/big" + "strings" + "time" +) + +// Cert is an X.509 certificate read for what spec v0.11 §29.10 uses of it: +// who it names, when it is valid and its key. DateKeys does not check who +// issued it. It is read with encoding/asn1 and not with crypto/x509, which +// refuses a certificate with a key of a curve that Go does not have, such as +// brainpool: that certificate is still one that a signer names, and its +// signature is "not verifiable", not malformed. +type Cert struct { + // Raw is the DER of the certificate and Hash its SHA-256. + Raw []byte + Hash [32]byte + + Serial *big.Int + RawIssuer, RawSubject []byte + SKI []byte + NotBefore, NotAfter time.Time + // SPKI is the DER of the SubjectPublicKeyInfo. + SPKI []byte + Subject pkix.RDNSequence + Issuer pkix.RDNSequence +} + +type tbsCertificate struct { + Raw asn1.RawContent + Version int `asn1:"optional,explicit,default:0,tag:0"` + Serial *big.Int + SigAlg asn1.RawValue + Issuer asn1.RawValue + Validity validity + Subject asn1.RawValue + PublicKey asn1.RawValue + IssuerUID asn1.BitString `asn1:"optional,tag:1"` + SubjectUID asn1.BitString `asn1:"optional,tag:2"` + Extensions []pkix.Extension `asn1:"optional,explicit,tag:3"` +} + +type validity struct{ NotBefore, NotAfter time.Time } + +type certificate struct { + TBS tbsCertificate + SigAlg asn1.RawValue + Sig asn1.BitString +} + +var oidSKI = asn1.ObjectIdentifier{2, 5, 29, 14} + +// ParseCert reads the DER of a certificate. +func ParseCert(raw []byte) (*Cert, error) { + var c certificate + rest, err := asn1.Unmarshal(raw, &c) + if err != nil { + return nil, fmt.Errorf("certificate: %w", err) + } + if len(rest) != 0 { + return nil, errors.New("certificate: bytes after it") + } + t := &c.TBS + if t.Serial == nil || t.Issuer.Tag != asn1.TagSequence || t.Subject.Tag != asn1.TagSequence || t.PublicKey.Tag != asn1.TagSequence { + return nil, errors.New("certificate: malformed") + } + out := &Cert{ + Raw: raw, Hash: sha256.Sum256(raw), Serial: t.Serial, + RawIssuer: t.Issuer.FullBytes, RawSubject: t.Subject.FullBytes, + NotBefore: t.Validity.NotBefore, NotAfter: t.Validity.NotAfter, SPKI: t.PublicKey.FullBytes, + } + if _, err := asn1.Unmarshal(out.RawSubject, &out.Subject); err != nil { + return nil, fmt.Errorf("certificate: subject: %w", err) + } + if _, err := asn1.Unmarshal(out.RawIssuer, &out.Issuer); err != nil { + return nil, fmt.Errorf("certificate: issuer: %w", err) + } + for _, e := range t.Extensions { + if e.Id.Equal(oidSKI) { + var ski []byte + if rest, err := asn1.Unmarshal(e.Value, &ski); err != nil || len(rest) != 0 { + return nil, errors.New("certificate: subjectKeyIdentifier malformed") + } + out.SKI = ski + } + } + return out, nil +} + +var ( + oidCommonName = asn1.ObjectIdentifier{2, 5, 4, 3} + oidSurname = asn1.ObjectIdentifier{2, 5, 4, 4} + oidGivenName = asn1.ObjectIdentifier{2, 5, 4, 42} +) + +func rdnString(rdn pkix.RDNSequence, oid asn1.ObjectIdentifier) string { + for _, set := range rdn { + for _, a := range set { + if s, ok := a.Value.(string); ok && a.Type.Equal(oid) { + return s + } + } + } + return "" +} + +// Holder returns the name of the subject, taken from its commonName or from +// its givenName and surname, and "" when it has neither. It does not apply the +// rules of text of spec §29.6 to it: the caller does, and shows the hash of +// the certificate when they fail. +func (c *Cert) Holder() string { + if n := rdnString(c.Subject, oidCommonName); n != "" { + return n + } + given, sur := rdnString(c.Subject, oidGivenName), rdnString(c.Subject, oidSurname) + if given != "" && sur != "" { + return given + " " + sur + } + return "" +} + +// IssuerName returns the issuer as the certificate names it, as a text for +// the person to read: commonName, or all of the attributes it has. +func (c *Cert) IssuerName() string { + if n := rdnString(c.Issuer, oidCommonName); n != "" { + return n + } + return strings.TrimSpace(c.Issuer.String()) +} + +// ValidAt reports whether t is in the validity period of the certificate. +func (c *Cert) ValidAt(t time.Time) bool { + return !t.Before(c.NotBefore) && !t.After(c.NotAfter) +} + +func (c *Cert) hasSID(issuer []byte, serial *big.Int) bool { + return bytes.Equal(c.RawIssuer, issuer) && c.Serial.Cmp(serial) == 0 +} diff --git a/internal/cms/cms.go b/internal/cms/cms.go new file mode 100644 index 0000000..6918481 --- /dev/null +++ b/internal/cms/cms.go @@ -0,0 +1,470 @@ +// Package cms reads the CMS signatures (RFC 5652) and the RFC 3161 time-stamp +// tokens that spec v0.11 §29.10 and §29.11 define, with the CAdES profile +// that AutoFirma and other signing applications produce, and checks them with +// a closed table of algorithms. It uses the standard library only. +// +// It checks the signature and the dates, never who issued a certificate or +// whether it was revoked: a validator of the country that corresponds does +// that (spec §29.10). +package cms + +import ( + "bytes" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/asn1" + "errors" + "fmt" + "hash" + "math/big" + + "g.activething.com/go/DateKeys/internal/der" +) + +// ErrForm is the error of a signature or a token whose form breaks the +// profile of spec §29.10 or §29.11: the verdicts F1 and S2. +var ErrForm = errors.New("cms: the form breaks the profile") + +// ErrAlgorithm is the error of a token that uses an algorithm outside the +// table of spec §29.10: the verdict S1. +var ErrAlgorithm = errors.New("cms: an algorithm outside the table") + +func formErr(format string, args ...any) error { + return fmt.Errorf("%w: %s", ErrForm, fmt.Sprintf(format, args...)) +} + +// The object identifiers of the profile. +var ( + oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} + oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} + oidContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} + oidMessageDig = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} + oidSigCertV1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 12} + oidSigCertV2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 47} + oidSigTimeStamp = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14} + oidTSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4} + oidRIOCSP = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 16, 2} + + oidSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} + oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} + oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} + oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} +) + +// SignedData is the part of a CMS SignedData that the profile uses. +type SignedData struct { + Certs []*Cert + // OCSP are the OCSP responses of crls. + OCSP [][]byte + // Signers are the SignerInfo, in the order of the encoding. + Signers []*SignerInfo + // EContent is the content of a token, nil in a detached signature. + EContent []byte +} + +// SignerInfo is a SignerInfo with the certificate that its sid names. +type SignerInfo struct { + Cert *Cert + // DigestAlg and SigAlg are the algorithm identifiers as written. + DigestAlg, SigAlg algID + // SignedAttrs is the DER of the signedAttrs as stored, with the + // context tag [0]; the signature covers it with the tag of a SET. + SignedAttrs []byte + MessageDigest []byte + Signature []byte + // Token is the signature-time-stamp attribute, the DER of its + // ContentInfo, nil when there is none. + Token []byte +} + +type algID struct { + OID asn1.ObjectIdentifier + Params []byte // the DER of the parameters, nil when absent +} + +func parseAlgID(b []byte) (algID, error) { + id, kids, err := der.Split(b) + if err != nil || id != 0x30 || len(kids) < 1 || len(kids) > 2 || kids[0][0] != 0x06 { + return algID{}, formErr("an AlgorithmIdentifier") + } + var a algID + if _, err := asn1.Unmarshal(kids[0], &a.OID); err != nil { + return algID{}, formErr("an AlgorithmIdentifier: %v", err) + } + if len(kids) == 2 { + a.Params = kids[1] + } + return a, nil +} + +// hashOf returns the hash that an identifier of the table names, and false +// for any other. +func (a algID) hashOf() (func() hash.Hash, bool) { + if a.Params != nil && !bytes.Equal(a.Params, []byte{5, 0}) { + return nil, false + } + switch { + case a.OID.Equal(oidSHA256): + return sha256.New, true + case a.OID.Equal(oidSHA384): + return sha512.New384, true + case a.OID.Equal(oidSHA512): + return sha512.New, true + } + return nil, false +} + +// ParseSignature reads the detached CMS signature of an author-signature of +// alg 2 (spec §29.10), checking its form in the order of the spec. +func ParseSignature(b []byte) (*SignedData, error) { + return parse(b, false) +} + +func parse(b []byte, token bool) (*SignedData, error) { + if err := der.Check(b); err != nil { + return nil, formErr("%v", err) + } + _, ci, err := der.Split(b) + if err != nil || len(ci) != 2 || ci[0][0] != 0x06 || ci[1][0] != 0xa0 { + return nil, formErr("a ContentInfo") + } + var oid asn1.ObjectIdentifier + if _, err := asn1.Unmarshal(ci[0], &oid); err != nil || !oid.Equal(oidSignedData) { + return nil, formErr("the content type is not id-signedData") + } + _, inner, err := der.Split(ci[1]) + if err != nil || len(inner) != 1 || inner[0][0] != 0x30 { + return nil, formErr("a SignedData") + } + _, sd, err := der.Split(inner[0]) + if err != nil || len(sd) < 4 || sd[0][0] != 0x02 || sd[1][0] != 0x31 || sd[2][0] != 0x30 { + return nil, formErr("a SignedData") + } + // digestAlgorithms: a SET OF in order. + _, algs, err := der.Split(sd[1]) + if err != nil || !der.SetOfSorted(algs) { + return nil, formErr("digestAlgorithms is not a SET OF in DER order") + } + for _, a := range algs { + if _, err := parseAlgID(a); err != nil { + return nil, err + } + } + out := &SignedData{} + if err := parseEncap(sd[2], token, out); err != nil { + return nil, err + } + rest := sd[3:] + if len(rest) > 0 && rest[0][0] == 0xa0 { + if err := parseCerts(rest[0], out); err != nil { + return nil, err + } + rest = rest[1:] + } + if len(rest) > 0 && rest[0][0] == 0xa1 { + if err := parseCRLs(rest[0], out); err != nil { + return nil, err + } + rest = rest[1:] + } + if len(rest) != 1 || rest[0][0] != 0x31 { + return nil, formErr("signerInfos") + } + _, infos, err := der.Split(rest[0]) + if err != nil || len(infos) == 0 || !der.SetOfSorted(infos) { + return nil, formErr("signerInfos is not a SET OF in DER order, or is empty") + } + if token && len(infos) != 1 { + return nil, formErr("a token has one SignerInfo, not %d", len(infos)) + } + used := map[*Cert]bool{} + for _, si := range infos { + s, err := parseSignerInfo(si, out, token) + if err != nil { + return nil, err + } + if used[s.Cert] { + return nil, formErr("two SignerInfo for one certificate") + } + used[s.Cert] = true + out.Signers = append(out.Signers, s) + } + return out, nil +} + +// parseEncap checks encapContentInfo: id-data without content in a detached +// signature, and id-ct-TSTInfo with its content in a token. +func parseEncap(b []byte, token bool, out *SignedData) error { + _, kids, err := der.Split(b) + if err != nil || len(kids) < 1 || len(kids) > 2 || kids[0][0] != 0x06 { + return formErr("encapContentInfo") + } + var oid asn1.ObjectIdentifier + if _, err := asn1.Unmarshal(kids[0], &oid); err != nil { + return formErr("encapContentInfo") + } + if !token { + if !oid.Equal(oidData) || len(kids) != 1 { + return formErr("a signature is detached: id-data and no eContent") + } + return nil + } + if !oid.Equal(oidTSTInfo) || len(kids) != 2 || kids[1][0] != 0xa0 { + return formErr("a token holds a TSTInfo") + } + _, e, err := der.Split(kids[1]) + if err != nil || len(e) != 1 || e[0][0] != 0x04 { + return formErr("eContent") + } + if out.EContent, err = der.Content(e[0]); err != nil { + return formErr("eContent") + } + return nil +} + +func parseCerts(b []byte, out *SignedData) error { + _, kids, err := der.Split(b) + if err != nil || !der.SetOfSorted(kids) { + return formErr("certificates is not a SET OF in DER order") + } + for _, k := range kids { + if k[0] != 0x30 { // another choice of CertificateChoices: it decides nothing + continue + } + c, err := ParseCert(k) + if err != nil { + return formErr("%v", err) + } + out.Certs = append(out.Certs, c) + } + return nil +} + +func parseCRLs(b []byte, out *SignedData) error { + _, kids, err := der.Split(b) + if err != nil || !der.SetOfSorted(kids) { + return formErr("crls is not a SET OF in DER order") + } + for _, k := range kids { + if k[0] != 0xa1 { + return formErr("crls holds only OCSP responses") + } + _, f, err := der.Split(k) + if err != nil || len(f) != 2 || f[0][0] != 0x06 { + return formErr("an OtherRevocationInfoFormat") + } + var oid asn1.ObjectIdentifier + if _, err := asn1.Unmarshal(f[0], &oid); err != nil || !oid.Equal(oidRIOCSP) { + return formErr("crls holds only OCSP responses") + } + out.OCSP = append(out.OCSP, f[1]) + } + return nil +} + +func parseSignerInfo(b []byte, sd *SignedData, token bool) (*SignerInfo, error) { + _, f, err := der.Split(b) + if err != nil || len(f) < 6 || f[0][0] != 0x02 || f[2][0] != 0x30 || f[3][0] != 0xa0 || f[4][0] != 0x30 || f[5][0] != 0x04 { + return nil, formErr("a SignerInfo with signedAttrs") + } + s := &SignerInfo{SignedAttrs: f[3]} + if s.Cert, err = findSigner(f[1], sd.Certs); err != nil { + return nil, err + } + if s.DigestAlg, err = parseAlgID(f[2]); err != nil { + return nil, err + } + if s.SigAlg, err = parseAlgID(f[4]); err != nil { + return nil, err + } + if s.Signature, err = der.Content(f[5]); err != nil { + return nil, formErr("signature") + } + if len(f) > 7 || len(f) == 7 && f[6][0] != 0xa1 { + return nil, formErr("a SignerInfo with something after its signature") + } + if err := parseSignedAttrs(s, token); err != nil { + return nil, err + } + if len(f) == 7 { + if err := parseUnsignedAttrs(s, f[6]); err != nil { + return nil, err + } + } + return s, nil +} + +// findSigner returns the one certificate that the sid names. +func findSigner(sid []byte, certs []*Cert) (*Cert, error) { + var found *Cert + n := 0 + switch sid[0] { + case 0x30: // issuerAndSerialNumber + _, p, err := der.Split(sid) + if err != nil || len(p) != 2 || p[0][0] != 0x30 || p[1][0] != 0x02 { + return nil, formErr("issuerAndSerialNumber") + } + var serial *big.Int + if _, err := asn1.Unmarshal(p[1], &serial); err != nil { + return nil, formErr("issuerAndSerialNumber") + } + for _, c := range certs { + if c.hasSID(p[0], serial) { + found, n = c, n+1 + } + } + case 0x80: // subjectKeyIdentifier + ski, err := der.Content(sid) + if err != nil { + return nil, formErr("subjectKeyIdentifier") + } + for _, c := range certs { + if c.SKI != nil && bytes.Equal(c.SKI, ski) { + found, n = c, n+1 + } + } + default: + return nil, formErr("a SignerIdentifier") + } + if n != 1 { + return nil, formErr("a sid that names %d certificates, not one", n) + } + return found, nil +} + +// attrs returns the values of each attribute type of the SET OF Attribute b. +func attrs(b []byte) (map[string][][]byte, error) { + _, kids, err := der.Split(b) + if err != nil || !der.SetOfSorted(kids) { + return nil, formErr("attributes are not a SET OF in DER order") + } + out := map[string][][]byte{} + for _, a := range kids { + _, p, err := der.Split(a) + if err != nil || len(p) != 2 || a[0] != 0x30 || p[0][0] != 0x06 || p[1][0] != 0x31 { + return nil, formErr("an Attribute") + } + var oid asn1.ObjectIdentifier + if _, err := asn1.Unmarshal(p[0], &oid); err != nil { + return nil, formErr("an Attribute") + } + _, vals, err := der.Split(p[1]) + if err != nil || !der.SetOfSorted(vals) { + return nil, formErr("the values of an attribute are not a SET OF in DER order") + } + out[oid.String()] = append(out[oid.String()], vals...) + } + return out, nil +} + +// one returns the only value of the attribute, which must exist once. +func one(m map[string][][]byte, oid asn1.ObjectIdentifier, name string) ([]byte, error) { + v := m[oid.String()] + if len(v) != 1 { + return nil, formErr("%s: %d values, not one", name, len(v)) + } + return v[0], nil +} + +// parseSignedAttrs checks the signedAttrs of the profile (spec §29.10 rule 4, +// §29.11): content-type, message-digest and the signing certificate. +func parseSignedAttrs(s *SignerInfo, token bool) error { + m, err := attrs(s.SignedAttrs) + if err != nil { + return err + } + // Each of the three is one attribute with one value: attrs merges the + // values of attributes of one type, so two attributes show as two values. + ct, err := one(m, oidContentType, "content-type") + if err != nil { + return err + } + want := oidData + if token { + want = oidTSTInfo + } + var got asn1.ObjectIdentifier + if _, err := asn1.Unmarshal(ct, &got); err != nil || !got.Equal(want) { + return formErr("content-type is not %v", want) + } + md, err := one(m, oidMessageDig, "message-digest") + if err != nil { + return err + } + if md[0] != 0x04 { + return formErr("message-digest is not an OCTET STRING") + } + if s.MessageDigest, err = der.Content(md); err != nil { + return formErr("message-digest") + } + switch { + case len(m[oidSigCertV2.String()]) == 1 && len(m[oidSigCertV1.String()]) == 0: + return checkESSCert(s.Cert, m[oidSigCertV2.String()][0], true) + case token && len(m[oidSigCertV1.String()]) == 1 && len(m[oidSigCertV2.String()]) == 0: + return checkESSCert(s.Cert, m[oidSigCertV1.String()][0], false) + } + return formErr("signing-certificate: one attribute of one value is required") +} + +// checkESSCert checks that the first ESSCertID of a signing-certificate or +// signing-certificate-v2 (RFC 2634, RFC 5035) is the hash of the certificate. +func checkESSCert(c *Cert, v []byte, v2 bool) error { + id, sc, err := der.Split(v) + if err != nil || id != 0x30 || len(sc) < 1 || sc[0][0] != 0x30 { + return formErr("a SigningCertificate") + } + _, ids, err := der.Split(sc[0]) + if err != nil || len(ids) < 1 || ids[0][0] != 0x30 { + return formErr("an ESSCertID") + } + _, f, err := der.Split(ids[0]) + if err != nil || len(f) < 1 { + return formErr("an ESSCertID") + } + newHash := sha1.New + if v2 { + newHash = sha256.New + if f[0][0] == 0x30 { // hashAlgorithm, which defaults to SHA-256 + a, err := parseAlgID(f[0]) + if err != nil { + return err + } + h, ok := a.hashOf() + if !ok { + return formErr("the hash of the ESSCertIDv2 is outside the table") + } + newHash, f = h, f[1:] + } + } + if len(f) < 1 || f[0][0] != 0x04 { + return formErr("certHash") + } + hv, err := der.Content(f[0]) + if err != nil { + return formErr("certHash") + } + h := newHash() + h.Write(c.Raw) + if !bytes.Equal(h.Sum(nil), hv) { + return formErr("the certHash is not that of the certificate of the signer") + } + return nil +} + +// parseUnsignedAttrs reads the signature-time-stamp, at most one with one +// value (spec §29.10 rule 4); the other attributes decide nothing. +func parseUnsignedAttrs(s *SignerInfo, b []byte) error { + m, err := attrs(b) + if err != nil { + return err + } + switch v := m[oidSigTimeStamp.String()]; len(v) { + case 0: + case 1: + s.Token = v[0] + default: + return formErr("signature-time-stamp: %d values, not one", len(v)) + } + return nil +} diff --git a/internal/cms/cms_test.go b/internal/cms/cms_test.go new file mode 100644 index 0000000..7fbc1c9 --- /dev/null +++ b/internal/cms/cms_test.go @@ -0,0 +1,175 @@ +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) } diff --git a/internal/cms/cmstest/cmstest.go b/internal/cms/cmstest/cmstest.go new file mode 100644 index 0000000..1128221 --- /dev/null +++ b/internal/cms/cmstest/cmstest.go @@ -0,0 +1,348 @@ +// Package cmstest builds the CMS signatures and the RFC 3161 tokens that the +// tests of internal/cms and of capsule read: certificates of test keys, a +// detached signature of a message with its signedAttrs and, optionally, a +// time-stamp token of its signature. It is the encoder that a signing +// application has; nothing outside tests uses it. +package cmstest + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "math/big" + "slices" + "time" +) + +// Signer is a certificate with its private key. +type Signer struct { + Cert *x509.Certificate + Key crypto.Signer +} + +// NewRSA returns a signer with an RSA key of bits bits, valid in +// [notBefore, notAfter], named cn. +func NewRSA(cn string, bits int, notBefore, notAfter time.Time) Signer { + k, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + panic(err) + } + return newSigner(cn, k, notBefore, notAfter) +} + +// NewECDSA returns a signer with an ECDSA key on curve. +func NewECDSA(cn string, curve elliptic.Curve, notBefore, notAfter time.Time) Signer { + k, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + panic(err) + } + return newSigner(cn, k, notBefore, notAfter) +} + +func newSigner(cn string, k crypto.Signer, notBefore, notAfter time.Time) Signer { + serial, _ := rand.Int(rand.Reader, big.NewInt(1<<62)) + t := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: cn, Organization: []string{"DateKeys test"}}, + NotBefore: notBefore, NotAfter: notAfter, + KeyUsage: x509.KeyUsageDigitalSignature, + SubjectKeyId: []byte(cn), + } + raw, err := x509.CreateCertificate(rand.Reader, t, t, k.Public(), k) + if err != nil { + panic(err) + } + c, err := x509.ParseCertificate(raw) + if err != nil { + panic(err) + } + return Signer{Cert: c, Key: k} +} + +// The DER building blocks. + +func tlv(tag byte, content ...[]byte) []byte { + c := bytes.Join(content, nil) + out := []byte{tag} + switch n := len(c); { + case n < 0x80: + out = append(out, byte(n)) + case n < 0x100: + out = append(out, 0x81, byte(n)) + case n < 0x10000: + out = append(out, 0x82, byte(n>>8), byte(n)) + default: + out = append(out, 0x83, byte(n>>16), byte(n>>8), byte(n)) + } + return append(out, c...) +} + +// Seq is a SEQUENCE. +func Seq(content ...[]byte) []byte { return tlv(0x30, content...) } + +// Set is a SET OF, in DER order. +func Set(tag byte, elems ...[]byte) []byte { + e := slices.Clone(elems) + slices.SortFunc(e, bytes.Compare) + return tlv(tag, e...) +} + +// OID is an OBJECT IDENTIFIER. +func OID(oid asn1.ObjectIdentifier) []byte { + b, err := asn1.Marshal(oid) + if err != nil { + panic(err) + } + return b +} + +// Octets is an OCTET STRING. +func Octets(b []byte) []byte { return tlv(0x04, b) } + +// Int is an INTEGER. +func Int(n int64) []byte { + b, err := asn1.Marshal(n) + if err != nil { + panic(err) + } + return b +} + +var ( + OIDData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} + OIDSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} + oidContent = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} + oidDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} + OIDSigCertV2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 47} + oidSigCertV1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 12} + OIDTimeStamp = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14} + oidTSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4} + oidOCSP = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 16, 2} + oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} + oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} + oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} + oidRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + oidPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} + oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8} + oidECDSA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} + oidECDSA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} + oidECDSA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} +) + +func hashAlg(h crypto.Hash) []byte { + switch h { + case crypto.SHA384: + return Seq(OID(oidSHA384)) + case crypto.SHA512: + return Seq(OID(oidSHA512)) + } + return Seq(OID(oidSHA256)) +} + +func hashOID(h crypto.Hash) asn1.ObjectIdentifier { + switch h { + case crypto.SHA384: + return oidSHA384 + case crypto.SHA512: + return oidSHA512 + } + return oidSHA256 +} + +func sum(h crypto.Hash, b []byte) []byte { + x := h.New() + x.Write(b) + return x.Sum(nil) +} + +// Options changes what Build writes, to make signatures that the profile +// rejects or that verify to something else. +type Options struct { + // Hash is the digest of the signature, SHA-256 by default. + Hash crypto.Hash + // PSS signs with RSASSA-PSS instead of PKCS #1 v1.5. + PSS bool + // SKI names the signer by subjectKeyIdentifier instead of by issuer + // and serial. + SKI bool + // Token, when not nil, is the time-stamp token of the signature that + // Build wants as an unsigned attribute: it receives the signature value. + Token func(signature []byte) []byte + // Message is what the message-digest covers, when not the signed message. + Message []byte + // Mutate edits the signedAttrs, as a list of the DER of each attribute, + // before they are signed. + Mutate func(attrs [][]byte) [][]byte + // Token2 puts two signature-time-stamp attributes, to break the profile. + Token2 bool + // OCSP adds this response in crls. + OCSP []byte + // OmitCert leaves the certificate of the signer out of certificates. + OmitCert bool +} + +func attr(oid asn1.ObjectIdentifier, values ...[]byte) []byte { + return Seq(OID(oid), Set(0x31, values...)) +} + +// Signature returns the detached CMS signature of message by the signers, in +// DER, as AutoFirma writes it: one SignerInfo each, with content-type, +// message-digest and signing-certificate-v2 as signed attributes. +func Signature(message []byte, opts Options, signers ...Signer) []byte { + return build(message, opts, false, signers) +} + +func build(message []byte, o Options, token bool, signers []Signer) []byte { + if o.Hash == 0 { + o.Hash = crypto.SHA256 + } + var certs, infos [][]byte + for _, s := range signers { + if !o.OmitCert { + certs = append(certs, s.Cert.Raw) + } + infos = append(infos, signerInfo(message, o, token, s)) + } + var body []byte + body = append(body, Int(1)...) + body = append(body, Set(0x31, hashAlg(o.Hash))...) + body = append(body, encap(message, token)...) + if len(certs) > 0 { + body = append(body, Set(0xa0, certs...)...) + } + if o.OCSP != nil { + body = append(body, Set(0xa1, tlv(0xa1, OID(oidOCSP), o.OCSP))...) + } + body = append(body, Set(0x31, infos...)...) + return Seq(OID(OIDSignedData), tlv(0xa0, Seq(body))) +} + +func encap(content []byte, token bool) []byte { + if !token { + return Seq(OID(OIDData)) + } + return Seq(OID(oidTSTInfo), tlv(0xa0, Octets(content))) +} + +func signerInfo(message []byte, o Options, token bool, s Signer) []byte { + var sid []byte + if o.SKI { + sid = tlv(0x80, s.Cert.SubjectKeyId) + } else { + sid = Seq(s.Cert.RawIssuer, mustMarshal(s.Cert.SerialNumber)) + } + contentType := OIDData + if token { + contentType = oidTSTInfo + } + md := message + if o.Message != nil { + md = o.Message + } + essHash := sha256.Sum256(s.Cert.Raw) + attrs := [][]byte{ + attr(oidContent, OID(contentType)), + attr(oidDigest, Octets(sum(o.Hash, md))), + } + if token { + h := sha1.Sum(s.Cert.Raw) + attrs = append(attrs, attr(oidSigCertV1, Seq(Seq(Seq(Octets(h[:])))))) + } else { + attrs = append(attrs, attr(OIDSigCertV2, Seq(Seq(Seq(Octets(essHash[:])))))) + } + if o.Mutate != nil { + attrs = o.Mutate(attrs) + } + signed := Set(0xa0, attrs...) + forSig := append([]byte{0x31}, signed[1:]...) + digest := sum(o.Hash, forSig) + + var sigAlg, sig []byte + switch k := s.Key.(type) { + case *rsa.PrivateKey: + if o.PSS { + params := Seq(tlv(0xa0, hashAlg(o.Hash)), tlv(0xa1, Seq(OID(oidMGF1), hashAlg(o.Hash))), tlv(0xa2, Int(int64(o.Hash.Size())))) + sigAlg = Seq(OID(oidPSS), params) + sig, _ = rsa.SignPSS(rand.Reader, k, o.Hash, digest, &rsa.PSSOptions{SaltLength: o.Hash.Size(), Hash: o.Hash}) + } else { + sigAlg = Seq(OID(oidRSA), []byte{5, 0}) + sig, _ = rsa.SignPKCS1v15(rand.Reader, k, o.Hash, digest) + } + case *ecdsa.PrivateKey: + oid := oidECDSA256 + switch o.Hash { + case crypto.SHA384: + oid = oidECDSA384 + case crypto.SHA512: + oid = oidECDSA512 + } + sigAlg = Seq(OID(oid)) + sig, _ = ecdsa.SignASN1(rand.Reader, k, digest) + } + f := [][]byte{Int(1), sid, hashAlg(o.Hash), signed, sigAlg, Octets(sig)} + if o.Token != nil { + t := o.Token(sig) + v := attr(OIDTimeStamp, t) + if o.Token2 { + v = Seq(OID(OIDTimeStamp), Set(0x31, t, Seq(OID(OIDData)))) + } + f = append(f, Set(0xa1, v)) + } + return Seq(f...) +} + +func mustMarshal(v any) []byte { + b, err := asn1.Marshal(v) + if err != nil { + panic(err) + } + return b +} + +// TokenOptions changes what Token writes. +type TokenOptions struct { + Hash crypto.Hash // the hash of the messageImprint, SHA-256 by default + Accuracy time.Duration // whole seconds; zero for none + Version int // the version of the TSTInfo, 1 by default + // Imprint, when not nil, is written as the hashed message instead of + // the hash of the subject. + Imprint []byte +} + +// Token returns the RFC 3161 time-stamp token that tsa issues over subject +// at genTime. +func Token(subject []byte, genTime time.Time, o TokenOptions, tsa Signer) []byte { + if o.Hash == 0 { + o.Hash = crypto.SHA256 + } + if o.Version == 0 { + o.Version = 1 + } + imprint := sum(o.Hash, subject) + if o.Imprint != nil { + imprint = o.Imprint + } + gt, err := asn1.MarshalWithParams(genTime.UTC(), "generalized") + if err != nil { + panic(err) + } + info := []byte{} + info = append(info, Int(int64(o.Version))...) + info = append(info, OID(asn1.ObjectIdentifier{1, 2, 3, 4})...) + info = append(info, Seq(hashAlg(o.Hash), Octets(imprint))...) + info = append(info, Int(42)...) + info = append(info, gt...) + if o.Accuracy != 0 { + info = append(info, Seq(Int(int64(o.Accuracy/time.Second)))...) + } + tst := Seq(info) + return build(tst, Options{Hash: crypto.SHA256}, true, []Signer{tsa}) +} diff --git a/internal/cms/verify.go b/internal/cms/verify.go new file mode 100644 index 0000000..a790004 --- /dev/null +++ b/internal/cms/verify.go @@ -0,0 +1,304 @@ +package cms + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "math/big" + "time" + + "g.activething.com/go/DateKeys/internal/der" +) + +// Result is the result of checking the signature of a SignerInfo (spec +// §29.10, "Verificación"). +type Result int + +const ( + // Valid: the message-digest is the hash of the message and the signature + // of the signedAttrs verifies with the key of the certificate. + Valid Result = iota + // Invalid: one of the two does not hold. + Invalid + // NotVerifiable: an algorithm, a key size or a curve outside the table. + NotVerifiable +) + +var ( + oidRSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + oidSHA256RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} + oidSHA384RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} + oidSHA512RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} + oidPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} + oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8} + oidECDSA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} + oidECDSA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} + oidECDSA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} +) + +type scheme int + +const ( + schemePKCS1 scheme = iota + 1 + schemePSS + schemeECDSA +) + +// params returns the hash, the signature scheme and the hash that the +// signature algorithm itself names, when it does, of the SignerInfo, and +// whether they are in the table. +func (s *SignerInfo) params() (crypto.Hash, scheme, bool) { + newHash, ok := s.DigestAlg.hashOf() + if !ok { + return 0, 0, false + } + var h crypto.Hash + switch newHash().Size() { + case 32: + h = crypto.SHA256 + case 48: + h = crypto.SHA384 + default: + h = crypto.SHA512 + } + oid, params := s.SigAlg.OID, s.SigAlg.Params + nullOrAbsent := params == nil || bytes.Equal(params, []byte{5, 0}) + switch { + case oid.Equal(oidRSAEncryption): + return h, schemePKCS1, nullOrAbsent + case oid.Equal(oidSHA256RSA): + return h, schemePKCS1, nullOrAbsent && h == crypto.SHA256 + case oid.Equal(oidSHA384RSA): + return h, schemePKCS1, nullOrAbsent && h == crypto.SHA384 + case oid.Equal(oidSHA512RSA): + return h, schemePKCS1, nullOrAbsent && h == crypto.SHA512 + case oid.Equal(oidECDSA256): + return h, schemeECDSA, params == nil && h == crypto.SHA256 + case oid.Equal(oidECDSA384): + return h, schemeECDSA, params == nil && h == crypto.SHA384 + case oid.Equal(oidECDSA512): + return h, schemeECDSA, params == nil && h == crypto.SHA512 + case oid.Equal(oidPSS): + return h, schemePSS, pssParamsOK(params, newHash().Size(), s.DigestAlg) + } + return 0, 0, false +} + +// pssParamsOK checks RSASSA-PSS-params (RFC 4055): the hash of digestAlgorithm, +// MGF1 with that hash, a salt of its length and trailerField 1. +func pssParamsOK(params []byte, hashLen int, digest algID) bool { + if params == nil { + return false + } + id, f, err := der.Split(params) + if err != nil || id != 0x30 { + return false + } + var hashOK, mgfOK, saltOK bool + for _, e := range f { + _, in, err := der.Split(e) + if err != nil || len(in) != 1 { + return false + } + switch e[0] { + case 0xa0: + a, err := parseAlgID(in[0]) + hashOK = err == nil && a.OID.Equal(digest.OID) && (a.Params == nil || bytes.Equal(a.Params, []byte{5, 0})) + case 0xa1: + a, err := parseAlgID(in[0]) + if err != nil || !a.OID.Equal(oidMGF1) || a.Params == nil { + return false + } + inner, err := parseAlgID(a.Params) + mgfOK = err == nil && inner.OID.Equal(digest.OID) && (inner.Params == nil || bytes.Equal(inner.Params, []byte{5, 0})) + case 0xa2: + var n int + _, err := asn1.Unmarshal(in[0], &n) + saltOK = err == nil && n == hashLen + case 0xa3: + var n int + if _, err := asn1.Unmarshal(in[0], &n); err != nil || n != 1 { + return false + } + default: + return false + } + } + // hashAlgorithm and maskGenAlgorithm default to SHA-1, and the salt to 20 + // bytes: none of them is in the table, so each must be present. + return hashOK && mgfOK && saltOK +} + +// publicKey returns the key of the certificate when it is in the table: +// RSA of 2048 to 4096 bits with an odd exponent from 3 to 2^31 - 1, or ECDSA +// on P-256, P-384 or P-521. +func (c *Cert) publicKey() (any, scheme, bool) { + k, err := x509.ParsePKIXPublicKey(c.SPKI) + if err != nil { + return nil, 0, false + } + switch k := k.(type) { + case *rsa.PublicKey: + bits := k.N.BitLen() + if bits < 2048 || bits > 4096 || k.E < 3 || k.E%2 == 0 || int64(k.E) > 1<<31-1 { + return nil, 0, false + } + return k, schemePKCS1, true + case *ecdsa.PublicKey: + switch k.Curve { + case elliptic.P256(), elliptic.P384(), elliptic.P521(): + return k, schemeECDSA, true + } + } + return nil, 0, false +} + +// algorithmsOK reports whether the algorithms of the SignerInfo and the key of +// its certificate are in the table of spec §29.10. +func (s *SignerInfo) algorithmsOK() bool { + _, sch, ok := s.params() + if !ok { + return false + } + _, ksch, ok := s.Cert.publicKey() + if !ok { + return false + } + return sch == ksch || sch == schemePSS && ksch == schemePKCS1 +} + +// Check checks the signature of the SignerInfo over message, the bytes that +// the signature is detached from (spec §29.10): not verifiable for an +// algorithm outside the table; invalid when the message-digest is not the +// hash of message or the signature of the signedAttrs does not verify. +func (s *SignerInfo) Check(message []byte) Result { + if !s.algorithmsOK() { + return NotVerifiable + } + h, sch, _ := s.params() + if sum := hashBytes(h, message); !bytes.Equal(sum, s.MessageDigest) { + return Invalid + } + // The signature covers the signedAttrs with the tag of a SET. + attrs := bytes.Clone(s.SignedAttrs) + attrs[0] = 0x31 + digest := hashBytes(h, attrs) + key, _, _ := s.Cert.publicKey() + var ok bool + switch k := key.(type) { + case *rsa.PublicKey: + if sch == schemePSS { + ok = rsa.VerifyPSS(k, h, digest, s.Signature, &rsa.PSSOptions{SaltLength: h.Size(), Hash: h}) == nil + } else { + ok = rsa.VerifyPKCS1v15(k, h, digest, s.Signature) == nil + } + case *ecdsa.PublicKey: + ok = ecdsa.VerifyASN1(k, digest, s.Signature) + } + if !ok { + return Invalid + } + return Valid +} + +func hashBytes(h crypto.Hash, b []byte) []byte { + x := h.New() + x.Write(b) + return x.Sum(nil) +} + +// Token is a time-stamp token of RFC 3161 read with the profile of spec +// §29.11. +type Token struct { + // GenTime is t, and Accuracy the precision of the token, zero when it + // has none. + GenTime time.Time + Accuracy time.Duration + // ImprintAlg is the hash of the messageImprint, and Imprint the hash. + ImprintAlg asn1.ObjectIdentifier + Imprint []byte + // TSA is the certificate of the time-stamping authority. + TSA *Cert + + data *SignedData +} + +type messageImprint struct { + Alg asn1.RawValue + Hash []byte +} + +type accuracy struct { + Seconds int `asn1:"optional"` + Millis int `asn1:"optional,tag:0"` + Micros int `asn1:"optional,tag:1"` +} + +type tstInfo struct { + Version int + Policy asn1.ObjectIdentifier + Imprint messageImprint + Serial *big.Int + GenTime time.Time `asn1:"generalized"` + Accuracy accuracy `asn1:"optional"` + Ordering bool `asn1:"optional,default:false"` + Nonce *big.Int `asn1:"optional"` + TSA asn1.RawValue `asn1:"optional,explicit,tag:0"` + Ext asn1.RawValue `asn1:"optional,tag:1"` +} + +// ParseToken reads a time-stamp token. It fails with ErrForm when the form +// breaks the profile, and with ErrAlgorithm when an algorithm is outside the +// table, in that order (spec §29.11): the verdicts S2 and S1. +func ParseToken(b []byte) (*Token, error) { + sd, err := parse(b, true) + if err != nil { + return nil, err + } + var info tstInfo + if rest, err := asn1.Unmarshal(sd.EContent, &info); err != nil || len(rest) != 0 { + return nil, formErr("the TSTInfo does not decode") + } + if info.Version != 1 || info.Serial == nil || info.Imprint.Alg.Tag != asn1.TagSequence { + return nil, formErr("a TSTInfo of version 1") + } + ia, err := parseAlgID(info.Imprint.Alg.FullBytes) + if err != nil { + return nil, err + } + t := &Token{ + GenTime: info.GenTime, ImprintAlg: ia.OID, Imprint: info.Imprint.Hash, TSA: sd.Signers[0].Cert, data: sd, + Accuracy: time.Duration(info.Accuracy.Seconds)*time.Second + time.Duration(info.Accuracy.Millis)*time.Millisecond + time.Duration(info.Accuracy.Micros)*time.Microsecond, + } + newHash, ok := ia.hashOf() + if ok && len(info.Imprint.Hash) != newHash().Size() { + return nil, formErr("a messageImprint of the wrong length") + } + if !ok || !sd.Signers[0].algorithmsOK() { + return nil, ErrAlgorithm + } + return t, nil +} + +// ImprintIsSHA256 reports whether the messageImprint uses SHA-256, which a +// seal of seal_type 2 requires (spec §29.11). +func (t *Token) ImprintIsSHA256() bool { return t.ImprintAlg.Equal(oidSHA256) } + +// Check verifies the token over subject, the bytes that it seals: the +// message-digest is the hash of the TSTInfo, the signature of the TSA +// verifies, the messageImprint is the hash of subject and the certificate of +// the TSA is valid at genTime. It returns false for the verdict S3. +func (t *Token) Check(subject []byte) bool { + s := t.data.Signers[0] + if s.Check(t.data.EContent) != Valid { + return false + } + newHash, _ := algID{OID: t.ImprintAlg}.hashOf() + h := newHash() + h.Write(subject) + return bytes.Equal(h.Sum(nil), t.Imprint) && t.TSA.ValidAt(t.GenTime) +} diff --git a/internal/der/der.go b/internal/der/der.go new file mode 100644 index 0000000..75a8981 --- /dev/null +++ b/internal/der/der.go @@ -0,0 +1,191 @@ +// Package der checks that bytes are one element in the Distinguished Encoding +// Rules of X.690, as spec v0.11 §29.10 asks of a CMS signature before it +// looks inside it: definite and minimal lengths, no high tag numbers, no +// constructed form of a type that DER only has primitive, canonical BOOLEAN, +// INTEGER, NULL, OBJECT IDENTIFIER and BIT STRING, and no bytes after the +// element. Go's encoding/asn1 accepts some of what DER forbids, and a +// signature that two implementations read the same must not depend on it. +// +// The order of the elements of a SET OF cannot be checked without a schema: +// SetOfSorted does it for the callers that know theirs. +package der + +import ( + "bytes" + "errors" + "fmt" +) + +const maxDepth = 32 + +// Check returns nil when b is exactly one DER element. +func Check(b []byte) error { + n, err := check(b, 0) + if err != nil { + return err + } + if n != len(b) { + return fmt.Errorf("der: %d bytes after the element", len(b)-n) + } + return nil +} + +// Split returns the content of the DER element b, which must be constructed, +// as the encodings of its children, and the identifier octet of b. It +// assumes Check passed. +func Split(b []byte) (id byte, children [][]byte, err error) { + if len(b) == 0 || b[0]&0x20 == 0 { + return 0, nil, errors.New("der: not a constructed element") + } + hl, cl, err := header(b) + if err != nil { + return 0, nil, err + } + rest := b[hl : hl+cl] + for len(rest) > 0 { + h, c, err := header(rest) + if err != nil { + return 0, nil, err + } + children = append(children, rest[:h+c]) + rest = rest[h+c:] + } + return b[0], children, nil +} + +// Content returns the content octets of the DER element b. +func Content(b []byte) ([]byte, error) { + hl, cl, err := header(b) + if err != nil { + return nil, err + } + return b[hl : hl+cl], nil +} + +// SetOfSorted reports whether the encodings are in ascending order of their +// bytes, as DER requires of the elements of a SET OF (X.690 11.6), and +// without repetitions: a SET OF of this profile has none. +func SetOfSorted(elems [][]byte) bool { + for i := 1; i < len(elems); i++ { + if bytes.Compare(elems[i-1], elems[i]) >= 0 { + return false + } + } + return true +} + +// header returns the length of the identifier and length octets of the +// element at the start of b, and the length of its content, which must fit +// in b. +func header(b []byte) (headerLen, contentLen int, err error) { + if len(b) < 2 { + return 0, 0, errors.New("der: truncated element") + } + if b[0]&0x1f == 0x1f { + return 0, 0, errors.New("der: a tag number of 31 or more") + } + l := b[1] + switch { + case l < 0x80: + headerLen, contentLen = 2, int(l) + case l == 0x80: + return 0, 0, errors.New("der: an indefinite length") + case l == 0xff: + return 0, 0, errors.New("der: a length of 0xff") + default: + n := int(l & 0x7f) + if n > 4 || len(b) < 2+n { + return 0, 0, errors.New("der: a length that does not fit") + } + if b[2] == 0 { + return 0, 0, errors.New("der: a length with a leading zero") + } + v := 0 + for _, c := range b[2 : 2+n] { + v = v<<8 | int(c) + } + if v < 0x80 { + return 0, 0, errors.New("der: a long form for a length under 128") + } + headerLen, contentLen = 2+n, v + } + if contentLen < 0 || contentLen > len(b)-headerLen { + return 0, 0, errors.New("der: an element longer than its container") + } + return headerLen, contentLen, nil +} + +// check validates the element at the start of b and returns its length. +func check(b []byte, depth int) (int, error) { + if depth > maxDepth { + return 0, errors.New("der: nested too deep") + } + hl, cl, err := header(b) + if err != nil { + return 0, err + } + content := b[hl : hl+cl] + id := b[0] + class, constructed, tag := id>>6, id&0x20 != 0, id&0x1f + if constructed { + if class == 0 && tag != 16 && tag != 17 { + return 0, fmt.Errorf("der: constructed form of the universal type %d", tag) + } + for len(content) > 0 { + n, err := check(content, depth+1) + if err != nil { + return 0, err + } + content = content[n:] + } + return hl + cl, nil + } + if class == 0 { + if err := checkPrimitive(tag, content); err != nil { + return 0, err + } + } + return hl + cl, nil +} + +func checkPrimitive(tag byte, c []byte) error { + switch tag { + case 1: // BOOLEAN + if len(c) != 1 || (c[0] != 0 && c[0] != 0xff) { + return errors.New("der: a BOOLEAN that is not 00 or FF") + } + case 2, 10: // INTEGER, ENUMERATED + if len(c) == 0 { + return errors.New("der: an empty INTEGER") + } + if len(c) > 1 && (c[0] == 0 && c[1]&0x80 == 0 || c[0] == 0xff && c[1]&0x80 != 0) { + return errors.New("der: an INTEGER that is not minimal") + } + case 3: // BIT STRING + if len(c) == 0 || c[0] > 7 || len(c) == 1 && c[0] != 0 { + return errors.New("der: a malformed BIT STRING") + } + if len(c) > 1 && c[0] != 0 && c[len(c)-1]&(1<