Skip to content

Commit

Permalink
Add IBM Key Protect wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
luizgn committed Aug 25, 2020
1 parent 6714981 commit 67f9df9
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ as they may have been used for past encryption operations.
* * Azure KeyVault (uses envelopes)
* * GCP CKMS (uses envelopes)
* * Huawei Cloud KMS (uses envelopes)
* * IBM Key Protect (uses envelopes)
* * OCI KMS (uses envelopes)
* * Tencent Cloud KMS (uses envelopes)
* * Vault Transit mount
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2
github.com/Azure/go-autorest/autorest/to v0.3.0
github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect
github.com/IBM/keyprotect-go-client v0.5.0
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f
github.com/aws/aws-sdk-go v1.30.27
github.com/golang/protobuf v1.4.2
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/IBM/keyprotect-go-client v0.5.0 h1:kTvP7BC723b8fVXnaNTUXOKBsYRechK24dNDxvPfCEU=
github.com/IBM/keyprotect-go-client v0.5.0/go.mod h1:5TwDM/4FRJq1ZOlwQL1xFahLWQ3TveR88VmL1u3njyI=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
Expand Down Expand Up @@ -197,13 +199,15 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
Expand Down Expand Up @@ -324,6 +328,7 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down Expand Up @@ -665,6 +670,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
Expand Down
1 change: 1 addition & 0 deletions wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
AzureKeyVault = "azurekeyvault"
GCPCKMS = "gcpckms"
HuaweiCloudKMS = "huaweicloudkms"
IBMKP = "ibmkp"
MultiWrapper = "multiwrapper"
OCIKMS = "ocikms"
PKCS11 = "pkcs11"
Expand Down
249 changes: 249 additions & 0 deletions wrappers/ibmkp/ibmkp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
package ibmkp

import (
"context"
"encoding/base64"
"errors"
"fmt"
"os"
"sync/atomic"

kp "github.com/IBM/keyprotect-go-client"
wrapping "github.com/hashicorp/go-kms-wrapping"
)

// These constants contain the accepted env vars
const (
EnvIBMApiKey = "IBMCLOUD_API_KEY"
EnvIBMKPEndpoint = "IBMCLOUD_KP_ENDPOINT"
EnvIBMKPInstanceID = "IBMCLOUD_KP_INSTANCE_ID"
EnvIBMKPKeyID = "IBMCLOUD_KP_KEY_ID"
)

// Wrapper represents credentials and Key information for the KMS Key used to
// encryption and decryption
type Wrapper struct {
endpoint string
apiKey string
instanceID string
keyID string

currentKeyID *atomic.Value

client *kp.Client
}

// Ensure that we are implementing Wrapper
var _ wrapping.Wrapper = (*Wrapper)(nil)

// NewWrapper creates a new IBMKP wrapper with the provided options
func NewWrapper(opts *wrapping.WrapperOptions) *Wrapper {
if opts == nil {
opts = new(wrapping.WrapperOptions)
}
k := &Wrapper{
currentKeyID: new(atomic.Value),
}
k.currentKeyID.Store("")
return k
}

// SetConfig sets the fields on the Wrapper object based on
// values from the config parameter.
//
// Order of precedence IBM Key Protect values:
// * Environment variable
// * Value from Vault configuration file
func (k *Wrapper) SetConfig(config map[string]string) (map[string]string, error) {
if config == nil {
config = map[string]string{}
}

// Check and set API Key
switch {
case os.Getenv(EnvIBMApiKey) != "":
k.apiKey = os.Getenv(EnvIBMApiKey)
case config["api_key"] != "":
k.apiKey = config["api_key"]
default:
return nil, fmt.Errorf("'api_key' was not found for IBM Key Protect wrapper configuration")
}

// Check and set Endpoint
switch {
case os.Getenv(EnvIBMKPEndpoint) != "":
k.endpoint = os.Getenv(EnvIBMKPEndpoint)
case config["endpoint"] != "":
k.endpoint = config["endpoint"]
default:
k.endpoint = kp.DefaultBaseURL
}

// Check and set instanceID
switch {
case os.Getenv(EnvIBMKPInstanceID) != "":
k.instanceID = os.Getenv(EnvIBMKPInstanceID)
case config["instance_id"] != "":
k.instanceID = config["instance_id"]
default:
return nil, fmt.Errorf("'instance_id' was not found for IBM Key Protect wrapper configuration")
}

// Check and set keyID
switch {
case os.Getenv(EnvIBMKPKeyID) != "":
k.keyID = os.Getenv(EnvIBMKPKeyID)
case config["key_id"] != "":
k.keyID = config["key_id"]
default:
return nil, fmt.Errorf("'key_id' was not found for IBM Key Protect wrapper configuration")
}

// Check and set k.client
if k.client == nil {
client, err := k.GetIBMKPClient()
if err != nil {
return nil, fmt.Errorf("error initializing IBM Key Protect wrapping client: %w", err)
}

// Test the client connection using provided key ID
key, err := client.GetKeyMetadata(context.Background(), k.keyID)
if err != nil {
return nil, fmt.Errorf("error fetching IBM Key Protect wrapping key information: %w", err)
}
if key == nil || key.ID == "" {
return nil, errors.New("no key information returned")
}
k.currentKeyID.Store(key.ID)

k.client = client
}

// Map that holds non-sensitive configuration info
wrappingInfo := make(map[string]string)
wrappingInfo["endpoint"] = k.endpoint
wrappingInfo["instance_id"] = k.instanceID
wrappingInfo["key_id"] = k.keyID

return wrappingInfo, nil
}

