pkcs7: 签名支持外部hash

This commit is contained in:
zhangyongding 2025-01-13 19:51:19 +08:00
parent 5a0ff81dc3
commit cbdb454b14
5 changed files with 201 additions and 92 deletions

View File

@ -26,6 +26,7 @@ type PKCS7 struct {
Certificates []*smx509.Certificate
CRLs []pkix.CertificateList
Signers []signerInfo
isDigest bool
raw any
session Session
}

View File

@ -23,6 +23,7 @@ type SignedData struct {
sd signedData
certs []*smx509.Certificate
data []byte
isDigest bool
contentTypeOid asn1.ObjectIdentifier
digestOid asn1.ObjectIdentifier
encryptionOid asn1.ObjectIdentifier
@ -129,6 +130,10 @@ func (sd *SignedData) SetEncryptionAlgorithm(d asn1.ObjectIdentifier) {
sd.encryptionOid = d
}
func (sd *SignedData) SetIsDigest() {
sd.isDigest = true
}
// AddSigner is a wrapper around AddSignerChain() that adds a signer without any parent.
func (sd *SignedData) AddSigner(ee *smx509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error {
var parents []*smx509.Certificate
@ -250,7 +255,7 @@ func (sd *SignedData) SignWithoutAttr(ee *smx509.Certificate, pkey crypto.Privat
if err != nil {
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
}
var ias issuerAndSerial
@ -377,22 +382,29 @@ func signAttributes(attrs []attribute, pkey crypto.PrivateKey, hasher crypto.Has
if err != nil {
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.
// 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)
if !ok {
return nil, errors.New("pkcs7: private key does not implement crypto.Signer")
}
hash := data
var opts crypto.SignerOpts = hasher
if isDigest {
opts = crypto.Hash(0)
}
if !hasher.Available() {
if sm2.IsSM2PublicKey(key.Public()) {
if isDigest {
opts = sm2.NewSM2SignerOption(false, nil)
} else {
opts = sm2.DefaultSM2SignerOpts
}
switch realKey := key.(type) {
case *ecdsa.PrivateKey:
{
@ -405,10 +417,12 @@ func signData(data []byte, pkey crypto.PrivateKey, hasher crypto.Hash) ([]byte,
return nil, fmt.Errorf("pkcs7: unsupported hash function %s", hasher)
}
} else {
if !isDigest {
h := hasher.New()
h.Write(data)
hash = h.Sum(nil)
}
}
return key.Sign(rand.Reader, hash, opts)
}

View File

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

View File

@ -2,6 +2,7 @@ package pkcs7
import (
"bytes"
"crypto/ecdsa"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
@ -12,6 +13,7 @@ import (
"os/exec"
"testing"
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/smx509"
)
@ -36,19 +38,39 @@ func testSign(t *testing.T, isSM bool, content []byte, sigalgs []x509.SignatureA
t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
for _, testDetach := range []bool{false, true} {
log.Printf("test %s/%s/%s detached %t\n", sigalgroot, sigalginter, sigalgsigner, testDetach)
for _, testDigest := range []bool{false, true} {
log.Printf("test %s/%s/%s detached %t hashed %t\n", sigalgroot, sigalginter, sigalgsigner, testDetach, testDigest)
signerDigest, _ := getDigestOIDForSignatureAlgorithm(sigalgsigner)
data := content
if testDigest {
if isSM {
data, err = sm2.CalculateSM2Hash(signerCert.Certificate.PublicKey.(*ecdsa.PublicKey), content, nil)
if err != nil {
t.Fatalf("test %s/%s/%s: cannot generate hash: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
} else {
hasher, err := getHashForOID(signerDigest)
if err != nil {
t.Fatalf("test %s/%s/%s: cannot generate hash: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
h := newHash(hasher, signerDigest)
h.Write(content)
data = h.Sum(nil)
}
}
var toBeSigned *SignedData
if isSM {
toBeSigned, err = NewSMSignedData(content)
toBeSigned, err = NewSMSignedData(data)
} else {
toBeSigned, err = NewSignedData(content)
toBeSigned, err = NewSignedData(data)
}
toBeSigned.isDigest = testDigest
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 {
@ -70,11 +92,14 @@ func testSign(t *testing.T, isSM bool, content []byte, sigalgs []x509.SignatureA
// 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 = data
}
if !bytes.Equal(content, p7.Content) {
if !bytes.Equal(data, p7.Content) {
t.Errorf("test %s/%s/%s: content was not found in the parsed data:\n\tExpected: %s\n\tActual: %s", sigalgroot, sigalginter, sigalgsigner, content, p7.Content)
}
if testDigest {
p7.SetIsDigest()
}
if err := p7.VerifyWithChain(truststore); err != nil {
t.Errorf("test %s/%s/%s: cannot verify signed data: %s", sigalgroot, sigalginter, sigalgsigner, err)
}
@ -87,6 +112,7 @@ func testSign(t *testing.T, isSM bool, content []byte, sigalgs []x509.SignatureA
}
}
}
}
func TestSign(t *testing.T) {
content := []byte("Hello World")
@ -299,21 +325,44 @@ func TestSignWithoutAttr(t *testing.T) {
},
}
for _, sigalg := range sigalgs {
for _, testDigest := range []bool{false, true} {
cert, err := createTestCertificate(sigalg.sigAlg, false)
if err != nil {
t.Fatal(err)
}
signerDigest, _ := getDigestOIDForSignatureAlgorithm(sigalg.sigAlg)
data := content
if testDigest {
if sigalg.isSM {
priv := (*cert.PrivateKey).(*sm2.PrivateKey)
data, err = sm2.CalculateSM2Hash(&priv.PublicKey, content, nil)
if err != nil {
t.Fatal(err)
}
} else {
hasher, err := getHashForOID(signerDigest)
if err != nil {
t.Fatal(err)
}
h := newHash(hasher, signerDigest)
h.Write(content)
data = h.Sum(nil)
}
}
var toBeSigned *SignedData
if sigalg.isSM {
toBeSigned, err = NewSMSignedData(content)
toBeSigned, err = NewSMSignedData(data)
} else {
toBeSigned, err = NewSignedData(content)
signerDigest, _ := getDigestOIDForSignatureAlgorithm(sigalg.sigAlg)
toBeSigned, err = NewSignedData(data)
toBeSigned.SetDigestAlgorithm(signerDigest)
}
if err != nil {
t.Fatalf("Cannot initialize signed data: %s", err)
}
toBeSigned.isDigest = testDigest
if err := toBeSigned.SignWithoutAttr(cert.Certificate, *cert.PrivateKey, SignerInfoConfig{SkipCertificates: sigalg.skipCert}); err != nil {
t.Fatalf("Cannot add signer: %s", err)
}
@ -325,6 +374,9 @@ func TestSignWithoutAttr(t *testing.T) {
if err != nil {
t.Fatalf("Cannot parse signed data: %v", err)
}
if testDigest {
p7.SetIsDigest()
}
if !sigalg.skipCert {
if len(p7.Certificates) == 0 {
t.Errorf("No certificates")
@ -349,3 +401,4 @@ func TestSignWithoutAttr(t *testing.T) {
}
}
}
}

View File

@ -1,6 +1,10 @@
package pkcs7
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
"crypto/x509/pkix"
@ -9,6 +13,7 @@ import (
"fmt"
"time"
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/smx509"
)
@ -19,6 +24,10 @@ func (p7 *PKCS7) Verify() (err error) {
return p7.VerifyWithChain(nil)
}
func (p7 *PKCS7) SetIsDigest() {
p7.isDigest = true
}
// VerifyWithChain checks the signatures of a PKCS7 object.
//
// If truststore is not nil, it also verifies the chain of trust of
@ -111,8 +120,40 @@ func verifySignature(p7 *PKCS7, signer signerInfo, truststore *smx509.CertPool,
if err != nil {
return err
}
if len(signer.AuthenticatedAttributes) == 0 && p7.isDigest {
return checkSignature(sigalg, signedData, signer.EncryptedDigest, ee.PublicKey)
} else {
return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)
}
}
func checkSignature(algo smx509.SignatureAlgorithm, signed, signature []byte, publicKey crypto.PublicKey) (err error) {
isSM2 := (algo == smx509.SM2WithSM3)
switch pub := publicKey.(type) {
case *rsa.PublicKey:
if algo == smx509.SHA256WithRSAPSS || algo == smx509.SHA384WithRSAPSS || algo == smx509.SHA512WithRSAPSS {
return rsa.VerifyPSS(pub, 0, signed, signature, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash})
} else {
return rsa.VerifyPKCS1v15(pub, 0, signed, signature)
}
case *ecdsa.PublicKey:
if isSM2 {
if !sm2.VerifyASN1(pub, signed, signature) {
return errors.New("x509: SM2 verification failure")
}
} else if !ecdsa.VerifyASN1(pub, signed, signature) {
return errors.New("x509: ECDSA verification failure")
}
return
case ed25519.PublicKey:
if !ed25519.Verify(pub, signed, signature) {
return errors.New("x509: Ed25519 verification failure")
}
return
}
return x509.ErrUnsupportedAlgorithm
}
// GetOnlySigner returns an x509.Certificate for the first signer of the signed
// data payload. If there are more or less than one signer, nil is returned