Hero Image
- Philipp Ludewig

Zertifikate und Ruby

Hi zusammen heute schreibe ich mal über etwas, aus meinem Leben als Programmierer.

Für meine Masterarbeit arbeitete ich vor kurzer Zeit an einem Ruby Skript mit dem ein Zertifikat erstellt und von einer CA im gleichen Atemzug signiert wird. Am Ende des Skriptes muss dann die Zertifikatskette auch noch als PKCS12 exportiert werden.

Die Schwierigkeit der ich am Anfang Gegenüberstand war die Frage wie funktioniert das den mit OpenSSL überhaupt in Ruby? Wie signiere ich den das Zertifikat und wie übergebe ich all die Informationen die da reinkommen? Diese und weitere Fragen konnte ich schon mal mit diesen beiden hilfreichen Blogeinträgen hier klären: Tech Linguist , devnotcorp

Nun hatte ich schon einmal die Grundlagen der Thematik verstanden. Dennoch beschrieben beide Blogeinträge nicht wie man meine Aufgabe lösen würde. Nach einigen Try-and-Fail sowie Rubber Duck Debugging hatte ich den Bogen raus.

Damit ihr den Überblick behaltet habe ich mich dazu entschieden das Skript in drei Methoden aufzuteilen:

  • create_certificate
  • create_key
  • export_to_pkcs12
# The Greeter class
class Certificate_create
  gem 'openssl'
  require 'openssl'
  require 'date'
  require 'time'
  require 'passgen'
  require 'fileutils'
  require 'sequel'

  # Methode welche das Zertifikat erstellt
  def create_certificate
  begin

   # Sammle Informationen
   # Die Methode steht beispielhaft dafür dass das Passwort zur CA irgendwoher eingelesen wird. 
   # Ihr könnt natürlich auch das Passwort im Klartext speichern aber das sollte man nur zum Debugging tun und ansonsten NIEMALS.
     password_to_ca = getPassword_of_Pkey_CA

   # Ordner für Zertifikat erstellen
     path_to_folder = 'Pfad wo ihr euer Zertifikat speichern möchtet'
     FileUtils.mkdir(path_to_folder)

   # Erstelle die Keys durch die Methode create_key zu der ich später komme. 
     key,key_passphrase = self.create_key(path_to_folder)
     pk12_passphrase = Passgen::generate(:length => 8,:symbols => false)

   # Lade das SUB-CA und den Schlüssel der CA
      ca_cert = OpenSSL::X509::Certificate.new(File.read('Pfad zur CA'))
      ca_pkey_pem = File.read('Pfad zum privaten Schlüssel der CA')
      ca_key = OpenSSL::PKey::RSA.new(ca_pkey_pem,password_to_ca)

   # Bestimme das Datum und die Uhrzeit damit die Lebenszeit des Zertifikates definiert werden kann
      time_now = Time.new
      period_of_validity = time_now + (60*60*24*365) # 1 Jahr (sek*min*Stunden*Tage)

   #Erstelle Zertifikat
      cert = OpenSSL::X509::Certificate.new
      cert.version = 2
      cert.serial = Random.rand(68717379584) + 1 # Damit die Zertifikate intern keine Kolisionen haben füge ich hier eine Serial hinzu.
      cert.not_before = time_now
   # Dauer des Zertifikates festlegen
      cert.not_after = period_of_validity
      cert.public_key = key.public_key

   # Hier werden die Informationen über den Herausgeber und dem Empfänger definiert
   # C = Name des Landes    CE = Name des Bundeslandes    L = Name der Stadt
   # O = Name des Unternehmes OU = Name der Abteilung    CN = Name des Zertifikates
   # emailAddress = Die Emailadresse welche mit dem Zertifikat verknüpft ist
      cn_name = 'Name der Person'
      cert.subject =  OpenSSL::X509::Name.parse("C=#{'DE'},ST=#{'Saxony'},L=#{'Dresden'},O=#{'Fakery'},OU=#{'FakeOU'},CN=#{cn_name},emailAddress=#{fake@fakenet.fk}")
   # Natürlich werden die Informationen der CA übernommen bei den Informationen des Herausgebers
      cert.issuer = ca_cert.subject

   # Hier wird das Zertifikat zusammengebaut und dazu nutzen wir die extension_factory
      extension_factory = OpenSSL::X509::ExtensionFactory.new
      extension_factory.subject_certificate = cert
      extension_factory.issuer_certificate = ca_cert
      cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier','hash'))
      cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE', true))
      cert.add_extension(extension_factory.create_extension('keyUsage',  'keyEncipherment,nonRepudiation,digitalSignature', true))

   # Nun signieren wir das Zertifikat mit dem Schlüssel der CA
      cert.sign(ca_key, OpenSSL::Digest::SHA512.new)
      File.write("#{path_to_folder}/#{@VPN_Obj.first_name_of_subject}_#{@VPN_Obj.last_name_of_subject}_cert.pem", cert.to_pem.to_s )

   # Last but not least wird das ganze als Zertifikatskette exportiert.
   self.export_as_PKCS12(db.global_var.getPath_for_sub_ca(con),key,cert,path_to_folder,pk12_passphrase)
 rescue e
 puts e.message
 end
