mirror of
https://github.com/amnezia-vpn/amneziawg-go.git
synced 2026-10-12 22:24:18 +03:00
* feat: add header protection mechanism, random transport payload trailing size, and handshake timings randomization * fix: use uint range instead of the int one * fix: uapi typo * fix: simultaneous padding access * feat: prohibit Sx < 8 if headerProtection is set * fix: do not count bytes on crypt * fix: trailing size calculation * fix: get rid of buffer copying on receival * chore: rename random padding multiple * fix: atomize junk packets * fix: use padding from device by default * chore: readme changes * chore: add readme annotation about client-side params * chore: add both side recommendation for content padding * feat: change ContentPaddingMultiple to ContentPaddingAddition * feat: bring chacha20 in there * chore: use UintRange instead of magicHeader * feat: make all timers parameters * fix: reissue max_handshake_amount on successful handshake * chore: add readme info about new timers * fix: use ParseUint for UintRange * feat: make persistent keepalive a range * feat: use atomics everywhere where possible * chore: readme changes * fix: typo * fix: minor timer fixes
187 lines
3.6 KiB
Go
187 lines
3.6 KiB
Go
/* SPDX-License-Identifier: MIT
|
|
*
|
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
|
*/
|
|
|
|
package device
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
)
|
|
|
|
const (
|
|
NoisePublicKeySize = 32
|
|
NoisePrivateKeySize = 32
|
|
NoisePresharedKeySize = 32
|
|
HeaderCipherKeySize = 32
|
|
HeaderCipherNonceSize = 12
|
|
)
|
|
|
|
type (
|
|
NoisePublicKey [NoisePublicKeySize]byte
|
|
NoisePrivateKey [NoisePrivateKeySize]byte
|
|
NoisePresharedKey [NoisePresharedKeySize]byte
|
|
NoiseNonce uint64 // padded to 12-bytes
|
|
HeaderCipherKey [HeaderCipherKeySize]byte
|
|
)
|
|
|
|
func loadExactHex(dst []byte, src string) error {
|
|
slice, err := hex.DecodeString(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(slice) != len(dst) {
|
|
return errors.New("hex string does not fit the slice")
|
|
}
|
|
copy(dst, slice)
|
|
return nil
|
|
}
|
|
|
|
func (key NoisePrivateKey) IsZero() bool {
|
|
var zero NoisePrivateKey
|
|
return key.Equals(zero)
|
|
}
|
|
|
|
func (key NoisePrivateKey) Equals(tar NoisePrivateKey) bool {
|
|
return subtle.ConstantTimeCompare(key[:], tar[:]) == 1
|
|
}
|
|
|
|
func (key *NoisePrivateKey) FromHex(src string) (err error) {
|
|
err = loadExactHex(key[:], src)
|
|
key.clamp()
|
|
return
|
|
}
|
|
|
|
func (key *NoisePrivateKey) FromMaybeZeroHex(src string) (err error) {
|
|
err = loadExactHex(key[:], src)
|
|
if key.IsZero() {
|
|
return
|
|
}
|
|
key.clamp()
|
|
return
|
|
}
|
|
|
|
func (key *NoisePublicKey) FromHex(src string) error {
|
|
return loadExactHex(key[:], src)
|
|
}
|
|
|
|
func (key NoisePublicKey) IsZero() bool {
|
|
var zero NoisePublicKey
|
|
return key.Equals(zero)
|
|
}
|
|
|
|
func (key NoisePublicKey) Equals(tar NoisePublicKey) bool {
|
|
return subtle.ConstantTimeCompare(key[:], tar[:]) == 1
|
|
}
|
|
|
|
func (key *NoisePresharedKey) FromHex(src string) error {
|
|
return loadExactHex(key[:], src)
|
|
}
|
|
|
|
func (key HeaderCipherKey) IsZero() bool {
|
|
var zero HeaderCipherKey
|
|
return key.Equals(zero)
|
|
}
|
|
|
|
func (key HeaderCipherKey) Equals(tar HeaderCipherKey) bool {
|
|
return subtle.ConstantTimeCompare(key[:], tar[:]) == 1
|
|
}
|
|
|
|
func (key *HeaderCipherKey) FromHex(src string) error {
|
|
return loadExactHex(key[:], src)
|
|
}
|
|
|
|
type UintRange uint64
|
|
|
|
func (r *UintRange) FromUint32(lo, hi uint32) {
|
|
*r = UintRange(uint64(hi)<<32 | uint64(lo))
|
|
}
|
|
|
|
func (r *UintRange) FromString(str string) error {
|
|
parts := strings.Split(str, "-")
|
|
if len(parts) < 1 || len(parts) > 2 {
|
|
return errors.New("wrong format")
|
|
}
|
|
|
|
lo, err := strconv.ParseUint(parts[0], 10, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hi := lo
|
|
if len(parts) > 1 {
|
|
hi, err = strconv.ParseUint(parts[1], 10, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if hi < lo {
|
|
return errors.New("wrong range specified")
|
|
}
|
|
|
|
r.FromUint32(uint32(lo), uint32(hi))
|
|
return nil
|
|
}
|
|
|
|
func (r UintRange) Contains(num uint32) bool {
|
|
lo, hi := uint32(r), uint32(r>>32)
|
|
return lo <= num && num <= hi
|
|
}
|
|
|
|
func (r UintRange) IsZero() bool {
|
|
return r == 0
|
|
}
|
|
|
|
func (r UintRange) PickOne() uint32 {
|
|
lo, hi := uint32(r), uint32(r>>32)
|
|
return lo + fastrandn(hi-lo+1)
|
|
}
|
|
|
|
func (r UintRange) ToString() string {
|
|
lo, hi := uint32(r), uint32(r>>32)
|
|
|
|
if lo == hi {
|
|
return fmt.Sprintf("%d", lo)
|
|
} else {
|
|
return fmt.Sprintf("%d-%d", lo, hi)
|
|
}
|
|
}
|
|
|
|
func (r UintRange) Overlap(right UintRange) bool {
|
|
l_lo, l_hi := uint32(r), uint32(r>>32)
|
|
r_lo, r_hi := uint32(right), uint32(right>>32)
|
|
|
|
return l_lo <= r_hi && r_lo <= l_hi
|
|
}
|
|
|
|
func (r UintRange) Lo() uint32 {
|
|
return uint32(r)
|
|
}
|
|
|
|
func (r UintRange) Hi() uint32 {
|
|
return uint32(r >> 32)
|
|
}
|
|
|
|
type AtomicUintRange struct {
|
|
v atomic.Uint64
|
|
}
|
|
|
|
func (a *AtomicUintRange) Load() UintRange {
|
|
return UintRange(a.v.Load())
|
|
}
|
|
|
|
func (a *AtomicUintRange) Store(r UintRange) {
|
|
a.v.Store(uint64(r))
|
|
}
|
|
|
|
func (a *AtomicUintRange) Swap(r UintRange) UintRange {
|
|
return UintRange(a.v.Swap(uint64(r)))
|
|
}
|