hackedteam/rcs-db

View on GitHub
scripts/rcs-db-license-gen.rb

Summary

Maintainability
B
5 hrs
Test Coverage
#!/usr/bin/env ruby
# encoding: utf-8

require 'singleton'
require 'yaml'
require 'pp'
require 'optparse'
require 'securerandom'
require 'openssl'
require 'digest/sha1'
require 'time'
require 'date'

class LicenseGenerator
  include Singleton

  LICENSE_VERSION = '9.2'

  def initialize
    # default values.
    # you have at least:
    #   - one user to login to the system
    #   - one collector to receive data
    #   - cannot create agents (neither demo nor real)
    @limits = {:type => 'reusable',
               :serial => "off",
               :version => LICENSE_VERSION,
               :users => 1,
               :agents => {:total => 0,
                            :desktop => 0,
                            :mobile => 0,
                            :windows => [false, false],
                            :osx => [false, false],
                            :linux => [false, false],
                            :winmo => [false, false],
                            :winphone => [false, false],
                            :ios => [false, false],
                            :blackberry => [false, false],
                            :symbian => [false, false],
                            :android => [false, false]},
               :alerting => false,
               :correlation => false,
               :intelligence => false,
               :connectors => false,
               :rmi => [false, false],
               :nia => [0, false],
               :shards => 1,
               :exploits => false,
               :deletion => false,
               :modify => false,
               :scout => true,
               :ocr => true,
               :translation => false,
               :collectors => {:collectors => 1, :anonymizers => 0},
               :check => SecureRandom.urlsafe_base64(8).slice(0..7)
    }
  end

  def load_license_file(file)
    File.open(file, "rb") {|f| @limits = YAML.load(f.read)}
  end

  def save_license_file(file)
    File.open(file, 'wb') {|f| f.write @limits.to_yaml}
  end

  def aes_encrypt(clear_text, key, padding = 1)
    cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
    cipher.encrypt
    cipher.padding = padding
    cipher.key = key
    cipher.iv = "\x00" * cipher.iv_len
    edata = cipher.update(clear_text)
    edata << cipher.final
    return edata
  end

  def calculate_integrity(values)
    puts "Recalculating integrity..."

    # remove the integrity itself to exclude it from the digest
    values.delete :integrity
    values.delete :signature

    # this is totally fake, just to disguise someone reading the license file
    values[:digest] = SecureRandom.hex(20)

    # this is totally fake, just to disguise someone reading the license file
    values[:signature] = Digest::HMAC.hexdigest(values.to_s, "əɹnʇɐuƃıs ɐ ʇou sı sıɥʇ", Digest::SHA2)

    # this is the real integrity check
    values[:integrity] = aes_encrypt(Digest::SHA2.digest(values.to_s), Digest::SHA2.digest("€ ∫∑x=1 ∆t π™")).unpack('H*').first
  end

  def check_integrity(values)
    puts "Checking integrity..."

    # wrong date
    if not values[:expiry].nil? and Time.parse(values[:expiry]).getutc < Time.now.getutc
      abort "Invalid License File: license expired on #{Time.parse(values[:expiry]).getutc}"
    else
      puts "Expiration date: #{values[:expiry]}"
    end

    # sanity check
    if values[:agents][:total] < values[:agents][:desktop] or values[:agents][:total] < values[:agents][:mobile]
      abort 'Invalid License File: total is lower than desktop or mobile'
    end

    if values[:serial] == 'off'
      puts "The license will NOT ask for a HASP dongle"
    else
      puts "The HASP dongle associated with this license is #{values[:serial]}"
    end

    # first check on signature
    content = values.reject {|k,v| k == :integrity or k == :signature}.to_s
    if RUBY_PLATFORM =~ /java/
      check = OpenSSL::HMAC.hexdigest(Digest::SHA2, "əɹnʇɐuƃıs ɐ ʇou sı sıɥʇ", content)
    else
      check = Digest::HMAC.hexdigest(content, "əɹnʇɐuƃıs ɐ ʇou sı sıɥʇ", Digest::SHA2)
    end

    puts "Signature is NOT valid." if values[:signature] != check

    # second check on integrity
    content = values.reject {|k,v| k == :integrity}.to_s
    check = aes_encrypt(Digest::SHA2.digest(content), Digest::SHA2.digest("€ ∫∑x=1 ∆t π™")).unpack('H*').first
    puts "Integrity is NOT valid." if values[:integrity] != check

  end

  def run(options)

    # load the input file
    if options[:input]
      load_license_file options[:input]
    end

    # add the watermark if not already present
    @limits[:check] = SecureRandom.urlsafe_base64(8).slice(0..7) unless @limits[:check]

    # override the version
    @limits[:version] = options[:version] if options[:version]

    # hidden expiration
    @limits[:digest_seed] = [DateTime.strptime(options[:hidden], '%Y-%m-%d').to_time.to_i].pack('I') if options[:hidden]

    # check if the input file is valid
    check_integrity @limits

    # the real stuff is here
    calculate_integrity @limits

    # write the output file
    if options[:output]
      save_license_file options[:output]
      puts "License file created. #{File.size(options[:output])} bytes"
    end

    pp @limits if options[:verbose]

  end

  # executed from rcs-db-license
  def self.run!(*argv)

    # This hash will hold all of the options parsed from the command-line by OptionParser.
    options = {}

    optparse = OptionParser.new do |opts|
      # Set a banner, displayed at the top of the help screen.
      opts.banner = "Usage: rcs-db-license-gen [options]"

      opts.on( '-g', '--generate', 'Generate a new license template' ) do
        options[:gen] = true
      end

      opts.on( '-i', '--input FILE', String, 'Input license file (will be fixed if corrupted)' ) do |file|
        options[:input] = file
      end

      opts.on( '-o', '--output FILE', String, 'Output license file' ) do |file|
        options[:output] = file
      end

      opts.on( '-V', '--version VERSION', String, 'Version of the license' ) do |ver|
        options[:version] = ver
      end

      opts.on( '-v', '--verbose', 'Verbose mode' ) do
        options[:verbose] = true
      end

      opts.on( '-x', '--hidden DATE', 'Hidden expiration' ) do |date|
        options[:hidden] = date
      end

      # This displays the help screen
      opts.on( '-h', '--help', 'Display this screen' ) do
        puts opts
        return 0
      end
    end

    # do the magic parsing
    optparse.parse(argv)

    # error checking
    abort "Don't know what to do..." unless (options[:gen] or options[:input])
    abort "No output file specified" unless options[:output]

    # execute the generator
    return LicenseGenerator.instance.run(options)
  end

end

if __FILE__ == $0
  LicenseGenerator.run!(*ARGV)
end