parent
50f7a4116d
commit
fc0d1e11e0
@ -0,0 +1,449 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"crypto/aes" |
||||||
|
"crypto/cipher" |
||||||
|
"crypto/md5" |
||||||
|
"encoding/hex" |
||||||
|
"encoding/json" |
||||||
|
"flag" |
||||||
|
"github.com/harmony-one/harmony/crypto/bls" |
||||||
|
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/pkg/errors" |
||||||
|
"io" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
type AwsConfiguration struct { |
||||||
|
AccessKey string `json:"aws_access_key_id"` |
||||||
|
SecretKey string `json:"aws_secret_access_key"` |
||||||
|
Region string `json:"aws_region"` |
||||||
|
} |
||||||
|
|
||||||
|
func readline(prompt string, timeout time.Duration) (string, error) { |
||||||
|
s := make(chan string) |
||||||
|
e := make(chan error) |
||||||
|
|
||||||
|
go func() { |
||||||
|
fmt.Print(prompt) |
||||||
|
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") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func writeToFile(filename string, data string) error { |
||||||
|
file, err := os.Create(filename) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer file.Close() |
||||||
|
_, err = io.WriteString(file, data) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return file.Sync() |
||||||
|
} |
||||||
|
|
||||||
|
func readAllAsString(r io.Reader) (data string, err error) { |
||||||
|
bytes, err := ioutil.ReadAll(r) |
||||||
|
return string(bytes), err |
||||||
|
} |
||||||
|
|
||||||
|
func getPassphraseFromSource(src string) (pass string, err error) { |
||||||
|
switch src { |
||||||
|
case "stdin": |
||||||
|
return readAllAsString(os.Stdin) |
||||||
|
} |
||||||
|
methodArg := strings.SplitN(src, ":", 2) |
||||||
|
if len(methodArg) < 2 { |
||||||
|
return "", errors.Errorf("invalid passphrase reading method %#v", src) |
||||||
|
} |
||||||
|
method := methodArg[0] |
||||||
|
arg := methodArg[1] |
||||||
|
switch method { |
||||||
|
case "pass": |
||||||
|
return arg, nil |
||||||
|
case "env": |
||||||
|
pass, ok := os.LookupEnv(arg) |
||||||
|
if !ok { |
||||||
|
return "", errors.Errorf("environment variable %#v undefined", arg) |
||||||
|
} |
||||||
|
return pass, nil |
||||||
|
case "file": |
||||||
|
f, err := os.Open(arg) |
||||||
|
if err != nil { |
||||||
|
return "", errors.Wrapf(err, "cannot open file %#v", arg) |
||||||
|
} |
||||||
|
defer func() { _ = f.Close() }() |
||||||
|
return readAllAsString(f) |
||||||
|
case "fd": |
||||||
|
fd, err := strconv.ParseUint(arg, 10, 0) |
||||||
|
if err != nil { |
||||||
|
return "", errors.Wrapf(err, "invalid fd literal %#v", arg) |
||||||
|
} |
||||||
|
f := os.NewFile(uintptr(fd), "(passphrase-source)") |
||||||
|
if f == nil { |
||||||
|
return "", errors.Errorf("cannot open fd %#v", fd) |
||||||
|
} |
||||||
|
defer func() { _ = f.Close() }() |
||||||
|
return readAllAsString(f) |
||||||
|
} |
||||||
|
return "", errors.Errorf("invalid passphrase reading method %#v", method) |
||||||
|
} |
||||||
|
|
||||||
|
func createHash(key string) string { |
||||||
|
hasher := md5.New() |
||||||
|
hasher.Write([]byte(key)) |
||||||
|
return hex.EncodeToString(hasher.Sum(nil)) |
||||||
|
} |
||||||
|
|
||||||
|
func decryptRaw(data []byte, passphrase string) ([]byte, error) { |
||||||
|
var err error |
||||||
|
key := []byte(createHash(passphrase)) |
||||||
|
block, err := aes.NewCipher(key) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
gcm, err := cipher.NewGCM(block) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
nonceSize := gcm.NonceSize() |
||||||
|
nonce, ciphertext := data[:nonceSize], data[nonceSize:] |
||||||
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) |
||||||
|
return plaintext, err |
||||||
|
} |
||||||
|
|
||||||
|
func decrypt(encrypted []byte, passphrase string) (decrypted []byte, err error) { |
||||||
|
unhexed := make([]byte, hex.DecodedLen(len(encrypted))) |
||||||
|
if _, err = hex.Decode(unhexed, encrypted); err == nil { |
||||||
|
if decrypted, err = decryptRaw(unhexed, passphrase); err == nil { |
||||||
|
return decrypted, nil |
||||||
|
} |
||||||
|
} |
||||||
|
// At this point err != nil, either from hex decode or from decryptRaw.
|
||||||
|
decrypted, binErr := decryptRaw(encrypted, passphrase) |
||||||
|
if binErr != nil { |
||||||
|
// Disregard binary decryption error and return the original error,
|
||||||
|
// because our canonical form is hex and not binary.
|
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return decrypted, nil |
||||||
|
} |
||||||
|
|
||||||
|
// LoadBlsKeyWithPassPhrase loads bls key with passphrase.
|
||||||
|
func loadBlsKeyWithPassPhrase(fileName, passphrase string) (*ffi_bls.SecretKey, error) { |
||||||
|
encryptedPrivateKeyBytes, err := ioutil.ReadFile(fileName) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
for len(passphrase) > 0 && passphrase[len(passphrase)-1] == '\n' { |
||||||
|
passphrase = passphrase[:len(passphrase)-1] |
||||||
|
} |
||||||
|
decryptedBytes, err := decrypt(encryptedPrivateKeyBytes, passphrase) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
priKey := &ffi_bls.SecretKey{} |
||||||
|
priKey.DeserializeHexStr(string(decryptedBytes)) |
||||||
|
return priKey, nil |
||||||
|
} |
||||||
|
|
||||||
|
func setupAwsService() *kms.KMS { |
||||||
|
var envJSON string |
||||||
|
envSettingString, err := readline(envJSON, 1 * time.Second); |
||||||
|
var awsConfig AwsConfiguration |
||||||
|
|
||||||
|
if (err == nil) { |
||||||
|
err := json.Unmarshal([]byte(envSettingString), &awsConfig) |
||||||
|
if err != nil { |
||||||
|
fmt.Println(envSettingString, " is not a valid JSON string for setting aws configuration.") |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sess := session.Must(session.NewSessionWithOptions(session.Options{ |
||||||
|
SharedConfigState: session.SharedConfigEnable, |
||||||
|
})) |
||||||
|
|
||||||
|
svc := kms.New(sess, &aws.Config{ |
||||||
|
Region: aws.String(awsConfig.Region), |
||||||
|
Credentials: credentials.NewStaticCredentials(awsConfig.AccessKey, awsConfig.SecretKey, ""), |
||||||
|
}) |
||||||
|
|
||||||
|
return svc |
||||||
|
} |
||||||
|
|
||||||
|
func printHeader() { |
||||||
|
fmt.Println("\nBLS key utility for the Harmony blockchain") |
||||||
|
fmt.Println("Usage:") |
||||||
|
fmt.Println(os.Args[0], "[command]\n") |
||||||
|
fmt.Println("Available Commands:") |
||||||
|
fmt.Println("generate\tgenerate a new BLS key and encrypt it with aws CMK key") |
||||||
|
fmt.Println("convert\t\tconvert the legacy BLS key file to new aws CMK encrypted BLS key file") |
||||||
|
fmt.Println("rotate\t\tdecrypt and re-encrypt BLS key using new aws CMK key ID") |
||||||
|
fmt.Println("pubkey\t\tdisplay the public key of a BLS key file\n") |
||||||
|
fmt.Println("command arguments:") |
||||||
|
} |
||||||
|
|
||||||
|
func generateBlsKey(keyId string) { |
||||||
|
svc := setupAwsService() |
||||||
|
|
||||||
|
privateKey := bls.RandPrivateKey() |
||||||
|
publicKey := privateKey.GetPublicKey() |
||||||
|
publicKeyHex := publicKey.SerializeToHexStr() |
||||||
|
|
||||||
|
// Encrypt the data
|
||||||
|
result, err := svc.Encrypt(&kms.EncryptInput{ |
||||||
|
KeyId: aws.String(keyId), |
||||||
|
Plaintext: privateKey.Serialize(), |
||||||
|
}) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
fmt.Println("Got error encrypting data: ", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
var filePath string |
||||||
|
if filePath == "" { |
||||||
|
cwd, _ := os.Getwd() |
||||||
|
filePath = fmt.Sprintf("%s/%s.bls", cwd, publicKeyHex) |
||||||
|
} |
||||||
|
err = writeToFile(filePath, hex.EncodeToString(result.CiphertextBlob)) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Error creating the new bls file : %s ", err) |
||||||
|
os.Exit(1) |
||||||
|
} else { |
||||||
|
fmt.Println("Successfully created a new bls key file ", filePath, " using key id ", keyId) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func rotateBlsKey(blsKeyFileOld, blsKeyFileNew, keyId string) { |
||||||
|
svc := setupAwsService() |
||||||
|
|
||||||
|
encryptedPrivateKeyBytes, err := ioutil.ReadFile(blsKeyFileOld) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Got error %s reading file %s ", err, blsKeyFileOld) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
unhexed := make([]byte, hex.DecodedLen(len(encryptedPrivateKeyBytes))) |
||||||
|
_, err = hex.Decode(unhexed, encryptedPrivateKeyBytes) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Got error decoding BLS key data: ", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
reEncrypted, err := svc.ReEncrypt(&kms.ReEncryptInput{CiphertextBlob: unhexed, DestinationKeyId: &keyId}) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Got error re-encrypting data: ", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
err = writeToFile(blsKeyFileNew, hex.EncodeToString(reEncrypted.CiphertextBlob)) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Error creating the new bls file : %s ", err) |
||||||
|
os.Exit(1) |
||||||
|
} else { |
||||||
|
fmt.Println("Successfully created a new bls key file ", blsKeyFileNew, " using key id ", keyId) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func displayPublicKey(blsKeyFile string) { |
||||||
|
svc := setupAwsService() |
||||||
|
|
||||||
|
encryptedPrivateKeyBytes, err := ioutil.ReadFile(blsKeyFile) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Got error %s reading file %s ", err, blsKeyFile) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
unhexed := make([]byte, hex.DecodedLen(len(encryptedPrivateKeyBytes))) |
||||||
|
_, err = hex.Decode(unhexed, encryptedPrivateKeyBytes) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Got error decoding BLS key data: ", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
clearKey, err := svc.Decrypt(&kms.DecryptInput{ |
||||||
|
CiphertextBlob: unhexed, |
||||||
|
}) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
fmt.Println("Got error re-encrypting data: ", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
priKey := &ffi_bls.SecretKey{} |
||||||
|
priKey.DeserializeHexStr(hex.EncodeToString(clearKey.Plaintext)) |
||||||
|
|
||||||
|
fmt.Println("\nThe BLS public key from file", blsKeyFile, "is ") |
||||||
|
fmt.Println(hex.EncodeToString(priKey.GetPublicKey().Serialize())) |
||||||
|
} |
||||||
|
|
||||||
|
func convertOldBlsKeyFile(legacyBlsKeyFile, blsPass, newBlsKeyFile, keyId string) { |
||||||
|
svc := setupAwsService() |
||||||
|
|
||||||
|
var privateKey *ffi_bls.SecretKey |
||||||
|
if legacyBlsKeyFile != "" { |
||||||
|
if blsPass == "" { |
||||||
|
fmt.Println("Needs blspass to decrypt blskey") |
||||||
|
os.Exit(101) |
||||||
|
} |
||||||
|
passphrase, err := getPassphraseFromSource(blsPass) |
||||||
|
if err != nil { |
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "ERROR when reading passphrase file: %v\n", err) |
||||||
|
os.Exit(100) |
||||||
|
} |
||||||
|
privateKey, err = loadBlsKeyWithPassPhrase(legacyBlsKeyFile, passphrase) |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, |
||||||
|
"failed to load legacy bls key file %s, error = %s\n", |
||||||
|
legacyBlsKeyFile, err) |
||||||
|
os.Exit(100) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
result, err := svc.Encrypt(&kms.EncryptInput{ |
||||||
|
KeyId: aws.String(keyId), |
||||||
|
Plaintext: privateKey.Serialize(), |
||||||
|
}) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
fmt.Println("Got error encrypting data: ", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
err = writeToFile(newBlsKeyFile, hex.EncodeToString(result.CiphertextBlob)) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Error creating the new bls file : %s ", err) |
||||||
|
os.Exit(1) |
||||||
|
} else { |
||||||
|
fmt.Println("Successfully created a new bls key file ", newBlsKeyFile, " using key id ", keyId) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
func main() { |
||||||
|
generateCommand := flag.NewFlagSet("generate", flag.ExitOnError) |
||||||
|
rotateCommand := flag.NewFlagSet("rotate", flag.ExitOnError) |
||||||
|
convertCommand := flag.NewFlagSet("convert", flag.ExitOnError) |
||||||
|
pubkeyCommand := flag.NewFlagSet("pubkey", flag.ExitOnError) |
||||||
|
|
||||||
|
generateCmdKeyId := generateCommand.String("enc_key_id", "", "The aws CMK key Id used for encrypting new bls key file. (Required)") |
||||||
|
|
||||||
|
rotateCmdBlsOld := rotateCommand.String("old_blskey_file", "", "The old aws CMK encrypted bls private key file. (Required)") |
||||||
|
rotateCmdBlsNew := rotateCommand.String("new_blskey_file", "", "The new aws CMK encrypted bls private key file. (Required)") |
||||||
|
rotateCmdKeyId := rotateCommand.String("new_key_id", "", "The aws CMK key Id used for encrypting new bls key file. (Required)") |
||||||
|
|
||||||
|
convertCmdBlsOld := convertCommand.String("legacy_blskey_file", "", "The legacy encrypted file of bls serialized private key by passphrase. (Required)") |
||||||
|
convertCmdBlsNew := convertCommand.String("cms_blskey_file", "", "The new aws CMK encrypted bls private key file. (Required)") |
||||||
|
convertCmdBlsPass:= convertCommand.String("blspass", "", "The passphrase to decrypt the encrypted bls file. i.e. file:<file_name>, pass:<string>, env:<key>") |
||||||
|
convertCmdKeyId := convertCommand.String("key_id", "", "The aws CMK key Id used for encrypting bls key file. (Required)") |
||||||
|
|
||||||
|
pubkeyCmdBlsKey := pubkeyCommand.String("blskey_file", "", "The aws CMK encrypted bls private key file . (Required)") |
||||||
|
|
||||||
|
if len(os.Args) < 2 { |
||||||
|
printHeader() |
||||||
|
fmt.Println("generate:") |
||||||
|
generateCommand.PrintDefaults() |
||||||
|
fmt.Println("convert:") |
||||||
|
convertCommand.PrintDefaults() |
||||||
|
fmt.Println("roate:") |
||||||
|
rotateCommand.PrintDefaults() |
||||||
|
fmt.Println("pubkey:") |
||||||
|
pubkeyCommand.PrintDefaults() |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
//flag.Parse()
|
||||||
|
switch os.Args[1] { |
||||||
|
case "generate": |
||||||
|
generateCommand.Parse(os.Args[2:]) |
||||||
|
case "rotate": |
||||||
|
rotateCommand.Parse(os.Args[2:]) |
||||||
|
case "convert": |
||||||
|
convertCommand.Parse(os.Args[2:]) |
||||||
|
case "pubkey": |
||||||
|
pubkeyCommand.Parse(os.Args[2:]) |
||||||
|
default: |
||||||
|
printHeader() |
||||||
|
fmt.Println("generate") |
||||||
|
generateCommand.PrintDefaults() |
||||||
|
fmt.Println("convert") |
||||||
|
convertCommand.PrintDefaults() |
||||||
|
fmt.Println("roate") |
||||||
|
rotateCommand.PrintDefaults() |
||||||
|
fmt.Println("pubkey") |
||||||
|
pubkeyCommand.PrintDefaults() |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
if generateCommand.Parsed() { |
||||||
|
// Required Flags
|
||||||
|
if *generateCmdKeyId == "" { |
||||||
|
generateCommand.PrintDefaults() |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
generateBlsKey(*generateCmdKeyId) |
||||||
|
} |
||||||
|
|
||||||
|
if rotateCommand.Parsed() { |
||||||
|
// Required Flags
|
||||||
|
if *rotateCmdBlsOld == "" || *rotateCmdBlsNew == "" || *rotateCmdBlsNew == "" { |
||||||
|
rotateCommand.PrintDefaults() |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
rotateBlsKey(*rotateCmdBlsOld, *rotateCmdBlsNew, *rotateCmdKeyId) |
||||||
|
} |
||||||
|
|
||||||
|
if convertCommand.Parsed() { |
||||||
|
// Required Flags
|
||||||
|
if *convertCmdBlsOld == "" || *convertCmdBlsNew == "" || *convertCmdKeyId == "" { |
||||||
|
convertCommand.PrintDefaults() |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
convertOldBlsKeyFile(*convertCmdBlsOld, *convertCmdBlsPass, *convertCmdBlsNew, *convertCmdKeyId) |
||||||
|
} |
||||||
|
|
||||||
|
if pubkeyCommand.Parsed() { |
||||||
|
// Required Flags
|
||||||
|
if *pubkeyCmdBlsKey == "" { |
||||||
|
pubkeyCommand.PrintDefaults() |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
displayPublicKey(*pubkeyCmdBlsKey) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue