diff --git a/pkcs7/pkcs7.go b/pkcs7/pkcs7.go index 0fa2a6c..b18a310 100644 --- a/pkcs7/pkcs7.go +++ b/pkcs7/pkcs7.go @@ -26,6 +26,7 @@ type PKCS7 struct { Certificates []*smx509.Certificate CRLs []pkix.CertificateList Signers []signerInfo + isDigest bool raw any session Session } diff --git a/pkcs7/sign.go b/pkcs7/sign.go index 53feeda..2e92d63 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 @@ -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()) { - opts = sm2.DefaultSM2SignerOpts + if isDigest { + opts = sm2.NewSM2SignerOption(false, nil) + } else { + opts = sm2.DefaultSM2SignerOpts + } switch realKey := key.(type) { case *ecdsa.PrivateKey: { @@ -405,9 +417,11 @@ func signData(data []byte, pkey crypto.PrivateKey, hasher crypto.Hash) ([]byte, return nil, fmt.Errorf("pkcs7: unsupported hash function %s", hasher) } } else { - h := hasher.New() - h.Write(data) - hash = h.Sum(nil) + if !isDigest { + h := hasher.New() + h.Write(data) + hash = h.Sum(nil) + } } 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..566a2fa 100644 --- a/pkcs7/sign_test.go +++ b/pkcs7/sign_test.go @@ -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,51 +38,75 @@ 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) - var toBeSigned *SignedData - if isSM { - toBeSigned, err = NewSMSignedData(content) - } else { - toBeSigned, err = NewSignedData(content) - } - if err != nil { - t.Fatalf("test %s/%s/%s: cannot initialize signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) - } + 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(data) + } else { + toBeSigned, err = NewSignedData(data) + } + toBeSigned.isDigest = testDigest - // Set the digest to match the end entity cert - signerDigest, _ := getDigestOIDForSignatureAlgorithm(sigalgsigner) - toBeSigned.SetDigestAlgorithm(signerDigest) + if err != nil { + t.Fatalf("test %s/%s/%s: cannot initialize signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) + } - 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) - } - if testDetach { - toBeSigned.Detach() - } - 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 testDetach { - // Detached signature should not contain the content - // So we should not be able to find the content in the parsed data - // We should suppliment the content to the parsed data before verifying - p7.Content = content - } - if !bytes.Equal(content, 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 err := p7.VerifyWithChain(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) + // Set the digest to match the end entity cert + 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) + } + if testDetach { + toBeSigned.Detach() + } + 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 testDetach { + // Detached signature should not contain the content + // So we should not be able to find the content in the parsed data + // We should suppliment the content to the parsed data before verifying + p7.Content = data + } + 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) + } + 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) + } } } } @@ -299,52 +325,79 @@ func TestSignWithoutAttr(t *testing.T) { }, } for _, sigalg := range sigalgs { - cert, err := createTestCertificate(sigalg.sigAlg, false) - if err != nil { - t.Fatal(err) - } - var toBeSigned *SignedData - if sigalg.isSM { - toBeSigned, err = NewSMSignedData(content) - } else { - toBeSigned, err = NewSignedData(content) + for _, testDigest := range []bool{false, true} { + cert, err := createTestCertificate(sigalg.sigAlg, false) + if err != nil { + t.Fatal(err) + } + signerDigest, _ := getDigestOIDForSignatureAlgorithm(sigalg.sigAlg) - toBeSigned.SetDigestAlgorithm(signerDigest) - } - 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 !sigalg.skipCert { - if len(p7.Certificates) == 0 { - t.Errorf("No certificates") + 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(data) + } else { + toBeSigned, err = NewSignedData(data) + toBeSigned.SetDigestAlgorithm(signerDigest) } - err = p7.Verify() if err != nil { - t.Fatal(err) + t.Fatalf("Cannot initialize signed data: %s", err) } - } else { - if len(p7.Certificates) > 0 { - t.Errorf("No certificates expected") + toBeSigned.isDigest = testDigest + + if err := toBeSigned.SignWithoutAttr(cert.Certificate, *cert.PrivateKey, SignerInfoConfig{SkipCertificates: sigalg.skipCert}); err != nil { + t.Fatalf("Cannot add signer: %s", err) } - err = p7.Verify() - 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.Verify() + signed, err := toBeSigned.Finish() if err != nil { - t.Fatal(err) + t.Fatalf("Cannot finish signing data: %s", err) + } + p7, err := Parse(signed) + 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") + } + err = p7.Verify() + if err != nil { + t.Fatal(err) + } + } else { + if len(p7.Certificates) > 0 { + t.Errorf("No certificates expected") + } + err = p7.Verify() + 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.Verify() + if err != nil { + t.Fatal(err) + } } } } diff --git a/pkcs7/verify.go b/pkcs7/verify.go index 4dc40b1..f3c5905 100644 --- a/pkcs7/verify.go +++ b/pkcs7/verify.go @@ -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,7 +120,39 @@ func verifySignature(p7 *PKCS7, signer signerInfo, truststore *smx509.CertPool, if err != nil { return err } - return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest) + + 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