From a599819ef879d19ba14ecd84862bb0dca15caa0b Mon Sep 17 00:00:00 2001 From: Sun Yimin Date: Tue, 3 Dec 2024 17:53:25 +0800 Subject: [PATCH] cfca: provide cfca envelope message facades #283 --- cfca/pkcs12_sm2.go | 7 +- cfca/pkcs7_envelope.go | 53 +++++++++++ cfca/pkcs7_envelope_test.go | 171 ++++++++++++++++++++++++++++++++++++ smx509/x509.go | 2 +- 4 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 cfca/pkcs7_envelope.go create mode 100644 cfca/pkcs7_envelope_test.go diff --git a/cfca/pkcs12_sm2.go b/cfca/pkcs12_sm2.go index 97eb02d..b985b53 100644 --- a/cfca/pkcs12_sm2.go +++ b/cfca/pkcs12_sm2.go @@ -10,6 +10,7 @@ import ( "github.com/emmansun/gmsm/padding" "github.com/emmansun/gmsm/pkcs" + "github.com/emmansun/gmsm/pkcs7" "github.com/emmansun/gmsm/sm2" "github.com/emmansun/gmsm/sm3" "github.com/emmansun/gmsm/sm4" @@ -38,9 +39,9 @@ type certData struct { } var ( - oidSM2Data = asn1.ObjectIdentifier{1, 2, 156, 10197, 6, 1, 4, 2, 1} - oidSM4 = asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 104} // SADK中认为这就是SM4_CBC,不知道是不是历史原因 - oidSM4CBC = asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 104, 2} + oidSM2Data = pkcs7.SM2OIDData + oidSM4 = pkcs.SM4.OID() + oidSM4CBC = pkcs.SM4CBC.OID() ) // ParseSM2 parses the der data, returns private key and related certificate, it's CFCA private structure. diff --git a/cfca/pkcs7_envelope.go b/cfca/pkcs7_envelope.go new file mode 100644 index 0000000..01c09ba --- /dev/null +++ b/cfca/pkcs7_envelope.go @@ -0,0 +1,53 @@ +// Copyright 2024 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cfca + +import ( + "crypto" + + "github.com/emmansun/gmsm/pkcs" + "github.com/emmansun/gmsm/pkcs7" + "github.com/emmansun/gmsm/smx509" +) + +// EnvelopeMessage creates and returns an envelope data PKCS7 structure (DER encoded) with encrypted +// recipient keys for each recipient public key. +// +// The OIDs use GM/T 0010 - 2012 set and the encrypted key uses ASN.1 format. +// This function uses recipient's SubjectKeyIdentifier to identify the recipient. +func EnvelopeMessage(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate) ([]byte, error) { + return pkcs7.EnvelopeMessageCFCA(cipher, content, recipients) +} + +// OpenEnvelopedMessage decrypts the enveloped message (DER encoded) using the provided certificate and private key. +// The certificate is used to identify the recipient and the private key is used to decrypt the encrypted key. +func OpenEnvelopedMessage(data []byte, recipientCert *smx509.Certificate, key crypto.PrivateKey) ([]byte, error) { + p7, err := pkcs7.Parse(data) + if err != nil { + return nil, err + } + return p7.Decrypt(recipientCert, key) +} + +// EnvelopeMessageLegacy creates and returns an envelope data PKCS7 structure (DER encoded) with encrypted +// recipient keys for each recipient public key. This method is used for CFCA SADK verion less than 3.2 compatibility. +// +// The OIDs use GM/T 0010 - 2012 set and the encrypted key use C1C2C3 format and without 0x4 prefix. +// This function uses recipient's IssuerAndSerialNumber to identify the recipient. +func EnvelopeMessageLegacy(cipher pkcs.Cipher, content []byte, recipients []*smx509.Certificate) ([]byte, error) { + return pkcs7.EncryptCFCA(cipher, content, recipients) +} + +// OpenEnvelopedMessageLegacy decrypts the enveloped message (DER encoded) using the provided certificate and private key. +// The certificate is used to identify the recipient and the private key is used to decrypt the encrypted key. +// +// This method is used for CFCA SADK verion less than 3.2 compatibility. +func OpenEnvelopedMessageLegacy(data []byte, recipientCert *smx509.Certificate, key crypto.PrivateKey) ([]byte, error) { + p7, err := pkcs7.Parse(data) + if err != nil { + return nil, err + } + return p7.DecryptCFCA(recipientCert, key) +} diff --git a/cfca/pkcs7_envelope_test.go b/cfca/pkcs7_envelope_test.go new file mode 100644 index 0000000..e2261c4 --- /dev/null +++ b/cfca/pkcs7_envelope_test.go @@ -0,0 +1,171 @@ +// Copyright 2024 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cfca + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "math/big" + "testing" + "time" + + "github.com/emmansun/gmsm/pkcs" + "github.com/emmansun/gmsm/sm2" + "github.com/emmansun/gmsm/smx509" +) + +type certKeyPair struct { + Certificate *smx509.Certificate + PrivateKey *crypto.PrivateKey +} + +func createTestSM2Certificate(allCA bool) (certKeyPair, error) { + signer, err := createTestSM2CertificateByIssuer("Eddard Stark", nil, smx509.SM2WithSM3, true) + if err != nil { + return certKeyPair{}, err + } + pair, err := createTestSM2CertificateByIssuer("Jon Snow", signer, smx509.SM2WithSM3, allCA) + if err != nil { + return certKeyPair{}, err + } + return *pair, nil +} + +func createTestSM2CertificateByIssuer(name string, issuer *certKeyPair, sigAlg x509.SignatureAlgorithm, isCA bool) (*certKeyPair, error) { + var ( + err error + priv crypto.PrivateKey + derCert []byte + issuerCert *smx509.Certificate + issuerKey crypto.PrivateKey + ) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: name, + Organization: []string{"Acme Co"}, + }, + NotBefore: time.Now().Add(-1 * time.Second), + NotAfter: time.Now().AddDate(1, 0, 0), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}, + } + if issuer != nil { + issuerCert = issuer.Certificate + issuerKey = *issuer.PrivateKey + } + + switch sigAlg { + case smx509.SM2WithSM3: + priv, err = sm2.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unsupported signature algorithm %v", sigAlg) + } + if isCA { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + template.BasicConstraintsValid = true + } + if issuer == nil { + // no issuer given,make this a self-signed root cert + issuerCert = (*smx509.Certificate)(&template) + issuerKey = priv + } + + switch pkey := priv.(type) { + case *sm2.PrivateKey: + derCert, err = smx509.CreateCertificate(rand.Reader, &template, (*x509.Certificate)(issuerCert), pkey.Public(), issuerKey) + default: + return nil, fmt.Errorf("unsupported private key type %T", pkey) + } + if err != nil { + return nil, err + } + if len(derCert) == 0 { + return nil, fmt.Errorf("no certificate created, probably due to wrong keys. types were %T and %T", priv, issuerKey) + } + cert, err := smx509.ParseCertificate(derCert) + if err != nil { + return nil, err + } + // pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + return &certKeyPair{ + Certificate: cert, + PrivateKey: &priv, + }, nil +} + +func TestEnvelopeMessage(t *testing.T) { + ciphers := []pkcs.Cipher{ + pkcs.SM4, + pkcs.SM4CBC, + } + for _, cipher := range ciphers { + plaintext := []byte("Hello Secret World!") + cert, err := createTestSM2Certificate(true) + if err != nil { + t.Fatal(err) + } + encrypted, err := EnvelopeMessage(cipher, plaintext, []*smx509.Certificate{cert.Certificate}) + if err != nil { + t.Fatal(err) + } + _, err = OpenEnvelopedMessage(encrypted[:len(encrypted)-1], cert.Certificate, *cert.PrivateKey) + if err == nil { + t.Fatalf("expected error when decrypting with wrong key, got nil") + } + // pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: encrypted}) + result, err := OpenEnvelopedMessage(encrypted, cert.Certificate, *cert.PrivateKey) + if err != nil { + t.Fatalf("cannot Decrypt encrypted result: %v", err) + } + if !bytes.Equal(plaintext, result) { + t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) + } + } +} + +func TestEnvelopeMessageLegacy(t *testing.T) { + ciphers := []pkcs.Cipher{ + pkcs.SM4, + pkcs.SM4CBC, + } + for _, cipher := range ciphers { + plaintext := []byte("Hello Secret World!") + cert, err := createTestSM2Certificate(false) + if err != nil { + t.Fatal(err) + } + encrypted, err := EnvelopeMessageLegacy(cipher, plaintext, []*smx509.Certificate{cert.Certificate}) + if err != nil { + t.Fatal(err) + } + _, err = OpenEnvelopedMessage(encrypted[:len(encrypted)-1], cert.Certificate, *cert.PrivateKey) + if err == nil { + t.Fatalf("expected error when decrypting with wrong key, got nil") + } + // pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: encrypted}) + result, err := OpenEnvelopedMessageLegacy(encrypted, cert.Certificate, *cert.PrivateKey) + if err != nil { + t.Fatalf("cannot Decrypt encrypted result: %v", err) + } + if !bytes.Equal(plaintext, result) { + t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) + } + } +} diff --git a/smx509/x509.go b/smx509/x509.go index 4aaa820..fcd2330 100644 --- a/smx509/x509.go +++ b/smx509/x509.go @@ -1466,7 +1466,7 @@ func CreateCertificate(rand io.Reader, template, parent, pub, priv any) ([]byte, } } - // RFC 5280 Section 4.1.2.2: serial number must positive + // RFC 5280 Section 4.1.2.2: serial number must be positive // We _should_ also restrict serials to <= 20 octets, but it turns out a lot of people // get this wrong, in part because the encoding can itself alter the length of the