summaryrefslogtreecommitdiff
path: root/vendor/github.com/fullsailor/pkcs7/pkcs7.go
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2019-12-19 13:29:25 -0500
committerDaniel J Walsh <dwalsh@redhat.com>2019-12-20 09:30:47 -0500
commit50ece79387dcf6c748e3ae1bd6a7067059c0dfe3 (patch)
tree6b30c4f66f7be315ff2257447be3818be98fb50f /vendor/github.com/fullsailor/pkcs7/pkcs7.go
parenta359ca0d1825859dd8b7c1384f11d703ec6625b4 (diff)
downloadpodman-50ece79387dcf6c748e3ae1bd6a7067059c0dfe3.tar.gz
podman-50ece79387dcf6c748e3ae1bd6a7067059c0dfe3.tar.bz2
podman-50ece79387dcf6c748e3ae1bd6a7067059c0dfe3.zip
build(deps): bump github.com/containers/image/v5 from 5.0.0 to 5.1.0
Bumps [github.com/containers/image/v5](https://github.com/containers/image) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/containers/image/releases) - [Commits](https://github.com/containers/image/compare/v5.0.0...v5.1.0) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
Diffstat (limited to 'vendor/github.com/fullsailor/pkcs7/pkcs7.go')
-rw-r--r--vendor/github.com/fullsailor/pkcs7/pkcs7.go962
1 files changed, 962 insertions, 0 deletions
diff --git a/vendor/github.com/fullsailor/pkcs7/pkcs7.go b/vendor/github.com/fullsailor/pkcs7/pkcs7.go
new file mode 100644
index 000000000..0264466b4
--- /dev/null
+++ b/vendor/github.com/fullsailor/pkcs7/pkcs7.go
@@ -0,0 +1,962 @@
+// 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"`
+}
+
+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
+ hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm)
+ if err != nil {
+ return err
+ }
+ 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
+ }
+ 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 := getSignatureAlgorithmFromAI(signer.DigestEncryptionAlgorithm)
+ if algo == x509.UnknownSignatureAlgorithm {
+ // I'm not sure what the spec here is, and the openssl sources were not
+ // helpful. But, this is what App Store receipts appear to do.
+ // The DigestEncryptionAlgorithm is just "rsaEncryption (PKCS #1)"
+ // But we're expecting a digest + encryption algorithm. So... we're going
+ // to determine an algorithm based on the DigestAlgorithm and this
+ // encryption algorithm.
+ if signer.DigestEncryptionAlgorithm.Algorithm.Equal(oidEncryptionAlgorithmRSA) {
+ algo = getRSASignatureAlgorithmForDigestAlgorithm(hash)
+ }
+ }
+ 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
+ case oid.Equal(oidSHA256):
+ return crypto.SHA256, nil
+ }
+ return crypto.Hash(0), ErrUnsupportedAlgorithm
+}
+
+func getRSASignatureAlgorithmForDigestAlgorithm(hash crypto.Hash) x509.SignatureAlgorithm {
+ for _, details := range signatureAlgorithmDetails {
+ if details.pubKeyAlgo == x509.RSA && details.hash == hash {
+ return details.algo
+ }
+ }
+ return x509.UnknownSignatureAlgorithm
+}
+
+// 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, &params)
+ 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: oidSignatureSHA1WithRSA},
+ 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: oidData}
+}
+
+// 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
+}