blob: 90da67e4eed2dd5743233dea6262763d9a75d980 [file] [log] [blame]
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
}