aboutsummaryrefslogtreecommitdiff
path: root/pkg/nspkt/r2crypto.go
blob: 7d346d06fd2676d8f20b66f84562f3c34aea2523 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package nspkt

import (
	"crypto/aes"
	"crypto/cipher"
	"fmt"
)

const r2cryptoNonceSize = 12
const r2cryptoTagSize = 16

var r2cryptoGCM cipher.AEAD
var r2cryptoKey = []byte("X3V.bXCfe3EhN'wb")
var r2cryptoAAD = []byte("\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10")

// r2cb efficiently implements allocation-free Titanfall 2 packet crypto.
//
//	go:            data tag
//	net: nonce tag data
type r2cb []byte

// init initializes the AES-GCM cipher for Titanfall 2 packet crypto.
func init() {
	if c, err := aes.NewCipher([]byte(r2cryptoKey)); err != nil {
		panic(fmt.Errorf("r2crypto: init aes: %w", err))
	} else if a, err := cipher.NewGCMWithTagSize(c, r2cryptoTagSize); err != nil {
		panic(fmt.Errorf("r2crypto: init gcm: %w", err))
	} else if n := a.NonceSize(); n != r2cryptoNonceSize {
		panic(fmt.Errorf("r2crypto: unexpected nonce size %d", n))
	} else {
		r2cryptoGCM = a
	}
}

// r2crypto allocates a new buffer which can hold up to n bytes of data.
func r2crypto(n int) r2cb {
	return make(r2cb, r2cryptoNonceSize+r2cryptoTagSize+n+r2cryptoTagSize)
}

// WithPacketLen returns a slice of the buffer for a packet of length n.
func (pkt r2cb) WithPacketLen(n int) r2cb {
	return pkt[:n+r2cryptoTagSize]
}

// WithDataLen returns a slice of the buffer for a packet with data of length n.
func (pkt r2cb) WithDataLen(n int) r2cb {
	return pkt[:r2cryptoNonceSize+r2cryptoTagSize+n+r2cryptoTagSize]
}

// Packet returns a slice of the buffer containing the raw packet.
func (pkt r2cb) Packet() []byte {
	return pkt[:len(pkt)-r2cryptoTagSize]
}

// Data returns a slice of the buffer contains the packet data.
func (pkt r2cb) Data() []byte {
	return pkt[r2cryptoNonceSize+r2cryptoTagSize : len(pkt)-r2cryptoTagSize]
}

// Nonce returns a slice of the buffer containing the nonce. It should be
// randomized before calling Encrypt.
func (pkt r2cb) Nonce() []byte {
	return pkt[:r2cryptoNonceSize]
}

func (pkt r2cb) tagNet() []byte {
	return pkt[r2cryptoNonceSize:][:r2cryptoTagSize]
}

func (pkt r2cb) tagGo() []byte {
	return pkt[len(pkt)-r2cryptoTagSize:][:r2cryptoTagSize]
}

func (pkt r2cb) gcmGo() []byte {
	return pkt[r2cryptoNonceSize+r2cryptoTagSize:]
}

// Decrypt decrypts the packet data in-place. It is the inverse of Encrypt.
func (pkt r2cb) Decrypt() bool {
	copy(pkt.tagGo(), pkt.tagNet())
	b, err := r2cryptoGCM.Open(pkt.Data()[:0], pkt.Nonce(), pkt.gcmGo(), r2cryptoAAD)
	if len(b) != 0 && len(pkt.Data()) != 0 && &b[0] != &pkt.Data()[0] {
		panic("buffer was moved (wtf?)")
	}
	return err == nil
}

// Encrypt encrypts the packet data in-place. It is the inverse of Decrypt.
func (pkt r2cb) Encrypt() {
	b := r2cryptoGCM.Seal(pkt.gcmGo()[:0], pkt.Nonce(), pkt.Data(), r2cryptoAAD)
	if len(b) != 0 && len(pkt.Data()) != 0 && &b[0] != &pkt.Data()[0] {
		panic("buffer was moved (wtf?)")
	}
	copy(pkt.tagNet(), pkt.tagGo())
}