From 5d46f47c153db16e97c7acd95149fcd34acf3489 Mon Sep 17 00:00:00 2001 From: Sun Yimin Date: Fri, 10 Mar 2023 17:09:29 +0800 Subject: [PATCH] add cfca sm2 key and certificate parse --- cfca/pkcs12_sm2.go | 124 ++++++++++++++++++++++++++++++++++++++++ cfca/pkcs12_sm2_test.go | 94 ++++++++++++++++++++++++++++++ pkcs7/pkcs7.go | 2 +- pkcs7/sign_enveloped.go | 2 +- sm2/sm2_envelopedkey.go | 2 +- 5 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 cfca/pkcs12_sm2.go create mode 100644 cfca/pkcs12_sm2_test.go diff --git a/cfca/pkcs12_sm2.go b/cfca/pkcs12_sm2.go new file mode 100644 index 0000000..1d894ed --- /dev/null +++ b/cfca/pkcs12_sm2.go @@ -0,0 +1,124 @@ +// Package cfca handles cfca issued key and certificate +package cfca + +import ( + "crypto/cipher" + "encoding/asn1" + "errors" + "fmt" + "math/big" + + "github.com/emmansun/gmsm/kdf" + "github.com/emmansun/gmsm/padding" + "github.com/emmansun/gmsm/pkcs" + "github.com/emmansun/gmsm/sm2" + "github.com/emmansun/gmsm/sm3" + "github.com/emmansun/gmsm/sm4" + "github.com/emmansun/gmsm/smx509" +) + +// CFCA私有格式,在SADK中把它定义为PKCS12_SM2 + +type cfcaKeyPairData struct { + Version int `asn1:"default:1"` + EncryptedKey keyData + Certificate certData +} + +// 被加密的私钥数据 +type keyData struct { + ContentType asn1.ObjectIdentifier + Algorithm asn1.ObjectIdentifier + EncryptedContent asn1.RawValue +} + +// 对应的证书 +type certData struct { + ContentType asn1.ObjectIdentifier + Content asn1.RawContent +} + +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} +) + +// ParseSM2 parses the der data, returns private key and related certificate, it's CFCA private structure. +func ParseSM2(password, data []byte) (*sm2.PrivateKey, *smx509.Certificate, error) { + var keys cfcaKeyPairData + if _, err := asn1.Unmarshal(data, &keys); err != nil { + return nil, nil, err + } + if !keys.Certificate.ContentType.Equal(oidSM2Data) { + return nil, nil, fmt.Errorf("cfca: unsupported content type oid <%v>", keys.Certificate.ContentType) + } + if !keys.EncryptedKey.ContentType.Equal(oidSM2Data) { + return nil, nil, fmt.Errorf("cfca: unsupported content type oid <%v>", keys.EncryptedKey.ContentType) + } + if !keys.EncryptedKey.Algorithm.Equal(oidSM4) && !keys.EncryptedKey.Algorithm.Equal(oidSM4CBC) { + return nil, nil, fmt.Errorf("cfca: unsupported algorithm <%v>", keys.EncryptedKey.Algorithm) + } + ivkey := kdf.Kdf(sm3.New(), password, 32) + marshalledIV, err := asn1.Marshal(ivkey[:16]) + if err != nil { + return nil, nil, err + } + pk, err := pkcs.SM4CBC.Decrypt(ivkey[16:], &asn1.RawValue{FullBytes: marshalledIV}, keys.EncryptedKey.EncryptedContent.Bytes) + if err != nil { + return nil, nil, err + } + d := new(big.Int).SetBytes(pk) // here we do NOT check if the d is in (0, N) or not + // Create private key from *big.Int + prvKey := new(sm2.PrivateKey) + prvKey.Curve = sm2.P256() + prvKey.D = d + prvKey.PublicKey.X, prvKey.PublicKey.Y = prvKey.ScalarBaseMult(prvKey.D.Bytes()) + + cert, err := smx509.ParseCertificate(keys.Certificate.Content) + if err != nil { + return nil, nil, err + } + + if !prvKey.PublicKey.Equal(cert.PublicKey) { + return nil, nil, errors.New("cfca: public key and private key do not match") + } + return prvKey, cert, nil +} + +// MarshalSM2 encodes sm2 private key and related certificate to cfca defined format +func MarshalSM2(password []byte, key *sm2.PrivateKey, cert *smx509.Certificate) ([]byte, error) { + if len(password) == 0 { + return nil, errors.New("cfca: invalid password") + } + ivkey := kdf.Kdf(sm3.New(), password, 32) + block, err := sm4.NewCipher(ivkey[16:]) + if err != nil { + return nil, err + } + mode := cipher.NewCBCEncrypter(block, ivkey[:16]) + pkcs7 := padding.NewPKCS7Padding(uint(block.BlockSize())) + plainText := pkcs7.Pad(key.D.Bytes()) + ciphertext := make([]byte, len(plainText)) + mode.CryptBlocks(ciphertext, plainText) + + ciphertext, err = asn1.Marshal(ciphertext) + if err != nil { + return nil, err + } + + keys := cfcaKeyPairData{ + Version: 1, + EncryptedKey: keyData{ + ContentType: oidSM2Data, + Algorithm: oidSM4, + EncryptedContent: asn1.RawValue{FullBytes: ciphertext}, + }, + Certificate: certData{ + ContentType: oidSM2Data, + Content: cert.Raw, + }, + } + + return asn1.Marshal(keys) +} diff --git a/cfca/pkcs12_sm2_test.go b/cfca/pkcs12_sm2_test.go new file mode 100644 index 0000000..35c5153 --- /dev/null +++ b/cfca/pkcs12_sm2_test.go @@ -0,0 +1,94 @@ +package cfca + +import ( + "encoding/pem" + "errors" + "testing" + + "github.com/emmansun/gmsm/sm2" + "github.com/emmansun/gmsm/smx509" +) + +var v2exKeyPem = `-----BEGIN CFCA KEY----- +MIIDSQIBATBHBgoqgRzPVQYBBAIBBgcqgRzPVQFoBDDkLvKllj9ZWhaKU6MSnxBBV5yaF3tEcOk1 +vQniWyVzyaQA4F3j/YvDJwEoE8gOF/swggL5BgoqgRzPVQYBBAIBBIIC6TCCAuUwggKJoAMCAQIC +BRBAmQgJMAwGCCqBHM9VAYN1BQAwXDELMAkGA1UEBhMCQ04xMDAuBgNVBAoMJ0NoaW5hIEZpbmFu +Y2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEbMBkGA1UEAwwSQ0ZDQSBURVNUIFNNMiBPQ0Ex +MB4XDTIwMTExOTA4MzExOFoXDTI1MTExOTA4MzExOFowgYkxCzAJBgNVBAYTAkNOMRcwFQYDVQQK +DA5DRkNBIFRFU1QgT0NBMTENMAsGA1UECwwEUFNCQzEZMBcGA1UECwwQT3JnYW5pemF0aW9uYWwt +MjE3MDUGA1UEAwwuMDUxQOmCruWCqOe6v+S4iuaUtuWNleWVhuaIt0BONTEwMTEzMDAwMTg4NzhA +MTBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABJVRC63OKfcL4H324rDOdb4SSlbAjoJDXnK0qmwX +Z59FWmiSqt3ipreljKew4QynjTgR/yfp9yjNgNU8G5pkYdujggEGMIIBAjAfBgNVHSMEGDAWgBRr +/hjaj0I6prhtsy6Igzo0osEw4TAMBgNVHRMBAf8EAjAAMEgGA1UdIARBMD8wPQYIYIEchu8qAQEw +MTAvBggrBgEFBQcCARYjaHR0cDovL3d3dy5jZmNhLmNvbS5jbi91cy91cy0xNC5odG0wOQYDVR0f +BDIwMDAuoCygKoYoaHR0cDovL3VjcmwuY2ZjYS5jb20uY24vU00yL2NybDE0MzU2LmNybDAOBgNV +HQ8BAf8EBAMCBsAwHQYDVR0OBBYEFPiGPZT0oTuRXvkyGoOgviNEWnc1MB0GA1UdJQQWMBQGCCsG +AQUFBwMCBggrBgEFBQcDBDAMBggqgRzPVQGDdQUAA0gAMEUCIQCJDSsVPfhr+gnDASMj5Syt+hxs +amHygPecjCLbcdFQQgIgSXC4musF5Fnj/CpNTqvk9+56FuINkATGS8xRh7kzKBE= +-----END CFCA KEY----- +` + +var cfcasm2oca1 = `-----BEGIN CERTIFICATE----- +MIICTTCCAfKgAwIBAgIKZCTXgL0MKPOtBzAMBggqgRzPVQGDdQUAMF0xCzAJBgNV +BAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkxHDAaBgNVBAMME0NGQ0EgVEVTVCBDUyBTTTIgQ0EwHhcNMTIxMjI1 +MTIyNTA2WhcNMzIwNzIzMTIyNTA2WjBcMQswCQYDVQQGEwJDTjEwMC4GA1UECgwn +Q2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRswGQYDVQQD +DBJDRkNBIFRFU1QgU00yIE9DQTEwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAAQz +uFgJbedY55u6NToJElGWzPT+9UF1dxcopnerNO3fqRd4C1lDzz9LJZSfmMyNYaky +YC+6zh9G6/aPXW1Od/RFo4GYMIGVMB8GA1UdIwQYMBaAFLXYkG9c8Ngz0mO9frLD +jcZPEnphMAwGA1UdEwQFMAMBAf8wOAYDVR0fBDEwLzAtoCugKYYnaHR0cDovLzIx +MC43NC40Mi4zL3Rlc3RyY2EvU00yL2NybDEuY3JsMAsGA1UdDwQEAwIBBjAdBgNV +HQ4EFgQUa/4Y2o9COqa4bbMuiIM6NKLBMOEwDAYIKoEcz1UBg3UFAANHADBEAiAR +kDmkQ0Clio48994IUs63nA8k652O2C4+7EQs1SSbuAIgcwNUrHJyEYX8xT5BKl9T +lJOefzCNNJW5Z0f3Y/SjaG0= +-----END CERTIFICATE----- +` + +func parseTestKeyAndCert() (*sm2.PrivateKey, *smx509.Certificate, error) { + password := []byte("123456") + var block *pem.Block + block, rest := pem.Decode([]byte(v2exKeyPem)) + if len(rest) != 0 { + return nil, nil, errors.New("unexpected remaining PEM block during decode") + } + return ParseSM2(password, block.Bytes) +} + +func TestParseSM2(t *testing.T) { + _, _, err := parseTestKeyAndCert() + if err != nil { + t.Fatal(err) + } +} + +func TestMarshalSM2(t *testing.T) { + password := []byte("changeit") + priv, cert, err := parseTestKeyAndCert() + if err != nil { + t.Fatal(err) + } + rootca1, err := smx509.ParseCertificatePEM([]byte(cfcasm2oca1)) + if err != nil { + t.Fatal(err) + } + err = rootca1.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature) + if err != nil { + t.Fatal(err) + } + result, err := MarshalSM2(password, priv, cert) + if err != nil { + t.Fatal(err) + } + + priv1, cert1, err := ParseSM2(password, result) + if err != nil { + t.Fatal(err) + } + if !priv.Equal(priv1) { + t.Fatal("not same private key") + } + if !cert.Equal(cert1) { + t.Fatal("not same certficate") + } +} diff --git a/pkcs7/pkcs7.go b/pkcs7/pkcs7.go index 646a151..50fc7e0 100644 --- a/pkcs7/pkcs7.go +++ b/pkcs7/pkcs7.go @@ -190,7 +190,7 @@ func getOIDForEncryptionAlgorithm(pkey interface{}, OIDDigestAlg asn1.ObjectIden case crypto.Signer: return getOIDForEncryptionAlgorithm(k.Public(), OIDDigestAlg) } - return nil, fmt.Errorf("pkcs7: cannot convert encryption algorithm to oid, unknown private key type %T", pkey) + return nil, fmt.Errorf("pkcs7: cannot convert encryption algorithm to oid, unknown or unsupported private key type %T", pkey) } diff --git a/pkcs7/sign_enveloped.go b/pkcs7/sign_enveloped.go index d65ea5c..a058c21 100644 --- a/pkcs7/sign_enveloped.go +++ b/pkcs7/sign_enveloped.go @@ -16,7 +16,7 @@ import ( // since the signed-and-enveloped-data content type does not have authenticated or unauthenticated attributes, // and does not provide enveloping of signer information other than the signature. type signedEnvelopedData struct { - Version int + Version int `asn1:"default:1"` RecipientInfos []recipientInfo `asn1:"set"` DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` EncryptedContentInfo encryptedContentInfo diff --git a/sm2/sm2_envelopedkey.go b/sm2/sm2_envelopedkey.go index 0bd109a..0a76786 100644 --- a/sm2/sm2_envelopedkey.go +++ b/sm2/sm2_envelopedkey.go @@ -25,7 +25,7 @@ var ( // // SM2EnvelopedKey ::= SEQUENCE { // symAlgID AlgorithmIdentifier, -// sysmEncryptedKey SM2Cipher, +// symEncryptedKey SM2Cipher, // sm2PublicKey SM2PublicKey, // sm2EncryptedPrivateKey BIT STRING, // }