diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d795b1..0f854a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Added kid and alg to `db` package +- Updated loading cipher options ## [v0.3.3] - Fix box loader in `cipher` package diff --git a/cipher/algo_types.go b/cipher/algo_types.go new file mode 100644 index 0000000..fea907c --- /dev/null +++ b/cipher/algo_types.go @@ -0,0 +1,38 @@ +/** + * Copyright 2019 Comcast Cable Communications Management, LLC + * + * Licensed 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 cipher + +type AlgorithmType string + +const ( + None AlgorithmType = "none" + Box AlgorithmType = "box" + RSASymmetric AlgorithmType = "rsa-sym" + RSAAsymmetric AlgorithmType = "rsa-asy" +) + +func ParseAlogrithmType(algo string) AlgorithmType { + if algo == string(Box) { + return Box + } else if algo == string(RSASymmetric) { + return RSASymmetric + } else if algo == string(RSAAsymmetric) { + return RSAAsymmetric + } + return None +} diff --git a/cipher/boxLoader.go b/cipher/boxLoader.go index 9d64ef1..86d4d54 100644 --- a/cipher/boxLoader.go +++ b/cipher/boxLoader.go @@ -23,6 +23,7 @@ import ( ) type BoxLoader struct { + KID string PrivateKey KeyLoader PublicKey KeyLoader } @@ -65,7 +66,7 @@ func (boxLoader *BoxLoader) LoadEncrypt() (Encrypt, error) { if err != nil { return nil, err } - return NewBoxEncrypter(privateKey, publicKey), nil + return NewBoxEncrypter(privateKey, publicKey, boxLoader.KID), nil } func (boxLoader *BoxLoader) LoadDecrypt() (Decrypt, error) { @@ -78,5 +79,5 @@ func (boxLoader *BoxLoader) LoadDecrypt() (Decrypt, error) { if err != nil { return nil, err } - return NewBoxDecrypter(privateKey, publicKey), nil + return NewBoxDecrypter(privateKey, publicKey, boxLoader.KID), nil } diff --git a/cipher/boxRecipient.json b/cipher/boxRecipient.json index 7c57dfd..637ca8c 100644 --- a/cipher/boxRecipient.json +++ b/cipher/boxRecipient.json @@ -1,7 +1,16 @@ { - "algorithm": { - "type": "box" - }, - "recipientPrivateKey": "boxPrivate.pem", - "senderPublicKey": "sendBoxPublic.pem" -} \ No newline at end of file + "cipher": [ + { + "type": "box", + "kid": "test", + "keys": { + "senderPublicKey": "sendBoxPublic.pem", + "recipientPrivateKey": "boxPrivate.pem" + } + }, + { + "type": "none", + "kid": "none" + } + ] +} diff --git a/cipher/boxSender.json b/cipher/boxSender.json index 5bc69d9..b05e03b 100644 --- a/cipher/boxSender.json +++ b/cipher/boxSender.json @@ -1,7 +1,12 @@ { - "algorithm": { - "type": "box" - }, - "senderPrivateKey": "sendBoxPrivate.pem", - "recipientPublicKey": "boxPublic.pem" -} \ No newline at end of file + "cipher": [ + { + "type": "box", + "kid": "test", + "keys": { + "senderPrivateKey": "sendBoxPrivate.pem", + "recipientPublicKey": "boxPublic.pem" + } + } + ] +} diff --git a/cipher/cipher.go b/cipher/cipher.go index 6d1e747..1c8d627 100644 --- a/cipher/cipher.go +++ b/cipher/cipher.go @@ -44,8 +44,18 @@ func init() { }) } +type Identification interface { + // GetAlgorithm will return the algorithm Encrypt and Decrypt uses + GetAlgorithm() AlgorithmType + + // GetKID returns the id of the specific keys used + GetKID() string +} + // Encrypt represents the ability to encrypt messages type Encrypt interface { + Identification + // EncryptMessage attempts to encode the message into an array of bytes. // and error will be returned if failed to encode the message. EncryptMessage(message []byte) (crypt []byte, nonce []byte, err error) @@ -53,6 +63,8 @@ type Encrypt interface { // Decrypt represents the ability to decrypt messages type Decrypt interface { + Identification + // DecryptMessage attempts to decode the message into a string. // and error will be returned if failed to decode the message. DecryptMessage(cipher []byte, nonce []byte) (message []byte, err error) @@ -83,6 +95,14 @@ func DefaultCipherDecrypter() Decrypt { // NOOP will just return the message type NOOP struct{} +func (*NOOP) GetAlgorithm() AlgorithmType { + return None +} + +func (*NOOP) GetKID() string { + return "none" +} + func (*NOOP) EncryptMessage(message []byte) (crypt []byte, nonce []byte, err error) { return message, []byte{}, nil } @@ -91,39 +111,48 @@ func (*NOOP) DecryptMessage(cipher []byte, nonce []byte) (message []byte, err er return cipher, nil } -type basicEncrypter struct { - hasher crypto.Hash - senderPrivateKey *rsa.PrivateKey - recipientPublicKey *rsa.PublicKey - label []byte +func (c *rsaEncrypterDecrypter) GetAlgorithm() AlgorithmType { + if c.recipientPublicKey == nil || c.senderPublicKey == nil { + return RSASymmetric + } + return RSAAsymmetric } -type basicDecrypter struct { +func (c *rsaEncrypterDecrypter) GetKID() string { + return c.kid +} + +type rsaEncrypterDecrypter struct { + kid string hasher crypto.Hash + recipientPublicKey *rsa.PublicKey recipientPrivateKey *rsa.PrivateKey senderPublicKey *rsa.PublicKey + senderPrivateKey *rsa.PrivateKey label []byte } -func NewBasicEncrypter(hash crypto.Hash, senderPrivateKey *rsa.PrivateKey, recipientPublicKey *rsa.PublicKey) Encrypt { - return &basicEncrypter{ +func NewRSAEncrypter(hash crypto.Hash, senderPrivateKey *rsa.PrivateKey, recipientPublicKey *rsa.PublicKey, kid string) Encrypt { + return &rsaEncrypterDecrypter{ + kid: kid, hasher: hash, senderPrivateKey: senderPrivateKey, recipientPublicKey: recipientPublicKey, - label: []byte("codex-basic-encrypter"), + label: []byte("codex-rsa-cipher"), } } -func NewBasicDecrypter(hash crypto.Hash, recipientPrivateKey *rsa.PrivateKey, senderPublicKey *rsa.PublicKey) Decrypt { - return &basicDecrypter{ +func NewRSADecrypter(hash crypto.Hash, recipientPrivateKey *rsa.PrivateKey, senderPublicKey *rsa.PublicKey, kid string) Decrypt { + return &rsaEncrypterDecrypter{ + kid: kid, hasher: hash, recipientPrivateKey: recipientPrivateKey, senderPublicKey: senderPublicKey, - label: []byte("codex-basic-encrypter"), + label: []byte("codex-rsa-cipher"), } } -func (c *basicEncrypter) EncryptMessage(message []byte) ([]byte, []byte, error) { +func (c *rsaEncrypterDecrypter) EncryptMessage(message []byte) ([]byte, []byte, error) { cipherdata, err := rsa.EncryptOAEP( c.hasher.New(), rand.Reader, @@ -135,22 +164,26 @@ func (c *basicEncrypter) EncryptMessage(message []byte) ([]byte, []byte, error) return []byte(""), []byte{}, emperror.Wrap(err, "failed to encrypt message") } - var opts rsa.PSSOptions - opts.SaltLength = rsa.PSSSaltLengthAuto // for simple example + signature := []byte{} - pssh := c.hasher.New() - pssh.Write(message) - hashed := pssh.Sum(nil) + if c.senderPrivateKey != nil { + var opts rsa.PSSOptions + opts.SaltLength = rsa.PSSSaltLengthAuto // for simple example - signature, err := rsa.SignPSS(rand.Reader, c.senderPrivateKey, c.hasher, hashed, &opts) - if err != nil { - return []byte(""), []byte{}, emperror.Wrap(err, "failed to sign message") + pssh := c.hasher.New() + pssh.Write(message) + hashed := pssh.Sum(nil) + + signature, err = rsa.SignPSS(rand.Reader, c.senderPrivateKey, c.hasher, hashed, &opts) + if err != nil { + return []byte(""), []byte{}, emperror.Wrap(err, "failed to sign message") + } } return cipherdata, signature, nil } -func (c *basicDecrypter) DecryptMessage(cipher []byte, nonce []byte) ([]byte, error) { +func (c *rsaEncrypterDecrypter) DecryptMessage(cipher []byte, nonce []byte) ([]byte, error) { decrypted, err := rsa.DecryptOAEP( c.hasher.New(), rand.Reader, @@ -162,30 +195,42 @@ func (c *basicDecrypter) DecryptMessage(cipher []byte, nonce []byte) ([]byte, er return []byte{}, emperror.Wrap(err, "failed to decrypt message") } - var opts rsa.PSSOptions - opts.SaltLength = rsa.PSSSaltLengthAuto // for simple example + if c.senderPublicKey != nil { + var opts rsa.PSSOptions + opts.SaltLength = rsa.PSSSaltLengthAuto // for simple example - pssh := c.hasher.New() - pssh.Write(decrypted) - hashed := pssh.Sum(nil) + pssh := c.hasher.New() + pssh.Write(decrypted) + hashed := pssh.Sum(nil) - err = rsa.VerifyPSS(c.senderPublicKey, c.hasher, hashed, nonce, &opts) - if err != nil { - return []byte{}, emperror.Wrap(err, "failed to validate signature") + err = rsa.VerifyPSS(c.senderPublicKey, c.hasher, hashed, nonce, &opts) + if err != nil { + return []byte{}, emperror.Wrap(err, "failed to validate signature") + } } return decrypted, nil } type encryptBox struct { + kid string senderPrivateKey [32]byte recipientPublicKey [32]byte sharedEncryptKey *[32]byte } -func NewBoxEncrypter(senderPrivateKey [32]byte, recipientPublicKey [32]byte) Encrypt { +func (enBox *encryptBox) GetAlgorithm() AlgorithmType { + return Box +} + +func (enBox *encryptBox) GetKID() string { + return enBox.kid +} + +func NewBoxEncrypter(senderPrivateKey [32]byte, recipientPublicKey [32]byte, kid string) Encrypt { encrypter := encryptBox{ + kid: kid, senderPrivateKey: senderPrivateKey, recipientPublicKey: recipientPublicKey, sharedEncryptKey: new([32]byte), @@ -208,14 +253,24 @@ func (enBox *encryptBox) EncryptMessage(message []byte) ([]byte, []byte, error) } type decryptBox struct { + kid string recipientPrivateKey [32]byte senderPublicKey [32]byte sharedDecryptKey *[32]byte } -func NewBoxDecrypter(recipientPrivateKey [32]byte, senderPublicKey [32]byte) Decrypt { +func (deBox *decryptBox) GetAlgorithm() AlgorithmType { + return Box +} + +func (deBox *decryptBox) GetKID() string { + return deBox.kid +} + +func NewBoxDecrypter(recipientPrivateKey [32]byte, senderPublicKey [32]byte, kid string) Decrypt { decrypter := decryptBox{ + kid: kid, recipientPrivateKey: recipientPrivateKey, senderPublicKey: senderPublicKey, sharedDecryptKey: new([32]byte), diff --git a/cipher/cipher_test.go b/cipher/cipher_test.go index f149ac4..a43dece 100644 --- a/cipher/cipher_test.go +++ b/cipher/cipher_test.go @@ -51,9 +51,9 @@ func TestBasicCrypt(t *testing.T) { recipientPrivateKey := GeneratePrivateKey(tc.size) require.NotNil(recipientPrivateKey) - encrypter := NewBasicEncrypter(tc.hashAlgo, senderPrivateKey, &recipientPrivateKey.PublicKey) + encrypter := NewRSAEncrypter(tc.hashAlgo, senderPrivateKey, &recipientPrivateKey.PublicKey, "") require.NotEmpty(encrypter) - decrypter := NewBasicDecrypter(tc.hashAlgo, recipientPrivateKey, &senderPrivateKey.PublicKey) + decrypter := NewRSADecrypter(tc.hashAlgo, recipientPrivateKey, &senderPrivateKey.PublicKey, "") require.NotEmpty(decrypter) testCryptoPair(t, encrypter, decrypter, tc.errOnLarge) @@ -116,9 +116,9 @@ func TestBoxCipher(t *testing.T) { require.NoError(err) require.NotEqual(recipientPublicKey, senderPublicKey) - encrypter := NewBoxEncrypter(*senderPrivateKey, *recipientPublicKey) + encrypter := NewBoxEncrypter(*senderPrivateKey, *recipientPublicKey, "") require.NotEmpty(encrypter) - decrypter := NewBoxDecrypter(*recipientPrivateKey, *senderPublicKey) + decrypter := NewBoxDecrypter(*recipientPrivateKey, *senderPublicKey, "") require.NotEmpty(decrypter) testCryptoPair(t, encrypter, decrypter, false) diff --git a/cipher/example.json b/cipher/example.json index 42b0969..2b46e80 100644 --- a/cipher/example.json +++ b/cipher/example.json @@ -1,8 +1,14 @@ { - "algorithm": { - "type": "basic", - "hash": "SHA512" - }, - "senderPrivateKey": "private.pem", - "recipientPublicKey": "public.pem" + "cipher": [ + { + "type": "rsa-sym", + "params": { + "hash": "SHA512" + }, + "keys": { + "publicKey": "public.pem", + "privateKey": "private.pem" + } + } + ] } \ No newline at end of file diff --git a/cipher/key_types.go b/cipher/key_types.go new file mode 100644 index 0000000..13577db --- /dev/null +++ b/cipher/key_types.go @@ -0,0 +1,41 @@ +/** + * Copyright 2019 Comcast Cable Communications Management, LLC + * + * Licensed 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 cipher + +type KeyType string + +const ( + PublicKey KeyType = "publicKey" + PrivateKey KeyType = "privateKey" + SenderPrivateKey KeyType = "senderPrivateKey" + SenderPublicKey KeyType = "senderPublicKey" + RecipientPrivateKey KeyType = "recipientPrivateKey" + RecipientPublicKey KeyType = "recipientPublicKey" +) + +func hasBothEncryptKeys(data map[KeyType]string) bool { + _, privateOK := data[SenderPrivateKey] + _, publicOK := data[RecipientPublicKey] + return privateOK && publicOK +} + +func hasBothDecryptKeys(data map[KeyType]string) bool { + _, privateOK := data[RecipientPrivateKey] + _, publicOK := data[SenderPublicKey] + return privateOK && publicOK +} diff --git a/cipher/loader.go b/cipher/loader.go index f788763..36f41f0 100644 --- a/cipher/loader.go +++ b/cipher/loader.go @@ -27,7 +27,10 @@ import ( "github.com/goph/emperror" "github.com/pkg/errors" "io/ioutil" - "strings" +) + +var ( + errIncorrectKeys = errors.New("incorrect keys provided") ) var ( @@ -39,17 +42,24 @@ var ( } ) -type Options struct { +// Config used load the Encrypt or Decrypt +type Config struct { // Logger is the go-kit Logger to use for server startup and error logging. If not // supplied, logging.DefaultLogger() is used instead. Logger log.Logger `json:"-"` - Algorithm map[string]interface{} `json:"algorithm,omitempty"` + // Type is the algorithm type. Like none, box, rsa etc. + Type AlgorithmType `json:"type"` - SenderPrivateKey string `json:"senderPrivateKey,omitempty"` - SenderPublicKey string `json:"senderPublicKey,omitempty"` - RecipientPrivateKey string `json:"recipientPrivateKey,omitempty"` - RecipientPublicKey string `json:"recipientPublicKey,omitempty"` + // KID is the key id of the cipher + KID string `json:"kid,omitempty"` + + // Params to be provided to the algorithm type. + // For example providing a hash algorithm to rsa. + Params map[string]string `json:"params,omitempty"` + + // Keys is a map of keys to path. aka senderPrivateKey : private.pem + Keys map[KeyType]string `json:"keys,omitempty"` } type KeyLoader interface { @@ -70,6 +80,12 @@ func (f *FileLoader) GetBytes() ([]byte, error) { return ioutil.ReadFile(f.Path) } +func CreateFileLoader(keys map[KeyType]string, keyType KeyType) KeyLoader { + return &FileLoader{ + Path: keys[keyType], + } +} + type BytesLoader struct { Data []byte } @@ -79,6 +95,10 @@ func (b *BytesLoader) GetBytes() ([]byte, error) { } func GetPrivateKey(loader KeyLoader) (*rsa.PrivateKey, error) { + if loader == nil { + return nil, errors.New("no loader") + } + data, err := loader.GetBytes() if err != nil { return nil, err @@ -101,6 +121,10 @@ func GetPrivateKey(loader KeyLoader) (*rsa.PrivateKey, error) { } func GetPublicKey(loader KeyLoader) (*rsa.PublicKey, error) { + if loader == nil { + return nil, errors.New("no loader") + } + data, err := loader.GetBytes() if err != nil { return nil, err @@ -122,96 +146,104 @@ func GetPublicKey(loader KeyLoader) (*rsa.PublicKey, error) { } } -func (o *Options) LoadEncrypt() (Encrypt, error) { - if o.Logger == nil { - o.Logger = logging.DefaultLogger() - } - logging.Info(o.Logger).Log(logging.MessageKey(), "new encrypter", "options", o) - - if algorithm, ok := o.Algorithm["type"].(string); ok { - switch strings.ToLower(algorithm) { - case "noop": - return DefaultCipherEncrypter(), nil - case "box": - if o.SenderPrivateKey == "" || o.RecipientPublicKey == "" { - break - } - boxLoader := BoxLoader{ - PrivateKey: &FileLoader{ - Path: o.SenderPrivateKey, - }, - PublicKey: &FileLoader{ - Path: o.RecipientPublicKey, - }, - } - return boxLoader.LoadEncrypt() - case "basic": - if hashName, ok := o.Algorithm["hash"].(string); ok { - if o.SenderPrivateKey == "" || o.RecipientPublicKey == "" { - break - } - basicLoader := BasicLoader{ - Hash: &BasicHashLoader{hashName}, - PrivateKey: &FileLoader{ - Path: o.SenderPrivateKey, - }, - PublicKey: &FileLoader{ - Path: o.RecipientPublicKey, - }, - } - return basicLoader.LoadEncrypt() - } else { - return nil, errors.New("failed to find hash name for basic algorithm cipher type") - } +func (config *Config) LoadEncrypt() (Encrypt, error) { + var err error + if config.Logger == nil { + config.Logger = logging.DefaultLogger() + } + logging.Debug(config.Logger).Log(logging.MessageKey(), "new encrypter", "config", config) + + switch config.Type { + case None: + return DefaultCipherEncrypter(), nil + case Box: + if !hasBothEncryptKeys(config.Keys) { + err = errIncorrectKeys + break + } + boxLoader := BoxLoader{ + KID: config.KID, + PrivateKey: CreateFileLoader(config.Keys, SenderPrivateKey), + PublicKey: CreateFileLoader(config.Keys, RecipientPublicKey), + } + return boxLoader.LoadEncrypt() + case RSASymmetric: + if _, ok := config.Keys[PublicKey]; !ok { + err = errIncorrectKeys + break + } + rsaLoader := RSALoader{ + KID: config.KID, + Hash: &BasicHashLoader{HashName: config.Params["hash"]}, + PublicKey: CreateFileLoader(config.Keys, PublicKey), + } + return rsaLoader.LoadEncrypt() + case RSAAsymmetric: + if !hasBothEncryptKeys(config.Keys) { + err = errIncorrectKeys + break + } + rsaLoader := RSALoader{ + KID: config.KID, + Hash: &BasicHashLoader{HashName: config.Params["hash"]}, + PrivateKey: CreateFileLoader(config.Keys, SenderPrivateKey), + PublicKey: CreateFileLoader(config.Keys, RecipientPublicKey), } + return rsaLoader.LoadEncrypt() + default: + err = errors.New("no algorithm type specified") } - return DefaultCipherEncrypter(), errors.New("failed to load custom algorithm") + return DefaultCipherEncrypter(), emperror.Wrap(err, "failed to load custom algorithm") } -func (o *Options) LoadDecrypt() (Decrypt, error) { - if o.Logger == nil { - o.Logger = logging.DefaultLogger() - } - logging.Info(o.Logger).Log(logging.MessageKey(), "new decrypter", "options", o) - - if algorithm, ok := o.Algorithm["type"].(string); ok { - switch strings.ToLower(algorithm) { - case "noop": - return DefaultCipherDecrypter(), nil - case "box": - if o.RecipientPrivateKey == "" || o.SenderPublicKey == "" { - break - } - boxLoader := BoxLoader{ - PrivateKey: &FileLoader{ - Path: o.RecipientPrivateKey, - }, - PublicKey: &FileLoader{ - Path: o.SenderPublicKey, - }, - } - return boxLoader.LoadDecrypt() - case "basic": - if o.RecipientPrivateKey == "" || o.SenderPublicKey == "" { - break - } - if hashName, ok := o.Algorithm["hash"].(string); ok { - basicLoader := BasicLoader{ - Hash: &BasicHashLoader{hashName}, - PrivateKey: &FileLoader{ - Path: o.RecipientPrivateKey, - }, - PublicKey: &FileLoader{ - Path: o.SenderPublicKey, - }, - } - return basicLoader.LoadDecrypt() - } else { - return nil, errors.New("failed to find hash name for basic algorithm cipher type") - } +func (config *Config) LoadDecrypt() (Decrypt, error) { + var err error + if config.Logger == nil { + config.Logger = logging.DefaultLogger() + } + logging.Debug(config.Logger).Log(logging.MessageKey(), "new decrypter", "config", config) + + switch config.Type { + case None: + return DefaultCipherDecrypter(), nil + case Box: + if !hasBothDecryptKeys(config.Keys) { + err = errIncorrectKeys + break + } + boxLoader := BoxLoader{ + KID: config.KID, + PrivateKey: CreateFileLoader(config.Keys, RecipientPrivateKey), + PublicKey: CreateFileLoader(config.Keys, SenderPublicKey), + } + return boxLoader.LoadDecrypt() + case RSASymmetric: + if _, ok := config.Keys[PrivateKey]; !ok { + err = errIncorrectKeys + break + } + rsaLoader := RSALoader{ + KID: config.KID, + Hash: &BasicHashLoader{HashName: config.Params["hash"]}, + PrivateKey: CreateFileLoader(config.Keys, PrivateKey), + } + return rsaLoader.LoadDecrypt() + case RSAAsymmetric: + if !hasBothDecryptKeys(config.Keys) { + err = errIncorrectKeys + break + } + rsaLoader := RSALoader{ + KID: config.KID, + Hash: &BasicHashLoader{HashName: config.Params["hash"]}, + PrivateKey: CreateFileLoader(config.Keys, RecipientPrivateKey), + PublicKey: CreateFileLoader(config.Keys, SenderPublicKey), } + return rsaLoader.LoadDecrypt() + default: + err = errors.New("no algorithm type specified") } - return DefaultCipherDecrypter(), errors.New("failed to load custom algorithm") + return DefaultCipherDecrypter(), emperror.Wrap(err, "failed to load custom algorithm") } diff --git a/cipher/loader_test.go b/cipher/loader_test.go index 21b6393..ba29144 100644 --- a/cipher/loader_test.go +++ b/cipher/loader_test.go @@ -31,7 +31,7 @@ func TestBasicCipherLoader(t *testing.T) { dir, err := os.Getwd() assert.NoError(err) - encrypter, err := (&BasicLoader{ + encrypter, err := (&RSALoader{ Hash: &BasicHashLoader{HashName: "SHA512"}, PrivateKey: &FileLoader{ Path: dir + string(os.PathSeparator) + "private.pem", @@ -43,7 +43,7 @@ func TestBasicCipherLoader(t *testing.T) { assert.NotEmpty(encrypter) assert.NoError(err) - decrypter, err := (&BasicLoader{ + decrypter, err := (&RSALoader{ Hash: &BasicHashLoader{HashName: "SHA512"}, PrivateKey: &FileLoader{ Path: dir + string(os.PathSeparator) + "private.pem", @@ -74,42 +74,49 @@ func TestLoadOptions(t *testing.T) { testData := []struct { description string - option Options + config Config errOnLarge bool }{ - {"noop", Options{Algorithm: map[string]interface{}{"type": "noop"}}, false}, - {"basic", Options{ - Logger: logging.NewTestLogger(nil, t), - Algorithm: map[string]interface{}{"type": "basic", "hash": "SHA512"}, - SenderPublicKey: dir + string(os.PathSeparator) + "public.pem", - SenderPrivateKey: dir + string(os.PathSeparator) + "private.pem", - RecipientPrivateKey: dir + string(os.PathSeparator) + "private.pem", - RecipientPublicKey: dir + string(os.PathSeparator) + "public.pem", + {"noop", Config{Type: None}, false}, + {"basic", Config{ + Logger: logging.NewTestLogger(nil, t), + Type: RSAAsymmetric, + Params: map[string]string{"hash": "SHA512"}, + KID: "neato", + Keys: map[KeyType]string{ + SenderPrivateKey: dir + string(os.PathSeparator) + "private.pem", + SenderPublicKey: dir + string(os.PathSeparator) + "public.pem", + RecipientPrivateKey: dir + string(os.PathSeparator) + "private.pem", + RecipientPublicKey: dir + string(os.PathSeparator) + "public.pem", + }, }, true}, - {"box", Options{ - Logger: logging.NewTestLogger(nil, t), - Algorithm: map[string]interface{}{"type": "box"}, - SenderPublicKey: dir + string(os.PathSeparator) + "boxPublic.pem", - SenderPrivateKey: dir + string(os.PathSeparator) + "boxPrivate.pem", - RecipientPrivateKey: dir + string(os.PathSeparator) + "boxPrivate.pem", - RecipientPublicKey: dir + string(os.PathSeparator) + "boxPublic.pem", + {"box", Config{ + Logger: logging.NewTestLogger(nil, t), + Type: Box, + KID: "coolio", + Keys: map[KeyType]string{ + SenderPrivateKey: dir + string(os.PathSeparator) + "sendBoxPrivate.pem", + SenderPublicKey: dir + string(os.PathSeparator) + "sendBoxPublic.pem", + RecipientPrivateKey: dir + string(os.PathSeparator) + "boxPrivate.pem", + RecipientPublicKey: dir + string(os.PathSeparator) + "boxPublic.pem", + }, }, true}, } for _, tc := range testData { t.Run(tc.description, func(t *testing.T) { - testOptions(t, tc.option, tc.errOnLarge) + testOptions(t, tc.config, tc.errOnLarge) }) } } -func testOptions(t *testing.T, o Options, errOnLarge bool) { +func testOptions(t *testing.T, c Config, errOnLarge bool) { require := require.New(t) - encrypter, err := o.LoadEncrypt() + encrypter, err := c.LoadEncrypt() require.NoError(err) - decrypter, err := o.LoadDecrypt() + decrypter, err := c.LoadDecrypt() require.NoError(err) testCryptoPair(t, encrypter, decrypter, errOnLarge) diff --git a/cipher/noop.json b/cipher/noop.json index ab7ee5e..b4131b2 100644 --- a/cipher/noop.json +++ b/cipher/noop.json @@ -1,5 +1,7 @@ { - "algorithm": { - "type": "noop" - } + "cipher": [ + { + "type": "none" + } + ] } \ No newline at end of file diff --git a/cipher/basicLoader.go b/cipher/rsaLoader.go similarity index 80% rename from cipher/basicLoader.go rename to cipher/rsaLoader.go index 0faff00..cc7e059 100644 --- a/cipher/basicLoader.go +++ b/cipher/rsaLoader.go @@ -49,13 +49,14 @@ func (b *BasicHashLoader) GetHash() (crypto.Hash, error) { return 0, errors.New("hashname " + b.HashName + " not found") } -type BasicLoader struct { +type RSALoader struct { + KID string Hash HashLoader PrivateKey KeyLoader PublicKey KeyLoader } -func (loader *BasicLoader) LoadEncrypt() (Encrypt, error) { +func (loader *RSALoader) LoadEncrypt() (Encrypt, error) { hashFunc, err := loader.Hash.GetHash() if err != nil { return nil, err @@ -65,28 +66,23 @@ func (loader *BasicLoader) LoadEncrypt() (Encrypt, error) { if err != nil { return nil, err } + privateKey, _ := GetPrivateKey(loader.PrivateKey) - privateKey, err := GetPrivateKey(loader.PrivateKey) - if err != nil { - return nil, err - } - return NewBasicEncrypter(hashFunc, privateKey, publicKey), nil + return NewRSAEncrypter(hashFunc, privateKey, publicKey, loader.KID), nil } -func (loader *BasicLoader) LoadDecrypt() (Decrypt, error) { +func (loader *RSALoader) LoadDecrypt() (Decrypt, error) { hashFunc, err := loader.Hash.GetHash() if err != nil { return nil, err } - publicKey, err := GetPublicKey(loader.PublicKey) - if err != nil { - return nil, err - } - privateKey, err := GetPrivateKey(loader.PrivateKey) if err != nil { return nil, err } - return NewBasicDecrypter(hashFunc, privateKey, publicKey), nil + + publicKey, _ := GetPublicKey(loader.PublicKey) + + return NewRSADecrypter(hashFunc, privateKey, publicKey, loader.KID), nil } diff --git a/cipher/viper.go b/cipher/viper.go index 69d16ba..22945ca 100644 --- a/cipher/viper.go +++ b/cipher/viper.go @@ -1,6 +1,9 @@ package cipher import ( + "encoding/json" + "github.com/go-kit/kit/log" + "github.com/goph/emperror" "github.com/spf13/viper" ) @@ -15,25 +18,56 @@ const ( CipherKey = "cipher" ) -// Sub returns the standard child Viper, using CipherKey, for this package. -// If passed nil, this function returns nil. -func Sub(v *viper.Viper) *viper.Viper { - if v != nil { - return v.Sub(CipherKey) +type Options []Config + +type Ciphers struct { + Options map[AlgorithmType]map[string]Decrypt +} + +func (o Options) GetEncrypter(logger log.Logger) (Encrypt, error) { + var lastErr error + for _, elem := range o { + if encrypter, err := elem.LoadEncrypt(); err == nil { + return encrypter, nil + } else { + lastErr = err + } + } + return DefaultCipherEncrypter(), emperror.Wrap(lastErr, "failed to load encrypt options") +} + +func PopulateCiphers(o Options, logger log.Logger) Ciphers { + c := Ciphers{ + Options: map[AlgorithmType]map[string]Decrypt{}, + } + for _, elem := range o { + elem.Logger = logger + if decrypter, err := elem.LoadDecrypt(); err == nil { + if _, ok := c.Options[elem.Type]; !ok { + c.Options[elem.Type] = map[string]Decrypt{} + } + c.Options[elem.Type][elem.KID] = decrypter + } } + return c +} - return nil +func (c *Ciphers) Get(alg AlgorithmType, KID string) (Decrypt, bool) { + if d, ok := c.Options[alg][KID]; ok { + return d, ok + } + return nil, false } // FromViper produces an Options from a (possibly nil) Viper instance. -// Callers should use FromViper(Sub(v)) if the standard subkey is desired. -func FromViper(v *viper.Viper) (*Options, error) { - o := new(Options) - if v != nil { - if err := v.Unmarshal(o); err != nil { - return nil, err - } +// cipher key is expected +func FromViper(v *viper.Viper) (o Options, err error) { + obj := v.Get("cipher") + data, err := json.Marshal(obj) + if err != nil { + return []Config{}, emperror.Wrap(err, "failed to load cipher config") } - return o, nil + err = json.Unmarshal(data, &o) + return } diff --git a/cipher/viper_test.go b/cipher/viper_test.go index 2ea337f..8def130 100644 --- a/cipher/viper_test.go +++ b/cipher/viper_test.go @@ -1,6 +1,8 @@ package cipher import ( + "fmt" + "github.com/Comcast/webpa-common/logging" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "os" @@ -23,9 +25,9 @@ func TestViper(t *testing.T) { options, err := FromViper(v) assert.NoError(err) - encrypter, err := options.LoadEncrypt() + encrypter, err := options.GetEncrypter(logging.NewTestLogger(nil, t)) assert.NoError(err) - assert.NotEmpty(options) + assert.NotNil(encrypter) msg := "hello" data, _, err := encrypter.EncryptMessage([]byte(msg)) @@ -49,7 +51,7 @@ func TestNOOPViper(t *testing.T) { options, err := FromViper(v) assert.NoError(err) - encrypter, err := options.LoadEncrypt() + encrypter, err := options.GetEncrypter(logging.NewTestLogger(nil, t)) msg := "hello" data, _, err := encrypter.EncryptMessage([]byte(msg)) @@ -72,7 +74,7 @@ func TestBoxBothSides(t *testing.T) { options, err := FromViper(vSend) assert.NoError(err) - encrypter, err := options.LoadEncrypt() + encrypter, err := options.GetEncrypter(logging.NewTestLogger(nil, t)) assert.NoError(err) vRec := viper.New() @@ -86,16 +88,56 @@ func TestBoxBothSides(t *testing.T) { options, err = FromViper(vRec) assert.NoError(err) - decrypter, err := options.LoadDecrypt() + decrypters := PopulateCiphers(options, logging.NewTestLogger(nil, t)) + assert.NoError(err) msg := []byte("hello") data, nonce, err := encrypter.EncryptMessage(msg) assert.NoError(err) - decodedMSG, err := decrypter.DecryptMessage(data, nonce) + if decrypter, ok := decrypters.Get(encrypter.GetAlgorithm(), encrypter.GetKID()); ok { + decodedMSG, err := decrypter.DecryptMessage(data, nonce) + assert.NoError(err) + + assert.Equal(msg, decodedMSG) + } else { + assert.Fail("failed to get decrypter with kid") + } +} + +func TestGetDecrypterErr(t *testing.T) { + assert := assert.New(t) + + vSend := viper.New() + path, err := os.Getwd() assert.NoError(err) + vSend.AddConfigPath(path) + vSend.SetConfigName("boxRecipient") + if err := vSend.ReadInConfig(); err != nil { + t.Fatalf("%s\n", err) + } + + options, err := FromViper(vSend) + assert.NoError(err) + + decrypters := PopulateCiphers(options, logging.NewTestLogger(nil, t)) + fmt.Printf("%#v\n", decrypters) + + decrypter, ok := decrypters.Get(Box, "test") + assert.True(ok) + assert.NotNil(decrypter) + + decrypter, ok = decrypters.Get(None, "none") + assert.True(ok) + assert.NotNil(decrypter) - assert.Equal(msg, decodedMSG) + // negative test + decrypter, ok = decrypters.Get(None, "neato") + assert.False(ok) + assert.Nil(decrypter) + decrypter, ok = decrypters.Get(RSAAsymmetric, "testing") + assert.False(ok) + assert.Nil(decrypter) } diff --git a/db/db.go b/db/db.go index b90e114..84f39b7 100644 --- a/db/db.go +++ b/db/db.go @@ -87,6 +87,8 @@ type Record struct { DeathDate int64 `json:"deathdate"` Data []byte `json:"data"` Nonce []byte `json:"nonce"` + Alg string `json:"alg"` + KID string `json:"kid" gorm:"Column:kid"` } // set Record's table name to be `events` diff --git a/deploy/docker-compose/docFiles/gungnir.yaml b/deploy/docker-compose/docFiles/gungnir.yaml index ceaeeb2..5a78ce7 100644 --- a/deploy/docker-compose/docFiles/gungnir.yaml +++ b/deploy/docker-compose/docFiles/gungnir.yaml @@ -38,7 +38,12 @@ db: connectTimeout: 1m opTimeout: 100ms - cipher: - hashName: "MD5" - path: "/etc/gungnir/private.pem" \ No newline at end of file + - type: rsa-sym + kid: "basic-sha" + params: + hash: SHA512 + keys: + privateKey: "/etc/gungnir/private.pem" + - type: none + kid: none \ No newline at end of file diff --git a/deploy/docker-compose/docFiles/svalinn.yaml b/deploy/docker-compose/docFiles/svalinn.yaml index 30bb3fd..199aed3 100644 --- a/deploy/docker-compose/docFiles/svalinn.yaml +++ b/deploy/docker-compose/docFiles/svalinn.yaml @@ -74,5 +74,11 @@ webhook: cipher: - hashName: "MD5" - path: "/etc/svalinn/public.pem" \ No newline at end of file + - type: rsa-sym + kid: "basic-sha" + params: + hash: SHA512 + keys: + publicKey: "/etc/svalinn/public.pem" + - type: none + kid: none diff --git a/deploy/docker-compose/setup_db.sh b/deploy/docker-compose/setup_db.sh index e974c23..286877b 100644 --- a/deploy/docker-compose/setup_db.sh +++ b/deploy/docker-compose/setup_db.sh @@ -6,5 +6,5 @@ SQL="/cockroach/cockroach.sh sql $HOSTPARAMS" $SQL -e "CREATE USER IF NOT EXISTS roachadmin;" $SQL -e "CREATE DATABASE IF NOT EXISTS devices;" $SQL -e "GRANT ALL ON DATABASE devices TO roachadmin;" -$SQL -e "CREATE TABLE devices.events (id BIGINT PRIMARY KEY DEFAULT unique_rowid(), type INT NOT NULL, device_id STRING NOT NULL, birth_date BIGINT NOT NULL, death_date BIGINT NOT NULL, data BYTES NOT NULL, nonce BYTES NOT NULL, INDEX idx_events_id_birth_date (device_id, birth_date DESC), INDEX idx_events_death_date (death_date DESC)) " +$SQL -e "CREATE TABLE devices.events (id BIGINT PRIMARY KEY DEFAULT unique_rowid(), type INT NOT NULL, device_id STRING NOT NULL, birth_date BIGINT NOT NULL, death_date BIGINT NOT NULL, data BYTES NOT NULL, nonce BYTES NOT NULL, alg STRING NOT NULL, kid STRING NOT NULL, INDEX idx_events_id_birth_date (device_id, birth_date DESC), INDEX idx_events_death_date (death_date DESC)) " $SQL -e "CREATE TABLE devices.blacklist (id STRING PRIMARY KEY, reason STRING NOT NULL);" \ No newline at end of file