lib/packetgen/plugin/ike/sk.rb in packetgen-plugin-ipsec-1.0.2 vs lib/packetgen/plugin/ike/sk.rb in packetgen-plugin-ipsec-1.0.3
- old
+ new
@@ -1,261 +1,259 @@
# coding: utf-8
+# frozen_string_literal: true
+
# This file is part of IPsec packetgen plugin.
# See https://github.com/sdaubert/packetgen-plugin-ipsec for more informations
# Copyright (c) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
# This program is published under MIT license.
-# frozen_string_literal: true
+module PacketGen::Plugin
+ class IKE
+ # This class handles encrypted payloads, denoted SK.
+ #
+ # The encrypted payload contains other payloads in encrypted form.
+ # The Encrypted payload consists of the IKE generic payload Plugin followed
+ # by individual fields as follows:
+ # 1 2 3
+ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Next Payload |C| RESERVED | Payload Length |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Initialization Vector |
+ # | (length is block size for encryption algorithm) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # ~ Encrypted IKE Payloads ~
+ # + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | | Padding (0-255 octets) |
+ # +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+ # | | Pad Length |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # ~ Integrity Checksum Data ~
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # Encrypted payloads are set in {#content} field, as a {PacketGen::Types::String}.
+ # All others fields are only set when decrypting a previously read SK
+ # payload. They also may be set manually to encrypt IKE payloads.
+ #
+ # == Read and decrypt a SK payload
+ # # Read a IKE packet
+ # pkt = PacketGen.read(str)
+ # # decrypt SK payload
+ # cipher = OpenSSL::Cipher.new('aes-128-ctr')
+ # cipher.decrypt
+ # cipher_key = aes_key
+ # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
+ # pkt.ike_sk.decrypt! cipher, intmode: hmac, icv_length: 16 # => true if authentication is verified
+ # pkt.ike_sk.body # => kind of PacketGen::Plugin::IKE::Payload
+ #
+ # == Set and encrypt a SK payload
+ # # Create a IKE packet
+ # pkt = PacketGen.gen('IP').add('IP').add('UDP').add('IKE', init_spi: 0x123456789, resp_spi: 0x987654321, type: 'IKE_AUTH', message_id: 1)
+ # # Add SK payload
+ # pkt.add('IKE::SK', icv_length: 16)
+ # # Add others unencrypted payloads
+ # pkt.add('IKE::IDi').add('IKE::Auth').add('IKE::SA').add('IKE::TSi').add('IKE::TSr')
+ # # encrypt SK payload
+ # cipher = OpenSSL::Cipher.new('aes-128-ctr')
+ # cipher.encrypt
+ # cipher_key = aes_key
+ # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
+ # pkt.ike_sk.encrypt! cipher, iv, salt: salt, intmode: hmac
+ # pkt.ike_sk.body # => String
+ # pkt.calc_length
+ #
+ # @author Sylvain Daubert
+ class SK < Payload
+ include Crypto
-module PacketGen
- module Plugin
- class IKE
- # This class handles encrypted payloads, denoted SK.
- #
- # The encrypted payload contains other payloads in encrypted form.
- # The Encrypted payload consists of the IKE generic payload Plugin followed
- # by individual fields as follows:
- # 1 2 3
- # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- # | Next Payload |C| RESERVED | Payload Length |
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- # | Initialization Vector |
- # | (length is block size for encryption algorithm) |
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- # ~ Encrypted IKE Payloads ~
- # + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- # | | Padding (0-255 octets) |
- # +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
- # | | Pad Length |
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- # ~ Integrity Checksum Data ~
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- # Encrypted payloads are set in {#content} field, as a {PacketGen::Types::String}.
- # All others fields are only set when decrypting a previously read SK
- # payload. They also may be set manually to encrypt IKE payloads.
- #
- # == Read and decrypt a SK payload
- # # Read a IKE packet
- # pkt = PacketGen.read(str)
- # # decrypt SK payload
- # cipher = OpenSSL::Cipher.new('aes-128-ctr')
- # cipher.decrypt
- # cipher_key = aes_key
- # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
- # pkt.ike_sk.decrypt! cipher, intmode: hmac, icv_length: 16 # => true if authentication is verified
- # pkt.ike_sk.body # => kind of PacketGen::Plugin::IKE::Payload
- #
- # == Set and encrypt a SK payload
- # # Create a IKE packet
- # pkt = PacketGen.gen('IP').add('IP').add('UDP').add('IKE', init_spi: 0x123456789, resp_spi: 0x987654321, type: 'IKE_AUTH', message_id: 1)
- # # Add SK payload
- # pkt.add('IKE::SK', icv_length: 16)
- # # Add others unencrypted payloads
- # pkt.add('IKE::IDi').add('IKE::Auth').add('IKE::SA').add('IKE::TSi').add('IKE::TSr')
- # # encrypt SK payload
- # cipher = OpenSSL::Cipher.new('aes-128-ctr')
- # cipher.encrypt
- # cipher_key = aes_key
- # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
- # pkt.ike_sk.encrypt! cipher, iv, salt: salt, intmode: hmac
- # pkt.ike_sk.body # => String
- # pkt.calc_length
- #
- # @author Sylvain Daubert
- class SK < Payload
- include Crypto
+ # Payload type number
+ PAYLOAD_TYPE = 46
- # Payload type number
- PAYLOAD_TYPE = 46
+ # ICV (Integrity Check Value) length
+ # @return [Integer]
+ attr_accessor :icv_length
- # ICV (Integrity Check Value) length
- # @return [Integer]
- attr_accessor :icv_length
+ # @param [Hash] options
+ # @option options [Integer] :icv_length ICV length
+ def initialize(options={})
+ @icv_length = options[:icv_length] || 0
+ super
+ end
- # @param [Hash] options
- # @option options [Integer] :icv_length ICV length
- def initialize(options={})
- @icv_length = options[:icv_length] || 0
- super
- end
+ # Decrypt in-place SK payload.
+ # @param [OpenSSL::Cipher] cipher keyed cipher
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
+ # cipher to add integrity, use +:intmode+ option.
+ # @param [Hash] options
+ # @option options [Boolean] :parse parse deciphered payload to retrieve
+ # Plugins (default: +true+)
+ # @option options [Fixnum] :icv_length ICV length for captured packets,
+ # or read from PCapNG files
+ # @option options [String] :salt salt value for CTR and GCM modes
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
+ # confidentiality-only cipher. Only HMAC are supported.
+ # @return [Boolean] +true+ if SK payload is authenticated
+ def decrypt!(cipher, options={})
+ opt = { salt: '', parse: true }.merge!(options)
- # Decrypt in-place SK payload.
- # @param [OpenSSL::Cipher] cipher keyed cipher
- # This cipher is confidentiality-only one, or AEAD one. To use a second
- # cipher to add integrity, use +:intmode+ option.
- # @param [Hash] options
- # @option options [Boolean] :parse parse deciphered payload to retrieve
- # Plugins (default: +true+)
- # @option options [Fixnum] :icv_length ICV length for captured packets,
- # or read from PCapNG files
- # @option options [String] :salt salt value for CTR and GCM modes
- # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
- # confidentiality-only cipher. Only HMAC are supported.
- # @return [Boolean] +true+ if SK payload is authenticated
- def decrypt!(cipher, options={})
- opt = { salt: '', parse: true }.merge!(options)
+ set_crypto cipher, opt[:intmode]
- set_crypto cipher, opt[:intmode]
+ case confidentiality_mode
+ when 'gcm'
+ iv = self[:content].slice!(0, 8)
+ real_iv = force_binary(opt[:salt]) + iv
+ when 'cbc'
+ cipher.padding = 0
+ real_iv = iv = self[:content].slice!(0, 16)
+ when 'ctr'
+ iv = self[:content].slice!(0, 8)
+ real_iv = force_binary(opt[:salt]) + iv + [1].pack('N')
+ else
+ real_iv = iv = self[:content].slice!(0, 16)
+ end
+ cipher.iv = real_iv
- case confidentiality_mode
- when 'gcm'
- iv = self[:content].slice!(0, 8)
- real_iv = force_binary(opt[:salt]) + iv
- when 'cbc'
- cipher.padding = 0
- real_iv = iv = self[:content].slice!(0, 16)
- when 'ctr'
- iv = self[:content].slice!(0, 8)
- real_iv = force_binary(opt[:salt]) + iv + [1].pack('N')
- else
- real_iv = iv = self[:content].slice!(0, 16)
+ if authenticated?
+ if @icv_length.zero?
+ @icv_length = opt[:icv_length].to_i if opt[:icv_length]
+ raise PacketGen::ParseError, 'unknown ICV size' if @icv_length.zero?
end
- cipher.iv = real_iv
-
- if authenticated?
- if @icv_length.zero?
- @icv_length = opt[:icv_length].to_i if opt[:icv_length]
- raise ParseError, 'unknown ICV size' if @icv_length.zero?
- end
- icv = self[:content].slice!(-@icv_length, @icv_length)
- end
-
- authenticate_if_needed iv, icv
- private_decrypt opt
+ icv = self[:content].slice!(-@icv_length, @icv_length)
end
- # Encrypt in-place SK payload.
- # @param [OpenSSL::Cipher] cipher keyed cipher
- # This cipher is confidentiality-only one, or AEAD one. To use a second
- # cipher to add integrity, use +:intmode+ option.
- # @param [String] iv IV to encipher SK payload content
- # * CTR and GCM modes: +iv+ is 8-bytes long.
- # @param [Hash] options
- # @option options [Fixnum] :icv_length ICV length for captured packets,
- # or read from PCapNG files
- # @option options [String] :salt salt value for CTR and GCM modes
- # @option options [Fixnum] :pad_length set a padding length
- # @option options [String] :padding set a padding. No check with
- # +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
- # length is shortened to correct padding length
- # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
- # confidentiality-only cipher. Only HMAC are supported.
- # @return [self]
- def encrypt!(cipher, iv, options={})
- opt = { salt: '' }.merge!(options)
+ authenticate_if_needed iv, icv
+ private_decrypt opt
+ end
- set_crypto cipher, opt[:intmode]
+ # Encrypt in-place SK payload.
+ # @param [OpenSSL::Cipher] cipher keyed cipher
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
+ # cipher to add integrity, use +:intmode+ option.
+ # @param [String] iv IV to encipher SK payload content
+ # * CTR and GCM modes: +iv+ is 8-bytes long.
+ # @param [Hash] options
+ # @option options [Fixnum] :icv_length ICV length for captured packets,
+ # or read from PCapNG files
+ # @option options [String] :salt salt value for CTR and GCM modes
+ # @option options [Fixnum] :pad_length set a padding length
+ # @option options [String] :padding set a padding. No check with
+ # +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
+ # length is shortened to correct padding length
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
+ # confidentiality-only cipher. Only HMAC are supported.
+ # @return [self]
+ def encrypt!(cipher, iv, options={})
+ opt = { salt: '' }.merge!(options)
- real_iv = force_binary(opt[:salt]) + force_binary(iv)
- real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
- cipher.iv = real_iv
+ set_crypto cipher, opt[:intmode]
- authenticate_if_needed iv
+ real_iv = force_binary(opt[:salt]) + force_binary(iv)
+ real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
+ cipher.iv = real_iv
- if opt[:pad_length]
- pad_length = opt[:pad_length]
- padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
- else
- pad_length = cipher.block_size
- pad_length = 16 if cipher.block_size == 1 # Some AES mode returns 1...
- pad_length -= (self[:body].sz + iv.size + 1) % cipher.block_size
- pad_length = 0 if pad_length == 16
- padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
- padding = padding[0, pad_length]
- end
- msg = self[:body].to_s + padding + PacketGen::Types::Int8.new(pad_length).to_s
- encrypted_msg = encipher(msg)
- cipher.final # message is already padded. No need for mode padding
+ authenticate_if_needed iv
- if authenticated?
- @icv_length = opt[:icv_length] if opt[:icv_length]
- encrypted_msg << if @conf.authenticated?
- @conf.auth_tag[0, @icv_length]
- else
- @intg.digest[0, @icv_length]
- end
- end
- self[:content].read(iv + encrypted_msg)
+ if opt[:pad_length]
+ pad_length = opt[:pad_length]
+ padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
+ else
+ pad_length = cipher.block_size
+ pad_length = 16 if cipher.block_size == 1 # Some AES mode returns 1...
+ pad_length -= (self[:body].sz + iv.size + 1) % cipher.block_size
+ pad_length = 0 if pad_length == 16
+ padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
+ padding = padding[0, pad_length]
+ end
+ msg = self[:body].to_s + padding + PacketGen::Types::Int8.new(pad_length).to_s
+ encrypted_msg = encipher(msg)
+ cipher.final # message is already padded. No need for mode padding
- # Remove plain payloads
- self[:body] = PacketGen::Types::String.new
+ if authenticated?
+ @icv_length = opt[:icv_length] if opt[:icv_length]
+ encrypted_msg << if @conf.authenticated?
+ @conf.auth_tag[0, @icv_length]
+ else
+ @intg.digest[0, @icv_length]
+ end
+ end
+ self[:content].read(iv + encrypted_msg)
- # Remove enciphered payloads from packet
- id = header_id(self)
- if id < packet.headers.size - 1
- (packet.headers.size - 1).downto(id + 1) do |index|
- packet.headers.delete_at index
- end
- end
+ # Remove plain payloads
+ self[:body] = PacketGen::Types::String.new
- self.calc_length
- self
+ # Remove enciphered payloads from packet
+ id = header_id(self)
+ if id < packet.headers.size - 1
+ (packet.headers.size - 1).downto(id + 1) do |index|
+ packet.headers.delete_at index
+ end
end
- private
+ self.calc_length
+ self
+ end
- def authenticate_if_needed(iv, icv=nil)
- if @conf.authenticated?
- @conf.auth_tag = icv if icv
- @conf.auth_data = get_ad
- elsif @intg
- @intg.reset
- @intg.update get_ad
- @intg.update iv
- @icv = icv
- else
- @icv = nil
- end
+ private
+
+ def authenticate_if_needed(iv, icv=nil)
+ if @conf.authenticated?
+ @conf.auth_tag = icv if icv
+ @conf.auth_data = get_ad
+ elsif @intg
+ @intg.reset
+ @intg.update get_ad
+ @intg.update iv
+ @icv = icv
+ else
+ @icv = nil
end
+ end
- # From RFC 7206, §5.1: The associated data MUST consist of the partial
- # contents of the IKEv2 message, starting from the first octet of the
- # Fixed IKE Plugin through the last octet of the Payload Plugin of the
- # Encrypted Payload (i.e., the fourth octet of the Encrypted Payload).
- def get_ad
- str = packet.ike.to_s[0, IKE.new.sz]
- current_payload = packet.ike[:body]
- until current_payload.is_a? SK
- str << current_payload.to_s[0, current_payload.to_s.length]
- current_payload = current_payload[:body]
- end
- str << self.to_s[0, SK.new.sz]
+ # From RFC 7206, §5.1: The associated data MUST consist of the partial
+ # contents of the IKEv2 message, starting from the first octet of the
+ # Fixed IKE Plugin through the last octet of the Payload Plugin of the
+ # Encrypted Payload (i.e., the fourth octet of the Encrypted Payload).
+ def get_ad
+ str = packet.ike.to_s[0, IKE.new.sz]
+ current_payload = packet.ike[:body]
+ until current_payload.is_a? SK
+ str << current_payload.to_s[0, current_payload.to_s.length]
+ current_payload = current_payload[:body]
end
+ str << self.to_s[0, SK.new.sz]
+ end
- def private_decrypt(options)
- # decrypt
- plain_msg = decipher(content.to_s)
- # Remove cipher text
- self[:content].read ''
+ def private_decrypt(options)
+ # decrypt
+ plain_msg = decipher(content.to_s)
+ # Remove cipher text
+ self[:content].read ''
- # check authentication tag
- if authenticated?
- return false unless authenticate!
- end
+ # check authentication tag
+ if authenticated?
+ return false unless authenticate!
+ end
- # remove padding
- pad_len = PacketGen::Types::Int8.new.read(plain_msg[-1]).to_i
- payloads = plain_msg[0, plain_msg.size - 1 - pad_len]
+ # remove padding
+ pad_len = PacketGen::Types::Int8.new.read(plain_msg[-1]).to_i
+ payloads = plain_msg[0, plain_msg.size - 1 - pad_len]
- # parse IKE payloads
- if options[:parse]
- klass = IKE.constants.select do |c|
- cst = IKE.const_get(c)
- cst.is_a?(Class) && (cst < Payload) && (cst::PAYLOAD_TYPE == self.next)
- end
- klass = klass.nil? ? Payload : IKE.const_get(klass.first)
- firsth = klass.protocol_name
- pkt = Packet.parse(payloads, first_header: firsth)
- packet.encapsulate(pkt, parsing: true) unless pkt.nil?
- else
- self[:body].read payloads
+ # parse IKE payloads
+ if options[:parse]
+ klass = IKE.constants.select do |c|
+ cst = IKE.const_get(c)
+ cst.is_a?(Class) && (cst < Payload) && (cst::PAYLOAD_TYPE == self.next)
end
-
- true
+ klass = klass.nil? ? Payload : IKE.const_get(klass.first)
+ firsth = klass.protocol_name
+ pkt = PacketGen::Packet.parse(payloads, first_header: firsth)
+ packet.encapsulate(pkt, parsing: true) unless pkt.nil?
+ else
+ self[:body].read payloads
end
+
+ true
end
end
-
- Header.add_class IKE::SK
end
+
+ PacketGen::Header.add_class IKE::SK
end