Support plaintext crypto method for protocol header encryption
This commit is contained in:
parent
29e06fb83c
commit
881f1a7e35
78
client.go
78
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,13 +833,23 @@ 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) {
|
||||
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
|
||||
}
|
||||
ret = struct {
|
||||
rw = struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}{
|
||||
|
@ -843,20 +857,50 @@ func maybeReceiveEncryptedHandshake(rw io.ReadWriter, skeys mse.SecretKeyIter) (
|
|||
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 {
|
||||
rw, err = mse.InitiateHandshake(
|
||||
struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}{c.r, c.w}, t.infoHash[:], nil)
|
||||
}{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,9 +926,8 @@ 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)
|
||||
rw, c.headerEncrypted, c.cryptoMethod, err = handleEncryption(c.rw(), cl.forSkeys, cl.config.EncryptionPolicy)
|
||||
c.setRW(rw)
|
||||
if err != nil {
|
||||
if err == mse.ErrNoSecretKeyMatch {
|
||||
|
@ -892,8 +935,7 @@ func (cl *Client) receiveHandshakes(c *connection) (t *Torrent, err error) {
|
|||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if cl.config.ForceEncryption && !c.encrypted {
|
||||
if cl.config.ForceEncryption && !c.headerEncrypted {
|
||||
err = errors.New("connection not encrypted")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
10
config.go
10
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
|
||||
}
|
||||
|
|
|
@ -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,7 +45,8 @@ type connection struct {
|
|||
w io.Writer
|
||||
r io.Reader
|
||||
// True if the connection is operating over MSE obfuscation.
|
||||
encrypted bool
|
||||
headerEncrypted bool
|
||||
cryptoMethod uint32
|
||||
Discovery peerSource
|
||||
uTP bool
|
||||
closed missinggo.Event
|
||||
|
@ -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 {
|
||||
|
|
35
mse/mse.go
35
mse/mse.go
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue