Skip to content

Commit

Permalink
pkcs7: improve compatibility with CFCA #270
Browse files Browse the repository at this point in the history
  • Loading branch information
emmansun authored Nov 20, 2024
1 parent c9f55b0 commit 0205d1a
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 56 deletions.
24 changes: 21 additions & 3 deletions pkcs7/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ import (

// IssuerAndSerial is a structure that holds the issuer name and serial number
type IssuerAndSerial struct {
RawIssuer []byte
RawIssuer []byte
SerialNumber *big.Int
}

// RecipientInfo is a structure that holds the recipient information
// supports IssuerAndSerial and SubjectKeyIdentifier.
type RecipientInfo struct {
IssuerAndSerial
SubjectKeyIdentifier []byte
}

func newIssuerAndSerial(issuerAndSerial issuerAndSerial) IssuerAndSerial {
is := IssuerAndSerial{}
if len(issuerAndSerial.IssuerName.FullBytes) > 0 {
Expand All @@ -31,6 +38,17 @@ func newIssuerAndSerial(issuerAndSerial issuerAndSerial) IssuerAndSerial {
return is
}

func newRecipientInfo(recipientInfo recipientInfo) RecipientInfo {
ri := RecipientInfo{
IssuerAndSerial: newIssuerAndSerial(recipientInfo.IssuerAndSerialNumber),
}
if len(recipientInfo.SubjectKeyIdentifier.Bytes) > 0 {
ri.SubjectKeyIdentifier = append(ri.SubjectKeyIdentifier, recipientInfo.SubjectKeyIdentifier.Bytes...)
}

return ri
}

// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed
var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, SM2, DES, DES-EDE3, AES and SM4 supported")

Expand All @@ -42,12 +60,12 @@ var ErrNotEnvelopedData = errors.New("pkcs7: content data is NOT an enveloped da

type decryptable interface {
GetRecipient(cert *smx509.Certificate) *recipientInfo
GetRecipients() ([]IssuerAndSerial, error)
GetRecipients() ([]RecipientInfo, error)
GetEncryptedContentInfo() *encryptedContentInfo
}

// GetRecipients returns the list of recipients for the enveloped data
func (p7 *PKCS7) GetRecipients() ([]IssuerAndSerial, error) {
func (p7 *PKCS7) GetRecipients() ([]RecipientInfo, error) {
decryptableData, ok := p7.raw.(decryptable)
if !ok {
return nil, ErrNotEnvelopedData
Expand Down
126 changes: 87 additions & 39 deletions pkcs7/envelope.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package pkcs7

import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509/pkix"
"encoding/asn1"
"errors"

"github.com/emmansun/gmsm/pkcs"
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/smx509"
"golang.org/x/crypto/cryptobyte"
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
)

type EnvelopedData struct {
Expand All @@ -28,7 +32,8 @@ type envelopedData struct {

type recipientInfo struct {
Version int
IssuerAndSerialNumber issuerAndSerial
IssuerAndSerialNumber issuerAndSerial `asn1:"optional"`
SubjectKeyIdentifier asn1.RawValue `asn1:"tag:0,optional"`
KeyEncryptionAlgorithm pkix.AlgorithmIdentifier
EncryptedKey []byte
}
Expand All @@ -43,16 +48,39 @@ func (data envelopedData) GetRecipient(cert *smx509.Certificate) *recipientInfo
for _, recp := range data.RecipientInfos {
if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) {
return &recp
} else if len(recp.SubjectKeyIdentifier.Bytes) > 0 {
// This is for the case when the recipient is identified by the SubjectKeyId instead of the IssuerAndSerial
subjectKeyID := cert.SubjectKeyId
// SubjectKeyId is optional, so we need to check if it's set before comparing
if len(subjectKeyID) == 0 {
var (
inner cryptobyte.String
pub asn1.BitString
)
input := cryptobyte.String(cert.RawSubjectPublicKeyInfo)
if input.ReadASN1(&inner, cryptobyte_asn1.SEQUENCE) &&
input.Empty() &&
inner.SkipASN1(cryptobyte_asn1.SEQUENCE) &&
inner.ReadASN1BitString(&pub) &&
inner.Empty() {
h := sha1.Sum(pub.RightAlign())
subjectKeyID = h[:]
}
}

if len(subjectKeyID) > 0 && bytes.Equal(subjectKeyID, recp.SubjectKeyIdentifier.Bytes) {
return &recp
}
}
}
return nil
}

// GetRecipients returns the list of recipients (READONLY) for the enveloped data
func (data envelopedData) GetRecipients() ([]IssuerAndSerial, error) {
var recipients []IssuerAndSerial
func (data envelopedData) GetRecipients() ([]RecipientInfo, error) {
var recipients []RecipientInfo
for _, recp := range data.RecipientInfos {
recipients = append(recipients, newIssuerAndSerial(recp.IssuerAndSerialNumber))
recipients = append(recipients, newRecipientInfo(recp))
}
return recipients, nil
}
Expand Down Expand Up @@ -92,7 +120,7 @@ func Encrypt(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificat
//
// The algorithm used to perform encryption is determined by the argument cipher
func EncryptSM(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate) ([]byte, error) {
return encryptSM(cipher, content, recipients, false)
return encryptSM(cipher, content, recipients, 1, false)
}

// EncryptCFCA creates and returns an envelope data PKCS7 structure with encrypted
Expand All @@ -101,16 +129,25 @@ func EncryptSM(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certific
//
// The algorithm used to perform encryption is determined by the argument cipher
func EncryptCFCA(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate) ([]byte, error) {
return encryptSM(cipher, content, recipients, true)
return encryptSM(cipher, content, recipients, 1, true)
}

// EnvelopeMessageCFCA creates and returns an envelope data PKCS7 structure with encrypted
// recipient keys for each recipient public key.
// The OIDs use GM/T 0010 - 2012 set and the encrypted key uses ASN.1 format.
// This function uses recipient's SubjectKeyIdentifier to identify the recipient.
// This function is used for CFCA compatibility.
func EnvelopeMessageCFCA(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate) ([]byte, error) {
return encryptSM(cipher, content, recipients, 2, false)
}

func encryptSM(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate, isLegacyCFCA bool) ([]byte, error) {
func encryptSM(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate, version int, isLegacyCFCA bool) ([]byte, error) {
ed, err := NewSM2EnvelopedData(cipher, content)
if err != nil {
return nil, err
}
for _, recipient := range recipients {
if err := ed.AddRecipient(recipient, 1, func(cert *smx509.Certificate, key []byte) ([]byte, error) {
if err := ed.AddRecipient(recipient, version, func(cert *smx509.Certificate, key []byte) ([]byte, error) {
return encryptKey(key, cert, isLegacyCFCA)
}); err != nil {
return nil, err
Expand All @@ -121,33 +158,16 @@ func encryptSM(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certific

// NewEnvelopedData creates a new EnvelopedData structure with the provided cipher and content.
func NewEnvelopedData(cipher pkcs.Cipher, content []byte) (*EnvelopedData, error) {
var key []byte
var err error

// Create key
key = make([]byte, cipher.KeySize())
if _, err = rand.Read(key); err != nil {
return nil, err
}

id, ciphertext, err := cipher.Encrypt(rand.Reader, key, content)
if err != nil {
return nil, err
}
ed := &EnvelopedData{}
ed.contentType = OIDEnvelopedData
ed.encryptedContentType = OIDData
ed.key = key
ed.ed = envelopedData{
Version: 0,
EncryptedContentInfo: newEncryptedContent(ed.encryptedContentType, id, marshalEncryptedContent(ciphertext)),
}
return ed, nil
return newEnvelopedData(cipher, content, OIDEnvelopedData)
}

// NewSM2EnvelopedData creates a new EnvelopedData structure with the provided cipher and content.
// The OIDs use GM/T 0010 - 2012 set.
func NewSM2EnvelopedData(cipher pkcs.Cipher, content []byte) (*EnvelopedData, error) {
return newEnvelopedData(cipher, content, SM2OIDEnvelopedData)
}

func newEnvelopedData(cipher pkcs.Cipher, content []byte, contentType asn1.ObjectIdentifier) (*EnvelopedData, error) {
var key []byte
var err error

Expand All @@ -162,23 +182,30 @@ func NewSM2EnvelopedData(cipher pkcs.Cipher, content []byte) (*EnvelopedData, er
return nil, err
}
ed := &EnvelopedData{}
ed.contentType = SM2OIDEnvelopedData
ed.encryptedContentType = SM2OIDData
ed.contentType = contentType
ed.encryptedContentType = OIDData
version := 0
if SM2OIDEnvelopedData.Equal(contentType) {
ed.encryptedContentType = SM2OIDData
version = 1
}
ed.key = key
ed.ed = envelopedData{
Version: 1,
Version: version,
EncryptedContentInfo: newEncryptedContent(ed.encryptedContentType, id, marshalEncryptedContent(ciphertext)),
}
return ed, nil
}

// AddRecipient adds a recipient to the EnvelopedData structure.
// version 0: IssuerAndSerialNumber
// version 1: SM2
// version 2: SubjectKeyIdentifier
func (ed *EnvelopedData) AddRecipient(cert *smx509.Certificate, version int, encryptKeyFunc func(cert *smx509.Certificate, key []byte) ([]byte, error)) error {
encrypted, err := encryptKeyFunc(cert, ed.key)
if err != nil {
return err
if version < 0 || version > 2 {
return errors.New("pkcs7: invalid recipient version")
}
ias, err := cert2issuerAndSerial(cert)
encrypted, err := encryptKeyFunc(cert, ed.key)
if err != nil {
return err
}
Expand All @@ -188,20 +215,41 @@ func (ed *EnvelopedData) AddRecipient(cert *smx509.Certificate, version int, enc
}

info := recipientInfo{
Version: version,
IssuerAndSerialNumber: ias,
Version: version,
KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{
Algorithm: keyEncryptionAlgorithm,
Parameters: asn1.NullRawValue,
},
EncryptedKey: encrypted,
}

if version == 2 {
if len(cert.SubjectKeyId) == 0 {
return errors.New("pkcs7: envelope required certificate extension SubjectKeyIdentifier")
}
info.SubjectKeyIdentifier = asn1.RawValue{Tag: 0, Class: asn1.ClassContextSpecific, Bytes: cert.SubjectKeyId}
} else {
ias, err := cert2issuerAndSerial(cert)
if err != nil {
return err
}
info.IssuerAndSerialNumber = ias
}
ed.ed.RecipientInfos = append(ed.ed.RecipientInfos, info)

return nil
}

// Finish creates the final PKCS7 structure.
func (ed *EnvelopedData) Finish() ([]byte, error) {
// Check if we need to upgrade the version to 2
for _, recp := range ed.ed.RecipientInfos {
if recp.Version == 2 {
ed.ed.Version = 2
break
}
}

innerContent, err := asn1.Marshal(ed.ed)
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit 0205d1a

Please sign in to comment.