package ike import ( "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/rand" "crypto/sha1" "crypto/sha256" "crypto/subtle" "encoding/binary" "encoding/hex" "errors" "fmt" "hash" "io" "math/big" "net" ) type ikeKeys struct { SKd []byte SKai []byte SKar []byte SKei []byte SKer []byte SKpi []byte SKpr []byte } func (suite negotiatedSuite) prf() (func() hash.Hash, int, error) { switch suite.PRFID { case prfHMACSHA1: return sha1.New, sha1.Size, nil case prfHMACSHA256: return sha256.New, sha256.Size, nil default: return nil, 0, fmt.Errorf("%w: PRF id %d", errUnsupportedSuite, suite.PRFID) } } func (suite negotiatedSuite) encryptionKeyLength() (int, error) { switch suite.EncryptionBits { case 128, 256: return suite.EncryptionBits / 8, nil default: return 0, fmt.Errorf("%w: AES key length %d", errUnsupportedSuite, suite.EncryptionBits) } } func (suite negotiatedSuite) integrityLengths() (keyLength int, checksumLength int, err error) { switch suite.IntegrityID { case integrityHMACSHA1_96: return sha1.Size, 12, nil case integrityHMACSHA256_128: return sha256.Size, 16, nil default: return 0, 0, fmt.Errorf("%w: integrity id %d", errUnsupportedSuite, suite.IntegrityID) } } func prf(suite negotiatedSuite, key, data []byte) ([]byte, error) { hashFactory, _, err := suite.prf() if err != nil { return nil, err } mac := hmac.New(hashFactory, key) _, _ = mac.Write(data) return mac.Sum(nil), nil } func prfPlus(suite negotiatedSuite, key, seed []byte, length int) ([]byte, error) { if length < 0 { return nil, errors.New("ike: negative key stream length") } result := make([]byte, 0, length) var previous []byte for counter := byte(1); len(result) < length; counter++ { if counter == 0 { return nil, errors.New("ike: PRF+ output is too long") } input := make([]byte, 0, len(previous)+len(seed)+1) input = append(input, previous...) input = append(input, seed...) input = append(input, counter) block, err := prf(suite, key, input) if err != nil { return nil, err } result = append(result, block...) previous = block } return result[:length], nil } func deriveIKEKeys( suite negotiatedSuite, sharedSecret []byte, initiatorNonce []byte, responderNonce []byte, initiatorSPI [8]byte, responderSPI [8]byte, ) (ikeKeys, error) { _, preferredLength, err := suite.prf() if err != nil { return ikeKeys{}, err } encryptionLength, err := suite.encryptionKeyLength() if err != nil { return ikeKeys{}, err } integrityLength, _, err := suite.integrityLengths() if err != nil { return ikeKeys{}, err } nonceKey := append(append([]byte(nil), initiatorNonce...), responderNonce...) skeyseed, err := prf(suite, nonceKey, sharedSecret) if err != nil { return ikeKeys{}, err } seed := make([]byte, 0, len(initiatorNonce)+len(responderNonce)+16) seed = append(seed, initiatorNonce...) seed = append(seed, responderNonce...) seed = append(seed, initiatorSPI[:]...) seed = append(seed, responderSPI[:]...) total := preferredLength + integrityLength*2 + encryptionLength*2 + preferredLength*2 stream, err := prfPlus(suite, skeyseed, seed, total) if err != nil { return ikeKeys{}, err } take := func(length int) []byte { value := append([]byte(nil), stream[:length]...) stream = stream[length:] return value } return ikeKeys{ SKd: take(preferredLength), SKai: take(integrityLength), SKar: take(integrityLength), SKei: take(encryptionLength), SKer: take(encryptionLength), SKpi: take(preferredLength), SKpr: take(preferredLength), }, nil } func integrityMAC(suite negotiatedSuite, key, packetWithoutChecksum []byte) ([]byte, error) { var hashFactory func() hash.Hash switch suite.IntegrityID { case integrityHMACSHA1_96: hashFactory = sha1.New case integrityHMACSHA256_128: hashFactory = sha256.New default: return nil, fmt.Errorf("%w: integrity id %d", errUnsupportedSuite, suite.IntegrityID) } _, checksumLength, err := suite.integrityLengths() if err != nil { return nil, err } mac := hmac.New(hashFactory, key) _, _ = mac.Write(packetWithoutChecksum) return mac.Sum(nil)[:checksumLength], nil } func encryptPayloads( header ikeHeader, inner []payload, suite negotiatedSuite, encryptionKey []byte, integrityKey []byte, random io.Reader, ) ([]byte, error) { if random == nil { random = rand.Reader } first, plaintext, err := marshalPayloadChain(inner) if err != nil { return nil, err } block, err := aes.NewCipher(encryptionKey) if err != nil { return nil, fmt.Errorf("ike: initialize AES: %w", err) } paddingLength := block.BlockSize() - (len(plaintext)+1)%block.BlockSize() if paddingLength == block.BlockSize() { paddingLength = 0 } padding := make([]byte, paddingLength) if _, err := io.ReadFull(random, padding); err != nil { return nil, fmt.Errorf("ike: generate encrypted payload padding: %w", err) } plaintext = append(plaintext, padding...) plaintext = append(plaintext, byte(paddingLength)) iv := make([]byte, block.BlockSize()) if _, err := io.ReadFull(random, iv); err != nil { return nil, fmt.Errorf("ike: generate encrypted payload IV: %w", err) } ciphertext := make([]byte, len(plaintext)) cipher.NewCBCEncrypter(block, iv).CryptBlocks(ciphertext, plaintext) _, checksumLength, err := suite.integrityLengths() if err != nil { return nil, err } skLength := 4 + len(iv) + len(ciphertext) + checksumLength if skLength > 65535 { return nil, errors.New("ike: encrypted payload exceeds 65535 bytes") } body := make([]byte, skLength) body[0] = first body[1] = 0 binary.BigEndian.PutUint16(body[2:4], uint16(skLength)) copy(body[4:], iv) copy(body[4+len(iv):], ciphertext) header.NextPayload = payloadEncrypted packet := header.marshal(body) checksum, err := integrityMAC(suite, integrityKey, packet[:len(packet)-checksumLength]) if err != nil { return nil, err } copy(packet[len(packet)-checksumLength:], checksum) return packet, nil } func decryptPayloads( packet []byte, suite negotiatedSuite, encryptionKey []byte, integrityKey []byte, ) (ikeHeader, []payload, error) { header, body, err := parseIKEPacket(packet) if err != nil { return ikeHeader{}, nil, err } if header.NextPayload != payloadEncrypted || len(body) < 4 { return ikeHeader{}, nil, fmt.Errorf("%w: message is not an encrypted IKE payload", errUnexpectedPacket) } skLength := int(binary.BigEndian.Uint16(body[2:4])) if skLength != len(body) { return ikeHeader{}, nil, fmt.Errorf("%w: encrypted payload length mismatch", errMalformedPacket) } block, err := aes.NewCipher(encryptionKey) if err != nil { return ikeHeader{}, nil, fmt.Errorf("ike: initialize AES: %w", err) } _, checksumLength, err := suite.integrityLengths() if err != nil { return ikeHeader{}, nil, err } if len(body) < 4+block.BlockSize()+block.BlockSize()+checksumLength { return ikeHeader{}, nil, fmt.Errorf("%w: encrypted payload is too short", errMalformedPacket) } expected, err := integrityMAC(suite, integrityKey, packet[:len(packet)-checksumLength]) if err != nil { return ikeHeader{}, nil, err } actual := packet[len(packet)-checksumLength:] if subtle.ConstantTimeCompare(actual, expected) != 1 { return ikeHeader{}, nil, errIntegrityMismatch } ivStart := 4 ciphertextStart := ivStart + block.BlockSize() ciphertextEnd := len(body) - checksumLength ciphertext := body[ciphertextStart:ciphertextEnd] if len(ciphertext) == 0 || len(ciphertext)%block.BlockSize() != 0 { return ikeHeader{}, nil, fmt.Errorf("%w: ciphertext is not block aligned", errMalformedPacket) } plaintext := make([]byte, len(ciphertext)) cipher.NewCBCDecrypter(block, body[ivStart:ciphertextStart]).CryptBlocks(plaintext, ciphertext) paddingLength := int(plaintext[len(plaintext)-1]) if paddingLength+1 > len(plaintext) { return ikeHeader{}, nil, fmt.Errorf("%w: invalid encrypted payload padding", errMalformedPacket) } plaintext = plaintext[:len(plaintext)-paddingLength-1] payloads, err := parsePayloadChain(body[0], plaintext) if err != nil { return ikeHeader{}, nil, err } return header, payloads, nil } var modpPrimes = map[uint16]string{ dhMODP1024: "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + "FFFFFFFFFFFFFFFF", dhMODP2048: "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", } type dhExchange struct { Group uint16 prime *big.Int private *big.Int Public []byte } func newDHExchange(group uint16, random io.Reader) (*dhExchange, error) { primeHex, ok := modpPrimes[group] if !ok { return nil, fmt.Errorf("%w: DH group %d", errUnsupportedSuite, group) } primeBytes, err := hex.DecodeString(primeHex) if err != nil { return nil, fmt.Errorf("ike: internal MODP constant: %w", err) } prime := new(big.Int).SetBytes(primeBytes) if random == nil { random = rand.Reader } sample := make([]byte, len(primeBytes)) if _, err := io.ReadFull(random, sample); err != nil { return nil, fmt.Errorf("ike: generate DH private value: %w", err) } private := new(big.Int).SetBytes(sample) private.Mod(private, new(big.Int).Sub(prime, big.NewInt(3))) private.Add(private, big.NewInt(2)) publicInteger := new(big.Int).Exp(big.NewInt(2), private, prime) public := publicInteger.FillBytes(make([]byte, len(primeBytes))) return &dhExchange{Group: group, prime: prime, private: private, Public: public}, nil } func (exchange *dhExchange) shared(peerPublic []byte) ([]byte, error) { if exchange == nil || exchange.prime == nil || exchange.private == nil { return nil, errors.New("ike: DH exchange is not initialized") } if len(peerPublic) != len(exchange.Public) { return nil, fmt.Errorf("ike: peer DH value length %d does not match group length %d", len(peerPublic), len(exchange.Public)) } peer := new(big.Int).SetBytes(peerPublic) upper := new(big.Int).Sub(exchange.prime, big.NewInt(2)) if peer.Cmp(big.NewInt(2)) < 0 || peer.Cmp(upper) > 0 { return nil, errors.New("ike: peer DH public value is outside the safe range") } shared := new(big.Int).Exp(peer, exchange.private, exchange.prime) if shared.Sign() == 0 || shared.Cmp(big.NewInt(1)) == 0 { return nil, errors.New("ike: invalid trivial DH shared secret") } return shared.FillBytes(make([]byte, len(exchange.Public))), nil } func natDetectionHash( initiatorSPI [8]byte, responderSPI [8]byte, ip net.IP, port uint16, ) ([]byte, error) { if ip4 := ip.To4(); ip4 != nil { ip = ip4 } else if ip16 := ip.To16(); ip16 != nil { ip = ip16 } else { return nil, errors.New("ike: NAT detection address is not an IP address") } input := make([]byte, 0, 16+len(ip)+2) input = append(input, initiatorSPI[:]...) input = append(input, responderSPI[:]...) input = append(input, ip...) var encodedPort [2]byte binary.BigEndian.PutUint16(encodedPort[:], port) input = append(input, encodedPort[:]...) sum := sha1.Sum(input) return sum[:], nil }