lib/packetgen/plugin/ike/auth.rb in packetgen-plugin-ipsec-1.0.2 vs lib/packetgen/plugin/ike/auth.rb in packetgen-plugin-ipsec-1.0.3

- old
+ new

@@ -1,164 +1,164 @@ # 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 Authentication payloads. + # + # A AUTH payload consists of the IKE generic payload Plugin (see {Payload}) + # and some specific fields: + # 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 | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Auth Method | RESERVED | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # ~ Authentication Data ~ + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # These specific fields are: + # * {#type} (ID type), + # * {#reserved}, + # * and {#content} (Identification Data). + # + # == Create a KE payload + # # create a IKE packet with a Auth payload + # pkt = PacketGen.gen('IP').add('UDP').add('IKE').add('IKE::Auth', auth_method: 'SHARED_KEY') + # pkt.calc_length + # @author Sylvain Daubert + class Auth < Payload + # Payload type number + PAYLOAD_TYPE = 39 -module PacketGen - module Plugin - class IKE - # This class handles Authentication payloads. - # - # A AUTH payload consists of the IKE generic payload Plugin (see {Payload}) - # and some specific fields: - # 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 | - # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - # | Auth Method | RESERVED | - # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - # | | - # ~ Authentication Data ~ - # | | - # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - # These specific fields are: - # * {#type} (ID type), - # * {#reserved}, - # * and {#content} (Identification Data). - # - # == Create a KE payload - # # create a IKE packet with a Auth payload - # pkt = PacketGen.gen('IP').add('UDP').add('IKE').add('IKE::Auth', auth_method: 'SHARED_KEY') - # pkt.calc_length - # @author Sylvain Daubert - class Auth < Payload - # Payload type number - PAYLOAD_TYPE = 39 + # Authentication methods + METHODS = { + 'RSA_SIGNATURE' => 1, + 'SHARED_KEY' => 2, + 'DSA_SIGNATURE' => 3, + 'ECDSA256' => 9, + 'ECDSA384' => 10, + 'ECDSA512' => 11, + 'PASSWORD' => 12, + 'NULL' => 13, + 'DIGITAL_SIGNATURE' => 14 + }.freeze - METHODS = { - 'RSA_SIGNATURE' => 1, - 'SHARED_KEY' => 2, - 'DSA_SIGNATURE' => 3, - 'ECDSA256' => 9, - 'ECDSA384' => 10, - 'ECDSA512' => 11, - 'PASSWORD' => 12, - 'NULL' => 13, - 'DIGITAL_SIGNATURE' => 14 - }.freeze + # @attribute [r] auth_method + # 8-bit Auth Method + # @return [Integer] + define_field_before :content, :auth_method, PacketGen::Types::Int8Enum, enum: METHODS + # @attribute reserved + # 24-bit reserved field + # @return [Integer] + define_field_before :content, :reserved, PacketGen::Types::Int24 - # @attribute [r] auth_method - # 8-bit Auth Method - # @return [Integer] - define_field_before :content, :auth_method, PacketGen::Types::Int8Enum, enum: METHODS - # @attribute reserved - # 24-bit reserved field - # @return [Integer] - define_field_before :content, :reserved, PacketGen::Types::Int24 + # Check authentication (see RFC 7296 §2.15) + # @param [Packet] init_msg first IKE message sent by peer + # @param [String] nonce my nonce, sent in first message + # @param [String] sk_p secret key used to compute prf(SK_px, IDx') + # @param [Integer] prf PRF type to use (see {Transform}+::PRF_*+ constants) + # @param [String] shared_secret shared secret to use as PSK (shared secret + # method only) + # @param [OpenSSL::X509::Certificate] cert certificate to check AUTH signature, + # if not embedded in IKE message + # @return [Boolean] + # @note For now, only NULL, SHARED_KEY and RSA, DSA and ECDSA signatures are + # supported. + # @note For certificates, only check AUTH authenticity with given (or guessed + # from packet) certificate, but certificate chain is not verified. + def check?(init_msg: nil, nonce: '', sk_p: '', prf: 1, shared_secret: '', + cert: nil) + raise TypeError, 'init_msg should be a Packet' unless init_msg.is_a?(PacketGen::Packet) - # Check authentication (see RFC 7296 §2.15) - # @param [Packet] init_msg first IKE message sent by peer - # @param [String] nonce my nonce, sent in first message - # @param [String] sk_p secret key used to compute prf(SK_px, IDx') - # @param [Integer] prf PRF type to use (see {Transform}+::PRF_*+ constants) - # @param [String] shared_secret shared secret to use as PSK (shared secret - # method only) - # @param [OpenSSL::X509::Certificate] cert certificate to check AUTH signature, - # if not embedded in IKE message - # @return [Boolean] - # @note For now, only NULL, SHARED_KEY and RSA, DSA and ECDSA signatures are - # supported. - # @note For certificates, only check AUTH authenticity with given (or guessed - # from packet) certificate, but certificate chain is not verified. - def check?(init_msg: nil, nonce: '', sk_p: '', prf: 1, shared_secret: '', - cert: nil) - raise TypeError, 'init_msg should be a Packet' unless init_msg.is_a?(Packet) - signed_octets = init_msg.ike.to_s - signed_octets << nonce - id = packet.ike.flag_i? ? packet.ike_idi : packet.ike_idr - signed_octets << prf(prf, sk_p, id.to_s[4, id.length - 4]) + signed_octets = init_msg.ike.to_s + signed_octets << nonce + id = packet.ike.flag_i? ? packet.ike_idi : packet.ike_idr + signed_octets << prf(prf, sk_p, id.to_s[4, id.length - 4]) - case auth_method - when METHODS['SHARED_KEY'] - auth = prf(prf(shared_secret, 'Key Pad for IKEv2'), signed_octets) - auth == content - when METHODS['RSA_SIGNATURE'], METHODS['ECDSA256'], METHODS['ECDSA384'], - METHODS['ECDSA512'] - if packet.ike_cert - # FIXME: Expect a ENCODING_X509_CERT_SIG - # Others types not supported for now... - cert = OpenSSL::X509::Certificate.new(packet.ike_cert.content) - elsif cert.nil? - raise CryptoError, 'a certificate should be provided' - end + case auth_method + when METHODS['SHARED_KEY'] + auth = prf(prf(shared_secret, 'Key Pad for IKEv2'), signed_octets) + auth == content + when METHODS['RSA_SIGNATURE'], METHODS['ECDSA256'], METHODS['ECDSA384'], + METHODS['ECDSA512'] + if packet.ike_cert + # FIXME: Expect a ENCODING_X509_CERT_SIG + # Others types not supported for now... + cert = OpenSSL::X509::Certificate.new(packet.ike_cert.content) + elsif cert.nil? + raise CryptoError, 'a certificate should be provided' + end - text = cert.to_text - m = text.match(/Public Key Algorithm: ([a-zA-Z0-9-]+)/) - digest = case m[1] - when 'id-ecPublicKey' - m2 = text.match(/Public-Key: \((\d+) bit\)/) - case m2[1] - when '256' - OpenSSL::Digest::SHA256.new - when '384' - OpenSSL::Digest::SHA384.new - when '521' - OpenSSL::Digest::SHA512.new - end - when /sha([235]\d+)/ - OpenSSL::Digest.const_get("SHA#{$1}").new - when /sha1/, 'rsaEncryption' - OpenSSL::Digest::SHA1.new + text = cert.to_text + m = text.match(/Public Key Algorithm: ([a-zA-Z0-9-]+)/) + digest = case m[1] + when 'id-ecPublicKey' + m2 = text.match(/Public-Key: \((\d+) bit\)/) + case m2[1] + when '256' + OpenSSL::Digest::SHA256.new + when '384' + OpenSSL::Digest::SHA384.new + when '521' + OpenSSL::Digest::SHA512.new end - signature = format_signature(cert.public_key, content.to_s) - cert.public_key.verify(digest, signature, signed_octets) - when METHOD_NULL - true - else - raise NotImplementedError, "unsupported auth method #{human_auth_method}" - end + when /sha([235]\d+)/ + OpenSSL::Digest.const_get("SHA#{$1}").new + when /sha1/, 'rsaEncryption' + OpenSSL::Digest::SHA1.new + end + signature = format_signature(cert.public_key, content.to_s) + cert.public_key.verify(digest, signature, signed_octets) + when METHOD_NULL + true + else + raise NotImplementedError, "unsupported auth method #{human_auth_method}" end + end - # Get authentication method name - # @return [String] - def human_auth_method - self[:auth_method].to_human - end + # Get authentication method name + # @return [String] + def human_auth_method + self[:auth_method].to_human + end - private + private - def prf(type, key, msg) - case type - when Transform::PRF_HMAC_MD5, Transform::PRF_HMAC_SHA1, - Transform::PRF_HMAC_SHA2_256, Transform::PRF_HMAC_SHA2_384, - Transform::PRF_HMAC_SHA2_512 - digestname = Transform.constants.grep(/PRF_/) - .detect { |c| Transform.const_get(c) == type } - .to_s.sub(/^PRF_HMAC_/, '').sub(/2_/, '') - digest = OpenSSL::Digest.const_get(digestname).new - else - raise NotImplementedError, 'for now, only HMAC-based PRF are supported' - end - hmac = OpenSSL::HMAC.new(key, digest) - hmac << msg - hmac.digest + def prf(type, key, msg) + case type + when Transform::PRF_HMAC_MD5, Transform::PRF_HMAC_SHA1, + Transform::PRF_HMAC_SHA2_256, Transform::PRF_HMAC_SHA2_384, + Transform::PRF_HMAC_SHA2_512 + digestname = Transform.constants.grep(/PRF_/) + .detect { |c| Transform.const_get(c) == type } + .to_s.sub(/^PRF_HMAC_/, '').sub(/2_/, '') + digest = OpenSSL::Digest.const_get(digestname).new + else + raise NotImplementedError, 'for now, only HMAC-based PRF are supported' end + hmac = OpenSSL::HMAC.new(key, digest) + hmac << msg + hmac.digest + end - def format_signature(pkey, sig) - if pkey.is_a?(OpenSSL::PKey::EC) - # PKey::EC need a signature as a DER string representing a sequence of - # 2 integers: r and s - r = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(sig[0, sig.size / 2], 2).to_i) - s = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(sig[sig.size / 2, - sig.size / 2], 2).to_i) - OpenSSL::ASN1::Sequence.new([r, s]).to_der - else - sig - end + def format_signature(pkey, sig) + if pkey.is_a?(OpenSSL::PKey::EC) + # PKey::EC need a signature as a DER string representing a sequence of + # 2 integers: r and s + r = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(sig[0, sig.size / 2], 2).to_i) + s = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(sig[sig.size / 2, + sig.size / 2], 2).to_i) + OpenSSL::ASN1::Sequence.new([r, s]).to_der + else + sig end end end end end