Skip to content

Commit

Permalink
Merge pull request #402 from perun-network/381_wire_authentication
Browse files Browse the repository at this point in the history
381 wire authentication
  • Loading branch information
NhoxxKienn authored Mar 11, 2024
2 parents d51ee68 + b238968 commit 61ed2dc
Show file tree
Hide file tree
Showing 36 changed files with 859 additions and 206 deletions.
5 changes: 5 additions & 0 deletions backend/sim/wire/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ func (acc *Account) Address() wire.Address {
return acc.addr
}

// Sign signs the given message with the account's private key.
func (acc *Account) Sign(msg []byte) ([]byte, error) {
return []byte("Authenticate"), nil
}

// NewRandomAccount generates a new random account.
func NewRandomAccount(rng *rand.Rand) *Account {
return &Account{
Expand Down
9 changes: 9 additions & 0 deletions backend/sim/wire/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package wire

import (
"bytes"
"errors"
"math/rand"

"perun.network/go-perun/wire"
Expand Down Expand Up @@ -62,6 +63,14 @@ func (a Address) Cmp(b wire.Address) int {
return bytes.Compare(a[:], bTyped[:])
}

// Verify verifies a signature.
func (a Address) Verify(msg, sig []byte) error {
if !bytes.Equal(sig, []byte("Authenticate")) {
return errors.New("invalid signature")
}
return nil
}

// NewRandomAddress returns a new random peer address.
func NewRandomAddress(rng *rand.Rand) *Address {
addr := Address{}
Expand Down
5 changes: 4 additions & 1 deletion backend/sim/wire/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package wire contains the implementation of the wire interfaces.
// Package wire is used for internal tests in the packages channel, wire, and client.
// Note that Account.Sign and Address.Verify are mock methods.
// We use the backend/wire/sim mock implementation for testing other go-perun functionalities.
// Our default wire.Account and wire.Address implementations can be found in wire/net/simple and are used for our applications.
package wire // import "perun.network/go-perun/backend/sim/wire"
2 changes: 1 addition & 1 deletion client/adjudicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func (c *Channel) Settle(ctx context.Context, secondary bool) (err error) {
// Withdraw.
err = c.withdraw(ctx, secondary)
if err != nil {
return
return err
}

// Set phase `Withdrawn`.
Expand Down
2 changes: 1 addition & 1 deletion client/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ func (c *Client) validTwoPartyProposal(
multiLedger := multi.IsMultiLedgerAssets(proposal.Base().InitBals.Assets)
appChannel := !channel.IsNoApp(proposal.Base().App)
if multiLedger && appChannel {
return fmt.Errorf("multi-ledger app channel not supported")
return errors.New("multi-ledger app channel not supported")
}

peers := c.proposalPeers(proposal)
Expand Down
3 changes: 2 additions & 1 deletion client/virtual_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,8 @@ func (c *Client) matchFundingProposal(ctx context.Context, a, b interface{}) boo
}

go func() {
err := virtual.watchVirtual() //nolint:contextcheck // The context will be derived from the channel context.
// The context will be derived from the channel context.
err := virtual.watchVirtual() //nolint:contextcheck
c.log.Debugf("channel %v: watcher stopped: %v", virtual.ID(), err)
}()
return true
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ require (
go.uber.org/goleak v1.1.11
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
google.golang.org/protobuf v1.23.0
google.golang.org/protobuf v1.32.0
polycry.pt/poly-go v0.0.0-20220222131629-aa4bdbaab60b
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/google/uuid v1.6.0
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
9 changes: 6 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
Expand Down Expand Up @@ -116,8 +117,10 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
Expand Down
4 changes: 2 additions & 2 deletions wallet/test/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ func TestAddress(t *testing.T, s *Setup) { //nolint:revive // `test.Test...` stu
// Test Address.String.
nullString := null.String()
addrString := addr.String()
assert.Greater(t, len(nullString), 0)
assert.Greater(t, len(addrString), 0)
assert.NotEmpty(t, nullString)
assert.NotEmpty(t, addrString)
assert.NotEqual(t, addrString, nullString)

// Test Address.Equals.
Expand Down
2 changes: 1 addition & 1 deletion wallet/test/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestAccountWithWalletAndBackend(t *testing.T, s *Setup) { //nolint:revive /
t.Error("Verification of invalid signature should produce error or return false")
}
// Expand the signature and check for error
// nolint:gocritic
//nolint:gocritic
tampered = append(sig, 0)
valid, err = s.Backend.VerifySignature(s.DataToSign, tampered, acc.Address())
if valid && err != nil {
Expand Down
6 changes: 3 additions & 3 deletions wallet/test/walletbench.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func GenericAccountBenchmark(b *testing.B, s *Setup) {
func benchAccountSign(b *testing.B, s *Setup) {
b.Helper()
perunAcc, err := s.Wallet.Unlock(s.AddressInWallet)
require.Nil(b, err)
require.NoError(b, err)

for n := 0; n < b.N; n++ {
_, err := perunAcc.SignData(s.DataToSign)
Expand All @@ -56,9 +56,9 @@ func benchBackendVerifySig(b *testing.B, s *Setup) {
// We dont want to measure the SignDataWithPW here, just need it for the verification
b.StopTimer()
perunAcc, err := s.Wallet.Unlock(s.AddressInWallet)
require.Nil(b, err)
require.NoError(b, err)
signature, err := perunAcc.SignData(s.DataToSign)
require.Nil(b, err)
require.NoError(b, err)
b.StartTimer()

for n := 0; n < b.N; n++ {
Expand Down
47 changes: 41 additions & 6 deletions wire/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package wire

import (
"encoding/binary"
"fmt"
"io"
)

Expand All @@ -31,31 +33,64 @@ func init() {
type Account interface {
// Address used by this account.
Address() Address

// Sign signs the given message with this account's private key.
Sign(msg []byte) ([]byte, error)
}

var _ Msg = (*AuthResponseMsg)(nil)

// AuthResponseMsg is the response message in the peer authentication protocol.
//
// This will be expanded later to contain signatures.
type AuthResponseMsg struct{}
type AuthResponseMsg struct {
Signature []byte
}

// Type returns AuthResponse.
func (m *AuthResponseMsg) Type() Type {
return AuthResponse
}

// Encode encodes this AuthResponseMsg into an io.Writer.
// It writes the signature to the writer.
func (m *AuthResponseMsg) Encode(w io.Writer) error {
return nil
// Write the length of the signature
err := binary.Write(w, binary.BigEndian, uint32(len(m.Signature)))
if err != nil {
return fmt.Errorf("failed to write signature length: %w", err)
}
// Write the signature itself
_, err = w.Write(m.Signature)
return err
}

// Decode decodes an AuthResponseMsg from an io.Reader.
// It reads the signature from the reader.
func (m *AuthResponseMsg) Decode(r io.Reader) (err error) {
// Read the length of the signature
var signatureLen uint32
if err := binary.Read(r, binary.BigEndian, &signatureLen); err != nil {
return fmt.Errorf("failed to read signature length: %w", err)
}
// Read the signature bytes
m.Signature = make([]byte, signatureLen)
if _, err := io.ReadFull(r, m.Signature); err != nil {
return fmt.Errorf("failed to read signature: %w", err)
}
return nil
}

// NewAuthResponseMsg creates an authentication response message.
func NewAuthResponseMsg(_ Account) Msg {
return &AuthResponseMsg{}
func NewAuthResponseMsg(acc Account) (Msg, error) {
addressBytes, err := acc.Address().MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal address: %w", err)
}
signature, err := acc.Sign(addressBytes)
if err != nil {
return nil, fmt.Errorf("failed to sign address: %w", err)
}

return &AuthResponseMsg{
Signature: signature,
}, nil
}
3 changes: 3 additions & 0 deletions wire/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type Address interface {
// Cmp compares the byte representation of two addresses. For `a.Cmp(b)`
// returns -1 if a < b, 0 if a == b, 1 if a > b.
Cmp(Address) int
// Verify verifies a message signature.
// It returns an error if the signature is invalid.
Verify(msg []byte, sig []byte) error
}

// Addresses is a helper type for encoding and decoding address slices in
Expand Down
2 changes: 1 addition & 1 deletion wire/cache_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestCache(t *testing.T) {
assert.Equal(2, c.Size())

empty := c.Messages(func(*Envelope) bool { return false })
assert.Len(empty, 0)
assert.Empty(empty)

c.Release(&isPing)
assert.False(c.Put(ping2), "Put into cache with canceled predicate")
Expand Down
31 changes: 29 additions & 2 deletions wire/net/exchange_addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,15 @@ func IsAuthenticationError(err error) bool {
func ExchangeAddrsActive(ctx context.Context, id wire.Account, peer wire.Address, conn Conn) error {
var err error
ok := pkg.TerminatesCtx(ctx, func() {
authMsg, err2 := wire.NewAuthResponseMsg(id)
if err2 != nil {
err = errors.WithMessage(err2, "creating auth message")
return
}
err = conn.Send(&wire.Envelope{
Sender: id.Address(),
Recipient: peer,
Msg: wire.NewAuthResponseMsg(id),
Msg: authMsg,
})
if err != nil {
err = errors.WithMessage(err, "sending message")
Expand All @@ -74,6 +79,8 @@ func ExchangeAddrsActive(ctx context.Context, id wire.Account, peer wire.Address
err = errors.WithMessage(err, "receiving message")
} else if _, ok := e.Msg.(*wire.AuthResponseMsg); !ok {
err = errors.Errorf("expected AuthResponse wire msg, got %v", e.Msg.Type())
} else if check := VerifyAddressSignature(peer, e.Msg.(*wire.AuthResponseMsg).Signature); check != nil {
err = errors.WithMessage(err, "verifying peer address's signature")
} else if !e.Recipient.Equal(id.Address()) &&
!e.Sender.Equal(peer) {
err = NewAuthenticationError(e.Sender, e.Recipient, id.Address(), "unmatched response sender or recipient")
Expand Down Expand Up @@ -101,14 +108,23 @@ func ExchangeAddrsPassive(ctx context.Context, id wire.Account, conn Conn) (wire
err = errors.Errorf("expected AuthResponse wire msg, got %v", e.Msg.Type())
} else if !e.Recipient.Equal(id.Address()) {
err = NewAuthenticationError(e.Sender, e.Recipient, id.Address(), "unmatched response sender or recipient")
} else if err = VerifyAddressSignature(e.Sender, e.Msg.(*wire.AuthResponseMsg).Signature); err != nil {
err = errors.WithMessage(err, "verifying peer address's signature")
}

if err != nil {
return
}

authMsg, err2 := wire.NewAuthResponseMsg(id)
if err2 != nil {
err = errors.WithMessage(err2, "creating auth message")
return
}
addr, err = e.Sender, conn.Send(&wire.Envelope{
Sender: id.Address(),
Recipient: e.Sender,
Msg: wire.NewAuthResponseMsg(id),
Msg: authMsg,
})
})

Expand All @@ -120,3 +136,14 @@ func ExchangeAddrsPassive(ctx context.Context, id wire.Account, conn Conn) (wire
}
return addr, err
}

// VerifyAddressSignature verifies a signature against the hash of an address.
// It relies on the MarshalBinary method of the provided wire.Address interface to generate the address hash.
// In case the MarshalBinary method doesn't produce the expected hash, the verification may fail.
func VerifyAddressSignature(addr wire.Address, sig []byte) error {
addressBytes, err := addr.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal address: %w", err)
}
return addr.Verify(addressBytes, sig)
}
40 changes: 31 additions & 9 deletions wire/net/simple/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,53 @@
package simple

import (
"crypto"
crypto_rand "crypto/rand"
"crypto/rsa"
"crypto/sha256"
"math/rand"

"github.com/pkg/errors"
"perun.network/go-perun/wire"
)

// Account is a wire account.
type Account struct {
addr wire.Address
}

// NewAccount creates a new account.
func NewAccount(addr *Address) *Account {
return &Account{
addr: addr,
}
addr wire.Address
privateKey *rsa.PrivateKey
}

// Address returns the account's address.
func (acc *Account) Address() wire.Address {
return acc.addr
}

// Sign signs the given message with the account's private key.
func (acc *Account) Sign(msg []byte) ([]byte, error) {
if acc.privateKey == nil {
return nil, errors.New("private key is nil")
}
hashed := sha256.Sum256(msg)
signature, err := rsa.SignPKCS1v15(crypto_rand.Reader, acc.privateKey, crypto.SHA256, hashed[:])
if err != nil {
return nil, err
}
return signature, nil
}

// NewRandomAccount generates a new random account.
func NewRandomAccount(rng *rand.Rand) *Account {
keySize := 2048
privateKey, err := rsa.GenerateKey(rng, keySize)
if err != nil {
panic(err)
}

address := NewRandomAddress(rng)
address.PublicKey = &privateKey.PublicKey

return &Account{
addr: NewRandomAddress(rng),
addr: address,
privateKey: privateKey,
}
}
Loading

0 comments on commit 61ed2dc

Please sign in to comment.