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/block_cipher_base.rb
Views: 11766
# -*- coding: binary -*-12module Rex3module Proto4module Kerberos5module Crypto6class BlockCipherBase78# Subclasses must define:9# BLOCK_SIZE10# SEED_SIZE11# PADDING_SIZE12# HASH_FUNCTION13# MAC_SIZE14# decrypt_basic15# encrypt_basic16# string_to_key1718# Derive an encryption key based on a password and salt for the given cipher type19#20# @param password [String] The password to use as the basis for key generation21# @param salt [String] A salt (usually based on domain and username)22# @param params [String] A cipher-specific parameter (currently only used in AES, and even then not usually provided)23# @return [String] The derived key24def string_to_key(password, salt, params=nil)25raise NotImplementedError26end2728# 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 method in use32# @param data [String] the data to checksum33# @return [String] the generated checksum34def checksum(key, msg_type, data)35kc = derive(key, [msg_type, 0x99].pack('NC'))36hmac = OpenSSL::HMAC.digest(self.class::HASH_FUNCTION, kc, data)3738hmac[0, self.class::MAC_SIZE]39end4041# Decrypts the cipher42#43# @param ciphertext_and_mac [String] the data to decrypt44# @param key [String] the key to decrypt45# @param msg_type [Integer] type of kerberos message46# @return [String] the decrypted cipher47# @raise [Rex::Proto::Kerberos::Model::Error::KerberosError] if decryption doesn't succeed48def decrypt(ciphertext_and_mac, key, msg_type)49ki = derive(key, [msg_type, 0x55].pack('NC'))50ke = derive(key, [msg_type, 0xAA].pack('NC'))5152raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Ciphertext too short' if ciphertext_and_mac.length < (self.class::BLOCK_SIZE + self.class::MAC_SIZE)5354ciphertext = ciphertext_and_mac.slice(0..-(self.class::MAC_SIZE+1))55mac = ciphertext_and_mac[-self.class::MAC_SIZE, self.class::MAC_SIZE]565758plaintext = decrypt_basic(ciphertext, ke)59hmac = OpenSSL::HMAC.digest(self.class::HASH_FUNCTION, ki, plaintext)60hmac_subset = hmac[0, self.class::MAC_SIZE]61if mac != hmac_subset62raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'HMAC integrity error'63end6465# Discard the confounder.66plaintext[self.class::BLOCK_SIZE, plaintext.length]67end6869# Some ciphers add padding and leave it up to the application layer to remove70# them. In kerberos, this seems to be reliably done by respecting the expected71# length of the ASN1 structure.72# For ciphers that remove padding themselves, this just does basic decryption73alias decrypt_asn1 decrypt7475# Encrypts the cipher76#77# @param plaintext [String] the data to encrypt78# @param key [String] the key to encrypt79# @param msg_type [Integer] type of kerberos message80# @param confounder [String] Optionally force the confounder to a specific value81# @return [String] the encrypted data82def encrypt(plaintext, key, msg_type, confounder: nil)83ki = derive(key, [msg_type, 0x55].pack('NC'))84ke = derive(key, [msg_type, 0xAA].pack('NC'))85confounder = Rex::Text::rand_text(self.class::BLOCK_SIZE) if confounder == nil86plaintext = confounder + pad_with_zeroes(plaintext, self.class::PADDING_SIZE)87hmac = OpenSSL::HMAC.digest(self.class::HASH_FUNCTION, ki, plaintext)8889encrypt_basic(plaintext, ke) + hmac[0,self.class::MAC_SIZE]90end9192def gss_wrap(plaintext, key, sequence_number, is_initiator, opts={})93raise NotImplementedError94end9596def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts={})97raise NotImplementedError98end99100def calculate_encrypted_length(plaintext_len)101raise NotImplementedError102end103104private105106# Functions must be overridden by subclasses:107108def decrypt_basic(cipher, key)109raise NotImplementedError110end111112def encrypt_basic(data, key)113raise NotImplementedError114end115116# Functions may be overridden by subclasses:117def random_to_key(seed)118if seed.length != self.class::SEED_SIZE119raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid seed size'120end121122seed123end124125126# Functions used by subclasses127128# Pads the provided data to a multiple of block_length129# Zeroes are added at the end130def pad_with_zeroes(data, padding_size)131pad_length = padding_size - (data.length % padding_size)132pad_length %= padding_size # In case it's a perfect multiple, do no padding133return data + "\x00" * pad_length134end135136def nfold(ba, nbytes)137# Convert bytearray to a string of length nbytes using the RFC 3961 nfold138# operation.139140# Rotate the bytes in ba to the right by nbits bits.141def rotate_right(ba, nbits)142nbytes, remain = (nbits / 8) % ba.length, nbits % 8143ba.length.times.map do |i|144(ba[i-nbytes] >> remain) | ((ba[i-nbytes-1] << (8-remain)) & 0xff)145end146end147148# Add equal-length strings together with end-around carry.149def add_ones_complement(arr1, arr2)150n = arr1.length151# Add all pairs of numbers152v = arr1.zip(arr2).map { |a,b| a+b}153154while v.any? { |x| x > 0xff }155v = v.length.times.map {|i| (v[i-n+1]>>8) + (v[i]&0xff)}156end157158v159end160161# Let's work in terms of numbers rather than strings162slen = ba.length163lcm = nbytes * slen / nbytes.gcd(slen)164bigstr = []165num_copies = lcm / slen166num_copies.times do |i|167bigstr += rotate_right(ba, 13 * i)168end169170result = bigstr.each_slice(nbytes).reduce {|l1,l2| add_ones_complement(l1,l2) }171172result173end174175def derive(key_str, constant_str)176# RFC 3961 only says to n-fold the constant only if it is177# shorter than the cipher block size. But all Unix178# implementations n-fold constants if their length is larger179# than the block size as well, and n-folding when the length180# is equal to the block size is a no-op.181constant_arr = constant_str.bytes182plaintext_arr = nfold(constant_arr, self.class::BLOCK_SIZE)183rndseed = []184while rndseed.length < self.class::SEED_SIZE do185plaintext_str = plaintext_arr.pack('C*')186ciphertext_str = encrypt_basic(plaintext_str, key_str)187ciphertext_arr = ciphertext_str.unpack('C*')188rndseed += ciphertext_arr189plaintext_arr = ciphertext_arr190end191192result = random_to_key(rndseed[0,self.class::SEED_SIZE])193194result.pack('C*')195end196197def _is_weak_des_key(keybytes)198["\x01\x01\x01\x01\x01\x01\x01\x01",199"\xFE\xFE\xFE\xFE\xFE\xFE\xFE\xFE",200"\x1F\x1F\x1F\x1F\x0E\x0E\x0E\x0E",201"\xE0\xE0\xE0\xE0\xF1\xF1\xF1\xF1",202"\x01\xFE\x01\xFE\x01\xFE\x01\xFE",203"\xFE\x01\xFE\x01\xFE\x01\xFE\x01",204"\x1F\xE0\x1F\xE0\x0E\xF1\x0E\xF1",205"\xE0\x1F\xE0\x1F\xF1\x0E\xF1\x0E",206"\x01\xE0\x01\xE0\x01\xF1\x01\xF1",207"\xE0\x01\xE0\x01\xF1\x01\xF1\x01",208"\x1F\xFE\x1F\xFE\x0E\xFE\x0E\xFE",209"\xFE\x1F\xFE\x1F\xFE\x0E\xFE\x0E",210"\x01\x1F\x01\x1F\x01\x0E\x01\x0E",211"\x1F\x01\x1F\x01\x0E\x01\x0E\x01",212"\xE0\xFE\xE0\xFE\xF1\xFE\xF1\xFE",213"\xFE\xE0\xFE\xE0\xFE\xF1\xFE\xF1"].include?(keybytes)214end215end216end217end218end219end220221222