2023-03-09 11:45:39 +08:00
package pkcs7
import (
"bytes"
2025-01-14 16:41:03 +08:00
"crypto"
"crypto/ecdsa"
2023-03-09 11:45:39 +08:00
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"fmt"
2025-01-14 16:41:03 +08:00
"hash"
2023-03-09 11:45:39 +08:00
"log"
"os"
"os/exec"
"testing"
2025-01-14 16:41:03 +08:00
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/sm3"
2023-03-09 11:45:39 +08:00
"github.com/emmansun/gmsm/smx509"
)
func testSign ( 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 )
}
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 )
}
// Set the digest to match the end entity cert
signerDigest , _ := getDigestOIDForSignatureAlgorithm ( sigalgsigner )
toBeSigned . SetDigestAlgorithm ( signerDigest )
2024-12-09 19:54:01 +08:00
if err := toBeSigned . AddSignerChain ( signerCert . Certificate , * signerCert . PrivateKey , parents , SignerInfoConfig { } ) ; err != nil {
2023-03-09 11:45:39 +08:00
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 {
2024-06-17 17:42:00 +08:00
// 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
2023-03-09 11:45:39 +08:00
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 )
}
}
}
}
}
}
2024-12-19 15:42:19 +08:00
2023-03-09 11:45:39 +08:00
func TestSign ( 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 ,
}
testSign ( t , false , content , sigalgs )
}
func TestSignSM ( t * testing . T ) {
content := [ ] byte ( "Hello World" )
sigalgs := [ ] x509 . SignatureAlgorithm {
smx509 . SM2WithSM3 ,
}
testSign ( t , true , content , sigalgs )
}
func ExampleSignedData ( ) {
// generate a signing cert or load a key pair
2024-11-20 08:22:43 +08:00
cert , err := createTestCertificate ( x509 . SHA256WithRSA , false )
2023-03-09 11:45:39 +08:00
if err != nil {
fmt . Printf ( "Cannot create test certificates: %s" , err )
}
// Initialize a SignedData struct with content to be signed
signedData , err := NewSignedData ( [ ] byte ( "Example data to be signed" ) )
if err != nil {
fmt . Printf ( "Cannot initialize signed data: %s" , err )
}
// Add the signing cert and private key
if err := signedData . AddSigner ( cert . Certificate , cert . PrivateKey , SignerInfoConfig { } ) ; err != nil {
fmt . Printf ( "Cannot add signer: %s" , err )
}
// Call Detach() is you want to remove content from the signature
// and generate an S/MIME detached signature
signedData . Detach ( )
// Finish() to obtain the signature bytes
detachedSignature , err := signedData . Finish ( )
if err != nil {
fmt . Printf ( "Cannot finish signing data: %s" , err )
}
pem . Encode ( os . Stdout , & pem . Block { Type : "PKCS7" , Bytes : detachedSignature } )
}
func TestUnmarshalSignedAttribute ( t * testing . T ) {
2024-11-20 08:22:43 +08:00
cert , err := createTestCertificate ( x509 . SHA512WithRSA , false )
2023-03-09 11:45:39 +08:00
if err != nil {
t . Fatal ( err )
}
content := [ ] byte ( "Hello World" )
toBeSigned , err := NewSignedData ( content )
if err != nil {
t . Fatalf ( "Cannot initialize signed data: %s" , err )
}
oidTest := asn1 . ObjectIdentifier { 2 , 3 , 4 , 5 , 6 , 7 }
testValue := "TestValue"
2024-12-09 19:54:01 +08:00
if err := toBeSigned . AddSigner ( cert . Certificate , * cert . PrivateKey , SignerInfoConfig {
2023-03-15 17:50:27 +08:00
ExtraSignedAttributes : [ ] Attribute { { Type : oidTest , Value : testValue } } ,
2023-03-09 11:45:39 +08:00
} ) ; 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 )
}
var actual string
err = p7 . UnmarshalSignedAttribute ( oidTest , & actual )
if err != nil {
t . Fatalf ( "Cannot unmarshal test value: %s" , err )
}
if testValue != actual {
t . Errorf ( "Attribute does not match test value\n\tExpected: %s\n\tActual: %s" , testValue , actual )
}
err = p7 . Verify ( )
if err != nil {
t . Fatal ( err )
}
}
func TestSkipCertificates ( t * testing . T ) {
2024-11-20 08:22:43 +08:00
cert , err := createTestCertificate ( x509 . SHA512WithRSA , false )
2023-03-09 11:45:39 +08:00
if err != nil {
t . Fatal ( err )
}
content := [ ] byte ( "Hello World" )
toBeSigned , err := NewSignedData ( content )
if err != nil {
t . Fatalf ( "Cannot initialize signed data: %s" , err )
}
2024-12-09 19:54:01 +08:00
if err := toBeSigned . AddSigner ( cert . Certificate , * cert . PrivateKey , SignerInfoConfig { } ) ; err != nil {
2023-03-09 11:45:39 +08:00
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 . Certificates ) == 0 {
t . Errorf ( "No certificates" )
}
toBeSigned2 , err := NewSignedData ( content )
if err != nil {
t . Fatalf ( "Cannot initialize signed data: %s" , err )
}
2024-12-09 19:54:01 +08:00
if err := toBeSigned2 . AddSigner ( cert . Certificate , * cert . PrivateKey , SignerInfoConfig { SkipCertificates : true } ) ; err != nil {
2023-03-09 11:45:39 +08:00
t . Fatalf ( "Cannot add signer: %s" , err )
}
signed , err = toBeSigned2 . 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 . Certificates ) > 0 {
t . Errorf ( "Have certificates: %v" , p7 . Certificates )
}
2024-06-17 17:42:00 +08:00
// For skip certificates, we should not be able to verify the signature
// because the signer certificate is not in the chain
// we should suppliment the signer certificate to the parsed data before verifying
p7 . Certificates = append ( p7 . Certificates , cert . Certificate )
err = p7 . Verify ( )
if err != nil {
t . Fatal ( err )
}
2023-03-09 11:45:39 +08:00
}
func TestDegenerateCertificate ( t * testing . T ) {
2024-11-20 08:22:43 +08:00
cert , err := createTestCertificate ( x509 . SHA1WithRSA , false )
2023-03-09 11:45:39 +08:00
if err != nil {
t . Fatal ( err )
}
deg , err := DegenerateCertificate ( cert . Certificate . Raw )
if err != nil {
t . Fatal ( err )
}
testOpenSSLParse ( t , deg )
pem . Encode ( os . Stdout , & pem . Block { Type : "PKCS7" , Bytes : deg } )
}
// writes the cert to a temporary file and tests that openssl can read it.
func testOpenSSLParse ( t * testing . T , certBytes [ ] byte ) {
2025-01-22 10:28:01 +08:00
tmpCertFile , err := os . CreateTemp ( "" , "testCertificate" )
2023-03-09 11:45:39 +08:00
if err != nil {
t . Fatal ( err )
}
defer os . Remove ( tmpCertFile . Name ( ) ) // clean up
if _ , err := tmpCertFile . Write ( certBytes ) ; err != nil {
t . Fatal ( err )
}
opensslCMD := exec . Command ( "openssl" , "pkcs7" , "-inform" , "der" , "-in" , tmpCertFile . Name ( ) )
_ , err = opensslCMD . Output ( )
if err != nil {
t . Fatal ( err )
}
if err := tmpCertFile . Close ( ) ; err != nil {
t . Fatal ( err )
}
}
2023-03-28 08:41:01 +08:00
func TestSignWithoutAttr ( t * testing . T ) {
content := [ ] byte ( "Hello World" )
sigalgs := [ ] struct {
2024-10-07 15:02:49 +08:00
isSM bool
sigAlg x509 . SignatureAlgorithm
skipCert bool
2023-03-28 08:41:01 +08:00
} {
{
false ,
x509 . SHA256WithRSA ,
2024-10-07 15:02:49 +08:00
false ,
} ,
{
true ,
smx509 . SM2WithSM3 ,
false ,
} ,
{
false ,
x509 . SHA256WithRSA ,
true ,
2023-03-28 08:41:01 +08:00
} ,
{
true ,
smx509 . SM2WithSM3 ,
2024-10-07 15:02:49 +08:00
true ,
2023-03-28 08:41:01 +08:00
} ,
}
for _ , sigalg := range sigalgs {
2024-11-20 08:22:43 +08:00
cert , err := createTestCertificate ( sigalg . sigAlg , false )
2023-03-28 08:41:01 +08:00
if err != nil {
t . Fatal ( err )
}
var toBeSigned * SignedData
if sigalg . isSM {
toBeSigned , err = NewSMSignedData ( content )
} else {
toBeSigned , err = NewSignedData ( content )
signerDigest , _ := getDigestOIDForSignatureAlgorithm ( sigalg . sigAlg )
toBeSigned . SetDigestAlgorithm ( signerDigest )
}
if err != nil {
t . Fatalf ( "Cannot initialize signed data: %s" , err )
}
2024-12-09 19:54:01 +08:00
if err := toBeSigned . SignWithoutAttr ( cert . Certificate , * cert . PrivateKey , SignerInfoConfig { SkipCertificates : sigalg . skipCert } ) ; err != nil {
2023-03-28 08:41:01 +08:00
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 )
}
2024-10-07 15:02:49 +08:00
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 )
}
2023-03-28 08:41:01 +08:00
}
}
}
2025-01-14 16:41:03 +08:00
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 {
2025-01-15 08:50:09 +08:00
toBeSigned , err = NewSMSignedDataWithDigest ( digest )
2025-01-14 16:41:03 +08:00
} else {
2025-01-15 08:50:09 +08:00
toBeSigned , err = NewSignedDataWithDigest ( digest )
2025-01-14 16:41:03 +08:00
}
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" )
}
2025-01-14 16:52:15 +08:00
p7 . Content = digest
2025-01-14 16:41:03 +08:00
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 {
2025-01-15 08:50:09 +08:00
toBeSigned , err = NewSMSignedDataWithDigest ( digest )
2025-01-14 16:41:03 +08:00
} else {
2025-01-15 08:50:09 +08:00
toBeSigned , err = NewSignedDataWithDigest ( digest )
2025-01-14 16:41:03 +08:00
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 )
}
}
}
}