2017-09-05 22:06:47 +00:00
|
|
|
package radius
|
2017-02-07 21:04:27 +00:00
|
|
|
|
2017-09-05 22:06:47 +00:00
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/md5"
|
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
2018-01-26 23:51:00 +00:00
|
|
|
"math"
|
2017-09-05 22:06:47 +00:00
|
|
|
"net"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ErrNoAttribute is returned when an attribute was not found when one was
|
|
|
|
// expected.
|
|
|
|
var ErrNoAttribute = errors.New("radius: attribute not found")
|
|
|
|
|
2018-01-26 23:51:00 +00:00
|
|
|
// Attribute is a wire encoded RADIUS attribute.
|
2017-09-05 22:06:47 +00:00
|
|
|
type Attribute []byte
|
|
|
|
|
2018-01-26 23:51:00 +00:00
|
|
|
// Integer returns the given attribute as an integer. An error is returned if
|
2017-09-05 22:06:47 +00:00
|
|
|
// the attribute is not 4 bytes long.
|
|
|
|
func Integer(a Attribute) (uint32, error) {
|
|
|
|
if len(a) != 4 {
|
|
|
|
return 0, errors.New("invalid length")
|
|
|
|
}
|
|
|
|
return binary.BigEndian.Uint32(a), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInteger creates a new Attribute from the given integer value.
|
|
|
|
func NewInteger(i uint32) Attribute {
|
|
|
|
v := make([]byte, 4)
|
|
|
|
binary.BigEndian.PutUint32(v, i)
|
|
|
|
return Attribute(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the given attribute as a string.
|
|
|
|
func String(a Attribute) string {
|
|
|
|
return string(a)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewString returns a new Attribute from the given string. An error is returned
|
|
|
|
// if the string length is greater than 253.
|
|
|
|
func NewString(s string) (Attribute, error) {
|
|
|
|
if len(s) > 253 {
|
|
|
|
return nil, errors.New("string too long")
|
|
|
|
}
|
|
|
|
return Attribute(s), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bytes returns the given Attribute as a byte slice.
|
|
|
|
func Bytes(a Attribute) []byte {
|
|
|
|
b := make([]byte, len(a))
|
|
|
|
copy(b, []byte(a))
|
|
|
|
return b
|
2017-02-07 21:04:27 +00:00
|
|
|
}
|
|
|
|
|
2017-09-05 22:06:47 +00:00
|
|
|
// NewBytes returns a new Attribute from the given byte slice. An error is
|
|
|
|
// returned if the slice is longer than 253.
|
|
|
|
func NewBytes(b []byte) (Attribute, error) {
|
|
|
|
if len(b) > 253 {
|
|
|
|
return nil, errors.New("value too long")
|
|
|
|
}
|
|
|
|
a := make(Attribute, len(b))
|
|
|
|
copy(a, Attribute(b))
|
|
|
|
return a, nil
|
2017-02-07 21:04:27 +00:00
|
|
|
}
|
2017-09-05 22:06:47 +00:00
|
|
|
|
|
|
|
// IPAddr returns the given Attribute as an IPv4 IP address. An error is
|
|
|
|
// returned if the attribute is not 4 bytes long.
|
|
|
|
func IPAddr(a Attribute) (net.IP, error) {
|
|
|
|
if len(a) != net.IPv4len {
|
|
|
|
return nil, errors.New("invalid length")
|
|
|
|
}
|
|
|
|
b := make([]byte, net.IPv4len)
|
|
|
|
copy(b, []byte(a))
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewIPAddr returns a new Attribute from the given IP address. An error is
|
|
|
|
// returned if the given address is not an IPv4 address.
|
|
|
|
func NewIPAddr(a net.IP) (Attribute, error) {
|
|
|
|
a = a.To4()
|
|
|
|
if a == nil {
|
|
|
|
return nil, errors.New("invalid IPv4 address")
|
|
|
|
}
|
|
|
|
b := make(Attribute, len(a))
|
|
|
|
copy(b, Attribute(a))
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
2018-07-06 16:09:34 +00:00
|
|
|
// IPv6Addr returns the given Attribute as an IPv6 IP address. An error is
|
|
|
|
// returned if the attribute is not 16 bytes long.
|
|
|
|
func IPv6Addr(a Attribute) (net.IP, error) {
|
|
|
|
if len(a) != net.IPv6len {
|
|
|
|
return nil, errors.New("invalid length")
|
|
|
|
}
|
|
|
|
b := make([]byte, net.IPv6len)
|
|
|
|
copy(b, []byte(a))
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewIPv6Addr returns a new Attribute from the given IP address. An error is
|
|
|
|
// returned if the given address is not an IPv6 address.
|
|
|
|
func NewIPv6Addr(a net.IP) (Attribute, error) {
|
|
|
|
a = a.To16()
|
|
|
|
if a == nil {
|
|
|
|
return nil, errors.New("invalid IPv6 address")
|
|
|
|
}
|
|
|
|
b := make(Attribute, len(a))
|
|
|
|
copy(b, Attribute(a))
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IFID returns the given attribute as a 8-byte hardware address. An error is
|
|
|
|
// return if the attribute is not 8 bytes long.
|
|
|
|
func IFID(a Attribute) (net.HardwareAddr, error) {
|
|
|
|
if len(a) != 8 {
|
|
|
|
return nil, errors.New("invalid length")
|
|
|
|
}
|
|
|
|
ifid := make(net.HardwareAddr, len(a))
|
|
|
|
copy(ifid, a)
|
|
|
|
return ifid, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewIFID returns a new Attribute from the given hardware address. An error
|
|
|
|
// is returned if the address is not 8 bytes long.
|
|
|
|
func NewIFID(addr net.HardwareAddr) (Attribute, error) {
|
|
|
|
if len(addr) != 8 {
|
|
|
|
return nil, errors.New("invalid length")
|
|
|
|
}
|
|
|
|
attr := make(Attribute, len(addr))
|
|
|
|
copy(attr, addr)
|
|
|
|
return attr, nil
|
|
|
|
}
|
|
|
|
|
2017-09-05 22:06:47 +00:00
|
|
|
// UserPassword decrypts the given "User-Password"-encrypted (as defined in RFC
|
|
|
|
// 2865) Attribute, and returns the plaintext. An error is returned if the
|
|
|
|
// attribute length is invalid, the secret is empty, or the requestAuthenticator
|
|
|
|
// length is invalid.
|
|
|
|
func UserPassword(a Attribute, secret, requestAuthenticator []byte) ([]byte, error) {
|
|
|
|
if len(a) < 16 || len(a) > 128 {
|
|
|
|
return nil, errors.New("invalid attribute length (" + strconv.Itoa(len(a)) + ")")
|
|
|
|
}
|
|
|
|
if len(secret) == 0 {
|
|
|
|
return nil, errors.New("empty secret")
|
|
|
|
}
|
|
|
|
if len(requestAuthenticator) != 16 {
|
|
|
|
return nil, errors.New("invalid requestAuthenticator length (" + strconv.Itoa(len(requestAuthenticator)) + ")")
|
|
|
|
}
|
|
|
|
|
|
|
|
dec := make([]byte, 0, len(a))
|
|
|
|
|
|
|
|
hash := md5.New()
|
|
|
|
hash.Write(secret)
|
|
|
|
hash.Write(requestAuthenticator)
|
|
|
|
dec = hash.Sum(dec)
|
|
|
|
|
|
|
|
for i, b := range a[:16] {
|
|
|
|
dec[i] ^= b
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 16; i < len(a); i += 16 {
|
|
|
|
hash.Reset()
|
|
|
|
hash.Write(secret)
|
|
|
|
hash.Write(a[i-16 : i])
|
|
|
|
dec = hash.Sum(dec)
|
|
|
|
|
|
|
|
for j, b := range a[i : i+16] {
|
|
|
|
dec[i+j] ^= b
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if i := bytes.IndexByte(dec, 0); i > -1 {
|
|
|
|
return dec[:i], nil
|
|
|
|
}
|
|
|
|
return dec, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUserPassword returns a new "User-Password"-encrypted attribute from the
|
|
|
|
// given plaintext, secret, and requestAuthenticator. An error is returned if
|
|
|
|
// the plaintext is too long, the secret is empty, or the requestAuthenticator
|
|
|
|
// is an invalid length.
|
|
|
|
func NewUserPassword(plaintext, secret, requestAuthenticator []byte) (Attribute, error) {
|
|
|
|
if len(plaintext) > 128 {
|
|
|
|
return nil, errors.New("plaintext longer than 128 characters")
|
|
|
|
}
|
|
|
|
if len(secret) == 0 {
|
|
|
|
return nil, errors.New("empty secret")
|
|
|
|
}
|
|
|
|
if len(requestAuthenticator) != 16 {
|
|
|
|
return nil, errors.New("requestAuthenticator not 16-bytes")
|
|
|
|
}
|
|
|
|
|
2018-07-09 16:41:21 +00:00
|
|
|
chunks := (len(plaintext) + 16 - 1) / 16
|
2017-09-05 22:06:47 +00:00
|
|
|
if chunks == 0 {
|
|
|
|
chunks = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
enc := make([]byte, 0, chunks*16)
|
|
|
|
|
|
|
|
hash := md5.New()
|
|
|
|
hash.Write(secret)
|
|
|
|
hash.Write(requestAuthenticator)
|
|
|
|
enc = hash.Sum(enc)
|
|
|
|
|
|
|
|
for i, b := range plaintext[:16] {
|
|
|
|
enc[i] ^= b
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 16; i < len(plaintext); i += 16 {
|
|
|
|
hash.Reset()
|
|
|
|
hash.Write(secret)
|
|
|
|
hash.Write(enc[i-16 : i])
|
|
|
|
enc = hash.Sum(enc)
|
|
|
|
|
|
|
|
for j, b := range plaintext[i : i+16] {
|
|
|
|
enc[i+j] ^= b
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return enc, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Date returns the given Attribute as time.Time. An error is returned if the
|
|
|
|
// attribute is not 4 bytes long.
|
|
|
|
func Date(a Attribute) (time.Time, error) {
|
|
|
|
if len(a) != 4 {
|
|
|
|
return time.Time{}, errors.New("invalid length")
|
|
|
|
}
|
|
|
|
sec := binary.BigEndian.Uint32([]byte(a))
|
|
|
|
return time.Unix(int64(sec), 0), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewDate returns a new Attribute from the given time.Time.
|
2018-01-26 23:51:00 +00:00
|
|
|
func NewDate(t time.Time) (Attribute, error) {
|
|
|
|
unix := t.Unix()
|
|
|
|
if unix > math.MaxUint32 {
|
|
|
|
return nil, errors.New("time out of range")
|
|
|
|
}
|
2017-09-05 22:06:47 +00:00
|
|
|
a := make([]byte, 4)
|
|
|
|
binary.BigEndian.PutUint32(a, uint32(t.Unix()))
|
2018-01-26 23:51:00 +00:00
|
|
|
return a, nil
|
2017-09-05 22:06:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// VendorSpecific returns the vendor ID and value from the given attribute. An
|
|
|
|
// error is returned if the attribute is less than 5 bytes long.
|
|
|
|
func VendorSpecific(a Attribute) (vendorID uint32, value Attribute, err error) {
|
|
|
|
if len(a) < 5 {
|
|
|
|
err = errors.New("invalid length")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
vendorID = binary.BigEndian.Uint32(a[:4])
|
|
|
|
value = make([]byte, len(a)-4)
|
|
|
|
copy(value, a[4:])
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewVendorSpecific returns a new vendor specific attribute with the given
|
|
|
|
// vendor ID and value.
|
2018-01-26 23:51:00 +00:00
|
|
|
func NewVendorSpecific(vendorID uint32, value Attribute) (Attribute, error) {
|
|
|
|
if len(value) > 249 {
|
|
|
|
return nil, errors.New("value too long")
|
|
|
|
}
|
2017-09-05 22:06:47 +00:00
|
|
|
a := make([]byte, 4+len(value))
|
|
|
|
binary.BigEndian.PutUint32(a, vendorID)
|
|
|
|
copy(a[4:], value)
|
2018-01-26 23:51:00 +00:00
|
|
|
return a, nil
|
2017-09-05 22:06:47 +00:00
|
|
|
}
|
|
|
|
|
2018-07-06 16:09:34 +00:00
|
|
|
// Integer64 returns the given attribute as an integer. An error is returned if
|
|
|
|
// the attribute is not 8 bytes long.
|
|
|
|
func Integer64(a Attribute) (uint64, error) {
|
|
|
|
if len(a) != 8 {
|
|
|
|
return 0, errors.New("invalid length")
|
|
|
|
}
|
|
|
|
return binary.BigEndian.Uint64(a), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInteger64 creates a new Attribute from the given integer value.
|
|
|
|
func NewInteger64(i uint64) Attribute {
|
|
|
|
v := make([]byte, 8)
|
|
|
|
binary.BigEndian.PutUint64(v, i)
|
|
|
|
return Attribute(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TLV returns a components of a Type-Length-Value (TLV) attribute.
|
2019-04-12 15:51:37 +00:00
|
|
|
func TLV(a Attribute) (tlvType byte, tlvValue Attribute, err error) {
|
2018-07-06 16:09:34 +00:00
|
|
|
if len(a) < 3 || len(a) > 255 || int(a[1]) != len(a) {
|
|
|
|
err = errors.New("invalid length")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
tlvType = a[0]
|
2019-04-12 15:51:37 +00:00
|
|
|
tlvValue = make(Attribute, len(a)-2)
|
|
|
|
copy(tlvValue, a[2:])
|
2018-07-06 16:09:34 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewTLV returns a new TLV attribute.
|
|
|
|
func NewTLV(tlvType byte, tlvValue Attribute) (Attribute, error) {
|
|
|
|
if len(tlvValue) < 1 || len(tlvValue) > 253 {
|
|
|
|
return nil, errors.New("invalid value length")
|
|
|
|
}
|
|
|
|
a := make(Attribute, 1+1+len(tlvValue))
|
|
|
|
a[0] = tlvType
|
|
|
|
a[1] = byte(1 + 1 + len(tlvValue))
|
2019-04-12 15:51:37 +00:00
|
|
|
copy(a[2:], tlvValue)
|
2018-07-06 16:09:34 +00:00
|
|
|
return a, nil
|
|
|
|
}
|
|
|
|
|
2018-07-09 16:41:21 +00:00
|
|
|
// NewTunnelPassword returns an RFC 2868 encrypted Tunnel-Password.
|
|
|
|
// A tag must be added on to the returned Attribute.
|
|
|
|
func NewTunnelPassword(password, salt, secret, requestAuthenticator []byte) (Attribute, error) {
|
|
|
|
if len(password) > 249 {
|
|
|
|
return nil, errors.New("invalid password length")
|
|
|
|
}
|
|
|
|
if len(salt) != 2 {
|
|
|
|
return nil, errors.New("invalid salt length")
|
|
|
|
}
|
|
|
|
if salt[0]&0x80 != 0x80 { // MSB must be 1
|
|
|
|
return nil, errors.New("invalid salt")
|
|
|
|
}
|
|
|
|
if len(secret) == 0 {
|
|
|
|
return nil, errors.New("empty secret")
|
|
|
|
}
|
|
|
|
if len(requestAuthenticator) != 16 {
|
|
|
|
return nil, errors.New("invalid requestAuthenticator length")
|
|
|
|
}
|
|
|
|
|
|
|
|
chunks := (1 + len(password) + 16 - 1) / 16
|
|
|
|
if chunks == 0 {
|
|
|
|
chunks = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
attr := make([]byte, 2+chunks*16)
|
|
|
|
copy(attr[:2], salt)
|
|
|
|
attr[2] = byte(len(password))
|
|
|
|
copy(attr[3:], password)
|
|
|
|
|
|
|
|
hash := md5.New()
|
|
|
|
var b [md5.Size]byte
|
|
|
|
|
|
|
|
for chunk := 0; chunk < chunks; chunk++ {
|
|
|
|
hash.Reset()
|
|
|
|
|
|
|
|
hash.Write(secret)
|
|
|
|
if chunk == 0 {
|
|
|
|
hash.Write(requestAuthenticator)
|
|
|
|
hash.Write(salt)
|
|
|
|
} else {
|
|
|
|
hash.Write(attr[2+(chunk-1)*16 : 2+chunk*16])
|
|
|
|
}
|
|
|
|
hash.Sum(b[:0])
|
|
|
|
|
|
|
|
for i := 0; i < 16; i++ {
|
|
|
|
attr[2+chunk*16+i] ^= b[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return attr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TunnelPassword decrypts an RFC 2868 encrypted Tunnel-Password.
|
|
|
|
// The Attribute must not be prefixed with a tag.
|
|
|
|
func TunnelPassword(a Attribute, secret, requestAuthenticator []byte) (password, salt []byte, err error) {
|
|
|
|
if len(a) > 252 || len(a) < 18 || (len(a)-2)%16 != 0 {
|
|
|
|
err = errors.New("invalid length")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(secret) == 0 {
|
|
|
|
err = errors.New("empty secret")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(requestAuthenticator) != 16 {
|
|
|
|
err = errors.New("invalid requestAuthenticator length")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if a[0]&0x80 != 0x80 { // salt MSB must be 1
|
|
|
|
err = errors.New("invalid salt")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
chunks := (len(a) - 2) / 16
|
|
|
|
plaintext := make([]byte, chunks*16)
|
|
|
|
|
|
|
|
hash := md5.New()
|
|
|
|
var b [md5.Size]byte
|
|
|
|
|
|
|
|
for chunk := 0; chunk < chunks; chunk++ {
|
|
|
|
hash.Reset()
|
|
|
|
|
|
|
|
hash.Write(secret)
|
|
|
|
if chunk == 0 {
|
|
|
|
hash.Write(requestAuthenticator)
|
|
|
|
hash.Write(a[:2]) // salt
|
|
|
|
} else {
|
|
|
|
hash.Write(a[2+(chunk-1)*16 : 2+chunk*16])
|
|
|
|
}
|
|
|
|
hash.Sum(b[:0])
|
|
|
|
|
|
|
|
for i := 0; i < 16; i++ {
|
|
|
|
plaintext[chunk*16+i] = a[2+chunk*16+i] ^ b[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
passwordLength := plaintext[0]
|
|
|
|
if int(passwordLength) > (len(plaintext) - 1) {
|
|
|
|
err = errors.New("invalid password length")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
password = plaintext[1 : 1+passwordLength]
|
|
|
|
salt = append([]byte(nil), a[:2]...)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-12 15:51:37 +00:00
|
|
|
func NewIPv6Prefix(prefix *net.IPNet) (Attribute, error) {
|
|
|
|
if prefix == nil {
|
|
|
|
return nil, errors.New("nil prefix")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(prefix.IP) != net.IPv6len {
|
|
|
|
return nil, errors.New("IP is not IPv6")
|
|
|
|
}
|
|
|
|
|
|
|
|
ones, bits := prefix.Mask.Size()
|
|
|
|
if bits != net.IPv6len*8 {
|
|
|
|
return nil, errors.New("mask is not IPv6")
|
|
|
|
}
|
|
|
|
|
|
|
|
attr := make(Attribute, 2+((ones+7)/8))
|
|
|
|
// attr[0] = 0x00
|
|
|
|
attr[1] = byte(ones)
|
|
|
|
copy(attr[2:], prefix.IP)
|
|
|
|
|
|
|
|
// clear final non-mask bits
|
|
|
|
if i := uint(ones % 8); i != 0 {
|
|
|
|
for ; i < 8; i++ {
|
|
|
|
attr[len(attr)-1] &^= 1 << (7 - i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return attr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func IPv6Prefix(a Attribute) (*net.IPNet, error) {
|
|
|
|
if len(a) < 2 || len(a) > 18 {
|
|
|
|
return nil, errors.New("invalid length")
|
|
|
|
}
|
|
|
|
|
|
|
|
prefixLength := int(a[1])
|
|
|
|
if (len(a)-2)*8 < prefixLength {
|
|
|
|
return nil, errors.New("invalid prefix length")
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := make(net.IP, net.IPv6len)
|
|
|
|
copy(ip, a[2:])
|
|
|
|
|
|
|
|
// clear final non-mask bits
|
|
|
|
if i := uint(prefixLength % 8); i != 0 {
|
|
|
|
for ; i < 8; i++ {
|
|
|
|
ip[prefixLength/8] &^= 1 << (7 - i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &net.IPNet{
|
|
|
|
IP: ip,
|
|
|
|
Mask: net.CIDRMask(prefixLength, net.IPv6len*8),
|
|
|
|
}, nil
|
|
|
|
}
|