| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- // https://wiki.vuze.com/w/Message_Stream_Encryption
- package mse
- import (
- "bytes"
- "context"
- "crypto/rand"
- "crypto/rc4"
- "crypto/sha1"
- "encoding/binary"
- "errors"
- "expvar"
- "fmt"
- "github.com/anacrolix/torrent/internal/ctxrw"
- "io"
- "math"
- "math/big"
- "strconv"
- "sync"
- "github.com/anacrolix/missinggo/perf"
- )
- const (
- maxPadLen = 512
- CryptoMethodPlaintext CryptoMethod = 1 // After header obfuscation, drop into plaintext
- CryptoMethodRC4 CryptoMethod = 2 // After header obfuscation, use RC4 for the rest of the stream
- AllSupportedCrypto = CryptoMethodPlaintext | CryptoMethodRC4
- )
- type CryptoMethod uint32
- var (
- // Prime P according to the spec, and G, the generator.
- p, specG big.Int
- // The rand.Int max arg for use in newPadLen()
- newPadLenMax big.Int
- // For use in initer's hashes
- req1 = []byte("req1")
- req2 = []byte("req2")
- req3 = []byte("req3")
- // Verification constant "VC" which is all zeroes in the bittorrent
- // implementation.
- vc [8]byte
- // Zero padding
- zeroPad [512]byte
- // Tracks counts of received crypto_provides
- cryptoProvidesCount = expvar.NewMap("mseCryptoProvides")
- )
- func init() {
- p.SetString("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A36210000000000090563", 0)
- specG.SetInt64(2)
- newPadLenMax.SetInt64(maxPadLen + 1)
- }
- func hash(parts ...[]byte) []byte {
- h := sha1.New()
- for _, p := range parts {
- n, err := h.Write(p)
- if err != nil {
- panic(err)
- }
- if n != len(p) {
- panic(n)
- }
- }
- return h.Sum(nil)
- }
- func newEncrypt(initer bool, s, skey []byte) (c *rc4.Cipher) {
- c, err := rc4.NewCipher(hash([]byte(func() string {
- if initer {
- return "keyA"
- } else {
- return "keyB"
- }
- }()), s, skey))
- if err != nil {
- panic(err)
- }
- var burnSrc, burnDst [1024]byte
- c.XORKeyStream(burnDst[:], burnSrc[:])
- return
- }
- type cipherReader struct {
- c *rc4.Cipher
- r io.Reader
- be []byte
- }
- func (cr *cipherReader) Read(b []byte) (n int, err error) {
- if cap(cr.be) < len(b) {
- cr.be = make([]byte, len(b))
- }
- n, err = cr.r.Read(cr.be[:len(b)])
- cr.c.XORKeyStream(b[:n], cr.be[:n])
- return
- }
- func newCipherReader(c *rc4.Cipher, r io.Reader) io.Reader {
- return &cipherReader{c: c, r: r}
- }
- type cipherWriter struct {
- c *rc4.Cipher
- w io.Writer
- b []byte
- }
- func (cr *cipherWriter) Write(b []byte) (n int, err error) {
- be := func() []byte {
- if len(cr.b) < len(b) {
- return make([]byte, len(b))
- } else {
- ret := cr.b
- cr.b = nil
- return ret
- }
- }()
- cr.c.XORKeyStream(be, b)
- n, err = cr.w.Write(be[:len(b)])
- if n != len(b) {
- // The cipher will have advanced beyond the callers stream position.
- // We can't use the cipher anymore.
- cr.c = nil
- }
- if len(be) > len(cr.b) {
- cr.b = be
- }
- return
- }
- func newX() big.Int {
- var X big.Int
- X.SetBytes(func() []byte {
- var b [20]byte
- _, err := rand.Read(b[:])
- if err != nil {
- panic(err)
- }
- return b[:]
- }())
- return X
- }
- func paddedLeft(b []byte, _len int) []byte {
- if len(b) == _len {
- return b
- }
- ret := make([]byte, _len)
- if n := copy(ret[_len-len(b):], b); n != len(b) {
- panic(n)
- }
- return ret
- }
- // Calculate, and send Y, our public key.
- func (h *handshake) postY(x *big.Int) error {
- var y big.Int
- y.Exp(&specG, x, &p)
- return h.postWrite(paddedLeft(y.Bytes(), 96))
- }
- func (h *handshake) establishS() error {
- x := newX()
- h.postY(&x)
- var b [96]byte
- _, err := io.ReadFull(h.ctxConn, b[:])
- if err != nil {
- return fmt.Errorf("error reading Y: %w", err)
- }
- var Y, S big.Int
- Y.SetBytes(b[:])
- S.Exp(&Y, &x, &p)
- sBytes := S.Bytes()
- copy(h.s[96-len(sBytes):96], sBytes)
- return nil
- }
- func newPadLen() int64 {
- i, err := rand.Int(rand.Reader, &newPadLenMax)
- if err != nil {
- panic(err)
- }
- ret := i.Int64()
- if ret < 0 || ret > maxPadLen {
- panic(ret)
- }
- return ret
- }
- // Manages state for both initiating and receiving handshakes.
- type handshake struct {
- conn io.ReadWriter
- // The conn with Reads and Writes wrapped to the context given in handshake.Do.
- ctxConn io.ReadWriter
- s [96]byte
- initer bool // Whether we're initiating or receiving.
- skeys SecretKeyIter // Skeys we'll accept if receiving.
- skey []byte // Skey we're initiating with.
- ia []byte // Initial payload. Only used by the initiator.
- // Return the bit for the crypto method the receiver wants to use.
- chooseMethod CryptoSelector
- // Sent to the receiver.
- cryptoProvides CryptoMethod
- writeMu sync.Mutex
- writes [][]byte
- writeErr error
- writeCond sync.Cond
- writeClose bool
- writerMu sync.Mutex
- writerCond sync.Cond
- writerDone bool
- }
- func (h *handshake) finishWriting() {
- h.writeMu.Lock()
- h.writeClose = true
- h.writeCond.Broadcast()
- h.writeMu.Unlock()
- h.writerMu.Lock()
- for !h.writerDone {
- h.writerCond.Wait()
- }
- h.writerMu.Unlock()
- }
- func (h *handshake) writer() {
- defer func() {
- h.writerMu.Lock()
- h.writerDone = true
- h.writerCond.Broadcast()
- h.writerMu.Unlock()
- }()
- for {
- h.writeMu.Lock()
- for {
- if len(h.writes) != 0 {
- break
- }
- if h.writeClose {
- h.writeMu.Unlock()
- return
- }
- h.writeCond.Wait()
- }
- b := h.writes[0]
- h.writes = h.writes[1:]
- h.writeMu.Unlock()
- _, err := h.ctxConn.Write(b)
- if err != nil {
- h.writeMu.Lock()
- h.writeErr = err
- h.writeMu.Unlock()
- return
- }
- }
- }
- func (h *handshake) postWrite(b []byte) error {
- h.writeMu.Lock()
- defer h.writeMu.Unlock()
- if h.writeErr != nil {
- return h.writeErr
- }
- h.writes = append(h.writes, b)
- h.writeCond.Signal()
- return nil
- }
- func xor(a, b []byte) (ret []byte) {
- max := len(a)
- if max > len(b) {
- max = len(b)
- }
- ret = make([]byte, max)
- xorInPlace(ret, a, b)
- return
- }
- func xorInPlace(dst, a, b []byte) {
- for i := range dst {
- dst[i] = a[i] ^ b[i]
- }
- }
- func marshal(w io.Writer, data ...interface{}) (err error) {
- for _, data := range data {
- err = binary.Write(w, binary.BigEndian, data)
- if err != nil {
- break
- }
- }
- return
- }
- func unmarshal(r io.Reader, data ...interface{}) (err error) {
- for _, data := range data {
- err = binary.Read(r, binary.BigEndian, data)
- if err != nil {
- break
- }
- }
- return
- }
- // Looking for b at the end of a.
- func suffixMatchLen(a, b []byte) int {
- if len(b) > len(a) {
- b = b[:len(a)]
- }
- // i is how much of b to try to match
- for i := len(b); i > 0; i-- {
- // j is how many chars we've compared
- j := 0
- for ; j < i; j++ {
- if b[i-1-j] != a[len(a)-1-j] {
- goto shorter
- }
- }
- return j
- shorter:
- }
- return 0
- }
- // Reads from r until b has been seen. Keeps the minimum amount of data in
- // memory.
- func readUntil(r io.Reader, b []byte) error {
- b1 := make([]byte, len(b))
- i := 0
- for {
- _, err := io.ReadFull(r, b1[i:])
- if err != nil {
- return err
- }
- i = suffixMatchLen(b1, b)
- if i == len(b) {
- break
- }
- if copy(b1, b1[len(b1)-i:]) != i {
- panic("wat")
- }
- }
- return nil
- }
- type readWriter struct {
- io.Reader
- io.Writer
- }
- func (h *handshake) newEncrypt(initer bool) *rc4.Cipher {
- return newEncrypt(initer, h.s[:], h.skey)
- }
- func (h *handshake) initerSteps(ctx context.Context) (ret io.ReadWriter, selected CryptoMethod, err error) {
- h.postWrite(hash(req1, h.s[:]))
- h.postWrite(xor(hash(req2, h.skey), hash(req3, h.s[:])))
- buf := &bytes.Buffer{}
- padLen := uint16(newPadLen())
- if len(h.ia) > math.MaxUint16 {
- err = errors.New("initial payload too large")
- return
- }
- err = marshal(buf, vc[:], h.cryptoProvides, padLen, zeroPad[:padLen], uint16(len(h.ia)), h.ia)
- if err != nil {
- return
- }
- e := h.newEncrypt(true)
- be := make([]byte, buf.Len())
- e.XORKeyStream(be, buf.Bytes())
- h.postWrite(be)
- bC := h.newEncrypt(false)
- var eVC [8]byte
- bC.XORKeyStream(eVC[:], vc[:])
- // Read until the all zero VC. At this point we've only read the 96 byte
- // public key, Y. There is potentially 512 byte padding, between us and
- // the 8 byte verification constant.
- err = readUntil(io.LimitReader(h.ctxConn, 520), eVC[:])
- if err != nil {
- if err == io.EOF {
- err = errors.New("failed to synchronize on VC")
- } else {
- err = fmt.Errorf("error reading until VC: %w", err)
- }
- return
- }
- ctxReader := newCipherReader(bC, h.ctxConn)
- var method CryptoMethod
- err = unmarshal(ctxReader, &method, &padLen)
- if err != nil {
- return
- }
- _, err = io.CopyN(io.Discard, ctxReader, int64(padLen))
- if err != nil {
- return
- }
- selected = method & h.cryptoProvides
- switch selected {
- case CryptoMethodRC4:
- ret = readWriter{
- newCipherReader(bC, h.conn),
- &cipherWriter{e, h.conn, nil},
- }
- case CryptoMethodPlaintext:
- ret = h.conn
- default:
- err = fmt.Errorf("receiver chose unsupported method: %x", method)
- }
- return
- }
- var ErrNoSecretKeyMatch = errors.New("no skey matched")
- func (h *handshake) receiverSteps(ctx context.Context) (ret io.ReadWriter, chosen CryptoMethod, err error) {
- // There is up to 512 bytes of padding, then the 20 byte hash.
- err = readUntil(io.LimitReader(h.ctxConn, 532), hash(req1, h.s[:]))
- if err != nil {
- if err == io.EOF {
- err = errors.New("failed to synchronize on S hash")
- }
- return
- }
- var b [20]byte
- _, err = io.ReadFull(h.ctxConn, b[:])
- if err != nil {
- return
- }
- expectedHash := hash(req3, h.s[:])
- eachHash := sha1.New()
- var sum, xored [sha1.Size]byte
- err = ErrNoSecretKeyMatch
- h.skeys(func(skey []byte) bool {
- eachHash.Reset()
- eachHash.Write(req2)
- eachHash.Write(skey)
- eachHash.Sum(sum[:0])
- xorInPlace(xored[:], sum[:], expectedHash)
- if bytes.Equal(xored[:], b[:]) {
- h.skey = skey
- err = nil
- return false
- }
- return true
- })
- if err != nil {
- return
- }
- cipher := newEncrypt(true, h.s[:], h.skey)
- ctxReader := newCipherReader(cipher, h.ctxConn)
- var (
- vc [8]byte
- provides CryptoMethod
- padLen uint16
- )
- err = unmarshal(ctxReader, vc[:], &provides, &padLen)
- if err != nil {
- return
- }
- cryptoProvidesCount.Add(strconv.FormatUint(uint64(provides), 16), 1)
- chosen = h.chooseMethod(provides)
- _, err = io.CopyN(io.Discard, ctxReader, int64(padLen))
- if err != nil {
- return
- }
- var lenIA uint16
- unmarshal(ctxReader, &lenIA)
- if lenIA != 0 {
- h.ia = make([]byte, lenIA)
- unmarshal(ctxReader, h.ia)
- }
- buf := &bytes.Buffer{}
- w := cipherWriter{h.newEncrypt(false), buf, nil}
- padLen = uint16(newPadLen())
- err = marshal(&w, &vc, uint32(chosen), padLen, zeroPad[:padLen])
- if err != nil {
- return
- }
- err = h.postWrite(buf.Bytes())
- if err != nil {
- return
- }
- switch chosen {
- case CryptoMethodRC4:
- ret = readWriter{
- io.MultiReader(bytes.NewReader(h.ia), newCipherReader(cipher, h.conn)),
- &cipherWriter{w.c, h.conn, nil},
- }
- case CryptoMethodPlaintext:
- ret = readWriter{
- io.MultiReader(bytes.NewReader(h.ia), h.conn),
- h.conn,
- }
- default:
- err = errors.New("chosen crypto method is not supported")
- }
- return
- }
- func (h *handshake) Do(ctx context.Context) (ret io.ReadWriter, method CryptoMethod, err error) {
- h.writeCond.L = &h.writeMu
- h.writerCond.L = &h.writerMu
- go h.writer()
- defer func() {
- h.finishWriting()
- if err == nil {
- err = h.writeErr
- }
- }()
- err = h.establishS()
- if err != nil {
- err = fmt.Errorf("error while establishing secret: %w", err)
- return
- }
- pad := make([]byte, newPadLen())
- io.ReadFull(rand.Reader, pad)
- err = h.postWrite(pad)
- if err != nil {
- return
- }
- if h.initer {
- ret, method, err = h.initerSteps(ctx)
- } else {
- ret, method, err = h.receiverSteps(ctx)
- }
- return
- }
- func InitiateHandshake(
- rw io.ReadWriter,
- skey, initialPayload []byte,
- cryptoProvides CryptoMethod,
- ) (
- ret io.ReadWriter, method CryptoMethod, err error,
- ) {
- return InitiateHandshakeContext(context.TODO(), rw, skey, initialPayload, cryptoProvides)
- }
- func InitiateHandshakeContext(
- ctx context.Context,
- rw io.ReadWriter,
- skey, initialPayload []byte,
- cryptoProvides CryptoMethod,
- ) (
- ret io.ReadWriter, method CryptoMethod, err error,
- ) {
- h := handshake{
- conn: rw,
- ctxConn: ctxrw.WrapReadWriter(ctx, rw),
- initer: true,
- skey: skey,
- ia: initialPayload,
- cryptoProvides: cryptoProvides,
- }
- defer perf.ScopeTimerErr(&err)()
- return h.Do(ctx)
- }
- type HandshakeResult struct {
- io.ReadWriter
- CryptoMethod
- error
- SecretKey []byte
- }
- func ReceiveHandshake(
- ctx context.Context,
- rw io.ReadWriter,
- skeys SecretKeyIter,
- selectCrypto CryptoSelector,
- ) (io.ReadWriter, CryptoMethod, error) {
- res := ReceiveHandshakeEx(ctx, rw, skeys, selectCrypto)
- return res.ReadWriter, res.CryptoMethod, res.error
- }
- func ReceiveHandshakeEx(
- ctx context.Context,
- rw io.ReadWriter,
- skeys SecretKeyIter,
- selectCrypto CryptoSelector,
- ) (ret HandshakeResult) {
- h := handshake{
- conn: rw,
- ctxConn: ctxrw.WrapReadWriter(ctx, rw),
- initer: false,
- skeys: skeys,
- chooseMethod: selectCrypto,
- }
- ret.ReadWriter, ret.CryptoMethod, ret.error = h.Do(ctx)
- ret.SecretKey = h.skey
- return
- }
- // A function that given a function, calls it with secret keys until it
- // returns false or exhausted.
- type SecretKeyIter func(callback func(skey []byte) (more bool))
- func DefaultCryptoSelector(provided CryptoMethod) CryptoMethod {
- // We prefer plaintext for performance reasons.
- if provided&CryptoMethodPlaintext != 0 {
- return CryptoMethodPlaintext
- }
- return CryptoMethodRC4
- }
- type CryptoSelector func(CryptoMethod) CryptoMethod
|