Support plaintext crypto method for protocol header encryption
This commit is contained in:
parent
29e06fb83c
commit
881f1a7e35
120
client.go
120
client.go
|
@ -586,9 +586,9 @@ func (cl *Client) noLongerHalfOpen(t *Torrent, addr string) {
|
||||||
|
|
||||||
// Performs initiator handshakes and returns a connection. Returns nil
|
// Performs initiator handshakes and returns a connection. Returns nil
|
||||||
// *connection if no connection for valid reasons.
|
// *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 = cl.newConnection(nc)
|
||||||
c.encrypted = encrypted
|
c.headerEncrypted = encryptHeader
|
||||||
c.uTP = utp
|
c.uTP = utp
|
||||||
ctx, cancel := context.WithTimeout(ctx, handshakesTimeout)
|
ctx, cancel := context.WithTimeout(ctx, handshakesTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -616,8 +616,8 @@ func (cl *Client) establishOutgoingConn(t *Torrent, addr string) (c *connection,
|
||||||
if nc == nil {
|
if nc == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
encryptFirst := !cl.config.DisableEncryption && !cl.config.PreferNoEncryption
|
obfuscatedHeaderFirst := !cl.config.DisableEncryption && !cl.config.PreferNoEncryption
|
||||||
c, err = cl.handshakesConnection(ctx, nc, t, encryptFirst, utp)
|
c, err = cl.handshakesConnection(ctx, nc, t, obfuscatedHeaderFirst, utp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
nc.Close()
|
nc.Close()
|
||||||
return
|
return
|
||||||
|
@ -625,8 +625,12 @@ func (cl *Client) establishOutgoingConn(t *Torrent, addr string) (c *connection,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nc.Close()
|
nc.Close()
|
||||||
if cl.config.DisableEncryption || cl.config.ForceEncryption {
|
if cl.config.ForceEncryption {
|
||||||
// There's no alternate encryption case to try.
|
// 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
|
return
|
||||||
}
|
}
|
||||||
// Try again with encryption if we didn't earlier, or without if we did,
|
// 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)
|
err = fmt.Errorf("error dialing for unencrypted connection: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c, err = cl.handshakesConnection(ctx, nc, t, !encryptFirst, utp)
|
c, err = cl.handshakesConnection(ctx, nc, t, !obfuscatedHeaderFirst, utp)
|
||||||
if err != nil || c == nil {
|
if err != nil || c == nil {
|
||||||
nc.Close()
|
nc.Close()
|
||||||
}
|
}
|
||||||
|
@ -829,34 +833,74 @@ func (r deadlineReader) Read(b []byte) (n int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybeReceiveEncryptedHandshake(rw io.ReadWriter, skeys mse.SecretKeyIter) (ret io.ReadWriter, encrypted bool, err error) {
|
func handleEncryption(
|
||||||
var protocol [len(pp.Protocol)]byte
|
rw io.ReadWriter,
|
||||||
_, err = io.ReadFull(rw, protocol[:])
|
skeys mse.SecretKeyIter,
|
||||||
if err != nil {
|
policy EncryptionPolicy,
|
||||||
return
|
) (
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ret = struct {
|
headerEncrypted = true
|
||||||
io.Reader
|
ret, err = mse.ReceiveHandshake(rw, skeys, func(provides uint32) uint32 {
|
||||||
io.Writer
|
cryptoMethod = func() uint32 {
|
||||||
}{
|
switch {
|
||||||
io.MultiReader(bytes.NewReader(protocol[:]), rw),
|
case policy.ForceEncryption:
|
||||||
rw,
|
return mse.CryptoMethodRC4
|
||||||
}
|
case policy.DisableEncryption:
|
||||||
if string(protocol[:]) == pp.Protocol {
|
return mse.CryptoMethodPlaintext
|
||||||
return
|
case policy.PreferNoEncryption && provides&mse.CryptoMethodPlaintext != 0:
|
||||||
}
|
return mse.CryptoMethodPlaintext
|
||||||
encrypted = true
|
default:
|
||||||
ret, err = mse.ReceiveHandshakeLazy(ret, skeys)
|
return mse.DefaultCryptoSelector(provides)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return cryptoMethod
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *Client) initiateHandshakes(c *connection, t *Torrent) (ok bool, err error) {
|
func (cl *Client) initiateHandshakes(c *connection, t *Torrent) (ok bool, err error) {
|
||||||
if c.encrypted {
|
if c.headerEncrypted {
|
||||||
var rw io.ReadWriter
|
var rw io.ReadWriter
|
||||||
rw, err = mse.InitiateHandshake(struct {
|
rw, err = mse.InitiateHandshake(
|
||||||
io.Reader
|
struct {
|
||||||
io.Writer
|
io.Reader
|
||||||
}{c.r, c.w}, t.infoHash[:], nil)
|
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)
|
c.setRW(rw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -882,18 +926,16 @@ func (cl *Client) forSkeys(f func([]byte) bool) {
|
||||||
|
|
||||||
// Do encryption and bittorrent handshakes as receiver.
|
// Do encryption and bittorrent handshakes as receiver.
|
||||||
func (cl *Client) receiveHandshakes(c *connection) (t *Torrent, err error) {
|
func (cl *Client) receiveHandshakes(c *connection) (t *Torrent, err error) {
|
||||||
if !cl.config.DisableEncryption {
|
var rw io.ReadWriter
|
||||||
var rw io.ReadWriter
|
rw, c.headerEncrypted, c.cryptoMethod, err = handleEncryption(c.rw(), cl.forSkeys, cl.config.EncryptionPolicy)
|
||||||
rw, c.encrypted, err = maybeReceiveEncryptedHandshake(c.rw(), cl.forSkeys)
|
c.setRW(rw)
|
||||||
c.setRW(rw)
|
if err != nil {
|
||||||
if err != nil {
|
if err == mse.ErrNoSecretKeyMatch {
|
||||||
if err == mse.ErrNoSecretKeyMatch {
|
err = nil
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if cl.config.ForceEncryption && !c.encrypted {
|
if cl.config.ForceEncryption && !c.headerEncrypted {
|
||||||
err = errors.New("connection not encrypted")
|
err = errors.New("connection not encrypted")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,21 +23,6 @@ import (
|
||||||
"github.com/anacrolix/torrent/storage"
|
"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) {
|
func torrentBar(t *torrent.Torrent) {
|
||||||
bar := uiprogress.AddBar(1)
|
bar := uiprogress.AddBar(1)
|
||||||
bar.AppendCompleted()
|
bar.AppendCompleted()
|
||||||
|
|
10
config.go
10
config.go
|
@ -51,12 +51,16 @@ type Config struct {
|
||||||
// used.
|
// used.
|
||||||
DefaultStorage storage.ClientImpl
|
DefaultStorage storage.ClientImpl
|
||||||
|
|
||||||
DisableEncryption bool `long:"disable-encryption"`
|
EncryptionPolicy
|
||||||
ForceEncryption bool // Don't allow unobfuscated connections.
|
|
||||||
PreferNoEncryption bool
|
|
||||||
|
|
||||||
IPBlocklist iplist.Ranger
|
IPBlocklist iplist.Ranger
|
||||||
DisableIPv6 bool `long:"disable-ipv6"`
|
DisableIPv6 bool `long:"disable-ipv6"`
|
||||||
// Perform logging and any other behaviour that will help debug.
|
// Perform logging and any other behaviour that will help debug.
|
||||||
Debug bool `help:"enable debug logging"`
|
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"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/anacrolix/torrent/mse"
|
||||||
|
|
||||||
"github.com/anacrolix/missinggo"
|
"github.com/anacrolix/missinggo"
|
||||||
"github.com/anacrolix/missinggo/bitmap"
|
"github.com/anacrolix/missinggo/bitmap"
|
||||||
"github.com/anacrolix/missinggo/iter"
|
"github.com/anacrolix/missinggo/iter"
|
||||||
|
@ -43,10 +45,11 @@ type connection struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
r io.Reader
|
r io.Reader
|
||||||
// True if the connection is operating over MSE obfuscation.
|
// True if the connection is operating over MSE obfuscation.
|
||||||
encrypted bool
|
headerEncrypted bool
|
||||||
Discovery peerSource
|
cryptoMethod uint32
|
||||||
uTP bool
|
Discovery peerSource
|
||||||
closed missinggo.Event
|
uTP bool
|
||||||
|
closed missinggo.Event
|
||||||
|
|
||||||
stats ConnStats
|
stats ConnStats
|
||||||
UnwantedChunksReceived int
|
UnwantedChunksReceived int
|
||||||
|
@ -148,8 +151,10 @@ func (cn *connection) connectionFlags() (ret string) {
|
||||||
c := func(b byte) {
|
c := func(b byte) {
|
||||||
ret += string([]byte{b})
|
ret += string([]byte{b})
|
||||||
}
|
}
|
||||||
if cn.encrypted {
|
if cn.cryptoMethod == mse.CryptoMethodRC4 {
|
||||||
c('E')
|
c('E')
|
||||||
|
} else if cn.headerEncrypted {
|
||||||
|
c('e')
|
||||||
}
|
}
|
||||||
ret += string(cn.Discovery)
|
ret += string(cn.Discovery)
|
||||||
if cn.uTP {
|
if cn.uTP {
|
||||||
|
|
35
mse/mse.go
35
mse/mse.go
|
@ -24,9 +24,9 @@ import (
|
||||||
const (
|
const (
|
||||||
maxPadLen = 512
|
maxPadLen = 512
|
||||||
|
|
||||||
cryptoMethodPlaintext = 1
|
CryptoMethodPlaintext = 1
|
||||||
cryptoMethodRC4 = 2
|
CryptoMethodRC4 = 2
|
||||||
AllSupportedCrypto = cryptoMethodPlaintext | cryptoMethodRC4
|
AllSupportedCrypto = CryptoMethodPlaintext | CryptoMethodRC4
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -409,9 +409,9 @@ func (h *handshake) initerSteps() (ret io.ReadWriter, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch method & h.cryptoProvides {
|
switch method & h.cryptoProvides {
|
||||||
case cryptoMethodRC4:
|
case CryptoMethodRC4:
|
||||||
ret = readWriter{r, &cipherWriter{e, h.conn, nil}}
|
ret = readWriter{r, &cipherWriter{e, h.conn, nil}}
|
||||||
case cryptoMethodPlaintext:
|
case CryptoMethodPlaintext:
|
||||||
ret = h.conn
|
ret = h.conn
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("receiver chose unsupported method: %x", method)
|
err = fmt.Errorf("receiver chose unsupported method: %x", method)
|
||||||
|
@ -482,12 +482,12 @@ func (h *handshake) receiverSteps() (ret io.ReadWriter, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch chosen {
|
switch chosen {
|
||||||
case cryptoMethodRC4:
|
case CryptoMethodRC4:
|
||||||
ret = readWriter{
|
ret = readWriter{
|
||||||
io.MultiReader(bytes.NewReader(h.ia), r),
|
io.MultiReader(bytes.NewReader(h.ia), r),
|
||||||
&cipherWriter{w.c, h.conn, nil},
|
&cipherWriter{w.c, h.conn, nil},
|
||||||
}
|
}
|
||||||
case cryptoMethodPlaintext:
|
case CryptoMethodPlaintext:
|
||||||
ret = readWriter{
|
ret = readWriter{
|
||||||
io.MultiReader(bytes.NewReader(h.ia), h.conn),
|
io.MultiReader(bytes.NewReader(h.ia), h.conn),
|
||||||
h.conn,
|
h.conn,
|
||||||
|
@ -538,11 +538,11 @@ func InitiateHandshake(rw io.ReadWriter, skey []byte, initialPayload []byte, cry
|
||||||
return h.Do()
|
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{
|
h := handshake{
|
||||||
conn: rw,
|
conn: rw,
|
||||||
initer: false,
|
initer: false,
|
||||||
skeys: sliceIter(skeys),
|
skeys: skeys,
|
||||||
chooseMethod: selectCrypto,
|
chooseMethod: selectCrypto,
|
||||||
}
|
}
|
||||||
return h.Do()
|
return h.Do()
|
||||||
|
@ -562,22 +562,11 @@ func sliceIter(skeys [][]byte) SecretKeyIter {
|
||||||
// returns false or exhausted.
|
// returns false or exhausted.
|
||||||
type SecretKeyIter func(callback func(skey []byte) (more bool))
|
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 {
|
func DefaultCryptoSelector(provided uint32) uint32 {
|
||||||
if provided&cryptoMethodRC4 != 0 {
|
if provided&CryptoMethodRC4 != 0 {
|
||||||
return cryptoMethodRC4
|
return CryptoMethodRC4
|
||||||
}
|
}
|
||||||
return cryptoMethodPlaintext
|
return CryptoMethodPlaintext
|
||||||
}
|
}
|
||||||
|
|
||||||
type CryptoSelector func(uint32) uint32
|
type CryptoSelector func(uint32) uint32
|
||||||
|
|
|
@ -71,7 +71,7 @@ func handshakeTest(t testing.TB, ia []byte, aData, bData string, cryptoProvides
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
|
@ -103,7 +103,7 @@ func TestHandshakeDefault(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandshakeSelectPlaintext(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) {
|
func BenchmarkHandshakeDefault(b *testing.B) {
|
||||||
|
@ -180,7 +180,7 @@ func benchmarkStream(t *testing.B, crypto uint32) {
|
||||||
}()
|
}()
|
||||||
func() {
|
func() {
|
||||||
defer bc.Close()
|
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, err)
|
||||||
require.NoError(t, readAndWrite(rw, br, b))
|
require.NoError(t, readAndWrite(rw, br, b))
|
||||||
}()
|
}()
|
||||||
|
@ -201,11 +201,11 @@ func benchmarkStream(t *testing.B, crypto uint32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkStreamRC4(t *testing.B) {
|
func BenchmarkStreamRC4(t *testing.B) {
|
||||||
benchmarkStream(t, cryptoMethodRC4)
|
benchmarkStream(t, CryptoMethodRC4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkStreamPlaintext(t *testing.B) {
|
func BenchmarkStreamPlaintext(t *testing.B) {
|
||||||
benchmarkStream(t, cryptoMethodPlaintext)
|
benchmarkStream(t, CryptoMethodPlaintext)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkPipeRC4(t *testing.B) {
|
func BenchmarkPipeRC4(t *testing.B) {
|
||||||
|
|
Loading…
Reference in New Issue