doc: suppliement pkcs7 document and comments

This commit is contained in:
Sun Yimin 2024-06-17 17:42:00 +08:00 committed by GitHub
parent 1ef4cf9510
commit 5b3d0a424e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 107 additions and 12 deletions

View File

@ -19,6 +19,7 @@ Go语言商用密码软件简称**GMSM**,一个安全、高性能、易于
* [SM4分组密码算法应用指南](./docs/sm4.md) * [SM4分组密码算法应用指南](./docs/sm4.md)
* [SM9标识密码算法应用指南](./docs/sm9.md) * [SM9标识密码算法应用指南](./docs/sm9.md)
* [CFCA互操作性指南](./docs/cfca.md) * [CFCA互操作性指南](./docs/cfca.md)
* [PKCS7应用指南](./docs/pkcs7.md)
## 包结构 ## 包结构
* **SM2** - SM2椭圆曲线公钥密码算法曲线的具体实现位于[internal/sm2ec](https://github.com/emmansun/gmsm/tree/main/internal/sm2ec) package中。SM2曲线实现性能和Golang SDK中的NIST P256椭圆曲线原生实现非BoringCrypto类似也对**amd64** 和 **arm64**架构做了专门汇编优化实现,您也可以参考[SM2实现细节](https://github.com/emmansun/gmsm/wiki/SM2%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96)及相关Wiki和代码以获得更多实现细节。SM2包实现了SM2椭圆曲线公钥密码算法的数字签名算法、公钥加密算法、密钥交换算法以及《GB/T 35276-2017信息安全技术 SM2密码算法使用规范》中的密钥对保护数据格式。 * **SM2** - SM2椭圆曲线公钥密码算法曲线的具体实现位于[internal/sm2ec](https://github.com/emmansun/gmsm/tree/main/internal/sm2ec) package中。SM2曲线实现性能和Golang SDK中的NIST P256椭圆曲线原生实现非BoringCrypto类似也对**amd64** 和 **arm64**架构做了专门汇编优化实现,您也可以参考[SM2实现细节](https://github.com/emmansun/gmsm/wiki/SM2%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96)及相关Wiki和代码以获得更多实现细节。SM2包实现了SM2椭圆曲线公钥密码算法的数字签名算法、公钥加密算法、密钥交换算法以及《GB/T 35276-2017信息安全技术 SM2密码算法使用规范》中的密钥对保护数据格式。

77
docs/pkcs7.md Normal file
View File

@ -0,0 +1,77 @@
# PKCS7应用指南
本项目实现 PKCS#7/加密消息语法的子集rfc2315、rfc5652以及相应国密支持《GB/T 35275-2017 信息安全技术 SM2密码算法加密签名消息语法规范》。这是 [mozilla-services/pkcs7](https://github.com/mozilla-services/pkcs7) 的一个分支,目前[mozilla-services/pkcs7](https://github.com/mozilla-services/pkcs7)已经是弃用状态,代码仓库也已经进入存档、只读状态。
## 支持的功能
### 数字信封数据Enveloped Data
数字信封数据,使用对称加密算法加密数据,使用非对称加密加密数据密钥。支持的对称加密算法(以及模式)有
* AES128-CBC
* AES192-CBC
* AES256-CBC
* AES128-GCM
* AES192-GCM
* AES256-GCM
* DES-CBC
* 3DES-CBC
* SM4-CBC
* SM4-GCM
支持的非对称加密算法为:
* RSAPKCS1v15目前尚不支持RSAOAEP
* SM2
#### 主要方法
是否国密是指OID也使用国密体系
| 是否国密 | 加密 | 解密(先调用```Parse``` |
| :--- | :--- | :--- |
| 否 | Encrypt | Decrypt |
| 否 | EncryptUsingPSK | DecryptUsingPSK |
| 是 | EncryptSM | Decrypt |
| 是 | EncryptCFCA | DecryptCFCA |
| 是 | EncryptSMUsingPSK | DecryptUsingPSK |
关于```EncryptSM / EncryptCFCA```的区别,请参考**CFCA互操作性指南**。
带PSKPre-shared key后缀的方法其对称加密密钥由调用者提供而非随机生成。
### 签名数据Signed Data
签名数据,使用证书对应的私钥进行签名,理论上支持多个签名者,但通常使用场景都是单签。和数字信封数据类似,也分国密和非国密。
#### 创建签名数据
是否国密是指OID也使用国密体系
| 是否国密 | 方法 |
| :--- | :--- |
| 否 | ```NewSignedData``` |
| 是 | ```NewSMSignedData``` |
接着调用```AddSigner```或```AddSignerChain```方法,进行签名;可以通过```SignerInfoConfig.SkipCertificates```指定忽略证书项(最终签名数据中不包含证书项);
如果进行Detach签名则调用```Detach```方法;
最后调用```Finish```方法,序列化输出结果。
#### 验证签名
而验证的话,流程如下:
1. 调用```Parse```方法;
2. 如果是Detach签名数据则手动设置原始数据参考```testSign```方法);
3. 如果签名数据中不包含证书项,则手动设置验签证书(参考```TestSkipCertificates```
4. 调用```Verify```或```VerifyWithChain```方法。
### 签名及数字信封数据Signed and Enveloped Data
签名和数字信封数据使用场景较少有些实现用它来传输私钥譬如www.gmcert.org。具体请参考```sign_enveloped_test.go```。
The "signed and enveloped data" content type is a part of the Cryptographic Message Syntax (CMS), which is used in various Internet Standards. However, it's not recommended for use due to several reasons:
1. **Complexity**: The "signed and enveloped data" content type combines two operations - signing and enveloping (encryption). This increases the complexity of the implementation and can lead to potential security vulnerabilities if not handled correctly.
2. **Order of Operations**: The "signed and enveloped data" content type first signs the data and then encrypts it. This means that to verify the signature, the data must first be decrypted. This could potentially expose sensitive data to unauthorized parties before the signature is verified.
3. **Lack of Flexibility**: Combining signing and enveloping into a single operation reduces flexibility. It's often more useful to be able to perform these operations separately, as it allows for more varied use cases.
Instead of using the "signed and enveloped data" content type, it's generally recommended to use separate "signed data" and "enveloped data" content types. This allows the operations to be performed in the order that best suits the application's needs, and also simplifies the implementation.
#### 加密签名流程
1. 调用```NewSignedAndEnvelopedData```或者```NewSMSignedAndEnvelopedData```创建```SignedAndEnvelopedData```数据结构,此过程包含了数据加密过程;
2. 调用```AddSigner```或```AddSignerChain```方法,进行签名;
3. 调用```AddRecipient```方法用Recipient的公钥加密数据密钥
4. 最后调用```Finish```方法,序列化输出结果。
#### 解密验签流程
1. 调用```Parse```方法;
2. 调用```DecryptAndVerify```或者```DecryptAndVerifyOnlyOne```进行解密和验签。

View File

@ -154,7 +154,7 @@ func encrypt(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificat
// Prepare outer payload structure // Prepare outer payload structure
wrapper := contentInfo{ wrapper := contentInfo{
ContentType: OIDEnvelopedData, ContentType: OIDEnvelopedData,
Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, Content: asn1.RawValue{Class: asn1.ClassContextSpecific, Tag: 0, IsCompound: true, Bytes: innerContent},
} }
if isSM { if isSM {
@ -214,7 +214,7 @@ func encryptUsingPSK(isSM bool, cipher pkcs.Cipher, content []byte, key []byte)
// Prepare outer payload structure // Prepare outer payload structure
wrapper := contentInfo{ wrapper := contentInfo{
ContentType: contentType, ContentType: contentType,
Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, Content: asn1.RawValue{Class: asn1.ClassContextSpecific, Tag: 0, IsCompound: true, Bytes: innerContent},
} }
return asn1.Marshal(wrapper) return asn1.Marshal(wrapper)
@ -222,7 +222,7 @@ func encryptUsingPSK(isSM bool, cipher pkcs.Cipher, content []byte, key []byte)
func marshalEncryptedContent(content []byte) asn1.RawValue { func marshalEncryptedContent(content []byte) asn1.RawValue {
asn1Content, _ := asn1.Marshal(content) asn1Content, _ := asn1.Marshal(content)
return asn1.RawValue{Tag: 0, Class: 2, Bytes: asn1Content, IsCompound: true} return asn1.RawValue{Tag: 0, Class: asn1.ClassContextSpecific, Bytes: asn1Content, IsCompound: true}
} }
func encryptKey(key []byte, recipient *smx509.Certificate, isCFCA bool) ([]byte, error) { func encryptKey(key []byte, recipient *smx509.Certificate, isCFCA bool) ([]byte, error) {

View File

@ -214,7 +214,6 @@ func Parse(data []byte) (p7 *PKCS7, err error) {
return return
} }
// fmt.Printf("--> Content Type: %s", info.ContentType)
switch { switch {
case info.ContentType.Equal(OIDSignedData) || info.ContentType.Equal(SM2OIDSignedData): case info.ContentType.Equal(OIDSignedData) || info.ContentType.Equal(SM2OIDSignedData):
return parseSignedData(info.Content.Bytes) return parseSignedData(info.Content.Bytes)

View File

@ -64,7 +64,7 @@ func NewSMSignedData(data []byte) (*SignedData, error) {
type SignerInfoConfig struct { type SignerInfoConfig struct {
ExtraSignedAttributes []Attribute ExtraSignedAttributes []Attribute
ExtraUnsignedAttributes []Attribute ExtraUnsignedAttributes []Attribute
SkipCertificates bool SkipCertificates bool // Skip adding certificates to the payload
} }
type signedData struct { type signedData struct {
@ -322,7 +322,7 @@ func (sd *SignedData) Finish() ([]byte, error) {
} }
outer := contentInfo{ outer := contentInfo{
ContentType: OIDSignedData, ContentType: OIDSignedData,
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, Content: asn1.RawValue{Class: asn1.ClassContextSpecific, Tag: 0, Bytes: inner, IsCompound: true},
} }
if sd.isSM { if sd.isSM {
outer.ContentType = SM2OIDSignedData outer.ContentType = SM2OIDSignedData
@ -410,7 +410,7 @@ func marshalCertificates(certs []*smx509.Certificate) rawCertificates {
// RawContent, we have to encode it into the RawContent. If its missing, // RawContent, we have to encode it into the RawContent. If its missing,
// then `asn1.Marshal()` will strip out the certificate wrapper instead. // then `asn1.Marshal()` will strip out the certificate wrapper instead.
func marshalCertificateBytes(certs []byte) (rawCertificates, error) { func marshalCertificateBytes(certs []byte) (rawCertificates, error) {
var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true} var val = asn1.RawValue{Bytes: certs, Class: asn1.ClassContextSpecific, Tag: 0, IsCompound: true}
b, err := asn1.Marshal(val) b, err := asn1.Marshal(val)
if err != nil { if err != nil {
return rawCertificates{}, err return rawCertificates{}, err
@ -438,7 +438,7 @@ func DegenerateCertificate(cert []byte) ([]byte, error) {
} }
signedContent := contentInfo{ signedContent := contentInfo{
ContentType: OIDSignedData, ContentType: OIDSignedData,
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, Content: asn1.RawValue{Class: asn1.ClassContextSpecific, Tag: 0, Bytes: content, IsCompound: true},
} }
return asn1.Marshal(signedContent) return asn1.Marshal(signedContent)
} }

View File

@ -70,7 +70,7 @@ func (p7 *PKCS7) DecryptAndVerifyOnlyOne(pkey crypto.PrivateKey, verifyFunc Veri
defer func() { defer func() {
p7.Content = nil p7.Content = nil
}() }()
plaintext, err := decryptSED(p7, &sed, &sed.RecipientInfos[0], pkey) plaintext, err := p7.decryptSED(&sed, &sed.RecipientInfos[0], pkey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -97,7 +97,7 @@ func (p7 *PKCS7) DecryptAndVerify(cert *smx509.Certificate, pkey crypto.PrivateK
defer func() { defer func() {
p7.Content = nil p7.Content = nil
}() }()
plaintext, err := decryptSED(p7, &sed, recipient, pkey) plaintext, err := p7.decryptSED(&sed, recipient, pkey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -110,7 +110,7 @@ func (p7 *PKCS7) DecryptAndVerify(cert *smx509.Certificate, pkey crypto.PrivateK
return plaintext, nil return plaintext, nil
} }
func decryptSED(p7 *PKCS7, sed *signedEnvelopedData, recipient *recipientInfo, pkey crypto.PrivateKey) ([]byte, error) { func (p7 *PKCS7) decryptSED(sed *signedEnvelopedData, recipient *recipientInfo, pkey crypto.PrivateKey) ([]byte, error) {
switch pkey := pkey.(type) { switch pkey := pkey.(type) {
case crypto.Decrypter: case crypto.Decrypter:
// Generic case to handle anything that provides the crypto.Decrypter interface. // Generic case to handle anything that provides the crypto.Decrypter interface.
@ -124,6 +124,7 @@ func decryptSED(p7 *PKCS7, sed *signedEnvelopedData, recipient *recipientInfo, p
} }
} }
// SignedAndEnvelopedData is an opaque data structure for creating signed and enveloped data payloads
type SignedAndEnvelopedData struct { type SignedAndEnvelopedData struct {
sed signedEnvelopedData sed signedEnvelopedData
certs []*smx509.Certificate certs []*smx509.Certificate
@ -132,6 +133,9 @@ type SignedAndEnvelopedData struct {
isSM bool isSM bool
} }
// NewSignedAndEnvelopedData takes data and cipher and initializes a new PKCS7 SignedAndEnvelopedData structure
// that is ready to be signed via AddSigner and encrypted via AddRecipient. The digest algorithm is set to SHA1 by default
// and can be changed by calling SetDigestAlgorithm.
func NewSignedAndEnvelopedData(data []byte, cipher pkcs.Cipher) (*SignedAndEnvelopedData, error) { func NewSignedAndEnvelopedData(data []byte, cipher pkcs.Cipher) (*SignedAndEnvelopedData, error) {
var key []byte var key []byte
var err error var err error
@ -159,6 +163,8 @@ func NewSignedAndEnvelopedData(data []byte, cipher pkcs.Cipher) (*SignedAndEnvel
return &SignedAndEnvelopedData{sed: sed, data: data, cek: key, digestOid: OIDDigestAlgorithmSHA1, isSM: false}, nil return &SignedAndEnvelopedData{sed: sed, data: data, cek: key, digestOid: OIDDigestAlgorithmSHA1, isSM: false}, nil
} }
// NewSMSignedAndEnvelopedData takes data and cipher and initializes a new PKCS7(SM) SignedAndEnvelopedData structure
// that is ready to be signed via AddSigner and encrypted via AddRecipient. The digest algorithm is set to SM3 by default.
func NewSMSignedAndEnvelopedData(data []byte, cipher pkcs.Cipher) (*SignedAndEnvelopedData, error) { func NewSMSignedAndEnvelopedData(data []byte, cipher pkcs.Cipher) (*SignedAndEnvelopedData, error) {
sd, err := NewSignedAndEnvelopedData(data, cipher) sd, err := NewSignedAndEnvelopedData(data, cipher)
if err != nil { if err != nil {
@ -253,6 +259,7 @@ func (saed *SignedAndEnvelopedData) AddCertificate(cert *smx509.Certificate) {
saed.certs = append(saed.certs, cert) saed.certs = append(saed.certs, cert)
} }
// AddRecipient adds a recipient to the payload
func (saed *SignedAndEnvelopedData) AddRecipient(recipient *smx509.Certificate) error { 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 { if err != nil {
@ -289,7 +296,7 @@ func (saed *SignedAndEnvelopedData) Finish() ([]byte, error) {
} }
outer := contentInfo{ outer := contentInfo{
ContentType: OIDSignedEnvelopedData, ContentType: OIDSignedEnvelopedData,
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, Content: asn1.RawValue{Class: asn1.ClassContextSpecific, Tag: 0, Bytes: inner, IsCompound: true},
} }
if saed.isSM { if saed.isSM {
outer.ContentType = SM2OIDSignedEnvelopedData outer.ContentType = SM2OIDSignedEnvelopedData

View File

@ -67,6 +67,9 @@ func testSign(t *testing.T, isSM bool, content []byte, sigalgs []x509.SignatureA
t.Fatalf("test %s/%s/%s: cannot parse signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) t.Fatalf("test %s/%s/%s: cannot parse signed data: %s", sigalgroot, sigalginter, sigalgsigner, err)
} }
if testDetach { if testDetach {
// Detached signature should not contain the content
// So we should not be able to find the content in the parsed data
// We should suppliment the content to the parsed data before verifying
p7.Content = content p7.Content = content
} }
if !bytes.Equal(content, p7.Content) { if !bytes.Equal(content, p7.Content) {
@ -220,6 +223,14 @@ func TestSkipCertificates(t *testing.T) {
if len(p7.Certificates) > 0 { if len(p7.Certificates) > 0 {
t.Errorf("Have certificates: %v", p7.Certificates) t.Errorf("Have certificates: %v", p7.Certificates)
} }
// For skip certificates, we should not be able to verify the signature
// because the signer certificate is not in the chain
// we should suppliment the signer certificate to the parsed data before verifying
p7.Certificates = append(p7.Certificates, cert.Certificate)
err = p7.Verify()
if err != nil {
t.Fatal(err)
}
} }
func TestDegenerateCertificate(t *testing.T) { func TestDegenerateCertificate(t *testing.T) {