diff --git a/smx509/cfca_csr.go b/smx509/cfca_csr.go index 63c862b..07b4559 100644 --- a/smx509/cfca_csr.go +++ b/smx509/cfca_csr.go @@ -8,6 +8,7 @@ import ( "bytes" "crypto" "crypto/ecdsa" + "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" @@ -65,14 +66,12 @@ func CreateCFCACertificateRequest(rand io.Reader, template *x509.CertificateRequ var rawAttributes []asn1.RawValue // Add the temporary public key and challenge password if requested. if tmpPub != nil { - if !sm2.IsSM2PublicKey(tmpPub) { - return nil, errors.New("x509: only SM2 public key is supported") - } - rawAttributes, err = buildChallengePasswordAttr(rawAttributes, challengePassword) + rawAttributes, err = buildTmpPublicKeyAttr(key, rawAttributes, tmpPub) if err != nil { return nil, err } - rawAttributes, err = buildTmpPublicKeyAttr(rawAttributes, tmpPub) + + rawAttributes, err = buildChallengePasswordAttr(rawAttributes, challengePassword) if err != nil { return nil, err } @@ -150,13 +149,30 @@ type tmpPublicKeyInfo struct { PublicKey []byte } -func buildTmpPublicKeyAttr(rawAttributes []asn1.RawValue, tmpPub crypto.PublicKey) ([]asn1.RawValue, error) { - var publicKeyBytes [136]byte - // Prefix{8} || X{32} || zero{32} || Y{32} || zero{32} - copy(publicKeyBytes[:], tmpPublicKeyPrefix) - ecPub, _ := tmpPub.(*ecdsa.PublicKey) - ecPub.X.FillBytes(publicKeyBytes[8:40]) - ecPub.Y.FillBytes(publicKeyBytes[72:104]) +func buildTmpPublicKeyAttr(key crypto.Signer, rawAttributes []asn1.RawValue, tmpPub crypto.PublicKey) ([]asn1.RawValue, error) { + var publicKeyBytes []byte + var err error + switch key.(type) { + case *sm2.PrivateKey: + if !sm2.IsSM2PublicKey(tmpPub) { + return nil, errors.New("x509: SM2 temp public key is required") + } + publicKeyBytes = make([]byte, 136) + // Prefix{8} || X{32} || zero{32} || Y{32} || zero{32} + copy(publicKeyBytes[:], tmpPublicKeyPrefix) + ecPub, _ := tmpPub.(*ecdsa.PublicKey) + ecPub.X.FillBytes(publicKeyBytes[8:40]) + ecPub.Y.FillBytes(publicKeyBytes[72:104]) + case *rsa.PrivateKey: + pub, ok := tmpPub.(*rsa.PublicKey) + if !ok { + return nil, errors.New("x509: RSA temp public key is required") + } + publicKeyBytes = x509.MarshalPKCS1PublicKey(pub) + default: + return nil, errors.New("x509: only RSA or SM2 key is supported") + + } var tmpPublicKey = tmpPublicKeyInfo{ Version: 1, PublicKey: publicKeyBytes[:], @@ -231,13 +247,20 @@ func parseCFCAAttributes(out *CertificateRequestCFCA, rawAttributes []asn1.RawVa continue } keyBytes := tmpPub.PublicKey - if len(keyBytes) == 136 && bytes.Equal(tmpPublicKeyPrefix, keyBytes[:8]) { - // parse the public key - copy(keyBytes[40:72], keyBytes[72:104]) - keyBytes[7] = 4 - if tmpKey, err := sm2.NewPublicKey(keyBytes[7:72]); err == nil { + switch out.PublicKeyAlgorithm { + case RSA: + if tmpKey, err := x509.ParsePKCS1PublicKey(keyBytes); err == nil { out.TmpPublicKey = tmpKey } + case ECDSA: + if len(keyBytes) == 136 && bytes.Equal(tmpPublicKeyPrefix, keyBytes[:8]) { + // parse the public key + copy(keyBytes[40:72], keyBytes[72:104]) + keyBytes[7] = 4 + if tmpKey, err := sm2.NewPublicKey(keyBytes[7:72]); err == nil { + out.TmpPublicKey = tmpKey + } + } } } } diff --git a/smx509/cfca_csr_test.go b/smx509/cfca_csr_test.go index fdfc81e..e1c2e75 100644 --- a/smx509/cfca_csr_test.go +++ b/smx509/cfca_csr_test.go @@ -8,6 +8,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -22,14 +23,24 @@ func TestCreateCFCACertificateRequest(t *testing.T) { if err != nil { t.Fatal(err) } + certRSAKey, err := rsa.GenerateKey(random, 2048) + if err != nil { + t.Fatal(err) + } tmpKey, err := sm2.GenerateKey(random) if err != nil { t.Fatal(err) } - invalidTmpKey, err := ecdsa.GenerateKey(elliptic.P256(), random) + p256Key, err := ecdsa.GenerateKey(elliptic.P256(), random) if err != nil { t.Fatal(err) } + + rsaKey, err := rsa.GenerateKey(random, 2048) + if err != nil { + t.Fatal(err) + } + template := &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: "certRequisition", @@ -37,38 +48,110 @@ func TestCreateCFCACertificateRequest(t *testing.T) { Country: []string{"CN"}, }, } - _, err = CreateCFCACertificateRequest(random, template, "", "", "") - if err == nil || err.Error() != "x509: certificate private key does not implement crypto.Signer" { - t.Fatalf("expect certificate private key does not implement crypto.Signer, got %v", err) + + testCases := []struct { + template *x509.CertificateRequest + priv interface{} + tmpPub interface{} + challengePassword string + wantErr bool + errormsg string + }{ + { + template: template, + priv: certKey, + tmpPub: tmpKey.Public(), + challengePassword: "111111", + wantErr: false, + errormsg: "", + }, + { + template: template, + priv: certRSAKey, + tmpPub: rsaKey.Public(), + challengePassword: "111111", + wantErr: false, + errormsg: "", + }, + { + template: template, + priv: p256Key, + tmpPub: nil, + challengePassword: "", + wantErr: false, + errormsg: "", + }, + { + template: template, + priv: "", + tmpPub: "", + challengePassword: "", + wantErr: true, + errormsg: "x509: certificate private key does not implement crypto.Signer", + }, + { + template: template, + priv: certKey, + tmpPub: "", + challengePassword: "", + wantErr: true, + errormsg: "x509: SM2 temp public key is required", + }, + { + template: template, + priv: certKey, + tmpPub: rsaKey.Public(), + challengePassword: "", + wantErr: true, + errormsg: "x509: SM2 temp public key is required", + }, + { + template: template, + priv: certRSAKey, + tmpPub: tmpKey.Public(), + challengePassword: "", + wantErr: true, + errormsg: "x509: RSA temp public key is required", + }, + { + template: template, + priv: certKey, + tmpPub: p256Key.Public(), + challengePassword: "", + wantErr: true, + errormsg: "x509: SM2 temp public key is required", + }, + { + template: template, + priv: p256Key, + tmpPub: certKey.Public(), + challengePassword: "111111", + wantErr: true, + errormsg: "x509: only RSA or SM2 key is supported", + }, + { + template: template, + priv: certKey, + tmpPub: tmpKey.Public(), + challengePassword: "", + wantErr: true, + errormsg: "x509: challenge password is required", + }, } - _, err = CreateCFCACertificateRequest(random, template, certKey, "", "") - if err == nil || err.Error() != "x509: only SM2 public key is supported" { - t.Fatalf("expected only SM2 public key is supported, got %v", err) - } - _, err = CreateCFCACertificateRequest(random, template, certKey, invalidTmpKey.Public(), "") - if err == nil || err.Error() != "x509: only SM2 public key is supported" { - t.Fatalf("expect only SM2 public key is supported, got %v", err) - } - _, err = CreateCFCACertificateRequest(random, template, certKey, tmpKey.Public(), "") - if err == nil || err.Error() != "x509: challenge password is required" { - t.Fatalf("expect challenge password is required, got %v", err) - } - csrDer, err := CreateCFCACertificateRequest(random, template, certKey, tmpKey.Public(), "111111") - if err != nil { - t.Fatal(err) - } - csr, err := ParseCFCACertificateRequest(csrDer) - if err != nil { - t.Fatal(err) - } - if csr.Subject.CommonName != "certRequisition" { - t.Fatal("common name not match") - } - if csr.ChallengePassword != "111111" { - t.Fatal("challenge password not match") - } - if !tmpKey.PublicKey.Equal(csr.TmpPublicKey) { - t.Fatal("tmp public key not match") + for _, tc := range testCases { + _, err := CreateCFCACertificateRequest(random, tc.template, tc.priv, tc.tmpPub, tc.challengePassword) + if tc.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if err.Error() != tc.errormsg { + t.Fatalf("expected error %s, got %s", tc.errormsg, err.Error()) + } + } else { + if err != nil { + t.Fatal(err) + } + } } } @@ -102,6 +185,21 @@ func TestSADKGeneratedCSR(t *testing.T) { if pub, ok := csr.TmpPublicKey.(*ecdsa.PublicKey); !ok || pub.X == nil { t.Fatal("tmp public key is nil") } + + block, _ = pem.Decode([]byte(rsaSignedCSR)) + csr, err = ParseCFCACertificateRequest(block.Bytes) + if err != nil { + t.Fatal(err) + } + if csr.Subject.CommonName != "certRequisition" { + t.Fatal("common name not match") + } + if csr.ChallengePassword != "111111" { + t.Fatal("challenge password not match") + } + if pub, ok := csr.TmpPublicKey.(*rsa.PublicKey); !ok || pub.N == nil { + t.Fatal("tmp public key is nil") + } } // https://myssl.com/csr_create.html @@ -137,3 +235,29 @@ func TestTrustAsiaGeneratedCSR(t *testing.T) { t.Fatal("tmp public key is nil") } } + +var rsaSignedCSR = ` +-----BEGIN CERTIFICATE REQUEST----- +MIIDxjCCAq4CAQAwPjEYMBYGA1UEAwwPY2VydFJlcXVpc2l0aW9uMRUwEwYDVQQK +DAxDRkNBIFRFU1QgQ0ExCzAJBgNVBAYTAkNOMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAzKukdxeh3wJXNtwbYEnfFnYXHSCTOP4/CO1C5vU+OMNx9yML +WEBrPZ4UufaM6l0xKRiWKLvwgjrkSb8tz3V/6ff8cDrKx4OzZZWs2xuUH4vL3Wrt +Pi6D2Mb0PBRwImSg4rxoQHoR+CQb+wfXsGwE5GaFhfStoNX1pWapGDM0+cpbIozN +4zG6b7sRuvCOIl/32p69Dl7eZ8AgCHV9pLLqB7wAHTKHatIs46XKaCDeVXTO6oDt +hi8yUShjLh94h8ILm8a/zSXT1q8lBXOPm9sRkwUMr5zPjnt+UpmfMsTQAP8DW3GF +PLaCiEUpK/muGy4ndMzsokrnRn+3cQFSewIyLQIDAQABoIIBQTATBgkqhkiG9w0B +CQcTBjExMTExMTCCASgGCSqGSIb3DQEJPwSCARkwggEVAgEBBIIBDjCCAQoCggEB +AJjSdzS6Y2tSaisHfgnLMDhugWZly7ros/Le8jKKI+tJKzjR4iHMD/B+kvdn8rCL +eaRu8Zqhr2vYqhNNs5NaGfoiSBx+yONWJrtTLFCo4uSD/BASAMtXYSHPh5nVb3vk +ssWqKVcMHfHy6IqSIahxi1tqyhWikaB86VFQyOpt6mCdg0W9cJvGNhjX5bbsMKHg +qbnrHpUQ1fgHqsBqeBxvPhUxcX89PeeBYH+x1Uz5m8pd1wnzSGJeIE+YyPRnQpRX +cBy2my6WLgGjO157raQZX8Kz0HfuIJekQUWTuB33J7RMvgY7neHKvlJher9zFEoA +Rfjt+krxvdLReRhTx7DghPsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAHzJVPBHc +WoThQICR9TbX4+UBS1ToVsl9GedMRSiE373PqoWygax3bPoy1M3ySDBwj5zfyThb +KiWf972CDkKVDeUZq++oTlEr4+BVmOzWQXlbTfuUdpLx14ygFyg7wpAViBF4aR+y +LFKfGhdBvkaU/yFYn3bGjgpc0m+Wecl5XWSTOK1zj3jf0ZVr9e8lsTcvLI7Clq9T +7Wh6UhRoPGgZ5+giRqATkSA61UlhKwk2qdbg7RTUSy/OVQuT2v4TKoE5ArBHo15z +7FVX3QQDEP65oJ7WS7c+L9Pkcj+n271uwlsZUzzHAJSEZkdWZIunDRqB/KzCLoBD +zwV8qP5llIORug== +-----END CERTIFICATE REQUEST----- +` \ No newline at end of file