BMCS password protected keys via config file
Implements support for signing requests with encrypted private keys in the BMCS SDK. The pass_phrase property in the SDK config file is now supported. See https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm for more information.
This commit is contained in:
parent
3473162234
commit
25e4843a6f
|
@ -160,9 +160,11 @@ func (c *baseClient) Request() (*http.Request, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
err = addQueryStruct(reqURL, c.queryStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if c.queryStruct != nil {
|
||||
err = addQueryStruct(reqURL, c.queryStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
|
|
|
@ -37,6 +37,9 @@ type Config struct {
|
|||
// Path to BMCS config file (e.g. ~/.oraclebmc/config)
|
||||
KeyFile string `ini:"key_file"`
|
||||
|
||||
// Passphrase used for the key, if it is encrypted.
|
||||
PassPhrase string `ini:"pass_phrase"`
|
||||
|
||||
// Private key (loaded via LoadPrivateKey or ParsePrivateKey)
|
||||
Key *rsa.PrivateKey
|
||||
|
||||
|
@ -108,7 +111,7 @@ func loadConfigSection(f *ini.File, sectionName string, config *Config) (*Config
|
|||
return nil, err
|
||||
}
|
||||
|
||||
config.Key, err = LoadPrivateKey(config.KeyFile)
|
||||
config.Key, err = LoadPrivateKey(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -117,9 +120,9 @@ func loadConfigSection(f *ini.File, sectionName string, config *Config) (*Config
|
|||
}
|
||||
|
||||
// LoadPrivateKey loads private key from disk and parses it.
|
||||
func LoadPrivateKey(path string) (*rsa.PrivateKey, error) {
|
||||
func LoadPrivateKey(config *Config) (*rsa.PrivateKey, error) {
|
||||
// Expand '~' to $HOME
|
||||
path, err := homedir.Expand(path)
|
||||
path, err := homedir.Expand(config.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -129,15 +132,34 @@ func LoadPrivateKey(path string) (*rsa.PrivateKey, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := ParsePrivateKey(keyContent)
|
||||
key, err := ParsePrivateKey(keyContent, []byte(config.PassPhrase))
|
||||
|
||||
return key, err
|
||||
}
|
||||
|
||||
// ParsePrivateKey parses a PEM encoded array of bytes into an rsa.PrivateKey.
|
||||
func ParsePrivateKey(content []byte) (*rsa.PrivateKey, error) {
|
||||
// Attempts to decrypt the PEM encoded array of bytes with the given password
|
||||
// if the PEM encoded byte array is encrypted.
|
||||
func ParsePrivateKey(content, password []byte) (*rsa.PrivateKey, error) {
|
||||
keyBlock, _ := pem.Decode(content)
|
||||
der := keyBlock.Bytes
|
||||
|
||||
if keyBlock == nil {
|
||||
return nil, errors.New("could not decode PEM private key")
|
||||
}
|
||||
|
||||
var der []byte
|
||||
var err error
|
||||
if x509.IsEncryptedPEMBlock(keyBlock) {
|
||||
if len(password) < 1 {
|
||||
return nil, errors.New("encrypted private key but no pass phrase provided")
|
||||
}
|
||||
der, err = x509.DecryptPEMBlock(keyBlock, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
der = keyBlock.Bytes
|
||||
}
|
||||
|
||||
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
|
||||
return key, nil
|
||||
|
|
|
@ -7,7 +7,14 @@
|
|||
package bmcs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -119,3 +126,164 @@ func TestNewConfigDefaultsOverridden(t *testing.T) {
|
|||
adminConfig.Fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEncryptedPrivateKeyValidPassword(t *testing.T) {
|
||||
// Generate private key
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected generating RSA key: %+v", err)
|
||||
}
|
||||
publicKey := priv.PublicKey
|
||||
|
||||
// ASN.1 DER encoded form
|
||||
privDer := x509.MarshalPKCS1PrivateKey(priv)
|
||||
|
||||
blockType := "RSA PRIVATE KEY"
|
||||
password := []byte("password")
|
||||
cipherType := x509.PEMCipherAES256
|
||||
|
||||
// Encrypt priv with password
|
||||
encryptedPEMBlock, err := x509.EncryptPEMBlock(
|
||||
rand.Reader,
|
||||
blockType,
|
||||
privDer,
|
||||
password,
|
||||
cipherType)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error encryting PEM block: %+v", err)
|
||||
}
|
||||
|
||||
// Parse private key
|
||||
key, err := ParsePrivateKey(pem.EncodeToMemory(encryptedPEMBlock), password)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
// Check we get the same key back
|
||||
if !reflect.DeepEqual(publicKey, key.PublicKey) {
|
||||
t.Errorf("expected public key of encrypted and decrypted key to match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEncryptedPrivateKeyPKCS8(t *testing.T) {
|
||||
// Generate private key
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected generating RSA key: %+v", err)
|
||||
}
|
||||
publicKey := priv.PublicKey
|
||||
|
||||
// Implements x509.MarshalPKCS8PrivateKey which is not included in the
|
||||
// standard library.
|
||||
pkey := struct {
|
||||
Version int
|
||||
PrivateKeyAlgorithm []asn1.ObjectIdentifier
|
||||
PrivateKey []byte
|
||||
}{
|
||||
Version: 0,
|
||||
PrivateKeyAlgorithm: []asn1.ObjectIdentifier{{1, 2, 840, 113549, 1, 1, 1}},
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(priv),
|
||||
}
|
||||
privDer, err := asn1.Marshal(pkey)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected marshaling RSA key: %+v", err)
|
||||
}
|
||||
|
||||
blockType := "RSA PRIVATE KEY"
|
||||
password := []byte("password")
|
||||
cipherType := x509.PEMCipherAES256
|
||||
|
||||
// Encrypt priv with password
|
||||
encryptedPEMBlock, err := x509.EncryptPEMBlock(
|
||||
rand.Reader,
|
||||
blockType,
|
||||
privDer,
|
||||
password,
|
||||
cipherType)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error encryting PEM block: %+v", err)
|
||||
}
|
||||
|
||||
// Parse private key
|
||||
key, err := ParsePrivateKey(pem.EncodeToMemory(encryptedPEMBlock), password)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
// Check we get the same key back
|
||||
if !reflect.DeepEqual(publicKey, key.PublicKey) {
|
||||
t.Errorf("expected public key of encrypted and decrypted key to match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEncryptedPrivateKeyInvalidPassword(t *testing.T) {
|
||||
// Generate private key
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected generating RSA key: %+v", err)
|
||||
}
|
||||
|
||||
// ASN.1 DER encoded form
|
||||
privDer := x509.MarshalPKCS1PrivateKey(priv)
|
||||
|
||||
blockType := "RSA PRIVATE KEY"
|
||||
password := []byte("password")
|
||||
cipherType := x509.PEMCipherAES256
|
||||
|
||||
// Encrypt priv with password
|
||||
encryptedPEMBlock, err := x509.EncryptPEMBlock(
|
||||
rand.Reader,
|
||||
blockType,
|
||||
privDer,
|
||||
password,
|
||||
cipherType)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error encrypting PEM block: %+v", err)
|
||||
}
|
||||
|
||||
// Parse private key (with wrong password)
|
||||
_, err = ParsePrivateKey(pem.EncodeToMemory(encryptedPEMBlock), []byte("foo"))
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "decryption password incorrect") {
|
||||
t.Errorf("Expected error to contain 'decryption password incorrect', got %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEncryptedPrivateKeyInvalidNoPassword(t *testing.T) {
|
||||
// Generate private key
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected generating RSA key: %+v", err)
|
||||
}
|
||||
|
||||
// ASN.1 DER encoded form
|
||||
privDer := x509.MarshalPKCS1PrivateKey(priv)
|
||||
|
||||
blockType := "RSA PRIVATE KEY"
|
||||
password := []byte("password")
|
||||
cipherType := x509.PEMCipherAES256
|
||||
|
||||
// Encrypt priv with password
|
||||
encryptedPEMBlock, err := x509.EncryptPEMBlock(
|
||||
rand.Reader,
|
||||
blockType,
|
||||
privDer,
|
||||
password,
|
||||
cipherType)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error encrypting PEM block: %+v", err)
|
||||
}
|
||||
|
||||
// Parse private key (with wrong password)
|
||||
_, err = ParsePrivateKey(pem.EncodeToMemory(encryptedPEMBlock), []byte{})
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "no pass phrase provided") {
|
||||
t.Errorf("Expected error to contain 'no pass phrase provided', got %+v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ var KnownGoodCases = []struct {
|
|||
}
|
||||
|
||||
func TestKnownGoodRequests(t *testing.T) {
|
||||
pKey, err := ParsePrivateKey([]byte(testKey))
|
||||
pKey, err := ParsePrivateKey([]byte(testKey), []byte{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse test key: %s", err.Error())
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ func NewConfig(raws ...interface{}) (*Config, error) {
|
|||
|
||||
if c.KeyFile != "" {
|
||||
accessCfg.KeyFile = c.KeyFile
|
||||
accessCfg.Key, err = client.LoadPrivateKey(accessCfg.KeyFile)
|
||||
accessCfg.Key, err = client.LoadPrivateKey(accessCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to load private key %s : %s", accessCfg.KeyFile, err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue