For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

SSLKeyAndCSRCreator

Problem this snippet solves:

This is a script will generate a private key and output a certificate signing request. This is all done from a workstation command line and communicated to the BIG-IP via iControl.

How to use this snippet:

Requirements

Installation Steps

  1. Install Ruby, Ruby's ""OpenSSL"" library, Ruby Gems, and the Ruby iControl libraries
  2. Copy this code to /usr/local/bin and chmod +x to make the script executable
  3. Run it!
    • minimal options: ssl-key-and-csr-creator.rb -b 192.168.1.245 -u admin -i test-key-001
    • full options: ssl-key-and-csr-creator.rb -b 192.168.1.245 -u admin -p admin -i test-key-001 -t RSA -l 4096 -s fips --common-name=www.example.com --country=US --state=Washington --locality=Seattle --organization="Example Company, Inc." --division="Information Technology" -o /my-ssl-directory/ -k -c

Code :

#!/usr/bin/ruby

require "rubygems"
require "f5-icontrol"
require "getoptlong"

options = GetoptLong.new(
  [ "--bigip-address",    "-b",   GetoptLong::REQUIRED_ARGUMENT ],
  [ "--bigip-user",       "-u",   GetoptLong::REQUIRED_ARGUMENT ],
  [ "--bigip-pass",       "-p",   GetoptLong::REQUIRED_ARGUMENT ],
  [ "--key-id",           "-i",   GetoptLong::REQUIRED_ARGUMENT ],
  [ "--key-type",         "-t",   GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--key-bit-length",   "-l",   GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--key-security",     "-s",   GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--common-name",              GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--country",                  GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--state",                    GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--locality",                 GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--organization",             GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--division",                 GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--output-dir",       "-o",   GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--key-output",       "-k",   GetoptLong::NO_ARGUMENT ],
  [ "--csr-output",       "-c",   GetoptLong::NO_ARGUMENT ],
  [ "--help",             "-h",   GetoptLong::NO_ARGUMENT ]
)

def usage 
  puts $0 + " -b  -u  -i "
  puts
  puts "BIG-IP connection parameters"
  puts "-" * 20
  puts "  -b  (--bigip-address)   BIG-IP management-accessible address"
  puts "  -u  (--bigip-user)      BIG-IP username"
  puts "  -p  (--bigip-pass)      BIG-IP password (will prompt if left blank"
  puts
  puts "Private key parameters"
  puts "-" * 20
  puts "  -i  (--key-id)          key ID: must be unique and should be indicative of the purpose (required)"
  puts "  -t  (--key-type)        key type: [RSA|DSA] (default is 'RSA')"
  puts "  -l  (--key-bit-length)  key bit length: should be a minimum of 1024-bit (default is 2048; most CAs won't sign weaker keys)"
  puts "  -s  (--key-security)    key security: [normal|fips|password] (default is 'normal' with no passphrase)"
  puts
  puts "X.509 data parameters (if blank, you'll be prompted for the answers)"
  puts "-" * 20
  puts "      (--common-name)     common name: FQDN for virtual server (www.example.com)"
  puts "      (--country)         country: two letter country abbreviation (US, CN, etc.)"
  puts "      (--state)           state: two letter state abbreviation (WA, OR, CA, etc.)"
  puts "      (--locality)        locality: locality or city name (Seattle, Portland, etc.)"
  puts "      (--organization)    organization: organization or company name (F5 Networks, Company XYZ, etc.)"
  puts "      (--division)        division: department or division name (IT, HR, Finance, etc.)"
  puts
  puts "Output options"
  puts "-" * 20
  puts "  -o  (--output-dir)      CSR/key output directory: location to output private key and CSR files (defaults to current working directory)"
  puts "  -k  (--key-output)      key output: save private key to a local file (saved as key_id.key)"
  puts "  -c  (--csr-output)      CSR output: save certificate signing request to a local file (saved as key_id.csr)"
  puts
  puts "Help and usage"
  puts "-" * 20
  puts "  -h  (--help)            shows this help/usage dialog"
  puts

  exit
end

# set STDOUT buffer to synchronous
STDOUT.sync = true

# global variables
KEY_TYPES = { "RSA" => "KTYPE_RSA_PRIVATE", "DSA" => "KTYPE_DSA_PRIVATE" }
KEY_SECURITIES = { "normal" => "STYPE_NORMAL", "fips" => "STYPE_FIPS", "password" => "STYPE_PASSWORD" }

# initial parameter values
overwrite_key = false

# key/CSR default output file values
key_output = false
csr_output = false
output_dir = Dir.pwd

# BIG-IP connection parameters
bigip = {}
bigip['address'] = ''
bigip['user'] = ''
bigip['pass'] = ''

