mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-31 11:04:14 +03:00
Add OpenVPN, TrustTunnel, Sudoku, inbound managers. Fixes
This commit is contained in:
227
transport/openvpn/cipher.go
Normal file
227
transport/openvpn/cipher.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
const (
|
||||
AESGCMTagSize = 16
|
||||
AESGCMIVSize = 12
|
||||
CBCIVSize = aes.BlockSize
|
||||
)
|
||||
|
||||
type DataCipher interface {
|
||||
Encrypt(header []byte, packetID uint32, payload []byte) ([]byte, error)
|
||||
Decrypt(packet []byte, headerSize int) ([]byte, error)
|
||||
}
|
||||
|
||||
type AEADDataCipher struct {
|
||||
send cipher.AEAD
|
||||
recv cipher.AEAD
|
||||
sendImplicitIV [AESGCMIVSize]byte
|
||||
recvImplicitIV [AESGCMIVSize]byte
|
||||
}
|
||||
|
||||
func NewAEADCipher(keys *KeyMaterial, cipherName string) (*AEADDataCipher, error) {
|
||||
var send, recv cipher.AEAD
|
||||
var err error
|
||||
if cipherName == CipherCHACHA20POLY {
|
||||
send, err = chacha20poly1305.New(keys.SendCipherKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recv, err = chacha20poly1305.New(keys.RecvCipherKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
sendBlock, err := aes.NewCipher(keys.SendCipherKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recvBlock, err := aes.NewCipher(keys.RecvCipherKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
send, err = cipher.NewGCMWithTagSize(sendBlock, AESGCMTagSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recv, err = cipher.NewGCMWithTagSize(recvBlock, AESGCMTagSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(keys.SendHMACKey) < AESGCMIVSize-4 || len(keys.RecvHMACKey) < AESGCMIVSize-4 {
|
||||
return nil, errors.New("openvpn implicit IV keys are too short")
|
||||
}
|
||||
g := &AEADDataCipher{send: send, recv: recv}
|
||||
copy(g.sendImplicitIV[4:], keys.SendHMACKey[:AESGCMIVSize-4])
|
||||
copy(g.recvImplicitIV[4:], keys.RecvHMACKey[:AESGCMIVSize-4])
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *AEADDataCipher) Encrypt(header []byte, packetID uint32, payload []byte) ([]byte, error) {
|
||||
var pidBytes [4]byte
|
||||
binary.BigEndian.PutUint32(pidBytes[:], packetID)
|
||||
nonce := g.nonce(packetID, g.sendImplicitIV)
|
||||
ad := append(header, pidBytes[:]...)
|
||||
sealed := g.send.Seal(nil, nonce[:], payload, ad)
|
||||
out := make([]byte, 0, len(header)+4+len(sealed))
|
||||
out = append(out, header...)
|
||||
out = append(out, pidBytes[:]...)
|
||||
out = append(out, sealed[len(sealed)-AESGCMTagSize:]...)
|
||||
out = append(out, sealed[:len(sealed)-AESGCMTagSize]...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *AEADDataCipher) Decrypt(packet []byte, headerSize int) ([]byte, error) {
|
||||
if len(packet) < headerSize+4+AESGCMTagSize+1 {
|
||||
return nil, errors.New("openvpn gcm data packet too short")
|
||||
}
|
||||
header := packet[:headerSize]
|
||||
pidBytes := packet[headerSize : headerSize+4]
|
||||
tag := packet[headerSize+4 : headerSize+4+AESGCMTagSize]
|
||||
ciphertext := packet[headerSize+4+AESGCMTagSize:]
|
||||
combined := append(ciphertext, tag...)
|
||||
ad := append(header, pidBytes...)
|
||||
nonce := g.nonce(binary.BigEndian.Uint32(pidBytes), g.recvImplicitIV)
|
||||
return g.recv.Open(nil, nonce[:], combined, ad)
|
||||
}
|
||||
|
||||
func (g *AEADDataCipher) nonce(packetID uint32, implicit [AESGCMIVSize]byte) [AESGCMIVSize]byte {
|
||||
nonce := implicit
|
||||
binary.BigEndian.PutUint32(nonce[:4], binary.BigEndian.Uint32(nonce[:4])^packetID)
|
||||
return nonce
|
||||
}
|
||||
|
||||
type CBCDataCipher struct {
|
||||
sendBlock cipher.Block
|
||||
recvBlock cipher.Block
|
||||
sendHMAC []byte
|
||||
recvHMAC []byte
|
||||
newHash func() hash.Hash
|
||||
hmacSize int
|
||||
}
|
||||
|
||||
func NewCBCCipher(keys *KeyMaterial, auth string) (*CBCDataCipher, error) {
|
||||
sendBlock, err := aes.NewCipher(keys.SendCipherKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recvBlock, err := aes.NewCipher(keys.RecvCipherKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var newHash func() hash.Hash
|
||||
var hmacSize int
|
||||
switch auth {
|
||||
case AuthSHA256:
|
||||
newHash = sha256.New
|
||||
hmacSize = sha256.Size
|
||||
case AuthSHA384:
|
||||
newHash = sha512.New384
|
||||
hmacSize = 48
|
||||
case AuthSHA512:
|
||||
newHash = sha512.New
|
||||
hmacSize = sha512.Size
|
||||
default:
|
||||
newHash = sha1.New
|
||||
hmacSize = sha1.Size
|
||||
}
|
||||
return &CBCDataCipher{
|
||||
sendBlock: sendBlock,
|
||||
recvBlock: recvBlock,
|
||||
sendHMAC: cloneBytes(keys.SendHMACKey[:hmacSize]),
|
||||
recvHMAC: cloneBytes(keys.RecvHMACKey[:hmacSize]),
|
||||
newHash: newHash,
|
||||
hmacSize: hmacSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CBCDataCipher) Encrypt(header []byte, packetID uint32, payload []byte) ([]byte, error) {
|
||||
var pidBytes [4]byte
|
||||
binary.BigEndian.PutUint32(pidBytes[:], packetID)
|
||||
plain := append(pidBytes[:], payload...)
|
||||
padLen := aes.BlockSize - (len(plain) % aes.BlockSize)
|
||||
for i := 0; i < padLen; i++ {
|
||||
plain = append(plain, byte(padLen))
|
||||
}
|
||||
iv := make([]byte, CBCIVSize)
|
||||
if _, err := rand.Read(iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct := make([]byte, len(plain))
|
||||
cipher.NewCBCEncrypter(c.sendBlock, iv).CryptBlocks(ct, plain)
|
||||
mac := hmac.New(c.newHash, c.sendHMAC)
|
||||
mac.Write(iv)
|
||||
mac.Write(ct)
|
||||
tag := mac.Sum(nil)
|
||||
out := make([]byte, 0, len(header)+c.hmacSize+CBCIVSize+len(ct))
|
||||
out = append(out, header...)
|
||||
out = append(out, tag...)
|
||||
out = append(out, iv...)
|
||||
out = append(out, ct...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *CBCDataCipher) Decrypt(packet []byte, headerSize int) ([]byte, error) {
|
||||
minSize := headerSize + c.hmacSize + CBCIVSize + aes.BlockSize
|
||||
if len(packet) < minSize {
|
||||
return nil, errors.New("openvpn cbc data packet too short")
|
||||
}
|
||||
tag := packet[headerSize : headerSize+c.hmacSize]
|
||||
iv := packet[headerSize+c.hmacSize : headerSize+c.hmacSize+CBCIVSize]
|
||||
ct := packet[headerSize+c.hmacSize+CBCIVSize:]
|
||||
if len(ct)%aes.BlockSize != 0 {
|
||||
return nil, errors.New("openvpn cbc ciphertext not block-aligned")
|
||||
}
|
||||
mac := hmac.New(c.newHash, c.recvHMAC)
|
||||
mac.Write(iv)
|
||||
mac.Write(ct)
|
||||
if !hmac.Equal(tag, mac.Sum(nil)) {
|
||||
return nil, errors.New("openvpn cbc hmac verification failed")
|
||||
}
|
||||
plain := make([]byte, len(ct))
|
||||
cipher.NewCBCDecrypter(c.recvBlock, iv).CryptBlocks(plain, ct)
|
||||
padLen := int(plain[len(plain)-1])
|
||||
if padLen < 1 || padLen > aes.BlockSize {
|
||||
return nil, errors.New("openvpn cbc invalid padding")
|
||||
}
|
||||
plain = plain[:len(plain)-padLen]
|
||||
if len(plain) < 4 {
|
||||
return nil, errors.New("openvpn cbc payload too short")
|
||||
}
|
||||
return plain[4:], nil
|
||||
}
|
||||
|
||||
func CipherKeyLength(cipher string) int {
|
||||
switch cipher {
|
||||
case CipherAES128GCM, CipherAES128CBC:
|
||||
return 16
|
||||
case CipherAES192GCM, CipherAES192CBC:
|
||||
return 24
|
||||
default:
|
||||
return 32
|
||||
}
|
||||
}
|
||||
|
||||
func IsAEAD(cipher string) bool {
|
||||
switch cipher {
|
||||
case CipherAES128GCM, CipherAES192GCM, CipherAES256GCM, CipherCHACHA20POLY:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user