diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 86f44fa536..b3ccc51ce3 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -68,6 +68,10 @@ "Comment": "2.2.0-4-gff4f55a", "Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46" }, + { + "ImportPath": "github.com/fullsailor/pkcs7", + "Rev": "a009d8d7de53d9503c797cb8ec66fa3b21eed209" + }, { "ImportPath": "github.com/ghodss/yaml", "Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee" @@ -287,6 +291,11 @@ "ImportPath": "github.com/spf13/pflag", "Rev": "9ff6c6923cfffbcd502984b8e0c80539a94968b7" }, + { + "ImportPath": "github.com/zakjan/cert-chain-resolver/certUtil", + "Comment": "1.0.1-2-g176864a", + "Rev": "176864ad3d2959f437789ea0a2cce95c92d0dc50" + } { "ImportPath": "golang.org/x/crypto/nacl/secretbox", "Rev": "81e90905daefcd6fd217b62423c0908922eadb30" diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index 5d87e36331..76351a9f33 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -621,6 +621,11 @@ stream { # PEM sha: {{ $server.SSLPemChecksum }} ssl_certificate {{ $server.SSLCertificate }}; ssl_certificate_key {{ $server.SSLCertificate }}; + {{ if not (empty $server.SSLFullChainCertificate)}} + ssl_trusted_certificate {{ $server.SSLFullChainCertificate }}; + ssl_stapling on; + ssl_stapling_verify on; + {{ end }} {{ end }} {{ if (and (not (empty $server.SSLCertificate)) $all.Cfg.HSTS) }} diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index 0ec872e3f9..cf4876056e 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -1056,6 +1056,7 @@ func (ic *GenericController) createServers(data []*extensions.Ingress, } servers[host].SSLCertificate = cert.PemFileName + servers[host].SSLFullChainCertificate = cert.FullChainPemFileName servers[host].SSLPemChecksum = cert.PemSHA servers[host].SSLExpireTime = cert.ExpireTime diff --git a/core/pkg/ingress/sort_ingress.go b/core/pkg/ingress/sort_ingress.go index 6d13f861b0..d5b112283e 100644 --- a/core/pkg/ingress/sort_ingress.go +++ b/core/pkg/ingress/sort_ingress.go @@ -32,6 +32,9 @@ type SSLCert struct { CAFileName string `json:"caFileName"` // PemFileName contains the path to the file with the certificate and key concatenated PemFileName string `json:"pemFileName"` + // FullChainPemFileName contains the path to the file with the certificate and key concatenated + // This certificate contains the full chain (ca + intermediates + cert) + FullChainPemFileName string `json:"fullChainPemFileName"` // PemSHA contains the sha1 of the pem file. // This is used to detect changes in the secret that contains the certificates PemSHA string `json:"pemSha"` diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index e3a7ab34da..da6d86386a 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -220,6 +220,9 @@ type Server struct { SSLPassthrough bool `json:"sslPassthrough"` // SSLCertificate path to the SSL certificate on disk SSLCertificate string `json:"sslCertificate"` + // SSLFullChainCertificate path to the SSL certificate on disk + // This certificate contains the full chain (ca + intermediates + cert) + SSLFullChainCertificate string `json:"sslFullChainCertificate"` // SSLExpireTime has the expire date of this certificate SSLExpireTime time.Time `json:"sslExpireTime"` // SSLPemChecksum returns the checksum of the certificate file on disk. diff --git a/core/pkg/net/ssl/ssl.go b/core/pkg/net/ssl/ssl.go index 535c5f5fe7..799cb835c4 100644 --- a/core/pkg/net/ssl/ssl.go +++ b/core/pkg/net/ssl/ssl.go @@ -34,6 +34,7 @@ import ( "time" "github.com/golang/glog" + "github.com/zakjan/cert-chain-resolver/certUtil" "k8s.io/apimachinery/pkg/util/sets" @@ -49,6 +50,7 @@ var ( func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, error) { pemName := fmt.Sprintf("%v.pem", name) pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName) + fullChainPemFileName := fmt.Sprintf("%v/%v-full-chain.pem", ingress.DefaultSSLDirectory, name) tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName) @@ -170,13 +172,23 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, }, nil } - return &ingress.SSLCert{ + s := &ingress.SSLCert{ Certificate: pemCert, PemFileName: pemFileName, PemSHA: file.SHA1(pemFileName), CN: cn.List(), ExpireTime: pemCert.NotAfter, - }, nil + } + + err = fullChainCert(pemFileName, fullChainPemFileName) + if err != nil { + glog.Errorf("unexpected error generating SSL certificate with full chain: %v", err) + return s, nil + } + + s.FullChainPemFileName = fullChainPemFileName + + return s, nil } func getExtension(c *x509.Certificate, id asn1.ObjectIdentifier) []pkix.Extension { @@ -376,3 +388,33 @@ func GetFakeSSLCert() ([]byte, []byte) { return cert, key } + +func fullChainCert(in, out string) error { + inputFile, err := os.Open(in) + if err != nil { + return err + } + + data, err := ioutil.ReadAll(inputFile) + if err != nil { + return err + } + + cert, err := certUtil.DecodeCertificate(data) + if err != nil { + return err + } + + certs, err := certUtil.FetchCertificateChain(cert) + if err != nil { + return err + } + + certs, err = certUtil.AddRootCA(certs) + if err != nil { + return err + } + + data = certUtil.EncodeCertificates(certs) + return ioutil.WriteFile(out, data, 0644) +} diff --git a/vendor/github.com/fullsailor/pkcs7/.gitignore b/vendor/github.com/fullsailor/pkcs7/.gitignore new file mode 100644 index 0000000000..daf913b1b3 --- /dev/null +++ b/vendor/github.com/fullsailor/pkcs7/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/fullsailor/pkcs7/.travis.yml b/vendor/github.com/fullsailor/pkcs7/.travis.yml new file mode 100644 index 0000000000..c7154e46c3 --- /dev/null +++ b/vendor/github.com/fullsailor/pkcs7/.travis.yml @@ -0,0 +1,6 @@ +language: go + +go: + - 1.6 + - 1.7 + - tip diff --git a/vendor/github.com/fullsailor/pkcs7/LICENSE b/vendor/github.com/fullsailor/pkcs7/LICENSE new file mode 100644 index 0000000000..75f3209085 --- /dev/null +++ b/vendor/github.com/fullsailor/pkcs7/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Andrew Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/fullsailor/pkcs7/README.md b/vendor/github.com/fullsailor/pkcs7/README.md new file mode 100644 index 0000000000..bfd948f321 --- /dev/null +++ b/vendor/github.com/fullsailor/pkcs7/README.md @@ -0,0 +1,8 @@ +# pkcs7 + +[![GoDoc](https://godoc.org/github.com/fullsailor/pkcs7?status.svg)](https://godoc.org/github.com/fullsailor/pkcs7) +[![Build Status](https://travis-ci.org/fullsailor/pkcs7.svg?branch=master)](https://travis-ci.org/fullsailor/pkcs7) + +pkcs7 implements parsing and creating signed and enveloped messages. + +- Documentation on [GoDoc](http://godoc.org/github.com/fullsailor/pkcs7) diff --git a/vendor/github.com/fullsailor/pkcs7/ber.go b/vendor/github.com/fullsailor/pkcs7/ber.go new file mode 100644 index 0000000000..bf3e804296 --- /dev/null +++ b/vendor/github.com/fullsailor/pkcs7/ber.go @@ -0,0 +1,248 @@ +package pkcs7 + +import ( + "bytes" + "errors" +) + +var encodeIndent = 0 + +type asn1Object interface { + EncodeTo(writer *bytes.Buffer) error +} + +type asn1Structured struct { + tagBytes []byte + content []asn1Object +} + +func (s asn1Structured) EncodeTo(out *bytes.Buffer) error { + //fmt.Printf("%s--> tag: % X\n", strings.Repeat("| ", encodeIndent), s.tagBytes) + encodeIndent++ + inner := new(bytes.Buffer) + for _, obj := range s.content { + err := obj.EncodeTo(inner) + if err != nil { + return err + } + } + encodeIndent-- + out.Write(s.tagBytes) + encodeLength(out, inner.Len()) + out.Write(inner.Bytes()) + return nil +} + +type asn1Primitive struct { + tagBytes []byte + length int + content []byte +} + +func (p asn1Primitive) EncodeTo(out *bytes.Buffer) error { + _, err := out.Write(p.tagBytes) + if err != nil { + return err + } + if err = encodeLength(out, p.length); err != nil { + return err + } + //fmt.Printf("%s--> tag: % X length: %d\n", strings.Repeat("| ", encodeIndent), p.tagBytes, p.length) + //fmt.Printf("%s--> content length: %d\n", strings.Repeat("| ", encodeIndent), len(p.content)) + out.Write(p.content) + + return nil +} + +func ber2der(ber []byte) ([]byte, error) { + if len(ber) == 0 { + return nil, errors.New("ber2der: input ber is empty") + } + //fmt.Printf("--> ber2der: Transcoding %d bytes\n", len(ber)) + out := new(bytes.Buffer) + + obj, _, err := readObject(ber, 0) + if err != nil { + return nil, err + } + obj.EncodeTo(out) + + // if offset < len(ber) { + // return nil, fmt.Errorf("ber2der: Content longer than expected. Got %d, expected %d", offset, len(ber)) + //} + + return out.Bytes(), nil +} + +// encodes lengths that are longer than 127 into string of bytes +func marshalLongLength(out *bytes.Buffer, i int) (err error) { + n := lengthLength(i) + + for ; n > 0; n-- { + err = out.WriteByte(byte(i >> uint((n-1)*8))) + if err != nil { + return + } + } + + return nil +} + +// computes the byte length of an encoded length value +func lengthLength(i int) (numBytes int) { + numBytes = 1 + for i > 255 { + numBytes++ + i >>= 8 + } + return +} + +// encodes the length in DER format +// If the length fits in 7 bits, the value is encoded directly. +// +// Otherwise, the number of bytes to encode the length is first determined. +// This number is likely to be 4 or less for a 32bit length. This number is +// added to 0x80. The length is encoded in big endian encoding follow after +// +// Examples: +// length | byte 1 | bytes n +// 0 | 0x00 | - +// 120 | 0x78 | - +// 200 | 0x81 | 0xC8 +// 500 | 0x82 | 0x01 0xF4 +// +func encodeLength(out *bytes.Buffer, length int) (err error) { + if length >= 128 { + l := lengthLength(length) + err = out.WriteByte(0x80 | byte(l)) + if err != nil { + return + } + err = marshalLongLength(out, length) + if err != nil { + return + } + } else { + err = out.WriteByte(byte(length)) + if err != nil { + return + } + } + return +} + +func readObject(ber []byte, offset int) (asn1Object, int, error) { + //fmt.Printf("\n====> Starting readObject at offset: %d\n\n", offset) + tagStart := offset + b := ber[offset] + offset++ + tag := b & 0x1F // last 5 bits + if tag == 0x1F { + tag = 0 + for ber[offset] >= 0x80 { + tag = tag*128 + ber[offset] - 0x80 + offset++ + } + tag = tag*128 + ber[offset] - 0x80 + offset++ + } + tagEnd := offset + + kind := b & 0x20 + /* + if kind == 0 { + fmt.Print("--> Primitive\n") + } else { + fmt.Print("--> Constructed\n") + } + */ + // read length + var length int + l := ber[offset] + offset++ + indefinite := false + if l > 0x80 { + numberOfBytes := (int)(l & 0x7F) + if numberOfBytes > 4 { // int is only guaranteed to be 32bit + return nil, 0, errors.New("ber2der: BER tag length too long") + } + if numberOfBytes == 4 && (int)(ber[offset]) > 0x7F { + return nil, 0, errors.New("ber2der: BER tag length is negative") + } + if 0x0 == (int)(ber[offset]) { + return nil, 0, errors.New("ber2der: BER tag length has leading zero") + } + //fmt.Printf("--> (compute length) indicator byte: %x\n", l) + //fmt.Printf("--> (compute length) length bytes: % X\n", ber[offset:offset+numberOfBytes]) + for i := 0; i < numberOfBytes; i++ { + length = length*256 + (int)(ber[offset]) + offset++ + } + } else if l == 0x80 { + indefinite = true + } else { + length = (int)(l) + } + + //fmt.Printf("--> length : %d\n", length) + contentEnd := offset + length + if contentEnd > len(ber) { + return nil, 0, errors.New("ber2der: BER tag length is more than available data") + } + //fmt.Printf("--> content start : %d\n", offset) + //fmt.Printf("--> content end : %d\n", contentEnd) + //fmt.Printf("--> content : % X\n", ber[offset:contentEnd]) + var obj asn1Object + if indefinite && kind == 0 { + return nil, 0, errors.New("ber2der: Indefinite form tag must have constructed encoding") + } + if kind == 0 { + obj = asn1Primitive{ + tagBytes: ber[tagStart:tagEnd], + length: length, + content: ber[offset:contentEnd], + } + } else { + var subObjects []asn1Object + for (offset < contentEnd) || indefinite { + var subObj asn1Object + var err error + subObj, offset, err = readObject(ber, offset) + if err != nil { + return nil, 0, err + } + subObjects = append(subObjects, subObj) + + if indefinite { + terminated, err := isIndefiniteTermination(ber, offset) + if err != nil { + return nil, 0, err + } + + if terminated { + break + } + } + } + obj = asn1Structured{ + tagBytes: ber[tagStart:tagEnd], + content: subObjects, + } + } + + // Apply indefinite form length with 0x0000 terminator. + if indefinite { + contentEnd = offset + 2 + } + + return obj, contentEnd, nil +} + +func isIndefiniteTermination(ber []byte, offset int) (bool, error) { + if len(ber) - offset < 2 { + return false, errors.New("ber2der: Invalid BER format") + } + + return bytes.Index(ber[offset:], []byte{0x0, 0x0}) == 0, nil +} diff --git a/vendor/github.com/fullsailor/pkcs7/pkcs7.go b/vendor/github.com/fullsailor/pkcs7/pkcs7.go new file mode 100644 index 0000000000..8d5af853ad --- /dev/null +++ b/vendor/github.com/fullsailor/pkcs7/pkcs7.go @@ -0,0 +1,940 @@ +// Package pkcs7 implements parsing and generation of some PKCS#7 structures. +package pkcs7 + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "math/big" + "sort" + "time" + + _ "crypto/sha1" // for crypto.SHA1 +) + +// PKCS7 Represents a PKCS7 structure +type PKCS7 struct { + Content []byte + Certificates []*x509.Certificate + CRLs []pkix.CertificateList + Signers []signerInfo + raw interface{} +} + +type contentInfo struct { + ContentType asn1.ObjectIdentifier + Content asn1.RawValue `asn1:"explicit,optional,tag:0"` +} + +// ErrUnsupportedContentType is returned when a PKCS7 content is not supported. +// Currently only Data (1.2.840.113549.1.7.1), Signed Data (1.2.840.113549.1.7.2), +// and Enveloped Data are supported (1.2.840.113549.1.7.3) +var ErrUnsupportedContentType = errors.New("pkcs7: cannot parse data: unimplemented content type") + +type unsignedData []byte + +var ( + oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} + oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} + oidEnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 3} + oidSignedAndEnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 4} + oidDigestedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 5} + oidEncryptedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 6} + oidAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} + oidAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} + oidAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5} +) + +type signedData struct { + Version int `asn1:"default:1"` + DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` + ContentInfo contentInfo + Certificates rawCertificates `asn1:"optional,tag:0"` + CRLs []pkix.CertificateList `asn1:"optional,tag:1"` + SignerInfos []signerInfo `asn1:"set"` +} + +type rawCertificates struct { + Raw asn1.RawContent +} + +type envelopedData struct { + Version int + RecipientInfos []recipientInfo `asn1:"set"` + EncryptedContentInfo encryptedContentInfo +} + +type recipientInfo struct { + Version int + IssuerAndSerialNumber issuerAndSerial + KeyEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedKey []byte +} + +type encryptedContentInfo struct { + ContentType asn1.ObjectIdentifier + ContentEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedContent asn1.RawValue `asn1:"tag:0,optional,explicit"` +} + +type attribute struct { + Type asn1.ObjectIdentifier + Value asn1.RawValue `asn1:"set"` +} + +type issuerAndSerial struct { + IssuerName asn1.RawValue + SerialNumber *big.Int +} + +// MessageDigestMismatchError is returned when the signer data digest does not +// match the computed digest for the contained content +type MessageDigestMismatchError struct { + ExpectedDigest []byte + ActualDigest []byte +} + +func (err *MessageDigestMismatchError) Error() string { + return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest) +} + +type signerInfo struct { + Version int `asn1:"default:1"` + IssuerAndSerialNumber issuerAndSerial + DigestAlgorithm pkix.AlgorithmIdentifier + AuthenticatedAttributes []attribute `asn1:"optional,tag:0"` + DigestEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedDigest []byte + UnauthenticatedAttributes []attribute `asn1:"optional,tag:1"` +} + +// Parse decodes a DER encoded PKCS7 package +func Parse(data []byte) (p7 *PKCS7, err error) { + if len(data) == 0 { + return nil, errors.New("pkcs7: input data is empty") + } + var info contentInfo + der, err := ber2der(data) + if err != nil { + return nil, err + } + rest, err := asn1.Unmarshal(der, &info) + if len(rest) > 0 { + err = asn1.SyntaxError{Msg: "trailing data"} + return + } + if err != nil { + return + } + + // fmt.Printf("--> Content Type: %s", info.ContentType) + switch { + case info.ContentType.Equal(oidSignedData): + return parseSignedData(info.Content.Bytes) + case info.ContentType.Equal(oidEnvelopedData): + return parseEnvelopedData(info.Content.Bytes) + } + return nil, ErrUnsupportedContentType +} + +func parseSignedData(data []byte) (*PKCS7, error) { + var sd signedData + asn1.Unmarshal(data, &sd) + certs, err := sd.Certificates.Parse() + if err != nil { + return nil, err + } + // fmt.Printf("--> Signed Data Version %d\n", sd.Version) + + var compound asn1.RawValue + var content unsignedData + + // The Content.Bytes maybe empty on PKI responses. + if len(sd.ContentInfo.Content.Bytes) > 0 { + if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil { + return nil, err + } + } + // Compound octet string + if compound.IsCompound { + if _, err = asn1.Unmarshal(compound.Bytes, &content); err != nil { + return nil, err + } + } else { + // assuming this is tag 04 + content = compound.Bytes + } + return &PKCS7{ + Content: content, + Certificates: certs, + CRLs: sd.CRLs, + Signers: sd.SignerInfos, + raw: sd}, nil +} + +func (raw rawCertificates) Parse() ([]*x509.Certificate, error) { + if len(raw.Raw) == 0 { + return nil, nil + } + + var val asn1.RawValue + if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil { + return nil, err + } + + return x509.ParseCertificates(val.Bytes) +} + +func parseEnvelopedData(data []byte) (*PKCS7, error) { + var ed envelopedData + if _, err := asn1.Unmarshal(data, &ed); err != nil { + return nil, err + } + return &PKCS7{ + raw: ed, + }, nil +} + +// Verify checks the signatures of a PKCS7 object +// WARNING: Verify does not check signing time or verify certificate chains at +// this time. +func (p7 *PKCS7) Verify() (err error) { + if len(p7.Signers) == 0 { + return errors.New("pkcs7: Message has no signers") + } + for _, signer := range p7.Signers { + if err := verifySignature(p7, signer); err != nil { + return err + } + } + return nil +} + +func verifySignature(p7 *PKCS7, signer signerInfo) error { + signedData := p7.Content + if len(signer.AuthenticatedAttributes) > 0 { + // TODO(fullsailor): First check the content type match + var digest []byte + err := unmarshalAttribute(signer.AuthenticatedAttributes, oidAttributeMessageDigest, &digest) + if err != nil { + return err + } + hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm) + if err != nil { + return err + } + h := hash.New() + h.Write(p7.Content) + computed := h.Sum(nil) + if !hmac.Equal(digest, computed) { + return &MessageDigestMismatchError{ + ExpectedDigest: digest, + ActualDigest: computed, + } + } + // TODO(fullsailor): Optionally verify certificate chain + // TODO(fullsailor): Optionally verify signingTime against certificate NotAfter/NotBefore + signedData, err = marshalAttributes(signer.AuthenticatedAttributes) + if err != nil { + return err + } + } + cert := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) + if cert == nil { + return errors.New("pkcs7: No certificate for signer") + } + + algo := x509.SHA1WithRSA + return cert.CheckSignature(algo, signedData, signer.EncryptedDigest) +} + +func marshalAttributes(attrs []attribute) ([]byte, error) { + encodedAttributes, err := asn1.Marshal(struct { + A []attribute `asn1:"set"` + }{A: attrs}) + if err != nil { + return nil, err + } + + // Remove the leading sequence octets + var raw asn1.RawValue + asn1.Unmarshal(encodedAttributes, &raw) + return raw.Bytes, nil +} + +var ( + oidDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} + oidEncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} +) + +func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate { + for _, cert := range certs { + if isCertMatchForIssuerAndSerial(cert, ias) { + return cert + } + } + return nil +} + +func getHashForOID(oid asn1.ObjectIdentifier) (crypto.Hash, error) { + switch { + case oid.Equal(oidDigestAlgorithmSHA1): + return crypto.SHA1, nil + } + return crypto.Hash(0), ErrUnsupportedAlgorithm +} + +// GetOnlySigner returns an x509.Certificate for the first signer of the signed +// data payload. If there are more or less than one signer, nil is returned +func (p7 *PKCS7) GetOnlySigner() *x509.Certificate { + if len(p7.Signers) != 1 { + return nil + } + signer := p7.Signers[0] + return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) +} + +// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed +var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported") + +// ErrNotEncryptedContent is returned when attempting to Decrypt data that is not encrypted data +var ErrNotEncryptedContent = errors.New("pkcs7: content data is a decryptable data type") + +// Decrypt decrypts encrypted content info for recipient cert and private key +func (p7 *PKCS7) Decrypt(cert *x509.Certificate, pk crypto.PrivateKey) ([]byte, error) { + data, ok := p7.raw.(envelopedData) + if !ok { + return nil, ErrNotEncryptedContent + } + recipient := selectRecipientForCertificate(data.RecipientInfos, cert) + if recipient.EncryptedKey == nil { + return nil, errors.New("pkcs7: no enveloped recipient for provided certificate") + } + if priv := pk.(*rsa.PrivateKey); priv != nil { + var contentKey []byte + contentKey, err := rsa.DecryptPKCS1v15(rand.Reader, priv, recipient.EncryptedKey) + if err != nil { + return nil, err + } + return data.EncryptedContentInfo.decrypt(contentKey) + } + fmt.Printf("Unsupported Private Key: %v\n", pk) + return nil, ErrUnsupportedAlgorithm +} + +var oidEncryptionAlgorithmDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} +var oidEncryptionAlgorithmDESEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} +var oidEncryptionAlgorithmAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42} +var oidEncryptionAlgorithmAES128GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 6} +var oidEncryptionAlgorithmAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2} + +func (eci encryptedContentInfo) decrypt(key []byte) ([]byte, error) { + alg := eci.ContentEncryptionAlgorithm.Algorithm + if !alg.Equal(oidEncryptionAlgorithmDESCBC) && + !alg.Equal(oidEncryptionAlgorithmDESEDE3CBC) && + !alg.Equal(oidEncryptionAlgorithmAES256CBC) && + !alg.Equal(oidEncryptionAlgorithmAES128CBC) && + !alg.Equal(oidEncryptionAlgorithmAES128GCM) { + fmt.Printf("Unsupported Content Encryption Algorithm: %s\n", alg) + return nil, ErrUnsupportedAlgorithm + } + + // EncryptedContent can either be constructed of multple OCTET STRINGs + // or _be_ a tagged OCTET STRING + var cyphertext []byte + if eci.EncryptedContent.IsCompound { + // Complex case to concat all of the children OCTET STRINGs + var buf bytes.Buffer + cypherbytes := eci.EncryptedContent.Bytes + for { + var part []byte + cypherbytes, _ = asn1.Unmarshal(cypherbytes, &part) + buf.Write(part) + if cypherbytes == nil { + break + } + } + cyphertext = buf.Bytes() + } else { + // Simple case, the bytes _are_ the cyphertext + cyphertext = eci.EncryptedContent.Bytes + } + + var block cipher.Block + var err error + + switch { + case alg.Equal(oidEncryptionAlgorithmDESCBC): + block, err = des.NewCipher(key) + case alg.Equal(oidEncryptionAlgorithmDESEDE3CBC): + block, err = des.NewTripleDESCipher(key) + case alg.Equal(oidEncryptionAlgorithmAES256CBC): + fallthrough + case alg.Equal(oidEncryptionAlgorithmAES128GCM), alg.Equal(oidEncryptionAlgorithmAES128CBC): + block, err = aes.NewCipher(key) + } + + if err != nil { + return nil, err + } + + if alg.Equal(oidEncryptionAlgorithmAES128GCM) { + params := aesGCMParameters{} + paramBytes := eci.ContentEncryptionAlgorithm.Parameters.Bytes + + _, err := asn1.Unmarshal(paramBytes, ¶ms) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + if len(params.Nonce) != gcm.NonceSize() { + return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") + } + if params.ICVLen != gcm.Overhead() { + return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") + } + + plaintext, err := gcm.Open(nil, params.Nonce, cyphertext, nil) + if err != nil { + return nil, err + } + + return plaintext, nil + } + + iv := eci.ContentEncryptionAlgorithm.Parameters.Bytes + if len(iv) != block.BlockSize() { + return nil, errors.New("pkcs7: encryption algorithm parameters are malformed") + } + mode := cipher.NewCBCDecrypter(block, iv) + plaintext := make([]byte, len(cyphertext)) + mode.CryptBlocks(plaintext, cyphertext) + if plaintext, err = unpad(plaintext, mode.BlockSize()); err != nil { + return nil, err + } + return plaintext, nil +} + +func selectRecipientForCertificate(recipients []recipientInfo, cert *x509.Certificate) recipientInfo { + for _, recp := range recipients { + if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) { + return recp + } + } + return recipientInfo{} +} + +func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool { + return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Compare(cert.RawIssuer, ias.IssuerName.FullBytes) == 0 +} + +func pad(data []byte, blocklen int) ([]byte, error) { + if blocklen < 1 { + return nil, fmt.Errorf("invalid blocklen %d", blocklen) + } + padlen := blocklen - (len(data) % blocklen) + if padlen == 0 { + padlen = blocklen + } + pad := bytes.Repeat([]byte{byte(padlen)}, padlen) + return append(data, pad...), nil +} + +func unpad(data []byte, blocklen int) ([]byte, error) { + if blocklen < 1 { + return nil, fmt.Errorf("invalid blocklen %d", blocklen) + } + if len(data)%blocklen != 0 || len(data) == 0 { + return nil, fmt.Errorf("invalid data len %d", len(data)) + } + + // the last byte is the length of padding + padlen := int(data[len(data)-1]) + + // check padding integrity, all bytes should be the same + pad := data[len(data)-padlen:] + for _, padbyte := range pad { + if padbyte != byte(padlen) { + return nil, errors.New("invalid padding") + } + } + + return data[:len(data)-padlen], nil +} + +func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error { + for _, attr := range attrs { + if attr.Type.Equal(attributeType) { + _, err := asn1.Unmarshal(attr.Value.Bytes, out) + return err + } + } + return errors.New("pkcs7: attribute type not in attributes") +} + +// UnmarshalSignedAttribute decodes a single attribute from the signer info +func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error { + sd, ok := p7.raw.(signedData) + if !ok { + return errors.New("pkcs7: payload is not signedData content") + } + if len(sd.SignerInfos) < 1 { + return errors.New("pkcs7: payload has no signers") + } + attributes := sd.SignerInfos[0].AuthenticatedAttributes + return unmarshalAttribute(attributes, attributeType, out) +} + +// SignedData is an opaque data structure for creating signed data payloads +type SignedData struct { + sd signedData + certs []*x509.Certificate + messageDigest []byte +} + +// Attribute represents a key value pair attribute. Value must be marshalable byte +// `encoding/asn1` +type Attribute struct { + Type asn1.ObjectIdentifier + Value interface{} +} + +// SignerInfoConfig are optional values to include when adding a signer +type SignerInfoConfig struct { + ExtraSignedAttributes []Attribute +} + +// NewSignedData initializes a SignedData with content +func NewSignedData(data []byte) (*SignedData, error) { + content, err := asn1.Marshal(data) + if err != nil { + return nil, err + } + ci := contentInfo{ + ContentType: oidData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, + } + digAlg := pkix.AlgorithmIdentifier{ + Algorithm: oidDigestAlgorithmSHA1, + } + h := crypto.SHA1.New() + h.Write(data) + md := h.Sum(nil) + sd := signedData{ + ContentInfo: ci, + Version: 1, + DigestAlgorithmIdentifiers: []pkix.AlgorithmIdentifier{digAlg}, + } + return &SignedData{sd: sd, messageDigest: md}, nil +} + +type attributes struct { + types []asn1.ObjectIdentifier + values []interface{} +} + +// Add adds the attribute, maintaining insertion order +func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) { + attrs.types = append(attrs.types, attrType) + attrs.values = append(attrs.values, value) +} + +type sortableAttribute struct { + SortKey []byte + Attribute attribute +} + +type attributeSet []sortableAttribute + +func (sa attributeSet) Len() int { + return len(sa) +} + +func (sa attributeSet) Less(i, j int) bool { + return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0 +} + +func (sa attributeSet) Swap(i, j int) { + sa[i], sa[j] = sa[j], sa[i] +} + +func (sa attributeSet) Attributes() []attribute { + attrs := make([]attribute, len(sa)) + for i, attr := range sa { + attrs[i] = attr.Attribute + } + return attrs +} + +func (attrs *attributes) ForMarshaling() ([]attribute, error) { + sortables := make(attributeSet, len(attrs.types)) + for i := range sortables { + attrType := attrs.types[i] + attrValue := attrs.values[i] + asn1Value, err := asn1.Marshal(attrValue) + if err != nil { + return nil, err + } + attr := attribute{ + Type: attrType, + Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag + } + encoded, err := asn1.Marshal(attr) + if err != nil { + return nil, err + } + sortables[i] = sortableAttribute{ + SortKey: encoded, + Attribute: attr, + } + } + sort.Sort(sortables) + return sortables.Attributes(), nil +} + +// AddSigner signs attributes about the content and adds certificate to payload +func (sd *SignedData) AddSigner(cert *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { + attrs := &attributes{} + attrs.Add(oidAttributeContentType, sd.sd.ContentInfo.ContentType) + attrs.Add(oidAttributeMessageDigest, sd.messageDigest) + attrs.Add(oidAttributeSigningTime, time.Now()) + for _, attr := range config.ExtraSignedAttributes { + attrs.Add(attr.Type, attr.Value) + } + finalAttrs, err := attrs.ForMarshaling() + if err != nil { + return err + } + signature, err := signAttributes(finalAttrs, pkey, crypto.SHA1) + if err != nil { + return err + } + + ias, err := cert2issuerAndSerial(cert) + if err != nil { + return err + } + + signer := signerInfo{ + AuthenticatedAttributes: finalAttrs, + DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidDigestAlgorithmSHA1}, + DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidEncryptionAlgorithmRSA}, + IssuerAndSerialNumber: ias, + EncryptedDigest: signature, + Version: 1, + } + // create signature of signed attributes + sd.certs = append(sd.certs, cert) + sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) + return nil +} + +// AddCertificate adds the certificate to the payload. Useful for parent certificates +func (sd *SignedData) AddCertificate(cert *x509.Certificate) { + sd.certs = append(sd.certs, cert) +} + +// Detach removes content from the signed data struct to make it a detached signature. +// This must be called right before Finish() +func (sd *SignedData) Detach() { + sd.sd.ContentInfo = contentInfo{ContentType: oidSignedData} +} + +// Finish marshals the content and its signers +func (sd *SignedData) Finish() ([]byte, error) { + sd.sd.Certificates = marshalCertificates(sd.certs) + inner, err := asn1.Marshal(sd.sd) + if err != nil { + return nil, err + } + outer := contentInfo{ + ContentType: oidSignedData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, + } + return asn1.Marshal(outer) +} + +func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) { + var ias issuerAndSerial + // The issuer RDNSequence has to match exactly the sequence in the certificate + // We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence + ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer} + ias.SerialNumber = cert.SerialNumber + + return ias, nil +} + +// signs the DER encoded form of the attributes with the private key +func signAttributes(attrs []attribute, pkey crypto.PrivateKey, hash crypto.Hash) ([]byte, error) { + attrBytes, err := marshalAttributes(attrs) + if err != nil { + return nil, err + } + h := hash.New() + h.Write(attrBytes) + hashed := h.Sum(nil) + switch priv := pkey.(type) { + case *rsa.PrivateKey: + return rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA1, hashed) + } + return nil, ErrUnsupportedAlgorithm +} + +// concats and wraps the certificates in the RawValue structure +func marshalCertificates(certs []*x509.Certificate) rawCertificates { + var buf bytes.Buffer + for _, cert := range certs { + buf.Write(cert.Raw) + } + rawCerts, _ := marshalCertificateBytes(buf.Bytes()) + return rawCerts +} + +// Even though, the tag & length are stripped out during marshalling the +// RawContent, we have to encode it into the RawContent. If its missing, +// then `asn1.Marshal()` will strip out the certificate wrapper instead. +func marshalCertificateBytes(certs []byte) (rawCertificates, error) { + var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true} + b, err := asn1.Marshal(val) + if err != nil { + return rawCertificates{}, err + } + return rawCertificates{Raw: b}, nil +} + +// DegenerateCertificate creates a signed data structure containing only the +// provided certificate or certificate chain. +func DegenerateCertificate(cert []byte) ([]byte, error) { + rawCert, err := marshalCertificateBytes(cert) + if err != nil { + return nil, err + } + emptyContent := contentInfo{ContentType: oidData} + sd := signedData{ + Version: 1, + ContentInfo: emptyContent, + Certificates: rawCert, + CRLs: []pkix.CertificateList{}, + } + content, err := asn1.Marshal(sd) + if err != nil { + return nil, err + } + signedContent := contentInfo{ + ContentType: oidSignedData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, + } + return asn1.Marshal(signedContent) +} + +const ( + EncryptionAlgorithmDESCBC = iota + EncryptionAlgorithmAES128GCM +) + +// ContentEncryptionAlgorithm determines the algorithm used to encrypt the +// plaintext message. Change the value of this variable to change which +// algorithm is used in the Encrypt() function. +var ContentEncryptionAlgorithm = EncryptionAlgorithmDESCBC + +// ErrUnsupportedEncryptionAlgorithm is returned when attempting to encrypt +// content with an unsupported algorithm. +var ErrUnsupportedEncryptionAlgorithm = errors.New("pkcs7: cannot encrypt content: only DES-CBC and AES-128-GCM supported") + +const nonceSize = 12 + +type aesGCMParameters struct { + Nonce []byte `asn1:"tag:4"` + ICVLen int +} + +func encryptAES128GCM(content []byte) ([]byte, *encryptedContentInfo, error) { + // Create AES key and nonce + key := make([]byte, 16) + nonce := make([]byte, nonceSize) + + _, err := rand.Read(key) + if err != nil { + return nil, nil, err + } + + _, err = rand.Read(nonce) + if err != nil { + return nil, nil, err + } + + // Encrypt content + block, err := aes.NewCipher(key) + if err != nil { + return nil, nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, nil, err + } + + ciphertext := gcm.Seal(nil, nonce, content, nil) + + // Prepare ASN.1 Encrypted Content Info + paramSeq := aesGCMParameters{ + Nonce: nonce, + ICVLen: gcm.Overhead(), + } + + paramBytes, err := asn1.Marshal(paramSeq) + if err != nil { + return nil, nil, err + } + + eci := encryptedContentInfo{ + ContentType: oidData, + ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: oidEncryptionAlgorithmAES128GCM, + Parameters: asn1.RawValue{ + Tag: asn1.TagSequence, + Bytes: paramBytes, + }, + }, + EncryptedContent: marshalEncryptedContent(ciphertext), + } + + return key, &eci, nil +} + +func encryptDESCBC(content []byte) ([]byte, *encryptedContentInfo, error) { + // Create DES key & CBC IV + key := make([]byte, 8) + iv := make([]byte, des.BlockSize) + _, err := rand.Read(key) + if err != nil { + return nil, nil, err + } + _, err = rand.Read(iv) + if err != nil { + return nil, nil, err + } + + // Encrypt padded content + block, err := des.NewCipher(key) + if err != nil { + return nil, nil, err + } + mode := cipher.NewCBCEncrypter(block, iv) + plaintext, err := pad(content, mode.BlockSize()) + cyphertext := make([]byte, len(plaintext)) + mode.CryptBlocks(cyphertext, plaintext) + + // Prepare ASN.1 Encrypted Content Info + eci := encryptedContentInfo{ + ContentType: oidData, + ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: oidEncryptionAlgorithmDESCBC, + Parameters: asn1.RawValue{Tag: 4, Bytes: iv}, + }, + EncryptedContent: marshalEncryptedContent(cyphertext), + } + + return key, &eci, nil +} + +// Encrypt creates and returns an envelope data PKCS7 structure with encrypted +// recipient keys for each recipient public key. +// +// The algorithm used to perform encryption is determined by the current value +// of the global ContentEncryptionAlgorithm package variable. By default, the +// value is EncryptionAlgorithmDESCBC. To use a different algorithm, change the +// value before calling Encrypt(). For example: +// +// ContentEncryptionAlgorithm = EncryptionAlgorithmAES128GCM +// +// TODO(fullsailor): Add support for encrypting content with other algorithms +func Encrypt(content []byte, recipients []*x509.Certificate) ([]byte, error) { + var eci *encryptedContentInfo + var key []byte + var err error + + // Apply chosen symmetric encryption method + switch ContentEncryptionAlgorithm { + case EncryptionAlgorithmDESCBC: + key, eci, err = encryptDESCBC(content) + + case EncryptionAlgorithmAES128GCM: + key, eci, err = encryptAES128GCM(content) + + default: + return nil, ErrUnsupportedEncryptionAlgorithm + } + + if err != nil { + return nil, err + } + + // Prepare each recipient's encrypted cipher key + recipientInfos := make([]recipientInfo, len(recipients)) + for i, recipient := range recipients { + encrypted, err := encryptKey(key, recipient) + if err != nil { + return nil, err + } + ias, err := cert2issuerAndSerial(recipient) + if err != nil { + return nil, err + } + info := recipientInfo{ + Version: 0, + IssuerAndSerialNumber: ias, + KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: oidEncryptionAlgorithmRSA, + }, + EncryptedKey: encrypted, + } + recipientInfos[i] = info + } + + // Prepare envelope content + envelope := envelopedData{ + EncryptedContentInfo: *eci, + Version: 0, + RecipientInfos: recipientInfos, + } + innerContent, err := asn1.Marshal(envelope) + if err != nil { + return nil, err + } + + // Prepare outer payload structure + wrapper := contentInfo{ + ContentType: oidEnvelopedData, + Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, + } + + return asn1.Marshal(wrapper) +} + +func marshalEncryptedContent(content []byte) asn1.RawValue { + asn1Content, _ := asn1.Marshal(content) + return asn1.RawValue{Tag: 0, Class: 2, Bytes: asn1Content, IsCompound: true} +} + +func encryptKey(key []byte, recipient *x509.Certificate) ([]byte, error) { + if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil { + return rsa.EncryptPKCS1v15(rand.Reader, pub, key) + } + return nil, ErrUnsupportedAlgorithm +} diff --git a/vendor/github.com/zakjan/cert-chain-resolver/LICENCE b/vendor/github.com/zakjan/cert-chain-resolver/LICENCE new file mode 100644 index 0000000000..0b5b3cb96b --- /dev/null +++ b/vendor/github.com/zakjan/cert-chain-resolver/LICENCE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Jan Žák (http://zakjan.cz) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/zakjan/cert-chain-resolver/certUtil/chain.go b/vendor/github.com/zakjan/cert-chain-resolver/certUtil/chain.go new file mode 100644 index 0000000000..59e24b7b64 --- /dev/null +++ b/vendor/github.com/zakjan/cert-chain-resolver/certUtil/chain.go @@ -0,0 +1,75 @@ +package certUtil + +import ( + "crypto/x509" + "io/ioutil" + "net/http" +) + +func isSelfSigned(cert *x509.Certificate) bool { + return cert.CheckSignatureFrom(cert) == nil +} + +func isChainRootNode(cert *x509.Certificate) bool { + if isSelfSigned(cert) { + return true + } + return false +} + +func FetchCertificateChain(cert *x509.Certificate) ([]*x509.Certificate, error) { + var certs []*x509.Certificate + + certs = append(certs, cert) + + for certs[len(certs)-1].IssuingCertificateURL != nil { + parentURL := certs[len(certs)-1].IssuingCertificateURL[0] + + resp, err := http.Get(parentURL) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + cert, err := DecodeCertificate(data) + if err != nil { + return nil, err + } + + if isChainRootNode(cert) { + break + } + + certs = append(certs, cert) + } + + return certs, nil +} + +func AddRootCA(certs []*x509.Certificate) ([]*x509.Certificate, error) { + lastCert := certs[len(certs)-1] + + chains, err := lastCert.Verify(x509.VerifyOptions{}) + if err != nil { + if _, e := err.(x509.UnknownAuthorityError); e { + return certs, nil + } + return nil, err + } + + for _, cert := range chains[0] { + if lastCert.Equal(cert) { + continue + } + certs = append(certs, cert) + } + + return certs, nil +} diff --git a/vendor/github.com/zakjan/cert-chain-resolver/certUtil/io.go b/vendor/github.com/zakjan/cert-chain-resolver/certUtil/io.go new file mode 100644 index 0000000000..40818ac515 --- /dev/null +++ b/vendor/github.com/zakjan/cert-chain-resolver/certUtil/io.go @@ -0,0 +1,110 @@ +package certUtil + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "errors" + "github.com/fullsailor/pkcs7" +) + +var pemStart = []byte("-----BEGIN ") +var certBlockType = "CERTIFICATE" + +func IsPEM(data []byte) bool { + return bytes.HasPrefix(data, pemStart) +} + +func DecodeCertificates(data []byte) ([]*x509.Certificate, error) { + if IsPEM(data) { + var certs []*x509.Certificate + + for len(data) > 0 { + var block *pem.Block + + block, data = pem.Decode(data) + if block == nil { + return nil, errors.New("Invalid certificate.") + } + if block.Type != certBlockType { + return nil, errors.New("Invalid certificate.") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, errors.New("Invalid certificate.") + } + + certs = append(certs, cert) + } + + return certs, nil + } else { + certs, err := x509.ParseCertificates(data) + if err != nil { + return nil, errors.New("Invalid certificate.") + } + + return certs, nil + } +} + +func DecodeCertificate(data []byte) (*x509.Certificate, error) { + if IsPEM(data) { + block, _ := pem.Decode(data) + if block == nil { + return nil, errors.New("Invalid certificate.") + } + if block.Type != certBlockType { + return nil, errors.New("Invalid certificate.") + } + + data = block.Bytes + } + + cert, err := x509.ParseCertificate(data) + if err == nil { + return cert, nil + } + + p, err := pkcs7.Parse(data) + if err == nil { + return p.Certificates[0], nil + } + + return nil, errors.New("Invalid certificate.") +} + +func EncodeCertificate(cert *x509.Certificate) []byte { + block := pem.Block{ + Type: certBlockType, + Bytes: cert.Raw, + } + return pem.EncodeToMemory(&block) +} + +func EncodeCertificateDER(cert *x509.Certificate) []byte { + return cert.Raw +} + +func EncodeCertificates(certs []*x509.Certificate) []byte { + var data []byte + + for _, cert := range certs { + data2 := EncodeCertificate(cert) + data = append(data, data2...) + } + + return data +} + +func EncodeCertificatesDER(certs []*x509.Certificate) []byte { + var data []byte + + for _, cert := range certs { + data2 := EncodeCertificateDER(cert) + data = append(data, data2...) + } + + return data +}