From d6bf79d46b3ec4f0831c9dd6f6824f3d776030b5 Mon Sep 17 00:00:00 2001 From: Kavinda Rajapakse Date: Sun, 10 Aug 2025 19:03:23 +0530 Subject: [PATCH 1/6] Add software.sslmate.com/src/go-pkcs12 v0.6.0 dependency --- import-export-cli/go.mod | 5 ++++- import-export-cli/go.sum | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/import-export-cli/go.mod b/import-export-cli/go.mod index bf9e75fa..d4cb132c 100644 --- a/import-export-cli/go.mod +++ b/import-export-cli/go.mod @@ -23,6 +23,7 @@ require ( github.com/wso2/k8s-api-operator/api-operator v0.0.0-20210223103109-66ee766c8413 golang.org/x/crypto v0.31.0 gopkg.in/yaml.v2 v2.4.0 + software.sslmate.com/src/go-pkcs12 v0.6.0 ) require ( @@ -72,6 +73,8 @@ require ( sigs.k8s.io/structured-merge-diff/v3 v3.0.0 // indirect ) +module github.com/wso2/product-apim-tooling/import-export-cli + replace k8s.io/client-go => k8s.io/client-go v0.18.2 -module github.com/wso2/product-apim-tooling/import-export-cli +exclude github.com/ugorji/go v1.1.4 diff --git a/import-export-cli/go.sum b/import-export-cli/go.sum index 34a55a8e..acaafc87 100644 --- a/import-export-cli/go.sum +++ b/import-export-cli/go.sum @@ -911,10 +911,9 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber/jaeger-client-go v2.20.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= @@ -1420,4 +1419,6 @@ sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnM sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= +software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= From e7ea1ad61c9fdecc363e4ca4a2d0996a27cffa60 Mon Sep 17 00:00:00 2001 From: Kavinda Rajapakse Date: Sun, 10 Aug 2025 19:03:31 +0530 Subject: [PATCH 2/6] Update Key Store initialization to support JKS and PKCS12 formats --- import-export-cli/cmd/secret/init.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/import-export-cli/cmd/secret/init.go b/import-export-cli/cmd/secret/init.go index e47870d6..9e698861 100644 --- a/import-export-cli/cmd/secret/init.go +++ b/import-export-cli/cmd/secret/init.go @@ -39,7 +39,7 @@ const secretInitCmdLongDesc = "Initialize the Key Store information required for var secretInitCmdExamples = "To initialize a Key Store information\n" + " " + utils.ProjectName + " " + utils.MiCmdLiteral + " " + secretCmdLiteral + " " + secretInitCmdLiteral + "\n" + - "NOTE: Secret encryption supports only JKS Key Stores" + "NOTE: Secret encryption supports JKS and PKCS12 Key Stores (.jks, .p12, .pfx)" var secretInitCmd = &cobra.Command{ Use: secretInitCmdLiteral, @@ -62,8 +62,8 @@ func startConsoleForKeyStore() { fmt.Printf("Enter Key Store location: ") path, _ := reader.ReadString('\n') - if !isJKSKeyStore(path) { - utils.HandleErrorAndExit("Invalid Key Store Type. Supports only JKS Key Stores", nil) + if !isValidKeyStore(path) { + utils.HandleErrorAndExit("Invalid Key Store Type. Supports only JKS and PKCS12 Key Stores (.jks, .p12, .pfx)", nil) } keyStoreConfig.KeyStorePath = strings.TrimSpace(path) @@ -100,3 +100,12 @@ func updateMap(params map[string]string, key, value string) { func isJKSKeyStore(path string) bool { return filepath.Ext(strings.TrimSpace(path)) == ".jks" } + +func isPKCS12KeyStore(path string) bool { + ext := strings.ToLower(filepath.Ext(strings.TrimSpace(path))) + return ext == ".p12" || ext == ".pfx" +} + +func isValidKeyStore(path string) bool { + return isJKSKeyStore(path) || isPKCS12KeyStore(path) +} From 3f4b68b9369c920864661f36267d72a48113962b Mon Sep 17 00:00:00 2001 From: Kavinda Rajapakse Date: Sun, 10 Aug 2025 19:03:36 +0530 Subject: [PATCH 3/6] Refactor keystore handling to support PKCS12 and JKS formats; update error messages and improve file reading logic --- import-export-cli/utils/encryptionUtils.go | 115 +++++++++++++++++++-- 1 file changed, 106 insertions(+), 9 deletions(-) diff --git a/import-export-cli/utils/encryptionUtils.go b/import-export-cli/utils/encryptionUtils.go index 04a4e431..77fbdcb4 100644 --- a/import-export-cli/utils/encryptionUtils.go +++ b/import-export-cli/utils/encryptionUtils.go @@ -26,7 +26,6 @@ import ( "encoding/base64" "errors" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -34,6 +33,7 @@ import ( "github.com/magiconair/properties" "github.com/pavel-v-chernykh/keystore-go/v4" "gopkg.in/yaml.v2" + "software.sslmate.com/src/go-pkcs12" ) const keystoreDirName = "keystore" @@ -139,16 +139,16 @@ func GetKeyStoreConfigFilePath() string { // GetKeyStoreConfigFromFile read and return KeyStoreConfig func GetKeyStoreConfigFromFile(filePath string) (*KeyStoreConfig, error) { - data, err := ioutil.ReadFile(filePath) + data, err := os.ReadFile(filePath) if err != nil { - return nil, errors.New("Config file not found.\nExecute 'apictl secret init --help' for more information") + return nil, errors.New("config file not found.\nExecute 'apictl secret init --help' for more information") } config := &KeyStoreConfig{} if err := yaml.Unmarshal(data, config); err != nil { - return nil, errors.New("Parsing error.\nExecute 'apictl secret init --help' for more information") + return nil, errors.New("parsing error.\nExecute 'apictl secret init --help' for more information") } if !IsValidKeyStoreConfig(config) { - return nil, errors.New("Missing required fields.\nExecute 'apictl secret init --help' for more information") + return nil, errors.New("missing required fields.\nExecute 'apictl secret init --help' for more information") } return config, nil } @@ -156,21 +156,118 @@ func GetKeyStoreConfigFromFile(filePath string) (*KeyStoreConfig, error) { func getEncryptionKey(keyStoreConfig *KeyStoreConfig) (*rsa.PublicKey, error) { keyStorePath := keyStoreConfig.KeyStorePath keyStorePassword, _ := base64.StdEncoding.DecodeString(keyStoreConfig.KeyStorePassword) + + // Detect keystore type and handle accordingly + keystoreType, err := detectKeystoreType(keyStorePath) + if err != nil { + return nil, errors.New("Detecting keystore type: " + err.Error()) + } + + if keystoreType == "PKCS12" { + return getPublicKeyFromPKCS12(keyStorePath, keyStorePassword, keyStoreConfig) + } else { + return getPublicKeyFromJKS(keyStorePath, keyStorePassword, keyStoreConfig) + } +} + +// detectKeystoreType detects whether the keystore is JKS or PKCS12 based on file extension and magic bytes +func detectKeystoreType(keyStorePath string) (string, error) { + // Check file extension first + ext := strings.ToLower(filepath.Ext(keyStorePath)) + if ext == ".p12" || ext == ".pfx" { + return "PKCS12", nil + } + if ext == ".jks" { + return "JKS", nil + } + + // If extension is ambiguous, try to read magic bytes + file, err := os.Open(keyStorePath) + if err != nil { + return "", err + } + defer file.Close() + + // Read first few bytes to detect format + header := make([]byte, 4) + _, err = file.Read(header) + if err != nil { + return "", err + } + + // PKCS12 files typically start with 0x30 (ASN.1 SEQUENCE) + if header[0] == 0x30 && header[1] == 0x82 { + return "PKCS12", nil + } + + // Default to JKS for other cases + return "JKS", nil +} + +// getPublicKeyFromJKS extracts RSA public key from JKS keystore (existing logic) +func getPublicKeyFromJKS(keyStorePath string, keyStorePassword []byte, keyStoreConfig *KeyStoreConfig) (*rsa.PublicKey, error) { keyStore, err := readKeyStore(keyStorePath, keyStorePassword) if err != nil { - return nil, errors.New("Reading Key Store: " + err.Error()) + return nil, errors.New("Reading JKS Key Store: " + err.Error()) } keyAlias := keyStoreConfig.KeyAlias keyPassword, _ := base64.StdEncoding.DecodeString(keyStoreConfig.KeyPassword) pke, err := keyStore.GetPrivateKeyEntry(keyAlias, keyPassword) if err != nil { - return nil, errors.New("Reading Key Entry: " + err.Error()) + return nil, errors.New("Reading Key Entry from JKS: " + err.Error()) } key, err := x509.ParsePKCS8PrivateKey(pke.PrivateKey) + if err != nil { + return nil, errors.New("Parsing PKCS8 Key Entry from JKS: " + err.Error()) + } rsaKey := key.(*rsa.PrivateKey) + return &rsaKey.PublicKey, nil +} + +// getPublicKeyFromPKCS12 extracts RSA public key from PKCS12 keystore +func getPublicKeyFromPKCS12(keyStorePath string, keyStorePassword []byte, keyStoreConfig *KeyStoreConfig) (*rsa.PublicKey, error) { + // Read PKCS12 file + p12Data, err := os.ReadFile(keyStorePath) + if err != nil { + return nil, errors.New("reading PKCS12 file: " + err.Error()) + } + + // Decode PKCS12 data using the improved go-pkcs12 library + privateKey, certificate, caCerts, err := pkcs12.DecodeChain(p12Data, string(keyStorePassword)) if err != nil { - return nil, errors.New("Parsing Key Entry: " + err.Error()) + // Try with alias-specific password if main password fails + keyPassword, _ := base64.StdEncoding.DecodeString(keyStoreConfig.KeyPassword) + privateKey, certificate, caCerts, err = pkcs12.DecodeChain(p12Data, string(keyPassword)) + if err != nil { + return nil, errors.New("decoding PKCS12 keystore: " + err.Error()) + } + } + + // Handle case where we need to find a specific alias + if keyStoreConfig.KeyAlias != "" { + // For PKCS12 with aliases, we might need additional logic + // For now, we'll use the decoded certificate and key + _ = caCerts // Keep for potential future use + } + + // Extract RSA private key + var rsaKey *rsa.PrivateKey + switch key := privateKey.(type) { + case *rsa.PrivateKey: + rsaKey = key + default: + return nil, errors.New("PKCS12 keystore does not contain an RSA private key") } + + // Verify certificate matches the private key (optional validation) + if certificate != nil { + if pubKey, ok := certificate.PublicKey.(*rsa.PublicKey); ok { + if pubKey.N.Cmp(rsaKey.N) != 0 || pubKey.E != rsaKey.E { + return nil, errors.New("certificate public key does not match private key in PKCS12") + } + } + } + return &rsaKey.PublicKey, nil } @@ -215,7 +312,7 @@ func printSecretsToYamlFile(secrets map[string]string) { StringData: secrets, Type: "Opaque", MetaData: metaData{ - Name: "wso2secret", + Name: "wso2secret", }, } secretFilePath := getSecretFilePath(encryptedSecretsYamlFileName) From 39771cc68af862bc3ffd93a678cd9accba7368ea Mon Sep 17 00:00:00 2001 From: Kavinda Rajapakse Date: Wed, 13 Aug 2025 09:20:24 +0530 Subject: [PATCH 4/6] Enhance keystore type detection logic to improve PKCS12 validation and maintain JKS compatibility --- import-export-cli/utils/encryptionUtils.go | 40 ++++++++++++++++------ 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/import-export-cli/utils/encryptionUtils.go b/import-export-cli/utils/encryptionUtils.go index 77fbdcb4..22f1ee06 100644 --- a/import-export-cli/utils/encryptionUtils.go +++ b/import-export-cli/utils/encryptionUtils.go @@ -19,6 +19,7 @@ package utils import ( + "bytes" "crypto/rand" "crypto/rsa" "crypto/sha1" @@ -172,14 +173,6 @@ func getEncryptionKey(keyStoreConfig *KeyStoreConfig) (*rsa.PublicKey, error) { // detectKeystoreType detects whether the keystore is JKS or PKCS12 based on file extension and magic bytes func detectKeystoreType(keyStorePath string) (string, error) { - // Check file extension first - ext := strings.ToLower(filepath.Ext(keyStorePath)) - if ext == ".p12" || ext == ".pfx" { - return "PKCS12", nil - } - if ext == ".jks" { - return "JKS", nil - } // If extension is ambiguous, try to read magic bytes file, err := os.Open(keyStorePath) @@ -195,10 +188,37 @@ func detectKeystoreType(keyStorePath string) (string, error) { return "", err } - // PKCS12 files typically start with 0x30 (ASN.1 SEQUENCE) - if header[0] == 0x30 && header[1] == 0x82 { + // PKCS12 files start with ASN.1 SEQUENCE tag (0x30) + // Additional validation for proper ASN.1 length encoding + if header[0] == 0x30 { + // Check length encoding + if header[1]&0x80 == 0 { + // Short form length + if header[1] > 0 { + return "PKCS12", nil + } + } else { + // Long form length - validate length-of-length + lengthBytes := int(header[1] & 0x7F) + if lengthBytes > 0 && lengthBytes <= 4 { + return "PKCS12", nil + } + } + } + + jksMagic := []byte{0xFE, 0xED, 0xFE, 0xED} + if bytes.Equal(header[:4], jksMagic) { + return "JKS", nil + } + + // Check file extension + ext := strings.ToLower(filepath.Ext(keyStorePath)) + if ext == ".p12" || ext == ".pfx" { return "PKCS12", nil } + if ext == ".jks" { + return "JKS", nil + } // Default to JKS for other cases return "JKS", nil From 6be0204d5f5addf0975eaff1b92de96927868bb5 Mon Sep 17 00:00:00 2001 From: Kavinda Rajapakse Date: Fri, 15 Aug 2025 13:06:17 +0530 Subject: [PATCH 5/6] Fix typos --- import-export-cli/utils/encryptionUtils.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/import-export-cli/utils/encryptionUtils.go b/import-export-cli/utils/encryptionUtils.go index 22f1ee06..d790e88e 100644 --- a/import-export-cli/utils/encryptionUtils.go +++ b/import-export-cli/utils/encryptionUtils.go @@ -142,14 +142,14 @@ func GetKeyStoreConfigFilePath() string { func GetKeyStoreConfigFromFile(filePath string) (*KeyStoreConfig, error) { data, err := os.ReadFile(filePath) if err != nil { - return nil, errors.New("config file not found.\nExecute 'apictl secret init --help' for more information") + return nil, errors.New("Config file not found.\nExecute 'apictl secret init --help' for more information") } config := &KeyStoreConfig{} if err := yaml.Unmarshal(data, config); err != nil { - return nil, errors.New("parsing error.\nExecute 'apictl secret init --help' for more information") + return nil, errors.New("Parsing error.\nExecute 'apictl secret init --help' for more information") } if !IsValidKeyStoreConfig(config) { - return nil, errors.New("missing required fields.\nExecute 'apictl secret init --help' for more information") + return nil, errors.New("Missing required fields.\nExecute 'apictl secret init --help' for more information") } return config, nil } @@ -161,7 +161,7 @@ func getEncryptionKey(keyStoreConfig *KeyStoreConfig) (*rsa.PublicKey, error) { // Detect keystore type and handle accordingly keystoreType, err := detectKeystoreType(keyStorePath) if err != nil { - return nil, errors.New("Detecting keystore type: " + err.Error()) + return nil, errors.New("Error while detecting keystore type: " + err.Error()) } if keystoreType == "PKCS12" { @@ -249,7 +249,7 @@ func getPublicKeyFromPKCS12(keyStorePath string, keyStorePassword []byte, keySto // Read PKCS12 file p12Data, err := os.ReadFile(keyStorePath) if err != nil { - return nil, errors.New("reading PKCS12 file: " + err.Error()) + return nil, errors.New("Reading PKCS12 file: " + err.Error()) } // Decode PKCS12 data using the improved go-pkcs12 library @@ -259,7 +259,7 @@ func getPublicKeyFromPKCS12(keyStorePath string, keyStorePassword []byte, keySto keyPassword, _ := base64.StdEncoding.DecodeString(keyStoreConfig.KeyPassword) privateKey, certificate, caCerts, err = pkcs12.DecodeChain(p12Data, string(keyPassword)) if err != nil { - return nil, errors.New("decoding PKCS12 keystore: " + err.Error()) + return nil, errors.New("Decoding PKCS12 keystore: " + err.Error()) } } @@ -283,7 +283,7 @@ func getPublicKeyFromPKCS12(keyStorePath string, keyStorePassword []byte, keySto if certificate != nil { if pubKey, ok := certificate.PublicKey.(*rsa.PublicKey); ok { if pubKey.N.Cmp(rsaKey.N) != 0 || pubKey.E != rsaKey.E { - return nil, errors.New("certificate public key does not match private key in PKCS12") + return nil, errors.New("Certificate public key does not match private key in PKCS12") } } } From 25cdfcfbd4d52f817130ceec3a5dcb4e5793543e Mon Sep 17 00:00:00 2001 From: Kavinda Rajapakse Date: Fri, 15 Aug 2025 13:10:33 +0530 Subject: [PATCH 6/6] Refactor PKCS12 decoding to remove unused CA certificates and simplify error handling --- import-export-cli/utils/encryptionUtils.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/import-export-cli/utils/encryptionUtils.go b/import-export-cli/utils/encryptionUtils.go index d790e88e..5d182e62 100644 --- a/import-export-cli/utils/encryptionUtils.go +++ b/import-export-cli/utils/encryptionUtils.go @@ -253,23 +253,16 @@ func getPublicKeyFromPKCS12(keyStorePath string, keyStorePassword []byte, keySto } // Decode PKCS12 data using the improved go-pkcs12 library - privateKey, certificate, caCerts, err := pkcs12.DecodeChain(p12Data, string(keyStorePassword)) + privateKey, certificate, _, err := pkcs12.DecodeChain(p12Data, string(keyStorePassword)) if err != nil { // Try with alias-specific password if main password fails keyPassword, _ := base64.StdEncoding.DecodeString(keyStoreConfig.KeyPassword) - privateKey, certificate, caCerts, err = pkcs12.DecodeChain(p12Data, string(keyPassword)) + privateKey, certificate, _, err = pkcs12.DecodeChain(p12Data, string(keyPassword)) if err != nil { return nil, errors.New("Decoding PKCS12 keystore: " + err.Error()) } } - // Handle case where we need to find a specific alias - if keyStoreConfig.KeyAlias != "" { - // For PKCS12 with aliases, we might need additional logic - // For now, we'll use the decoded certificate and key - _ = caCerts // Keep for potential future use - } - // Extract RSA private key var rsaKey *rsa.PrivateKey switch key := privateKey.(type) {