frostfs: Fix invalid signatures issued by key from json
Ephemeral keys worked fine while keys loaded from filesystem would generate invalid signatures. This was caused by destroying private key material during calls to Wallet.Close() and Account.Close(). Since these calls do nothing except wiping the private key, we omit them now. Responsibility for private key security is delegated to caller of getKey() Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
This commit is contained in:
parent
254983fbe2
commit
9ff9d5be25
2 changed files with 88 additions and 16 deletions
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
@ -73,6 +74,9 @@ func (s *Storage) Save(ctx context.Context, data []byte, attr ...string) (oid st
|
|||
if err != nil {
|
||||
return "", fmt.Errorf("signing object: %w", err)
|
||||
}
|
||||
if !obj.VerifyIDSignature() {
|
||||
return "", errors.New("signing object: invalid signature was generated")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, storageRequestTimeout)
|
||||
defer cancel()
|
||||
|
@ -163,19 +167,21 @@ func keyval2attrs(attr ...string) ([]object.Attribute, error) {
|
|||
}
|
||||
|
||||
// Load private key from wallet file.
|
||||
func getKey(walletPath, walletAccount, walletPassword string) (*ecdsa.PrivateKey, error) {
|
||||
func getKey(walletPath, walletAccount, walletPassword string) (*ecdsa.PrivateKey, error) { //nolint:gocyclo
|
||||
if walletPath == "" {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // TODO: using ephemeral keys for now, later read from env vars
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generating ephemeral key: %w", err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// This function intentionally omits calls to w.Close() and account.Close()
|
||||
// because that would destroy the underlying ecdsa.PrivateKey.
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer w.Close()
|
||||
if len(w.Accounts) == 0 {
|
||||
return nil, fmt.Errorf("no accounts in wallet: %s", walletPath)
|
||||
}
|
||||
|
@ -185,6 +191,12 @@ func getKey(walletPath, walletAccount, walletPassword string) (*ecdsa.PrivateKey
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid account address: %w", err)
|
||||
}
|
||||
if len(decode) != 21 {
|
||||
return nil, fmt.Errorf("invalid account address length: %d bytes", len(decode))
|
||||
}
|
||||
if decode[0] != 0x35 {
|
||||
return nil, fmt.Errorf("invalid account address first byte: %s -> %#x", walletAccount, decode[0])
|
||||
}
|
||||
hash, err := util.Uint160DecodeBytesBE(decode[1:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid account hash: %w", err)
|
||||
|
@ -194,10 +206,11 @@ func getKey(walletPath, walletAccount, walletPassword string) (*ecdsa.PrivateKey
|
|||
return nil, fmt.Errorf("account not found: %s", walletAccount)
|
||||
}
|
||||
}
|
||||
defer account.Close()
|
||||
err = account.Decrypt(walletPassword, w.Scrypt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt wallet: %w", err)
|
||||
if account.PrivateKey() == nil {
|
||||
err = account.Decrypt(walletPassword, w.Scrypt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt wallet: %w", err)
|
||||
}
|
||||
}
|
||||
key := account.PrivateKey().PrivateKey
|
||||
return &key, nil
|
||||
|
|
|
@ -3,27 +3,32 @@ package frostfs
|
|||
// Tests for our FrostFS client code
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"crypto/sha256"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
// Initialize storage backend for tests.
|
||||
func openStorage(t *testing.T) *Storage {
|
||||
t.Helper()
|
||||
cid := os.Getenv("FROSTFS_CID") // sample: "348WWfBKbS79Wbmm38MRE3oBoEDM5Ga1XXbGKGNyisDM"
|
||||
cid := os.Getenv("FROSTFS_CONTAINER") // sample: "348WWfBKbS79Wbmm38MRE3oBoEDM5Ga1XXbGKGNyisDM"
|
||||
endpoint := os.Getenv("FROSTFS_ENDPOINT") // sample: "grpc://localhost:8802"
|
||||
if cid == "" || endpoint == "" {
|
||||
t.Skipf("one or more environment variables not set: FROSTFS_ENDPOINT, FROSTFS_CID")
|
||||
t.Skipf("one or more environment variables not set: FROSTFS_ENDPOINT, FROSTFS_CONTAINER")
|
||||
}
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // TODO: using ephemeral keys for now, later read from env vars
|
||||
key, err := getKey(
|
||||
os.Getenv("FROSTFS_WALLET"),
|
||||
os.Getenv("FROSTFS_WALLET_ACCOUNT"),
|
||||
os.Getenv("FROSTFS_WALLET_PASSWORD"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -76,7 +81,8 @@ func TestMulipleObjects(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
if obj != baseline {
|
||||
panic(fmt.Errorf("non-identical object id: %s != %s", baseline, obj))
|
||||
t.Errorf("non-identical object id: %s != %s", baseline, obj)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,11 +96,18 @@ func TestMulipleObjects(t *testing.T) {
|
|||
defer wg.Done()
|
||||
oid, err := storage.Save(ctx, payload, "FileName", "CAS.txt", "Tag", "test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
err = storage.Delete(ctx, oid)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case objects <- oid:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
t.Log(oid)
|
||||
}()
|
||||
|
@ -129,3 +142,49 @@ func key2addr(k *ecdsa.PrivateKey) string {
|
|||
user.IDFromKey(&owner, k.PublicKey)
|
||||
return owner.String()
|
||||
}
|
||||
|
||||
// Check that loaded wallet key generates valid signature.
|
||||
func TestLoadWalletAndSign(t *testing.T) {
|
||||
const (
|
||||
walletPath = "client_test_wallet.json"
|
||||
walletAccount = "NWZnjbTKbzwtX6w1q5R3kbEKrnJ5bp1kn7"
|
||||
walletAccountPassword = ""
|
||||
walletAccountIndex = 0
|
||||
dataString = "some data to sign in this test"
|
||||
)
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
if err != nil {
|
||||
t.Fatalf("wallet from file: %v", err)
|
||||
}
|
||||
accountFromWallet := w.Accounts[walletAccountIndex]
|
||||
err = accountFromWallet.Decrypt(walletAccountPassword, w.Scrypt)
|
||||
if err != nil {
|
||||
t.Fatalf("wallet decrypt: %v", err)
|
||||
}
|
||||
|
||||
key, err := getKey(walletPath, walletAccount, walletAccountPassword)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wrappedKey := &keys.PrivateKey{PrivateKey: *key}
|
||||
accountFromKey := wallet.NewAccountFromPrivateKey(wrappedKey)
|
||||
|
||||
if !accountFromKey.PublicKey().Equal(accountFromWallet.PublicKey()) {
|
||||
t.Fatalf("corrupted key: want %s, got %s", accountFromWallet.PublicKey().Address(), accountFromKey.PublicKey().Address())
|
||||
}
|
||||
data := []byte(dataString)
|
||||
sig := accountFromKey.PrivateKey().Sign(data)
|
||||
hash := sha256.Sum256(data)
|
||||
hashSig := accountFromKey.PrivateKey().SignHash(hash)
|
||||
if !bytes.Equal(sig, hashSig) {
|
||||
t.Fatalf("different signatures for data (%x) and for hash (%x)", sig, hashSig)
|
||||
}
|
||||
ok := accountFromKey.PublicKey().Verify(sig, hash[:])
|
||||
if !ok {
|
||||
t.Errorf("signature %x (%d bytes): check failed: signing key", sig, len(sig))
|
||||
}
|
||||
ok = accountFromWallet.PublicKey().Verify(sig, hash[:])
|
||||
if !ok {
|
||||
t.Errorf("signature %x (%d bytes): check failed: wallet key", sig, len(sig))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue