Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions import-export-cli/cmd/secret/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)

Expand Down Expand Up @@ -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)
}
5 changes: 4 additions & 1 deletion import-export-cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
5 changes: 3 additions & 2 deletions import-export-cli/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
122 changes: 116 additions & 6 deletions import-export-cli/utils/encryptionUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,22 @@
package utils

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

"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"
Expand Down Expand Up @@ -139,7 +140,7 @@ 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")
}
Expand All @@ -156,21 +157,130 @@ 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("Error while 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) {

// 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 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
}

// 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("Parsing Key Entry: " + err.Error())
return nil, errors.New("Reading PKCS12 file: " + err.Error())
}

// Decode PKCS12 data using the improved go-pkcs12 library
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, _, err = pkcs12.DecodeChain(p12Data, string(keyPassword))
if err != nil {
return nil, errors.New("Decoding PKCS12 keystore: " + err.Error())
}
}

// 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
}

Expand Down Expand Up @@ -215,7 +325,7 @@ func printSecretsToYamlFile(secrets map[string]string) {
StringData: secrets,
Type: "Opaque",
MetaData: metaData{
Name: "wso2secret",
Name: "wso2secret",
},
}
secretFilePath := getSecretFilePath(encryptedSecretsYamlFileName)
Expand Down
Loading