diff --git a/cfca/pkcs12_sm2.go b/cfca/pkcs12_sm2.go index 7baa2cc..0e381f1 100644 --- a/cfca/pkcs12_sm2.go +++ b/cfca/pkcs12_sm2.go @@ -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 ( diff --git a/cfca/pkcs7_sign.go b/cfca/pkcs7_sign.go index ed4085b..61a703f 100644 --- a/cfca/pkcs7_sign.go +++ b/cfca/pkcs7_sign.go @@ -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() +} diff --git a/cfca/pkcs7_sign_test.go b/cfca/pkcs7_sign_test.go index 9d1fefb..a42edd8 100644 --- a/cfca/pkcs7_sign_test.go +++ b/cfca/pkcs7_sign_test.go @@ -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) + } +} diff --git a/docs/cfca.md b/docs/cfca.md index 3b35e80..1e4964c 100644 --- a/docs/cfca.md +++ b/docs/cfca.md @@ -10,6 +10,7 @@ SADK(Security Application Development Kit)是CFCA推出的一套支持全平 其JAVA版本(其它语言版本未知)基本上是一个基于[Bouncy Castle](https://www.bouncycastle.org/)的实现(当然它看起来也支持JNI接入OpenSSL、U盾?)。 ## 为什么会有互操作性问题 +* CFCA有一些实现没有相关标准。 * SADK存在较早,可能有些实现早于标准发布。 * SADK版本较多,不同版本也会有互操作性兼容问题。 * 其它未知原因。 diff --git a/pkcs7/sign.go b/pkcs7/sign.go index 53feeda..f43bd60 100644 --- a/pkcs7/sign.go +++ b/pkcs7/sign.go @@ -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) } diff --git a/pkcs7/sign_enveloped.go b/pkcs7/sign_enveloped.go index 877836f..5c5345d 100644 --- a/pkcs7/sign_enveloped.go +++ b/pkcs7/sign_enveloped.go @@ -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 } diff --git a/pkcs7/sign_test.go b/pkcs7/sign_test.go index 1ec8589..88ab6aa 100644 --- a/pkcs7/sign_test.go +++ b/pkcs7/sign_test.go @@ -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) + } + } + } +} diff --git a/pkcs7/verify.go b/pkcs7/verify.go index 4dc40b1..63c8fa7 100644 --- a/pkcs7/verify.go +++ b/pkcs7/verify.go @@ -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) } diff --git a/smx509/x509.go b/smx509/x509.go index fcd2330..d490a4b 100644 --- a/smx509/x509.go +++ b/smx509/x509.go @@ -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 { diff --git a/smx509/x509_additional_test.go b/smx509/x509_additional_test.go index 3b4a20c..483596b 100644 --- a/smx509/x509_additional_test.go +++ b/smx509/x509_additional_test.go @@ -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 +}