Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for using AWS encrypted BLS key files #2650

Merged
merged 2 commits into from
Apr 1, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 66 additions & 23 deletions cmd/harmony/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ var (
shardID = flag.Int("shard_id", -1, "the shard ID of this node")
enableMemProfiling = flag.Bool("enableMemProfiling", false, "Enable memsize logging.")
enableGC = flag.Bool("enableGC", true, "Enable calling garbage collector manually .")
cmkEncryptedBLSKey = flag.String("aws_blskey", "", "The aws CMK encrypted bls private key file.")
blsKeyFile = flag.String("blskey_file", "", "The encrypted file of bls serialized private key by passphrase.")
blsFolder = flag.String("blsfolder", ".hmy/blskeys", "The folder that stores the bls keys and corresponding passphrases; e.g. <blskey>.key and <blskey>.pass; all bls keys mapped to same shard")
blsPass = flag.String("blspass", "", "The file containing passphrase to decrypt the encrypted bls file.")
Expand Down Expand Up @@ -144,6 +145,8 @@ var (
webHookYamlPath = flag.String(
"webhook_yaml", "", "path for yaml config reporting double signing",
)
// aws credentials
awsSettingString = ""
)

func initSetup() {
Expand All @@ -154,7 +157,9 @@ func initSetup() {
}

// maybe request passphrase for bls key.
passphraseForBLS()
if *cmkEncryptedBLSKey == "" {
passphraseForBLS()
}

// Configure log parameters
utils.SetLogContext(*port, *ip)
Expand Down Expand Up @@ -278,6 +283,8 @@ func setupStakingNodeAccount() error {
func readMultiBLSKeys(consensusMultiBLSPriKey *multibls.PrivateKey, consensusMultiBLSPubKey *multibls.PublicKey) error {
keyPasses := map[string]string{}
blsKeyFiles := []os.FileInfo{}
awsEncryptedBLSKeyFiles := []os.FileInfo{}

if err := filepath.Walk(*blsFolder, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
Expand All @@ -294,9 +301,11 @@ func readMultiBLSKeys(consensusMultiBLSPriKey *multibls.PrivateKey, consensusMul
}
name := fullName[:len(fullName)-len(ext)]
keyPasses[name] = passphrase
} else if ext == ".bls" {
awsEncryptedBLSKeyFiles = append(awsEncryptedBLSKeyFiles, info)
} else {
return errors.Errorf(
"[Multi-BLS] found file: %s that does not have .key or .pass file extension",
"[Multi-BLS] found file: %s that does not have .bls, .key or .pass file extension",
path,
)
}
Expand All @@ -309,29 +318,51 @@ func readMultiBLSKeys(consensusMultiBLSPriKey *multibls.PrivateKey, consensusMul
)
os.Exit(100)
}
if len(blsKeyFiles) > *maxBLSKeysPerNode {
fmt.Fprintf(os.Stderr,
"[Multi-BLS] maximum number of bls keys per node is %d, found: %d\n",
*maxBLSKeysPerNode,
len(blsKeyFiles),
)
os.Exit(100)
}
for _, blsKeyFile := range blsKeyFiles {
fullName := blsKeyFile.Name()
ext := filepath.Ext(fullName)
name := fullName[:len(fullName)-len(ext)]
if val, ok := keyPasses[name]; ok {
blsPassphrase = val

if len(awsEncryptedBLSKeyFiles) > 0 {
if len(awsEncryptedBLSKeyFiles) > *maxBLSKeysPerNode {
fmt.Fprintf(os.Stderr,
"[Multi-BLS] maximum number of bls keys per node is %d, found: %d\n",
*maxBLSKeysPerNode,
len(awsEncryptedBLSKeyFiles),
)
os.Exit(100)
}
blsKeyFilePath := path.Join(*blsFolder, blsKeyFile.Name())
consensusPriKey, err := blsgen.LoadBLSKeyWithPassPhrase(blsKeyFilePath, blsPassphrase)
if err != nil {
return err
for _, blsKeyFile := range awsEncryptedBLSKeyFiles {
blsKeyFilePath := path.Join(*blsFolder, blsKeyFile.Name())
consensusPriKey, err := blsgen.LoadAwsCMKEncryptedBLSKey(blsKeyFilePath, awsSettingString)
if err != nil {
return err
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved
}
// TODO: assumes order between public/private key pairs
multibls.AppendPriKey(consensusMultiBLSPriKey, consensusPriKey)
multibls.AppendPubKey(consensusMultiBLSPubKey, consensusPriKey.GetPublicKey())
}
} else {
if len(blsKeyFiles) > *maxBLSKeysPerNode {
fmt.Fprintf(os.Stderr,
"[Multi-BLS] maximum number of bls keys per node is %d, found: %d\n",
*maxBLSKeysPerNode,
len(blsKeyFiles),
)
os.Exit(100)
}
for _, blsKeyFile := range blsKeyFiles {
fullName := blsKeyFile.Name()
ext := filepath.Ext(fullName)
name := fullName[:len(fullName)-len(ext)]
if val, ok := keyPasses[name]; ok {
blsPassphrase = val
}
blsKeyFilePath := path.Join(*blsFolder, blsKeyFile.Name())
consensusPriKey, err := blsgen.LoadBLSKeyWithPassPhrase(blsKeyFilePath, blsPassphrase)
if err != nil {
return err
}
// TODO: assumes order between public/private key pairs
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved
multibls.AppendPriKey(consensusMultiBLSPriKey, consensusPriKey)
multibls.AppendPubKey(consensusMultiBLSPubKey, consensusPriKey.GetPublicKey())
}
// TODO: assumes order between public/private key pairs
multibls.AppendPriKey(consensusMultiBLSPriKey, consensusPriKey)
multibls.AppendPubKey(consensusMultiBLSPubKey, consensusPriKey.GetPublicKey())
}

return nil
Expand All @@ -347,6 +378,15 @@ func setupConsensusKey(nodeConfig *nodeconfig.ConfigType) multibls.PublicKey {
fmt.Fprintf(os.Stderr, "ERROR when loading bls key, err :%v\n", err)
os.Exit(100)
}
multibls.AppendPriKey(consensusMultiPriKey, consensusPriKey)
multibls.AppendPubKey(consensusMultiPubKey, consensusPriKey.GetPublicKey())
} else if *cmkEncryptedBLSKey != "" {
consensusPriKey, err := blsgen.LoadAwsCMKEncryptedBLSKey(*cmkEncryptedBLSKey, awsSettingString)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR when loading aws CMK encrypted bls key, err :%v\n", err)
os.Exit(100)
}

multibls.AppendPriKey(consensusMultiPriKey, consensusPriKey)
multibls.AppendPubKey(consensusMultiPubKey, consensusPriKey.GetPublicKey())
} else {
Expand Down Expand Up @@ -650,6 +690,9 @@ func main() {
// build time.
os.Setenv("GODEBUG", "netdns=go")

// Get aws credentials from prompt timeout 1 second if there's no input
awsSettingString, _ = blsgen.Readln(1 * time.Second)
fxfactorial marked this conversation as resolved.
Show resolved Hide resolved

flag.Var(&p2putils.BootNodes, "bootnodes", "a list of bootnode multiaddress (delimited by ,)")
flag.Parse()

Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/Workiva/go-datastructures v1.0.50
github.com/allegro/bigcache v1.2.1 // indirect
github.com/aristanetworks/goarista v0.0.0-20190607111240-52c2a7864a08 // indirect
github.com/aws/aws-sdk-go v1.30.1
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/cespare/cp v1.1.1
Expand Down Expand Up @@ -45,7 +46,7 @@ require (
github.com/multiformats/go-multiaddr-net v0.1.2
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/pborman/uuid v1.2.0
github.com/pkg/errors v0.8.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v0.9.3
github.com/prometheus/common v0.4.1 // indirect
github.com/prometheus/procfs v0.0.3 // indirect
Expand All @@ -55,7 +56,7 @@ require (
github.com/shirou/gopsutil v2.18.12+incompatible
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.6.1
github.com/stretchr/testify v1.4.0
github.com/stretchr/testify v1.5.1
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965
github.com/uber/jaeger-client-go v2.20.1+incompatible // indirect
github.com/uber/jaeger-lib v2.2.0+incompatible // indirect
Expand Down
91 changes: 91 additions & 0 deletions internal/blsgen/lib.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package blsgen

import (
"bufio"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
Expand All @@ -14,9 +17,19 @@ import (

ffi_bls "github.com/harmony-one/bls/ffi/go/bls"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/kms"
"github.com/harmony-one/harmony/crypto/bls"
)

type awsConfiguration struct {
AccessKey string `json:"aws_access_key_id"`
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved
SecretKey string `json:"aws_secret_access_key"`
Region string `json:"aws_region"`
}

func toISO8601(t time.Time) string {
var tz string
name, offset := t.Zone()
Expand Down Expand Up @@ -94,6 +107,84 @@ func LoadBLSKeyWithPassPhrase(fileName, passphrase string) (*ffi_bls.SecretKey,
return priKey, nil
}

// Readln reads aws configuratoin from prompt with a timeout
func Readln(timeout time.Duration) (string, error) {
s := make(chan string)
e := make(chan error)

go func() {
reader := bufio.NewReader(os.Stdin)
line, err := reader.ReadString('\n')
if err != nil {
e <- err
} else {
s <- line
}
close(s)
close(e)
}()

select {
case line := <-s:
return line, nil
case err := <-e:
return "", err
case <-time.After(timeout):
return "", errors.New("Timeout")
}
}

// LoadAwsCMKEncryptedBLSKey loads aws encrypted bls key.
func LoadAwsCMKEncryptedBLSKey(fileName, awsSettingString string) (*ffi_bls.SecretKey, error) {
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved
if awsSettingString == "" {
return nil, errors.New("aws credential is not set")
}

var awsConfig awsConfiguration
err := json.Unmarshal([]byte(awsSettingString), &awsConfig)
if err != nil {
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved
return nil, errors.New(awsSettingString + " is not a valid JSON string for setting aws configuration.")
}

// Initialize a session that the aws SDK uses to load
sess := session.Must(session.NewSessionWithOptions(session.Options{
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved
SharedConfigState: session.SharedConfigEnable,
}))

// Create KMS service client
svc := kms.New(sess, &aws.Config{
//Region: aws.String("us-east-1"),
Region: aws.String(awsConfig.Region),
Credentials: credentials.NewStaticCredentials(awsConfig.AccessKey, awsConfig.SecretKey, ""),
})

encryptedPrivateKeyBytes, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, err
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved
}

unhexed := make([]byte, hex.DecodedLen(len(encryptedPrivateKeyBytes)))
_, err = hex.Decode(unhexed, encryptedPrivateKeyBytes)
if err != nil {
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}

clearKey, err := svc.Decrypt(&kms.DecryptInput{
CiphertextBlob: unhexed,
})

if err != nil {
return nil, err
}

priKey := &ffi_bls.SecretKey{}
priKey.DeserializeHexStr(hex.EncodeToString(clearKey.Plaintext))
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved

fmt.Println(hex.EncodeToString(priKey.GetPublicKey().Serialize()))
coolcottontail marked this conversation as resolved.
Show resolved Hide resolved

return priKey, nil
}

func createHash(key string) string {
hasher := md5.New()
hasher.Write([]byte(key))
Expand Down