open-vault/vendor/layeh.com/radius/packet.go

161 lines
4 KiB
Go
Raw Normal View History

2017-09-05 22:06:47 +00:00
package radius
2017-02-07 21:04:27 +00:00
import (
"bytes"
"crypto/md5"
"crypto/rand"
"encoding/binary"
"errors"
)
2017-09-05 22:06:47 +00:00
// MaxPacketLength is the maximum possible wire length of a RADIUS packet.
const MaxPacketLength = 4095
2017-02-07 21:04:27 +00:00
2017-09-05 22:06:47 +00:00
// Packet is a RADIUS packet.
2017-02-07 21:04:27 +00:00
type Packet struct {
Code Code
Identifier byte
Authenticator [16]byte
2017-09-05 22:06:47 +00:00
Secret []byte
Attributes
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
// New creates a new packet with the Code, Secret fields set to the given
2018-02-11 00:29:52 +00:00
// values. The returned packet's Identifier and Authenticator fields are filled
// with random values.
//
// The function panics if not enough random data could be generated.
2017-02-07 21:04:27 +00:00
func New(code Code, secret []byte) *Packet {
2018-01-26 23:51:00 +00:00
var buff [17]byte
2017-02-07 21:04:27 +00:00
if _, err := rand.Read(buff[:]); err != nil {
2017-09-05 22:06:47 +00:00
panic(err)
2017-02-07 21:04:27 +00:00
}
packet := &Packet{
Code: code,
Identifier: buff[0],
Secret: secret,
2017-09-05 22:06:47 +00:00
Attributes: make(Attributes),
2017-02-07 21:04:27 +00:00
}
copy(packet.Authenticator[:], buff[1:])
return packet
}
2017-09-05 22:06:47 +00:00
// Parse parses an encoded RADIUS packet b. An error is returned if the packet
// is malformed.
func Parse(b, secret []byte) (*Packet, error) {
if len(b) < 20 {
return nil, errors.New("radius: packet not at least 20 bytes long")
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
length := int(binary.BigEndian.Uint16(b[2:4]))
if length < 20 || length > MaxPacketLength || len(b) > length {
2017-02-07 21:04:27 +00:00
return nil, errors.New("radius: invalid packet length")
}
2017-09-05 22:06:47 +00:00
attrs, err := ParseAttributes(b[20:])
if err != nil {
return nil, err
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
packet := &Packet{
Code: Code(b[0]),
Identifier: b[1],
Secret: secret,
Attributes: attrs,
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
copy(packet.Authenticator[:], b[4:20])
return packet, nil
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
// Response returns a new packet that has the same identifier, secret, and
// authenticator as the current packet.
func (p *Packet) Response(code Code) *Packet {
q := &Packet{
Code: code,
Identifier: p.Identifier,
Secret: p.Secret,
Attributes: make(Attributes),
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
copy(q.Authenticator[:], p.Authenticator[:])
return q
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
// Encode encodes the RADIUS packet to wire format. An error is returned if the
// encoded packet is too long (due to its Attributes), or if the packet has an
// unknown Code.
func (p *Packet) Encode() ([]byte, error) {
size := 20 + p.Attributes.wireSize()
if size > MaxPacketLength {
return nil, errors.New("encoded packet is too long")
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
b := make([]byte, size)
b[0] = byte(p.Code)
b[1] = byte(p.Identifier)
binary.BigEndian.PutUint16(b[2:4], uint16(size))
p.Attributes.encodeTo(b[20:])
2017-02-07 21:04:27 +00:00
2017-09-05 22:06:47 +00:00
switch p.Code {
case CodeAccessRequest:
copy(b[4:20], p.Authenticator[:])
case CodeAccessAccept, CodeAccessReject, CodeAccountingRequest, CodeAccountingResponse, CodeAccessChallenge, CodeDisconnectRequest, CodeCoARequest:
hash := md5.New()
hash.Write(b[:4])
switch p.Code {
case CodeAccountingRequest, CodeDisconnectRequest, CodeCoARequest:
var nul [16]byte
hash.Write(nul[:])
default:
hash.Write(p.Authenticator[:])
}
hash.Write(b[20:])
hash.Write(p.Secret)
hash.Sum(b[4:4:20])
default:
return nil, errors.New("radius: unknown Packet Code")
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
return b, nil
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
// IsAuthenticResponse returns if the given RADIUS response is an authentic
// response to the given request.
func IsAuthenticResponse(response, request, secret []byte) bool {
if len(response) < 20 || len(request) < 20 || len(secret) == 0 {
return false
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
hash := md5.New()
hash.Write(response[:4])
hash.Write(request[4:20])
hash.Write(response[20:])
hash.Write(secret)
var sum [md5.Size]byte
return bytes.Equal(hash.Sum(sum[:0]), response[4:20])
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
// IsAuthenticRequest returns if the given RADIUS request is an authentic
// request using the given secret.
func IsAuthenticRequest(request, secret []byte) bool {
if len(request) < 20 || len(secret) == 0 {
return false
2017-02-07 21:04:27 +00:00
}
2017-09-05 22:06:47 +00:00
switch Code(request[0]) {
2017-02-07 21:04:27 +00:00
case CodeAccessRequest:
2017-09-05 22:06:47 +00:00
return true
case CodeAccountingRequest, CodeDisconnectRequest, CodeCoARequest:
2017-02-07 21:04:27 +00:00
hash := md5.New()
2017-09-05 22:06:47 +00:00
hash.Write(request[:4])
var nul [16]byte
hash.Write(nul[:])
hash.Write(request[20:])
hash.Write(secret)
2017-02-07 21:04:27 +00:00
var sum [md5.Size]byte
2017-09-05 22:06:47 +00:00
return bytes.Equal(hash.Sum(sum[:0]), request[4:20])
2017-02-07 21:04:27 +00:00
default:
2017-09-05 22:06:47 +00:00
return false
2017-02-07 21:04:27 +00:00
}
}