Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/kerberos/crypto/rc4_hmac.rb
Views: 11766
# -*- coding: binary -*-12require 'rex/text'34module Rex5module Proto6module Kerberos7module Crypto8class Rc4Hmac9include Rex::Proto::Kerberos::Crypto::Utils10include Rex::Proto::Gss::Asn111MAC_SIZE = 1612CONFOUNDER_SIZE = 813PADDING_SIZE = 11415# Derive an encryption key based on a password and salt for the given cipher type16#17# @param password [String] The password to use as the basis for key generation18# @param salt [String] Ignored for this encryption algorithm19# @param params [String] Unused for this encryption type20# @return [String] The derived key21def string_to_key(password, salt=nil, params: nil)22raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Params not supported for RC4_HMAC' unless params == nil2324unicode_password = password.encode('utf-16le')25password_digest = OpenSSL::Digest.digest('MD4', unicode_password)26end2728# Use this class's encryption routines to create a checksum of the data based on the key and message type29#30# @param key [String] the key to use to generate the checksum31# @param msg_type [Integer] type of kerberos message32# @param data [String] the data to checksum33# @return [String] the generated checksum34def checksum(key, msg_type, data)35ksign = OpenSSL::HMAC.digest('MD5', key, "signaturekey\x00")36md5_hash = Rex::Text.md5_raw(usage_str(msg_type) + data)3738ksign = OpenSSL::HMAC.digest('MD5', ksign, md5_hash)39end4041# Decrypts the cipher using RC4-HMAC schema42# https://datatracker.ietf.org/doc/rfc4757/43#44# @param ciphertext [String] the data to decrypt45# @param key [String] the key to decrypt46# @param msg_type [Integer] type of kerberos message47# @return [String] the decrypted cipher48# @raise [Rex::Proto::Kerberos::Model::Error::KerberosError] if decryption doesn't succeed49def decrypt(ciphertext, key, msg_type)50unless ciphertext && ciphertext.length > MAC_SIZE51raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'RC4-HMAC decryption failed'52end5354checksum = ciphertext[0, MAC_SIZE]55data = ciphertext[MAC_SIZE, ciphertext.length - 1]5657k1 = OpenSSL::HMAC.digest('MD5', key, usage_str(msg_type))58k3 = OpenSSL::HMAC.digest('MD5', k1, checksum)5960cipher = OpenSSL::Cipher.new('rc4')61cipher.decrypt62cipher.key = k363decrypted = cipher.update(data) + cipher.final6465if OpenSSL::HMAC.digest('MD5', k1, decrypted) != checksum66raise ::Rex::Proto::Kerberos::Model::Error::KerberosError, 'RC4-HMAC decryption failed, incorrect checksum verification'67end6869# Expect the first CONFOUNDER_SIZE bytes to be the confounder70raise ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError, 'EncryptedData failed to decrypt' if decrypted.length < CONFOUNDER_SIZE7172# Skip the confounder when returning73decrypted[CONFOUNDER_SIZE,decrypted.length]74end7576alias decrypt_asn1 decrypt7778# Encrypts the cipher using RC4-HMAC schema79# https://datatracker.ietf.org/doc/rfc4757/80#81# @param plaintext [String] the data to encrypt82# @param key [String] the key to encrypt83# @param msg_type [Integer] type of kerberos message84# @param confounder [String] Optionally force the confounder to a specific value85# @return [String] the encrypted data86def encrypt(plaintext, key, msg_type, confounder: nil)87k1 = OpenSSL::HMAC.digest('MD5', key, usage_str(msg_type))8889confounder = Random.urandom(CONFOUNDER_SIZE) if confounder == nil90data_encrypt = confounder + plaintext9192checksum = OpenSSL::HMAC.digest('MD5', k1, data_encrypt)9394k3 = OpenSSL::HMAC.digest('MD5', k1, checksum)9596cipher = OpenSSL::Cipher.new('rc4')97cipher.encrypt98cipher.key = k399encrypted = cipher.update(data_encrypt) + cipher.final100101res = checksum + encrypted102res103end104105def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts={})106# Always 32-bit sequence number107expected_sequence_number &= 0xFFFFFFFF unless expected_sequence_number.nil?108109mech_id, ciphertext = unwrap_pseudo_asn1(ciphertext)110111raise Rex::Proto::Kerberos::Model::Error::KerberosError unless ciphertext.length > 0x20112header = ciphertext[0,8]113tok_id, alg, seal_alg, filler = header.unpack('nnnn')114raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid token id: #{tok_id}" unless tok_id == 0x0201115raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid alg: #{alg}" unless alg == 0x1100116raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid seal_alg: #{seal_alg}" unless seal_alg == 0x1000117raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid filler: #{filler}" unless filler == 0xFFFF118119encrypted_sequence_num = ciphertext[8,8]120eight_checksum_bytes = ciphertext[16,8]121encrypted_confounder = ciphertext[24,8]122emessage = ciphertext[32, ciphertext.length - 32]123124kseq = OpenSSL::HMAC.digest('MD5', key.value, [0].pack('V'))125126kseq = OpenSSL::HMAC.digest('MD5', kseq, eight_checksum_bytes)127128cipher_seq = OpenSSL::Cipher.new('rc4')129cipher_seq.decrypt130cipher_seq.key = kseq131132decrypted_sequence_num = cipher_seq.update(encrypted_sequence_num)133decrypted_sequence_num = decrypted_sequence_num.unpack('N')[0]134135#raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid sequence number' unless (decrypted_sequence_num == expected_sequence_number || expected_sequence_number.nil?)136137klocal = xor_strings(key.value, "\xF0"*16)138kcrypt = OpenSSL::HMAC.digest('MD5', klocal, [0].pack('V'))139140# Salt it with the sequence number141kcrypt = OpenSSL::HMAC.digest('MD5', kcrypt, [decrypted_sequence_num].pack('N'))142143cipher = OpenSSL::Cipher.new('rc4')144cipher.encrypt145cipher.key = kcrypt146decrypted_confounder = cipher.update(encrypted_confounder)147148plaintext = cipher.update(emessage)149150chksum_input = usage_str(Rex::Proto::Kerberos::Crypto::KeyUsage::KRB_PRIV_ENCPART) + header + decrypted_confounder151ksign = OpenSSL::HMAC.digest('MD5', key.value, "signaturekey\x00")152sgn_cksum = Rex::Text.md5_raw(chksum_input+plaintext)153sgn_cksum = OpenSSL::HMAC.digest('MD5', ksign, sgn_cksum)154155verification_eight_checksum_bytes = sgn_cksum[0,8]156raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Checksum error' unless verification_eight_checksum_bytes == eight_checksum_bytes157158# Remove padding, if present (seems MS may not send it back?)159pad_char = plaintext[-1].ord160if 1 <= pad_char && pad_char <= 8161plaintext = plaintext[0, plaintext.length-pad_char]162end163164plaintext165end166167# @option options [Boolean] :dce_style Whether the interaction is a 3-leg DCERPC interaction168# @option options [Symbol] :rc4_pad_style How to do padding - either :single_byte or :eight_byte_aligned169def gss_wrap(plaintext, key, sequence_number, is_initiator, opts={})170dce_style = opts.fetch(:dce_style) { false }171pad_style = opts.fetch(:rc4_pad_style) { :single_byte }172# Always 32-bit sequence number173sequence_number &= 0xFFFFFFFF174175# Header176tok_id = 0x0201177alg = 0x1100178seal_alg = 0x1000179filler = 0xFFFF180header = [tok_id, alg, seal_alg, filler].pack('nnnn')181182# Add padding (see RFC1964 section 1.2.2.3)183# Some protocols (LDAP) only support a single byte and seem to fail otherwise184# Others (DRSR) only support 8-byte and seem to fail otherwise185# Some (WinRM) are lenient and are fine with either186#187# It's not entirely clear why188if pad_style == :single_byte189pad_num = 1190elsif pad_style == :eight_byte_aligned191pad_num = (8 - (plaintext.length % 8))192else193raise ArgumentError.new('Unknown pad_style setting')194end195196plaintext += (pad_num.chr * pad_num)197198send_seq = [sequence_number].pack('N')199# See errata on RFC4757200initiator_bytes = "\xFF" * 4201initiator_bytes = "\x00" * 4 if is_initiator202send_seq += initiator_bytes203204confounder = Random.urandom(CONFOUNDER_SIZE)205chksum_input = usage_str(Rex::Proto::Kerberos::Crypto::KeyUsage::KRB_PRIV_ENCPART) + header + confounder206ksign = OpenSSL::HMAC.digest('MD5', key.value, "signaturekey\x00")207sgn_cksum = Rex::Text.md5_raw(chksum_input+plaintext)208209klocal = xor_strings(key.value, "\xF0"*16)210kcrypt = OpenSSL::HMAC.digest('MD5', klocal, [0].pack('V'))211212# Salt it with the sequence number213kcrypt = OpenSSL::HMAC.digest('MD5', kcrypt, [sequence_number].pack('N'))214215cipher = OpenSSL::Cipher.new('rc4')216cipher.encrypt217cipher.key = kcrypt218encrypted_confounder = cipher.update(confounder)219220encrypted = cipher.update(plaintext)221222sgn_cksum = OpenSSL::HMAC.digest('MD5', ksign, sgn_cksum)223eight_checksum_bytes = sgn_cksum[0,8]224225kseq = OpenSSL::HMAC.digest('MD5', key.value, [0].pack('V'))226227kseq = OpenSSL::HMAC.digest('MD5', kseq, eight_checksum_bytes)228229cipher_seq = OpenSSL::Cipher.new('rc4')230cipher_seq.encrypt231cipher_seq.key = kseq232233encrypted_sequence_num = cipher_seq.update(send_seq)234235token = header + encrypted_sequence_num + eight_checksum_bytes + encrypted_confounder236size_prior = (token+encrypted).length237238if dce_style239wrapped_token = wrap_pseudo_asn1(240::Rex::Proto::Gss::OID_KERBEROS_5,241token242) + encrypted243else244wrapped_token = wrap_pseudo_asn1(245::Rex::Proto::Gss::OID_KERBEROS_5,246token + encrypted247)248end249asn1_length = wrapped_token.length - size_prior250token_length = asn1_length + token.length251252[wrapped_token, token_length, pad_num]253end254255#256# The number of bytes in the encrypted plaintext that precede the actual plaintext257#258def header_byte_count259MAC_SIZE + CONFOUNDER_SIZE260end261262#263# The number of bytes in the encrypted plaintext that follow the actual plaintext264#265def trailing_byte_count2660267end268269def calculate_encrypted_length(plaintext_len)270# We add 1-8 bytes of padding, per RFC1964 section 1.2.2.3271plaintext_len + (8 - (plaintext_len % 8))272end273274private275276def usage_str(msg_type)277usage_table = {278Rex::Proto::Kerberos::Crypto::KeyUsage::AS_REP_ENCPART => Rex::Proto::Kerberos::Crypto::KeyUsage::TGS_REP_ENCPART_SESSION_KEY,279Rex::Proto::Kerberos::Crypto::KeyUsage::GSS_ACCEPTOR_SIGN => Rex::Proto::Kerberos::Crypto::KeyUsage::KRB_PRIV_ENCPART280}281usage_mapped = usage_table.fetch(msg_type) { msg_type }282[usage_mapped].pack('V')283end284end285end286end287end288end289290291