// Init is called during core.Initialize. No-op at the moment.
func (k *Wrapper) Init(_ context.Context) error {
return nil
}

// Finalize is called during shutdown. This is a no-op since
// Wrapper doesn't require any cleanup.
func (k *Wrapper) Finalize(_ context.Context) error {
return nil
}

// Type returns the wrapping type for this particular Wrapper implementation
func (k *Wrapper) Type() string {
return wrapping.IBMKP
}

// KeyID returns the last known key id
func (k *Wrapper) KeyID() string {
return k.currentKeyID.Load().(string)
}

// HMACKeyID returns the last known HMAC key id
func (k *Wrapper) HMACKeyID() string {
return ""
}

// Encrypt is used to encrypt the master key using the the AWS CMK.
// This returns the ciphertext, and/or any errors from this
// call. This should be called after the KMS client has been instantiated.
func (k *Wrapper) Encrypt(ctx context.Context, plaintext, aad []byte) (blob *wrapping.EncryptedBlobInfo, err error) {
if plaintext == nil {
return nil, fmt.Errorf("given plaintext for encryption is nil")
}

env, err := wrapping.NewEnvelope(nil).Encrypt(plaintext, aad)
if err != nil {
return nil, fmt.Errorf("error wrapping data: %w", err)
}

if k.client == nil {
return nil, fmt.Errorf("nil client")
}

envelopKeyBase64 := []byte(base64.StdEncoding.EncodeToString(env.Key))
ciphertext, err := k.client.Wrap(ctx, k.keyID, envelopKeyBase64, nil)
if err != nil {
return nil, fmt.Errorf("error encrypting data: %w", err)
}

k.currentKeyID.Store(k.keyID)

ret := &wrapping.EncryptedBlobInfo{
Ciphertext: env.Ciphertext,
IV: env.IV,
KeyInfo: &wrapping.KeyInfo{
KeyID: k.keyID,
WrappedKey: ciphertext,
},
}

return ret, nil
}

// Decrypt is used to decrypt the ciphertext. This should be called after Init.
func (k *Wrapper) Decrypt(ctx context.Context, in *wrapping.EncryptedBlobInfo, aad []byte) (pt []byte, err error) {
if in == nil {
return nil, errors.New("given input for decryption is nil")
}

if in.KeyInfo == nil {
return nil, errors.New("key info is nil")
}

envelopKeyBase64, err := k.client.Unwrap(ctx, in.KeyInfo.KeyID, in.KeyInfo.WrappedKey, nil)
if err != nil {
return nil, err
}

envelopKey, err := base64.StdEncoding.DecodeString(string(envelopKeyBase64))
if err != nil {
return nil, err
}

envInfo := &wrapping.EnvelopeInfo{
Key: envelopKey,
IV: in.IV,
Ciphertext: in.Ciphertext,
}

plaintext, err := wrapping.NewEnvelope(nil).Decrypt(envInfo, aad)
if err != nil {
return nil, fmt.Errorf("error decrypting data with envelope: %w", err)
}

return plaintext, nil
}

func (k *Wrapper) getConfigAPIKey() kp.ClientConfig {
return kp.ClientConfig{
BaseURL: k.endpoint,
APIKey: k.apiKey,
TokenURL: kp.DefaultTokenURL,
InstanceID: k.instanceID,
Verbose: kp.VerboseFailOnly,
}
}

// GetIBMKPClient returns an instance of the KMS client.
func (k *Wrapper) GetIBMKPClient() (*kp.Client, error) {

options := k.getConfigAPIKey()
api, err := kp.New(options, kp.DefaultTransport())
if err != nil {
return nil, err
}

return api, nil

}
Loading

0 comments on commit 67f9df9

Please sign in to comment.