CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/kerberos/crypto/block_cipher_base.rb
Views: 11766
1
# -*- coding: binary -*-
2
3
module Rex
4
module Proto
5
module Kerberos
6
module Crypto
7
class BlockCipherBase
8
9
# Subclasses must define:
10
# BLOCK_SIZE
11
# SEED_SIZE
12
# PADDING_SIZE
13
# HASH_FUNCTION
14
# MAC_SIZE
15
# decrypt_basic
16
# encrypt_basic
17
# string_to_key
18
19
# Derive an encryption key based on a password and salt for the given cipher type
20
#
21
# @param password [String] The password to use as the basis for key generation
22
# @param salt [String] A salt (usually based on domain and username)
23
# @param params [String] A cipher-specific parameter (currently only used in AES, and even then not usually provided)
24
# @return [String] The derived key
25
def string_to_key(password, salt, params=nil)
26
raise NotImplementedError
27
end
28
29
# Use this class's encryption routines to create a checksum of the data based on the key and message type
30
#
31
# @param key [String] the key to use to generate the checksum
32
# @param msg_type [Integer] type of kerberos method in use
33
# @param data [String] the data to checksum
34
# @return [String] the generated checksum
35
def checksum(key, msg_type, data)
36
kc = derive(key, [msg_type, 0x99].pack('NC'))
37
hmac = OpenSSL::HMAC.digest(self.class::HASH_FUNCTION, kc, data)
38
39
hmac[0, self.class::MAC_SIZE]
40
end
41
42
# Decrypts the cipher
43
#
44
# @param ciphertext_and_mac [String] the data to decrypt
45
# @param key [String] the key to decrypt
46
# @param msg_type [Integer] type of kerberos message
47
# @return [String] the decrypted cipher
48
# @raise [Rex::Proto::Kerberos::Model::Error::KerberosError] if decryption doesn't succeed
49
def decrypt(ciphertext_and_mac, key, msg_type)
50
ki = derive(key, [msg_type, 0x55].pack('NC'))
51
ke = derive(key, [msg_type, 0xAA].pack('NC'))
52
53
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Ciphertext too short' if ciphertext_and_mac.length < (self.class::BLOCK_SIZE + self.class::MAC_SIZE)
54
55
ciphertext = ciphertext_and_mac.slice(0..-(self.class::MAC_SIZE+1))
56
mac = ciphertext_and_mac[-self.class::MAC_SIZE, self.class::MAC_SIZE]
57
58
59
plaintext = decrypt_basic(ciphertext, ke)
60
hmac = OpenSSL::HMAC.digest(self.class::HASH_FUNCTION, ki, plaintext)
61
hmac_subset = hmac[0, self.class::MAC_SIZE]
62
if mac != hmac_subset
63
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'HMAC integrity error'
64
end
65
66
# Discard the confounder.
67
plaintext[self.class::BLOCK_SIZE, plaintext.length]
68
end
69
70
# Some ciphers add padding and leave it up to the application layer to remove
71
# them. In kerberos, this seems to be reliably done by respecting the expected
72
# length of the ASN1 structure.
73
# For ciphers that remove padding themselves, this just does basic decryption
74
alias decrypt_asn1 decrypt
75
76
# Encrypts the cipher
77
#
78
# @param plaintext [String] the data to encrypt
79
# @param key [String] the key to encrypt
80
# @param msg_type [Integer] type of kerberos message
81
# @param confounder [String] Optionally force the confounder to a specific value
82
# @return [String] the encrypted data
83
def encrypt(plaintext, key, msg_type, confounder: nil)
84
ki = derive(key, [msg_type, 0x55].pack('NC'))
85
ke = derive(key, [msg_type, 0xAA].pack('NC'))
86
confounder = Rex::Text::rand_text(self.class::BLOCK_SIZE) if confounder == nil
87
plaintext = confounder + pad_with_zeroes(plaintext, self.class::PADDING_SIZE)
88
hmac = OpenSSL::HMAC.digest(self.class::HASH_FUNCTION, ki, plaintext)
89
90
encrypt_basic(plaintext, ke) + hmac[0,self.class::MAC_SIZE]
91
end
92
93
def gss_wrap(plaintext, key, sequence_number, is_initiator, opts={})
94
raise NotImplementedError
95
end
96
97
def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts={})
98
raise NotImplementedError
99
end
100
101
def calculate_encrypted_length(plaintext_len)
102
raise NotImplementedError
103
end
104
105
private
106
107
# Functions must be overridden by subclasses:
108
109
def decrypt_basic(cipher, key)
110
raise NotImplementedError
111
end
112
113
def encrypt_basic(data, key)
114
raise NotImplementedError
115
end
116
117
# Functions may be overridden by subclasses:
118
def random_to_key(seed)
119
if seed.length != self.class::SEED_SIZE
120
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid seed size'
121
end
122
123
seed
124
end
125
126
127
# Functions used by subclasses
128
129
# Pads the provided data to a multiple of block_length
130
# Zeroes are added at the end
131
def pad_with_zeroes(data, padding_size)
132
pad_length = padding_size - (data.length % padding_size)
133
pad_length %= padding_size # In case it's a perfect multiple, do no padding
134
return data + "\x00" * pad_length
135
end
136
137
def nfold(ba, nbytes)
138
# Convert bytearray to a string of length nbytes using the RFC 3961 nfold
139
# operation.
140
141
# Rotate the bytes in ba to the right by nbits bits.
142
def rotate_right(ba, nbits)
143
nbytes, remain = (nbits / 8) % ba.length, nbits % 8
144
ba.length.times.map do |i|
145
(ba[i-nbytes] >> remain) | ((ba[i-nbytes-1] << (8-remain)) & 0xff)
146
end
147
end
148
149
# Add equal-length strings together with end-around carry.
150
def add_ones_complement(arr1, arr2)
151
n = arr1.length
152
# Add all pairs of numbers
153
v = arr1.zip(arr2).map { |a,b| a+b}
154
155
while v.any? { |x| x > 0xff }
156
v = v.length.times.map {|i| (v[i-n+1]>>8) + (v[i]&0xff)}
157
end
158
159
v
160
end
161
162
# Let's work in terms of numbers rather than strings
163
slen = ba.length
164
lcm = nbytes * slen / nbytes.gcd(slen)
165
bigstr = []
166
num_copies = lcm / slen
167
num_copies.times do |i|
168
bigstr += rotate_right(ba, 13 * i)
169
end
170
171
result = bigstr.each_slice(nbytes).reduce {|l1,l2| add_ones_complement(l1,l2) }
172
173
result
174
end
175
176
def derive(key_str, constant_str)
177
# RFC 3961 only says to n-fold the constant only if it is
178
# shorter than the cipher block size. But all Unix
179
# implementations n-fold constants if their length is larger
180
# than the block size as well, and n-folding when the length
181
# is equal to the block size is a no-op.
182
constant_arr = constant_str.bytes
183
plaintext_arr = nfold(constant_arr, self.class::BLOCK_SIZE)
184
rndseed = []
185
while rndseed.length < self.class::SEED_SIZE do
186
plaintext_str = plaintext_arr.pack('C*')
187
ciphertext_str = encrypt_basic(plaintext_str, key_str)
188
ciphertext_arr = ciphertext_str.unpack('C*')
189
rndseed += ciphertext_arr
190
plaintext_arr = ciphertext_arr
191
end
192
193
result = random_to_key(rndseed[0,self.class::SEED_SIZE])
194
195
result.pack('C*')
196
end
197
198
def _is_weak_des_key(keybytes)
199
["\x01\x01\x01\x01\x01\x01\x01\x01",
200
"\xFE\xFE\xFE\xFE\xFE\xFE\xFE\xFE",
201
"\x1F\x1F\x1F\x1F\x0E\x0E\x0E\x0E",
202
"\xE0\xE0\xE0\xE0\xF1\xF1\xF1\xF1",
203
"\x01\xFE\x01\xFE\x01\xFE\x01\xFE",
204
"\xFE\x01\xFE\x01\xFE\x01\xFE\x01",
205
"\x1F\xE0\x1F\xE0\x0E\xF1\x0E\xF1",
206
"\xE0\x1F\xE0\x1F\xF1\x0E\xF1\x0E",
207
"\x01\xE0\x01\xE0\x01\xF1\x01\xF1",
208
"\xE0\x01\xE0\x01\xF1\x01\xF1\x01",
209
"\x1F\xFE\x1F\xFE\x0E\xFE\x0E\xFE",
210
"\xFE\x1F\xFE\x1F\xFE\x0E\xFE\x0E",
211
"\x01\x1F\x01\x1F\x01\x0E\x01\x0E",
212
"\x1F\x01\x1F\x01\x0E\x01\x0E\x01",
213
"\xE0\xFE\xE0\xFE\xF1\xFE\xF1\xFE",
214
"\xFE\xE0\xFE\xE0\xFE\xF1\xFE\xF1"].include?(keybytes)
215
end
216
end
217
end
218
end
219
end
220
end
221
222