end

Damit wäre der schwere Teil schon abgehandelt. Jetzt kommen wir noch kurz zu den anderen Methoden.

 def create_key(path_to_folder)
    # Erstellung des Public und Private Key 
    # Wir nutzen 4MB als Größe des Schlüssels
    key = OpenSSL::PKey::RSA.new(4096)
    open("#{path_to_folder}/#{@VPN_Obj.first_name_of_subject}_#{@VPN_Obj.last_name_of_subject}_private_key.pem", 'w') do |io| io.write(key.to_pem) end
    open("#{path_to_folder}/#{@VPN_Obj.first_name_of_subject}_#{@VPN_Obj.last_name_of_subject}_public_key.pem", 'w') do |io| io.write(key.public_key.to_pem) end

    # Key mit Passphrase versehen
    cipher = OpenSSL::Cipher.new('AES-256-CBC')
    pass_phrase = Passgen::generate(:length => 8,:symbols => false)
    key_secure = key.export(cipher, pass_phrase)
    open("#{path_to_folder}/#{@VPN_Obj.first_name_of_subject}_#{@VPN_Obj.last_name_of_subject}_private_key.secure.pem", 'w') do |io| io.write(key_secure) end

    #Key und dessen Passphrase returnen (und ja auch hier sollte das Passwort schon verschlüsselt sein)
    return key,pass_phrase
  end

Am Ende ist es dann noch einmal etwas tricky geworden da par Tu die Methode die CA nicht als Zertifikat anerkennen wollte. Wie so oft lag es an einem einfachen Syntaxfehler. Indem bin ich bei Ruby immer noch ganz groß.

def export_as_PKCS12(db_path_to_sub_ca,key,cert,path_to_folder,pk12_passphrase)
    # Ein Certificate Object erstellen 
    ca_cert = OpenSSL::X509::Certificate.new(File.read(db_path_to_sub_ca))

    # Erstelle das PKCS12 Object und befülle es gleichzeitig mit den benötigten Informatioen
    p12 = OpenSSL::PKCS12.create(pk12_passphrase, "#{@VPN_Obj.first_name_of_subject}_#{@VPN_Obj.last_name_of_subject}_cert_signed", key, cert,[ca_cert])
    open("#{path_to_folder}/#{@VPN_Obj.first_name_of_subject}_#{@VPN_Obj.last_name_of_subject}_cert_signed.p12", 'w') do |io| io.write(p12.to_der) end
end

In der Dokumentation von Ruby ist die OpenSSL::PKCS12.create folgendermaßen definiert:

create(pass, name, key, cert [, ca, [, key_pbe [, cert_pbe [, key_iter [, mac_iter [, keytype]]]]]])

  • pass - string
  • name - A string describing the key.
  • key - Any PKey.
  • cert - A X509::Certificate.

Durch das Skript ist die Aufgabe erledigt und nun gilt es im weiteren Schritt die Ergebnisse in GIT Repositories einzupflegen. Wie das geht weiß ich noch nicht aber wenn ich es weiß lass ich es euch wissen.