diff --git a/docs/sm2.md b/docs/sm2.md index e144941..9927e9f 100644 --- a/docs/sm2.md +++ b/docs/sm2.md @@ -24,6 +24,12 @@ SM2既然是椭圆曲线公钥密码算法,它就和NIST P系列椭圆曲线 **注**:最新的阿里KMS支持ECIES,难道客户有这个需求? ECIES_DH_SHA_1_XOR_HMAC:遵循[SEC 1: Elliptic Curve Cryptography, Version 2.0](https://www.secg.org/sec1-v2.pdf)标准,密钥协商算法采用ECDH,密钥派生算法采用 KDF2 with SHA-1,MAC算法采用HMAC-SHA-1,对称加密算法采用XOR。 +**业界对RSA非对称加密的安全性担忧与日俱增**: +* [The Marvin Attack](https://people.redhat.com/~hkario/marvin/) +* [CVE-2023-45287 Detail](https://nvd.nist.gov/vuln/detail/CVE-2023-45287) +* [Vulnerability Report: GO-2023-2375](https://pkg.go.dev/vuln/GO-2023-2375) +* [Seriously, stop using RSA](https://blog.trailofbits.com/2019/07/08/fuck-rsa/) + ## SM2公私钥对 SM2公私钥对的话,要么是自己产生,要么是别的系统产生后通过某种方式传输给您的。 diff --git a/pkcs/cipher.go b/pkcs/cipher.go index 5f5e665..4c049f7 100644 --- a/pkcs/cipher.go +++ b/pkcs/cipher.go @@ -115,7 +115,7 @@ func (c *cbcBlockCipher) Encrypt(key, plaintext []byte) (*pkix.AlgorithmIdentifi if err != nil { return nil, nil, err } - ciphertext, err := cbcEncrypt(block, key, iv, plaintext) + ciphertext, err := cbcEncrypt(block, iv, plaintext) if err != nil { return nil, nil, err } @@ -144,10 +144,10 @@ func (c *cbcBlockCipher) Decrypt(key []byte, parameters *asn1.RawValue, encrypte return nil, errors.New("pkcs: invalid cipher parameters") } - return cbcDecrypt(block, key, iv, encryptedKey) + return cbcDecrypt(block, iv, encryptedKey) } -func cbcEncrypt(block cipher.Block, key, iv, plaintext []byte) ([]byte, error) { +func cbcEncrypt(block cipher.Block, iv, plaintext []byte) ([]byte, error) { mode := cipher.NewCBCEncrypter(block, iv) pkcs7 := padding.NewPKCS7Padding(uint(block.BlockSize())) plainText := pkcs7.Pad(plaintext) @@ -156,7 +156,7 @@ func cbcEncrypt(block cipher.Block, key, iv, plaintext []byte) ([]byte, error) { return ciphertext, nil } -func cbcDecrypt(block cipher.Block, key, iv, ciphertext []byte) ([]byte, error) { +func cbcDecrypt(block cipher.Block, iv, ciphertext []byte) ([]byte, error) { mode := cipher.NewCBCDecrypter(block, iv) pkcs7 := padding.NewPKCS7Padding(uint(block.BlockSize())) plaintext := make([]byte, len(ciphertext)) diff --git a/pkcs7/encrypt.go b/pkcs7/encrypt.go index 465e5c9..c3043f8 100644 --- a/pkcs7/encrypt.go +++ b/pkcs7/encrypt.go @@ -1,187 +1,35 @@ package pkcs7 import ( - "crypto/ecdsa" - "crypto/rand" - "crypto/rsa" - "crypto/x509/pkix" "encoding/asn1" "errors" "github.com/emmansun/gmsm/pkcs" - "github.com/emmansun/gmsm/sm2" - "github.com/emmansun/gmsm/smx509" ) -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"` -} - -func (data envelopedData) GetRecipient(cert *smx509.Certificate) *recipientInfo { - for _, recp := range data.RecipientInfos { - if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) { - return &recp - } - } - return 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") - // 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") -// 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) { - return encrypt(cipher, content, recipients, false, false) -} - -// 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 encrypt(cipher, content, recipients, true, 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 encrypt(cipher, content, recipients, true, true) -} - -func encrypt(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate, isSM, isCFCA bool) ([]byte, error) { - var key []byte - var err error - - // Create key - key = make([]byte, cipher.KeySize()) - _, err = rand.Read(key) - if err != nil { - return nil, err - } - - id, ciphertext, err := cipher.Encrypt(key, content) - if err != nil { - return nil, err - } - - envelope := envelopedData{ - Version: 0, - EncryptedContentInfo: encryptedContentInfo{ - ContentType: OIDData, - ContentEncryptionAlgorithm: *id, - EncryptedContent: marshalEncryptedContent(ciphertext), - }, - } - - if isSM { - envelope.Version = 1 // follow GB/T 35275-2017 9.1 - envelope.EncryptedContentInfo.ContentType = SM2OIDData - } - - // Prepare each recipient's encrypted cipher key - recipientInfos := make([]recipientInfo, len(recipients)) - for i, recipient := range recipients { - encrypted, err := encryptKey(key, recipient, isCFCA) - if err != nil { - return nil, err - } - ias, err := cert2issuerAndSerial(recipient) - if err != nil { - return nil, err - } - var keyEncryptionAlgorithm asn1.ObjectIdentifier = OIDEncryptionAlgorithmRSA - if recipient.SignatureAlgorithm == smx509.SM2WithSM3 { - keyEncryptionAlgorithm = OIDKeyEncryptionAlgorithmSM2 - } else if isSM { - return nil, errors.New("pkcs7: Shangmi does not support RSA") - } - - info := recipientInfo{ - Version: 0, - IssuerAndSerialNumber: ias, - KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: keyEncryptionAlgorithm, - }, - EncryptedKey: encrypted, - } - if isSM { - info.Version = 1 // follow GB/T 35275-2017 9.1 - } - recipientInfos[i] = info - } - - envelope.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: asn1.ClassContextSpecific, Tag: 0, IsCompound: true, Bytes: innerContent}, - } - - if isSM { - wrapper.ContentType = SM2OIDEnvelopedData - } - - return asn1.Marshal(wrapper) -} - // EncryptUsingPSK creates and returns an encrypted data PKCS7 structure, // encrypted using caller provided pre-shared secret. func EncryptUsingPSK(cipher pkcs.Cipher, content []byte, key []byte) ([]byte, error) { - return encryptUsingPSK(false, cipher, content, key) + return encryptUsingPSK(cipher, content, key, []asn1.ObjectIdentifier{OIDData, OIDEncryptedData}, 0) } // EncryptSMUsingPSK creates and returns an encrypted data PKCS7 structure, // encrypted using caller provided pre-shared secret. // This method uses China Standard OID func EncryptSMUsingPSK(cipher pkcs.Cipher, content []byte, key []byte) ([]byte, error) { - return encryptUsingPSK(true, cipher, content, key) + return encryptUsingPSK(cipher, content, key, []asn1.ObjectIdentifier{SM2OIDData, SM2OIDEncryptedData}, 1) } -func encryptUsingPSK(isSM bool, cipher pkcs.Cipher, content []byte, key []byte) ([]byte, error) { +func encryptUsingPSK(cipher pkcs.Cipher, content []byte, key []byte, contentTypes []asn1.ObjectIdentifier, version int) ([]byte, error) { var err error if key == nil { @@ -195,16 +43,8 @@ func encryptUsingPSK(isSM bool, cipher pkcs.Cipher, content []byte, key []byte) // Prepare encrypted-data content ed := encryptedData{ - Version: 0, - EncryptedContentInfo: encryptedContentInfo{ - ContentType: OIDData, - ContentEncryptionAlgorithm: *id, - EncryptedContent: marshalEncryptedContent(ciphertext), - }, - } - if isSM { - ed.Version = 1 // follow GB/T 35275-2017 9.1 - ed.EncryptedContentInfo.ContentType = SM2OIDData + Version: version, + EncryptedContentInfo: newEncryptedContent(contentTypes[0], id, marshalEncryptedContent(ciphertext)), } innerContent, err := asn1.Marshal(ed) @@ -212,38 +52,11 @@ func encryptUsingPSK(isSM bool, cipher pkcs.Cipher, content []byte, key []byte) return nil, err } - var contentType asn1.ObjectIdentifier = OIDEncryptedData - if isSM { - contentType = SM2OIDEncryptedData - } // Prepare outer payload structure wrapper := contentInfo{ - ContentType: contentType, + ContentType: contentTypes[1], Content: asn1.RawValue{Class: asn1.ClassContextSpecific, 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: asn1.ClassContextSpecific, Bytes: asn1Content, IsCompound: true} -} - -func encryptKey(key []byte, recipient *smx509.Certificate, isCFCA bool) ([]byte, error) { - if pub, ok := recipient.PublicKey.(*rsa.PublicKey); ok { - return rsa.EncryptPKCS1v15(rand.Reader, pub, key) - } - if pub, ok := recipient.PublicKey.(*ecdsa.PublicKey); ok && pub.Curve == sm2.P256() { - if isCFCA { - encryptedKey, err := sm2.Encrypt(rand.Reader, pub, key, sm2.NewPlainEncrypterOpts(sm2.MarshalUncompressed, sm2.C1C2C3)) - if err != nil { - return nil, err - } - return encryptedKey[1:], nil - } else { - return sm2.EncryptASN1(rand.Reader, pub, key) - } - } - return nil, errors.New("pkcs7: only supports RSA/SM2 key") -} diff --git a/pkcs7/encrypt_test.go b/pkcs7/encrypt_test.go index c316bd8..bfe75d4 100644 --- a/pkcs7/encrypt_test.go +++ b/pkcs7/encrypt_test.go @@ -2,131 +2,11 @@ package pkcs7 import ( "bytes" - "crypto/x509" - "encoding/pem" - "os" "testing" "github.com/emmansun/gmsm/pkcs" - "github.com/emmansun/gmsm/smx509" ) -func TestEncrypt(t *testing.T) { - ciphers := []pkcs.Cipher{ - pkcs.DESCBC, - pkcs.TripleDESCBC, - pkcs.SM4CBC, - pkcs.SM4GCM, - pkcs.AES128CBC, - pkcs.AES192CBC, - pkcs.AES256CBC, - pkcs.AES128GCM, - pkcs.AES192GCM, - pkcs.AES256GCM, - } - sigalgs := []x509.SignatureAlgorithm{ - x509.SHA1WithRSA, - x509.SHA256WithRSA, - x509.SHA512WithRSA, - smx509.SM2WithSM3, - } - for _, cipher := range ciphers { - for _, sigalg := range sigalgs { - plaintext := []byte("Hello Secret World!") - cert, err := createTestCertificate(sigalg) - if err != nil { - t.Fatal(err) - } - encrypted, err := Encrypt(cipher, plaintext, []*smx509.Certificate{cert.Certificate}) - if err != nil { - t.Fatal(err) - } - p7, err := Parse(encrypted) - if err != nil { - t.Fatalf("cannot Parse encrypted result: %s", err) - } - result, err := p7.Decrypt(cert.Certificate, *cert.PrivateKey) - if err != nil { - t.Fatalf("cannot Decrypt encrypted result: %s", err) - } - if !bytes.Equal(plaintext, result) { - t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) - } - } - } -} - -func TestEncryptSM(t *testing.T) { - ciphers := []pkcs.Cipher{ - pkcs.SM4CBC, - pkcs.SM4GCM, - } - sigalgs := []x509.SignatureAlgorithm{ - smx509.SM2WithSM3, - } - for _, cipher := range ciphers { - for _, sigalg := range sigalgs { - plaintext := []byte("Hello Secret World!") - cert, err := createTestCertificate(sigalg) - if err != nil { - t.Fatal(err) - } - encrypted, err := EncryptSM(cipher, plaintext, []*smx509.Certificate{cert.Certificate}) - if err != nil { - t.Fatal(err) - } - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: encrypted}) - p7, err := Parse(encrypted) - if err != nil { - t.Fatalf("cannot Parse encrypted result: %s", err) - } - result, err := p7.Decrypt(cert.Certificate, *cert.PrivateKey) - if err != nil { - t.Fatalf("cannot Decrypt encrypted result: %s", err) - } - if !bytes.Equal(plaintext, result) { - t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) - } - } - } -} - -func TestEncryptCFCA(t *testing.T) { - ciphers := []pkcs.Cipher{ - pkcs.SM4, - pkcs.SM4CBC, - pkcs.SM4GCM, - } - sigalgs := []x509.SignatureAlgorithm{ - smx509.SM2WithSM3, - } - for _, cipher := range ciphers { - for _, sigalg := range sigalgs { - plaintext := []byte("Hello Secret World!") - cert, err := createTestCertificate(sigalg) - if err != nil { - t.Fatal(err) - } - encrypted, err := EncryptCFCA(cipher, plaintext, []*smx509.Certificate{cert.Certificate}) - if err != nil { - t.Fatal(err) - } - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: encrypted}) - p7, err := Parse(encrypted) - if err != nil { - t.Fatalf("cannot Parse encrypted result: %s", err) - } - result, err := p7.DecryptCFCA(cert.Certificate, *cert.PrivateKey) - if err != nil { - t.Fatalf("cannot Decrypt encrypted result: %s", err) - } - if !bytes.Equal(plaintext, result) { - t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) - } - } - } -} - func TestEncryptUsingPSK(t *testing.T) { ciphers := []pkcs.Cipher{ pkcs.DESCBC, diff --git a/pkcs7/envelope.go b/pkcs7/envelope.go new file mode 100644 index 0000000..c92d6fa --- /dev/null +++ b/pkcs7/envelope.go @@ -0,0 +1,237 @@ +package pkcs7 + +import ( + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + + "github.com/emmansun/gmsm/pkcs" + "github.com/emmansun/gmsm/sm2" + "github.com/emmansun/gmsm/smx509" +) + +type EnvelopedData struct { + ed envelopedData + key []byte + contentType asn1.ObjectIdentifier + encryptedContentType asn1.ObjectIdentifier +} + +type envelopedData struct { + Version int + RecipientInfos []recipientInfo `asn1:"set"` + 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"` +} + +func (data envelopedData) GetRecipient(cert *smx509.Certificate) *recipientInfo { + for _, recp := range data.RecipientInfos { + if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) { + return &recp + } + } + return 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 encryptKey(key, cert, false) + }); 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, 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, true) +} + +func encryptSM(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate, 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) { + return encryptKey(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) { + 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(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 +} + +// 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) { + 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(key, content) + if err != nil { + return nil, err + } + ed := &EnvelopedData{} + ed.contentType = SM2OIDEnvelopedData + ed.encryptedContentType = SM2OIDData + ed.key = key + ed.ed = envelopedData{ + Version: 1, + EncryptedContentInfo: newEncryptedContent(ed.encryptedContentType, id, marshalEncryptedContent(ciphertext)), + } + return ed, nil +} + +// AddRecipient adds a recipient to the EnvelopedData structure. +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 + } + ias, err := cert2issuerAndSerial(cert) + if err != nil { + return err + } + var keyEncryptionAlgorithm asn1.ObjectIdentifier = OIDEncryptionAlgorithmRSA + if cert.SignatureAlgorithm == smx509.SM2WithSM3 { + keyEncryptionAlgorithm = OIDKeyEncryptionAlgorithmSM2 + } + + info := recipientInfo{ + Version: version, + IssuerAndSerialNumber: ias, + KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: keyEncryptionAlgorithm, + }, + EncryptedKey: encrypted, + } + ed.ed.RecipientInfos = append(ed.ed.RecipientInfos, info) + return nil +} + +// Finish creates the final PKCS7 structure. +func (ed *EnvelopedData) Finish() ([]byte, error) { + 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 { + asn1Content, _ := asn1.Marshal(content) + return asn1.RawValue{Tag: 0, Class: asn1.ClassContextSpecific, Bytes: asn1Content, IsCompound: true} +} + +func encryptKey(key []byte, recipient *smx509.Certificate, isCFCA bool) ([]byte, error) { + if pub, ok := recipient.PublicKey.(*rsa.PublicKey); ok { + return rsa.EncryptPKCS1v15(rand.Reader, pub, key) + } + if pub, ok := recipient.PublicKey.(*ecdsa.PublicKey); ok && pub.Curve == sm2.P256() { + if isCFCA { + encryptedKey, err := sm2.Encrypt(rand.Reader, pub, key, sm2.NewPlainEncrypterOpts(sm2.MarshalUncompressed, sm2.C1C2C3)) + if err != nil { + return nil, err + } + return encryptedKey[1:], nil + } else { + return sm2.EncryptASN1(rand.Reader, pub, key) + } + } + return nil, errors.New("pkcs7: only supports RSA/SM2 key") +} diff --git a/pkcs7/envelope_test.go b/pkcs7/envelope_test.go new file mode 100644 index 0000000..687527f --- /dev/null +++ b/pkcs7/envelope_test.go @@ -0,0 +1,128 @@ +package pkcs7 + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "os" + "testing" + + "github.com/emmansun/gmsm/pkcs" + "github.com/emmansun/gmsm/smx509" +) + +func TestEncrypt(t *testing.T) { + ciphers := []pkcs.Cipher{ + pkcs.DESCBC, + pkcs.TripleDESCBC, + pkcs.SM4CBC, + pkcs.SM4GCM, + pkcs.AES128CBC, + pkcs.AES192CBC, + pkcs.AES256CBC, + pkcs.AES128GCM, + pkcs.AES192GCM, + pkcs.AES256GCM, + } + sigalgs := []x509.SignatureAlgorithm{ + x509.SHA1WithRSA, + x509.SHA256WithRSA, + x509.SHA512WithRSA, + smx509.SM2WithSM3, + } + for _, cipher := range ciphers { + for _, sigalg := range sigalgs { + plaintext := []byte("Hello Secret World!") + cert, err := createTestCertificate(sigalg) + if err != nil { + t.Fatal(err) + } + encrypted, err := Encrypt(cipher, plaintext, []*smx509.Certificate{cert.Certificate}) + if err != nil { + t.Fatal(err) + } + p7, err := Parse(encrypted) + if err != nil { + t.Fatalf("cannot Parse encrypted result: %s", err) + } + result, err := p7.Decrypt(cert.Certificate, *cert.PrivateKey) + if err != nil { + t.Fatalf("cannot Decrypt encrypted result: %s", err) + } + if !bytes.Equal(plaintext, result) { + t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) + } + } + } +} + +func TestEncryptSM(t *testing.T) { + ciphers := []pkcs.Cipher{ + pkcs.SM4CBC, + pkcs.SM4GCM, + } + sigalgs := []x509.SignatureAlgorithm{ + smx509.SM2WithSM3, + } + for _, cipher := range ciphers { + for _, sigalg := range sigalgs { + plaintext := []byte("Hello Secret World!") + cert, err := createTestCertificate(sigalg) + if err != nil { + t.Fatal(err) + } + encrypted, err := EncryptSM(cipher, plaintext, []*smx509.Certificate{cert.Certificate}) + if err != nil { + t.Fatal(err) + } + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: encrypted}) + p7, err := Parse(encrypted) + if err != nil { + t.Fatalf("cannot Parse encrypted result: %s", err) + } + result, err := p7.Decrypt(cert.Certificate, *cert.PrivateKey) + if err != nil { + t.Fatalf("cannot Decrypt encrypted result: %s", err) + } + if !bytes.Equal(plaintext, result) { + t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) + } + } + } +} + +func TestEncryptCFCA(t *testing.T) { + ciphers := []pkcs.Cipher{ + pkcs.SM4, + pkcs.SM4CBC, + pkcs.SM4GCM, + } + sigalgs := []x509.SignatureAlgorithm{ + smx509.SM2WithSM3, + } + for _, cipher := range ciphers { + for _, sigalg := range sigalgs { + plaintext := []byte("Hello Secret World!") + cert, err := createTestCertificate(sigalg) + if err != nil { + t.Fatal(err) + } + encrypted, err := EncryptCFCA(cipher, plaintext, []*smx509.Certificate{cert.Certificate}) + if err != nil { + t.Fatal(err) + } + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: encrypted}) + p7, err := Parse(encrypted) + if err != nil { + t.Fatalf("cannot Parse encrypted result: %s", err) + } + result, err := p7.DecryptCFCA(cert.Certificate, *cert.PrivateKey) + if err != nil { + t.Fatalf("cannot Decrypt encrypted result: %s", err) + } + if !bytes.Equal(plaintext, result) { + t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) + } + } + } +} diff --git a/pkcs7/sign.go b/pkcs7/sign.go index 1ed0a85..ade379c 100644 --- a/pkcs7/sign.go +++ b/pkcs7/sign.go @@ -22,9 +22,9 @@ type SignedData struct { sd signedData certs []*smx509.Certificate data, messageDigest []byte + contentTypeOid asn1.ObjectIdentifier digestOid asn1.ObjectIdentifier encryptionOid asn1.ObjectIdentifier - isSM bool } // NewSignedData takes data and initializes a PKCS7 SignedData struct that is @@ -43,7 +43,7 @@ func NewSignedData(data []byte) (*SignedData, error) { ContentInfo: ci, Version: 1, } - return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA1, isSM: false}, nil + return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA1, contentTypeOid: OIDSignedData}, nil } // NewSMSignedData takes data and initializes a PKCS7 SignedData struct that is @@ -56,7 +56,7 @@ func NewSMSignedData(data []byte) (*SignedData, error) { } sd.sd.ContentInfo.ContentType = SM2OIDData sd.digestOid = OIDDigestAlgorithmSM3 - sd.isSM = true + sd.contentTypeOid = SM2OIDSignedData return sd, nil } @@ -300,10 +300,7 @@ func (sd *SignedData) AddCertificate(cert *smx509.Certificate) { // Detach removes content from the signed data struct to make it a detached signature. // This must be called right before Finish() func (sd *SignedData) Detach() { - sd.sd.ContentInfo = contentInfo{ContentType: OIDData} - if sd.isSM { - sd.sd.ContentInfo.ContentType = SM2OIDData - } + sd.sd.ContentInfo.Content = asn1.RawValue{} } // GetSignedData returns the private Signed Data @@ -321,12 +318,9 @@ func (sd *SignedData) Finish() ([]byte, error) { return nil, err } outer := contentInfo{ - ContentType: OIDSignedData, + ContentType: sd.contentTypeOid, Content: asn1.RawValue{Class: asn1.ClassContextSpecific, Tag: 0, Bytes: inner, IsCompound: true}, } - if sd.isSM { - outer.ContentType = SM2OIDSignedData - } return asn1.Marshal(outer) } diff --git a/pkcs7/sign_enveloped.go b/pkcs7/sign_enveloped.go index 3e2476c..c3bbfde 100644 --- a/pkcs7/sign_enveloped.go +++ b/pkcs7/sign_enveloped.go @@ -126,11 +126,11 @@ func (p7 *PKCS7) decryptSED(sed *signedEnvelopedData, recipient *recipientInfo, // SignedAndEnvelopedData is an opaque data structure for creating signed and enveloped data payloads type SignedAndEnvelopedData struct { - sed signedEnvelopedData - certs []*smx509.Certificate - data, cek []byte - digestOid asn1.ObjectIdentifier - isSM bool + sed signedEnvelopedData + certs []*smx509.Certificate + data, cek []byte + contentTypeOid asn1.ObjectIdentifier + digestOid asn1.ObjectIdentifier } // NewSignedAndEnvelopedData takes data and cipher and initializes a new PKCS7 SignedAndEnvelopedData structure @@ -160,7 +160,7 @@ func NewSignedAndEnvelopedData(data []byte, cipher pkcs.Cipher) (*SignedAndEnvel EncryptedContent: marshalEncryptedContent(ciphertext), }, } - return &SignedAndEnvelopedData{sed: sed, data: data, cek: key, digestOid: OIDDigestAlgorithmSHA1, isSM: false}, nil + return &SignedAndEnvelopedData{sed: sed, data: data, cek: key, digestOid: OIDDigestAlgorithmSHA1, contentTypeOid: OIDSignedEnvelopedData}, nil } // NewSMSignedAndEnvelopedData takes data and cipher and initializes a new PKCS7(SM) SignedAndEnvelopedData structure @@ -170,8 +170,8 @@ func NewSMSignedAndEnvelopedData(data []byte, cipher pkcs.Cipher) (*SignedAndEnv if err != nil { return nil, err } + sd.contentTypeOid = SM2OIDSignedEnvelopedData sd.digestOid = OIDDigestAlgorithmSM3 - sd.isSM = true sd.sed.EncryptedContentInfo.ContentType = SM2OIDData return sd, nil } @@ -223,10 +223,11 @@ func (saed *SignedAndEnvelopedData) AddSignerChain(ee *smx509.Certificate, pkey if !ok { return errors.New("pkcs7: private key does not implement crypto.Signer") } + var signOpt crypto.SignerOpts var tobeSigned []byte - if saed.isSM { + if _, isSM2 := pkey.(sm2.Signer); isSM2 { signOpt = sm2.DefaultSM2SignerOpts tobeSigned = saed.data } else { @@ -261,7 +262,7 @@ func (saed *SignedAndEnvelopedData) AddCertificate(cert *smx509.Certificate) { // AddRecipient adds a recipient to the payload func (saed *SignedAndEnvelopedData) AddRecipient(recipient *smx509.Certificate) error { - encryptedKey, err := encryptKey(saed.cek, recipient, false) //TODO: check if CFCA has such function + encryptedKey, err := encryptKey(saed.cek, recipient, false) //TODO: check if CFCA has such function if err != nil { return err } @@ -272,8 +273,6 @@ func (saed *SignedAndEnvelopedData) AddRecipient(recipient *smx509.Certificate) var keyEncryptionAlgorithm asn1.ObjectIdentifier = OIDEncryptionAlgorithmRSA if recipient.SignatureAlgorithm == smx509.SM2WithSM3 { keyEncryptionAlgorithm = OIDKeyEncryptionAlgorithmSM2 - } else if saed.isSM { - return errors.New("pkcs7: Shangmi does not support RSA") } info := recipientInfo{ Version: 1, @@ -295,11 +294,8 @@ func (saed *SignedAndEnvelopedData) Finish() ([]byte, error) { return nil, err } outer := contentInfo{ - ContentType: OIDSignedEnvelopedData, + ContentType: saed.contentTypeOid, Content: asn1.RawValue{Class: asn1.ClassContextSpecific, Tag: 0, Bytes: inner, IsCompound: true}, } - if saed.isSM { - outer.ContentType = SM2OIDSignedEnvelopedData - } return asn1.Marshal(outer) }