Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

381 wire authentication #402

Merged
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)
NhoxxKienn marked this conversation as resolved.
Show resolved Hide resolved
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
Loading