/
gcp.go
151 lines (135 loc) · 4.52 KB
/
gcp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package ethkms
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"math/big"
kms "cloud.google.com/go/kms/apiv1"
"cloud.google.com/go/kms/apiv1/kmspb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"google.golang.org/api/option"
)
// NewGCP constructs an EVM signer backed by the specified GCP KMS key, which
// MUST be a secp256k1-sha256 EC signing key. The SHA256 variant is the only
// secp256k1 signing algorithm supported by GCP KMS, but as it accepts already-
// hashed digests, we simply substitute SHA256 with KECCAK256 as they have the
// same bit length.
//
// Any ClientOptions are propagated to the constructor for the
// KeyManagementClient backing the returned signer.
func NewGCP(ctx context.Context, key string, chainID *big.Int, opts ...option.ClientOption) (*GCP, error) {
client, err := kms.NewKeyManagementClient(ctx, opts...)
if err != nil {
return nil, fmt.Errorf("kms.NewKeyManagementClient(…): %v", err)
}
req := &kmspb.GetPublicKeyRequest{Name: key}
pub, err := client.GetPublicKey(ctx, req)
if err != nil {
return nil, fmt.Errorf("%T.GetPublicKey(ctx, %+v): %v", client, req, err)
}
if got, want := pub.Algorithm, kmspb.CryptoKeyVersion_EC_SIGN_SECP256K1_SHA256; got != want {
return nil, fmt.Errorf("pubkey %q for algorithm %v; MUST be %v", key, got, want)
}
block, _ := pem.Decode([]byte(pub.Pem)) // _ = rest []byte, not an unhandled error
if block == nil {
return nil, fmt.Errorf("pem.Decode(%T.Pem) returned nil %T", pub, block)
}
var info struct {
Raw asn1.RawContent
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
if rest, err := asn1.Unmarshal(block.Bytes, &info); err != nil || len(rest) > 0 {
return nil, fmt.Errorf("asn1.Unmarshal(%#x, %T): %d leftover bytes; err=%v", block.Bytes, info, len(rest), err)
}
if !info.Algorithm.Algorithm.Equal(oidECDSAPubKey) {
return nil, errors.New("not an ECDSA public key")
}
x, y := elliptic.Unmarshal(crypto.S256(), info.PublicKey.RightAlign())
pubKey := ecdsa.PublicKey{
Curve: crypto.S256(),
X: x,
Y: y,
}
return &GCP{
client: client,
keyID: key,
pubKey: pubKey,
// The London signer falls back to older signers based on the tx type,
// so is safe to use as a catch-all.
signer: types.NewLondonSigner(chainID),
}, nil
}
// GCP instances are EVM signers backed by Google Cloud's key-management
// service.
type GCP struct {
client *kms.KeyManagementClient
keyID string
pubKey ecdsa.PublicKey
signer types.Signer
}
// Close closes the GCP connection.
func (g *GCP) Close() error {
return g.client.Close()
}
// Address returns the Ethereum address controlled by the signer's private key.
func (g *GCP) Address() common.Address {
return crypto.PubkeyToAddress(g.pubKey)
}
// SignTx returns tx, signed by the GCP KMS.
func (g *GCP) SignTx(ctx context.Context, tx *types.Transaction) (*types.Transaction, error) {
req := &kmspb.AsymmetricSignRequest{
Name: g.keyID,
// GCP accepts pre-hashed message digests for signing, expecting them to
// be from one of a limited set of hash functions. We piggyback on the
// SHA256 option as it has the same number of bits so won't result in an
// error.
Digest: &kmspb.Digest{
Digest: &kmspb.Digest_Sha256{
Sha256: g.signer.Hash(tx).Bytes(),
},
},
}
resp, err := g.client.AsymmetricSign(ctx, req)
if err != nil {
return nil, fmt.Errorf("%T.AsymmetricSign(%+v): %v", g.client, req, err)
}
var parsed struct{ R, S *big.Int }
if rest, err := asn1.Unmarshal(resp.Signature, &parsed); err != nil || len(rest) > 0 {
return nil, fmt.Errorf("asn1.Unmarshal([sig from GCP KMS], %T): %d bytes left; err=%v", parsed, len(rest), err)
}
// EIP-2 limits signatures to one half of the curve
order := curveOrder()
halfN := new(big.Int).Rsh(order, 1)
if parsed.S.Cmp(halfN) == 1 {
parsed.S.Sub(order, parsed.S)
}
sig := make([]byte, 65)
copy(sig[:32], parsed.R.Bytes())
copy(sig[32:64], parsed.S.Bytes())
addr := g.Address()
// The parity depends on a random value that we don't have access to, so
// trial-ane-error is the only feasible approach.
for _, v := range []byte{0, 1} {
sig[64] = v
signed, err := tx.WithSignature(g.signer, sig)
if err != nil {
return nil, err
}
sender, err := types.Sender(g.signer, signed)
if err != nil {
return nil, err
}
if sender == addr {
return signed, nil
}
}
return nil, fmt.Errorf("signature doesn't match expected sender address %v", addr)
}