mirror of
https://github.com/emmansun/gmsm.git
synced 2025-04-21 17:56:19 +08:00
288 lines
9.7 KiB
Go
288 lines
9.7 KiB
Go
package pkcs7
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"errors"
|
|
|
|
"github.com/emmansun/gmsm/pkcs"
|
|
"github.com/emmansun/gmsm/smx509"
|
|
"golang.org/x/crypto/cryptobyte"
|
|
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
|
|
)
|
|
|
|
type EnvelopedData struct {
|
|
ed envelopedData
|
|
key []byte
|
|
contentType asn1.ObjectIdentifier
|
|
encryptedContentType asn1.ObjectIdentifier
|
|
session Session
|
|
}
|
|
|
|
type envelopedData struct {
|
|
Version int
|
|
RecipientInfos []recipientInfo `asn1:"set"`
|
|
EncryptedContentInfo encryptedContentInfo
|
|
}
|
|
|
|
type recipientInfo struct {
|
|
Version int
|
|
IssuerAndSerialNumber issuerAndSerial `asn1:"optional"`
|
|
SubjectKeyIdentifier asn1.RawValue `asn1:"tag:0,optional"`
|
|
KeyEncryptionAlgorithm pkix.AlgorithmIdentifier
|
|
EncryptedKey []byte
|
|
}
|
|
|
|
type encryptedContentInfo struct {
|
|
ContentType asn1.ObjectIdentifier
|
|
ContentEncryptionAlgorithm pkix.AlgorithmIdentifier
|
|
EncryptedContent asn1.RawValue `asn1:"tag:0,optional"`
|
|
}
|
|
|
|
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() ([]RecipientInfo, error) {
|
|
var recipients []RecipientInfo
|
|
for _, recp := range data.RecipientInfos {
|
|
recipients = append(recipients, newRecipientInfo(recp))
|
|
}
|
|
return recipients, nil
|
|
}
|
|
|
|
func (data envelopedData) GetEncryptedContentInfo() *encryptedContentInfo {
|
|
return &data.EncryptedContentInfo
|
|
}
|
|
|
|
// 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, AES-GCM, SM4-CBC and SM4-GCM supported")
|
|
|
|
// 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 argument cipher
|
|
//
|
|
// TODO(fullsailor): Add support for encrypting content with other algorithms
|
|
func Encrypt(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate) ([]byte, error) {
|
|
ed, err := NewEnvelopedData(cipher, content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, recipient := range recipients {
|
|
if err := ed.AddRecipient(recipient, 0, func(cert *smx509.Certificate, key []byte) ([]byte, error) {
|
|
return ed.session.EncryptdDataKey(key, cert, nil)
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return ed.Finish()
|
|
}
|
|
|
|
// EncryptSM 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 use ASN.1 format.
|
|
//
|
|
// 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, 1, false)
|
|
}
|
|
|
|
// EncryptCFCA 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 use C1C2C3 format and without 0x4 prefix.
|
|
//
|
|
// 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, 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, 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, version, func(cert *smx509.Certificate, key []byte) ([]byte, error) {
|
|
return ed.session.EncryptdDataKey(key, cert, isLegacyCFCA)
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return ed.Finish()
|
|
}
|
|
|
|
// NewEnvelopedData creates a new EnvelopedData structure with the provided cipher and content.
|
|
func NewEnvelopedData(cipher pkcs.Cipher, content []byte) (*EnvelopedData, error) {
|
|
return newEnvelopedData(cipher, content, OIDEnvelopedData, nil)
|
|
}
|
|
|
|
// 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, nil)
|
|
}
|
|
|
|
// NewEnvelopedDataWithSession creates a new EnvelopedData structure with the provided cipher, content and sessionKey.
|
|
func NewEnvelopedDataWithSession(cipher pkcs.Cipher, content []byte, session Session) (*EnvelopedData, error) {
|
|
return newEnvelopedData(cipher, content, OIDEnvelopedData, session)
|
|
}
|
|
|
|
// NewSM2EnvelopedDataWithSession creates a new EnvelopedData structure with the provided cipher, content and sessionKey.
|
|
// The OIDs use GM/T 0010 - 2012 set.
|
|
func NewSM2EnvelopedDataWithSession(cipher pkcs.Cipher, content []byte, session Session) (*EnvelopedData, error) {
|
|
return newEnvelopedData(cipher, content, SM2OIDEnvelopedData, session)
|
|
}
|
|
|
|
func newEnvelopedData(cipher pkcs.Cipher, content []byte, contentType asn1.ObjectIdentifier, session Session) (*EnvelopedData, error) {
|
|
ed := &EnvelopedData{}
|
|
ed.session = session
|
|
if ed.session == nil {
|
|
ed.session = DefaultSession{}
|
|
}
|
|
|
|
key, err := ed.session.GenerateDataKey(cipher.KeySize())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
id, ciphertext, err := cipher.Encrypt(rand.Reader, key, content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ed.contentType = contentType
|
|
ed.encryptedContentType = OIDData
|
|
version := 0
|
|
if SM2OIDEnvelopedData.Equal(contentType) {
|
|
ed.encryptedContentType = SM2OIDData
|
|
version = 1
|
|
}
|
|
ed.key = key
|
|
ed.ed = envelopedData{
|
|
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: GB/T 35275-2017
|
|
// version 2: SubjectKeyIdentifier
|
|
func (ed *EnvelopedData) AddRecipient(cert *smx509.Certificate, version int, encryptKeyFunc func(cert *smx509.Certificate, key []byte) ([]byte, error)) error {
|
|
if version < 0 || version > 2 {
|
|
return errors.New("pkcs7: invalid recipient version")
|
|
}
|
|
encrypted, err := encryptKeyFunc(cert, ed.key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var keyEncryptionAlgorithm asn1.ObjectIdentifier = OIDEncryptionAlgorithmRSA
|
|
if cert.SignatureAlgorithm == smx509.SM2WithSM3 {
|
|
keyEncryptionAlgorithm = OIDKeyEncryptionAlgorithmSM2
|
|
}
|
|
|
|
info := recipientInfo{
|
|
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
|
|
}
|
|
|
|
// Prepare outer payload structure
|
|
wrapper := contentInfo{
|
|
ContentType: ed.contentType,
|
|
Content: asn1.RawValue{Class: asn1.ClassContextSpecific, Tag: 0, IsCompound: true, Bytes: innerContent},
|
|
}
|
|
return asn1.Marshal(wrapper)
|
|
}
|
|
|
|
func newEncryptedContent(contentType asn1.ObjectIdentifier, alg *pkix.AlgorithmIdentifier, ciphertext asn1.RawValue) encryptedContentInfo {
|
|
return encryptedContentInfo{
|
|
ContentType: contentType,
|
|
ContentEncryptionAlgorithm: *alg,
|
|
EncryptedContent: ciphertext,
|
|
}
|
|
}
|
|
|
|
func marshalEncryptedContent(content []byte) asn1.RawValue {
|
|
return asn1.RawValue{Tag: 0, Class: asn1.ClassContextSpecific, Bytes: content}
|
|
}
|