mirror of
https://github.com/emmansun/gmsm.git
synced 2025-04-21 09:46:18 +08:00
pkcs7: sign precomputed digest #294
This commit is contained in:
parent
5a0ff81dc3
commit
bef7f5421e
@ -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
|
||||
|
||||
import (
|
||||
|
@ -56,3 +56,28 @@ func VerifyMessageDetach(p7Der, sourceData []byte) error {
|
||||
p7.Content = sourceData
|
||||
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()
|
||||
}
|
||||
|
@ -5,8 +5,11 @@
|
||||
package cfca
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/emmansun/gmsm/sm2"
|
||||
)
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ SADK(Security Application Development Kit)是CFCA推出的一套支持全平
|
||||
其JAVA版本(其它语言版本未知)基本上是一个基于[Bouncy Castle](https://www.bouncycastle.org/)的实现(当然它看起来也支持JNI接入OpenSSL、U盾?)。
|
||||
|
||||
## 为什么会有互操作性问题
|
||||
* CFCA有一些实现没有相关标准。
|
||||
* SADK存在较早,可能有些实现早于标准发布。
|
||||
* SADK版本较多,不同版本也会有互操作性兼容问题。
|
||||
* 其它未知原因。
|
||||
|
@ -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
|
||||
@ -47,6 +48,22 @@ func NewSignedData(data []byte) (*SignedData, error) {
|
||||
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
|
||||
// ready to be signed via AddSigner. The digest algorithm is set to SM3 by default
|
||||
// and can be changed by calling SetDigestAlgorithm.
|
||||
@ -61,6 +78,20 @@ func NewSMSignedData(data []byte) (*SignedData, error) {
|
||||
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
|
||||
type SignerInfoConfig struct {
|
||||
ExtraSignedAttributes []Attribute
|
||||
@ -203,9 +234,12 @@ func (sd *SignedData) signWithAttributes(pkey crypto.PrivateKey, config SignerIn
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
h := newHash(hasher, sd.digestOid)
|
||||
h.Write(sd.data)
|
||||
messageDigest := h.Sum(nil)
|
||||
messageDigest := sd.data
|
||||
if !sd.isDigest {
|
||||
h := newHash(hasher, sd.digestOid)
|
||||
h.Write(sd.data)
|
||||
messageDigest = h.Sum(nil)
|
||||
}
|
||||
|
||||
attrs := &attributes{}
|
||||
attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType)
|
||||
@ -250,9 +284,10 @@ 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
|
||||
ias.SerialNumber = ee.SerialNumber
|
||||
// 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 {
|
||||
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")
|
||||
@ -392,7 +427,11 @@ func signData(data []byte, pkey crypto.PrivateKey, hasher crypto.Hash) ([]byte,
|
||||
|
||||
if !hasher.Available() {
|
||||
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) {
|
||||
case *ecdsa.PrivateKey:
|
||||
{
|
||||
@ -404,10 +443,12 @@ func signData(data []byte, pkey crypto.PrivateKey, hasher crypto.Hash) ([]byte,
|
||||
} else {
|
||||
return nil, fmt.Errorf("pkcs7: unsupported hash function %s", hasher)
|
||||
}
|
||||
} else {
|
||||
} else if !isDigest {
|
||||
h := hasher.New()
|
||||
h.Write(data)
|
||||
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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -2,16 +2,21 @@ package pkcs7
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/emmansun/gmsm/sm2"
|
||||
"github.com/emmansun/gmsm/sm3"
|
||||
"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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,12 @@ func (p7 *PKCS7) Verify() (err error) {
|
||||
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.
|
||||
//
|
||||
// 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
|
||||
// otherwise.
|
||||
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 {
|
||||
return errors.New("pkcs7: Message has no 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
|
||||
}
|
||||
}
|
||||
@ -45,24 +62,41 @@ func (p7 *PKCS7) VerifyWithChain(truststore *smx509.CertPool) (err error) {
|
||||
// currentTime. It does not use the signing time authenticated
|
||||
// attribute.
|
||||
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 {
|
||||
return errors.New("pkcs7: Message has no 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 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
|
||||
ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
|
||||
if ee == nil {
|
||||
return errors.New("pkcs7: No certificate for signer")
|
||||
}
|
||||
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 {
|
||||
// TODO(fullsailor): First check the content type match
|
||||
var digest []byte
|
||||
@ -74,9 +108,12 @@ func verifySignature(p7 *PKCS7, signer signerInfo, truststore *smx509.CertPool,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := newHash(hasher, signer.DigestAlgorithm.Algorithm)
|
||||
h.Write(p7.Content)
|
||||
computed := h.Sum(nil)
|
||||
computed := signedData
|
||||
if !isDigest {
|
||||
h := newHash(hasher, signer.DigestAlgorithm.Algorithm)
|
||||
h.Write(p7.Content)
|
||||
computed = h.Sum(nil)
|
||||
}
|
||||
if subtle.ConstantTimeCompare(digest, computed) != 1 {
|
||||
return &MessageDigestMismatchError{
|
||||
ExpectedDigest: digest,
|
||||
@ -97,19 +134,10 @@ func verifySignature(p7 *PKCS7, signer signerInfo, truststore *smx509.CertPool,
|
||||
ee.NotAfter.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)
|
||||
}
|
||||
if isDigest {
|
||||
return ee.CheckSignatureWithDigest(sigalg, signedData, signer.EncryptedDigest)
|
||||
}
|
||||
return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)
|
||||
}
|
||||
|
@ -499,7 +499,7 @@ func getPublicKeyAlgorithmFromOID(oid asn1.ObjectIdentifier) PublicKeyAlgorithm
|
||||
case oid.Equal(oidPublicKeyECDSA):
|
||||
return ECDSA
|
||||
case oid.Equal(oidPublicKeySM2):
|
||||
return ECDSA
|
||||
return ECDSA
|
||||
case oid.Equal(oidPublicKeyEd25519):
|
||||
return Ed25519
|
||||
}
|
||||
@ -732,6 +732,70 @@ func (c *Certificate) CheckSignature(algo SignatureAlgorithm, signed, signature
|
||||
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 {
|
||||
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
|
||||
// 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) {
|
||||
realTemplate, err := toCertificate(template)
|
||||
if err != nil {
|
||||
|
@ -1,14 +1,23 @@
|
||||
package smx509
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -292,3 +301,225 @@ func TestInvalidParentTemplate(t *testing.T) {
|
||||
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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user