diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6407b71..f83f1e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ jobs: name: Test strategy: matrix: - go: ['1.12', '1.13', '1.14', '1.15', '1.16'] + go: ['1.14', '1.15', '1.16', '1.21', '1.22', '1.23'] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -15,6 +15,7 @@ jobs: - uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - stable: false - name: Test run: go vet . && go build . && go test -count=1 -covermode=count -coverprofile=coverage.out . + env: + GODEBUG: x509sha1=1 # enable SHA1; it's used in the older fixtures diff --git a/go.mod b/go.mod index 7316d7b..0e205b1 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/smallstep/pkcs7 -go 1.11 +go 1.14 + +require golang.org/x/crypto v0.27.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9a779cf --- /dev/null +++ b/go.sum @@ -0,0 +1,65 @@ +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/legacy/x509/debug.go b/internal/legacy/x509/debug.go new file mode 100644 index 0000000..378cc26 --- /dev/null +++ b/internal/legacy/x509/debug.go @@ -0,0 +1,14 @@ +package legacyx509 + +import "fmt" + +// legacyGodebugSetting is a type mimicking Go's internal godebug package +// settings, which are used to enable / disable certain functionalities at +// build time. +type legacyGodebugSetting int + +func (s legacyGodebugSetting) Value() string { + return fmt.Sprintf("%d", s) +} + +func (s legacyGodebugSetting) IncNonDefault() {} diff --git a/internal/legacy/x509/doc.go b/internal/legacy/x509/doc.go new file mode 100644 index 0000000..7d1469b --- /dev/null +++ b/internal/legacy/x509/doc.go @@ -0,0 +1,14 @@ +/* +Package legacyx509 is a copy of certain parts of Go's crypto/x509 package. +It is based on Go 1.23, and has just the parts copied over required for +parsing X509 certificates. + +The primary reason this copy exists is to keep support for parsing PKCS7 +messages containing Simple Certificate Enrolment Protocol (SCEP) requests +from Windows devices. Go 1.23 made a change marking certificates with a +critical authority key identifier as invalid, which is mandated by RFC 5280, +but apparently Windows marks those specific certificates as such, resulting +in those SCEP requests failing from being parsed correctly. +*/ + +package legacyx509 diff --git a/internal/legacy/x509/oid.go b/internal/legacy/x509/oid.go new file mode 100644 index 0000000..8268a07 --- /dev/null +++ b/internal/legacy/x509/oid.go @@ -0,0 +1,377 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package legacyx509 + +import ( + "bytes" + "encoding/asn1" + "errors" + "math" + "math/big" + "math/bits" + "strconv" + "strings" +) + +var ( + errInvalidOID = errors.New("invalid oid") +) + +// An OID represents an ASN.1 OBJECT IDENTIFIER. +type OID struct { + der []byte +} + +// ParseOID parses a Object Identifier string, represented by ASCII numbers separated by dots. +func ParseOID(oid string) (OID, error) { + var o OID + return o, o.unmarshalOIDText(oid) +} + +func newOIDFromDER(der []byte) (OID, bool) { + if len(der) == 0 || der[len(der)-1]&0x80 != 0 { + return OID{}, false + } + + start := 0 + for i, v := range der { + // ITU-T X.690, section 8.19.2: + // The subidentifier shall be encoded in the fewest possible octets, + // that is, the leading octet of the subidentifier shall not have the value 0x80. + if i == start && v == 0x80 { + return OID{}, false + } + if v&0x80 == 0 { + start = i + 1 + } + } + + return OID{der}, true +} + +// OIDFromInts creates a new OID using ints, each integer is a separate component. +func OIDFromInts(oid []uint64) (OID, error) { + if len(oid) < 2 || oid[0] > 2 || (oid[0] < 2 && oid[1] >= 40) { + return OID{}, errInvalidOID + } + + length := base128IntLength(oid[0]*40 + oid[1]) + for _, v := range oid[2:] { + length += base128IntLength(v) + } + + der := make([]byte, 0, length) + der = appendBase128Int(der, oid[0]*40+oid[1]) + for _, v := range oid[2:] { + der = appendBase128Int(der, v) + } + return OID{der}, nil +} + +func base128IntLength(n uint64) int { + if n == 0 { + return 1 + } + return (bits.Len64(n) + 6) / 7 +} + +func appendBase128Int(dst []byte, n uint64) []byte { + for i := base128IntLength(n) - 1; i >= 0; i-- { + o := byte(n >> uint(i*7)) + o &= 0x7f + if i != 0 { + o |= 0x80 + } + dst = append(dst, o) + } + return dst +} + +func base128BigIntLength(n *big.Int) int { + if n.Cmp(big.NewInt(0)) == 0 { + return 1 + } + return (n.BitLen() + 6) / 7 +} + +func appendBase128BigInt(dst []byte, n *big.Int) []byte { + if n.Cmp(big.NewInt(0)) == 0 { + return append(dst, 0) + } + + for i := base128BigIntLength(n) - 1; i >= 0; i-- { + o := byte(big.NewInt(0).Rsh(n, uint(i)*7).Bits()[0]) + o &= 0x7f + if i != 0 { + o |= 0x80 + } + dst = append(dst, o) + } + return dst +} + +// AppendText implements [encoding.TextAppender] +func (o OID) AppendText(b []byte) ([]byte, error) { + return append(b, o.String()...), nil +} + +// MarshalText implements [encoding.TextMarshaler] +func (o OID) MarshalText() ([]byte, error) { + return o.AppendText(nil) +} + +// UnmarshalText implements [encoding.TextUnmarshaler] +func (o *OID) UnmarshalText(text []byte) error { + return o.unmarshalOIDText(string(text)) +} + +// cutString slices s around the first instance of sep, +// returning the text before and after sep. +// The found result reports whether sep appears in s. +// If sep does not appear in s, cut returns s, "", false. +func cutString(s, sep string) (before, after string, found bool) { + if i := strings.Index(s, sep); i >= 0 { + return s[:i], s[i+len(sep):], true + } + return s, "", false +} + +func (o *OID) unmarshalOIDText(oid string) error { + // (*big.Int).SetString allows +/- signs, but we don't want + // to allow them in the string representation of Object Identifier, so + // reject such encodings. + for _, c := range oid { + isDigit := c >= '0' && c <= '9' + if !isDigit && c != '.' { + return errInvalidOID + } + } + + var ( + firstNum string + secondNum string + ) + + var nextComponentExists bool + firstNum, oid, nextComponentExists = cutString(oid, ".") + if !nextComponentExists { + return errInvalidOID + } + secondNum, oid, nextComponentExists = cutString(oid, ".") + + var ( + first = big.NewInt(0) + second = big.NewInt(0) + ) + + if _, ok := first.SetString(firstNum, 10); !ok { + return errInvalidOID + } + if _, ok := second.SetString(secondNum, 10); !ok { + return errInvalidOID + } + + if first.Cmp(big.NewInt(2)) > 0 || (first.Cmp(big.NewInt(2)) < 0 && second.Cmp(big.NewInt(40)) >= 0) { + return errInvalidOID + } + + firstComponent := first.Mul(first, big.NewInt(40)) + firstComponent.Add(firstComponent, second) + + der := appendBase128BigInt(make([]byte, 0, 32), firstComponent) + + for nextComponentExists { + var strNum string + strNum, oid, nextComponentExists = cutString(oid, ".") + b, ok := big.NewInt(0).SetString(strNum, 10) + if !ok { + return errInvalidOID + } + der = appendBase128BigInt(der, b) + } + + o.der = der + return nil +} + +// AppendBinary implements [encoding.BinaryAppender] +func (o OID) AppendBinary(b []byte) ([]byte, error) { + return append(b, o.der...), nil +} + +// MarshalBinary implements [encoding.BinaryMarshaler] +func (o OID) MarshalBinary() ([]byte, error) { + return o.AppendBinary(nil) +} + +// cloneBytes returns a copy of b[:len(b)]. +// The result may have additional unused capacity. +// Clone(nil) returns nil. +func cloneBytes(b []byte) []byte { + if b == nil { + return nil + } + return append([]byte{}, b...) +} + +// UnmarshalBinary implements [encoding.BinaryUnmarshaler] +func (o *OID) UnmarshalBinary(b []byte) error { + oid, ok := newOIDFromDER(cloneBytes(b)) + if !ok { + return errInvalidOID + } + *o = oid + return nil +} + +// Equal returns true when oid and other represents the same Object Identifier. +func (oid OID) Equal(other OID) bool { + // There is only one possible DER encoding of + // each unique Object Identifier. + return bytes.Equal(oid.der, other.der) +} + +func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, failed bool) { + offset = initOffset + var ret64 int64 + for shifted := 0; offset < len(bytes); shifted++ { + // 5 * 7 bits per byte == 35 bits of data + // Thus the representation is either non-minimal or too large for an int32 + if shifted == 5 { + failed = true + return + } + ret64 <<= 7 + b := bytes[offset] + // integers should be minimally encoded, so the leading octet should + // never be 0x80 + if shifted == 0 && b == 0x80 { + failed = true + return + } + ret64 |= int64(b & 0x7f) + offset++ + if b&0x80 == 0 { + ret = int(ret64) + // Ensure that the returned value fits in an int on all platforms + if ret64 > math.MaxInt32 { + failed = true + } + return + } + } + failed = true + return +} + +// EqualASN1OID returns whether an OID equals an asn1.ObjectIdentifier. If +// asn1.ObjectIdentifier cannot represent the OID specified by oid, because +// a component of OID requires more than 31 bits, it returns false. +func (oid OID) EqualASN1OID(other asn1.ObjectIdentifier) bool { + if len(other) < 2 { + return false + } + v, offset, failed := parseBase128Int(oid.der, 0) + if failed { + // This should never happen, since we've already parsed the OID, + // but just in case. + return false + } + if v < 80 { + a, b := v/40, v%40 + if other[0] != a || other[1] != b { + return false + } + } else { + a, b := 2, v-80 + if other[0] != a || other[1] != b { + return false + } + } + + i := 2 + for ; offset < len(oid.der); i++ { + v, offset, failed = parseBase128Int(oid.der, offset) + if failed { + // Again, shouldn't happen, since we've already parsed + // the OID, but better safe than sorry. + return false + } + if i >= len(other) || v != other[i] { + return false + } + } + + return i == len(other) +} + +// Strings returns the string representation of the Object Identifier. +func (oid OID) String() string { + var b strings.Builder + b.Grow(32) + const ( + valSize = 64 // size in bits of val. + bitsPerByte = 7 + maxValSafeShift = (1 << (valSize - bitsPerByte)) - 1 + ) + var ( + start = 0 + val = uint64(0) + numBuf = make([]byte, 0, 21) + bigVal *big.Int + overflow bool + ) + for i, v := range oid.der { + curVal := v & 0x7F + valEnd := v&0x80 == 0 + if valEnd { + if start != 0 { + b.WriteByte('.') + } + } + if !overflow && val > maxValSafeShift { + if bigVal == nil { + bigVal = new(big.Int) + } + bigVal = bigVal.SetUint64(val) + overflow = true + } + if overflow { + bigVal = bigVal.Lsh(bigVal, bitsPerByte).Or(bigVal, big.NewInt(int64(curVal))) + if valEnd { + if start == 0 { + b.WriteString("2.") + bigVal = bigVal.Sub(bigVal, big.NewInt(80)) + } + numBuf = bigVal.Append(numBuf, 10) + b.Write(numBuf) + numBuf = numBuf[:0] + val = 0 + start = i + 1 + overflow = false + } + continue + } + val <<= bitsPerByte + val |= uint64(curVal) + if valEnd { + if start == 0 { + if val < 80 { + b.Write(strconv.AppendUint(numBuf, val/40, 10)) + b.WriteByte('.') + b.Write(strconv.AppendUint(numBuf, val%40, 10)) + } else { + b.WriteString("2.") + b.Write(strconv.AppendUint(numBuf, val-80, 10)) + } + } else { + b.Write(strconv.AppendUint(numBuf, val, 10)) + } + val = 0 + start = i + 1 + } + } + return b.String() +} diff --git a/internal/legacy/x509/parser.go b/internal/legacy/x509/parser.go new file mode 100644 index 0000000..ec57e79 --- /dev/null +++ b/internal/legacy/x509/parser.go @@ -0,0 +1,1027 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package legacyx509 + +import ( + "bytes" + "crypto/dsa" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "math/big" + "net" + "net/url" + "strconv" + "strings" + "time" + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/crypto/cryptobyte" + cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" + + stdx509 "crypto/x509" +) + +// ParseCertificates parses one or more certificates from the given ASN.1 DER +// data. The certificates must be concatenated with no intermediate padding. +func ParseCertificates(der []byte) ([]*stdx509.Certificate, error) { + var certs []*stdx509.Certificate + for len(der) > 0 { + cert, err := parseCertificate(der) + if err != nil { + return nil, err + } + certs = append(certs, cert) + der = der[len(cert.Raw):] + } + return certs, nil +} + +// isPrintable reports whether the given b is in the ASN.1 PrintableString set. +// This is a simplified version of encoding/asn1.isPrintable. +func isPrintable(b byte) bool { + return 'a' <= b && b <= 'z' || + 'A' <= b && b <= 'Z' || + '0' <= b && b <= '9' || + '\'' <= b && b <= ')' || + '+' <= b && b <= '/' || + b == ' ' || + b == ':' || + b == '=' || + b == '?' || + // This is technically not allowed in a PrintableString. + // However, x509 certificates with wildcard strings don't + // always use the correct string type so we permit it. + b == '*' || + // This is not technically allowed either. However, not + // only is it relatively common, but there are also a + // handful of CA certificates that contain it. At least + // one of which will not expire until 2027. + b == '&' +} + +// parseASN1String parses the ASN.1 string types T61String, PrintableString, +// UTF8String, BMPString, IA5String, and NumericString. This is mostly copied +// from the respective encoding/asn1.parse... methods, rather than just +// increasing the API surface of that package. +func parseASN1String(tag cryptobyte_asn1.Tag, value []byte) (string, error) { + switch tag { + case cryptobyte_asn1.T61String: + return string(value), nil + case cryptobyte_asn1.PrintableString: + for _, b := range value { + if !isPrintable(b) { + return "", errors.New("invalid PrintableString") + } + } + return string(value), nil + case cryptobyte_asn1.UTF8String: + if !utf8.Valid(value) { + return "", errors.New("invalid UTF-8 string") + } + return string(value), nil + case cryptobyte_asn1.Tag(asn1.TagBMPString): + if len(value)%2 != 0 { + return "", errors.New("invalid BMPString") + } + + // Strip terminator if present. + if l := len(value); l >= 2 && value[l-1] == 0 && value[l-2] == 0 { + value = value[:l-2] + } + + s := make([]uint16, 0, len(value)/2) + for len(value) > 0 { + s = append(s, uint16(value[0])<<8+uint16(value[1])) + value = value[2:] + } + + return string(utf16.Decode(s)), nil + case cryptobyte_asn1.IA5String: + s := string(value) + if isIA5String(s) != nil { + return "", errors.New("invalid IA5String") + } + return s, nil + case cryptobyte_asn1.Tag(asn1.TagNumericString): + for _, b := range value { + if !('0' <= b && b <= '9' || b == ' ') { + return "", errors.New("invalid NumericString") + } + } + return string(value), nil + } + return "", fmt.Errorf("unsupported string type: %v", tag) +} + +// parseName parses a DER encoded Name as defined in RFC 5280. We may +// want to export this function in the future for use in crypto/tls. +func parseName(raw cryptobyte.String) (*pkix.RDNSequence, error) { + if !raw.ReadASN1(&raw, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: invalid RDNSequence") + } + + var rdnSeq pkix.RDNSequence + for !raw.Empty() { + var rdnSet pkix.RelativeDistinguishedNameSET + var set cryptobyte.String + if !raw.ReadASN1(&set, cryptobyte_asn1.SET) { + return nil, errors.New("x509: invalid RDNSequence") + } + for !set.Empty() { + var atav cryptobyte.String + if !set.ReadASN1(&atav, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: invalid RDNSequence: invalid attribute") + } + var attr pkix.AttributeTypeAndValue + if !atav.ReadASN1ObjectIdentifier(&attr.Type) { + return nil, errors.New("x509: invalid RDNSequence: invalid attribute type") + } + var rawValue cryptobyte.String + var valueTag cryptobyte_asn1.Tag + if !atav.ReadAnyASN1(&rawValue, &valueTag) { + return nil, errors.New("x509: invalid RDNSequence: invalid attribute value") + } + var err error + attr.Value, err = parseASN1String(valueTag, rawValue) + if err != nil { + return nil, fmt.Errorf("x509: invalid RDNSequence: invalid attribute value: %s", err) + } + rdnSet = append(rdnSet, attr) + } + + rdnSeq = append(rdnSeq, rdnSet) + } + + return &rdnSeq, nil +} + +func parseAI(der cryptobyte.String) (pkix.AlgorithmIdentifier, error) { + ai := pkix.AlgorithmIdentifier{} + if !der.ReadASN1ObjectIdentifier(&ai.Algorithm) { + return ai, errors.New("x509: malformed OID") + } + if der.Empty() { + return ai, nil + } + var params cryptobyte.String + var tag cryptobyte_asn1.Tag + if !der.ReadAnyASN1Element(¶ms, &tag) { + return ai, errors.New("x509: malformed parameters") + } + ai.Parameters.Tag = int(tag) + ai.Parameters.FullBytes = params + return ai, nil +} + +func parseTime(der *cryptobyte.String) (time.Time, error) { + var t time.Time + switch { + case der.PeekASN1Tag(cryptobyte_asn1.UTCTime): + if !der.ReadASN1UTCTime(&t) { + return t, errors.New("x509: malformed UTCTime") + } + case der.PeekASN1Tag(cryptobyte_asn1.GeneralizedTime): + if !der.ReadASN1GeneralizedTime(&t) { + return t, errors.New("x509: malformed GeneralizedTime") + } + default: + return t, errors.New("x509: unsupported time format") + } + return t, nil +} + +func parseValidity(der cryptobyte.String) (time.Time, time.Time, error) { + notBefore, err := parseTime(&der) + if err != nil { + return time.Time{}, time.Time{}, err + } + notAfter, err := parseTime(&der) + if err != nil { + return time.Time{}, time.Time{}, err + } + + return notBefore, notAfter, nil +} + +func parseExtension(der cryptobyte.String) (pkix.Extension, error) { + var ext pkix.Extension + if !der.ReadASN1ObjectIdentifier(&ext.Id) { + return ext, errors.New("x509: malformed extension OID field") + } + if der.PeekASN1Tag(cryptobyte_asn1.BOOLEAN) { + if !der.ReadASN1Boolean(&ext.Critical) { + return ext, errors.New("x509: malformed extension critical field") + } + } + var val cryptobyte.String + if !der.ReadASN1(&val, cryptobyte_asn1.OCTET_STRING) { + return ext, errors.New("x509: malformed extension value field") + } + ext.Value = val + return ext, nil +} + +func parsePublicKey(keyData *publicKeyInfo) (interface{}, error) { + oid := keyData.Algorithm.Algorithm + params := keyData.Algorithm.Parameters + der := cryptobyte.String(keyData.PublicKey.RightAlign()) + switch { + case oid.Equal(oidPublicKeyRSA): + // RSA public keys must have a NULL in the parameters. + // See RFC 3279, Section 2.3.1. + if !bytes.Equal(params.FullBytes, asn1.NullBytes) { + return nil, errors.New("x509: RSA key missing NULL parameters") + } + + p := &pkcs1PublicKey{N: new(big.Int)} + if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: invalid RSA public key") + } + if !der.ReadASN1Integer(p.N) { + return nil, errors.New("x509: invalid RSA modulus") + } + if !der.ReadASN1Integer(&p.E) { + return nil, errors.New("x509: invalid RSA public exponent") + } + + if p.N.Sign() <= 0 { + return nil, errors.New("x509: RSA modulus is not a positive number") + } + if p.E <= 0 { + return nil, errors.New("x509: RSA public exponent is not a positive number") + } + + pub := &rsa.PublicKey{ + E: p.E, + N: p.N, + } + return pub, nil + case oid.Equal(oidPublicKeyECDSA): + paramsDer := cryptobyte.String(params.FullBytes) + namedCurveOID := new(asn1.ObjectIdentifier) + if !paramsDer.ReadASN1ObjectIdentifier(namedCurveOID) { + return nil, errors.New("x509: invalid ECDSA parameters") + } + namedCurve := namedCurveFromOID(*namedCurveOID) + if namedCurve == nil { + return nil, errors.New("x509: unsupported elliptic curve") + } + x, y := elliptic.Unmarshal(namedCurve, der) + if x == nil { + return nil, errors.New("x509: failed to unmarshal elliptic curve point") + } + pub := &ecdsa.PublicKey{ + Curve: namedCurve, + X: x, + Y: y, + } + return pub, nil + case oid.Equal(oidPublicKeyEd25519): + // RFC 8410, Section 3 + // > For all of the OIDs, the parameters MUST be absent. + if len(params.FullBytes) != 0 { + return nil, errors.New("x509: Ed25519 key encoded with illegal parameters") + } + if len(der) != ed25519.PublicKeySize { + return nil, errors.New("x509: wrong Ed25519 public key size") + } + return ed25519.PublicKey(der), nil + // case oid.Equal(oidPublicKeyX25519): + // // RFC 8410, Section 3 + // // > For all of the OIDs, the parameters MUST be absent. + // if len(params.FullBytes) != 0 { + // return nil, errors.New("x509: X25519 key encoded with illegal parameters") + // } + // return ecdh.X25519().NewPublicKey(der) + case oid.Equal(oidPublicKeyDSA): + y := new(big.Int) + if !der.ReadASN1Integer(y) { + return nil, errors.New("x509: invalid DSA public key") + } + pub := &dsa.PublicKey{ + Y: y, + Parameters: dsa.Parameters{ + P: new(big.Int), + Q: new(big.Int), + G: new(big.Int), + }, + } + paramsDer := cryptobyte.String(params.FullBytes) + if !paramsDer.ReadASN1(¶msDer, cryptobyte_asn1.SEQUENCE) || + !paramsDer.ReadASN1Integer(pub.Parameters.P) || + !paramsDer.ReadASN1Integer(pub.Parameters.Q) || + !paramsDer.ReadASN1Integer(pub.Parameters.G) { + return nil, errors.New("x509: invalid DSA parameters") + } + if pub.Y.Sign() <= 0 || pub.Parameters.P.Sign() <= 0 || + pub.Parameters.Q.Sign() <= 0 || pub.Parameters.G.Sign() <= 0 { + return nil, errors.New("x509: zero or negative DSA parameter") + } + return pub, nil + default: + return nil, errors.New("x509: unknown public key algorithm") + } +} + +func parseKeyUsageExtension(der cryptobyte.String) (stdx509.KeyUsage, error) { + var usageBits asn1.BitString + if !der.ReadASN1BitString(&usageBits) { + return 0, errors.New("x509: invalid key usage") + } + + var usage int + for i := 0; i < 9; i++ { + if usageBits.At(i) != 0 { + usage |= 1 << uint(i) + } + } + return stdx509.KeyUsage(usage), nil +} + +func parseBasicConstraintsExtension(der cryptobyte.String) (bool, int, error) { + var isCA bool + if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { + return false, 0, errors.New("x509: invalid basic constraints") + } + if der.PeekASN1Tag(cryptobyte_asn1.BOOLEAN) { + if !der.ReadASN1Boolean(&isCA) { + return false, 0, errors.New("x509: invalid basic constraints") + } + } + maxPathLen := -1 + if der.PeekASN1Tag(cryptobyte_asn1.INTEGER) { + if !der.ReadASN1Integer(&maxPathLen) { + return false, 0, errors.New("x509: invalid basic constraints") + } + } + + // TODO: map out.MaxPathLen to 0 if it has the -1 default value? (Issue 19285) + return isCA, maxPathLen, nil +} + +func forEachSAN(der cryptobyte.String, callback func(tag int, data []byte) error) error { + if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { + return errors.New("x509: invalid subject alternative names") + } + for !der.Empty() { + var san cryptobyte.String + var tag cryptobyte_asn1.Tag + if !der.ReadAnyASN1(&san, &tag) { + return errors.New("x509: invalid subject alternative name") + } + if err := callback(int(tag^0x80), san); err != nil { + return err + } + } + + return nil +} + +func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, err error) { + err = forEachSAN(der, func(tag int, data []byte) error { + switch tag { + case nameTypeEmail: + email := string(data) + if err := isIA5String(email); err != nil { + return errors.New("x509: SAN rfc822Name is malformed") + } + emailAddresses = append(emailAddresses, email) + case nameTypeDNS: + name := string(data) + if err := isIA5String(name); err != nil { + return errors.New("x509: SAN dNSName is malformed") + } + dnsNames = append(dnsNames, string(name)) + case nameTypeURI: + uriStr := string(data) + if err := isIA5String(uriStr); err != nil { + return errors.New("x509: SAN uniformResourceIdentifier is malformed") + } + uri, err := url.Parse(uriStr) + if err != nil { + return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err) + } + if len(uri.Host) > 0 { + if _, ok := domainToReverseLabels(uri.Host); !ok { + return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr) + } + } + uris = append(uris, uri) + case nameTypeIP: + switch len(data) { + case net.IPv4len, net.IPv6len: + ipAddresses = append(ipAddresses, data) + default: + return errors.New("x509: cannot parse IP address of length " + strconv.Itoa(len(data))) + } + } + + return nil + }) + + return +} + +func parseAuthorityKeyIdentifier(e pkix.Extension) ([]byte, error) { + // RFC 5280, Section 4.2.1.1 + // if e.Critical { + // // Conforming CAs MUST mark this extension as non-critical + // return nil, errors.New("x509: authority key identifier incorrectly marked critical") + // } + val := cryptobyte.String(e.Value) + var akid cryptobyte.String + if !val.ReadASN1(&akid, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: invalid authority key identifier") + } + if akid.PeekASN1Tag(cryptobyte_asn1.Tag(0).ContextSpecific()) { + if !akid.ReadASN1(&akid, cryptobyte_asn1.Tag(0).ContextSpecific()) { + return nil, errors.New("x509: invalid authority key identifier") + } + return akid, nil + } + return nil, nil +} + +func parseExtKeyUsageExtension(der cryptobyte.String) ([]stdx509.ExtKeyUsage, []asn1.ObjectIdentifier, error) { + var extKeyUsages []stdx509.ExtKeyUsage + var unknownUsages []asn1.ObjectIdentifier + if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { + return nil, nil, errors.New("x509: invalid extended key usages") + } + for !der.Empty() { + var eku asn1.ObjectIdentifier + if !der.ReadASN1ObjectIdentifier(&eku) { + return nil, nil, errors.New("x509: invalid extended key usages") + } + if extKeyUsage, ok := extKeyUsageFromOID(eku); ok { + extKeyUsages = append(extKeyUsages, stdx509.ExtKeyUsage(extKeyUsage)) + } else { + unknownUsages = append(unknownUsages, eku) + } + } + return extKeyUsages, unknownUsages, nil +} + +// func parseCertificatePoliciesExtension(der cryptobyte.String) ([]OID, error) { +// var oids []OID +// if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { +// return nil, errors.New("x509: invalid certificate policies") +// } +// for !der.Empty() { +// var cp cryptobyte.String +// var OIDBytes cryptobyte.String +// if !der.ReadASN1(&cp, cryptobyte_asn1.SEQUENCE) || !cp.ReadASN1(&OIDBytes, cryptobyte_asn1.OBJECT_IDENTIFIER) { +// return nil, errors.New("x509: invalid certificate policies") +// } +// oid, ok := newOIDFromDER(OIDBytes) +// if !ok { +// return nil, errors.New("x509: invalid certificate policies") +// } +// oids = append(oids, oid) +// } +// return oids, nil +// } + +// isValidIPMask reports whether mask consists of zero or more 1 bits, followed by zero bits. +func isValidIPMask(mask []byte) bool { + seenZero := false + + for _, b := range mask { + if seenZero { + if b != 0 { + return false + } + + continue + } + + switch b { + case 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe: + seenZero = true + case 0xff: + default: + return false + } + } + + return true +} + +func parseNameConstraintsExtension(out *stdx509.Certificate, e pkix.Extension) (unhandled bool, err error) { + // RFC 5280, 4.2.1.10 + + // NameConstraints ::= SEQUENCE { + // permittedSubtrees [0] GeneralSubtrees OPTIONAL, + // excludedSubtrees [1] GeneralSubtrees OPTIONAL } + // + // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree + // + // GeneralSubtree ::= SEQUENCE { + // base GeneralName, + // minimum [0] BaseDistance DEFAULT 0, + // maximum [1] BaseDistance OPTIONAL } + // + // BaseDistance ::= INTEGER (0..MAX) + + outer := cryptobyte.String(e.Value) + var toplevel, permitted, excluded cryptobyte.String + var havePermitted, haveExcluded bool + if !outer.ReadASN1(&toplevel, cryptobyte_asn1.SEQUENCE) || + !outer.Empty() || + !toplevel.ReadOptionalASN1(&permitted, &havePermitted, cryptobyte_asn1.Tag(0).ContextSpecific().Constructed()) || + !toplevel.ReadOptionalASN1(&excluded, &haveExcluded, cryptobyte_asn1.Tag(1).ContextSpecific().Constructed()) || + !toplevel.Empty() { + return false, errors.New("x509: invalid NameConstraints extension") + } + + if !havePermitted && !haveExcluded || len(permitted) == 0 && len(excluded) == 0 { + // From RFC 5280, Section 4.2.1.10: + // “either the permittedSubtrees field + // or the excludedSubtrees MUST be + // present” + return false, errors.New("x509: empty name constraints extension") + } + + getValues := func(subtrees cryptobyte.String) (dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error) { + for !subtrees.Empty() { + var seq, value cryptobyte.String + var tag cryptobyte_asn1.Tag + if !subtrees.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) || + !seq.ReadAnyASN1(&value, &tag) { + return nil, nil, nil, nil, fmt.Errorf("x509: invalid NameConstraints extension") + } + + var ( + dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific() + emailTag = cryptobyte_asn1.Tag(1).ContextSpecific() + ipTag = cryptobyte_asn1.Tag(7).ContextSpecific() + uriTag = cryptobyte_asn1.Tag(6).ContextSpecific() + ) + + switch tag { + case dnsTag: + domain := string(value) + if err := isIA5String(domain); err != nil { + return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) + } + + trimmedDomain := domain + if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' { + // constraints can have a leading + // period to exclude the domain + // itself, but that's not valid in a + // normal domain name. + trimmedDomain = trimmedDomain[1:] + } + if _, ok := domainToReverseLabels(trimmedDomain); !ok { + return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain) + } + dnsNames = append(dnsNames, domain) + + case ipTag: + l := len(value) + var ip, mask []byte + + switch l { + case 8: + ip = value[:4] + mask = value[4:] + + case 32: + ip = value[:16] + mask = value[16:] + + default: + return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l) + } + + if !isValidIPMask(mask) { + return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask) + } + + ips = append(ips, &net.IPNet{IP: net.IP(ip), Mask: net.IPMask(mask)}) + + case emailTag: + constraint := string(value) + if err := isIA5String(constraint); err != nil { + return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) + } + + // If the constraint contains an @ then + // it specifies an exact mailbox name. + if strings.Contains(constraint, "@") { + if _, ok := parseRFC2821Mailbox(constraint); !ok { + return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint) + } + } else { + // Otherwise it's a domain name. + domain := constraint + if len(domain) > 0 && domain[0] == '.' { + domain = domain[1:] + } + if _, ok := domainToReverseLabels(domain); !ok { + return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint) + } + } + emails = append(emails, constraint) + + case uriTag: + domain := string(value) + if err := isIA5String(domain); err != nil { + return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) + } + + if net.ParseIP(domain) != nil { + return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain) + } + + trimmedDomain := domain + if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' { + // constraints can have a leading + // period to exclude the domain itself, + // but that's not valid in a normal + // domain name. + trimmedDomain = trimmedDomain[1:] + } + if _, ok := domainToReverseLabels(trimmedDomain); !ok { + return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain) + } + uriDomains = append(uriDomains, domain) + + default: + unhandled = true + } + } + + return dnsNames, ips, emails, uriDomains, nil + } + + if out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil { + return false, err + } + if out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil { + return false, err + } + out.PermittedDNSDomainsCritical = e.Critical + + return unhandled, nil +} + +func processExtensions(out *stdx509.Certificate) error { + var err error + for _, e := range out.Extensions { + unhandled := false + + if len(e.Id) == 4 && e.Id[0] == 2 && e.Id[1] == 5 && e.Id[2] == 29 { + switch e.Id[3] { + case 15: + out.KeyUsage, err = parseKeyUsageExtension(e.Value) + if err != nil { + return err + } + case 19: + out.IsCA, out.MaxPathLen, err = parseBasicConstraintsExtension(e.Value) + if err != nil { + return err + } + out.BasicConstraintsValid = true + out.MaxPathLenZero = out.MaxPathLen == 0 + case 17: + out.DNSNames, out.EmailAddresses, out.IPAddresses, out.URIs, err = parseSANExtension(e.Value) + if err != nil { + return err + } + + if len(out.DNSNames) == 0 && len(out.EmailAddresses) == 0 && len(out.IPAddresses) == 0 && len(out.URIs) == 0 { + // If we didn't parse anything then we do the critical check, below. + unhandled = true + } + + case 30: + unhandled, err = parseNameConstraintsExtension(out, e) + if err != nil { + return err + } + + case 31: + // RFC 5280, 4.2.1.13 + + // CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint + // + // DistributionPoint ::= SEQUENCE { + // distributionPoint [0] DistributionPointName OPTIONAL, + // reasons [1] ReasonFlags OPTIONAL, + // cRLIssuer [2] GeneralNames OPTIONAL } + // + // DistributionPointName ::= CHOICE { + // fullName [0] GeneralNames, + // nameRelativeToCRLIssuer [1] RelativeDistinguishedName } + val := cryptobyte.String(e.Value) + if !val.ReadASN1(&val, cryptobyte_asn1.SEQUENCE) { + return errors.New("x509: invalid CRL distribution points") + } + for !val.Empty() { + var dpDER cryptobyte.String + if !val.ReadASN1(&dpDER, cryptobyte_asn1.SEQUENCE) { + return errors.New("x509: invalid CRL distribution point") + } + var dpNameDER cryptobyte.String + var dpNamePresent bool + if !dpDER.ReadOptionalASN1(&dpNameDER, &dpNamePresent, cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) { + return errors.New("x509: invalid CRL distribution point") + } + if !dpNamePresent { + continue + } + if !dpNameDER.ReadASN1(&dpNameDER, cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) { + return errors.New("x509: invalid CRL distribution point") + } + for !dpNameDER.Empty() { + if !dpNameDER.PeekASN1Tag(cryptobyte_asn1.Tag(6).ContextSpecific()) { + break + } + var uri cryptobyte.String + if !dpNameDER.ReadASN1(&uri, cryptobyte_asn1.Tag(6).ContextSpecific()) { + return errors.New("x509: invalid CRL distribution point") + } + out.CRLDistributionPoints = append(out.CRLDistributionPoints, string(uri)) + } + } + + case 35: + out.AuthorityKeyId, err = parseAuthorityKeyIdentifier(e) + if err != nil { + return err + } + case 37: + out.ExtKeyUsage, out.UnknownExtKeyUsage, err = parseExtKeyUsageExtension(e.Value) + if err != nil { + return err + } + case 14: + // RFC 5280, 4.2.1.2 + if e.Critical { + // Conforming CAs MUST mark this extension as non-critical + return errors.New("x509: subject key identifier incorrectly marked critical") + } + val := cryptobyte.String(e.Value) + var skid cryptobyte.String + if !val.ReadASN1(&skid, cryptobyte_asn1.OCTET_STRING) { + return errors.New("x509: invalid subject key identifier") + } + out.SubjectKeyId = skid + // case 32: + // out.Policies, err = parseCertificatePoliciesExtension(e.Value) + // if err != nil { + // return err + // } + // out.PolicyIdentifiers = make([]asn1.ObjectIdentifier, 0, len(out.Policies)) + // for _, oid := range out.Policies { + // if oid, ok := oid.toASN1OID(); ok { + // out.PolicyIdentifiers = append(out.PolicyIdentifiers, oid) + // } + // } + default: + // Unknown extensions are recorded if critical. + unhandled = true + } + } else if e.Id.Equal(oidExtensionAuthorityInfoAccess) { + // RFC 5280 4.2.2.1: Authority Information Access + if e.Critical { + // Conforming CAs MUST mark this extension as non-critical + return errors.New("x509: authority info access incorrectly marked critical") + } + val := cryptobyte.String(e.Value) + if !val.ReadASN1(&val, cryptobyte_asn1.SEQUENCE) { + return errors.New("x509: invalid authority info access") + } + for !val.Empty() { + var aiaDER cryptobyte.String + if !val.ReadASN1(&aiaDER, cryptobyte_asn1.SEQUENCE) { + return errors.New("x509: invalid authority info access") + } + var method asn1.ObjectIdentifier + if !aiaDER.ReadASN1ObjectIdentifier(&method) { + return errors.New("x509: invalid authority info access") + } + if !aiaDER.PeekASN1Tag(cryptobyte_asn1.Tag(6).ContextSpecific()) { + continue + } + if !aiaDER.ReadASN1(&aiaDER, cryptobyte_asn1.Tag(6).ContextSpecific()) { + return errors.New("x509: invalid authority info access") + } + switch { + case method.Equal(oidAuthorityInfoAccessOcsp): + out.OCSPServer = append(out.OCSPServer, string(aiaDER)) + case method.Equal(oidAuthorityInfoAccessIssuers): + out.IssuingCertificateURL = append(out.IssuingCertificateURL, string(aiaDER)) + } + } + } else { + // Unknown extensions are recorded if critical. + unhandled = true + } + + if e.Critical && unhandled { + out.UnhandledCriticalExtensions = append(out.UnhandledCriticalExtensions, e.Id) + } + } + + return nil +} + +var x509negativeserial = legacyGodebugSetting(0) // replaces godebug.New("x509negativeserial") + +func parseCertificate(der []byte) (*stdx509.Certificate, error) { + cert := &stdx509.Certificate{} + + input := cryptobyte.String(der) + // we read the SEQUENCE including length and tag bytes so that + // we can populate Certificate.Raw, before unwrapping the + // SEQUENCE so it can be operated on + if !input.ReadASN1Element(&input, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed certificate") + } + cert.Raw = input + if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed certificate") + } + + var tbs cryptobyte.String + // do the same trick again as above to extract the raw + // bytes for Certificate.RawTBSCertificate + if !input.ReadASN1Element(&tbs, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed tbs certificate") + } + cert.RawTBSCertificate = tbs + if !tbs.ReadASN1(&tbs, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed tbs certificate") + } + + if !tbs.ReadOptionalASN1Integer(&cert.Version, cryptobyte_asn1.Tag(0).Constructed().ContextSpecific(), 0) { + return nil, errors.New("x509: malformed version") + } + if cert.Version < 0 { + return nil, errors.New("x509: malformed version") + } + // for backwards compat reasons Version is one-indexed, + // rather than zero-indexed as defined in 5280 + cert.Version++ + if cert.Version > 3 { + return nil, errors.New("x509: invalid version") + } + + serial := new(big.Int) + if !tbs.ReadASN1Integer(serial) { + return nil, errors.New("x509: malformed serial number") + } + if serial.Sign() == -1 { + if x509negativeserial.Value() != "1" { + return nil, errors.New("x509: negative serial number") + } else { + x509negativeserial.IncNonDefault() + } + } + cert.SerialNumber = serial + + var sigAISeq cryptobyte.String + if !tbs.ReadASN1(&sigAISeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed signature algorithm identifier") + } + // Before parsing the inner algorithm identifier, extract + // the outer algorithm identifier and make sure that they + // match. + var outerSigAISeq cryptobyte.String + if !input.ReadASN1(&outerSigAISeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed algorithm identifier") + } + if !bytes.Equal(outerSigAISeq, sigAISeq) { + return nil, errors.New("x509: inner and outer signature algorithm identifiers don't match") + } + sigAI, err := parseAI(sigAISeq) + if err != nil { + return nil, err + } + cert.SignatureAlgorithm = getSignatureAlgorithmFromAI(sigAI) + + var issuerSeq cryptobyte.String + if !tbs.ReadASN1Element(&issuerSeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed issuer") + } + cert.RawIssuer = issuerSeq + issuerRDNs, err := parseName(issuerSeq) + if err != nil { + return nil, err + } + cert.Issuer.FillFromRDNSequence(issuerRDNs) + + var validity cryptobyte.String + if !tbs.ReadASN1(&validity, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed validity") + } + cert.NotBefore, cert.NotAfter, err = parseValidity(validity) + if err != nil { + return nil, err + } + + var subjectSeq cryptobyte.String + if !tbs.ReadASN1Element(&subjectSeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed issuer") + } + cert.RawSubject = subjectSeq + subjectRDNs, err := parseName(subjectSeq) + if err != nil { + return nil, err + } + cert.Subject.FillFromRDNSequence(subjectRDNs) + + var spki cryptobyte.String + if !tbs.ReadASN1Element(&spki, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed spki") + } + cert.RawSubjectPublicKeyInfo = spki + if !spki.ReadASN1(&spki, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed spki") + } + var pkAISeq cryptobyte.String + if !spki.ReadASN1(&pkAISeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed public key algorithm identifier") + } + pkAI, err := parseAI(pkAISeq) + if err != nil { + return nil, err + } + cert.PublicKeyAlgorithm = getPublicKeyAlgorithmFromOID(pkAI.Algorithm) + var spk asn1.BitString + if !spki.ReadASN1BitString(&spk) { + return nil, errors.New("x509: malformed subjectPublicKey") + } + if cert.PublicKeyAlgorithm != stdx509.UnknownPublicKeyAlgorithm { + cert.PublicKey, err = parsePublicKey(&publicKeyInfo{ + Algorithm: pkAI, + PublicKey: spk, + }) + if err != nil { + return nil, err + } + } + + if cert.Version > 1 { + if !tbs.SkipOptionalASN1(cryptobyte_asn1.Tag(1).ContextSpecific()) { + return nil, errors.New("x509: malformed issuerUniqueID") + } + if !tbs.SkipOptionalASN1(cryptobyte_asn1.Tag(2).ContextSpecific()) { + return nil, errors.New("x509: malformed subjectUniqueID") + } + if cert.Version == 3 { + var extensions cryptobyte.String + var present bool + if !tbs.ReadOptionalASN1(&extensions, &present, cryptobyte_asn1.Tag(3).Constructed().ContextSpecific()) { + return nil, errors.New("x509: malformed extensions") + } + if present { + seenExts := make(map[string]bool) + if !extensions.ReadASN1(&extensions, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed extensions") + } + for !extensions.Empty() { + var extension cryptobyte.String + if !extensions.ReadASN1(&extension, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed extension") + } + ext, err := parseExtension(extension) + if err != nil { + return nil, err + } + oidStr := ext.Id.String() + if seenExts[oidStr] { + return nil, fmt.Errorf("x509: certificate contains duplicate extension with OID %q", oidStr) + } + seenExts[oidStr] = true + cert.Extensions = append(cert.Extensions, ext) + } + err = processExtensions(cert) + if err != nil { + return nil, err + } + } + } + } + + var signature asn1.BitString + if !input.ReadASN1BitString(&signature) { + return nil, errors.New("x509: malformed signature") + } + cert.Signature = signature.RightAlign() + + return cert, nil +} diff --git a/internal/legacy/x509/pkcs1.go b/internal/legacy/x509/pkcs1.go new file mode 100644 index 0000000..da3c38a --- /dev/null +++ b/internal/legacy/x509/pkcs1.go @@ -0,0 +1,15 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package legacyx509 + +import ( + "math/big" +) + +// pkcs1PublicKey reflects the ASN.1 structure of a PKCS #1 public key. +type pkcs1PublicKey struct { + N *big.Int + E int +} diff --git a/internal/legacy/x509/verify.go b/internal/legacy/x509/verify.go new file mode 100644 index 0000000..901e3ba --- /dev/null +++ b/internal/legacy/x509/verify.go @@ -0,0 +1,193 @@ +package legacyx509 + +import ( + "bytes" + "strings" +) + +// rfc2821Mailbox represents a “mailbox” (which is an email address to most +// people) by breaking it into the “local” (i.e. before the '@') and “domain” +// parts. +type rfc2821Mailbox struct { + local, domain string +} + +// parseRFC2821Mailbox parses an email address into local and domain parts, +// based on the ABNF for a “Mailbox” from RFC 2821. According to RFC 5280, +// Section 4.2.1.6 that's correct for an rfc822Name from a certificate: “The +// format of an rfc822Name is a "Mailbox" as defined in RFC 2821, Section 4.1.2”. +func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) { + if len(in) == 0 { + return mailbox, false + } + + localPartBytes := make([]byte, 0, len(in)/2) + + if in[0] == '"' { + // Quoted-string = DQUOTE *qcontent DQUOTE + // non-whitespace-control = %d1-8 / %d11 / %d12 / %d14-31 / %d127 + // qcontent = qtext / quoted-pair + // qtext = non-whitespace-control / + // %d33 / %d35-91 / %d93-126 + // quoted-pair = ("\" text) / obs-qp + // text = %d1-9 / %d11 / %d12 / %d14-127 / obs-text + // + // (Names beginning with “obs-” are the obsolete syntax from RFC 2822, + // Section 4. Since it has been 16 years, we no longer accept that.) + in = in[1:] + QuotedString: + for { + if len(in) == 0 { + return mailbox, false + } + c := in[0] + in = in[1:] + + switch { + case c == '"': + break QuotedString + + case c == '\\': + // quoted-pair + if len(in) == 0 { + return mailbox, false + } + if in[0] == 11 || + in[0] == 12 || + (1 <= in[0] && in[0] <= 9) || + (14 <= in[0] && in[0] <= 127) { + localPartBytes = append(localPartBytes, in[0]) + in = in[1:] + } else { + return mailbox, false + } + + case c == 11 || + c == 12 || + // Space (char 32) is not allowed based on the + // BNF, but RFC 3696 gives an example that + // assumes that it is. Several “verified” + // errata continue to argue about this point. + // We choose to accept it. + c == 32 || + c == 33 || + c == 127 || + (1 <= c && c <= 8) || + (14 <= c && c <= 31) || + (35 <= c && c <= 91) || + (93 <= c && c <= 126): + // qtext + localPartBytes = append(localPartBytes, c) + + default: + return mailbox, false + } + } + } else { + // Atom ("." Atom)* + NextChar: + for len(in) > 0 { + // atext from RFC 2822, Section 3.2.4 + c := in[0] + + switch { + case c == '\\': + // Examples given in RFC 3696 suggest that + // escaped characters can appear outside of a + // quoted string. Several “verified” errata + // continue to argue the point. We choose to + // accept it. + in = in[1:] + if len(in) == 0 { + return mailbox, false + } + fallthrough + + case ('0' <= c && c <= '9') || + ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + c == '!' || c == '#' || c == '$' || c == '%' || + c == '&' || c == '\'' || c == '*' || c == '+' || + c == '-' || c == '/' || c == '=' || c == '?' || + c == '^' || c == '_' || c == '`' || c == '{' || + c == '|' || c == '}' || c == '~' || c == '.': + localPartBytes = append(localPartBytes, in[0]) + in = in[1:] + + default: + break NextChar + } + } + + if len(localPartBytes) == 0 { + return mailbox, false + } + + // From RFC 3696, Section 3: + // “period (".") may also appear, but may not be used to start + // or end the local part, nor may two or more consecutive + // periods appear.” + twoDots := []byte{'.', '.'} + if localPartBytes[0] == '.' || + localPartBytes[len(localPartBytes)-1] == '.' || + bytes.Contains(localPartBytes, twoDots) { + return mailbox, false + } + } + + if len(in) == 0 || in[0] != '@' { + return mailbox, false + } + in = in[1:] + + // The RFC species a format for domains, but that's known to be + // violated in practice so we accept that anything after an '@' is the + // domain part. + if _, ok := domainToReverseLabels(in); !ok { + return mailbox, false + } + + mailbox.local = string(localPartBytes) + mailbox.domain = in + return mailbox, true +} + +// domainToReverseLabels converts a textual domain name like foo.example.com to +// the list of labels in reverse order, e.g. ["com", "example", "foo"]. +func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { + for len(domain) > 0 { + if i := strings.LastIndexByte(domain, '.'); i == -1 { + reverseLabels = append(reverseLabels, domain) + domain = "" + } else { + reverseLabels = append(reverseLabels, domain[i+1:]) + domain = domain[:i] + if i == 0 { // domain == "" + // domain is prefixed with an empty label, append an empty + // string to reverseLabels to indicate this. + reverseLabels = append(reverseLabels, "") + } + } + } + + if len(reverseLabels) > 0 && len(reverseLabels[0]) == 0 { + // An empty label at the end indicates an absolute value. + return nil, false + } + + for _, label := range reverseLabels { + if len(label) == 0 { + // Empty labels are otherwise invalid. + return nil, false + } + + for _, c := range label { + if c < 33 || c > 126 { + // Invalid character. + return nil, false + } + } + } + + return reverseLabels, true +} diff --git a/internal/legacy/x509/x509.go b/internal/legacy/x509/x509.go new file mode 100644 index 0000000..a4500bf --- /dev/null +++ b/internal/legacy/x509/x509.go @@ -0,0 +1,488 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package x509 implements a subset of the X.509 standard. +// +// It allows parsing and generating certificates, certificate signing +// requests, certificate revocation lists, and encoded public and private keys. +// It provides a certificate verifier, complete with a chain builder. +// +// The package targets the X.509 technical profile defined by the IETF (RFC +// 2459/3280/5280), and as further restricted by the CA/Browser Forum Baseline +// Requirements. There is minimal support for features outside of these +// profiles, as the primary goal of the package is to provide compatibility +// with the publicly trusted TLS certificate ecosystem and its policies and +// constraints. +// +// On macOS and Windows, certificate verification is handled by system APIs, but +// the package aims to apply consistent validation rules across operating +// systems. +package legacyx509 + +import ( + "bytes" + "crypto" + "crypto/elliptic" + stdx509 "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "fmt" + "strconv" + "unicode" + + // Explicitly import these for their crypto.RegisterHash init side-effects. + // Keep these as blank imports, even if they're imported above. + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" +) + +type publicKeyInfo struct { + Raw asn1.RawContent + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString +} + +type SignatureAlgorithm int + +const ( + UnknownSignatureAlgorithm SignatureAlgorithm = iota + + MD2WithRSA // Unsupported. + MD5WithRSA // Only supported for signing, not verification. + SHA1WithRSA // Only supported for signing, and verification of CRLs, CSRs, and OCSP responses. + SHA256WithRSA + SHA384WithRSA + SHA512WithRSA + DSAWithSHA1 // Unsupported. + DSAWithSHA256 // Unsupported. + ECDSAWithSHA1 // Only supported for signing, and verification of CRLs, CSRs, and OCSP responses. + ECDSAWithSHA256 + ECDSAWithSHA384 + ECDSAWithSHA512 + SHA256WithRSAPSS + SHA384WithRSAPSS + SHA512WithRSAPSS + PureEd25519 +) + +func (algo SignatureAlgorithm) String() string { + for _, details := range signatureAlgorithmDetails { + if details.algo == algo { + return details.name + } + } + return strconv.Itoa(int(algo)) +} + +type PublicKeyAlgorithm int + +const ( + UnknownPublicKeyAlgorithm PublicKeyAlgorithm = iota + RSA + DSA // Only supported for parsing. + ECDSA + Ed25519 +) + +var publicKeyAlgoName = [...]string{ + RSA: "RSA", + DSA: "DSA", + ECDSA: "ECDSA", + Ed25519: "Ed25519", +} + +func (algo PublicKeyAlgorithm) String() string { + if 0 < algo && int(algo) < len(publicKeyAlgoName) { + return publicKeyAlgoName[algo] + } + return strconv.Itoa(int(algo)) +} + +// OIDs for signature algorithms +// +// pkcs-1 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 } +// +// RFC 3279 2.2.1 RSA Signature Algorithms +// +// md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 } +// +// sha-1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 } +// +// dsaWithSha1 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) x9-57(10040) x9cm(4) 3 } +// +// RFC 3279 2.2.3 ECDSA Signature Algorithm +// +// ecdsa-with-SHA1 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) ansi-x962(10045) +// signatures(4) ecdsa-with-SHA1(1)} +// +// RFC 4055 5 PKCS #1 Version 1.5 +// +// sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 } +// +// sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 } +// +// sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 } +// +// RFC 5758 3.1 DSA Signature Algorithms +// +// dsaWithSha256 OBJECT IDENTIFIER ::= { +// joint-iso-ccitt(2) country(16) us(840) organization(1) gov(101) +// csor(3) algorithms(4) id-dsa-with-sha2(3) 2} +// +// RFC 5758 3.2 ECDSA Signature Algorithm +// +// ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2) +// us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 } +// +// ecdsa-with-SHA384 OBJECT IDENTIFIER ::= { iso(1) member-body(2) +// us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 3 } +// +// ecdsa-with-SHA512 OBJECT IDENTIFIER ::= { iso(1) member-body(2) +// us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 4 } +// +// RFC 8410 3 Curve25519 and Curve448 Algorithm Identifiers +// +// id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 } +var ( + oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4} + oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} + oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} + oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} + oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} + oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} + oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} + oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2} + oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} + oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} + oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} + oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} + oidSignatureEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112} + + 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} + + oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8} + + // oidISOSignatureSHA1WithRSA means the same as oidSignatureSHA1WithRSA + // but it's specified by ISO. Microsoft's makecert.exe has been known + // to produce certificates with this OID. + oidISOSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 29} +) + +var signatureAlgorithmDetails = []struct { + algo SignatureAlgorithm + name string + oid asn1.ObjectIdentifier + params asn1.RawValue + pubKeyAlgo PublicKeyAlgorithm + hash crypto.Hash + isRSAPSS bool +}{ + {MD5WithRSA, "MD5-RSA", oidSignatureMD5WithRSA, asn1.NullRawValue, RSA, crypto.MD5, false}, + {SHA1WithRSA, "SHA1-RSA", oidSignatureSHA1WithRSA, asn1.NullRawValue, RSA, crypto.SHA1, false}, + {SHA1WithRSA, "SHA1-RSA", oidISOSignatureSHA1WithRSA, asn1.NullRawValue, RSA, crypto.SHA1, false}, + {SHA256WithRSA, "SHA256-RSA", oidSignatureSHA256WithRSA, asn1.NullRawValue, RSA, crypto.SHA256, false}, + {SHA384WithRSA, "SHA384-RSA", oidSignatureSHA384WithRSA, asn1.NullRawValue, RSA, crypto.SHA384, false}, + {SHA512WithRSA, "SHA512-RSA", oidSignatureSHA512WithRSA, asn1.NullRawValue, RSA, crypto.SHA512, false}, + {SHA256WithRSAPSS, "SHA256-RSAPSS", oidSignatureRSAPSS, pssParametersSHA256, RSA, crypto.SHA256, true}, + {SHA384WithRSAPSS, "SHA384-RSAPSS", oidSignatureRSAPSS, pssParametersSHA384, RSA, crypto.SHA384, true}, + {SHA512WithRSAPSS, "SHA512-RSAPSS", oidSignatureRSAPSS, pssParametersSHA512, RSA, crypto.SHA512, true}, + {DSAWithSHA1, "DSA-SHA1", oidSignatureDSAWithSHA1, emptyRawValue, DSA, crypto.SHA1, false}, + {DSAWithSHA256, "DSA-SHA256", oidSignatureDSAWithSHA256, emptyRawValue, DSA, crypto.SHA256, false}, + {ECDSAWithSHA1, "ECDSA-SHA1", oidSignatureECDSAWithSHA1, emptyRawValue, ECDSA, crypto.SHA1, false}, + {ECDSAWithSHA256, "ECDSA-SHA256", oidSignatureECDSAWithSHA256, emptyRawValue, ECDSA, crypto.SHA256, false}, + {ECDSAWithSHA384, "ECDSA-SHA384", oidSignatureECDSAWithSHA384, emptyRawValue, ECDSA, crypto.SHA384, false}, + {ECDSAWithSHA512, "ECDSA-SHA512", oidSignatureECDSAWithSHA512, emptyRawValue, ECDSA, crypto.SHA512, false}, + {PureEd25519, "Ed25519", oidSignatureEd25519, emptyRawValue, Ed25519, crypto.Hash(0) /* no pre-hashing */, false}, +} + +var emptyRawValue = asn1.RawValue{} + +// DER encoded RSA PSS parameters for the +// SHA256, SHA384, and SHA512 hashes as defined in RFC 3447, Appendix A.2.3. +// The parameters contain the following values: +// - hashAlgorithm contains the associated hash identifier with NULL parameters +// - maskGenAlgorithm always contains the default mgf1SHA1 identifier +// - saltLength contains the length of the associated hash +// - trailerField always contains the default trailerFieldBC value +var ( + pssParametersSHA256 = asn1.RawValue{FullBytes: []byte{48, 52, 160, 15, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 5, 0, 161, 28, 48, 26, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 8, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 5, 0, 162, 3, 2, 1, 32}} + pssParametersSHA384 = asn1.RawValue{FullBytes: []byte{48, 52, 160, 15, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 2, 5, 0, 161, 28, 48, 26, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 8, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 2, 5, 0, 162, 3, 2, 1, 48}} + pssParametersSHA512 = asn1.RawValue{FullBytes: []byte{48, 52, 160, 15, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 3, 5, 0, 161, 28, 48, 26, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 8, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 3, 5, 0, 162, 3, 2, 1, 64}} +) + +// pssParameters reflects the parameters in an AlgorithmIdentifier that +// specifies RSA PSS. See RFC 3447, Appendix A.2.3. +type pssParameters struct { + // The following three fields are not marked as + // optional because the default values specify SHA-1, + // which is no longer suitable for use in signatures. + Hash pkix.AlgorithmIdentifier `asn1:"explicit,tag:0"` + MGF pkix.AlgorithmIdentifier `asn1:"explicit,tag:1"` + SaltLength int `asn1:"explicit,tag:2"` + TrailerField int `asn1:"optional,explicit,tag:3,default:1"` +} + +func getSignatureAlgorithmFromAI(ai pkix.AlgorithmIdentifier) stdx509.SignatureAlgorithm { + if ai.Algorithm.Equal(oidSignatureEd25519) { + // RFC 8410, Section 3 + // > For all of the OIDs, the parameters MUST be absent. + if len(ai.Parameters.FullBytes) != 0 { + return stdx509.UnknownSignatureAlgorithm + } + } + + if !ai.Algorithm.Equal(oidSignatureRSAPSS) { + for _, details := range signatureAlgorithmDetails { + if ai.Algorithm.Equal(details.oid) { + return stdx509.SignatureAlgorithm(details.algo) + } + } + return stdx509.UnknownSignatureAlgorithm + } + + // RSA PSS is special because it encodes important parameters + // in the Parameters. + + var params pssParameters + if _, err := asn1.Unmarshal(ai.Parameters.FullBytes, ¶ms); err != nil { + return stdx509.UnknownSignatureAlgorithm + } + + var mgf1HashFunc pkix.AlgorithmIdentifier + if _, err := asn1.Unmarshal(params.MGF.Parameters.FullBytes, &mgf1HashFunc); err != nil { + return stdx509.UnknownSignatureAlgorithm + } + + // PSS is greatly overburdened with options. This code forces them into + // three buckets by requiring that the MGF1 hash function always match the + // message hash function (as recommended in RFC 3447, Section 8.1), that the + // salt length matches the hash length, and that the trailer field has the + // default value. + if (len(params.Hash.Parameters.FullBytes) != 0 && !bytes.Equal(params.Hash.Parameters.FullBytes, asn1.NullBytes)) || + !params.MGF.Algorithm.Equal(oidMGF1) || + !mgf1HashFunc.Algorithm.Equal(params.Hash.Algorithm) || + (len(mgf1HashFunc.Parameters.FullBytes) != 0 && !bytes.Equal(mgf1HashFunc.Parameters.FullBytes, asn1.NullBytes)) || + params.TrailerField != 1 { + return stdx509.UnknownSignatureAlgorithm + } + + switch { + case params.Hash.Algorithm.Equal(oidSHA256) && params.SaltLength == 32: + return stdx509.SHA256WithRSAPSS + case params.Hash.Algorithm.Equal(oidSHA384) && params.SaltLength == 48: + return stdx509.SHA384WithRSAPSS + case params.Hash.Algorithm.Equal(oidSHA512) && params.SaltLength == 64: + return stdx509.SHA512WithRSAPSS + } + + return stdx509.UnknownSignatureAlgorithm +} + +var ( + // RFC 3279, 2.3 Public Key Algorithms + // + // pkcs-1 OBJECT IDENTIFIER ::== { iso(1) member-body(2) us(840) + // rsadsi(113549) pkcs(1) 1 } + // + // rsaEncryption OBJECT IDENTIFIER ::== { pkcs1-1 1 } + // + // id-dsa OBJECT IDENTIFIER ::== { iso(1) member-body(2) us(840) + // x9-57(10040) x9cm(4) 1 } + oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + oidPublicKeyDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1} + // RFC 5480, 2.1.1 Unrestricted Algorithm Identifier and Parameters + // + // id-ecPublicKey OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 } + oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} + // RFC 8410, Section 3 + // + // id-X25519 OBJECT IDENTIFIER ::= { 1 3 101 110 } + // id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 } + oidPublicKeyX25519 = asn1.ObjectIdentifier{1, 3, 101, 110} + oidPublicKeyEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112} +) + +// getPublicKeyAlgorithmFromOID returns the exposed PublicKeyAlgorithm +// identifier for public key types supported in certificates and CSRs. Marshal +// and Parse functions may support a different set of public key types. +func getPublicKeyAlgorithmFromOID(oid asn1.ObjectIdentifier) stdx509.PublicKeyAlgorithm { + switch { + case oid.Equal(oidPublicKeyRSA): + return stdx509.RSA + case oid.Equal(oidPublicKeyDSA): + return stdx509.DSA + case oid.Equal(oidPublicKeyECDSA): + return stdx509.ECDSA + case oid.Equal(oidPublicKeyEd25519): + return stdx509.Ed25519 + } + return stdx509.UnknownPublicKeyAlgorithm +} + +// RFC 5480, 2.1.1.1. Named Curve +// +// secp224r1 OBJECT IDENTIFIER ::= { +// iso(1) identified-organization(3) certicom(132) curve(0) 33 } +// +// secp256r1 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) +// prime(1) 7 } +// +// secp384r1 OBJECT IDENTIFIER ::= { +// iso(1) identified-organization(3) certicom(132) curve(0) 34 } +// +// secp521r1 OBJECT IDENTIFIER ::= { +// iso(1) identified-organization(3) certicom(132) curve(0) 35 } +// +// NB: secp256r1 is equivalent to prime256v1 +var ( + oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33} + oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} + oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} + oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} +) + +func namedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve { + switch { + case oid.Equal(oidNamedCurveP224): + return elliptic.P224() + case oid.Equal(oidNamedCurveP256): + return elliptic.P256() + case oid.Equal(oidNamedCurveP384): + return elliptic.P384() + case oid.Equal(oidNamedCurveP521): + return elliptic.P521() + } + return nil +} + +// KeyUsage represents the set of actions that are valid for a given key. It's +// a bitmap of the KeyUsage* constants. +type KeyUsage int + +const ( + KeyUsageDigitalSignature KeyUsage = 1 << iota + KeyUsageContentCommitment + KeyUsageKeyEncipherment + KeyUsageDataEncipherment + KeyUsageKeyAgreement + KeyUsageCertSign + KeyUsageCRLSign + KeyUsageEncipherOnly + KeyUsageDecipherOnly +) + +// RFC 5280, 4.2.1.12 Extended Key Usage +// +// anyExtendedKeyUsage OBJECT IDENTIFIER ::= { id-ce-extKeyUsage 0 } +// +// id-kp OBJECT IDENTIFIER ::= { id-pkix 3 } +// +// id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 } +// id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 } +// id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 } +// id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 } +// id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 } +// id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 } +var ( + oidExtKeyUsageAny = asn1.ObjectIdentifier{2, 5, 29, 37, 0} + oidExtKeyUsageServerAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 1} + oidExtKeyUsageClientAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 2} + oidExtKeyUsageCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 3} + oidExtKeyUsageEmailProtection = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 4} + oidExtKeyUsageIPSECEndSystem = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 5} + oidExtKeyUsageIPSECTunnel = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 6} + oidExtKeyUsageIPSECUser = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 7} + oidExtKeyUsageTimeStamping = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 8} + oidExtKeyUsageOCSPSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 9} + oidExtKeyUsageMicrosoftServerGatedCrypto = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 10, 3, 3} + oidExtKeyUsageNetscapeServerGatedCrypto = asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 4, 1} + oidExtKeyUsageMicrosoftCommercialCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 2, 1, 22} + oidExtKeyUsageMicrosoftKernelCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 61, 1, 1} +) + +// ExtKeyUsage represents an extended set of actions that are valid for a given key. +// Each of the ExtKeyUsage* constants define a unique action. +type ExtKeyUsage int + +const ( + ExtKeyUsageAny ExtKeyUsage = iota + ExtKeyUsageServerAuth + ExtKeyUsageClientAuth + ExtKeyUsageCodeSigning + ExtKeyUsageEmailProtection + ExtKeyUsageIPSECEndSystem + ExtKeyUsageIPSECTunnel + ExtKeyUsageIPSECUser + ExtKeyUsageTimeStamping + ExtKeyUsageOCSPSigning + ExtKeyUsageMicrosoftServerGatedCrypto + ExtKeyUsageNetscapeServerGatedCrypto + ExtKeyUsageMicrosoftCommercialCodeSigning + ExtKeyUsageMicrosoftKernelCodeSigning +) + +// extKeyUsageOIDs contains the mapping between an ExtKeyUsage and its OID. +var extKeyUsageOIDs = []struct { + extKeyUsage ExtKeyUsage + oid asn1.ObjectIdentifier +}{ + {ExtKeyUsageAny, oidExtKeyUsageAny}, + {ExtKeyUsageServerAuth, oidExtKeyUsageServerAuth}, + {ExtKeyUsageClientAuth, oidExtKeyUsageClientAuth}, + {ExtKeyUsageCodeSigning, oidExtKeyUsageCodeSigning}, + {ExtKeyUsageEmailProtection, oidExtKeyUsageEmailProtection}, + {ExtKeyUsageIPSECEndSystem, oidExtKeyUsageIPSECEndSystem}, + {ExtKeyUsageIPSECTunnel, oidExtKeyUsageIPSECTunnel}, + {ExtKeyUsageIPSECUser, oidExtKeyUsageIPSECUser}, + {ExtKeyUsageTimeStamping, oidExtKeyUsageTimeStamping}, + {ExtKeyUsageOCSPSigning, oidExtKeyUsageOCSPSigning}, + {ExtKeyUsageMicrosoftServerGatedCrypto, oidExtKeyUsageMicrosoftServerGatedCrypto}, + {ExtKeyUsageNetscapeServerGatedCrypto, oidExtKeyUsageNetscapeServerGatedCrypto}, + {ExtKeyUsageMicrosoftCommercialCodeSigning, oidExtKeyUsageMicrosoftCommercialCodeSigning}, + {ExtKeyUsageMicrosoftKernelCodeSigning, oidExtKeyUsageMicrosoftKernelCodeSigning}, +} + +func extKeyUsageFromOID(oid asn1.ObjectIdentifier) (eku ExtKeyUsage, ok bool) { + for _, pair := range extKeyUsageOIDs { + if oid.Equal(pair.oid) { + return pair.extKeyUsage, true + } + } + return +} + +const ( + nameTypeEmail = 1 + nameTypeDNS = 2 + nameTypeURI = 6 + nameTypeIP = 7 +) + +var ( + oidExtensionAuthorityInfoAccess = []int{1, 3, 6, 1, 5, 5, 7, 1, 1} +) + +var ( + oidAuthorityInfoAccessOcsp = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1} + oidAuthorityInfoAccessIssuers = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 2} +) + +func isIA5String(s string) error { + for _, r := range s { + // Per RFC5280 "IA5String is limited to the set of ASCII characters" + if r > unicode.MaxASCII { + return fmt.Errorf("x509: %q cannot be encoded as an IA5String", s) + } + } + + return nil +} diff --git a/parse_go1.23_test.go b/parse_go1.23_test.go new file mode 100644 index 0000000..543ffe8 --- /dev/null +++ b/parse_go1.23_test.go @@ -0,0 +1,47 @@ +//go:build go1.23 +// +build go1.23 + +package pkcs7 + +import ( + "encoding/base64" + "testing" +) + +func TestParseWindowsSCEPCertificateRequest(t *testing.T) { + // base64 encoded SCEP requests from a Windows device + b64 := `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` + data, err := base64.StdEncoding.DecodeString(b64) + if err != nil { + t.Errorf("failed decoding base64 SCEP request data: %v", err) + } + + p7, err := Parse(data) + if err == nil { + t.Errorf("expected error while parsing SCEP request data") + } + if err.Error() != "x509: authority key identifier incorrectly marked critical" { + t.Errorf(`expected "x509: authority key identifier incorrectly marked critical"; got "%s"`, err.Error()) + } + if p7 != nil { + t.Errorf("expected p7 to be nil; got %v", p7) + } + + // enable the legacy parser when Go 1.23 or newer is used, and parse it again + SetFallbackLegacyX509CertificateParserEnabled(true) + t.Cleanup(func() { + SetFallbackLegacyX509CertificateParserEnabled(false) + }) + p7, err = Parse(data) + if err != nil { + t.Errorf("failed parsing SCEP request data with legacy X509 certificate parser enabled: %v", err) + } + + if len(p7.Certificates) != 1 { + t.Errorf("expected a single certificate; got %d", len(p7.Certificates)) + } + + if cn := p7.Certificates[0].Subject.CommonName; cn != "SCEP Protocol Certificate" { + t.Errorf("expected certificate subject common name to be SCEP Protocol Certificate; got %v", cn) + } +} diff --git a/parse_test.go b/parse_test.go new file mode 100644 index 0000000..af732d3 --- /dev/null +++ b/parse_test.go @@ -0,0 +1,31 @@ +//go:build !go1.23 +// +build !go1.23 + +package pkcs7 + +import ( + "encoding/base64" + "testing" +) + +func TestParseWindowsSCEPCertificateRequest(t *testing.T) { + // base64 encoded SCEP requests from a Windows device + b64 := `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` + data, err := base64.StdEncoding.DecodeString(b64) + if err != nil { + t.Errorf("failed decoding base64 SCEP request data: %v", err) + } + + p7, err := Parse(data) + if err != nil { + t.Errorf("failed parsing SCEP request data: %v", err) + } + + if len(p7.Certificates) != 1 { + t.Errorf("expected a single certificate; got %d", len(p7.Certificates)) + } + + if cn := p7.Certificates[0].Subject.CommonName; cn != "SCEP Protocol Certificate" { + t.Errorf("expected certificate subject common name to be SCEP Protocol Certificate; got %v", cn) + } +} diff --git a/pkcs7.go b/pkcs7.go index 7a7598d..f6c6dfb 100644 --- a/pkcs7.go +++ b/pkcs7.go @@ -13,8 +13,11 @@ import ( "errors" "fmt" "sort" + "sync" _ "crypto/sha1" // for crypto.SHA1 + + legacyx509 "github.com/smallstep/pkcs7/internal/legacy/x509" ) // PKCS7 Represents a PKCS7 structure @@ -213,6 +216,40 @@ func parseEncryptedData(data []byte) (*PKCS7, error) { }, nil } +// SetFallbackLegacyX509CertificateParserEnabled enables parsing certificates +// embedded in a PKCS7 message using the logic from crypto/x509 from before +// Go 1.23. Go 1.23 introduced a breaking change in case a certificate contains +// a critical authority key identifier, which is the correct thing to do based +// on RFC 5280, but it breaks Windows devices performing the Simple Certificate +// Enrolment Protocol (SCEP), as the certificates embedded in those requests +// apparently have authority key identifier extensions marked critical. +// +// See https://go-review.googlesource.com/c/go/+/562341 for the change in the +// Go source. +// +// When [SetFallbackLegacyX509CertificateParserEnabled] is called with true, it +// enables parsing using the legacy crypto/x509 certificate parser. It'll first +// try to parse the certificates using the regular Go crypto/x509 package, but +// if it fails on the above case, it'll retry parsing the certificates using a +// copy of the crypto/x509 package based on Go 1.23, but skips checking the +// authority key identifier extension being critical or not. +func SetFallbackLegacyX509CertificateParserEnabled(v bool) { + legacyX509CertificateParser.Lock() + legacyX509CertificateParser.enabled = v + legacyX509CertificateParser.Unlock() +} + +var legacyX509CertificateParser struct { + sync.RWMutex + enabled bool +} + +func isLegacyX509ParserEnabled() bool { + legacyX509CertificateParser.RLock() + defer legacyX509CertificateParser.RUnlock() + return legacyX509CertificateParser.enabled +} + func (raw rawCertificates) Parse() ([]*x509.Certificate, error) { if len(raw.Raw) == 0 { return nil, nil @@ -223,7 +260,14 @@ func (raw rawCertificates) Parse() ([]*x509.Certificate, error) { return nil, err } - return x509.ParseCertificates(val.Bytes) + certificates, err := x509.ParseCertificates(val.Bytes) + if err != nil && err.Error() == "x509: authority key identifier incorrectly marked critical" { + if isLegacyX509ParserEnabled() { + certificates, err = legacyx509.ParseCertificates(val.Bytes) + } + } + + return certificates, err } func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool {