pkcs7: sign precomputed digest #294

This commit is contained in:
Sun Yimin 2025-01-14 16:41:03 +08:00 committed by GitHub
parent 5a0ff81dc3
commit bef7f5421e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 664 additions and 31 deletions

View File

@ -1,4 +1,4 @@
// Package cfca supports part of CFCA SADK's functions. // Package cfca supports part of CFCA SADK's functions, provides interoperability with CFCA SADK.
package cfca package cfca
import ( import (

View File

@ -56,3 +56,28 @@ func VerifyMessageDetach(p7Der, sourceData []byte) error {
p7.Content = sourceData p7.Content = sourceData
return p7.Verify() return p7.Verify()
} }
// SignDigestDetach signs a given digest using the provided certificate and private key,
// and returns the detached PKCS7 signature.
//
// This method corresponds to CFCA SADK's cfca.sadk.util.p7SignByHash.
func SignDigestDetach(digest []byte, cert *smx509.Certificate, key crypto.PrivateKey) ([]byte, error) {
signData, _ := pkcs7.NewSMSignedDataWithDegist(digest)
if err := signData.SignWithoutAttr(cert, key, pkcs7.SignerInfoConfig{}); err != nil {
return nil, err
}
return signData.Finish()
}
// VerifyDigestDetach verifies a detached PKCS7 signature against a given digest.
// It parses the p7Der, assigns the provided digest to the parsed PKCS7 content, and then verifies it.
//
// This method corresponds to CFCA SADK's cfca.sadk.util.p7VerifyByHash.
func VerifyDigestDetach(p7Der, digest []byte) error {
p7, err := pkcs7.Parse(p7Der)
if err != nil {
return err
}
p7.Content = digest
return p7.VerifyAsDigest()
}

View File

@ -5,8 +5,11 @@
package cfca package cfca
import ( import (
"crypto/ecdsa"
"encoding/base64" "encoding/base64"
"testing" "testing"
"github.com/emmansun/gmsm/sm2"
) )
func TestSignMessageAttach(t *testing.T) { func TestSignMessageAttach(t *testing.T) {
@ -89,3 +92,31 @@ func TestSignMessageDetach(t *testing.T) {
var sadkSignedData = "MIICgAYKKoEcz1UGAQQCAqCCAnAwggJsAgEBMQ4wDAYIKoEcz1UBgxEFADAjBgoqgRzPVQYBBAIBoBUEE0hlbGxvIFNlY3JldCBXb3JsZCGgggGNMIIBiTCCAS+gAwIBAgIFAKncGpAwCgYIKoEcz1UBg3UwKTEQMA4GA1UEChMHQWNtZSBDbzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrMB4XDTI0MTExOTAwMTIyNVoXDTI1MTExOTAwMTIyNlowJTEQMA4GA1UEChMHQWNtZSBDbzERMA8GA1UEAxMISm9uIFNub3cwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAATYcgrHXJmFO1/t/9WQ6GkCW6D0yDyd2ya5wRXjVAU08I9Oo6k99jB2MPauCn64W81APRCPHLlwWOtuIsmSmQhjo0gwRjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwQwHwYDVR0jBBgwFoAUyBfYaeGJxaf9ST9aCRgotC+MwvwwCgYIKoEcz1UBg3UDSAAwRQIgRaF0PA74cCYKeu8pZ4VDQti+rE283Hq/tGXzXUzOWKUCIQDl3z1boZxtRscbnOGOXg1NY+yoY2lz5b63kGOTkn/SxzGBoDCBnQIBATAyMCkxEDAOBgNVBAoTB0FjbWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyawIFAKncGpAwDAYIKoEcz1UBgxEFADANBgkqgRzPVQGCLQEFAARHMEUCIQCl145xtYc7QWTymATxUGbLfF1mlPlyMoIKSp9alu14UQIgQSV/Ll3yYCyXSNxhPelz8Nsbxopky+Pt56Al54rv3p0=" var sadkSignedData = "MIICgAYKKoEcz1UGAQQCAqCCAnAwggJsAgEBMQ4wDAYIKoEcz1UBgxEFADAjBgoqgRzPVQYBBAIBoBUEE0hlbGxvIFNlY3JldCBXb3JsZCGgggGNMIIBiTCCAS+gAwIBAgIFAKncGpAwCgYIKoEcz1UBg3UwKTEQMA4GA1UEChMHQWNtZSBDbzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrMB4XDTI0MTExOTAwMTIyNVoXDTI1MTExOTAwMTIyNlowJTEQMA4GA1UEChMHQWNtZSBDbzERMA8GA1UEAxMISm9uIFNub3cwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAATYcgrHXJmFO1/t/9WQ6GkCW6D0yDyd2ya5wRXjVAU08I9Oo6k99jB2MPauCn64W81APRCPHLlwWOtuIsmSmQhjo0gwRjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwQwHwYDVR0jBBgwFoAUyBfYaeGJxaf9ST9aCRgotC+MwvwwCgYIKoEcz1UBg3UDSAAwRQIgRaF0PA74cCYKeu8pZ4VDQti+rE283Hq/tGXzXUzOWKUCIQDl3z1boZxtRscbnOGOXg1NY+yoY2lz5b63kGOTkn/SxzGBoDCBnQIBATAyMCkxEDAOBgNVBAoTB0FjbWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyawIFAKncGpAwDAYIKoEcz1UBgxEFADANBgkqgRzPVQGCLQEFAARHMEUCIQCl145xtYc7QWTymATxUGbLfF1mlPlyMoIKSp9alu14UQIgQSV/Ll3yYCyXSNxhPelz8Nsbxopky+Pt56Al54rv3p0="
var sadkSignedDataDetach = "MIICaQYKKoEcz1UGAQQCAqCCAlkwggJVAgEBMQ4wDAYIKoEcz1UBgxEFADAMBgoqgRzPVQYBBAIBoIIBjTCCAYkwggEvoAMCAQICBQCp3BqQMAoGCCqBHM9VAYN1MCkxEDAOBgNVBAoTB0FjbWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyazAeFw0yNDExMTkwMDEyMjVaFw0yNTExMTkwMDEyMjZaMCUxEDAOBgNVBAoTB0FjbWUgQ28xETAPBgNVBAMTCEpvbiBTbm93MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE2HIKx1yZhTtf7f/VkOhpAlug9Mg8ndsmucEV41QFNPCPTqOpPfYwdjD2rgp+uFvNQD0Qjxy5cFjrbiLJkpkIY6NIMEYwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMEMB8GA1UdIwQYMBaAFMgX2GnhicWn/Uk/WgkYKLQvjML8MAoGCCqBHM9VAYN1A0gAMEUCIEWhdDwO+HAmCnrvKWeFQ0LYvqxNvNx6v7Rl811MzlilAiEA5d89W6GcbUbHG5zhjl4NTWPsqGNpc+W+t5Bjk5J/0scxgaAwgZ0CAQEwMjApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3RhcmsCBQCp3BqQMAwGCCqBHM9VAYMRBQAwDQYJKoEcz1UBgi0BBQAERzBFAiEA4ylCl8qQDfNDfBw7VkxVN0bUs4N56TZDqZhAdEv01N8CIDtOG5VbmWNZeagC8VRfzEhu+ratFCo3fTu2liV8kH5h" var sadkSignedDataDetach = "MIICaQYKKoEcz1UGAQQCAqCCAlkwggJVAgEBMQ4wDAYIKoEcz1UBgxEFADAMBgoqgRzPVQYBBAIBoIIBjTCCAYkwggEvoAMCAQICBQCp3BqQMAoGCCqBHM9VAYN1MCkxEDAOBgNVBAoTB0FjbWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyazAeFw0yNDExMTkwMDEyMjVaFw0yNTExMTkwMDEyMjZaMCUxEDAOBgNVBAoTB0FjbWUgQ28xETAPBgNVBAMTCEpvbiBTbm93MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE2HIKx1yZhTtf7f/VkOhpAlug9Mg8ndsmucEV41QFNPCPTqOpPfYwdjD2rgp+uFvNQD0Qjxy5cFjrbiLJkpkIY6NIMEYwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMEMB8GA1UdIwQYMBaAFMgX2GnhicWn/Uk/WgkYKLQvjML8MAoGCCqBHM9VAYN1A0gAMEUCIEWhdDwO+HAmCnrvKWeFQ0LYvqxNvNx6v7Rl811MzlilAiEA5d89W6GcbUbHG5zhjl4NTWPsqGNpc+W+t5Bjk5J/0scxgaAwgZ0CAQEwMjApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3RhcmsCBQCp3BqQMAwGCCqBHM9VAYMRBQAwDQYJKoEcz1UBgi0BBQAERzBFAiEA4ylCl8qQDfNDfBw7VkxVN0bUs4N56TZDqZhAdEv01N8CIDtOG5VbmWNZeagC8VRfzEhu+ratFCo3fTu2liV8kH5h"
func TestSignDigestDetach(t *testing.T) {
_, err := SignDigestDetach(nil, nil, nil)
if err == nil {
t.Fatalf("SignDigestDetach() error = %v, wantErr %v", err, true)
}
pair, err := createTestSM2Certificate(false)
if err != nil {
t.Fatal(err)
}
rawMessage := []byte("test")
digest, err := sm2.CalculateSM2Hash(pair.Certificate.PublicKey.(*ecdsa.PublicKey), rawMessage, nil)
if err != nil {
t.Fatal(err)
}
p7, err := SignDigestDetach(digest, pair.Certificate, pair.PrivateKey)
if err != nil {
t.Fatal(err)
}
err = VerifyDigestDetach(p7, digest)
if err != nil {
t.Fatal(err)
}
err = VerifyMessageDetach(p7, rawMessage)
if err != nil {
t.Fatal(err)
}
}

View File

@ -10,6 +10,7 @@ SADKSecurity Application Development Kit是CFCA推出的一套支持全平
其JAVA版本(其它语言版本未知)基本上是一个基于[Bouncy Castle](https://www.bouncycastle.org/)的实现当然它看起来也支持JNI接入OpenSSL、U盾 其JAVA版本(其它语言版本未知)基本上是一个基于[Bouncy Castle](https://www.bouncycastle.org/)的实现当然它看起来也支持JNI接入OpenSSL、U盾
## 为什么会有互操作性问题 ## 为什么会有互操作性问题
* CFCA有一些实现没有相关标准。
* SADK存在较早可能有些实现早于标准发布。 * SADK存在较早可能有些实现早于标准发布。
* SADK版本较多不同版本也会有互操作性兼容问题。 * SADK版本较多不同版本也会有互操作性兼容问题。
* 其它未知原因。 * 其它未知原因。

View File

@ -23,6 +23,7 @@ type SignedData struct {
sd signedData sd signedData
certs []*smx509.Certificate certs []*smx509.Certificate
data []byte data []byte
isDigest bool
contentTypeOid asn1.ObjectIdentifier contentTypeOid asn1.ObjectIdentifier
digestOid asn1.ObjectIdentifier digestOid asn1.ObjectIdentifier
encryptionOid asn1.ObjectIdentifier encryptionOid asn1.ObjectIdentifier
@ -47,6 +48,22 @@ func NewSignedData(data []byte) (*SignedData, error) {
return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA1, contentTypeOid: OIDSignedData}, nil return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA1, contentTypeOid: OIDSignedData}, nil
} }
// NewSignedDataWithDegist creates a new SignedData instance using the provided digest.
// It sets the isDigest field to true, indicating that the input is already a digest.
// Returns the SignedData instance or an error if the creation fails.
func NewSignedDataWithDegist(digest []byte) (*SignedData, error) {
ci := contentInfo{
ContentType: OIDData,
Content: asn1.RawValue{}, // for sign digest, content is empty
}
sd := signedData{
ContentInfo: ci,
Version: 1,
}
return &SignedData{sd: sd, data: digest, digestOid: OIDDigestAlgorithmSHA1, contentTypeOid: OIDSignedData, isDigest: true}, nil
}
// NewSMSignedData takes data and initializes a PKCS7 SignedData struct that is // NewSMSignedData takes data and initializes a PKCS7 SignedData struct that is
// ready to be signed via AddSigner. The digest algorithm is set to SM3 by default // ready to be signed via AddSigner. The digest algorithm is set to SM3 by default
// and can be changed by calling SetDigestAlgorithm. // and can be changed by calling SetDigestAlgorithm.
@ -61,6 +78,20 @@ func NewSMSignedData(data []byte) (*SignedData, error) {
return sd, nil return sd, nil
} }
// NewSMSignedDataWithDegist creates a new SignedData object using the provided digest.
// It calls the NewSMSignedData function with the given digest and sets the isDigest flag to true.
// If there is an error during the creation of the SignedData object, it returns the error.
func NewSMSignedDataWithDegist(digest []byte) (*SignedData, error) {
sd, err := NewSignedDataWithDegist(digest)
if err != nil {
return nil, err
}
sd.sd.ContentInfo.ContentType = SM2OIDData
sd.digestOid = OIDDigestAlgorithmSM3
sd.contentTypeOid = SM2OIDSignedData
return sd, nil
}
// SignerInfoConfig are optional values to include when adding a signer // SignerInfoConfig are optional values to include when adding a signer
type SignerInfoConfig struct { type SignerInfoConfig struct {
ExtraSignedAttributes []Attribute ExtraSignedAttributes []Attribute
@ -203,9 +234,12 @@ func (sd *SignedData) signWithAttributes(pkey crypto.PrivateKey, config SignerIn
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
h := newHash(hasher, sd.digestOid) messageDigest := sd.data
h.Write(sd.data) if !sd.isDigest {
messageDigest := h.Sum(nil) h := newHash(hasher, sd.digestOid)
h.Write(sd.data)
messageDigest = h.Sum(nil)
}
attrs := &attributes{} attrs := &attributes{}
attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType) attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType)
@ -250,9 +284,10 @@ func (sd *SignedData) SignWithoutAttr(ee *smx509.Certificate, pkey crypto.Privat
if err != nil { if err != nil {
return err return err
} }
if signature, err = signData(sd.data, pkey, hasher); err != nil { if signature, err = signData(sd.data, pkey, hasher, sd.isDigest); err != nil {
return err return err
} }
var ias issuerAndSerial var ias issuerAndSerial
ias.SerialNumber = ee.SerialNumber ias.SerialNumber = ee.SerialNumber
// no parent, the issue is the end-entity cert itself // no parent, the issue is the end-entity cert itself
@ -377,12 +412,12 @@ func signAttributes(attrs []attribute, pkey crypto.PrivateKey, hasher crypto.Has
if err != nil { if err != nil {
return nil, err return nil, err
} }
return signData(attrBytes, pkey, hasher) return signData(attrBytes, pkey, hasher, false)
} }
// signData signs the provided data using the given private key and hash function. // signData signs the provided data using the given private key and hash function.
// It returns the signed data or an error if the signing process fails. // It returns the signed data or an error if the signing process fails.
func signData(data []byte, pkey crypto.PrivateKey, hasher crypto.Hash) ([]byte, error) { func signData(data []byte, pkey crypto.PrivateKey, hasher crypto.Hash, isDigest bool) ([]byte, error) {
key, ok := pkey.(crypto.Signer) key, ok := pkey.(crypto.Signer)
if !ok { if !ok {
return nil, errors.New("pkcs7: private key does not implement crypto.Signer") return nil, errors.New("pkcs7: private key does not implement crypto.Signer")
@ -392,7 +427,11 @@ func signData(data []byte, pkey crypto.PrivateKey, hasher crypto.Hash) ([]byte,
if !hasher.Available() { if !hasher.Available() {
if sm2.IsSM2PublicKey(key.Public()) { if sm2.IsSM2PublicKey(key.Public()) {
opts = sm2.DefaultSM2SignerOpts if !isDigest {
opts = sm2.DefaultSM2SignerOpts
} else if len(hash) != sm3.Size {
return nil, fmt.Errorf("pkcs7: invalid hash value fo SM2 signature")
}
switch realKey := key.(type) { switch realKey := key.(type) {
case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
{ {
@ -404,10 +443,12 @@ func signData(data []byte, pkey crypto.PrivateKey, hasher crypto.Hash) ([]byte,
} else { } else {
return nil, fmt.Errorf("pkcs7: unsupported hash function %s", hasher) return nil, fmt.Errorf("pkcs7: unsupported hash function %s", hasher)
} }
} else { } else if !isDigest {
h := hasher.New() h := hasher.New()
h.Write(data) h.Write(data)
hash = h.Sum(nil) hash = h.Sum(nil)
} else if len(hash) != hasher.Size() {
return nil, fmt.Errorf("pkcs7: invalid hash for %s", hasher)
} }
return key.Sign(rand.Reader, hash, opts) return key.Sign(rand.Reader, hash, opts)
} }

View File

@ -234,7 +234,7 @@ func (saed *SignedAndEnvelopedData) AddSignerChain(ee *smx509.Certificate, pkey
if err != nil { if err != nil {
return err return err
} }
signature, err := signData(saed.data, pkey, hasher) signature, err := signData(saed.data, pkey, hasher, false)
if err != nil { if err != nil {
return err return err
} }

View File

@ -2,16 +2,21 @@ package pkcs7
import ( import (
"bytes" "bytes"
"crypto"
"crypto/ecdsa"
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"hash"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"testing" "testing"
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/sm3"
"github.com/emmansun/gmsm/smx509" "github.com/emmansun/gmsm/smx509"
) )
@ -349,3 +354,211 @@ func TestSignWithoutAttr(t *testing.T) {
} }
} }
} }
func testSignDigest(t *testing.T, isSM bool, content []byte, sigalgs []x509.SignatureAlgorithm) {
for _, sigalgroot := range sigalgs {
rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true)
if err != nil {
t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err)
}
truststore := smx509.NewCertPool()
truststore.AddCert(rootCert.Certificate)
for _, sigalginter := range sigalgs {
interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true)
if err != nil {
t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err)
}
var parents []*smx509.Certificate
parents = append(parents, interCert.Certificate)
for _, sigalgsigner := range sigalgs {
signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false)
if err != nil {
t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
hashOID, err := getDigestOIDForSignatureAlgorithm(sigalgsigner)
if err != nil {
t.Fatalf("test %s/%s/%s: cannot get digest OID: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
hasher, err := getHashForOID(hashOID)
if err != nil {
t.Fatalf("test %s/%s/%s: cannot get hasher: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
var hashInst hash.Hash
if hasher == crypto.Hash(0) {
hashInst = sm3.New()
} else {
hashInst = hasher.New()
}
hashInst.Write(content)
digest := hashInst.Sum(nil)
var toBeSigned *SignedData
if isSM {
toBeSigned, err = NewSMSignedDataWithDegist(digest)
} else {
toBeSigned, err = NewSignedDataWithDegist(digest)
}
if err != nil {
t.Fatalf("test %s/%s/%s: cannot initialize signed data: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
// Set the digest to match the end entity cert
signerDigest, _ := getDigestOIDForSignatureAlgorithm(sigalgsigner)
toBeSigned.SetDigestAlgorithm(signerDigest)
if err := toBeSigned.AddSignerChain(signerCert.Certificate, *signerCert.PrivateKey, parents, SignerInfoConfig{}); err != nil {
t.Fatalf("test %s/%s/%s: cannot add signer: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
signed, err := toBeSigned.Finish()
if err != nil {
t.Fatalf("test %s/%s/%s: cannot finish signing data: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed})
p7, err := Parse(signed)
if err != nil {
t.Fatalf("test %s/%s/%s: cannot parse signed data: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
if len(p7.Content) > 0 {
t.Errorf("Content should be empty")
}
if err := p7.VerifyAsDigestWithChain(truststore); err != nil {
t.Errorf("test %s/%s/%s: cannot verify signed data: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
if !signerDigest.Equal(p7.Signers[0].DigestAlgorithm.Algorithm) {
t.Errorf("test %s/%s/%s: expected digest algorithm %q but got %q",
sigalgroot, sigalginter, sigalgsigner, signerDigest, p7.Signers[0].DigestAlgorithm.Algorithm)
}
}
}
}
}
func TestSignWithDigest(t *testing.T) {
content := []byte("Hello World")
sigalgs := []x509.SignatureAlgorithm{
x509.SHA1WithRSA,
x509.SHA256WithRSA,
x509.SHA512WithRSA,
x509.ECDSAWithSHA1,
x509.ECDSAWithSHA256,
x509.ECDSAWithSHA384,
x509.ECDSAWithSHA512,
smx509.SM2WithSM3,
}
testSignDigest(t, false, content, sigalgs)
}
func TestSignSMWithDigest(t *testing.T) {
content := []byte("Hello World")
sigalgs := []x509.SignatureAlgorithm{
smx509.SM2WithSM3,
}
testSignDigest(t, true, content, sigalgs)
}
func TestSignWithoutAttrWithDigest(t *testing.T) {
content := []byte("Hello World")
sigalgs := []struct {
isSM bool
sigAlg x509.SignatureAlgorithm
skipCert bool
}{
{
false,
x509.SHA256WithRSA,
false,
},
{
true,
smx509.SM2WithSM3,
false,
},
{
false,
x509.SHA256WithRSA,
true,
},
{
true,
smx509.SM2WithSM3,
true,
},
}
for _, sigalg := range sigalgs {
cert, err := createTestCertificate(sigalg.sigAlg, false)
if err != nil {
t.Fatal(err)
}
hashOID, err := getDigestOIDForSignatureAlgorithm(sigalg.sigAlg)
if err != nil {
t.Fatalf("test %s: cannot get digest OID: %s", sigalg.sigAlg, err)
}
hasher, err := getHashForOID(hashOID)
if err != nil {
t.Fatalf("test %s: cannot get hasher: %s", sigalg.sigAlg, err)
}
var digest []byte
if hasher == crypto.Hash(0) {
publicKey, ok := cert.Certificate.PublicKey.(*ecdsa.PublicKey)
if !ok {
t.Fatalf("Cannot cast public key to ECDSA public key")
}
digest, err = sm2.CalculateSM2Hash(publicKey, content, nil)
if err != nil {
t.Fatalf("Cannot calculate SM2 hash: %s", err)
}
} else {
hashInst := hasher.New()
hashInst.Write(content)
digest = hashInst.Sum(nil)
}
var toBeSigned *SignedData
if sigalg.isSM {
toBeSigned, err = NewSMSignedDataWithDegist(digest)
} else {
toBeSigned, err = NewSignedDataWithDegist(digest)
toBeSigned.SetDigestAlgorithm(hashOID)
}
if err != nil {
t.Fatalf("Cannot initialize signed data: %s", err)
}
if err := toBeSigned.SignWithoutAttr(cert.Certificate, *cert.PrivateKey, SignerInfoConfig{SkipCertificates: sigalg.skipCert}); err != nil {
t.Fatalf("Cannot add signer: %s", err)
}
signed, err := toBeSigned.Finish()
if err != nil {
t.Fatalf("Cannot finish signing data: %s", err)
}
p7, err := Parse(signed)
if err != nil {
t.Fatalf("Cannot parse signed data: %v", err)
}
if len(p7.Content) > 0 {
t.Errorf("Content should be empty")
}
p7.Content = digest
if !sigalg.skipCert {
if len(p7.Certificates) == 0 {
t.Errorf("No certificates")
}
err = p7.VerifyAsDigest()
if err != nil {
t.Fatal(err)
}
} else {
if len(p7.Certificates) > 0 {
t.Errorf("No certificates expected")
}
err = p7.VerifyAsDigest()
if sigalg.skipCert && err.Error() != "pkcs7: No certificate for signer" {
t.Fatalf("Expected pkcs7: No certificate for signer")
}
p7.Certificates = append(p7.Certificates, cert.Certificate)
err = p7.VerifyAsDigest()
if err != nil {
t.Fatal(err)
}
}
}
}

View File

@ -19,6 +19,12 @@ func (p7 *PKCS7) Verify() (err error) {
return p7.VerifyWithChain(nil) return p7.VerifyWithChain(nil)
} }
// VerifyAsDigest verifies the PKCS7 signature, treats the content as a digest.
// It returns an error if the verification fails.
func (p7 *PKCS7) VerifyAsDigest() (err error) {
return p7.verifyWithChain(nil, true)
}
// VerifyWithChain checks the signatures of a PKCS7 object. // VerifyWithChain checks the signatures of a PKCS7 object.
// //
// If truststore is not nil, it also verifies the chain of trust of // If truststore is not nil, it also verifies the chain of trust of
@ -27,11 +33,22 @@ func (p7 *PKCS7) Verify() (err error) {
// authenticated attr verifies the chain at that time and UTC now // authenticated attr verifies the chain at that time and UTC now
// otherwise. // otherwise.
func (p7 *PKCS7) VerifyWithChain(truststore *smx509.CertPool) (err error) { func (p7 *PKCS7) VerifyWithChain(truststore *smx509.CertPool) (err error) {
return p7.verifyWithChain(truststore, false)
}
// VerifyAsDigestWithChain verifies the PKCS7 signature using the provided truststore
// and treats the content as a precomputed digest. It returns an error if the verification fails.
func (p7 *PKCS7) VerifyAsDigestWithChain(truststore *smx509.CertPool) (err error) {
return p7.verifyWithChain(truststore, true)
}
func (p7 *PKCS7) verifyWithChain(truststore *smx509.CertPool, isDigest bool) (err error) {
if len(p7.Signers) == 0 { if len(p7.Signers) == 0 {
return errors.New("pkcs7: Message has no signers") return errors.New("pkcs7: Message has no signers")
} }
for _, signer := range p7.Signers { for _, signer := range p7.Signers {
if err := verifySignature(p7, signer, truststore, nil); err != nil { if err := verifySignature(p7, signer, truststore, nil, isDigest); err != nil {
return err return err
} }
} }
@ -45,24 +62,41 @@ func (p7 *PKCS7) VerifyWithChain(truststore *smx509.CertPool) (err error) {
// currentTime. It does not use the signing time authenticated // currentTime. It does not use the signing time authenticated
// attribute. // attribute.
func (p7 *PKCS7) VerifyWithChainAtTime(truststore *smx509.CertPool, currentTime *time.Time) (err error) { func (p7 *PKCS7) VerifyWithChainAtTime(truststore *smx509.CertPool, currentTime *time.Time) (err error) {
return p7.verifyWithChainAtTime(truststore, currentTime, false)
}
func (p7 *PKCS7) verifyWithChainAtTime(truststore *smx509.CertPool, currentTime *time.Time, isDigest bool) (err error) {
if len(p7.Signers) == 0 { if len(p7.Signers) == 0 {
return errors.New("pkcs7: Message has no signers") return errors.New("pkcs7: Message has no signers")
} }
for _, signer := range p7.Signers { for _, signer := range p7.Signers {
if err := verifySignature(p7, signer, truststore, currentTime); err != nil { if err := verifySignature(p7, signer, truststore, currentTime, isDigest); err != nil {
return err return err
} }
} }
return nil return nil
} }
func verifySignature(p7 *PKCS7, signer signerInfo, truststore *smx509.CertPool, currentTime *time.Time) (err error) { func verifySignature(p7 *PKCS7, signer signerInfo, truststore *smx509.CertPool, currentTime *time.Time, isDigest bool) (err error) {
signedData := p7.Content signedData := p7.Content
ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
if ee == nil { if ee == nil {
return errors.New("pkcs7: No certificate for signer") return errors.New("pkcs7: No certificate for signer")
} }
signingTime := time.Now().UTC() signingTime := time.Now().UTC()
if truststore != nil {
if currentTime != nil {
signingTime = *currentTime
}
_, err = verifyCertChain(ee, p7.Certificates, truststore, signingTime)
if err != nil {
return err
}
}
sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm)
if err != nil {
return err
}
if len(signer.AuthenticatedAttributes) > 0 { if len(signer.AuthenticatedAttributes) > 0 {
// TODO(fullsailor): First check the content type match // TODO(fullsailor): First check the content type match
var digest []byte var digest []byte
@ -74,9 +108,12 @@ func verifySignature(p7 *PKCS7, signer signerInfo, truststore *smx509.CertPool,
if err != nil { if err != nil {
return err return err
} }
h := newHash(hasher, signer.DigestAlgorithm.Algorithm) computed := signedData
h.Write(p7.Content) if !isDigest {
computed := h.Sum(nil) h := newHash(hasher, signer.DigestAlgorithm.Algorithm)
h.Write(p7.Content)
computed = h.Sum(nil)
}
if subtle.ConstantTimeCompare(digest, computed) != 1 { if subtle.ConstantTimeCompare(digest, computed) != 1 {
return &MessageDigestMismatchError{ return &MessageDigestMismatchError{
ExpectedDigest: digest, ExpectedDigest: digest,
@ -97,19 +134,10 @@ func verifySignature(p7 *PKCS7, signer signerInfo, truststore *smx509.CertPool,
ee.NotAfter.Format(time.RFC3339)) ee.NotAfter.Format(time.RFC3339))
} }
} }
return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)
} }
if truststore != nil { if isDigest {
if currentTime != nil { return ee.CheckSignatureWithDigest(sigalg, signedData, signer.EncryptedDigest)
signingTime = *currentTime
}
_, err = verifyCertChain(ee, p7.Certificates, truststore, signingTime)
if err != nil {
return err
}
}
sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm)
if err != nil {
return err
} }
return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest) return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)
} }

View File

@ -732,6 +732,70 @@ func (c *Certificate) CheckSignature(algo SignatureAlgorithm, signed, signature
return checkSignature(algo, signed, signature, c.PublicKey, true) return checkSignature(algo, signed, signature, c.PublicKey, true)
} }
// CheckSignatureWithDigest verifies the signature of a certificate using the specified
// signature algorithm and digest. It supports RSA, ECDSA, and SM2 public keys.
//
// This is a low-level API that performs no validity checks on the certificate.
func (c *Certificate) CheckSignatureWithDigest(algo SignatureAlgorithm, digest, signature []byte) (err error) {
var hashType crypto.Hash
var pubKeyAlgo PublicKeyAlgorithm
publicKey := c.PublicKey
isSM2 := (algo == SM2WithSM3)
for _, details := range signatureAlgorithmDetails {
if details.algo == algo {
hashType = details.hash
pubKeyAlgo = details.pubKeyAlgo
break
}
}
switch hashType {
case crypto.Hash(0):
if !isSM2 {
return x509.ErrUnsupportedAlgorithm
}
if len(digest) != 32 { // SM3 hash size
return errors.New("x509: inconsistent digest and signature algorithm")
}
case crypto.MD5:
return x509.InsecureAlgorithmError(algo)
default:
if !hashType.Available() {
return x509.ErrUnsupportedAlgorithm
}
if len(digest) != hashType.Size() {
return errors.New("x509: inconsistent digest and signature algorithm")
}
}
switch pub := publicKey.(type) {
case *rsa.PublicKey:
if pubKeyAlgo != RSA {
return signaturePublicKeyAlgoMismatchError(pubKeyAlgo, pub)
}
if isRSAPSS(algo) {
return rsa.VerifyPSS(pub, hashType, digest, signature, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash})
} else {
return rsa.VerifyPKCS1v15(pub, hashType, digest, signature)
}
case *ecdsa.PublicKey:
if pubKeyAlgo != ECDSA {
return signaturePublicKeyAlgoMismatchError(pubKeyAlgo, pub)
}
if isSM2 {
if !sm2.VerifyASN1(pub, digest, signature) {
return errors.New("x509: SM2 verification failure")
}
} else if !ecdsa.VerifyASN1(pub, digest, signature) {
return errors.New("x509: ECDSA verification failure")
}
return
}
return x509.ErrUnsupportedAlgorithm
}
func (c *Certificate) hasNameConstraints() bool { func (c *Certificate) hasNameConstraints() bool {
return oidInExtensions(oidExtensionNameConstraints, c.Extensions) return oidInExtensions(oidExtensionNameConstraints, c.Extensions)
} }
@ -1426,7 +1490,6 @@ var emptyASN1Subject = []byte{0x30, 0}
// //
// If template.SerialNumber is nil, a serial number will be generated which // If template.SerialNumber is nil, a serial number will be generated which
// conforms to RFC 5280, Section 4.1.2.2 using entropy from rand. // conforms to RFC 5280, Section 4.1.2.2 using entropy from rand.
//
func CreateCertificate(rand io.Reader, template, parent, pub, priv any) ([]byte, error) { func CreateCertificate(rand io.Reader, template, parent, pub, priv any) ([]byte, error) {
realTemplate, err := toCertificate(template) realTemplate, err := toCertificate(template)
if err != nil { if err != nil {

View File

@ -1,14 +1,23 @@
package smx509 package smx509
import ( import (
"crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"errors" "errors"
"math/big"
"strings" "strings"
"testing" "testing"
@ -292,3 +301,225 @@ func TestInvalidParentTemplate(t *testing.T) {
t.Fatalf("unexpected error message: %v", err.Error()) t.Fatalf("unexpected error message: %v", err.Error())
} }
} }
func TestCheckSignatureWithDigest(t *testing.T) {
rawMessage := []byte("test message")
rsaPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("failed to generate RSA key: %s", err)
}
ecdsaPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("failed to generate ECDSA key: %s", err)
}
ecdsaPrivateKey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("failed to generate ECDSA key: %s", err)
}
sm2PrivateKey, err := sm2.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("failed to generate SM2 key: %s", err)
}
sm2PrivateKey2, err := sm2.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("failed to generate SM2 key: %s", err)
}
ed25519Pub, ed25519Priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("failed to generate Ed25519 key: %s", err)
}
tests := []struct {
name string
cert *Certificate
algo SignatureAlgorithm
digest []byte
signature []byte
expectedError error
}{
{
name: "Valid RSA PKCS1v15 signature",
cert: &Certificate{
PublicKey: &rsaPrivateKey.PublicKey,
},
algo: SHA256WithRSA,
digest: func() []byte {
hash := sha256.Sum256(rawMessage)
return hash[:]
}(),
signature: func() []byte {
hash := sha256.Sum256(rawMessage)
return mustSignPKCS1v15(t, rsaPrivateKey, crypto.SHA256, hash[:])
}(),
expectedError: nil,
},
{
name: "Valid ECDSA signature",
cert: &Certificate{
PublicKey: &ecdsaPrivateKey.PublicKey,
},
algo: ECDSAWithSHA256,
digest: func() []byte {
hash := sha256.Sum256(rawMessage)
return hash[:]
}(),
signature: func() []byte {
hash := sha256.Sum256(rawMessage)
return mustSignECDSA(t, ecdsaPrivateKey, hash[:])
}(),
expectedError: nil,
},
{
name: "Invalid ECDSA signature",
cert: &Certificate{
PublicKey: &ecdsaPrivateKey.PublicKey,
},
algo: ECDSAWithSHA256,
digest: func() []byte {
hash := sha256.Sum256(rawMessage)
return hash[:]
}(),
signature: func() []byte {
hash := sha256.Sum256(rawMessage)
return mustSignECDSA(t, ecdsaPrivateKey2, hash[:])
}(),
expectedError: errors.New("x509: ECDSA verification failure"),
},
{
name: "Valid SM2 signature",
cert: &Certificate{
PublicKey: &sm2PrivateKey.PublicKey,
},
algo: SM2WithSM3,
digest: func() []byte {
hash, _ := sm2.CalculateSM2Hash(&sm2PrivateKey.PublicKey, rawMessage, nil)
return hash[:]
}(),
signature: func() []byte {
hash, _ := sm2.CalculateSM2Hash(&sm2PrivateKey.PublicKey, rawMessage, nil)
return mustSignSM2(t, sm2PrivateKey, hash[:])
}(),
expectedError: nil,
},
{
name: "Invalid SM2 signature",
cert: &Certificate{
PublicKey: &sm2PrivateKey.PublicKey,
},
algo: SM2WithSM3,
digest: func() []byte {
hash, _ := sm2.CalculateSM2Hash(&sm2PrivateKey.PublicKey, rawMessage, nil)
return hash[:]
}(),
signature: func() []byte {
hash, _ := sm2.CalculateSM2Hash(&sm2PrivateKey2.PublicKey, rawMessage, nil)
return mustSignSM2(t, sm2PrivateKey2, hash[:])
}(),
expectedError: errors.New("x509: SM2 verification failure"),
},
{
name: "Insecure algorithm",
cert: &Certificate{
PublicKey: &rsaPrivateKey.PublicKey,
},
algo: MD5WithRSA,
digest: func() []byte {
hash := md5.Sum(rawMessage)
return hash[:]
}(),
signature: func() []byte {
hash := md5.Sum(rawMessage)
return mustSignPKCS1v15(t, rsaPrivateKey, crypto.MD5, hash[:])
}(),
expectedError: x509.InsecureAlgorithmError(MD5WithRSA),
},
{
name: "Unsupported algorithm",
cert: &Certificate{
PublicKey: ed25519Pub,
},
algo: PureEd25519,
digest: func() []byte {
hash := sha256.Sum256(rawMessage)
return hash[:]
}(),
signature: func() []byte {
hash := sha256.Sum256(rawMessage)
return ed25519.Sign(ed25519Priv, hash[:])
}(),
expectedError: x509.ErrUnsupportedAlgorithm,
},
{
name: "Inconsistent digest and signature algorithm",
cert: &Certificate{
PublicKey: &rsaPrivateKey.PublicKey,
},
algo: SHA256WithRSA,
digest: func() []byte {
hash := sha1.Sum(rawMessage)
return hash[:]
}(),
signature: func() []byte {
hash := sha256.Sum256(rawMessage)
return mustSignPKCS1v15(t, rsaPrivateKey, crypto.SHA256, hash[:])
}(),
expectedError: errors.New("x509: inconsistent digest and signature algorithm"),
},
{
name: "Inconsistent digest and signature algorithm (SM2)",
cert: &Certificate{
PublicKey: &sm2PrivateKey.PublicKey,
},
algo: SM2WithSM3,
digest: func() []byte {
hash, _ := sm2.CalculateSM2Hash(&sm2PrivateKey.PublicKey, rawMessage, nil)
return hash[:20]
}(),
signature: func() []byte {
hash, _ := sm2.CalculateSM2Hash(&sm2PrivateKey.PublicKey, rawMessage, nil)
return mustSignSM2(t, sm2PrivateKey, hash[:])
}(),
expectedError: errors.New("x509: inconsistent digest and signature algorithm"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.cert.CheckSignatureWithDigest(tt.algo, tt.digest, tt.signature)
if (err == nil || tt.expectedError == nil) && err != tt.expectedError {
t.Errorf("Case <%v>: expected error %v, got %v", tt.name, tt.expectedError, err)
}
if err != nil && tt.expectedError != nil && err.Error() != tt.expectedError.Error() {
t.Errorf("Case <%v>: expected error %v, got %v", tt.name, tt.expectedError, err)
}
})
}
}
func mustSignPKCS1v15(t *testing.T, priv *rsa.PrivateKey, hash crypto.Hash, digest []byte) []byte {
signature, err := rsa.SignPKCS1v15(rand.Reader, priv, hash, digest)
if err != nil {
t.Fatalf("failed to sign: %s", err)
}
return signature
}
func mustSignECDSA(t *testing.T, priv *ecdsa.PrivateKey, digest []byte) []byte {
r, s, err := ecdsa.Sign(rand.Reader, priv, digest)
if err != nil {
t.Fatalf("failed to sign: %s", err)
}
signature, err := asn1.Marshal(struct{ R, S *big.Int }{r, s})
if err != nil {
t.Fatalf("failed to marshal signature: %s", err)
}
return signature
}
func mustSignSM2(t *testing.T, priv *sm2.PrivateKey, digest []byte) []byte {
signature, err := sm2.SignASN1(rand.Reader, priv, digest, nil)
if err != nil {
t.Fatalf("failed to sign: %s", err)
}
return signature
}