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 }