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:
Andrew Pryde 2017-02-14 09:50:28 +00:00
parent 3473162234
commit 25e4843a6f
5 changed files with 203 additions and 11 deletions

View File

@ -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{}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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())
}

View File

@ -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)
}