| package pkcs7 |
| |
| import ( |
| "bytes" |
| "crypto/aes" |
| "crypto/cipher" |
| "crypto/des" |
| "crypto/rand" |
| "crypto/rsa" |
| "crypto/x509" |
| "crypto/x509/pkix" |
| "encoding/asn1" |
| "errors" |
| "fmt" |
| ) |
| |
| type envelopedData struct { |
| Version int |
| RecipientInfos []recipientInfo `asn1:"set"` |
| EncryptedContentInfo encryptedContentInfo |
| } |
| |
| type encryptedData struct { |
| Version int |
| 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"` |
| } |
| |
| const ( |
| // EncryptionAlgorithmDESCBC is the DES CBC encryption algorithm |
| EncryptionAlgorithmDESCBC = iota |
| |
| // EncryptionAlgorithmAES128CBC is the AES 128 bits with CBC encryption algorithm |
| // Avoid this algorithm unless required for interoperability; use AES GCM instead. |
| EncryptionAlgorithmAES128CBC |
| |
| // EncryptionAlgorithmAES256CBC is the AES 256 bits with CBC encryption algorithm |
| // Avoid this algorithm unless required for interoperability; use AES GCM instead. |
| EncryptionAlgorithmAES256CBC |
| |
| // EncryptionAlgorithmAES128GCM is the AES 128 bits with GCM encryption algorithm |
| EncryptionAlgorithmAES128GCM |
| |
| // EncryptionAlgorithmAES256GCM is the AES 256 bits with GCM encryption algorithm |
| EncryptionAlgorithmAES256GCM |
| ) |
| |
| // 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, AES-CBC, and AES-GCM supported") |
| |
| // ErrPSKNotProvided is returned when attempting to encrypt |
| // using a PSK without actually providing the PSK. |
| var ErrPSKNotProvided = errors.New("pkcs7: cannot encrypt content: PSK not provided") |
| |
| const nonceSize = 12 |
| |
| type aesGCMParameters struct { |
| Nonce []byte `asn1:"tag:4"` |
| ICVLen int |
| } |
| |
| func encryptAESGCM(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { |
| var keyLen int |
| var algID asn1.ObjectIdentifier |
| switch ContentEncryptionAlgorithm { |
| case EncryptionAlgorithmAES128GCM: |
| keyLen = 16 |
| algID = OIDEncryptionAlgorithmAES128GCM |
| case EncryptionAlgorithmAES256GCM: |
| keyLen = 32 |
| algID = OIDEncryptionAlgorithmAES256GCM |
| default: |
| return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESGCM: %d", ContentEncryptionAlgorithm) |
| } |
| if key == nil { |
| // Create AES key |
| key = make([]byte, keyLen) |
| |
| _, err := rand.Read(key) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| |
| // Create nonce |
| nonce := make([]byte, nonceSize) |
| |
| _, 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: algID, |
| Parameters: asn1.RawValue{ |
| Tag: asn1.TagSequence, |
| Bytes: paramBytes, |
| }, |
| }, |
| EncryptedContent: marshalEncryptedContent(ciphertext), |
| } |
| |
| return key, &eci, nil |
| } |
| |
| func encryptDESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { |
| if key == nil { |
| // Create DES key |
| key = make([]byte, 8) |
| |
| _, err := rand.Read(key) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| |
| // Create CBC IV |
| iv := make([]byte, des.BlockSize) |
| _, 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()) |
| if err != nil { |
| return nil, nil, err |
| } |
| 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 |
| } |
| |
| func encryptAESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { |
| var keyLen int |
| var algID asn1.ObjectIdentifier |
| switch ContentEncryptionAlgorithm { |
| case EncryptionAlgorithmAES128CBC: |
| keyLen = 16 |
| algID = OIDEncryptionAlgorithmAES128CBC |
| case EncryptionAlgorithmAES256CBC: |
| keyLen = 32 |
| algID = OIDEncryptionAlgorithmAES256CBC |
| default: |
| return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESCBC: %d", ContentEncryptionAlgorithm) |
| } |
| |
| if key == nil { |
| // Create AES key |
| key = make([]byte, keyLen) |
| |
| _, err := rand.Read(key) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| |
| // Create CBC IV |
| iv := make([]byte, aes.BlockSize) |
| _, err := rand.Read(iv) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| // Encrypt padded content |
| block, err := aes.NewCipher(key) |
| if err != nil { |
| return nil, nil, err |
| } |
| mode := cipher.NewCBCEncrypter(block, iv) |
| plaintext, err := pad(content, mode.BlockSize()) |
| if err != nil { |
| return nil, nil, err |
| } |
| cyphertext := make([]byte, len(plaintext)) |
| mode.CryptBlocks(cyphertext, plaintext) |
| |
| // Prepare ASN.1 Encrypted Content Info |
| eci := encryptedContentInfo{ |
| ContentType: OIDData, |
| ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ |
| Algorithm: algID, |
| 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, nil) |
| case EncryptionAlgorithmAES128CBC: |
| fallthrough |
| case EncryptionAlgorithmAES256CBC: |
| key, eci, err = encryptAESCBC(content, nil) |
| case EncryptionAlgorithmAES128GCM: |
| fallthrough |
| case EncryptionAlgorithmAES256GCM: |
| key, eci, err = encryptAESGCM(content, nil) |
| |
| 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) |
| } |
| |
| // EncryptUsingPSK creates and returns an encrypted data PKCS7 structure, |
| // encrypted using caller provided pre-shared secret. |
| func EncryptUsingPSK(content []byte, key []byte) ([]byte, error) { |
| var eci *encryptedContentInfo |
| var err error |
| |
| if key == nil { |
| return nil, ErrPSKNotProvided |
| } |
| |
| // Apply chosen symmetric encryption method |
| switch ContentEncryptionAlgorithm { |
| case EncryptionAlgorithmDESCBC: |
| _, eci, err = encryptDESCBC(content, key) |
| |
| case EncryptionAlgorithmAES128GCM: |
| fallthrough |
| case EncryptionAlgorithmAES256GCM: |
| _, eci, err = encryptAESGCM(content, key) |
| |
| default: |
| return nil, ErrUnsupportedEncryptionAlgorithm |
| } |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| // Prepare encrypted-data content |
| ed := encryptedData{ |
| Version: 0, |
| EncryptedContentInfo: *eci, |
| } |
| innerContent, err := asn1.Marshal(ed) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Prepare outer payload structure |
| wrapper := contentInfo{ |
| ContentType: OIDEncryptedData, |
| 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 |
| } |
| |
| 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 |
| } |