WIP: HTTP-01 solver that stores challenge tokens in FrostFS #4

Draft
potyarkin wants to merge 9 commits from potyarkin/lego:feature/http-01-frostfs-solver into tcl/http-01-frostfs-solver
2 changed files with 88 additions and 16 deletions
Showing only changes of commit 9ff9d5be25 - Show all commits

View file

@ -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

View file

@ -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))
}
}