diff --git a/internal/cms/cert.go b/internal/cms/cert.go index 98b3e65..19dbda8 100644 --- a/internal/cms/cert.go +++ b/internal/cms/cert.go @@ -3,136 +3,284 @@ package cms import ( "bytes" "crypto/sha256" - "crypto/x509/pkix" - "encoding/asn1" "errors" "fmt" - "math/big" - "strings" "time" + "unicode/utf8" + + "g.activething.com/go/DateKeys/internal/der" ) -// 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. +// Cert is an X.509 certificate read with the profile of spec §29.10 for what +// DateKeys uses of it: who it names, when it is valid and its key. DateKeys +// does not check who issued it. It is read with the DER of this module, not +// with crypto/x509 or encoding/asn1, so that two implementations read it the +// same: a certificate with a key of a curve that Go does not have, such as +// brainpool, 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 + // Serial is the content of serialNumber; RawIssuer and RawSubject are the + // DER of the two names, and SKI the keyIdentifier of the extension + // subjectKeyIdentifier, nil without it. + Serial []byte RawIssuer, RawSubject []byte SKI []byte NotBefore, NotAfter time.Time // SPKI is the DER of the SubjectPublicKeyInfo. - SPKI []byte - Subject pkix.RDNSequence - Issuer pkix.RDNSequence + SPKI []byte + subject, issuer []attribute } -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"` -} +// attribute is an AttributeTypeAndValue of a name: the content of its object +// identifier and the DER of its value. +type attribute struct{ oid, value []byte } -type validity struct{ NotBefore, NotAfter time.Time } +var ( + oidSKI = oid("2.5.29.14") + oidCommonName = oid("2.5.4.3") + oidSurname = oid("2.5.4.4") + oidOrgName = oid("2.5.4.10") + oidGivenName = oid("2.5.4.42") +) -type certificate struct { - TBS tbsCertificate - SigAlg asn1.RawValue - Sig asn1.BitString +// ParseCert reads the DER of a certificate, which its caller checked, with +// the profile of spec §29.10: +// +// - a SEQUENCE of tbsCertificate, signatureAlgorithm and a BIT STRING; +// - tbsCertificate: version [0], which is 2 (version 3), serialNumber, +// signature, issuer, validity, subject and subjectPublicKeyInfo, then +// [1], [2] and [3] when present, in that order and nothing after; +// - a name: a SEQUENCE of non-empty SETs of AttributeTypeAndValue, each an +// object identifier and one value, in any order; +// - validity: two times, UTCTime or GeneralizedTime without a fraction; +// - extensions: [3] holds a SEQUENCE of one or more Extension, each an +// object identifier, an optional BOOLEAN and an OCTET STRING, none twice; +// subjectKeyIdentifier holds a non-empty OCTET STRING. +// +// What the profile does not read decides nothing: the signature of the +// certificate, its algorithm and the other extensions. +func ParseCert(raw []byte) (*Cert, error) { + bad := func(what string) (*Cert, error) { return nil, fmt.Errorf("certificate: %s", what) } + id, top, err := der.Split(raw) + if err != nil || id != 0x30 || len(top) != 3 || top[0][0] != 0x30 || top[1][0] != 0x30 || top[2][0] != 0x03 { + return bad("not a SEQUENCE of tbsCertificate, signatureAlgorithm and signature") + } + _, f, err := der.Split(top[0]) + if err != nil || len(f) < 7 || f[0][0] != 0xa0 { + return bad("a tbsCertificate without version, or with fields missing") + } + if _, v, err := der.Split(f[0]); err != nil || len(v) != 1 || !bytes.Equal(v[0], []byte{0x02, 0x01, 0x02}) { + return bad("the version is not 3") + } + f = f[1:] + if f[0][0] != 0x02 || f[1][0] != 0x30 || f[2][0] != 0x30 || f[3][0] != 0x30 || f[4][0] != 0x30 || f[5][0] != 0x30 { + return bad("the fields of tbsCertificate") + } + c := &Cert{Raw: raw, Hash: sha256.Sum256(raw), RawIssuer: f[2], RawSubject: f[4], SPKI: f[5]} + if c.Serial, err = der.Content(f[0]); err != nil { + return bad("serialNumber") + } + if c.issuer, err = parseName(f[2]); err != nil { + return bad("issuer: " + err.Error()) + } + if c.subject, err = parseName(f[4]); err != nil { + return bad("subject: " + err.Error()) + } + if _, v, err := der.Split(f[3]); err != nil || len(v) != 2 { + return bad("validity") + } else if c.NotBefore, err = certTime(v[0]); err != nil { + return bad("notBefore: " + err.Error()) + } else if c.NotAfter, err = certTime(v[1]); err != nil { + return bad("notAfter: " + err.Error()) + } + rest := f[6:] + for _, tag := range []byte{0x81, 0x82} { // issuerUniqueID, subjectUniqueID + if len(rest) > 0 && rest[0][0] == tag { + rest = rest[1:] + } + } + if len(rest) > 0 && rest[0][0] == 0xa3 { + if c.SKI, err = parseExtensions(rest[0]); err != nil { + return bad(err.Error()) + } + rest = rest[1:] + } + if len(rest) != 0 { + return bad("a field out of its place, or after the last") + } + return c, nil } -var oidSKI = asn1.ObjectIdentifier{2, 5, 29, 14} +// certTime reads a time of validity: a UTCTime or a GeneralizedTime in DER, +// without a fraction of seconds (RFC 5280 4.1.2.5). +func certTime(b []byte) (time.Time, error) { + t, fraction, err := der.ParseTime(b) + if err == nil && fraction { + err = errors.New("a GeneralizedTime with a fraction of seconds") + } + return t, err +} -// ParseCert reads the DER of a certificate. -func ParseCert(raw []byte) (*Cert, error) { - var c certificate - rest, err := asn1.Unmarshal(raw, &c) +// parseName reads a Name: a SEQUENCE of RelativeDistinguishedName, each a +// non-empty SET of AttributeTypeAndValue. The order of a SET is not checked: +// a name only gives the text that a reader shows. +func parseName(b []byte) ([]attribute, error) { + _, rdns, err := der.Split(b) if err != nil { - return nil, fmt.Errorf("certificate: %w", err) + return nil, errors.New("not a SEQUENCE") } - if len(rest) != 0 { - return nil, errors.New("certificate: bytes after it") + var out []attribute + for _, rdn := range rdns { + id, atvs, err := der.Split(rdn) + if err != nil || id != 0x31 || len(atvs) == 0 { + return nil, errors.New("a RelativeDistinguishedName that is not a non-empty SET") + } + for _, atv := range atvs { + id, p, err := der.Split(atv) + if err != nil || id != 0x30 || len(p) != 2 || p[0][0] != 0x06 { + return nil, errors.New("an AttributeTypeAndValue that is not an object identifier and a value") + } + o, _ := der.Content(p[0]) + out = append(out, attribute{o, p[1]}) + } } - 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") + return out, nil +} + +// parseExtensions reads [3] of tbsCertificate and returns the keyIdentifier +// of subjectKeyIdentifier, nil without it. +func parseExtensions(b []byte) ([]byte, error) { + _, in, err := der.Split(b) + if err != nil || len(in) != 1 || in[0][0] != 0x30 { + return nil, errors.New("extensions") } - 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, + _, exts, err := der.Split(in[0]) + if err != nil || len(exts) == 0 { + return nil, errors.New("extensions without an Extension") } - if _, err := asn1.Unmarshal(out.RawSubject, &out.Subject); err != nil { - return nil, fmt.Errorf("certificate: subject: %w", err) + seen := map[string]bool{} + var ski []byte + for _, e := range exts { + id, p, err := der.Split(e) + if err != nil || id != 0x30 || len(p) < 2 || len(p) > 3 || p[0][0] != 0x06 || p[len(p)-1][0] != 0x04 || len(p) == 3 && p[1][0] != 0x01 { + return nil, errors.New("an Extension that is not an object identifier, an optional BOOLEAN and an OCTET STRING") + } + o, _ := der.Content(p[0]) + if seen[string(o)] { + return nil, errors.New("an extension twice") + } + seen[string(o)] = true + if bytes.Equal(o, oidSKI) { + v, _ := der.Content(p[len(p)-1]) + if der.Check(v) != nil || v[0] != 0x04 { + return nil, errors.New("a subjectKeyIdentifier that is not an OCTET STRING") + } + if ski, _ = der.Content(v); len(ski) == 0 { + return nil, errors.New("an empty subjectKeyIdentifier") + } + } } - if _, err := asn1.Unmarshal(out.RawIssuer, &out.Issuer); err != nil { - return nil, fmt.Errorf("certificate: issuer: %w", err) + return ski, nil +} + +// text returns the text of the value of an attribute of a name, and false +// when it is not of a string type that the profile reads, or when its bytes +// break its type (spec §29.10): UTF8String in valid UTF-8, PrintableString +// in its alphabet, IA5String and TeletexString in ASCII, and BMPString in +// UTF-16BE without surrogates. Nothing is removed from the text: a byte order +// mark or a terminator stays, and the rules of §29.6 decide. +func text(v []byte) (string, bool) { + c, err := der.Content(v) + if err != nil { + return "", false } - 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") + switch v[0] { + case 0x0c: // UTF8String + return string(c), utf8.Valid(c) + case 0x13: // PrintableString + for _, b := range c { + if !printable(b) { + return "", false } - out.SKI = ski } + case 0x14, 0x16: // TeletexString, IA5String + for _, b := range c { + if b > 0x7f { + return "", false + } + } + case 0x1e: // BMPString + if len(c)%2 != 0 { + return "", false + } + r := make([]rune, 0, len(c)/2) + for i := 0; i < len(c); i += 2 { + u := rune(c[i])<<8 | rune(c[i+1]) + if u >= 0xd800 && u <= 0xdfff { + return "", false + } + r = append(r, u) + } + return string(r), true + default: + return "", false } - return out, nil + return string(c), true } -var ( - oidCommonName = asn1.ObjectIdentifier{2, 5, 4, 3} - oidSurname = asn1.ObjectIdentifier{2, 5, 4, 4} - oidGivenName = asn1.ObjectIdentifier{2, 5, 4, 42} -) +// printable reports whether b is in the alphabet of PrintableString (X.680). +func printable(b byte) bool { + return b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' || b >= '0' && b <= '9' || bytes.IndexByte([]byte(" '()+,-./:=?"), b) >= 0 +} -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 - } +// nameText returns the text of the only attribute of type o among attrs, and +// false when there is none, when there are several, or when its value is not +// text. +func nameText(attrs []attribute, o []byte) (string, bool) { + var v []byte + n := 0 + for _, a := range attrs { + if bytes.Equal(a.oid, o) { + v, n = a.value, n+1 } } - return "" + if n != 1 { + return "", false + } + return text(v) } -// 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 +// Holder returns the name of the subject as spec §29.7 takes it: its +// givenName and its surname, when it has one of each, and its commonName +// otherwise; "" when it has neither. The commonName comes second because it +// may carry the national identifier of the person, as the one of the FNMT +// does. The caller applies the rules of text of §29.6, 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 != "" { + given, ok1 := nameText(c.subject, oidGivenName) + sur, ok2 := nameText(c.subject, oidSurname) + if ok1 && ok2 && given != "" && sur != "" { return given + " " + sur } + if cn, ok := nameText(c.subject, oidCommonName); ok { + return cn + } 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. +// IssuerName returns the issuer that the certificate names: its commonName, +// or its organizationName, or "" when it has neither (spec §29.7). func (c *Cert) IssuerName() string { - if n := rdnString(c.Issuer, oidCommonName); n != "" { - return n + if cn, ok := nameText(c.issuer, oidCommonName); ok { + return cn + } + if o, ok := nameText(c.issuer, oidOrgName); ok { + return o } - return strings.TrimSpace(c.Issuer.String()) + return "" } // ValidAt reports whether t is in the validity period of the certificate. @@ -140,6 +288,6 @@ 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 +func (c *Cert) hasSID(issuer, serial []byte) bool { + return bytes.Equal(c.RawIssuer, issuer) && bytes.Equal(c.Serial, serial) } diff --git a/internal/cms/cms.go b/internal/cms/cms.go index 1042a07..ca09636 100644 --- a/internal/cms/cms.go +++ b/internal/cms/cms.go @@ -1,11 +1,13 @@ // 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. +// tokens that spec §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). +// that (spec §29.10). Object identifiers are compared by the bytes of their +// DER, so that an arc of any size is only an identifier that the table does +// not have. package cms import ( @@ -13,11 +15,11 @@ import ( "crypto/sha1" "crypto/sha256" "crypto/sha512" - "encoding/asn1" "errors" "fmt" "hash" - "math/big" + "strconv" + "strings" "g.activething.com/go/DateKeys/internal/der" ) @@ -34,26 +36,66 @@ func formErr(format string, args ...any) error { return fmt.Errorf("%w: %s", ErrForm, fmt.Sprintf(format, args...)) } +// oid returns the content of the DER of the object identifier s, written +// with dots: the bytes that this package compares. +func oid(s string) []byte { + parts := strings.Split(s, ".") + arcs := make([]uint64, len(parts)) + for i, p := range parts { + n, err := strconv.ParseUint(p, 10, 64) + if err != nil || len(parts) < 2 { + panic("cms: a bad object identifier " + s) + } + arcs[i] = n + } + out := base128(nil, arcs[0]*40+arcs[1]) + for _, a := range arcs[2:] { + out = base128(out, a) + } + return out +} + +func base128(out []byte, v uint64) []byte { + var tmp [10]byte + i := len(tmp) - 1 + tmp[i] = byte(v & 0x7f) + for v >>= 7; v > 0; v >>= 7 { + i-- + tmp[i] = byte(v&0x7f) | 0x80 + } + return append(out, tmp[i:]...) +} + +// oidOf returns the content of the object identifier element b. +func oidOf(b []byte) ([]byte, bool) { + if len(b) == 0 || b[0] != 0x06 { + return nil, false + } + c, err := der.Content(b) + return c, err == nil +} + // 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} + oidData = oid("1.2.840.113549.1.7.1") + oidSignedData = oid("1.2.840.113549.1.7.2") + oidContentType = oid("1.2.840.113549.1.9.3") + oidMessageDig = oid("1.2.840.113549.1.9.4") + oidSigCertV1 = oid("1.2.840.113549.1.9.16.2.12") + oidSigCertV2 = oid("1.2.840.113549.1.9.16.2.47") + oidSigTimeStamp = oid("1.2.840.113549.1.9.16.2.14") + oidTSTInfo = oid("1.2.840.113549.1.9.16.1.4") + oidRIOCSP = oid("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} + oidSHA256 = oid("2.16.840.1.101.3.4.2.1") + oidSHA384 = oid("2.16.840.1.101.3.4.2.2") + oidSHA512 = oid("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 are the certificates that meet the profile of §29.10, each once: + // another one decides nothing, unless a SignerInfo names it. Certs []*Cert // OCSP are the OCSP responses of crls. OCSP [][]byte @@ -79,18 +121,19 @@ type SignerInfo struct { } type algID struct { - OID asn1.ObjectIdentifier + OID []byte // the content of the object identifier 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 { + if err != nil || id != 0x30 || len(kids) < 1 || len(kids) > 2 { 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) + var ok bool + if a.OID, ok = oidOf(kids[0]); !ok { + return algID{}, formErr("an AlgorithmIdentifier without an object identifier") } if len(kids) == 2 { a.Params = kids[1] @@ -105,11 +148,11 @@ func (a algID) hashOf() (func() hash.Hash, bool) { return nil, false } switch { - case a.OID.Equal(oidSHA256): + case bytes.Equal(a.OID, oidSHA256): return sha256.New, true - case a.OID.Equal(oidSHA384): + case bytes.Equal(a.OID, oidSHA384): return sha512.New384, true - case a.OID.Equal(oidSHA512): + case bytes.Equal(a.OID, oidSHA512): return sha512.New, true } return nil, false @@ -126,11 +169,10 @@ func parse(b []byte, token bool) (*SignedData, error) { return nil, formErr("%v", err) } id, ci, err := der.Split(b) - if err != nil || id != 0x30 || len(ci) != 2 || ci[0][0] != 0x06 || ci[1][0] != 0xa0 { + if err != nil || id != 0x30 || len(ci) != 2 || 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) { + if ct, ok := oidOf(ci[0]); !ok || !bytes.Equal(ct, oidSignedData) { return nil, formErr("the content type is not id-signedData") } _, inner, err := der.Split(ci[1]) @@ -163,7 +205,7 @@ func parse(b []byte, token bool) (*SignedData, error) { rest = rest[1:] } if len(rest) > 0 && rest[0][0] == 0xa1 { - if err := parseCRLs(rest[0], out); err != nil { + if err := parseCRLs(rest[0], token, out); err != nil { return nil, err } rest = rest[1:] @@ -197,20 +239,20 @@ func parse(b []byte, token bool) (*SignedData, error) { // 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 { + if err != nil || len(kids) < 1 || len(kids) > 2 { return formErr("encapContentInfo") } - var oid asn1.ObjectIdentifier - if _, err := asn1.Unmarshal(kids[0], &oid); err != nil { + ct, ok := oidOf(kids[0]) + if !ok { return formErr("encapContentInfo") } if !token { - if !oid.Equal(oidData) || len(kids) != 1 { + if !bytes.Equal(ct, 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 { + if !bytes.Equal(ct, oidTSTInfo) || len(kids) != 2 || kids[1][0] != 0xa0 { return formErr("a token holds a TSTInfo") } _, e, err := der.Split(kids[1]) @@ -223,42 +265,51 @@ func parseEncap(b []byte, token bool, out *SignedData) error { return nil } +// parseCerts reads certificates. A certificate that breaks the profile of +// §29.10 decides nothing, as one that no SignerInfo names: an intermediate of +// another form is not a reason to refuse a signature, and the sid of a signer +// whose certificate breaks it names none. Two copies of a certificate are one. 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 { + for i, k := range kids { if k[0] >= 0xa0 && k[0] <= 0xa3 { // another choice of CertificateChoices: it decides nothing continue } if k[0] != 0x30 { return formErr("a CertificateChoice that is neither a certificate nor one of the other four choices") } - c, err := ParseCert(k) - if err != nil { - return formErr("%v", err) + if i > 0 && bytes.Equal(kids[i-1], k) { + continue + } + if c, err := ParseCert(k); err == nil { + out.Certs = append(out.Certs, c) } - out.Certs = append(out.Certs, c) } return nil } -func parseCRLs(b []byte, out *SignedData) error { +// parseCRLs reads crls: in a signature, only OCSP responses (spec §29.10 rule +// 3); in a token, whatever it holds decides nothing (§29.11). +func parseCRLs(b []byte, token bool, 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") } + if token { + return nil + } 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 { + if err != nil || len(f) != 2 { return formErr("an OtherRevocationInfoFormat") } - var oid asn1.ObjectIdentifier - if _, err := asn1.Unmarshal(f[0], &oid); err != nil || !oid.Equal(oidRIOCSP) { + if o, ok := oidOf(f[0]); !ok || !bytes.Equal(o, oidRIOCSP) { return formErr("crls holds only OCSP responses") } out.OCSP = append(out.OCSP, f[1]) @@ -303,7 +354,9 @@ func parseSignerInfo(b []byte, sd *SignedData, token bool) (*SignerInfo, error) return s, nil } -// findSigner returns the one certificate that the sid names. +// findSigner returns the one certificate that the sid names, comparing the +// DER of the issuer and the content of the serial number, or the +// keyIdentifier. func findSigner(sid []byte, certs []*Cert) (*Cert, error) { var found *Cert n := 0 @@ -313,10 +366,7 @@ func findSigner(sid []byte, certs []*Cert) (*Cert, error) { 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") - } + serial, _ := der.Content(p[1]) for _, c := range certs { if c.hasSID(p[0], serial) { found, n = c, n+1 @@ -336,20 +386,21 @@ func findSigner(sid []byte, certs []*Cert) (*Cert, error) { return nil, formErr("a SignerIdentifier") } if n != 1 { - return nil, formErr("a sid that names %d certificates, not one", n) + return nil, formErr("a sid that names %d certificates of the profile, not one", n) } return found, nil } // attrSet holds the attributes of a SET OF Attribute: the values of each type -// and the number of attributes of each type, which is not the number of values. +// and the number of attributes of each type, which is not the number of +// values. Both are kept by the bytes of the object identifier. type attrSet struct { vals map[string][][]byte count map[string]int } -func (a attrSet) get(oid asn1.ObjectIdentifier) ([][]byte, int) { - return a.vals[oid.String()], a.count[oid.String()] +func (a attrSet) get(o []byte) ([][]byte, int) { + return a.vals[string(o)], a.count[string(o)] } // attrs reads the SET OF Attribute b. An attribute needs at least one value @@ -363,26 +414,26 @@ func attrs(b []byte) (attrSet, error) { out := attrSet{vals: map[string][][]byte{}, count: map[string]int{}} 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 { + if err != nil || len(p) != 2 || a[0] != 0x30 || p[1][0] != 0x31 { return attrSet{}, formErr("an Attribute") } - var oid asn1.ObjectIdentifier - if _, err := asn1.Unmarshal(p[0], &oid); err != nil { + o, ok := oidOf(p[0]) + if !ok { return attrSet{}, formErr("an Attribute") } _, vals, err := der.Split(p[1]) if err != nil || len(vals) == 0 || !der.SetOfSorted(vals) { return attrSet{}, formErr("the values of an attribute are not a non-empty SET OF in DER order") } - out.vals[oid.String()] = append(out.vals[oid.String()], vals...) - out.count[oid.String()]++ + out.vals[string(o)] = append(out.vals[string(o)], vals...) + out.count[string(o)]++ } return out, nil } // one returns the only value of the only attribute of the type. -func one(m attrSet, oid asn1.ObjectIdentifier, name string) ([]byte, error) { - v, n := m.get(oid) +func one(m attrSet, o []byte, name string) ([]byte, error) { + v, n := m.get(o) if n != 1 || len(v) != 1 { return nil, formErr("%s: %d attributes with %d values, not one with one", name, n, len(v)) } @@ -401,13 +452,12 @@ func parseSignedAttrs(s *SignerInfo, token bool) error { if err != nil { return err } - want := oidData + want, name := oidData, "id-data" if token { - want = oidTSTInfo + want, name = oidTSTInfo, "id-ct-TSTInfo" } - var got asn1.ObjectIdentifier - if _, err := asn1.Unmarshal(ct, &got); err != nil || !got.Equal(want) { - return formErr("content-type is not %v", want) + if got, ok := oidOf(ct); !ok || !bytes.Equal(got, want) { + return formErr("content-type is not %s", name) } md, err := one(m, oidMessageDig, "message-digest") if err != nil { diff --git a/internal/cms/verify.go b/internal/cms/verify.go index 732cb11..94f8753 100644 --- a/internal/cms/verify.go +++ b/internal/cms/verify.go @@ -6,9 +6,8 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" - "crypto/x509" - "encoding/asn1" "errors" + "math/big" "time" "g.activething.com/go/DateKeys/internal/der" @@ -29,15 +28,19 @@ const ( ) 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} + oidRSAEncryption = oid("1.2.840.113549.1.1.1") + oidSHA256RSA = oid("1.2.840.113549.1.1.11") + oidSHA384RSA = oid("1.2.840.113549.1.1.12") + oidSHA512RSA = oid("1.2.840.113549.1.1.13") + oidPSS = oid("1.2.840.113549.1.1.10") + oidMGF1 = oid("1.2.840.113549.1.1.8") + oidECDSA256 = oid("1.2.840.10045.4.3.2") + oidECDSA384 = oid("1.2.840.10045.4.3.3") + oidECDSA512 = oid("1.2.840.10045.4.3.4") + oidECPublicKey = oid("1.2.840.10045.2.1") + oidP256 = oid("1.2.840.10045.3.1.7") + oidP384 = oid("1.3.132.0.34") + oidP521 = oid("1.3.132.0.35") ) type scheme int @@ -65,24 +68,24 @@ func (s *SignerInfo) params() (crypto.Hash, scheme, bool) { default: h = crypto.SHA512 } - oid, params := s.SigAlg.OID, s.SigAlg.Params + o, params := s.SigAlg.OID, s.SigAlg.Params nullOrAbsent := params == nil || bytes.Equal(params, []byte{5, 0}) switch { - case oid.Equal(oidRSAEncryption): + case bytes.Equal(o, oidRSAEncryption): return h, schemePKCS1, nullOrAbsent - case oid.Equal(oidSHA256RSA): + case bytes.Equal(o, oidSHA256RSA): return h, schemePKCS1, nullOrAbsent && h == crypto.SHA256 - case oid.Equal(oidSHA384RSA): + case bytes.Equal(o, oidSHA384RSA): return h, schemePKCS1, nullOrAbsent && h == crypto.SHA384 - case oid.Equal(oidSHA512RSA): + case bytes.Equal(o, oidSHA512RSA): return h, schemePKCS1, nullOrAbsent && h == crypto.SHA512 - case oid.Equal(oidECDSA256): + case bytes.Equal(o, oidECDSA256): return h, schemeECDSA, params == nil && h == crypto.SHA256 - case oid.Equal(oidECDSA384): + case bytes.Equal(o, oidECDSA384): return h, schemeECDSA, params == nil && h == crypto.SHA384 - case oid.Equal(oidECDSA512): + case bytes.Equal(o, oidECDSA512): return h, schemeECDSA, params == nil && h == crypto.SHA512 - case oid.Equal(oidPSS): + case bytes.Equal(o, oidPSS): return h, schemePSS, pssParamsOK(params, newHash().Size(), s.DigestAlg) } return 0, 0, false @@ -109,18 +112,17 @@ func pssParamsOK(params []byte, hashLen int, digest algID) bool { 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})) + hashOK = err == nil && bytes.Equal(a.OID, 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 { + if err != nil || !bytes.Equal(a.OID, 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})) + mgfOK = err == nil && bytes.Equal(inner.OID, 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 + n, ok := smallInt(in[0]) + saltOK = ok && n == hashLen default: // [3] trailerField is 1, its DEFAULT: DER does not write it return false } @@ -130,73 +132,124 @@ func pssParamsOK(params []byte, hashLen int, digest algID) bool { 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. +// smallInt reads an INTEGER element of at most 4 bytes that is not negative. +func smallInt(b []byte) (int, bool) { + if len(b) == 0 || b[0] != 0x02 { + return 0, false + } + c, err := der.Content(b) + if err != nil || len(c) == 0 || len(c) > 4 || c[0]&0x80 != 0 { + return 0, false + } + n := 0 + for _, x := range c { + n = n<<8 | int(x) + } + return n, true +} + +// publicKey returns the key of the certificate when the table has it (spec +// §29.10): a SubjectPublicKeyInfo of rsaEncryption with NULL parameters and +// an RSAPublicKey of exactly a modulus and an exponent, the modulus odd and +// of 2048 to 4096 bits and the exponent odd from 3 to 2^31 - 1; or of +// id-ecPublicKey with the named curve P-256, P-384 or P-521 and the +// uncompressed form of a point of it. Anything else is not usable. func (c *Cert) publicKey() (any, scheme, bool) { - k, err := x509.ParsePKIXPublicKey(c.SPKI) + _, f, err := der.Split(c.SPKI) + if err != nil || len(f) != 2 || f[0][0] != 0x30 || f[1][0] != 0x03 { + return nil, 0, false + } + alg, err := parseAlgID(f[0]) 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 { + bits, err := der.Content(f[1]) + if err != nil || len(bits) < 2 || bits[0] != 0 { + return nil, 0, false + } + key := bits[1:] + switch { + case bytes.Equal(alg.OID, oidRSAEncryption) && bytes.Equal(alg.Params, []byte{5, 0}): + if der.Check(key) != nil { 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 + id, ne, err := der.Split(key) + if err != nil || id != 0x30 || len(ne) != 2 || ne[0][0] != 0x02 || ne[1][0] != 0x02 { + return nil, 0, false } + nb, _ := der.Content(ne[0]) + eb, _ := der.Content(ne[1]) + if nb[0]&0x80 != 0 || eb[0]&0x80 != 0 || len(eb) > 4 { + return nil, 0, false + } + n := new(big.Int).SetBytes(nb) + e := new(big.Int).SetBytes(eb).Int64() + if b := n.BitLen(); b < 2048 || b > 4096 || n.Bit(0) == 0 || e < 3 || e%2 == 0 || e > 1<<31-1 { + return nil, 0, false + } + return &rsa.PublicKey{N: n, E: int(e)}, schemePKCS1, true + case bytes.Equal(alg.OID, oidECPublicKey): + curveOID, ok := oidOf(alg.Params) + if !ok { + return nil, 0, false + } + var curve elliptic.Curve + switch { + case bytes.Equal(curveOID, oidP256): + curve = elliptic.P256() + case bytes.Equal(curveOID, oidP384): + curve = elliptic.P384() + case bytes.Equal(curveOID, oidP521): + curve = elliptic.P521() + default: + return nil, 0, false + } + k, err := ecdsa.ParseUncompressedPublicKey(curve, key) + if err != nil { + return nil, 0, false + } + 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() +// Check checks the signature of the SignerInfo over message, the bytes that +// the signature is detached from (spec §29.10), in the order of the spec: not +// verifiable for an algorithm, a key or a curve outside the table; invalid +// when the message-digest is not the hash of message, or the signature of the +// signedAttrs does not verify with the key of the certificate, which is the +// case of a key of a scheme other than the one of the algorithm. +func (s *SignerInfo) Check(message []byte) Result { + h, sch, ok := s.params() if !ok { - return false + return NotVerifiable } - _, ksch, ok := s.Cert.publicKey() + key, 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 } + if sch != ksch && !(sch == schemePSS && ksch == schemePKCS1) { + 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 + var valid 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 + valid = 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 + valid = rsa.VerifyPKCS1v15(k, h, digest, s.Signature) == nil } case *ecdsa.PublicKey: - ok = ecdsa.VerifyASN1(k, digest, s.Signature) + valid = ecdsa.VerifyASN1(k, digest, s.Signature) } - if !ok { + if !valid { return Invalid } return Valid @@ -216,7 +269,7 @@ type Token struct { GenTime time.Time Accuracy time.Duration // ImprintAlg is the hash of the messageImprint, and Imprint the hash. - ImprintAlg asn1.ObjectIdentifier + ImprintAlg algID Imprint []byte // TSA is the certificate of the time-stamping authority. TSA *Cert @@ -224,9 +277,9 @@ type Token struct { data *SignedData } -// maxAccuracy bounds the seconds of accuracy: far above any real one, and far -// below what would overflow a Duration. -const maxAccuracy = 1 << 31 +// maxAccuracy bounds the seconds of accuracy (spec §29.11): far above any +// real one, and far below what would overflow a Duration. +const maxAccuracy = 1<<31 - 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 @@ -237,23 +290,23 @@ func ParseToken(b []byte) (*Token, error) { return nil, err } // The TSTInfo is an OCTET STRING inside the token, so the check of the - // token did not reach it, and encoding/asn1 would let through what DER - // forbids: it is read here, field by field. + // token did not reach it: it is read here, field by field. t, imprintAlg, hash, err := parseTSTInfo(sd.EContent) if err != nil { return nil, err } t.TSA, t.data = sd.Signers[0].Cert, sd - ia, err := parseAlgID(imprintAlg) - if err != nil { + if t.ImprintAlg, err = parseAlgID(imprintAlg); err != nil { return nil, err } - t.ImprintAlg, t.Imprint = ia.OID, hash - newHash, ok := ia.hashOf() - if ok && len(hash) != newHash().Size() { - return nil, formErr("a messageImprint of the wrong length") + t.Imprint = hash + if _, ok := t.ImprintAlg.hashOf(); !ok { + return nil, ErrAlgorithm + } + if _, _, ok := sd.Signers[0].params(); !ok { + return nil, ErrAlgorithm } - if !ok || !sd.Signers[0].algorithmsOK() { + if _, _, ok := t.TSA.publicKey(); !ok { return nil, ErrAlgorithm } return t, nil @@ -273,11 +326,7 @@ func parseTSTInfo(b []byte) (*Token, []byte, []byte, error) { if err != nil || id != 0x30 || len(f) < 5 { return bad("not a SEQUENCE of at least five fields") } - var version int - if f[0][0] != 0x02 { - return bad("the version") - } - if _, err := asn1.Unmarshal(f[0], &version); err != nil || version != 1 { + if v, ok := smallInt(f[0]); !ok || v != 1 { return bad("the version is not 1") } if f[1][0] != 0x06 || f[3][0] != 0x02 || f[4][0] != 0x18 { @@ -291,9 +340,9 @@ func parseTSTInfo(b []byte) (*Token, []byte, []byte, error) { if err != nil { return bad("the messageImprint") } - gen, err := parseGenTime(f[4]) + gen, _, err := der.ParseTime(f[4]) if err != nil { - return bad(err.Error()) + return bad("genTime: " + err.Error()) } t := &Token{GenTime: gen} rest := f[5:] @@ -324,47 +373,10 @@ func parseTSTInfo(b []byte) (*Token, []byte, []byte, error) { return t, mi[0], hash, nil } -// parseGenTime reads a GeneralizedTime as RFC 3161 and DER write it: -// YYYYMMDDHHMMSS, a fraction without a trailing zero, and Z. -func parseGenTime(b []byte) (time.Time, error) { - c, err := der.Content(b) - if err != nil { - return time.Time{}, errors.New("genTime") - } - s := string(c) - if len(s) < 15 || s[len(s)-1] != 'Z' { - return time.Time{}, errors.New("genTime is not in UTC with the letter Z") - } - body := s[:len(s)-1] - t, err := time.Parse("20060102150405", body[:14]) - if err != nil { - return time.Time{}, errors.New("genTime does not parse") - } - if frac := body[14:]; frac != "" { - if len(frac) < 2 || frac[0] != '.' || frac[len(frac)-1] == '0' { - return time.Time{}, errors.New("genTime has a fraction that DER does not write") - } - var nanos int - digits := frac[1:] - for i := 0; i < len(digits); i++ { - if digits[i] < '0' || digits[i] > '9' { - return time.Time{}, errors.New("genTime has a fraction that is not digits") - } - } - for i := 0; i < 9; i++ { - nanos *= 10 - if i < len(digits) { - nanos += int(digits[i] - '0') - } - } - t = t.Add(time.Duration(nanos)) - } - return t, nil -} - -// parseAccuracy reads Accuracy: seconds from 0, and millis and micros from 1 -// to 999, in that order, each optional (RFC 3161 2.4.2). A negative number -// would make a seal after the opening date look before it. +// parseAccuracy reads Accuracy: seconds from 0 to 2^31 - 1, and millis and +// micros from 1 to 999, in that order, each optional (RFC 3161 2.4.2, spec +// §29.11), each a minimal INTEGER. A negative number would make a seal after +// the opening date look before it. func parseAccuracy(b []byte) (time.Duration, error) { _, f, err := der.Split(b) if err != nil { @@ -372,9 +384,9 @@ func parseAccuracy(b []byte) (time.Duration, error) { } var total time.Duration if len(f) > 0 && f[0][0] == 0x02 { - var secs int64 - if _, err := asn1.Unmarshal(f[0], &secs); err != nil || secs < 0 || secs > maxAccuracy { - return 0, errors.New("accuracy seconds outside 0 to 2^31") + secs, ok := smallInt(f[0]) + if !ok || secs > maxAccuracy { + return 0, errors.New("accuracy seconds outside 0 to 2^31 - 1") } total += time.Duration(secs) * time.Second f = f[1:] @@ -385,8 +397,8 @@ func parseAccuracy(b []byte) (time.Duration, error) { }{{0x80, time.Millisecond}, {0x81, time.Microsecond}} { if len(f) > 0 && f[0][0] == part.tag { c, err := der.Content(f[0]) - if err != nil || len(c) < 1 || len(c) > 2 || c[0]&0x80 != 0 { - return 0, errors.New("accuracy millis or micros") + if err != nil || len(c) < 1 || len(c) > 2 || c[0]&0x80 != 0 || len(c) == 2 && c[0] == 0 && c[1]&0x80 == 0 { + return 0, errors.New("accuracy millis or micros that are not a minimal INTEGER") } n := 0 for _, x := range c { @@ -407,18 +419,19 @@ func parseAccuracy(b []byte) (time.Duration, error) { // 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) } +func (t *Token) ImprintIsSHA256() bool { return bytes.Equal(t.ImprintAlg.OID, 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. +// verifies, the messageImprint is the hash of subject, of any length, 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() + newHash, _ := 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 index b709f52..047ec79 100644 --- a/internal/der/der.go +++ b/internal/der/der.go @@ -2,9 +2,10 @@ // 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. +// INTEGER, NULL, OBJECT IDENTIFIER, BIT STRING, UTCTime and GeneralizedTime, +// 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. @@ -14,6 +15,8 @@ import ( "bytes" "errors" "fmt" + "strings" + "time" ) const maxDepth = 32 @@ -63,17 +66,98 @@ func Content(b []byte) ([]byte, error) { } // 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. +// bytes, as DER requires of the elements of a SET OF (X.690 11.6). Equal +// elements may repeat, side by side: X.690 does not forbid it, and a time- +// stamping authority may send its certificate twice. Whoever counts the +// elements of a SET OF decides what a repetition means. func SetOfSorted(elems [][]byte) bool { for i := 1; i < len(elems); i++ { - if bytes.Compare(elems[i-1], elems[i]) >= 0 { + if bytes.Compare(elems[i-1], elems[i]) > 0 { return false } } return true } +// ParseTime reads a UTCTime or a GeneralizedTime element as DER writes them +// (X.690 11.7 and 11.8): YYMMDDHHMMSSZ, with the years 50 to 99 in the 20th +// century (RFC 5280 4.1.2.5.1), or YYYYMMDDHHMMSS, an optional fraction of +// seconds without a trailing zero, and Z. A date or a time that does not +// exist, a second 60 included, is refused. fraction reports whether a +// GeneralizedTime has one. +func ParseTime(b []byte) (t time.Time, fraction bool, err error) { + if len(b) < 2 || (b[0] != 0x17 && b[0] != 0x18) { + return time.Time{}, false, errors.New("der: not a UTCTime or a GeneralizedTime") + } + c, err := Content(b) + if err != nil { + return time.Time{}, false, err + } + return parseTime(b[0], string(c)) +} + +func parseTime(tag byte, s string) (time.Time, bool, error) { + bad := errors.New("der: a time that is not in the form of DER") + if len(s) < 13 || s[len(s)-1] != 'Z' { + return time.Time{}, false, bad + } + body, frac := s[:len(s)-1], "" + var year int + switch tag { + case 0x17: + if len(body) != 12 || !digits(body) { + return time.Time{}, false, bad + } + year = atoi(body[:2]) + 1900 + if year < 1950 { + year += 100 + } + body = body[2:] + default: + if dot := strings.IndexByte(body, '.'); dot >= 0 { + body, frac = body[:dot], body[dot+1:] + if frac == "" || frac[len(frac)-1] == '0' || !digits(frac) { + return time.Time{}, false, bad + } + } + if len(body) != 14 || !digits(body) { + return time.Time{}, false, bad + } + year = atoi(body[:4]) + body = body[4:] + } + month, day, hour, minute, second := atoi(body[0:2]), atoi(body[2:4]), atoi(body[4:6]), atoi(body[6:8]), atoi(body[8:10]) + if month < 1 || month > 12 || day < 1 || hour > 23 || minute > 59 || second > 59 || + day > time.Date(year, time.Month(month)+1, 0, 0, 0, 0, 0, time.UTC).Day() { + return time.Time{}, false, errors.New("der: a date or a time that does not exist") + } + nanos := 0 + for i := 0; i < 9; i++ { + nanos *= 10 + if i < len(frac) { + nanos += int(frac[i] - '0') + } + } + return time.Date(year, time.Month(month), day, hour, minute, second, nanos, time.UTC), frac != "", nil +} + +func digits(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] < '0' || s[i] > '9' { + return false + } + } + return s != "" +} + +func atoi(s string) int { + n := 0 + for i := 0; i < len(s); i++ { + n = n*10 + int(s[i]-'0') + } + return n +} + // 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. @@ -183,7 +267,11 @@ func checkPrimitive(tag byte, c []byte) error { } start = x&0x80 == 0 } - case 4, 12, 19, 20, 22, 23, 24, 26, 28, 30: // OCTET STRING and the string and time types of X.509 + case 23, 24: // UTCTime, GeneralizedTime + if _, _, err := parseTime(tag, string(c)); err != nil { + return err + } + case 4, 12, 19, 20, 22, 26, 28, 30: // OCTET STRING and the string types of X.509 case 16, 17: return fmt.Errorf("der: the universal type %d in primitive form", tag) default: diff --git a/internal/der/der_test.go b/internal/der/der_test.go index 0da35cf..85c7253 100644 --- a/internal/der/der_test.go +++ b/internal/der/der_test.go @@ -3,6 +3,7 @@ package der import ( "encoding/hex" "testing" + "time" ) func TestCheck(t *testing.T) { @@ -14,6 +15,7 @@ func TestCheck(t *testing.T) { {"a long length", "04" + "8180" + zeros(128), true}, {"a long form under 128", "0481" + "01" + "00", false}, {"a length with a leading zero", "04820001" + "00", false}, + {"a length of 128 with a leading zero", "04820080" + zeros(128), false}, {"an indefinite length", "30800000", false}, {"a high tag number", "1f0100", false}, {"truncated", "0402aa", false}, @@ -38,6 +40,19 @@ func TestCheck(t *testing.T) { {"a reserved universal tag", "0e0141", false}, {"a SEQUENCE of the end of contents", "30020000", false}, {"UTF8String", "0c026162", true}, + // Times in the forms of DER (X.690 11.7, 11.8). + {"UTCTime", "170d" + hex.EncodeToString([]byte("250101120000Z")), true}, + {"UTCTime without seconds", "170b" + hex.EncodeToString([]byte("2501011200Z")), false}, + {"UTCTime with an offset", "1711" + hex.EncodeToString([]byte("250101120000+0100")), false}, + {"UTCTime of 30 February", "170d" + hex.EncodeToString([]byte("250230120000Z")), false}, + {"UTCTime with second 60", "170d" + hex.EncodeToString([]byte("250101235960Z")), false}, + {"a UTCTime that is not a time", "170a" + hex.EncodeToString([]byte("not a time")), false}, + {"GeneralizedTime", "180f" + hex.EncodeToString([]byte("20250101120000Z")), true}, + {"GeneralizedTime with a fraction", "1812" + hex.EncodeToString([]byte("20250101120000.25Z")), true}, + {"GeneralizedTime with a trailing zero", "1813" + hex.EncodeToString([]byte("20250101120000.250Z")), false}, + {"GeneralizedTime with an empty fraction", "1810" + hex.EncodeToString([]byte("20250101120000.Z")), false}, + {"GeneralizedTime without Z", "180e" + hex.EncodeToString([]byte("20250101120000")), false}, + {"GeneralizedTime with a comma", "1812" + hex.EncodeToString([]byte("20250101120000,25Z")), false}, } { b, err := hex.DecodeString(tc.hex) if err != nil { @@ -57,13 +72,54 @@ func zeros(n int) string { return string(b) } +// The elements of a SET OF go in ascending order, and equal ones may repeat +// (X.690 11.6). func TestSetOfSorted(t *testing.T) { a, b := []byte{0x02, 0x01, 0x01}, []byte{0x02, 0x01, 0x02} - if !SetOfSorted([][]byte{a, b}) || SetOfSorted([][]byte{b, a}) || SetOfSorted([][]byte{a, a}) { + if !SetOfSorted([][]byte{a, b}) || SetOfSorted([][]byte{b, a}) || !SetOfSorted([][]byte{a, a, b}) || SetOfSorted([][]byte{a, b, a}) { t.Error("SetOfSorted") } } +// Elements nested 32 levels below the outer one are DER; one more is too deep. +func TestDepth(t *testing.T) { + nested := func(n int) []byte { + b := []byte{0x05, 0x00} + for range n { + b = append([]byte{0x30, byte(len(b))}, b...) + } + return b + } + if err := Check(nested(maxDepth)); err != nil { + t.Errorf("%d levels: %v", maxDepth, err) + } + if err := Check(nested(maxDepth + 1)); err == nil { + t.Errorf("%d levels: accepted", maxDepth+1) + } +} + +func TestParseTime(t *testing.T) { + for _, c := range []struct { + el string + want time.Time + frac bool + }{ + {"\x17\x0d" + "491231235959Z", time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC), false}, + {"\x17\x0d" + "500101000000Z", time.Date(1950, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {"\x18\x13" + "20240229120000.125Z", time.Date(2024, 2, 29, 12, 0, 0, 125e6, time.UTC), true}, + } { + got, frac, err := ParseTime([]byte(c.el)) + if err != nil || !got.Equal(c.want) || frac != c.frac { + t.Errorf("%q: %v %v %v", c.el, got, frac, err) + } + } + for _, bad := range []string{"\x18\x0f" + "20230229120000Z", "\x04\x0d" + "491231235959Z", "\x17"} { + if _, _, err := ParseTime([]byte(bad)); err == nil { + t.Errorf("%q: accepted", bad) + } + } +} + func TestSplit(t *testing.T) { b, _ := hex.DecodeString("30050201010500") if err := Check(b); err != nil {