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.