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
25 changes: 12 additions & 13 deletions cmd/cmd/secret/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"encoding/base64"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"

Expand All @@ -39,7 +38,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"

var secretInitCmd = &cobra.Command{
Use: secretInitCmdLiteral,
Expand All @@ -62,10 +61,12 @@ 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)
keyStoreType, err := utils.GetKeyStoreType(path)
if err != nil {
utils.HandleErrorAndExit(err.Error(), nil)
}
keyStoreConfig.KeyStorePath = strings.TrimSpace(path)
keyStoreConfig.KeyStoreType = keyStoreType

fmt.Printf("Enter Key Store password: ")
byteStorePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
Expand All @@ -77,11 +78,13 @@ func startConsoleForKeyStore() {
alias, _ := reader.ReadString('\n')
keyStoreConfig.KeyAlias = strings.TrimSpace(alias)

fmt.Printf("Enter Key password: ")
bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
keyPassword := string(bytePassword)
fmt.Println()
keyStoreConfig.KeyPassword = base64.StdEncoding.EncodeToString([]byte(strings.TrimSpace(keyPassword)))
if !utils.IsPKCS12KeyStore(keyStoreType) {
fmt.Printf("Enter Key password: ")
bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
keyPassword := string(bytePassword)
fmt.Println()
keyStoreConfig.KeyPassword = base64.StdEncoding.EncodeToString([]byte(strings.TrimSpace(keyPassword)))
}

if utils.IsValidKeyStoreConfig(keyStoreConfig) {
utils.CreateDirIfNotExist(utils.GetKeyStoreDirectoryPath())
Expand All @@ -96,7 +99,3 @@ func startConsoleForKeyStore() {
func updateMap(params map[string]string, key, value string) {
params[key] = strings.TrimSpace(value)
}

func isJKSKeyStore(path string) bool {
return filepath.Ext(strings.TrimSpace(path)) == ".jks"
}
2 changes: 1 addition & 1 deletion cmd/docs/mi_secret_init.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mi secret init [flags]
```
To initialize a Key Store information
mi mi secret init
NOTE: Secret encryption supports only JKS Key Stores
NOTE: Secret encryption supports JKS and PKCS12 Key Stores
```

### Options
Expand Down
1 change: 1 addition & 0 deletions cmd/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
golang.org/x/crypto v0.45.0
gopkg.in/resty.v1 v1.12.0
gopkg.in/yaml.v2 v2.2.2
software.sslmate.com/src/go-pkcs12 v0.7.2
)

require (
Expand Down
2 changes: 2 additions & 0 deletions cmd/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,5 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
software.sslmate.com/src/go-pkcs12 v0.7.2 h1:Rh9FoMaI5k7Oo6EOS+2/BnoZ+JFIS+XHjM0VGkSPXLM=
software.sslmate.com/src/go-pkcs12 v0.7.2/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
65 changes: 59 additions & 6 deletions cmd/utils/encryptionUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,15 @@ import (
"github.com/magiconair/properties"
"github.com/pavlo-v-chernykh/keystore-go/v4"
"gopkg.in/yaml.v2"
pkcs12 "software.sslmate.com/src/go-pkcs12"
)

const keystoreDirName = "keystore"
const keyStoreConfigFileName = "keystore_info.yaml"

// JKSKeyStoreType and PKCS12KeyStoreType are the supported keystore types
const JKSKeyStoreType = "jks"
const PKCS12KeyStoreType = "pkcs12"
const encryptedSecretsPropertiesFileName = "wso2-secrets.properties"
const encryptedSecretsYamlFileName = "wso2-secrets.yaml"

Expand Down Expand Up @@ -65,22 +70,41 @@ type SecretConfig struct {

type KeyStoreConfig struct {
KeyStorePath string `yaml:"keyStorePath"`
KeyStoreType string `yaml:"keyStoreType,omitempty"`
KeyStorePassword string `yaml:"keyStorePassword"`
KeyAlias string `yaml:"keyAlias"`
KeyPassword string `yaml:"keyPassword"`
KeyPassword string `yaml:"keyPassword,omitempty"`
}

type encryptFunc func(key *rsa.PublicKey, plainText string) (string, error)

// IsValidKeyStoreConfig return true if the KeyStoreConfig is valid
func IsValidKeyStoreConfig(config *KeyStoreConfig) bool {
if IsNonEmptyString(config.KeyStorePath) && IsNonEmptyString(config.KeyStorePassword) &&
IsNonEmptyString(config.KeyAlias) && IsNonEmptyString(config.KeyPassword) {
return true
IsNonEmptyString(config.KeyAlias) {
// PKCS12 keystores use the keystore password for the key as well
return IsPKCS12KeyStore(config.KeyStoreType) || IsNonEmptyString(config.KeyPassword)
}
return false
}

// IsPKCS12KeyStore return true if the keystore type is pkcs12
func IsPKCS12KeyStore(keyStoreType string) bool {
return strings.EqualFold(keyStoreType, PKCS12KeyStoreType)
}

// GetKeyStoreType resolves the keystore type from the file extension of the keystore path
func GetKeyStoreType(keyStorePath string) (string, error) {
switch strings.ToLower(filepath.Ext(strings.TrimSpace(keyStorePath))) {
case ".jks":
return JKSKeyStoreType, nil
case ".p12", ".pfx", ".pkcs12":
return PKCS12KeyStoreType, nil
default:
return "", errors.New("Invalid Key Store Type. Supports only JKS and PKCS12 Key Stores")
}
}

// EncryptSecrets encrypts the secrets using the keystore and write them to a file or console depending on the config map argument
func EncryptSecrets(keyStoreConfig *KeyStoreConfig, secretConfig SecretConfig) error {
encryptionKey, err := getEncryptionKey(keyStoreConfig)
Expand Down Expand Up @@ -147,16 +171,26 @@ func GetKeyStoreConfigFromFile(filePath string) (*KeyStoreConfig, error) {
if err := yaml.Unmarshal(data, config); err != nil {
return nil, errors.New("Parsing error.\nExecute 'mi secret init --help' for more information")
}
if !IsNonEmptyString(config.KeyStoreType) {
// keystore configs created before PKCS12 support have no type field
config.KeyStoreType = JKSKeyStoreType
}
if !IsValidKeyStoreConfig(config) {
return nil, errors.New("Missing required fields.\nExecute 'mi secret init --help' for more information")
}
return config, nil
}

func getEncryptionKey(keyStoreConfig *KeyStoreConfig) (*rsa.PublicKey, error) {
keyStorePath := keyStoreConfig.KeyStorePath
keyStorePassword, _ := base64.StdEncoding.DecodeString(keyStoreConfig.KeyStorePassword)
keyStore, err := readKeyStore(keyStorePath, keyStorePassword)
if IsPKCS12KeyStore(keyStoreConfig.KeyStoreType) {
return getEncryptionKeyFromPKCS12(keyStoreConfig.KeyStorePath, string(keyStorePassword))
}
return getEncryptionKeyFromJKS(keyStoreConfig, keyStorePassword)
}

func getEncryptionKeyFromJKS(keyStoreConfig *KeyStoreConfig, keyStorePassword []byte) (*rsa.PublicKey, error) {
keyStore, err := readKeyStore(keyStoreConfig.KeyStorePath, keyStorePassword)
if err != nil {
return nil, errors.New("Reading Key Store: " + err.Error())
}
Expand All @@ -167,13 +201,32 @@ func getEncryptionKey(keyStoreConfig *KeyStoreConfig) (*rsa.PublicKey, error) {
return nil, errors.New("Reading Key Entry: " + err.Error())
}
key, err := x509.ParsePKCS8PrivateKey(pke.PrivateKey)
rsaKey := key.(*rsa.PrivateKey)
if err != nil {
return nil, errors.New("Parsing Key Entry: " + err.Error())
}
rsaKey, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("Parsing Key Entry: key entry is not an RSA key")
}
return &rsaKey.PublicKey, nil
}

func getEncryptionKeyFromPKCS12(keyStorePath string, keyStorePassword string) (*rsa.PublicKey, error) {
data, err := ioutil.ReadFile(keyStorePath)
if err != nil {
return nil, errors.New("Reading Key Store: " + err.Error())
}
_, cert, _, err := pkcs12.DecodeChain(data, keyStorePassword)
if err != nil {
return nil, errors.New("Reading Key Store: " + err.Error())
}
rsaKey, ok := cert.PublicKey.(*rsa.PublicKey)
if !ok {
return nil, errors.New("Reading Key Entry: certificate does not hold an RSA public key")
}
return rsaKey, nil
}

func encrypt(encryptionKey *rsa.PublicKey, plainTextSecrets map[string]string, encryptFunction encryptFunc) (map[string]string, error) {
var encryptedSecrets = make(map[string]string)
for alias, plainText := range plainTextSecrets {
Expand Down
75 changes: 75 additions & 0 deletions cmd/utils/encryptionUtils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package utils

import "testing"

func TestGetKeyStoreType(t *testing.T) {
validCases := map[string]string{
"wso2carbon.jks": JKSKeyStoreType,
"wso2carbon.JKS": JKSKeyStoreType,
"wso2carbon.p12": PKCS12KeyStoreType,
"wso2carbon.pfx": PKCS12KeyStoreType,
"wso2carbon.pkcs12": PKCS12KeyStoreType,
" wso2carbon.p12\n": PKCS12KeyStoreType,
"/a/b/c/wso2carbon.jks": JKSKeyStoreType,
}
for path, expected := range validCases {
actual, err := GetKeyStoreType(path)
if err != nil {
t.Errorf("GetKeyStoreType(%q) returned error: %v", path, err)
}
if actual != expected {
t.Errorf("GetKeyStoreType(%q) = %q, expected %q", path, actual, expected)
}
}

invalidCases := []string{"wso2carbon.txt", "wso2carbon", ""}
for _, path := range invalidCases {
if _, err := GetKeyStoreType(path); err == nil {
t.Errorf("GetKeyStoreType(%q) expected error, got nil", path)
}
}
}

func TestIsValidKeyStoreConfig(t *testing.T) {
jksConfig := &KeyStoreConfig{
KeyStorePath: "wso2carbon.jks",
KeyStoreType: JKSKeyStoreType,
KeyStorePassword: "cGFzcw==",
KeyAlias: "wso2carbon",
}
if IsValidKeyStoreConfig(jksConfig) {
t.Error("JKS config without key password should be invalid")
}
jksConfig.KeyPassword = "cGFzcw=="
if !IsValidKeyStoreConfig(jksConfig) {
t.Error("JKS config with all fields should be valid")
}

pkcs12Config := &KeyStoreConfig{
KeyStorePath: "wso2carbon.p12",
KeyStoreType: PKCS12KeyStoreType,
KeyStorePassword: "cGFzcw==",
KeyAlias: "wso2carbon",
}
if !IsValidKeyStoreConfig(pkcs12Config) {
t.Error("PKCS12 config without key password should be valid")
}
}
Loading