| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- package ike
- import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- )
- const (
- ikeHeaderLength = 28
- ikeMajorVersion = 2
- ikeMinorVersion = 0
- exchangeIKEInit = 34
- exchangeIKEAuth = 35
- exchangeInformational = 37
- flagInitiator = 0x08
- flagResponse = 0x20
- payloadNone = 0
- payloadSA = 33
- payloadKE = 34
- payloadIDi = 35
- payloadIDr = 36
- payloadCert = 37
- payloadAuth = 39
- payloadNonce = 40
- payloadNotify = 41
- payloadTSi = 44
- payloadTSr = 45
- payloadEncrypted = 46
- payloadCP = 47
- payloadEAP = 48
- protocolIKE = 1
- protocolESP = 3
- transformEncryption = 1
- transformPRF = 2
- transformIntegrity = 3
- transformDH = 4
- transformESN = 5
- encryptionAESCBC = 12
- prfHMACSHA1 = 2
- prfHMACSHA256 = 5
- integrityHMACSHA1_96 = 2
- integrityHMACSHA256_128 = 12
- dhMODP1024 = 2
- dhMODP2048 = 14
- transformAttributeKeyLen = 14
- notifyNATSource = 16388
- notifyNATDestination = 16389
- notifyEAPOnlyAuth = 16417
- notifyInvalidKE = 17
- notifyNoProposal = 14
- )
- var (
- errMalformedPacket = errors.New("ike: malformed packet")
- errUnexpectedPacket = errors.New("ike: unexpected packet")
- errUnsupportedSuite = errors.New("ike: unsupported negotiated suite")
- errIntegrityMismatch = errors.New("ike: encrypted payload integrity mismatch")
- )
- type ikeHeader struct {
- InitiatorSPI [8]byte
- ResponderSPI [8]byte
- NextPayload uint8
- Version uint8
- Exchange uint8
- Flags uint8
- MessageID uint32
- Length uint32
- }
- func (header ikeHeader) marshal(body []byte) []byte {
- packet := make([]byte, ikeHeaderLength+len(body))
- copy(packet[0:8], header.InitiatorSPI[:])
- copy(packet[8:16], header.ResponderSPI[:])
- packet[16] = header.NextPayload
- if header.Version == 0 {
- header.Version = ikeMajorVersion<<4 | ikeMinorVersion
- }
- packet[17] = header.Version
- packet[18] = header.Exchange
- packet[19] = header.Flags
- binary.BigEndian.PutUint32(packet[20:24], header.MessageID)
- binary.BigEndian.PutUint32(packet[24:28], uint32(len(packet)))
- copy(packet[28:], body)
- return packet
- }
- func parseIKEPacket(packet []byte) (ikeHeader, []byte, error) {
- if len(packet) < ikeHeaderLength {
- return ikeHeader{}, nil, fmt.Errorf("%w: header is truncated", errMalformedPacket)
- }
- var header ikeHeader
- copy(header.InitiatorSPI[:], packet[0:8])
- copy(header.ResponderSPI[:], packet[8:16])
- header.NextPayload = packet[16]
- header.Version = packet[17]
- header.Exchange = packet[18]
- header.Flags = packet[19]
- header.MessageID = binary.BigEndian.Uint32(packet[20:24])
- header.Length = binary.BigEndian.Uint32(packet[24:28])
- if header.Version>>4 != ikeMajorVersion {
- return ikeHeader{}, nil, fmt.Errorf("%w: unsupported IKE major version %d", errMalformedPacket, header.Version>>4)
- }
- if header.Length < ikeHeaderLength || uint64(header.Length) != uint64(len(packet)) {
- return ikeHeader{}, nil, fmt.Errorf("%w: encoded length %d does not match datagram length %d", errMalformedPacket, header.Length, len(packet))
- }
- return header, packet[ikeHeaderLength:], nil
- }
- type payload struct {
- Type uint8
- Critical bool
- Body []byte
- }
- func marshalPayloadChain(payloads []payload) (uint8, []byte, error) {
- if len(payloads) == 0 {
- return payloadNone, nil, nil
- }
- var output bytes.Buffer
- for index, item := range payloads {
- if item.Type == payloadNone || item.Type == payloadEncrypted {
- return 0, nil, fmt.Errorf("ike: invalid ordinary payload type %d", item.Type)
- }
- next := uint8(payloadNone)
- if index+1 < len(payloads) {
- next = payloads[index+1].Type
- }
- length := 4 + len(item.Body)
- if length > 65535 {
- return 0, nil, errors.New("ike: payload exceeds 65535 bytes")
- }
- output.WriteByte(next)
- if item.Critical {
- output.WriteByte(0x80)
- } else {
- output.WriteByte(0)
- }
- var encodedLength [2]byte
- binary.BigEndian.PutUint16(encodedLength[:], uint16(length))
- output.Write(encodedLength[:])
- output.Write(item.Body)
- }
- return payloads[0].Type, output.Bytes(), nil
- }
- func parsePayloadChain(first uint8, encoded []byte) ([]payload, error) {
- var result []payload
- next := first
- offset := 0
- for next != payloadNone {
- if len(result) >= 64 {
- return nil, fmt.Errorf("%w: too many chained payloads", errMalformedPacket)
- }
- if offset+4 > len(encoded) {
- return nil, fmt.Errorf("%w: payload header is truncated", errMalformedPacket)
- }
- following := encoded[offset]
- flags := encoded[offset+1]
- length := int(binary.BigEndian.Uint16(encoded[offset+2 : offset+4]))
- if length < 4 || offset+length > len(encoded) {
- return nil, fmt.Errorf("%w: payload type %d has invalid length %d", errMalformedPacket, next, length)
- }
- body := append([]byte(nil), encoded[offset+4:offset+length]...)
- result = append(result, payload{Type: next, Critical: flags&0x80 != 0, Body: body})
- offset += length
- next = following
- }
- if offset != len(encoded) {
- return nil, fmt.Errorf("%w: %d trailing payload bytes", errMalformedPacket, len(encoded)-offset)
- }
- return result, nil
- }
- func payloadsOfType(payloads []payload, kind uint8) []payload {
- var matches []payload
- for _, item := range payloads {
- if item.Type == kind {
- matches = append(matches, item)
- }
- }
- return matches
- }
- func onePayload(payloads []payload, kind uint8) (payload, error) {
- matches := payloadsOfType(payloads, kind)
- if len(matches) != 1 {
- return payload{}, fmt.Errorf("%w: expected one payload type %d, got %d", errUnexpectedPacket, kind, len(matches))
- }
- return matches[0], nil
- }
- type transform struct {
- Type uint8
- ID uint16
- KeyLength int
- }
- type proposal struct {
- Number uint8
- Protocol uint8
- SPI []byte
- Transforms []transform
- }
- func marshalProposals(proposals []proposal) ([]byte, error) {
- var output bytes.Buffer
- for proposalIndex, item := range proposals {
- if len(item.SPI) > 255 || len(item.Transforms) > 255 {
- return nil, errors.New("ike: proposal has too many bytes or transforms")
- }
- var transforms bytes.Buffer
- for transformIndex, candidate := range item.Transforms {
- var attributes []byte
- if candidate.KeyLength > 0 {
- attributes = make([]byte, 4)
- binary.BigEndian.PutUint16(attributes[0:2], 0x8000|transformAttributeKeyLen)
- binary.BigEndian.PutUint16(attributes[2:4], uint16(candidate.KeyLength))
- }
- length := 8 + len(attributes)
- if transformIndex+1 < len(item.Transforms) {
- transforms.WriteByte(3)
- } else {
- transforms.WriteByte(0)
- }
- transforms.WriteByte(0)
- var header [6]byte
- binary.BigEndian.PutUint16(header[0:2], uint16(length))
- header[2] = candidate.Type
- header[3] = 0
- binary.BigEndian.PutUint16(header[4:6], candidate.ID)
- transforms.Write(header[:])
- transforms.Write(attributes)
- }
- length := 8 + len(item.SPI) + transforms.Len()
- if proposalIndex+1 < len(proposals) {
- output.WriteByte(2)
- } else {
- output.WriteByte(0)
- }
- output.WriteByte(0)
- var header [6]byte
- binary.BigEndian.PutUint16(header[0:2], uint16(length))
- header[2] = item.Number
- header[3] = item.Protocol
- header[4] = uint8(len(item.SPI))
- header[5] = uint8(len(item.Transforms))
- output.Write(header[:])
- output.Write(item.SPI)
- output.Write(transforms.Bytes())
- }
- return output.Bytes(), nil
- }
- func parseProposals(encoded []byte) ([]proposal, error) {
- var result []proposal
- offset := 0
- for {
- if offset == len(encoded) {
- break
- }
- if len(result) >= 16 || offset+8 > len(encoded) {
- return nil, fmt.Errorf("%w: invalid SA proposal header", errMalformedPacket)
- }
- last := encoded[offset]
- length := int(binary.BigEndian.Uint16(encoded[offset+2 : offset+4]))
- spiSize := int(encoded[offset+6])
- transformCount := int(encoded[offset+7])
- if length < 8+spiSize || offset+length > len(encoded) {
- return nil, fmt.Errorf("%w: invalid SA proposal length", errMalformedPacket)
- }
- item := proposal{
- Number: encoded[offset+4],
- Protocol: encoded[offset+5],
- SPI: append([]byte(nil), encoded[offset+8:offset+8+spiSize]...),
- }
- transformOffset := offset + 8 + spiSize
- proposalEnd := offset + length
- for transformOffset < proposalEnd {
- if len(item.Transforms) >= 32 || transformOffset+8 > proposalEnd {
- return nil, fmt.Errorf("%w: invalid transform header", errMalformedPacket)
- }
- transformLength := int(binary.BigEndian.Uint16(encoded[transformOffset+2 : transformOffset+4]))
- if transformLength < 8 || transformOffset+transformLength > proposalEnd {
- return nil, fmt.Errorf("%w: invalid transform length", errMalformedPacket)
- }
- transformEnd := transformOffset + transformLength
- if transformEnd < proposalEnd && encoded[transformOffset] != 3 {
- return nil, fmt.Errorf("%w: non-final transform has invalid chaining marker", errMalformedPacket)
- }
- if transformEnd == proposalEnd && encoded[transformOffset] != 0 {
- return nil, fmt.Errorf("%w: final transform has invalid chaining marker", errMalformedPacket)
- }
- candidate := transform{
- Type: encoded[transformOffset+4],
- ID: binary.BigEndian.Uint16(encoded[transformOffset+6 : transformOffset+8]),
- }
- attributes := encoded[transformOffset+8 : transformOffset+transformLength]
- for len(attributes) > 0 {
- if len(attributes) < 4 {
- return nil, fmt.Errorf("%w: truncated transform attribute", errMalformedPacket)
- }
- attributeType := binary.BigEndian.Uint16(attributes[0:2])
- if attributeType&0x8000 != 0 {
- if attributeType&0x7fff == transformAttributeKeyLen {
- candidate.KeyLength = int(binary.BigEndian.Uint16(attributes[2:4]))
- }
- attributes = attributes[4:]
- continue
- }
- attributeLength := int(binary.BigEndian.Uint16(attributes[2:4]))
- if attributeLength < 0 || 4+attributeLength > len(attributes) {
- return nil, fmt.Errorf("%w: invalid transform TLV attribute", errMalformedPacket)
- }
- if attributeType == transformAttributeKeyLen && attributeLength == 2 {
- candidate.KeyLength = int(binary.BigEndian.Uint16(attributes[4:6]))
- }
- attributes = attributes[4+attributeLength:]
- }
- item.Transforms = append(item.Transforms, candidate)
- transformOffset += transformLength
- }
- if transformOffset != proposalEnd || len(item.Transforms) != transformCount {
- return nil, fmt.Errorf("%w: transform count mismatch", errMalformedPacket)
- }
- result = append(result, item)
- offset = proposalEnd
- if last == 0 {
- if offset != len(encoded) {
- return nil, fmt.Errorf("%w: bytes follow last proposal", errMalformedPacket)
- }
- break
- }
- if last != 2 {
- return nil, fmt.Errorf("%w: invalid proposal chaining marker %d", errMalformedPacket, last)
- }
- }
- if len(result) == 0 {
- return nil, fmt.Errorf("%w: empty SA payload", errMalformedPacket)
- }
- return result, nil
- }
- type negotiatedSuite struct {
- EncryptionID uint16
- EncryptionBits int
- PRFID uint16
- IntegrityID uint16
- DHID uint16
- }
- func parseIKESuite(item proposal) (negotiatedSuite, error) {
- if item.Protocol != protocolIKE || len(item.SPI) != 0 {
- return negotiatedSuite{}, fmt.Errorf("%w: responder selected a non-IKE proposal", errUnsupportedSuite)
- }
- var suite negotiatedSuite
- seen := make(map[uint8]bool)
- for _, candidate := range item.Transforms {
- if seen[candidate.Type] {
- return negotiatedSuite{}, fmt.Errorf("%w: duplicate transform type %d", errUnsupportedSuite, candidate.Type)
- }
- seen[candidate.Type] = true
- switch candidate.Type {
- case transformEncryption:
- suite.EncryptionID = candidate.ID
- suite.EncryptionBits = candidate.KeyLength
- case transformPRF:
- suite.PRFID = candidate.ID
- case transformIntegrity:
- suite.IntegrityID = candidate.ID
- case transformDH:
- suite.DHID = candidate.ID
- default:
- return negotiatedSuite{}, fmt.Errorf("%w: IKE transform type %d", errUnsupportedSuite, candidate.Type)
- }
- }
- if suite.EncryptionID != encryptionAESCBC || (suite.EncryptionBits != 128 && suite.EncryptionBits != 256) {
- return negotiatedSuite{}, fmt.Errorf("%w: encryption id=%d bits=%d", errUnsupportedSuite, suite.EncryptionID, suite.EncryptionBits)
- }
- if suite.PRFID != prfHMACSHA1 && suite.PRFID != prfHMACSHA256 {
- return negotiatedSuite{}, fmt.Errorf("%w: PRF id=%d", errUnsupportedSuite, suite.PRFID)
- }
- if suite.IntegrityID != integrityHMACSHA1_96 && suite.IntegrityID != integrityHMACSHA256_128 {
- return negotiatedSuite{}, fmt.Errorf("%w: integrity id=%d", errUnsupportedSuite, suite.IntegrityID)
- }
- if suite.DHID != dhMODP1024 && suite.DHID != dhMODP2048 {
- return negotiatedSuite{}, fmt.Errorf("%w: DH id=%d", errUnsupportedSuite, suite.DHID)
- }
- return suite, nil
- }
- func makeNotify(notifyType uint16, data []byte) payload {
- body := make([]byte, 4+len(data))
- body[0] = 0
- body[1] = 0
- binary.BigEndian.PutUint16(body[2:4], notifyType)
- copy(body[4:], data)
- return payload{Type: payloadNotify, Body: body}
- }
- func parseNotify(item payload) (uint16, []byte, error) {
- if item.Type != payloadNotify || len(item.Body) < 4 {
- return 0, nil, fmt.Errorf("%w: invalid notify payload", errMalformedPacket)
- }
- spiSize := int(item.Body[1])
- if 4+spiSize > len(item.Body) {
- return 0, nil, fmt.Errorf("%w: truncated notify SPI", errMalformedPacket)
- }
- return binary.BigEndian.Uint16(item.Body[2:4]), append([]byte(nil), item.Body[4+spiSize:]...), nil
- }
|