Add OCSP support
This commit is contained in:
parent
1c6ff88228
commit
f6ba3abca3
15 changed files with 1519 additions and 2 deletions
9
Godeps/Godeps.json
generated
9
Godeps/Godeps.json
generated
|
@ -68,6 +68,10 @@
|
||||||
"Comment": "2.2.0-4-gff4f55a",
|
"Comment": "2.2.0-4-gff4f55a",
|
||||||
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
|
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/fullsailor/pkcs7",
|
||||||
|
"Rev": "a009d8d7de53d9503c797cb8ec66fa3b21eed209"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/ghodss/yaml",
|
"ImportPath": "github.com/ghodss/yaml",
|
||||||
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
|
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
|
||||||
|
@ -287,6 +291,11 @@
|
||||||
"ImportPath": "github.com/spf13/pflag",
|
"ImportPath": "github.com/spf13/pflag",
|
||||||
"Rev": "9ff6c6923cfffbcd502984b8e0c80539a94968b7"
|
"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",
|
"ImportPath": "golang.org/x/crypto/nacl/secretbox",
|
||||||
"Rev": "81e90905daefcd6fd217b62423c0908922eadb30"
|
"Rev": "81e90905daefcd6fd217b62423c0908922eadb30"
|
||||||
|
|
|
@ -621,6 +621,11 @@ stream {
|
||||||
# PEM sha: {{ $server.SSLPemChecksum }}
|
# PEM sha: {{ $server.SSLPemChecksum }}
|
||||||
ssl_certificate {{ $server.SSLCertificate }};
|
ssl_certificate {{ $server.SSLCertificate }};
|
||||||
ssl_certificate_key {{ $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 }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if (and (not (empty $server.SSLCertificate)) $all.Cfg.HSTS) }}
|
{{ if (and (not (empty $server.SSLCertificate)) $all.Cfg.HSTS) }}
|
||||||
|
|
|
@ -1056,6 +1056,7 @@ func (ic *GenericController) createServers(data []*extensions.Ingress,
|
||||||
}
|
}
|
||||||
|
|
||||||
servers[host].SSLCertificate = cert.PemFileName
|
servers[host].SSLCertificate = cert.PemFileName
|
||||||
|
servers[host].SSLFullChainCertificate = cert.FullChainPemFileName
|
||||||
servers[host].SSLPemChecksum = cert.PemSHA
|
servers[host].SSLPemChecksum = cert.PemSHA
|
||||||
servers[host].SSLExpireTime = cert.ExpireTime
|
servers[host].SSLExpireTime = cert.ExpireTime
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,9 @@ type SSLCert struct {
|
||||||
CAFileName string `json:"caFileName"`
|
CAFileName string `json:"caFileName"`
|
||||||
// PemFileName contains the path to the file with the certificate and key concatenated
|
// PemFileName contains the path to the file with the certificate and key concatenated
|
||||||
PemFileName string `json:"pemFileName"`
|
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.
|
// PemSHA contains the sha1 of the pem file.
|
||||||
// This is used to detect changes in the secret that contains the certificates
|
// This is used to detect changes in the secret that contains the certificates
|
||||||
PemSHA string `json:"pemSha"`
|
PemSHA string `json:"pemSha"`
|
||||||
|
|
|
@ -220,6 +220,9 @@ type Server struct {
|
||||||
SSLPassthrough bool `json:"sslPassthrough"`
|
SSLPassthrough bool `json:"sslPassthrough"`
|
||||||
// SSLCertificate path to the SSL certificate on disk
|
// SSLCertificate path to the SSL certificate on disk
|
||||||
SSLCertificate string `json:"sslCertificate"`
|
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 has the expire date of this certificate
|
||||||
SSLExpireTime time.Time `json:"sslExpireTime"`
|
SSLExpireTime time.Time `json:"sslExpireTime"`
|
||||||
// SSLPemChecksum returns the checksum of the certificate file on disk.
|
// SSLPemChecksum returns the checksum of the certificate file on disk.
|
||||||
|
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"github.com/zakjan/cert-chain-resolver/certUtil"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ var (
|
||||||
func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, error) {
|
func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, error) {
|
||||||
pemName := fmt.Sprintf("%v.pem", name)
|
pemName := fmt.Sprintf("%v.pem", name)
|
||||||
pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName)
|
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)
|
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
|
||||||
|
|
||||||
|
@ -170,13 +172,23 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ingress.SSLCert{
|
s := &ingress.SSLCert{
|
||||||
Certificate: pemCert,
|
Certificate: pemCert,
|
||||||
PemFileName: pemFileName,
|
PemFileName: pemFileName,
|
||||||
PemSHA: file.SHA1(pemFileName),
|
PemSHA: file.SHA1(pemFileName),
|
||||||
CN: cn.List(),
|
CN: cn.List(),
|
||||||
ExpireTime: pemCert.NotAfter,
|
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 {
|
func getExtension(c *x509.Certificate, id asn1.ObjectIdentifier) []pkix.Extension {
|
||||||
|
@ -376,3 +388,33 @@ func GetFakeSSLCert() ([]byte, []byte) {
|
||||||
|
|
||||||
return cert, key
|
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)
|
||||||
|
}
|
||||||
|
|
24
vendor/github.com/fullsailor/pkcs7/.gitignore
generated
vendored
Normal file
24
vendor/github.com/fullsailor/pkcs7/.gitignore
generated
vendored
Normal file
|
@ -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
|
6
vendor/github.com/fullsailor/pkcs7/.travis.yml
generated
vendored
Normal file
6
vendor/github.com/fullsailor/pkcs7/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
- tip
|
22
vendor/github.com/fullsailor/pkcs7/LICENSE
generated
vendored
Normal file
22
vendor/github.com/fullsailor/pkcs7/LICENSE
generated
vendored
Normal file
|
@ -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.
|
||||||
|
|
8
vendor/github.com/fullsailor/pkcs7/README.md
generated
vendored
Normal file
8
vendor/github.com/fullsailor/pkcs7/README.md
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# pkcs7
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/fullsailor/pkcs7)
|
||||||
|
[](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)
|
248
vendor/github.com/fullsailor/pkcs7/ber.go
generated
vendored
Normal file
248
vendor/github.com/fullsailor/pkcs7/ber.go
generated
vendored
Normal file
|
@ -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
|
||||||
|
}
|
940
vendor/github.com/fullsailor/pkcs7/pkcs7.go
generated
vendored
Normal file
940
vendor/github.com/fullsailor/pkcs7/pkcs7.go
generated
vendored
Normal file
|
@ -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
|
||||||
|
}
|
21
vendor/github.com/zakjan/cert-chain-resolver/LICENCE
generated
vendored
Normal file
21
vendor/github.com/zakjan/cert-chain-resolver/LICENCE
generated
vendored
Normal file
|
@ -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.
|
75
vendor/github.com/zakjan/cert-chain-resolver/certUtil/chain.go
generated
vendored
Normal file
75
vendor/github.com/zakjan/cert-chain-resolver/certUtil/chain.go
generated
vendored
Normal file
|
@ -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
|
||||||
|
}
|
110
vendor/github.com/zakjan/cert-chain-resolver/certUtil/io.go
generated
vendored
Normal file
110
vendor/github.com/zakjan/cert-chain-resolver/certUtil/io.go
generated
vendored
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue