Skip to content

Commit

Permalink
Merge branch 'master' into feature/rest
Browse files Browse the repository at this point in the history
# Conflicts:
#	go.mod
#	go.sum
  • Loading branch information
Gregor Gololicic committed May 13, 2022
2 parents 0de71fc + d8021de commit dd7ebf3
Show file tree
Hide file tree
Showing 23 changed files with 353 additions and 144 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,12 @@ and is not limited to in-memory implementations.

```go
// construct a signer from your private key and configured hash algorithm
mySigner := crypto.NewInMemorySigner(myPrivateKey, myAccountKey.HashAlgo)
mySigner, err := crypto.NewInMemorySigner(myPrivateKey, myAccountKey.HashAlgo)
if err != nil {
panic("failed to create a signer")
}

err := tx.SignEnvelope(myAddress, myAccountKey.Index, mySigner)
err = tx.SignEnvelope(myAddress, myAccountKey.Index, mySigner)
if err != nil {
panic("failed to sign transaction")
}
Expand Down
7 changes: 5 additions & 2 deletions README_zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,12 @@ tx := flow.NewTransaction().

```go
// 通过你的私钥构造一个签名器,通过哈希算法完成签名
mySigner := crypto.NewInMemorySigner(myPrivateKey, myAccountKey.HashAlgo)
mySigner, err := crypto.NewInMemorySigner(myPrivateKey, myAccountKey.HashAlgo)
if err != nil {
panic("failed to create a signer")
}

err := tx.SignEnvelope(myAddress, myAccountKey.Index, mySigner)
err = tx.SignEnvelope(myAddress, myAccountKey.Index, mySigner)
if err != nil {
panic("failed to sign transaction")
}
Expand Down
15 changes: 11 additions & 4 deletions account.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,22 @@ func (a AccountKey) Encode() []byte {
return mustRLPEncode(&temp)
}

// accountCompatibleAlgorithms returns true if the signature and hash algorithms are a valid pair
// for a key on a Flow account.
func accountCompatibleAlgorithms(sigAlgo crypto.SignatureAlgorithm, hashAlgo crypto.HashAlgorithm) bool {
return (sigAlgo == crypto.ECDSA_P256 || sigAlgo == crypto.ECDSA_secp256k1) &&
(hashAlgo == crypto.SHA2_256 || hashAlgo == crypto.SHA3_256)
}

// Validate returns an error if this account key is invalid.
//
// An account key can be invalid for the following reasons:
// - It specifies an incompatible signature/hash algorithm pairing
// - It specifies a valid key weight
// - It specifies an incompatible signature/hash algorithm pair with regards to Flow accounts
// - It specifies an invalid key weight
func (a AccountKey) Validate() error {
if !crypto.CompatibleAlgorithms(a.SigAlgo, a.HashAlgo) {
if !accountCompatibleAlgorithms(a.SigAlgo, a.HashAlgo) {
return errors.Errorf(
"signing algorithm (%s) is incompatible with hashing algorithm (%s)",
"signing algorithm (%s) and hashing algorithm (%s) are not a valid pair for a Flow account key",
a.SigAlgo,
a.HashAlgo,
)
Expand Down
46 changes: 36 additions & 10 deletions account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package flow

import (
"crypto/rand"
"fmt"
"testing"

"github.com/onflow/flow-go-sdk/crypto"
Expand Down Expand Up @@ -77,18 +78,43 @@ func TestAccountKey(t *testing.T) {
assert.EqualError(t, key.Validate(), "invalid key weight: -1")
})

t.Run("Invalid Key Algorithm", func(t *testing.T) {
privateKey := generateKey()
key := AccountKey{
PublicKey: privateKey.PublicKey(),
t.Run("Key Algorithm", func(t *testing.T) {
hashAlgos := []crypto.HashAlgorithm{
crypto.UnknownHashAlgorithm,
crypto.SHA2_256,
crypto.SHA2_384,
crypto.SHA3_256,
crypto.SHA3_384,
crypto.Keccak256,
}
signAlgos := []crypto.SignatureAlgorithm{
crypto.UnknownSignatureAlgorithm,
crypto.ECDSA_P256,
crypto.ECDSA_secp256k1,
}

key.SetSigAlgo(privateKey.Algorithm())
assert.EqualError(t, key.Validate(), "signing algorithm (ECDSA_P256) is incompatible with hashing algorithm (UNKNOWN)")
validPairs := map[crypto.SignatureAlgorithm]map[crypto.HashAlgorithm]bool{
crypto.ECDSA_P256: map[crypto.HashAlgorithm]bool{
crypto.SHA2_256: true,
crypto.SHA3_256: true,
},
crypto.ECDSA_secp256k1: map[crypto.HashAlgorithm]bool{
crypto.SHA2_256: true,
crypto.SHA3_256: true,
},
}

key.SetHashAlgo(crypto.SHA3_256)
key.SetSigAlgo(crypto.UnknownSignatureAlgorithm)
assert.EqualError(t, key.Validate(), "signing algorithm (UNKNOWN) is incompatible with hashing algorithm (SHA3_256)")
key := AccountKey{}
for _, s := range signAlgos {
for _, h := range hashAlgos {
key.SetSigAlgo(s)
key.SetHashAlgo(h)
if validPairs[s][h] {
assert.NoError(t, key.Validate())
} else {
assert.EqualError(t, key.Validate(), fmt.Sprintf("signing algorithm (%s) and hashing algorithm (%s) are not a valid pair for a Flow account key", s, h))
}
}
}
})

}
21 changes: 13 additions & 8 deletions crypto/cloudkms/cloudkms.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func (k Key) ResourceID() string {
)
}

// KeyFromResourceID returns a `Key` from a resource ID.
func KeyFromResourceID(resourceID string) (Key, error) {
key := Key{}

Expand Down Expand Up @@ -103,7 +104,8 @@ func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error

// GetPublicKey fetches the public key portion of a KMS asymmetric signing key version.
//
// ECDSA_P256 is currently the only Flow signature algorithm supported by Google Cloud KMS.
// KMS keys of the type `CryptoKeyVersion_EC_SIGN_P256_SHA256` and `CryptoKeyVersion_EC_SIGN_SECP256K1_SHA256`
// are the only keys supported by the SDK.
//
// Ref: https://cloud.google.com/kms/docs/retrieve-public-key
func (c *Client) GetPublicKey(ctx context.Context, key Key) (crypto.PublicKey, crypto.HashAlgorithm, error) {
Expand Down Expand Up @@ -154,22 +156,25 @@ func (c *Client) KMSClient() *kms.KeyManagementClient {
return c.client
}

// ParseSignatureAlgorithm returns the `SignatureAlgorithm` corresponding to the input KMS key type.
func ParseSignatureAlgorithm(algo kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm) crypto.SignatureAlgorithm {
if algo == kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256 {
return crypto.ECDSA_P256
}

// TODO: update this once Google KMS API supports ECDSA_secp256k1
// https://github.com/onflow/flow-go-sdk/issues/193
return crypto.ECDSA_secp256k1
if algo == kmspb.CryptoKeyVersion_EC_SIGN_SECP256K1_SHA256 {
return crypto.ECDSA_secp256k1
}

return crypto.UnknownSignatureAlgorithm
}

// ParseHashAlgorithm returns the `HashAlgorithm` corresponding to the input KMS key type.
func ParseHashAlgorithm(algo kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm) crypto.HashAlgorithm {
if algo == kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256 {
if algo == kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256 || algo == kmspb.CryptoKeyVersion_EC_SIGN_SECP256K1_SHA256 {
return crypto.SHA2_256
}

// TODO: update this once Google KMS API supports ECDSA_secp256k1
// https://github.com/onflow/flow-go-sdk/issues/193
return crypto.SHA2_256
// the function can be extended to return SHA3-256 if it becomes supported by KMS.
return crypto.UnknownHashAlgorithm
}
81 changes: 81 additions & 0 deletions crypto/cloudkms/cloudkms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@
package cloudkms_test

import (
"context"
"fmt"
"os"
"os/exec"
"regexp"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/flow-go-sdk/crypto/cloudkms"
)

Expand All @@ -45,3 +51,78 @@ func TestKeyFromResourceID(t *testing.T) {

assert.Equal(t, key, keyFromResourceID)
}

// gcloudApplicationSignin signs in as an application user using gcloud command line tool
// currently assumes gcloud is already installed on the machine
// will by default pop a browser window to sign in
func gcloudApplicationSignin(kms cloudkms.Key) error {
googleAppCreds := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
if len(googleAppCreds) > 0 {
return nil
}

proj := kms.ProjectID
if len(proj) == 0 {
return fmt.Errorf(
"could not get GOOGLE_APPLICATION_CREDENTIALS, no google service account JSON provided but private key type is KMS",
)
}

loginCmd := exec.Command("gcloud", "auth", "application-default", "login", fmt.Sprintf("--project=%s", proj))

output, err := loginCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("Failed to run %q: %s\n", loginCmd.String(), err)
}

squareBracketRegex := regexp.MustCompile(`(?s)\[(.*)\]`)
regexResult := squareBracketRegex.FindAllStringSubmatch(string(output), -1)
// Should only be one value. Second index since first index contains the square brackets
googleApplicationCreds := regexResult[0][1]

os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", googleApplicationCreds)

return nil
}

// TestManualKMSSigning tests signing using a KMS key.
// This tests requires access to KMS and cannot be run by CI.
// Please use this test manually by commenting t.Skip(),
// when making any change to the KMS signing code.
func TestManualKMSSigning(t *testing.T) {
// to comment when testing manually
t.Skip()

// KMS_TEST_KEY_RESOURCE_ID is an env var containing the resource ID of a KMS key you
// have permissions to use.
id := os.Getenv(`KMS_TEST_KEY_RESOURCE_ID`)
fmt.Println(id)
key, err := cloudkms.KeyFromResourceID(id)
require.NoError(t, err)

// get google kms permission
err = gcloudApplicationSignin(key)
require.NoError(t, err)

// initialize the client
ctx := context.Background()
cl, err := cloudkms.NewClient(ctx)
require.NoError(t, err)

// Get the public key
pk, _, err := cl.GetPublicKey(ctx, key)
require.NoError(t, err)

// Sign
msg := []byte("random_message")
signer, err := cl.SignerForKey(ctx, key)
require.NoError(t, err)
sig, err := signer.Sign(msg)
require.NoError(t, err)

// verify
hasher := crypto.NewSHA2_256()
valid, err := pk.Verify(sig, msg, hasher)
require.NoError(t, err)
assert.True(t, valid)
}
Loading

0 comments on commit dd7ebf3

Please sign in to comment.