mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-04-29 16:58:34 +00:00
v1.0.0
This commit is contained in:
parent
47d23e9972
commit
c7f7c08ead
711 changed files with 82154 additions and 2 deletions
40
common/crypto/aes.go
Normal file
40
common/crypto/aes.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
)
|
||||
|
||||
// NewAesDecryptionStream creates a new AES encryption stream based on given key and IV.
|
||||
// Caller must ensure the length of key and IV is either 16, 24 or 32 bytes.
|
||||
func NewAesDecryptionStream(key []byte, iv []byte) cipher.Stream {
|
||||
return NewAesStreamMethod(key, iv, cipher.NewCFBDecrypter)
|
||||
}
|
||||
|
||||
// NewAesEncryptionStream creates a new AES description stream based on given key and IV.
|
||||
// Caller must ensure the length of key and IV is either 16, 24 or 32 bytes.
|
||||
func NewAesEncryptionStream(key []byte, iv []byte) cipher.Stream {
|
||||
return NewAesStreamMethod(key, iv, cipher.NewCFBEncrypter)
|
||||
}
|
||||
|
||||
func NewAesStreamMethod(key []byte, iv []byte, f func(cipher.Block, []byte) cipher.Stream) cipher.Stream {
|
||||
aesBlock, err := aes.NewCipher(key)
|
||||
common.Must(err)
|
||||
return f(aesBlock, iv)
|
||||
}
|
||||
|
||||
// NewAesCTRStream creates a stream cipher based on AES-CTR.
|
||||
func NewAesCTRStream(key []byte, iv []byte) cipher.Stream {
|
||||
return NewAesStreamMethod(key, iv, cipher.NewCTR)
|
||||
}
|
||||
|
||||
// NewAesGcm creates a AEAD cipher based on AES-GCM.
|
||||
func NewAesGcm(key []byte) cipher.AEAD {
|
||||
block, err := aes.NewCipher(key)
|
||||
common.Must(err)
|
||||
aead, err := cipher.NewGCM(block)
|
||||
common.Must(err)
|
||||
return aead
|
||||
}
|
345
common/crypto/auth.go
Normal file
345
common/crypto/auth.go
Normal file
|
@ -0,0 +1,345 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"io"
|
||||
"math/rand"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/common/bytespool"
|
||||
"github.com/xtls/xray-core/v1/common/protocol"
|
||||
)
|
||||
|
||||
type BytesGenerator func() []byte
|
||||
|
||||
func GenerateEmptyBytes() BytesGenerator {
|
||||
var b [1]byte
|
||||
return func() []byte {
|
||||
return b[:0]
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateStaticBytes(content []byte) BytesGenerator {
|
||||
return func() []byte {
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateIncreasingNonce(nonce []byte) BytesGenerator {
|
||||
c := append([]byte(nil), nonce...)
|
||||
return func() []byte {
|
||||
for i := range c {
|
||||
c[i]++
|
||||
if c[i] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateInitialAEADNonce() BytesGenerator {
|
||||
return GenerateIncreasingNonce([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
|
||||
}
|
||||
|
||||
type Authenticator interface {
|
||||
NonceSize() int
|
||||
Overhead() int
|
||||
Open(dst, cipherText []byte) ([]byte, error)
|
||||
Seal(dst, plainText []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type AEADAuthenticator struct {
|
||||
cipher.AEAD
|
||||
NonceGenerator BytesGenerator
|
||||
AdditionalDataGenerator BytesGenerator
|
||||
}
|
||||
|
||||
func (v *AEADAuthenticator) Open(dst, cipherText []byte) ([]byte, error) {
|
||||
iv := v.NonceGenerator()
|
||||
if len(iv) != v.AEAD.NonceSize() {
|
||||
return nil, newError("invalid AEAD nonce size: ", len(iv))
|
||||
}
|
||||
|
||||
var additionalData []byte
|
||||
if v.AdditionalDataGenerator != nil {
|
||||
additionalData = v.AdditionalDataGenerator()
|
||||
}
|
||||
return v.AEAD.Open(dst, iv, cipherText, additionalData)
|
||||
}
|
||||
|
||||
func (v *AEADAuthenticator) Seal(dst, plainText []byte) ([]byte, error) {
|
||||
iv := v.NonceGenerator()
|
||||
if len(iv) != v.AEAD.NonceSize() {
|
||||
return nil, newError("invalid AEAD nonce size: ", len(iv))
|
||||
}
|
||||
|
||||
var additionalData []byte
|
||||
if v.AdditionalDataGenerator != nil {
|
||||
additionalData = v.AdditionalDataGenerator()
|
||||
}
|
||||
return v.AEAD.Seal(dst, iv, plainText, additionalData), nil
|
||||
}
|
||||
|
||||
type AuthenticationReader struct {
|
||||
auth Authenticator
|
||||
reader *buf.BufferedReader
|
||||
sizeParser ChunkSizeDecoder
|
||||
sizeBytes []byte
|
||||
transferType protocol.TransferType
|
||||
padding PaddingLengthGenerator
|
||||
size uint16
|
||||
paddingLen uint16
|
||||
hasSize bool
|
||||
done bool
|
||||
}
|
||||
|
||||
func NewAuthenticationReader(auth Authenticator, sizeParser ChunkSizeDecoder, reader io.Reader, transferType protocol.TransferType, paddingLen PaddingLengthGenerator) *AuthenticationReader {
|
||||
r := &AuthenticationReader{
|
||||
auth: auth,
|
||||
sizeParser: sizeParser,
|
||||
transferType: transferType,
|
||||
padding: paddingLen,
|
||||
sizeBytes: make([]byte, sizeParser.SizeBytes()),
|
||||
}
|
||||
if breader, ok := reader.(*buf.BufferedReader); ok {
|
||||
r.reader = breader
|
||||
} else {
|
||||
r.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *AuthenticationReader) readSize() (uint16, uint16, error) {
|
||||
if r.hasSize {
|
||||
r.hasSize = false
|
||||
return r.size, r.paddingLen, nil
|
||||
}
|
||||
if _, err := io.ReadFull(r.reader, r.sizeBytes); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
var padding uint16
|
||||
if r.padding != nil {
|
||||
padding = r.padding.NextPaddingLen()
|
||||
}
|
||||
size, err := r.sizeParser.Decode(r.sizeBytes)
|
||||
return size, padding, err
|
||||
}
|
||||
|
||||
var errSoft = newError("waiting for more data")
|
||||
|
||||
func (r *AuthenticationReader) readBuffer(size int32, padding int32) (*buf.Buffer, error) {
|
||||
b := buf.New()
|
||||
if _, err := b.ReadFullFrom(r.reader, size); err != nil {
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
size -= padding
|
||||
rb, err := r.auth.Open(b.BytesTo(0), b.BytesTo(size))
|
||||
if err != nil {
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
b.Resize(0, int32(len(rb)))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (r *AuthenticationReader) readInternal(soft bool, mb *buf.MultiBuffer) error {
|
||||
if soft && r.reader.BufferedBytes() < r.sizeParser.SizeBytes() {
|
||||
return errSoft
|
||||
}
|
||||
|
||||
if r.done {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
size, padding, err := r.readSize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size == uint16(r.auth.Overhead())+padding {
|
||||
r.done = true
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
if soft && int32(size) > r.reader.BufferedBytes() {
|
||||
r.size = size
|
||||
r.paddingLen = padding
|
||||
r.hasSize = true
|
||||
return errSoft
|
||||
}
|
||||
|
||||
if size <= buf.Size {
|
||||
b, err := r.readBuffer(int32(size), int32(padding))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
*mb = append(*mb, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
payload := bytespool.Alloc(int32(size))
|
||||
defer bytespool.Free(payload)
|
||||
|
||||
if _, err := io.ReadFull(r.reader, payload[:size]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size -= padding
|
||||
|
||||
rb, err := r.auth.Open(payload[:0], payload[:size])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*mb = buf.MergeBytes(*mb, rb)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *AuthenticationReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
const readSize = 16
|
||||
mb := make(buf.MultiBuffer, 0, readSize)
|
||||
if err := r.readInternal(false, &mb); err != nil {
|
||||
buf.ReleaseMulti(mb)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 1; i < readSize; i++ {
|
||||
err := r.readInternal(true, &mb)
|
||||
if err == errSoft || err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
buf.ReleaseMulti(mb)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
type AuthenticationWriter struct {
|
||||
auth Authenticator
|
||||
writer buf.Writer
|
||||
sizeParser ChunkSizeEncoder
|
||||
transferType protocol.TransferType
|
||||
padding PaddingLengthGenerator
|
||||
}
|
||||
|
||||
func NewAuthenticationWriter(auth Authenticator, sizeParser ChunkSizeEncoder, writer io.Writer, transferType protocol.TransferType, padding PaddingLengthGenerator) *AuthenticationWriter {
|
||||
w := &AuthenticationWriter{
|
||||
auth: auth,
|
||||
writer: buf.NewWriter(writer),
|
||||
sizeParser: sizeParser,
|
||||
transferType: transferType,
|
||||
}
|
||||
if padding != nil {
|
||||
w.padding = padding
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *AuthenticationWriter) seal(b []byte) (*buf.Buffer, error) {
|
||||
encryptedSize := int32(len(b) + w.auth.Overhead())
|
||||
var paddingSize int32
|
||||
if w.padding != nil {
|
||||
paddingSize = int32(w.padding.NextPaddingLen())
|
||||
}
|
||||
|
||||
sizeBytes := w.sizeParser.SizeBytes()
|
||||
totalSize := sizeBytes + encryptedSize + paddingSize
|
||||
if totalSize > buf.Size {
|
||||
return nil, newError("size too large: ", totalSize)
|
||||
}
|
||||
|
||||
eb := buf.New()
|
||||
w.sizeParser.Encode(uint16(encryptedSize+paddingSize), eb.Extend(sizeBytes))
|
||||
if _, err := w.auth.Seal(eb.Extend(encryptedSize)[:0], b); err != nil {
|
||||
eb.Release()
|
||||
return nil, err
|
||||
}
|
||||
if paddingSize > 0 {
|
||||
// With size of the chunk and padding length encrypted, the content of padding doesn't matter much.
|
||||
paddingBytes := eb.Extend(paddingSize)
|
||||
common.Must2(rand.Read(paddingBytes))
|
||||
}
|
||||
|
||||
return eb, nil
|
||||
}
|
||||
|
||||
func (w *AuthenticationWriter) writeStream(mb buf.MultiBuffer) error {
|
||||
defer buf.ReleaseMulti(mb)
|
||||
|
||||
var maxPadding int32
|
||||
if w.padding != nil {
|
||||
maxPadding = int32(w.padding.MaxPaddingLen())
|
||||
}
|
||||
|
||||
payloadSize := buf.Size - int32(w.auth.Overhead()) - w.sizeParser.SizeBytes() - maxPadding
|
||||
mb2Write := make(buf.MultiBuffer, 0, len(mb)+10)
|
||||
|
||||
temp := buf.New()
|
||||
defer temp.Release()
|
||||
|
||||
rawBytes := temp.Extend(payloadSize)
|
||||
|
||||
for {
|
||||
nb, nBytes := buf.SplitBytes(mb, rawBytes)
|
||||
mb = nb
|
||||
|
||||
eb, err := w.seal(rawBytes[:nBytes])
|
||||
|
||||
if err != nil {
|
||||
buf.ReleaseMulti(mb2Write)
|
||||
return err
|
||||
}
|
||||
mb2Write = append(mb2Write, eb)
|
||||
if mb.IsEmpty() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return w.writer.WriteMultiBuffer(mb2Write)
|
||||
}
|
||||
|
||||
func (w *AuthenticationWriter) writePacket(mb buf.MultiBuffer) error {
|
||||
defer buf.ReleaseMulti(mb)
|
||||
|
||||
mb2Write := make(buf.MultiBuffer, 0, len(mb)+1)
|
||||
|
||||
for _, b := range mb {
|
||||
if b.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
|
||||
eb, err := w.seal(b.Bytes())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
mb2Write = append(mb2Write, eb)
|
||||
}
|
||||
|
||||
if mb2Write.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return w.writer.WriteMultiBuffer(mb2Write)
|
||||
}
|
||||
|
||||
// WriteMultiBuffer implements buf.Writer.
|
||||
func (w *AuthenticationWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
if mb.IsEmpty() {
|
||||
eb, err := w.seal([]byte{})
|
||||
common.Must(err)
|
||||
return w.writer.WriteMultiBuffer(buf.MultiBuffer{eb})
|
||||
}
|
||||
|
||||
if w.transferType == protocol.TransferTypeStream {
|
||||
return w.writeStream(mb)
|
||||
}
|
||||
|
||||
return w.writePacket(mb)
|
||||
}
|
143
common/crypto/auth_test.go
Normal file
143
common/crypto/auth_test.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
package crypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
. "github.com/xtls/xray-core/v1/common/crypto"
|
||||
"github.com/xtls/xray-core/v1/common/protocol"
|
||||
)
|
||||
|
||||
func TestAuthenticationReaderWriter(t *testing.T) {
|
||||
key := make([]byte, 16)
|
||||
rand.Read(key)
|
||||
block, err := aes.NewCipher(key)
|
||||
common.Must(err)
|
||||
|
||||
aead, err := cipher.NewGCM(block)
|
||||
common.Must(err)
|
||||
|
||||
const payloadSize = 1024 * 80
|
||||
rawPayload := make([]byte, payloadSize)
|
||||
rand.Read(rawPayload)
|
||||
|
||||
payload := buf.MergeBytes(nil, rawPayload)
|
||||
|
||||
cache := bytes.NewBuffer(nil)
|
||||
iv := make([]byte, 12)
|
||||
rand.Read(iv)
|
||||
|
||||
writer := NewAuthenticationWriter(&AEADAuthenticator{
|
||||
AEAD: aead,
|
||||
NonceGenerator: GenerateStaticBytes(iv),
|
||||
AdditionalDataGenerator: GenerateEmptyBytes(),
|
||||
}, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream, nil)
|
||||
|
||||
common.Must(writer.WriteMultiBuffer(payload))
|
||||
if cache.Len() <= 1024*80 {
|
||||
t.Error("cache len: ", cache.Len())
|
||||
}
|
||||
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{}))
|
||||
|
||||
reader := NewAuthenticationReader(&AEADAuthenticator{
|
||||
AEAD: aead,
|
||||
NonceGenerator: GenerateStaticBytes(iv),
|
||||
AdditionalDataGenerator: GenerateEmptyBytes(),
|
||||
}, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream, nil)
|
||||
|
||||
var mb buf.MultiBuffer
|
||||
|
||||
for mb.Len() < payloadSize {
|
||||
mb2, err := reader.ReadMultiBuffer()
|
||||
common.Must(err)
|
||||
|
||||
mb, _ = buf.MergeMulti(mb, mb2)
|
||||
}
|
||||
|
||||
if mb.Len() != payloadSize {
|
||||
t.Error("mb len: ", mb.Len())
|
||||
}
|
||||
|
||||
mbContent := make([]byte, payloadSize)
|
||||
buf.SplitBytes(mb, mbContent)
|
||||
if r := cmp.Diff(mbContent, rawPayload); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
|
||||
_, err = reader.ReadMultiBuffer()
|
||||
if err != io.EOF {
|
||||
t.Error("error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticationReaderWriterPacket(t *testing.T) {
|
||||
key := make([]byte, 16)
|
||||
common.Must2(rand.Read(key))
|
||||
block, err := aes.NewCipher(key)
|
||||
common.Must(err)
|
||||
|
||||
aead, err := cipher.NewGCM(block)
|
||||
common.Must(err)
|
||||
|
||||
cache := buf.New()
|
||||
iv := make([]byte, 12)
|
||||
rand.Read(iv)
|
||||
|
||||
writer := NewAuthenticationWriter(&AEADAuthenticator{
|
||||
AEAD: aead,
|
||||
NonceGenerator: GenerateStaticBytes(iv),
|
||||
AdditionalDataGenerator: GenerateEmptyBytes(),
|
||||
}, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket, nil)
|
||||
|
||||
var payload buf.MultiBuffer
|
||||
pb1 := buf.New()
|
||||
pb1.Write([]byte("abcd"))
|
||||
payload = append(payload, pb1)
|
||||
|
||||
pb2 := buf.New()
|
||||
pb2.Write([]byte("efgh"))
|
||||
payload = append(payload, pb2)
|
||||
|
||||
common.Must(writer.WriteMultiBuffer(payload))
|
||||
if cache.Len() == 0 {
|
||||
t.Error("cache len: ", cache.Len())
|
||||
}
|
||||
|
||||
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{}))
|
||||
|
||||
reader := NewAuthenticationReader(&AEADAuthenticator{
|
||||
AEAD: aead,
|
||||
NonceGenerator: GenerateStaticBytes(iv),
|
||||
AdditionalDataGenerator: GenerateEmptyBytes(),
|
||||
}, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket, nil)
|
||||
|
||||
mb, err := reader.ReadMultiBuffer()
|
||||
common.Must(err)
|
||||
|
||||
mb, b1 := buf.SplitFirst(mb)
|
||||
if b1.String() != "abcd" {
|
||||
t.Error("b1: ", b1.String())
|
||||
}
|
||||
|
||||
mb, b2 := buf.SplitFirst(mb)
|
||||
if b2.String() != "efgh" {
|
||||
t.Error("b2: ", b2.String())
|
||||
}
|
||||
|
||||
if !mb.IsEmpty() {
|
||||
t.Error("not empty")
|
||||
}
|
||||
|
||||
_, err = reader.ReadMultiBuffer()
|
||||
if err != io.EOF {
|
||||
t.Error("error: ", err)
|
||||
}
|
||||
}
|
50
common/crypto/benchmark_test.go
Normal file
50
common/crypto/benchmark_test.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package crypto_test
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"testing"
|
||||
|
||||
. "github.com/xtls/xray-core/v1/common/crypto"
|
||||
)
|
||||
|
||||
const benchSize = 1024 * 1024
|
||||
|
||||
func benchmarkStream(b *testing.B, c cipher.Stream) {
|
||||
b.SetBytes(benchSize)
|
||||
input := make([]byte, benchSize)
|
||||
output := make([]byte, benchSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.XORKeyStream(output, input)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChaCha20(b *testing.B) {
|
||||
key := make([]byte, 32)
|
||||
nonce := make([]byte, 8)
|
||||
c := NewChaCha20Stream(key, nonce)
|
||||
benchmarkStream(b, c)
|
||||
}
|
||||
|
||||
func BenchmarkChaCha20IETF(b *testing.B) {
|
||||
key := make([]byte, 32)
|
||||
nonce := make([]byte, 12)
|
||||
c := NewChaCha20Stream(key, nonce)
|
||||
benchmarkStream(b, c)
|
||||
}
|
||||
|
||||
func BenchmarkAESEncryption(b *testing.B) {
|
||||
key := make([]byte, 32)
|
||||
iv := make([]byte, 16)
|
||||
c := NewAesEncryptionStream(key, iv)
|
||||
|
||||
benchmarkStream(b, c)
|
||||
}
|
||||
|
||||
func BenchmarkAESDecryption(b *testing.B) {
|
||||
key := make([]byte, 32)
|
||||
iv := make([]byte, 16)
|
||||
c := NewAesDecryptionStream(key, iv)
|
||||
|
||||
benchmarkStream(b, c)
|
||||
}
|
13
common/crypto/chacha20.go
Normal file
13
common/crypto/chacha20.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/crypto/internal"
|
||||
)
|
||||
|
||||
// NewChaCha20Stream creates a new Chacha20 encryption/descryption stream based on give key and IV.
|
||||
// Caller must ensure the length of key is 32 bytes, and length of IV is either 8 or 12 bytes.
|
||||
func NewChaCha20Stream(key []byte, iv []byte) cipher.Stream {
|
||||
return internal.NewChaCha20Stream(key, iv, 20)
|
||||
}
|
77
common/crypto/chacha20_test.go
Normal file
77
common/crypto/chacha20_test.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package crypto_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
. "github.com/xtls/xray-core/v1/common/crypto"
|
||||
)
|
||||
|
||||
func mustDecodeHex(s string) []byte {
|
||||
b, err := hex.DecodeString(s)
|
||||
common.Must(err)
|
||||
return b
|
||||
}
|
||||
|
||||
func TestChaCha20Stream(t *testing.T) {
|
||||
var cases = []struct {
|
||||
key []byte
|
||||
iv []byte
|
||||
output []byte
|
||||
}{
|
||||
{
|
||||
key: mustDecodeHex("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
iv: mustDecodeHex("0000000000000000"),
|
||||
output: mustDecodeHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" +
|
||||
"da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586" +
|
||||
"9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed" +
|
||||
"29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f"),
|
||||
},
|
||||
{
|
||||
key: mustDecodeHex("5555555555555555555555555555555555555555555555555555555555555555"),
|
||||
iv: mustDecodeHex("5555555555555555"),
|
||||
output: mustDecodeHex("bea9411aa453c5434a5ae8c92862f564396855a9ea6e22d6d3b50ae1b3663311" +
|
||||
"a4a3606c671d605ce16c3aece8e61ea145c59775017bee2fa6f88afc758069f7" +
|
||||
"e0b8f676e644216f4d2a3422d7fa36c6c4931aca950e9da42788e6d0b6d1cd83" +
|
||||
"8ef652e97b145b14871eae6c6804c7004db5ac2fce4c68c726d004b10fcaba86"),
|
||||
},
|
||||
{
|
||||
key: mustDecodeHex("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
iv: mustDecodeHex("000000000000000000000000"),
|
||||
output: mustDecodeHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"),
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
s := NewChaCha20Stream(c.key, c.iv)
|
||||
input := make([]byte, len(c.output))
|
||||
actualOutout := make([]byte, len(c.output))
|
||||
s.XORKeyStream(actualOutout, input)
|
||||
if r := cmp.Diff(c.output, actualOutout); r != "" {
|
||||
t.Fatal(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChaCha20Decoding(t *testing.T) {
|
||||
key := make([]byte, 32)
|
||||
common.Must2(rand.Read(key))
|
||||
iv := make([]byte, 8)
|
||||
common.Must2(rand.Read(iv))
|
||||
stream := NewChaCha20Stream(key, iv)
|
||||
|
||||
payload := make([]byte, 1024)
|
||||
common.Must2(rand.Read(payload))
|
||||
|
||||
x := make([]byte, len(payload))
|
||||
stream.XORKeyStream(x, payload)
|
||||
|
||||
stream2 := NewChaCha20Stream(key, iv)
|
||||
stream2.XORKeyStream(x, x)
|
||||
if r := cmp.Diff(x, payload); r != "" {
|
||||
t.Fatal(r)
|
||||
}
|
||||
}
|
160
common/crypto/chunk.go
Normal file
160
common/crypto/chunk.go
Normal file
|
@ -0,0 +1,160 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
)
|
||||
|
||||
// ChunkSizeDecoder is a utility class to decode size value from bytes.
|
||||
type ChunkSizeDecoder interface {
|
||||
SizeBytes() int32
|
||||
Decode([]byte) (uint16, error)
|
||||
}
|
||||
|
||||
// ChunkSizeEncoder is a utility class to encode size value into bytes.
|
||||
type ChunkSizeEncoder interface {
|
||||
SizeBytes() int32
|
||||
Encode(uint16, []byte) []byte
|
||||
}
|
||||
|
||||
type PaddingLengthGenerator interface {
|
||||
MaxPaddingLen() uint16
|
||||
NextPaddingLen() uint16
|
||||
}
|
||||
|
||||
type PlainChunkSizeParser struct{}
|
||||
|
||||
func (PlainChunkSizeParser) SizeBytes() int32 {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (PlainChunkSizeParser) Encode(size uint16, b []byte) []byte {
|
||||
binary.BigEndian.PutUint16(b, size)
|
||||
return b[:2]
|
||||
}
|
||||
|
||||
func (PlainChunkSizeParser) Decode(b []byte) (uint16, error) {
|
||||
return binary.BigEndian.Uint16(b), nil
|
||||
}
|
||||
|
||||
type AEADChunkSizeParser struct {
|
||||
Auth *AEADAuthenticator
|
||||
}
|
||||
|
||||
func (p *AEADChunkSizeParser) SizeBytes() int32 {
|
||||
return 2 + int32(p.Auth.Overhead())
|
||||
}
|
||||
|
||||
func (p *AEADChunkSizeParser) Encode(size uint16, b []byte) []byte {
|
||||
binary.BigEndian.PutUint16(b, size-uint16(p.Auth.Overhead()))
|
||||
b, err := p.Auth.Seal(b[:0], b[:2])
|
||||
common.Must(err)
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *AEADChunkSizeParser) Decode(b []byte) (uint16, error) {
|
||||
b, err := p.Auth.Open(b[:0], b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.BigEndian.Uint16(b) + uint16(p.Auth.Overhead()), nil
|
||||
}
|
||||
|
||||
type ChunkStreamReader struct {
|
||||
sizeDecoder ChunkSizeDecoder
|
||||
reader *buf.BufferedReader
|
||||
|
||||
buffer []byte
|
||||
leftOverSize int32
|
||||
maxNumChunk uint32
|
||||
numChunk uint32
|
||||
}
|
||||
|
||||
func NewChunkStreamReader(sizeDecoder ChunkSizeDecoder, reader io.Reader) *ChunkStreamReader {
|
||||
return NewChunkStreamReaderWithChunkCount(sizeDecoder, reader, 0)
|
||||
}
|
||||
|
||||
func NewChunkStreamReaderWithChunkCount(sizeDecoder ChunkSizeDecoder, reader io.Reader, maxNumChunk uint32) *ChunkStreamReader {
|
||||
r := &ChunkStreamReader{
|
||||
sizeDecoder: sizeDecoder,
|
||||
buffer: make([]byte, sizeDecoder.SizeBytes()),
|
||||
maxNumChunk: maxNumChunk,
|
||||
}
|
||||
if breader, ok := reader.(*buf.BufferedReader); ok {
|
||||
r.reader = breader
|
||||
} else {
|
||||
r.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *ChunkStreamReader) readSize() (uint16, error) {
|
||||
if _, err := io.ReadFull(r.reader, r.buffer); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return r.sizeDecoder.Decode(r.buffer)
|
||||
}
|
||||
|
||||
func (r *ChunkStreamReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
size := r.leftOverSize
|
||||
if size == 0 {
|
||||
r.numChunk++
|
||||
if r.maxNumChunk > 0 && r.numChunk > r.maxNumChunk {
|
||||
return nil, io.EOF
|
||||
}
|
||||
nextSize, err := r.readSize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nextSize == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
size = int32(nextSize)
|
||||
}
|
||||
r.leftOverSize = size
|
||||
|
||||
mb, err := r.reader.ReadAtMost(size)
|
||||
if !mb.IsEmpty() {
|
||||
r.leftOverSize -= mb.Len()
|
||||
return mb, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type ChunkStreamWriter struct {
|
||||
sizeEncoder ChunkSizeEncoder
|
||||
writer buf.Writer
|
||||
}
|
||||
|
||||
func NewChunkStreamWriter(sizeEncoder ChunkSizeEncoder, writer io.Writer) *ChunkStreamWriter {
|
||||
return &ChunkStreamWriter{
|
||||
sizeEncoder: sizeEncoder,
|
||||
writer: buf.NewWriter(writer),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ChunkStreamWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
const sliceSize = 8192
|
||||
mbLen := mb.Len()
|
||||
mb2Write := make(buf.MultiBuffer, 0, mbLen/buf.Size+mbLen/sliceSize+2)
|
||||
|
||||
for {
|
||||
mb2, slice := buf.SplitSize(mb, sliceSize)
|
||||
mb = mb2
|
||||
|
||||
b := buf.New()
|
||||
w.sizeEncoder.Encode(uint16(slice.Len()), b.Extend(w.sizeEncoder.SizeBytes()))
|
||||
mb2Write = append(mb2Write, b)
|
||||
mb2Write = append(mb2Write, slice...)
|
||||
|
||||
if mb.IsEmpty() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return w.writer.WriteMultiBuffer(mb2Write)
|
||||
}
|
51
common/crypto/chunk_test.go
Normal file
51
common/crypto/chunk_test.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package crypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
. "github.com/xtls/xray-core/v1/common/crypto"
|
||||
)
|
||||
|
||||
func TestChunkStreamIO(t *testing.T) {
|
||||
cache := bytes.NewBuffer(make([]byte, 0, 8192))
|
||||
|
||||
writer := NewChunkStreamWriter(PlainChunkSizeParser{}, cache)
|
||||
reader := NewChunkStreamReader(PlainChunkSizeParser{}, cache)
|
||||
|
||||
b := buf.New()
|
||||
b.WriteString("abcd")
|
||||
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b}))
|
||||
|
||||
b = buf.New()
|
||||
b.WriteString("efg")
|
||||
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b}))
|
||||
|
||||
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{}))
|
||||
|
||||
if cache.Len() != 13 {
|
||||
t.Fatalf("Cache length is %d, want 13", cache.Len())
|
||||
}
|
||||
|
||||
mb, err := reader.ReadMultiBuffer()
|
||||
common.Must(err)
|
||||
|
||||
if s := mb.String(); s != "abcd" {
|
||||
t.Error("content: ", s)
|
||||
}
|
||||
|
||||
mb, err = reader.ReadMultiBuffer()
|
||||
common.Must(err)
|
||||
|
||||
if s := mb.String(); s != "efg" {
|
||||
t.Error("content: ", s)
|
||||
}
|
||||
|
||||
_, err = reader.ReadMultiBuffer()
|
||||
if err != io.EOF {
|
||||
t.Error("error: ", err)
|
||||
}
|
||||
}
|
4
common/crypto/crypto.go
Normal file
4
common/crypto/crypto.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Package crypto provides common crypto libraries for Xray.
|
||||
package crypto // import "github.com/xtls/xray-core/v1/common/crypto"
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
9
common/crypto/errors.generated.go
Normal file
9
common/crypto/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package crypto
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
80
common/crypto/internal/chacha.go
Normal file
80
common/crypto/internal/chacha.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package internal
|
||||
|
||||
//go:generate go run chacha_core_gen.go
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
wordSize = 4 // the size of ChaCha20's words
|
||||
stateSize = 16 // the size of ChaCha20's state, in words
|
||||
blockSize = stateSize * wordSize // the size of ChaCha20's block, in bytes
|
||||
)
|
||||
|
||||
type ChaCha20Stream struct {
|
||||
state [stateSize]uint32 // the state as an array of 16 32-bit words
|
||||
block [blockSize]byte // the keystream as an array of 64 bytes
|
||||
offset int // the offset of used bytes in block
|
||||
rounds int
|
||||
}
|
||||
|
||||
func NewChaCha20Stream(key []byte, nonce []byte, rounds int) *ChaCha20Stream {
|
||||
s := new(ChaCha20Stream)
|
||||
// the magic constants for 256-bit keys
|
||||
s.state[0] = 0x61707865
|
||||
s.state[1] = 0x3320646e
|
||||
s.state[2] = 0x79622d32
|
||||
s.state[3] = 0x6b206574
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
s.state[i+4] = binary.LittleEndian.Uint32(key[i*4 : i*4+4])
|
||||
}
|
||||
|
||||
switch len(nonce) {
|
||||
case 8:
|
||||
s.state[14] = binary.LittleEndian.Uint32(nonce[0:])
|
||||
s.state[15] = binary.LittleEndian.Uint32(nonce[4:])
|
||||
case 12:
|
||||
s.state[13] = binary.LittleEndian.Uint32(nonce[0:4])
|
||||
s.state[14] = binary.LittleEndian.Uint32(nonce[4:8])
|
||||
s.state[15] = binary.LittleEndian.Uint32(nonce[8:12])
|
||||
default:
|
||||
panic("bad nonce length")
|
||||
}
|
||||
|
||||
s.rounds = rounds
|
||||
ChaCha20Block(&s.state, s.block[:], s.rounds)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *ChaCha20Stream) XORKeyStream(dst, src []byte) {
|
||||
// Stride over the input in 64-byte blocks, minus the amount of keystream
|
||||
// previously used. This will produce best results when processing blocks
|
||||
// of a size evenly divisible by 64.
|
||||
i := 0
|
||||
max := len(src)
|
||||
for i < max {
|
||||
gap := blockSize - s.offset
|
||||
|
||||
limit := i + gap
|
||||
if limit > max {
|
||||
limit = max
|
||||
}
|
||||
|
||||
o := s.offset
|
||||
for j := i; j < limit; j++ {
|
||||
dst[j] = src[j] ^ s.block[o]
|
||||
o++
|
||||
}
|
||||
|
||||
i += gap
|
||||
s.offset = o
|
||||
|
||||
if o == blockSize {
|
||||
s.offset = 0
|
||||
s.state[12]++
|
||||
ChaCha20Block(&s.state, s.block[:], s.rounds)
|
||||
}
|
||||
}
|
||||
}
|
123
common/crypto/internal/chacha_core.generated.go
Normal file
123
common/crypto/internal/chacha_core.generated.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package internal
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
func ChaCha20Block(s *[16]uint32, out []byte, rounds int) {
|
||||
var x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 = s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15]
|
||||
for i := 0; i < rounds; i += 2 {
|
||||
var x uint32
|
||||
|
||||
x0 += x4
|
||||
x = x12 ^ x0
|
||||
x12 = (x << 16) | (x >> (32 - 16))
|
||||
x8 += x12
|
||||
x = x4 ^ x8
|
||||
x4 = (x << 12) | (x >> (32 - 12))
|
||||
x0 += x4
|
||||
x = x12 ^ x0
|
||||
x12 = (x << 8) | (x >> (32 - 8))
|
||||
x8 += x12
|
||||
x = x4 ^ x8
|
||||
x4 = (x << 7) | (x >> (32 - 7))
|
||||
x1 += x5
|
||||
x = x13 ^ x1
|
||||
x13 = (x << 16) | (x >> (32 - 16))
|
||||
x9 += x13
|
||||
x = x5 ^ x9
|
||||
x5 = (x << 12) | (x >> (32 - 12))
|
||||
x1 += x5
|
||||
x = x13 ^ x1
|
||||
x13 = (x << 8) | (x >> (32 - 8))
|
||||
x9 += x13
|
||||
x = x5 ^ x9
|
||||
x5 = (x << 7) | (x >> (32 - 7))
|
||||
x2 += x6
|
||||
x = x14 ^ x2
|
||||
x14 = (x << 16) | (x >> (32 - 16))
|
||||
x10 += x14
|
||||
x = x6 ^ x10
|
||||
x6 = (x << 12) | (x >> (32 - 12))
|
||||
x2 += x6
|
||||
x = x14 ^ x2
|
||||
x14 = (x << 8) | (x >> (32 - 8))
|
||||
x10 += x14
|
||||
x = x6 ^ x10
|
||||
x6 = (x << 7) | (x >> (32 - 7))
|
||||
x3 += x7
|
||||
x = x15 ^ x3
|
||||
x15 = (x << 16) | (x >> (32 - 16))
|
||||
x11 += x15
|
||||
x = x7 ^ x11
|
||||
x7 = (x << 12) | (x >> (32 - 12))
|
||||
x3 += x7
|
||||
x = x15 ^ x3
|
||||
x15 = (x << 8) | (x >> (32 - 8))
|
||||
x11 += x15
|
||||
x = x7 ^ x11
|
||||
x7 = (x << 7) | (x >> (32 - 7))
|
||||
x0 += x5
|
||||
x = x15 ^ x0
|
||||
x15 = (x << 16) | (x >> (32 - 16))
|
||||
x10 += x15
|
||||
x = x5 ^ x10
|
||||
x5 = (x << 12) | (x >> (32 - 12))
|
||||
x0 += x5
|
||||
x = x15 ^ x0
|
||||
x15 = (x << 8) | (x >> (32 - 8))
|
||||
x10 += x15
|
||||
x = x5 ^ x10
|
||||
x5 = (x << 7) | (x >> (32 - 7))
|
||||
x1 += x6
|
||||
x = x12 ^ x1
|
||||
x12 = (x << 16) | (x >> (32 - 16))
|
||||
x11 += x12
|
||||
x = x6 ^ x11
|
||||
x6 = (x << 12) | (x >> (32 - 12))
|
||||
x1 += x6
|
||||
x = x12 ^ x1
|
||||
x12 = (x << 8) | (x >> (32 - 8))
|
||||
x11 += x12
|
||||
x = x6 ^ x11
|
||||
x6 = (x << 7) | (x >> (32 - 7))
|
||||
x2 += x7
|
||||
x = x13 ^ x2
|
||||
x13 = (x << 16) | (x >> (32 - 16))
|
||||
x8 += x13
|
||||
x = x7 ^ x8
|
||||
x7 = (x << 12) | (x >> (32 - 12))
|
||||
x2 += x7
|
||||
x = x13 ^ x2
|
||||
x13 = (x << 8) | (x >> (32 - 8))
|
||||
x8 += x13
|
||||
x = x7 ^ x8
|
||||
x7 = (x << 7) | (x >> (32 - 7))
|
||||
x3 += x4
|
||||
x = x14 ^ x3
|
||||
x14 = (x << 16) | (x >> (32 - 16))
|
||||
x9 += x14
|
||||
x = x4 ^ x9
|
||||
x4 = (x << 12) | (x >> (32 - 12))
|
||||
x3 += x4
|
||||
x = x14 ^ x3
|
||||
x14 = (x << 8) | (x >> (32 - 8))
|
||||
x9 += x14
|
||||
x = x4 ^ x9
|
||||
x4 = (x << 7) | (x >> (32 - 7))
|
||||
}
|
||||
binary.LittleEndian.PutUint32(out[0:4], s[0]+x0)
|
||||
binary.LittleEndian.PutUint32(out[4:8], s[1]+x1)
|
||||
binary.LittleEndian.PutUint32(out[8:12], s[2]+x2)
|
||||
binary.LittleEndian.PutUint32(out[12:16], s[3]+x3)
|
||||
binary.LittleEndian.PutUint32(out[16:20], s[4]+x4)
|
||||
binary.LittleEndian.PutUint32(out[20:24], s[5]+x5)
|
||||
binary.LittleEndian.PutUint32(out[24:28], s[6]+x6)
|
||||
binary.LittleEndian.PutUint32(out[28:32], s[7]+x7)
|
||||
binary.LittleEndian.PutUint32(out[32:36], s[8]+x8)
|
||||
binary.LittleEndian.PutUint32(out[36:40], s[9]+x9)
|
||||
binary.LittleEndian.PutUint32(out[40:44], s[10]+x10)
|
||||
binary.LittleEndian.PutUint32(out[44:48], s[11]+x11)
|
||||
binary.LittleEndian.PutUint32(out[48:52], s[12]+x12)
|
||||
binary.LittleEndian.PutUint32(out[52:56], s[13]+x13)
|
||||
binary.LittleEndian.PutUint32(out[56:60], s[14]+x14)
|
||||
binary.LittleEndian.PutUint32(out[60:64], s[15]+x15)
|
||||
}
|
69
common/crypto/internal/chacha_core_gen.go
Normal file
69
common/crypto/internal/chacha_core_gen.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
// +build generate
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func writeQuarterRound(file *os.File, a, b, c, d int) {
|
||||
add := "x%d+=x%d\n"
|
||||
xor := "x=x%d^x%d\n"
|
||||
rotate := "x%d=(x << %d) | (x >> (32 - %d))\n"
|
||||
|
||||
fmt.Fprintf(file, add, a, b)
|
||||
fmt.Fprintf(file, xor, d, a)
|
||||
fmt.Fprintf(file, rotate, d, 16, 16)
|
||||
|
||||
fmt.Fprintf(file, add, c, d)
|
||||
fmt.Fprintf(file, xor, b, c)
|
||||
fmt.Fprintf(file, rotate, b, 12, 12)
|
||||
|
||||
fmt.Fprintf(file, add, a, b)
|
||||
fmt.Fprintf(file, xor, d, a)
|
||||
fmt.Fprintf(file, rotate, d, 8, 8)
|
||||
|
||||
fmt.Fprintf(file, add, c, d)
|
||||
fmt.Fprintf(file, xor, b, c)
|
||||
fmt.Fprintf(file, rotate, b, 7, 7)
|
||||
}
|
||||
|
||||
func writeChacha20Block(file *os.File) {
|
||||
fmt.Fprintln(file, `
|
||||
func ChaCha20Block(s *[16]uint32, out []byte, rounds int) {
|
||||
var x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15 = s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[8],s[9],s[10],s[11],s[12],s[13],s[14],s[15]
|
||||
for i := 0; i < rounds; i+=2 {
|
||||
var x uint32
|
||||
`)
|
||||
|
||||
writeQuarterRound(file, 0, 4, 8, 12)
|
||||
writeQuarterRound(file, 1, 5, 9, 13)
|
||||
writeQuarterRound(file, 2, 6, 10, 14)
|
||||
writeQuarterRound(file, 3, 7, 11, 15)
|
||||
writeQuarterRound(file, 0, 5, 10, 15)
|
||||
writeQuarterRound(file, 1, 6, 11, 12)
|
||||
writeQuarterRound(file, 2, 7, 8, 13)
|
||||
writeQuarterRound(file, 3, 4, 9, 14)
|
||||
fmt.Fprintln(file, "}")
|
||||
for i := 0; i < 16; i++ {
|
||||
fmt.Fprintf(file, "binary.LittleEndian.PutUint32(out[%d:%d], s[%d]+x%d)\n", i*4, i*4+4, i, i)
|
||||
}
|
||||
fmt.Fprintln(file, "}")
|
||||
fmt.Fprintln(file)
|
||||
}
|
||||
|
||||
func main() {
|
||||
file, err := os.OpenFile("chacha_core.generated.go", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate chacha_core.go: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fmt.Fprintln(file, "package internal")
|
||||
fmt.Fprintln(file)
|
||||
fmt.Fprintln(file, "import \"encoding/binary\"")
|
||||
fmt.Fprintln(file)
|
||||
writeChacha20Block(file)
|
||||
}
|
66
common/crypto/io.go
Normal file
66
common/crypto/io.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"io"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
)
|
||||
|
||||
type CryptionReader struct {
|
||||
stream cipher.Stream
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func NewCryptionReader(stream cipher.Stream, reader io.Reader) *CryptionReader {
|
||||
return &CryptionReader{
|
||||
stream: stream,
|
||||
reader: reader,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CryptionReader) Read(data []byte) (int, error) {
|
||||
nBytes, err := r.reader.Read(data)
|
||||
if nBytes > 0 {
|
||||
r.stream.XORKeyStream(data[:nBytes], data[:nBytes])
|
||||
}
|
||||
return nBytes, err
|
||||
}
|
||||
|
||||
var (
|
||||
_ buf.Writer = (*CryptionWriter)(nil)
|
||||
)
|
||||
|
||||
type CryptionWriter struct {
|
||||
stream cipher.Stream
|
||||
writer io.Writer
|
||||
bufWriter buf.Writer
|
||||
}
|
||||
|
||||
// NewCryptionWriter creates a new CryptionWriter.
|
||||
func NewCryptionWriter(stream cipher.Stream, writer io.Writer) *CryptionWriter {
|
||||
return &CryptionWriter{
|
||||
stream: stream,
|
||||
writer: writer,
|
||||
bufWriter: buf.NewWriter(writer),
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements io.Writer.Write().
|
||||
func (w *CryptionWriter) Write(data []byte) (int, error) {
|
||||
w.stream.XORKeyStream(data, data)
|
||||
|
||||
if err := buf.WriteAllBytes(w.writer, data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// WriteMultiBuffer implements buf.Writer.
|
||||
func (w *CryptionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
for _, b := range mb {
|
||||
w.stream.XORKeyStream(b.Bytes(), b.Bytes())
|
||||
}
|
||||
|
||||
return w.bufWriter.WriteMultiBuffer(mb)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue