diff --git a/builder/oracle/bmcs/client/base_client.go b/builder/oracle/bmcs/client/base_client.go index 5f287c6f8..7b478aeb1 100644 --- a/builder/oracle/bmcs/client/base_client.go +++ b/builder/oracle/bmcs/client/base_client.go @@ -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{} diff --git a/builder/oracle/bmcs/client/config.go b/builder/oracle/bmcs/client/config.go index 660cff68c..d13712f3c 100644 --- a/builder/oracle/bmcs/client/config.go +++ b/builder/oracle/bmcs/client/config.go @@ -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 diff --git a/builder/oracle/bmcs/client/config_test.go b/builder/oracle/bmcs/client/config_test.go index 82dbf58b4..61f5e64c4 100644 --- a/builder/oracle/bmcs/client/config_test.go +++ b/builder/oracle/bmcs/client/config_test.go @@ -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) + } +} diff --git a/builder/oracle/bmcs/client/transport_test.go b/builder/oracle/bmcs/client/transport_test.go index d8a1518b0..a683cdebc 100644 --- a/builder/oracle/bmcs/client/transport_test.go +++ b/builder/oracle/bmcs/client/transport_test.go @@ -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()) } diff --git a/builder/oracle/bmcs/config.go b/builder/oracle/bmcs/config.go index de0c9a2fe..8f975d5c5 100644 --- a/builder/oracle/bmcs/config.go +++ b/builder/oracle/bmcs/config.go @@ -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) }