476 lines
13 KiB
Go
476 lines
13 KiB
Go
package pkcs7
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"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")
|
|
|
|
// KeyEncryptionAlgorithm determines the algorithm used to encrypt a
|
|
// content key. Change the value of this variable to change which
|
|
// algorithm is used in the Encrypt() function.
|
|
var KeyEncryptionAlgorithm = OIDEncryptionAlgorithmRSA
|
|
|
|
// ErrUnsupportedKeyEncryptionAlgorithm is returned when an
|
|
// unsupported key encryption algorithm OID is provided.
|
|
var ErrUnsupportedKeyEncryptionAlgorithm = errors.New("pkcs7: unsupported key encryption algorithm provided")
|
|
|
|
// KeyEncryptionHash determines the crypto.Hash algorithm to use
|
|
// when encrypting a content key. Change the value of this variable
|
|
// to change which algorithm is used in the Encrypt() function.
|
|
var KeyEncryptionHash = crypto.SHA256
|
|
|
|
// ErrUnsupportedKeyEncryptionHash is returned when an
|
|
// unsupported key encryption hash is provided.
|
|
var ErrUnsupportedKeyEncryptionHash = errors.New("pkcs7: unsupported key encryption hash provided")
|
|
|
|
// 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 = EncryptionAlgorithmAES256GCM
|
|
//
|
|
// 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 {
|
|
algorithm := KeyEncryptionAlgorithm
|
|
hash := KeyEncryptionHash
|
|
var kea pkix.AlgorithmIdentifier
|
|
switch {
|
|
case algorithm.Equal(OIDEncryptionAlgorithmRSAESOAEP):
|
|
parameters, err := getParametersForKeyEncryptionAlgorithm(algorithm, hash)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get parameters for key encryption: %v", err)
|
|
}
|
|
kea = pkix.AlgorithmIdentifier{
|
|
Algorithm: algorithm,
|
|
Parameters: parameters,
|
|
}
|
|
case algorithm.Equal(OIDEncryptionAlgorithmRSA):
|
|
kea = pkix.AlgorithmIdentifier{
|
|
Algorithm: algorithm,
|
|
}
|
|
default:
|
|
return nil, ErrUnsupportedKeyEncryptionAlgorithm
|
|
}
|
|
encrypted, err := encryptKey(key, recipient, algorithm, hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ias, err := cert2issuerAndSerial(recipient)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
info := recipientInfo{
|
|
Version: 0,
|
|
IssuerAndSerialNumber: ias,
|
|
KeyEncryptionAlgorithm: kea,
|
|
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 getParametersForKeyEncryptionAlgorithm(algorithm asn1.ObjectIdentifier, hash crypto.Hash) (asn1.RawValue, error) {
|
|
if !algorithm.Equal(OIDEncryptionAlgorithmRSAESOAEP) {
|
|
return asn1.RawValue{}, nil // return empty; not used
|
|
}
|
|
|
|
params := rsaOAEPAlgorithmParameters{}
|
|
switch hash {
|
|
case crypto.SHA1:
|
|
params.HashFunc = pkix.AlgorithmIdentifier{Algorithm: OIDDigestAlgorithmSHA1}
|
|
case crypto.SHA224:
|
|
params.HashFunc = pkix.AlgorithmIdentifier{Algorithm: OIDDigestAlgorithmSHA224}
|
|
case crypto.SHA256:
|
|
params.HashFunc = pkix.AlgorithmIdentifier{Algorithm: OIDDigestAlgorithmSHA256}
|
|
case crypto.SHA384:
|
|
params.HashFunc = pkix.AlgorithmIdentifier{Algorithm: OIDDigestAlgorithmSHA384}
|
|
case crypto.SHA512:
|
|
params.HashFunc = pkix.AlgorithmIdentifier{Algorithm: OIDDigestAlgorithmSHA512}
|
|
default:
|
|
return asn1.RawValue{}, ErrUnsupportedAlgorithm
|
|
}
|
|
|
|
b, err := asn1.Marshal(params)
|
|
if err != nil {
|
|
return asn1.RawValue{}, fmt.Errorf("failed marshaling key encryption parameters: %v", err)
|
|
}
|
|
|
|
return asn1.RawValue{
|
|
FullBytes: b,
|
|
}, nil
|
|
}
|
|
|
|
// 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 {
|
|
return asn1.RawValue{Bytes: content, Class: 2, IsCompound: false}
|
|
}
|
|
|
|
func encryptKey(key []byte, recipient *x509.Certificate, algorithm asn1.ObjectIdentifier, hash crypto.Hash) ([]byte, error) {
|
|
pub, ok := recipient.PublicKey.(*rsa.PublicKey)
|
|
if !ok {
|
|
return nil, ErrUnsupportedKeyType
|
|
}
|
|
|
|
switch {
|
|
case algorithm.Equal(OIDEncryptionAlgorithmRSA):
|
|
return rsa.EncryptPKCS1v15(rand.Reader, pub, key)
|
|
case algorithm.Equal(OIDEncryptionAlgorithmRSAESOAEP):
|
|
return rsa.EncryptOAEP(hash.New(), rand.Reader, pub, key, nil)
|
|
default:
|
|
return nil, ErrUnsupportedKeyEncryptionAlgorithm
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|