# private key parameters
key_data = {}
key_data['id'] = ''
key_data['key_type'] = KEY_TYPES["RSA"]
key_data['bit_length'] = 2048
key_data['security'] = KEY_SECURITIES["normal"]

# X.509 data parameters
x509_data = {}
x509_data['common_name'] = ''
x509_data['country_name'] = ''
x509_data['state_name'] = ''
x509_data['locality_name'] = ''
x509_data['organization_name'] = ''
x509_data['division_name'] = ''

# loop through command line options
options.each do |option, arg|
  case option
    when "--bigip-address"
      bigip['address'] = arg
    when "--bigip-user"
      bigip['user'] = arg
    when "--bigip-pass"
      bigip['pass'] = arg
    when "--key-id"
      key_data['id'] = arg
    when "--key-type"
      if KEY_TYPES.keys.include? arg.upcase
        key_data['key_type'] = KEY_TYPES[arg.upcase]
      else
        puts "Error: Invalid key type. Exiting."
        exit 1
      end
    when "--key-bit-length"
      key_data['bit_length'] = arg.to_i
    when "--key-security"
      if KEY_SECURITIES.keys.include? arg.downcase
        key_data['security'] = KEY_SECURITIES[arg.downcase]
      else
        puts "Error: Invalid key security type. Exiting."
        exit 1
      end
    when "--common-name"
      x509_data['common_name'] = arg
    when "--country"
      if arg =~ /[a-z]{2}/i
        x509_data['country_name'] = arg.upcase
      else
        puts "Error: Use exactly two letters for the country code. Exiting."
        exit 1
      end
    when "--state"
      x509_data['state_name'] = arg
    when "--locality"
      x509_data['locality_name'] = arg
    when "--organization"
      x509_data['organization_name'] = arg
    when "--division"
      x509_data['division_name'] = arg
    when "--output-dir"
      if File.directory? arg
        output_dir = arg
      else
        puts "Error: Invalid directory for output. Exiting."
      end
    when "--key-output"
      key_output = true
    when "--csr-output"
      csr_output = true
    when "--help"
      usage
  end
end

# we need at least the BIG-IP's address, user, and a key ID to proceed

usage if bigip['address'].empty? or bigip['user'].empty? or key_data['id'].empty?

if bigip['pass'].empty?
  puts "Please enter the BIG-IPs password..."
  print "Password: "
  system("stty", "-echo")
  bigip['pass'] = gets.chomp
  system("stty", "echo")
  puts
end

# set up connection to BIG-IP and Management.KeyCertificate interface

bigip = F5::IControl.new(bigip['address'], bigip['user'], bigip['pass'], ["Management.KeyCertificate"]).get_interfaces

#grab a list of existing keys and confirm overwrite if a conflict exists

existing_keys = bigip["Management.KeyCertificate"].get_key_list('MANAGEMENT_MODE_DEFAULT').collect { |key| key["key_info"]["id"] }

if existing_keys.include? key_data['id']
  print "A key with an ID of '#{key_data['id']}' already exists. Overwrite it? (yes/no) "
  answer = gets.chomp

  if answer !~ /^yes$/i
    puts "Will not overwrite existing key. Exiting."
    exit
  else
    overwrite_key = true
  end
end

# time to play 20 questions with the X.509 data

if x509_data.values.delete_if { |value| !value.empty? }.size > 0
  puts "Please fill in the following X.509 data parameters..."
end

x509_data.sort.each do |key, value| 
  if value.empty?
    print key.capitalize.gsub('_', ' ') + "? "
    x509_data[key] = gets.chomp
  end
end

bigip["Management.KeyCertificate"].key_generate('MANAGEMENT_MODE_DEFAULT', [key_data], [x509_data], true, overwrite_key)

# write private key to local file if specified by user

if key_output
  key_output_file = output_dir + "/" + key_data['id'] + ".key"
  key = bigip["Management.KeyCertificate"].key_export_to_pem('MANAGEMENT_MODE_DEFAULT', [key_data['id']])[0]
  File.open(key_output_file, 'w') { |file| file.write(key) }
end

# display subject information for CSR as well as the CSR

puts "Certificate Request"
puts "-" * 20
puts "Subject: C=#{x509_data['country_name']}, ST=#{x509_data['state_name']}, L=#{x509_data['locality_name']}, O=#{x509_data['organization_name']}, OU=#{x509_data['division_name']}, CN=#{x509_data['common_name']}"

csr = bigip["Management.KeyCertificate"].certificate_request_export_to_pem('MANAGEMENT_MODE_DEFAULT', [key_data['id']])

# write csr key to local file if specified by user

if csr_output
  csr_output_file = output_dir + "/" + key_data['id'] + ".csr"
  File.open(csr_output_file, 'w') { |file| file.write(csr) }
end

puts
puts csr
Published Mar 09, 2015
Version 1.0
No CommentsBe the first to comment