| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- package ike
- import (
- "bytes"
- "crypto/aes"
- "crypto/cipher"
- "crypto/hmac"
- "crypto/rand"
- "crypto/sha1"
- "crypto/sha256"
- "crypto/subtle"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "math"
- "net"
- "sync"
- )
- const (
- espHeaderLength = 8
- espReplayWindow = 64
- )
- var (
- errESPAuthentication = errors.New("ike: ESP authentication failed")
- errESPReplay = errors.New("ike: ESP packet is outside the replay window")
- errESPPolicyDrop = errors.New("ike: ESP packet is not eligible for this CHILD_SA")
- )
- // espTunnel protects complete IPv4 or IPv6 packets using an IKEv2 CHILD_SA.
- // It deliberately implements only the negotiated suites offered by this
- // package: AES-CBC with HMAC-SHA1-96 or HMAC-SHA2-256-128 and no ESN.
- type espTunnel struct {
- outbound *espDirection
- inbound *espDirection
- initiatorSelectors []trafficSelector
- responderSelectors []trafficSelector
- }
- type espDirection struct {
- spi uint32
- block cipher.Block
- authKey []byte
- integrity string
- icvLength int
- random io.Reader
- mu sync.Mutex
- sequence uint32
- replay replayWindow
- }
- type replayWindow struct {
- highest uint32
- bitmap uint64
- }
- type innerPacketMetadata struct {
- source net.IP
- destination net.IP
- protocol uint8
- sourcePort uint16
- destinationPort uint16
- nextHeader uint8
- }
- func newESPTunnel(config ChildSAConfig, randomSource io.Reader) (*espTunnel, error) {
- if config.InboundSPI == 0 || config.OutboundSPI == 0 {
- return nil, errors.New("ike: ESP SPIs must be nonzero")
- }
- if randomSource == nil {
- randomSource = rand.Reader
- }
- outbound, err := newESPDirection(
- config.OutboundSPI,
- config.OutboundEncKey,
- config.OutboundAuthKey,
- config.Encryption,
- config.Integrity,
- randomSource,
- )
- if err != nil {
- return nil, fmt.Errorf("ike: outbound ESP: %w", err)
- }
- inbound, err := newESPDirection(
- config.InboundSPI,
- config.InboundEncKey,
- config.InboundAuthKey,
- config.Encryption,
- config.Integrity,
- randomSource,
- )
- if err != nil {
- return nil, fmt.Errorf("ike: inbound ESP: %w", err)
- }
- if len(config.InitiatorSelectors) == 0 || len(config.ResponderSelectors) == 0 {
- return nil, errors.New("ike: ESP traffic selectors are required")
- }
- return &espTunnel{
- outbound: outbound,
- inbound: inbound,
- initiatorSelectors: copyESPTrafficSelectors(config.InitiatorSelectors),
- responderSelectors: copyESPTrafficSelectors(config.ResponderSelectors),
- }, nil
- }
- func newESPDirection(
- spi uint32,
- encryptionKey []byte,
- authenticationKey []byte,
- encryption string,
- integrity string,
- randomSource io.Reader,
- ) (*espDirection, error) {
- expectedEncryptionLength := 0
- switch encryption {
- case "aes-cbc-128":
- expectedEncryptionLength = 16
- case "aes-cbc-256":
- expectedEncryptionLength = 32
- default:
- return nil, fmt.Errorf("unsupported encryption suite %q", encryption)
- }
- if len(encryptionKey) != expectedEncryptionLength {
- return nil, fmt.Errorf("AES key has length %d, want %d", len(encryptionKey), expectedEncryptionLength)
- }
- expectedAuthenticationLength := 0
- icvLength := 0
- switch integrity {
- case "hmac-sha1-96":
- expectedAuthenticationLength = sha1.Size
- icvLength = 12
- case "hmac-sha2-256-128":
- expectedAuthenticationLength = sha256.Size
- icvLength = 16
- default:
- return nil, fmt.Errorf("unsupported integrity suite %q", integrity)
- }
- if len(authenticationKey) != expectedAuthenticationLength {
- return nil, fmt.Errorf(
- "authentication key has length %d, want %d",
- len(authenticationKey),
- expectedAuthenticationLength,
- )
- }
- block, err := aes.NewCipher(encryptionKey)
- if err != nil {
- return nil, err
- }
- return &espDirection{
- spi: spi,
- block: block,
- authKey: append([]byte(nil), authenticationKey...),
- integrity: integrity,
- icvLength: icvLength,
- random: randomSource,
- }, nil
- }
- func (tunnel *espTunnel) seal(innerPacket []byte) ([]byte, error) {
- if tunnel == nil {
- return nil, errors.New("ike: nil ESP tunnel")
- }
- metadata, err := parseInnerPacket(innerPacket)
- if err != nil {
- return nil, fmt.Errorf("%w: %v", errESPPolicyDrop, err)
- }
- if !packetAllowed(
- metadata,
- tunnel.initiatorSelectors,
- tunnel.responderSelectors,
- ) {
- return nil, fmt.Errorf("%w: outbound packet is outside negotiated traffic selectors", errESPPolicyDrop)
- }
- return tunnel.outbound.seal(innerPacket, metadata.nextHeader)
- }
- func (tunnel *espTunnel) open(packet []byte) ([]byte, error) {
- if tunnel == nil {
- return nil, errors.New("ike: nil ESP tunnel")
- }
- return tunnel.inbound.open(packet, func(innerPacket []byte, nextHeader uint8) error {
- metadata, err := parseInnerPacket(innerPacket)
- if err != nil {
- return err
- }
- if metadata.nextHeader != nextHeader {
- return errors.New("ike: ESP trailer does not match the inner IP version")
- }
- if !packetAllowed(
- metadata,
- tunnel.responderSelectors,
- tunnel.initiatorSelectors,
- ) {
- return errors.New("ike: inbound packet is outside negotiated traffic selectors")
- }
- return nil
- })
- }
- func (direction *espDirection) seal(innerPacket []byte, nextHeader uint8) ([]byte, error) {
- direction.mu.Lock()
- defer direction.mu.Unlock()
- if direction.sequence == math.MaxUint32 {
- return nil, errors.New("ike: ESP sequence number exhausted; rekey is required")
- }
- direction.sequence++
- sequence := direction.sequence
- blockSize := direction.block.BlockSize()
- paddingLength := (blockSize - ((len(innerPacket) + 2) % blockSize)) % blockSize
- plaintext := make([]byte, len(innerPacket)+paddingLength+2)
- copy(plaintext, innerPacket)
- for index := 0; index < paddingLength; index++ {
- plaintext[len(innerPacket)+index] = byte(index + 1)
- }
- plaintext[len(plaintext)-2] = byte(paddingLength)
- plaintext[len(plaintext)-1] = nextHeader
- authenticatedLength := espHeaderLength + blockSize + len(plaintext)
- packet := make([]byte, authenticatedLength+direction.icvLength)
- binary.BigEndian.PutUint32(packet[0:4], direction.spi)
- binary.BigEndian.PutUint32(packet[4:8], sequence)
- iv := packet[espHeaderLength : espHeaderLength+blockSize]
- if _, err := io.ReadFull(direction.random, iv); err != nil {
- return nil, fmt.Errorf("ike: generate ESP IV: %w", err)
- }
- cipher.NewCBCEncrypter(direction.block, iv).CryptBlocks(
- packet[espHeaderLength+blockSize:authenticatedLength],
- plaintext,
- )
- icv := direction.authenticationCode(packet[:authenticatedLength])
- copy(packet[authenticatedLength:], icv)
- return packet, nil
- }
- func (direction *espDirection) open(
- packet []byte,
- validate func([]byte, uint8) error,
- ) ([]byte, error) {
- direction.mu.Lock()
- defer direction.mu.Unlock()
- blockSize := direction.block.BlockSize()
- minimumLength := espHeaderLength + blockSize + blockSize + direction.icvLength
- if len(packet) < minimumLength {
- return nil, errors.New("ike: ESP packet is truncated")
- }
- if binary.BigEndian.Uint32(packet[0:4]) != direction.spi {
- return nil, errors.New("ike: ESP packet has an unexpected SPI")
- }
- sequence := binary.BigEndian.Uint32(packet[4:8])
- if sequence == 0 || !direction.replay.wouldAccept(sequence) {
- return nil, errESPReplay
- }
- authenticatedLength := len(packet) - direction.icvLength
- ciphertext := packet[espHeaderLength+blockSize : authenticatedLength]
- if len(ciphertext) == 0 || len(ciphertext)%blockSize != 0 {
- return nil, errors.New("ike: ESP ciphertext is not block aligned")
- }
- expectedICV := direction.authenticationCode(packet[:authenticatedLength])
- if subtle.ConstantTimeCompare(expectedICV, packet[authenticatedLength:]) != 1 {
- return nil, errESPAuthentication
- }
- plaintext := make([]byte, len(ciphertext))
- iv := packet[espHeaderLength : espHeaderLength+blockSize]
- cipher.NewCBCDecrypter(direction.block, iv).CryptBlocks(plaintext, ciphertext)
- if len(plaintext) < 2 {
- return nil, errors.New("ike: ESP plaintext is truncated")
- }
- paddingLength := int(plaintext[len(plaintext)-2])
- if paddingLength > len(plaintext)-2 {
- return nil, errors.New("ike: ESP padding length is invalid")
- }
- paddingStart := len(plaintext) - 2 - paddingLength
- for index := 0; index < paddingLength; index++ {
- if plaintext[paddingStart+index] != byte(index+1) {
- return nil, errors.New("ike: ESP padding bytes are invalid")
- }
- }
- nextHeader := plaintext[len(plaintext)-1]
- if nextHeader != 4 && nextHeader != 41 {
- return nil, fmt.Errorf("ike: unsupported ESP next-header value %d", nextHeader)
- }
- innerPacket := append([]byte(nil), plaintext[:paddingStart]...)
- if validate != nil {
- if err := validate(innerPacket, nextHeader); err != nil {
- return nil, err
- }
- }
- direction.replay.commit(sequence)
- return innerPacket, nil
- }
- func (direction *espDirection) authenticationCode(packet []byte) []byte {
- var mac hashWriter
- switch direction.integrity {
- case "hmac-sha1-96":
- mac = hmac.New(sha1.New, direction.authKey)
- case "hmac-sha2-256-128":
- mac = hmac.New(sha256.New, direction.authKey)
- default:
- panic("unreachable ESP integrity suite")
- }
- _, _ = mac.Write(packet)
- return mac.Sum(nil)[:direction.icvLength]
- }
- type hashWriter interface {
- Write([]byte) (int, error)
- Sum([]byte) []byte
- }
- func (window replayWindow) wouldAccept(sequence uint32) bool {
- if sequence == 0 {
- return false
- }
- if window.highest == 0 || sequence > window.highest {
- return true
- }
- difference := window.highest - sequence
- if difference >= espReplayWindow {
- return false
- }
- return window.bitmap&(uint64(1)<<difference) == 0
- }
- func (window *replayWindow) commit(sequence uint32) {
- if window.highest == 0 {
- window.highest = sequence
- window.bitmap = 1
- return
- }
- if sequence > window.highest {
- difference := sequence - window.highest
- if difference >= espReplayWindow {
- window.bitmap = 1
- } else {
- window.bitmap = window.bitmap<<difference | 1
- }
- window.highest = sequence
- return
- }
- window.bitmap |= uint64(1) << (window.highest - sequence)
- }
- func parseInnerPacket(packet []byte) (innerPacketMetadata, error) {
- if len(packet) == 0 {
- return innerPacketMetadata{}, errors.New("ike: inner IP packet is empty")
- }
- switch packet[0] >> 4 {
- case 4:
- return parseInnerIPv4(packet)
- case 6:
- return parseInnerIPv6(packet)
- default:
- return innerPacketMetadata{}, errors.New("ike: inner packet is not IPv4 or IPv6")
- }
- }
- func parseInnerIPv4(packet []byte) (innerPacketMetadata, error) {
- if len(packet) < 20 {
- return innerPacketMetadata{}, errors.New("ike: inner IPv4 packet is truncated")
- }
- headerLength := int(packet[0]&0x0f) * 4
- if headerLength < 20 || headerLength > len(packet) {
- return innerPacketMetadata{}, errors.New("ike: inner IPv4 header length is invalid")
- }
- totalLength := int(binary.BigEndian.Uint16(packet[2:4]))
- if totalLength != len(packet) || totalLength < headerLength {
- return innerPacketMetadata{}, errors.New("ike: inner IPv4 total length is invalid")
- }
- metadata := innerPacketMetadata{
- source: append(net.IP(nil), packet[12:16]...),
- destination: append(net.IP(nil), packet[16:20]...),
- protocol: packet[9],
- nextHeader: 4,
- }
- fragmentOffset := binary.BigEndian.Uint16(packet[6:8]) & 0x1fff
- if fragmentOffset == 0 {
- parseTransportPorts(packet[headerLength:], &metadata)
- }
- return metadata, nil
- }
- func parseInnerIPv6(packet []byte) (innerPacketMetadata, error) {
- if len(packet) < 40 {
- return innerPacketMetadata{}, errors.New("ike: inner IPv6 packet is truncated")
- }
- payloadLength := int(binary.BigEndian.Uint16(packet[4:6]))
- if payloadLength+40 != len(packet) {
- return innerPacketMetadata{}, errors.New("ike: inner IPv6 payload length is invalid")
- }
- metadata := innerPacketMetadata{
- source: append(net.IP(nil), packet[8:24]...),
- destination: append(net.IP(nil), packet[24:40]...),
- nextHeader: 41,
- }
- protocol := packet[6]
- offset := 40
- firstFragment := true
- for {
- switch protocol {
- case 0, 43, 60:
- if offset+2 > len(packet) {
- return innerPacketMetadata{}, errors.New("ike: inner IPv6 extension header is truncated")
- }
- length := (int(packet[offset+1]) + 1) * 8
- if length < 8 || offset+length > len(packet) {
- return innerPacketMetadata{}, errors.New("ike: inner IPv6 extension header length is invalid")
- }
- protocol = packet[offset]
- offset += length
- case 44:
- if offset+8 > len(packet) {
- return innerPacketMetadata{}, errors.New("ike: inner IPv6 fragment header is truncated")
- }
- firstFragment = binary.BigEndian.Uint16(packet[offset+2:offset+4])&0xfff8 == 0
- protocol = packet[offset]
- offset += 8
- case 51:
- if offset+2 > len(packet) {
- return innerPacketMetadata{}, errors.New("ike: inner IPv6 AH header is truncated")
- }
- length := (int(packet[offset+1]) + 2) * 4
- if length < 8 || offset+length > len(packet) {
- return innerPacketMetadata{}, errors.New("ike: inner IPv6 AH header length is invalid")
- }
- protocol = packet[offset]
- offset += length
- default:
- metadata.protocol = protocol
- if firstFragment {
- parseTransportPorts(packet[offset:], &metadata)
- }
- return metadata, nil
- }
- }
- }
- func parseTransportPorts(payload []byte, metadata *innerPacketMetadata) {
- if metadata == nil || (metadata.protocol != 6 && metadata.protocol != 17) || len(payload) < 4 {
- return
- }
- metadata.sourcePort = binary.BigEndian.Uint16(payload[0:2])
- metadata.destinationPort = binary.BigEndian.Uint16(payload[2:4])
- }
- func packetAllowed(
- metadata innerPacketMetadata,
- sourceSelectors []trafficSelector,
- destinationSelectors []trafficSelector,
- ) bool {
- return endpointAllowed(
- metadata.source,
- metadata.protocol,
- metadata.sourcePort,
- sourceSelectors,
- ) && endpointAllowed(
- metadata.destination,
- metadata.protocol,
- metadata.destinationPort,
- destinationSelectors,
- )
- }
- func endpointAllowed(ip net.IP, protocol uint8, port uint16, selectors []trafficSelector) bool {
- for _, selector := range selectors {
- if selector.IPProtocol != 0 && selector.IPProtocol != protocol {
- continue
- }
- if port < selector.StartPort || port > selector.EndPort {
- continue
- }
- if ipWithinRange(ip, selector.StartIP, selector.EndIP) {
- return true
- }
- }
- return false
- }
- func ipWithinRange(ip net.IP, start net.IP, end net.IP) bool {
- normalizedIP, normalizedStart, normalizedEnd, ok := normalizeIPRange(ip, start, end)
- if !ok {
- return false
- }
- return bytes.Compare(normalizedIP, normalizedStart) >= 0 &&
- bytes.Compare(normalizedIP, normalizedEnd) <= 0
- }
- func normalizeIPRange(ip net.IP, start net.IP, end net.IP) ([]byte, []byte, []byte, bool) {
- if start4 := start.To4(); start4 != nil {
- ip4 := ip.To4()
- end4 := end.To4()
- if ip4 == nil || end4 == nil {
- return nil, nil, nil, false
- }
- return ip4, start4, end4, true
- }
- ip16 := ip.To16()
- start16 := start.To16()
- end16 := end.To16()
- if ip16 == nil || start16 == nil || end16 == nil || start.To4() != nil || end.To4() != nil {
- return nil, nil, nil, false
- }
- return ip16, start16, end16, true
- }
- func copyESPTrafficSelectors(selectors []trafficSelector) []trafficSelector {
- cloned := make([]trafficSelector, len(selectors))
- for index, selector := range selectors {
- cloned[index] = selector
- cloned[index].StartIP = append(net.IP(nil), selector.StartIP...)
- cloned[index].EndIP = append(net.IP(nil), selector.EndIP...)
- }
- return cloned
- }
|