|
|
@ -2,12 +2,15 @@ package pnyx
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"bytes"
|
|
|
|
|
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
|
|
|
"crypto/cipher"
|
|
|
|
"crypto/ed25519"
|
|
|
|
"crypto/ed25519"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/sha512"
|
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/binary"
|
|
|
|
"encoding/binary"
|
|
|
|
|
|
|
|
"hash/crc32"
|
|
|
|
"io"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
mrand "math/rand"
|
|
|
|
"net"
|
|
|
|
"net"
|
|
|
|
|
|
|
|
|
|
|
|
"fmt"
|
|
|
|
"fmt"
|
|
|
@ -19,6 +22,7 @@ import (
|
|
|
|
type PacketType uint8
|
|
|
|
type PacketType uint8
|
|
|
|
const (
|
|
|
|
const (
|
|
|
|
ID_LENGTH = 16
|
|
|
|
ID_LENGTH = 16
|
|
|
|
|
|
|
|
IV_LENGTH = aes.BlockSize
|
|
|
|
PUBKEY_LENGTH = 32
|
|
|
|
PUBKEY_LENGTH = 32
|
|
|
|
ECDH_LENGTH = 32
|
|
|
|
ECDH_LENGTH = 32
|
|
|
|
SIGNATURE_LENGTH = 64
|
|
|
|
SIGNATURE_LENGTH = 64
|
|
|
@ -27,11 +31,11 @@ const (
|
|
|
|
|
|
|
|
|
|
|
|
SESSION_OPEN_LENGTH = PUBKEY_LENGTH + ECDH_LENGTH + SIGNATURE_LENGTH
|
|
|
|
SESSION_OPEN_LENGTH = PUBKEY_LENGTH + ECDH_LENGTH + SIGNATURE_LENGTH
|
|
|
|
SESSION_CONNECT_LENGTH = 2 + HMAC_LENGTH // + return addr string length
|
|
|
|
SESSION_CONNECT_LENGTH = 2 + HMAC_LENGTH // + return addr string length
|
|
|
|
|
|
|
|
SESSION_CLOSE_LENGTH = HMAC_LENGTH
|
|
|
|
|
|
|
|
|
|
|
|
SESSION_OPEN PacketType = iota
|
|
|
|
SESSION_OPEN PacketType = iota
|
|
|
|
SESSION_CONNECT
|
|
|
|
SESSION_CONNECT
|
|
|
|
SESSION_CLOSE
|
|
|
|
SESSION_CLOSE
|
|
|
|
SESSION_CLOSED
|
|
|
|
|
|
|
|
SESSION_DATA
|
|
|
|
SESSION_DATA
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
@ -82,9 +86,9 @@ func NewSessionOpen(key ed25519.PrivateKey) ([]byte, ed25519.PrivateKey, error)
|
|
|
|
return packet, private, nil
|
|
|
|
return packet, private, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func ParseSessionOpen(session_open []byte) (ed25519.PublicKey, ed25519.PublicKey, error) {
|
|
|
|
func ParseSessionOpen(ecdh ed25519.PrivateKey, session_open []byte) (Session, error) {
|
|
|
|
if len(session_open) != SESSION_OPEN_LENGTH {
|
|
|
|
if len(session_open) != SESSION_OPEN_LENGTH {
|
|
|
|
return nil, nil, fmt.Errorf("Bad SESSION_OPEN length: %d/%d", len(session_open), SESSION_OPEN_LENGTH)
|
|
|
|
return Session{}, fmt.Errorf("Bad SESSION_OPEN length: %d/%d", len(session_open), SESSION_OPEN_LENGTH)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cur := 0
|
|
|
|
cur := 0
|
|
|
@ -100,10 +104,38 @@ func ParseSessionOpen(session_open []byte) (ed25519.PublicKey, ed25519.PublicKey
|
|
|
|
cur += SIGNATURE_LENGTH
|
|
|
|
cur += SIGNATURE_LENGTH
|
|
|
|
|
|
|
|
|
|
|
|
if ed25519.Verify(client_pubkey, digest, signature) == false {
|
|
|
|
if ed25519.Verify(client_pubkey, digest, signature) == false {
|
|
|
|
return nil, nil, fmt.Errorf("SESSION_OPEN signature verification failed")
|
|
|
|
return Session{}, fmt.Errorf("SESSION_OPEN signature verification failed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return client_pubkey, client_ecdh, nil
|
|
|
|
session_secret, err := ECDH(client_ecdh, ecdh)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return Session{}, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
session_id := ID[SessionID](session_secret)
|
|
|
|
|
|
|
|
client_id := ID[PeerID](client_pubkey)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
session_cipher, err := aes.NewCipher(session_secret)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return Session{}, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
seed_bytes := make([]byte, 8)
|
|
|
|
|
|
|
|
read, err := rand.Read(seed_bytes)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return Session{}, err
|
|
|
|
|
|
|
|
} else if read != 8 {
|
|
|
|
|
|
|
|
return Session{}, fmt.Errorf("Not enough entropy to create session")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Session{
|
|
|
|
|
|
|
|
ID: session_id,
|
|
|
|
|
|
|
|
remote: nil,
|
|
|
|
|
|
|
|
Peer: client_id,
|
|
|
|
|
|
|
|
secret: session_secret,
|
|
|
|
|
|
|
|
cipher: session_cipher,
|
|
|
|
|
|
|
|
iv_generator: mrand.NewSource(int64(binary.BigEndian.Uint64(seed_bytes))).(mrand.Source64),
|
|
|
|
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func NewSessionConnect(address *net.UDPAddr, session_secret []byte) []byte {
|
|
|
|
func NewSessionConnect(address *net.UDPAddr, session_secret []byte) []byte {
|
|
|
@ -166,20 +198,22 @@ func ParseSessionConnect(session_connect []byte, session_secret []byte) (*net.UD
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func NewSessionData(session *Session, packet []byte) ([]byte, error) {
|
|
|
|
func NewSessionData(session *Session, packet []byte) ([]byte, error) {
|
|
|
|
iv := make([]byte, 32)
|
|
|
|
iv := make([]byte, IV_LENGTH)
|
|
|
|
for i := 0; i < 4; i++ {
|
|
|
|
for i := 0; i < IV_LENGTH/8; i++ {
|
|
|
|
binary.BigEndian.PutUint64(iv[i*8:], session.iv_generator.Uint64())
|
|
|
|
binary.BigEndian.PutUint64(iv[i*8:], session.iv_generator.Uint64())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stream := cipher.NewOFB(session.cipher, iv[:])
|
|
|
|
stream := cipher.NewOFB(session.cipher, iv[:])
|
|
|
|
header := make([]byte, COMMAND_LENGTH + ID_LENGTH)
|
|
|
|
header := make([]byte, COMMAND_LENGTH + ID_LENGTH + IV_LENGTH)
|
|
|
|
header[0] = byte(SESSION_DATA)
|
|
|
|
header[0] = byte(SESSION_DATA)
|
|
|
|
copy(header[1:], session.ID[:])
|
|
|
|
copy(header[COMMAND_LENGTH:], session.ID[:])
|
|
|
|
|
|
|
|
copy(header[COMMAND_LENGTH+ID_LENGTH:], iv)
|
|
|
|
|
|
|
|
|
|
|
|
packet_encrypted := bytes.NewBuffer(header)
|
|
|
|
packet_encrypted := bytes.NewBuffer(header)
|
|
|
|
writer := &cipher.StreamWriter{S: stream, W: packet_encrypted}
|
|
|
|
writer := &cipher.StreamWriter{S: stream, W: packet_encrypted}
|
|
|
|
|
|
|
|
|
|
|
|
_, err := io.Copy(writer, bytes.NewBuffer(packet))
|
|
|
|
// Encrypt the packet with a crc32 checksum appended to the end
|
|
|
|
|
|
|
|
_, err := io.Copy(writer, bytes.NewBuffer(binary.BigEndian.AppendUint32(packet, crc32.ChecksumIEEE(packet))))
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -187,16 +221,44 @@ func NewSessionData(session *Session, packet []byte) ([]byte, error) {
|
|
|
|
return packet_encrypted.Bytes(), nil
|
|
|
|
return packet_encrypted.Bytes(), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func ParseSessionData(session *Session, data []byte) ([]byte, error) {
|
|
|
|
func ParseSessionData(session *Session, encrypted []byte) ([]byte, error) {
|
|
|
|
iv := data[0:32]
|
|
|
|
iv := encrypted[0:IV_LENGTH]
|
|
|
|
|
|
|
|
|
|
|
|
stream := cipher.NewOFB(session.cipher, iv)
|
|
|
|
stream := cipher.NewOFB(session.cipher, iv)
|
|
|
|
var packet_clear bytes.Buffer
|
|
|
|
var packet bytes.Buffer
|
|
|
|
reader := &cipher.StreamReader{S: stream, R: bytes.NewBuffer(data)}
|
|
|
|
reader := &cipher.StreamReader{S: stream, R: bytes.NewBuffer(encrypted[IV_LENGTH:])}
|
|
|
|
_, err := io.Copy(&packet_clear, reader)
|
|
|
|
_, err := io.Copy(&packet, reader)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return packet_clear.Bytes(), nil
|
|
|
|
packet_clear := packet.Bytes()
|
|
|
|
|
|
|
|
checksum := binary.BigEndian.Uint32(packet_clear[len(packet_clear)-4:])
|
|
|
|
|
|
|
|
data := packet_clear[:len(packet_clear)-4]
|
|
|
|
|
|
|
|
calculated := crc32.ChecksumIEEE(data)
|
|
|
|
|
|
|
|
if checksum != calculated {
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("SESSION_DATA checksum mismatch: %x != %x", checksum, calculated)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return data, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func NewSessionClose(session *Session) []byte {
|
|
|
|
|
|
|
|
packet := make([]byte, COMMAND_LENGTH + ID_LENGTH + SESSION_CLOSE_LENGTH)
|
|
|
|
|
|
|
|
packet[0] = COMMAND_LENGTH
|
|
|
|
|
|
|
|
copy(packet[1:], session.ID[:])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hmac := sha512.Sum512(append(session.ID[:], session.secret...))
|
|
|
|
|
|
|
|
copy(packet[COMMAND_LENGTH + ID_LENGTH:], hmac[:])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return packet
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func ParseSessionClose(session *Session, hmac []byte) error {
|
|
|
|
|
|
|
|
calculated_hmac := sha512.Sum512(append(session.ID[:], session.secret...))
|
|
|
|
|
|
|
|
if slices.Compare(hmac, calculated_hmac[:]) != 0 {
|
|
|
|
|
|
|
|
return fmt.Errorf("Session Close HMAC validation failed")
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|