400 lines
10 KiB
Go
400 lines
10 KiB
Go
|
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
|
||
|
}
|