]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Support plaintext crypto method for protocol header encryption
authorMatt Joiner <anacrolix@gmail.com>
Wed, 13 Sep 2017 08:20:20 +0000 (18:20 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Wed, 13 Sep 2017 08:20:20 +0000 (18:20 +1000)
client.go
cmd/torrent/main.go
config.go
connection.go
mse/mse.go
mse/mse_test.go

index 41fa89e289561008198d600d9530d444b90aeb3f..b87ce4ea6cd139f139e165ac1b2a736c75dfc9fb 100644 (file)
--- a/client.go
+++ b/client.go
@@ -586,9 +586,9 @@ func (cl *Client) noLongerHalfOpen(t *Torrent, addr string) {
 
 // Performs initiator handshakes and returns a connection. Returns nil
 // *connection if no connection for valid reasons.
-func (cl *Client) handshakesConnection(ctx context.Context, nc net.Conn, t *Torrent, encrypted, utp bool) (c *connection, err error) {
+func (cl *Client) handshakesConnection(ctx context.Context, nc net.Conn, t *Torrent, encryptHeader, utp bool) (c *connection, err error) {
        c = cl.newConnection(nc)
-       c.encrypted = encrypted
+       c.headerEncrypted = encryptHeader
        c.uTP = utp
        ctx, cancel := context.WithTimeout(ctx, handshakesTimeout)
        defer cancel()
@@ -616,8 +616,8 @@ func (cl *Client) establishOutgoingConn(t *Torrent, addr string) (c *connection,
        if nc == nil {
                return
        }
-       encryptFirst := !cl.config.DisableEncryption && !cl.config.PreferNoEncryption
-       c, err = cl.handshakesConnection(ctx, nc, t, encryptFirst, utp)
+       obfuscatedHeaderFirst := !cl.config.DisableEncryption && !cl.config.PreferNoEncryption
+       c, err = cl.handshakesConnection(ctx, nc, t, obfuscatedHeaderFirst, utp)
        if err != nil {
                nc.Close()
                return
@@ -625,8 +625,12 @@ func (cl *Client) establishOutgoingConn(t *Torrent, addr string) (c *connection,
                return
        }
        nc.Close()
-       if cl.config.DisableEncryption || cl.config.ForceEncryption {
-               // There's no alternate encryption case to try.
+       if cl.config.ForceEncryption {
+               // We should have just tried with an obfuscated header. A plaintext
+               // header can't result in an encrypted connection, so we're done.
+               if !obfuscatedHeaderFirst {
+                       panic(cl.config.EncryptionPolicy)
+               }
                return
        }
        // Try again with encryption if we didn't earlier, or without if we did,
@@ -640,7 +644,7 @@ func (cl *Client) establishOutgoingConn(t *Torrent, addr string) (c *connection,
                err = fmt.Errorf("error dialing for unencrypted connection: %s", err)
                return
        }
-       c, err = cl.handshakesConnection(ctx, nc, t, !encryptFirst, utp)
+       c, err = cl.handshakesConnection(ctx, nc, t, !obfuscatedHeaderFirst, utp)
        if err != nil || c == nil {
                nc.Close()
        }
@@ -829,34 +833,74 @@ func (r deadlineReader) Read(b []byte) (n int, err error) {
        return
 }
 
-func maybeReceiveEncryptedHandshake(rw io.ReadWriter, skeys mse.SecretKeyIter) (ret io.ReadWriter, encrypted bool, err error) {
-       var protocol [len(pp.Protocol)]byte
-       _, err = io.ReadFull(rw, protocol[:])
-       if err != nil {
-               return
-       }
-       ret = struct {
-               io.Reader
-               io.Writer
-       }{
-               io.MultiReader(bytes.NewReader(protocol[:]), rw),
-               rw,
-       }
-       if string(protocol[:]) == pp.Protocol {
-               return
+func handleEncryption(
+       rw io.ReadWriter,
+       skeys mse.SecretKeyIter,
+       policy EncryptionPolicy,
+) (
+       ret io.ReadWriter,
+       headerEncrypted bool,
+       cryptoMethod uint32,
+       err error,
+) {
+       if !policy.ForceEncryption {
+               var protocol [len(pp.Protocol)]byte
+               _, err = io.ReadFull(rw, protocol[:])
+               if err != nil {
+                       return
+               }
+               rw = struct {
+                       io.Reader
+                       io.Writer
+               }{
+                       io.MultiReader(bytes.NewReader(protocol[:]), rw),
+                       rw,
+               }
+               if string(protocol[:]) == pp.Protocol {
+                       ret = rw
+                       return
+               }
        }
-       encrypted = true
-       ret, err = mse.ReceiveHandshakeLazy(ret, skeys)
+       headerEncrypted = true
+       ret, err = mse.ReceiveHandshake(rw, skeys, func(provides uint32) uint32 {
+               cryptoMethod = func() uint32 {
+                       switch {
+                       case policy.ForceEncryption:
+                               return mse.CryptoMethodRC4
+                       case policy.DisableEncryption:
+                               return mse.CryptoMethodPlaintext
+                       case policy.PreferNoEncryption && provides&mse.CryptoMethodPlaintext != 0:
+                               return mse.CryptoMethodPlaintext
+                       default:
+                               return mse.DefaultCryptoSelector(provides)
+                       }
+               }()
+               return cryptoMethod
+       })
        return
 }
 
 func (cl *Client) initiateHandshakes(c *connection, t *Torrent) (ok bool, err error) {
-       if c.encrypted {
+       if c.headerEncrypted {
                var rw io.ReadWriter
-               rw, err = mse.InitiateHandshake(struct {
-                       io.Reader
-                       io.Writer
-               }{c.r, c.w}, t.infoHash[:], nil)
+               rw, err = mse.InitiateHandshake(
+                       struct {
+                               io.Reader
+                               io.Writer
+                       }{c.r, c.w},
+                       t.infoHash[:],
+                       nil,
+                       func() uint32 {
+                               switch {
+                               case cl.config.ForceEncryption:
+                                       return mse.CryptoMethodRC4
+                               case cl.config.DisableEncryption:
+                                       return mse.CryptoMethodPlaintext
+                               default:
+                                       return mse.AllSupportedCrypto
+                               }
+                       }(),
+               )
                c.setRW(rw)
                if err != nil {
                        return
@@ -882,18 +926,16 @@ func (cl *Client) forSkeys(f func([]byte) bool) {
 
 // Do encryption and bittorrent handshakes as receiver.
 func (cl *Client) receiveHandshakes(c *connection) (t *Torrent, err error) {
-       if !cl.config.DisableEncryption {
-               var rw io.ReadWriter
-               rw, c.encrypted, err = maybeReceiveEncryptedHandshake(c.rw(), cl.forSkeys)
-               c.setRW(rw)
-               if err != nil {
-                       if err == mse.ErrNoSecretKeyMatch {
-                               err = nil
-                       }
-                       return
+       var rw io.ReadWriter
+       rw, c.headerEncrypted, c.cryptoMethod, err = handleEncryption(c.rw(), cl.forSkeys, cl.config.EncryptionPolicy)
+       c.setRW(rw)
+       if err != nil {
+               if err == mse.ErrNoSecretKeyMatch {
+                       err = nil
                }
+               return
        }
-       if cl.config.ForceEncryption && !c.encrypted {
+       if cl.config.ForceEncryption && !c.headerEncrypted {
                err = errors.New("connection not encrypted")
                return
        }
index bb614264a0bd26986e123079d55bbaee075da61a..172c752f12f9325ca51ea2038e3b073017793e9f 100644 (file)
@@ -23,21 +23,6 @@ import (
        "github.com/anacrolix/torrent/storage"
 )
 
-func resolvedPeerAddrs(ss []string) (ret []torrent.Peer, err error) {
-       for _, s := range ss {
-               var addr *net.TCPAddr
-               addr, err = net.ResolveTCPAddr("tcp", s)
-               if err != nil {
-                       return
-               }
-               ret = append(ret, torrent.Peer{
-                       IP:   addr.IP,
-                       Port: addr.Port,
-               })
-       }
-       return
-}
-
 func torrentBar(t *torrent.Torrent) {
        bar := uiprogress.AddBar(1)
        bar.AppendCompleted()
index 8ff15b60bceaba4fab33db1d663c9a0aaa171481..ea947de1e7e2e2caf414985ebdbeed03cb7ac833 100644 (file)
--- a/config.go
+++ b/config.go
@@ -51,12 +51,16 @@ type Config struct {
        // used.
        DefaultStorage storage.ClientImpl
 
-       DisableEncryption  bool `long:"disable-encryption"`
-       ForceEncryption    bool // Don't allow unobfuscated connections.
-       PreferNoEncryption bool
+       EncryptionPolicy
 
        IPBlocklist iplist.Ranger
        DisableIPv6 bool `long:"disable-ipv6"`
        // Perform logging and any other behaviour that will help debug.
        Debug bool `help:"enable debug logging"`
 }
+
+type EncryptionPolicy struct {
+       DisableEncryption  bool
+       ForceEncryption    bool // Don't allow unobfuscated connections.
+       PreferNoEncryption bool
+}
index f519ac6e3850c5b9fbc52c0f484fa750f13a1bec..dc7b8390764d44ee745e34a038c00cd25429c5e9 100644 (file)
@@ -14,6 +14,8 @@ import (
        "sync"
        "time"
 
+       "github.com/anacrolix/torrent/mse"
+
        "github.com/anacrolix/missinggo"
        "github.com/anacrolix/missinggo/bitmap"
        "github.com/anacrolix/missinggo/iter"
@@ -43,10 +45,11 @@ type connection struct {
        w io.Writer
        r io.Reader
        // True if the connection is operating over MSE obfuscation.
-       encrypted bool
-       Discovery peerSource
-       uTP       bool
-       closed    missinggo.Event
+       headerEncrypted bool
+       cryptoMethod    uint32
+       Discovery       peerSource
+       uTP             bool
+       closed          missinggo.Event
 
        stats                  ConnStats
        UnwantedChunksReceived int
@@ -148,8 +151,10 @@ func (cn *connection) connectionFlags() (ret string) {
        c := func(b byte) {
                ret += string([]byte{b})
        }
-       if cn.encrypted {
+       if cn.cryptoMethod == mse.CryptoMethodRC4 {
                c('E')
+       } else if cn.headerEncrypted {
+               c('e')
        }
        ret += string(cn.Discovery)
        if cn.uTP {
index 8127b815052734f6600dca0cc7d87e611c0cbcb1..57fcc0582a672af2ae1b03ab35da5c1d1bdecb50 100644 (file)
@@ -24,9 +24,9 @@ import (
 const (
        maxPadLen = 512
 
-       cryptoMethodPlaintext = 1
-       cryptoMethodRC4       = 2
-       AllSupportedCrypto    = cryptoMethodPlaintext | cryptoMethodRC4
+       CryptoMethodPlaintext = 1
+       CryptoMethodRC4       = 2
+       AllSupportedCrypto    = CryptoMethodPlaintext | CryptoMethodRC4
 )
 
 var (
@@ -409,9 +409,9 @@ func (h *handshake) initerSteps() (ret io.ReadWriter, err error) {
                return
        }
        switch method & h.cryptoProvides {
-       case cryptoMethodRC4:
+       case CryptoMethodRC4:
                ret = readWriter{r, &cipherWriter{e, h.conn, nil}}
-       case cryptoMethodPlaintext:
+       case CryptoMethodPlaintext:
                ret = h.conn
        default:
                err = fmt.Errorf("receiver chose unsupported method: %x", method)
@@ -482,12 +482,12 @@ func (h *handshake) receiverSteps() (ret io.ReadWriter, err error) {
                return
        }
        switch chosen {
-       case cryptoMethodRC4:
+       case CryptoMethodRC4:
                ret = readWriter{
                        io.MultiReader(bytes.NewReader(h.ia), r),
                        &cipherWriter{w.c, h.conn, nil},
                }
-       case cryptoMethodPlaintext:
+       case CryptoMethodPlaintext:
                ret = readWriter{
                        io.MultiReader(bytes.NewReader(h.ia), h.conn),
                        h.conn,
@@ -538,11 +538,11 @@ func InitiateHandshake(rw io.ReadWriter, skey []byte, initialPayload []byte, cry
        return h.Do()
 }
 
-func ReceiveHandshake(rw io.ReadWriter, skeys [][]byte, selectCrypto func(uint32) uint32) (ret io.ReadWriter, err error) {
+func ReceiveHandshake(rw io.ReadWriter, skeys SecretKeyIter, selectCrypto func(uint32) uint32) (ret io.ReadWriter, err error) {
        h := handshake{
                conn:         rw,
                initer:       false,
-               skeys:        sliceIter(skeys),
+               skeys:        skeys,
                chooseMethod: selectCrypto,
        }
        return h.Do()
@@ -562,22 +562,11 @@ func sliceIter(skeys [][]byte) SecretKeyIter {
 // returns false or exhausted.
 type SecretKeyIter func(callback func(skey []byte) (more bool))
 
-// Doesn't unpack the secret keys until it needs to, and through the passed
-// function.
-func ReceiveHandshakeLazy(rw io.ReadWriter, skeys SecretKeyIter) (ret io.ReadWriter, err error) {
-       h := handshake{
-               conn:   rw,
-               initer: false,
-               skeys:  skeys,
-       }
-       return h.Do()
-}
-
 func DefaultCryptoSelector(provided uint32) uint32 {
-       if provided&cryptoMethodRC4 != 0 {
-               return cryptoMethodRC4
+       if provided&CryptoMethodRC4 != 0 {
+               return CryptoMethodRC4
        }
-       return cryptoMethodPlaintext
+       return CryptoMethodPlaintext
 }
 
 type CryptoSelector func(uint32) uint32
index d9ef7b68129b4ccc945701971d65f9ee01377bfc..79cabb18de974b757de0b06f227cc1ca47d0cf15 100644 (file)
@@ -71,7 +71,7 @@ func handshakeTest(t testing.TB, ia []byte, aData, bData string, cryptoProvides
        }()
        go func() {
                defer wg.Done()
-               b, err := ReceiveHandshake(b, [][]byte{[]byte("nope"), []byte("yep"), []byte("maybe")}, cryptoSelect)
+               b, err := ReceiveHandshake(b, sliceIter([][]byte{[]byte("nope"), []byte("yep"), []byte("maybe")}), cryptoSelect)
                if err != nil {
                        t.Fatal(err)
                        return
@@ -103,7 +103,7 @@ func TestHandshakeDefault(t *testing.T) {
 }
 
 func TestHandshakeSelectPlaintext(t *testing.T) {
-       allHandshakeTests(t, AllSupportedCrypto, func(uint32) uint32 { return cryptoMethodPlaintext })
+       allHandshakeTests(t, AllSupportedCrypto, func(uint32) uint32 { return CryptoMethodPlaintext })
 }
 
 func BenchmarkHandshakeDefault(b *testing.B) {
@@ -180,7 +180,7 @@ func benchmarkStream(t *testing.B, crypto uint32) {
                }()
                func() {
                        defer bc.Close()
-                       rw, err := ReceiveHandshake(bc, [][]byte{[]byte("cats")}, func(uint32) uint32 { return crypto })
+                       rw, err := ReceiveHandshake(bc, sliceIter([][]byte{[]byte("cats")}), func(uint32) uint32 { return crypto })
                        require.NoError(t, err)
                        require.NoError(t, readAndWrite(rw, br, b))
                }()
@@ -201,11 +201,11 @@ func benchmarkStream(t *testing.B, crypto uint32) {
 }
 
 func BenchmarkStreamRC4(t *testing.B) {
-       benchmarkStream(t, cryptoMethodRC4)
+       benchmarkStream(t, CryptoMethodRC4)
 }
 
 func BenchmarkStreamPlaintext(t *testing.B) {
-       benchmarkStream(t, cryptoMethodPlaintext)
+       benchmarkStream(t, CryptoMethodPlaintext)
 }
 
 func BenchmarkPipeRC4(t *testing.B) {