##
# encryptor.rb
# Created October, 2015
# By Ron Bowes
#
# See: LICENSE.md
#
##
require 'ecdsa'
require 'salsa20'
require 'securerandom'
require 'sha3'
require 'controller/crypto_helper'
require 'controller/encryptor_sas'
require 'libs/dnscat_exception'
require 'libs/swindow'
class Encryptor
include EncryptorSAS
ECDH_GROUP = ECDSA::Group::Nistp256
@@window = SWindow.new(WINDOW, false, { :noinput => true, :id => "crypto-debug", :name => "Debug window for crypto stuff"})
@@window.puts("This window is for debugging encryption problems!")
@@window.puts("In general, you can ignore it. :)")
@@window.puts()
@@window.puts("One thing to note: you'll see a lot of meaningless errors here,")
@@window.puts("because of retransmissions and such. They don't necessarily mean")
@@window.puts("anything!")
@@window.puts()
@@window.puts("But if you ARE having crypto problems, please send me these")
@@window.puts("logs! Don't worry too much about the private keys; they're")
@@window.puts("session-specific and won't harm anything in the future")
@@window.puts()
class Error < StandardError
end
def _create_key(key_name)
return SHA3::Digest::SHA256.digest(CryptoHelper.bignum_to_binary(@keys[:shared_secret]) + key_name)
end
def _create_authenticator(name, preshared_secret)
return SHA3::Digest::SHA256.digest(name +
CryptoHelper.bignum_to_binary(@keys[:shared_secret]) +
CryptoHelper.bignum_to_binary(@keys[:their_public_key].x) +
CryptoHelper.bignum_to_binary(@keys[:their_public_key].y) +
CryptoHelper.bignum_to_binary(@keys[:my_public_key].x) +
CryptoHelper.bignum_to_binary(@keys[:my_public_key].y) +
preshared_secret
)
end
def initialize(preshared_secret)
@@window.puts("Creating Encryptor with secret: #{preshared_secret}")
@preshared_secret = preshared_secret
@authenticated = false
# Start with encryption turned off
@keys = {
:my_nonce => -1,
:their_nonce => -1,
:my_private_key => nil,
:my_public_key => nil,
:their_public_key => nil,
:shared_secret => nil,
:their_authenticator => nil,
:my_authenticator => nil,
:their_write_key => nil,
:their_mac_key => nil,
:my_write_key => nil,
:my_mac_key => nil,
}
@old_keys = nil
end
# Returns true if something was changed
def set_their_public_key(their_public_key_x, their_public_key_y)
# Check if we're actually changing anything
if(@keys[:their_public_key_x] == their_public_key_x && @keys[:their_public_key_y] == their_public_key_y)
@@window.puts("Attempted to set the same public key!")
return false
end
@old_keys = @keys
@keys = {
:my_nonce => -1,
:their_nonce => -1,
}
if(ready?())
@@window.puts("Wow, this session is old (or the client is needy)! Key re-negotiation requested!")
end
@keys[:my_private_key] = 1 + SecureRandom.random_number(ECDH_GROUP.order - 1)
@keys[:my_public_key] = ECDH_GROUP.generator.multiply_by_scalar(@keys[:my_private_key])
@keys[:their_public_key_x] = their_public_key_x
@keys[:their_public_key_y] = their_public_key_y
@keys[:their_public_key] = ECDSA::Point.new(ECDH_GROUP, their_public_key_x, their_public_key_y)
@keys[:shared_secret] = @keys[:their_public_key].multiply_by_scalar(@keys[:my_private_key]).x
@keys[:their_authenticator] = _create_authenticator("client", @preshared_secret)
@keys[:my_authenticator] = _create_authenticator("server", @preshared_secret)
@keys[:their_write_key] = _create_key("client_write_key")
@keys[:their_mac_key] = _create_key("client_mac_key")
@keys[:my_write_key] = _create_key("server_write_key")
@keys[:my_mac_key] = _create_key("server_mac_key")
@@window.puts("Setting their public key: #{CryptoHelper.bignum_to_text(@keys[:their_public_key_x])} #{CryptoHelper.bignum_to_text(@keys[:their_public_key_y])}")
@@window.puts("Setting my public key: #{CryptoHelper.bignum_to_text(@keys[:my_public_key].x)} #{CryptoHelper.bignum_to_text(@keys[:my_public_key].y)}")
return true
end
def set_their_authenticator(their_authenticator)
if(!@keys[:their_authenticator])
@@window.puts("Tried to set an authenticator too early")
raise(DnscatException, "We weren't ready to set an authenticator!")
end
if(@keys[:their_authenticator] != their_authenticator)
@@window.puts("Tried to set a bad authenticator")
@@window.puts("Expected: #{@keys[:their_authenticator].unpack("H*")}")
@@window.puts("Received: #{their_authenticator.unpack("H*")}")
raise(Encryptor::Error, "Authenticator (pre-shared secret) doesn't match!")
end
@@window.puts("Successfully authenticated the session")
@authenticated = true
end
def to_s(keys = nil)
keys = keys || @keys
out = []
out << "My private key: #{CryptoHelper.bignum_to_text(@keys[:my_private_key])}"
out << "My public key [x]: #{CryptoHelper.bignum_to_text(@keys[:my_public_key].x)}"
out << "My public key [y]: #{CryptoHelper.bignum_to_text(@keys[:my_public_key].y)}"
out << "Their public key [x]: #{CryptoHelper.bignum_to_text(@keys[:their_public_key].x)}"
out << "Their public key [y]: #{CryptoHelper.bignum_to_text(@keys[:their_public_key].y)}"
out << "Shared secret: #{CryptoHelper.bignum_to_text(@keys[:shared_secret])}"
out << ""
out << "Their authenticator: #{@keys[:their_authenticator].unpack("H*")}"
out << "My authenticator: #{@keys[:my_authenticator].unpack("H*")}"
out << ""
out << "Their write key: #{@keys[:their_write_key].unpack("H*")}"
out << "Their mac key: #{@keys[:their_mac_key].unpack("H*")}"
out << "My write key: #{@keys[:my_write_key].unpack("H*")}"
out << "My mac key: #{@keys[:my_mac_key].unpack("H*")}"
out << ""
out << "SAS: #{get_sas()}"
return out.join("\n")
end
def my_public_key_x()
return @keys[:my_public_key].x
end
def my_public_key_x_s()
return CryptoHelper.bignum_to_binary(@keys[:my_public_key].x)
end
def my_public_key_y()
return @keys[:my_public_key].y
end
def my_public_key_y_s()
return CryptoHelper.bignum_to_binary(@keys[:my_public_key].y)
end
def my_nonce()
return @keys[:my_nonce] += 1
end
# We use this special internal function so we can try decrypting with different keys
def _decrypt_packet_internal(keys, data)
# Don't decrypt if we don't have a key set
if(!ready?(keys))
@@window.puts("Not decrypting data (incoming data seemed to be cleartext): #{data.unpack("H*")}")
return data
end
# Parse out the important fields
header, signature, nonce, encrypted_body = data.unpack("a5a6a2a*")
# Put together the data to sign
signed_data = header + nonce + encrypted_body
# Check the signature
correct_signature = SHA3::Digest::SHA256.digest(keys[:their_mac_key] + signed_data)
if(correct_signature[0,6] != signature)
@@window.puts("Couldn't verify packet signature!")
raise(Encryptor::Error, "Invalid signature!")
end
# Check the nonce *after* checking the signature (otherwise, we might update the nonce to a bad value and Bad Stuff happens)
nonce_int = nonce.unpack("n").pop()
if(nonce_int < keys[:their_nonce])
@@window.puts("Client tried to use an invalid nonce: #{nonce_int} < #{keys[:their_nonce]}")
raise(Encryptor::Error, "Invalid nonce!")
end
keys[:their_nonce] = nonce_int
# Decrypt the body
body = Salsa20.new(keys[:their_write_key], nonce.rjust(8, "\0")).decrypt(encrypted_body)
#@@window.puts("Decryption successful")
return header+body
end
# By doing this as a single operation, we can always be sure that we're encrypting data
# with the same key the client use to encrypt data
def decrypt_and_encrypt(data)
## ** Decrypt
keys = @keys
begin
#@@window.puts("Attempting to decrypt with primary key")
data = _decrypt_packet_internal(keys, data)
#@@window.puts("Successfully decrypted with primary key")
# If it was successfully decrypted, make sure the @old_keys will no longer work
@old_keys = nil
rescue Encryptor::Error => e
# Attempt to fall back to old keys
if(@old_keys.nil?)
@@window.puts("No secondary key to fallback to")
raise(e)
end
@@window.puts("Attempting to decrypt with secondary key")
keys = @old_keys
data = _decrypt_packet_internal(@old_keys, data)
@@window.puts("Successfully decrypted with secondary key")
end
# Send the decrypted data up and get the encrypted data back
data = yield(data, ready?(keys))
# If there was an error of some sort, return nothing
if(data.nil? || data == '')
return ''
end
# If encryption is turned off, return unencrypted data
if(!ready?(keys))
@@window.puts("Returning an unencrypted response")
return data
end
## ** Encrypt
#@@window.puts("Encrypting the response")
# Split the packet into a header and a body
header, body = data.unpack("a5a*")
# Encode the nonce properly
nonce = [keys[:my_nonce]].pack("n")
# Encrypt the body
encrypted_body = Salsa20.new(keys[:my_write_key], nonce.rjust(8, "\0")).encrypt(body)
# Sign it
signature = SHA3::Digest::SHA256.digest(keys[:my_mac_key] + header + nonce + encrypted_body)
# Arrange things appropriately
return [header, signature[0,6], nonce, encrypted_body].pack("a5a6a2a*")
end
def my_authenticator()
return @keys[:my_authenticator]
end
def ready?(keys = nil)
return !(keys || @keys)[:shared_secret].nil?
end
def authenticated?()
return @authenticated
end
end