lib/pkernel_jce/identity.rb in pkernel_jce-0.3 vs lib/pkernel_jce/identity.rb in pkernel_jce-0.7.0

- old
+ new

@@ -1,37 +1,42 @@ +require 'pkernel' + require_relative 'provider' require_relative 'csr' -module PkernelJce +require_relative 'keypair' - # - # Identity - # Identity is abstraction consist of keypair + certificate, stored separately - # - #class Identity - # attr_reader :priv_key, :cert, :keystore, :chain - # def initialize(opts = {}) - # @priv_key = opts[:priv_key] - # @cert = opts[:cert] - # @keystore = opts[:keystore] - # @chain = opts[:chain] - # end - #end - # end Identity - # +# Adding more functions to the parent class +# +# Identity +# Identity is abstraction consist of keypair + certificate, stored separately +# +module Pkernel + class Identity - class Pkernel::Identity - + def post_init(opts) + end + + def certificate=(cert) + @certificate = cert + end + + def chain=(val) + @chain = val + end + + #alias_method :super_key=, :key= def key=(val) @key = val if not @key.nil? @privKey = PkernelJce::KeyPair.private_key(@key) @pubKey = PkernelJce::KeyPair.public_key(@key) end end + alias_method :super_key, :key def key if @key.nil? if not @privKey.nil? if not @pubKey.nil? @key = java.security.KeyPair.new(@pubKey,@privKey) @@ -48,32 +53,72 @@ end @key end + def keypair=(val) + @keypair = val + end + + def keypair + if @keypair.nil? + if not @key.nil? and @key.is_a?(java.security.KeyPair) + @keypair = @key + elsif not @key.nil? and @key.is_a?(java.security.PrivateKey) + @privKey = @key + if not @pubKey.nil? + @keypair = java.security.KeyPair.new(@pubKey,@privKey) + elsif not @certificate.nil? + @keypair = java.security.KeyPair.new(PkernelJce::Certificate.public_key(@certificate),@privKey) + else + # no public key available + end + else + + if not @privKey.nil? + if not @pubKey.nil? + @keypair = java.security.KeyPair.new(@pubKey,@privKey) + elsif not @certificate.nil? + @keypair = java.security.KeyPair.new(@certificate.public_key,@privKey) + else + # no possible to generate without public key + end + else + # not possible to generate without private key + end + + end + end + + @keypair + end + + alias_method :super_privKey, :privKey def privKey if @privKey.nil? and not @key.nil? @privKey = PkernelJce::KeyPair.private_key(@key) end @privKey end + alias_method :super_pubKey, :pubKey def pubKey if @pubKey.nil? if not @key.nil? @pubKey = PkernelJce::KeyPair.public_key(@key) elsif not @certificate.nil? @pubKey = PkernelJce::KeyPair.public_key(@certificate) end end - + @pubKey end - + + alias_method :super_cert, :certificate def certificate - if not @certificate.nil? and @certificate.java_kind_of?(Java::OrgBouncycastleCert::X509CertificateHolder) - @certificate = @certificate.to_java_cert + if not @certificate.nil? + @certificate = Certificate.ensure_java_cert(@certificate) end @certificate end # In java world, JCE/JCA provides switchable engine to call if it is software/hardware @@ -94,15 +139,22 @@ def provider if @provider.nil? PkernelJce::GConf.instance.glog.debug "Provider is nil in Identity object. Setting it to default provider '#{PkernelJce::Provider::DefProvider.name}'" @provider = PkernelJce::Provider.add_default end - + @provider end end +end +# +# Pkernel::Identity extension +# + +module PkernelJce + # # IdentityFactory # module IdentityFactory @@ -135,139 +187,562 @@ id end alias_method :build, :build_from_components # end build_from_components - def dump(id, opts = {}) + def dump(id, opts = {}, &block) if id.nil? raise PkernelJce::Error, "Identity object is nil in write to keystore" end + result = { } + + format = opts[:format] + case format + when :pkcs8, :pk8, :p8 + + res = dump_pk8(id, opts, &block) + + # private key + file = opts[:file] + if file.nil? or file.empty? + result[:bin] = res[:bin] + else + ff = java.io.File.new(file) + ff.parent_file.mkdirs if not ff.parent_file.exists? + fos = java.io.FileOutputStream.new(ff) + fos.write res[:bin].to_java.getBytes + fos.flush + fos.close + + result[:file] = file + end + + # certificate + cfile = opts[:cert_file] + if not cfile.nil? + ff = java.io.File.new(cfile) + ff.parent_file.mkdirs if not ff.parent_file.exists? + fos = java.io.FileOutputStream.new(ff) + fos.write res[:cert_bin] + fos.flush + fos.close + result[:cert_file] = cfile + else + result[:cert_bin] = res[:cert_bin] + end + + # cert chain + cafile = opts[:ca_file] + if not cafile.nil? + ff = java.io.File.new(cafile) + ff.parent_file.mkdirs if not ff.parent_file.exists? + fos = java.io.FileOutputStream.new(ff) + res[:ca_bin].each do |ca| + fos.write ca + end + fos.flush + fos.close + + result[:ca_file] = cafile + else + result[:ca_bin] = res[:ca_bin] + end + + result + + when :pem + + res = dump_pem(id, opts, &block) + + # private key + file = opts[:file] + if file.nil? or file.empty? + result[:bin] = res[:bin] + else + ff = java.io.File.new(file) + ff.parent_file.mkdirs if not ff.parent_file.exists? + fos = java.io.FileOutputStream.new(ff) + fos.write res[:bin] + fos.flush + fos.close + + result[:file] = file + end + + # public key + cfile = opts[:pubKey_file] + if not cfile.nil? + ff = java.io.File.new(cfile) + ff.parent_file.mkdirs if not ff.parent_file.exists? + fos = java.io.FileOutputStream.new(ff) + fos.write res[:pubKey_bin] + fos.flush + fos.close + result[:pubKey_file] = cfile + else + result[:pubKey_bin] = res[:pubKey_bin] + end + + # certificate + cfile = opts[:cert_file] + if not cfile.nil? + ff = java.io.File.new(cfile) + ff.parent_file.mkdirs if not ff.parent_file.exists? + fos = java.io.FileOutputStream.new(ff) + fos.write res[:cert_bin] + fos.flush + fos.close + result[:cert_file] = cfile + else + result[:cert_bin] = res[:cert_bin] + end + + # cert chain + cafile = opts[:ca_file] + if not cafile.nil? + ff = java.io.File.new(cafile) + ff.parent_file.mkdirs if not ff.parent_file.exists? + fos = java.io.FileOutputStream.new(ff) + res[:ca_bin].each do |ca| + fos.write ca + end + fos.flush + fos.close + + result[:ca_file] = cafile + else + result[:ca_bin] = res[:ca_bin] + end + + result + + + else + # JCE/JCA keystore + rres = dump_keystore(id, opts, &block) + + result.merge!(rres) + + file = opts[:file] + if file.nil? or file.empty? + else + ff = java.io.File.new(file) + ff.parent_file.mkdirs if not ff.parent_file.exists? + fos = java.io.FileOutputStream.new(file) + fos.write result[:bin] + fos.flush + fos.close + + result[:file] = file + end + + result + end + + end + + def dump_to_file(id, file, opts = { }, &block) + opts = { } if opts.nil? + raise PkernelJce::Error, "Option for dump to file must be a hash" if not opts.is_a?(Hash) + dump(id, opts.merge({ file: file }), &block) + end + + def dump_to_file_p8(id, idFile, certFile, caFile, password, opts = { }, &block) + opts = { } if opts.nil? + raise PkernelJce::Error, "Option for dump to file in pkcs8 format must be a hash" if not opts.is_a?(Hash) + + raise PkernelJce::Error, "Identity file path cannot be empty" if empty?(idFile) + raise PkernelJce::Error, "Certificate file path cannot be empty" if empty?(certFile) + raise PkernelJce::Error, "CA chain file path cannot be empty" if empty?(caFile) + + dump(id, opts.merge({ file: idFile, cert_file: certFile, ca_file: caFile, format: :pkcs8 }),&block) + end + + def load(opts = {}, &block) + + format = opts[:format] + case format + when :pk8, :p8, :pkcs8 + res = load_pk8(opts, &block) + when :pem + res = load_pem(opts, &block) + else + res = load_keystore(opts, &block) + end + + if res[:key].nil? + raise Pkernel::Error, "Failed to load key from the store." + end + + Pkernel::Identity.new( { privKey: res[:key], certificate: res[:cert], chain: res[:chain] } ) + end + # end load() + + private + def dump_keystore(id, opts = { }, &block) + + result = { } prov = opts[:provider] if prov.nil? prov = PkernelJce::Provider.add_default else prov = PkernelJce::Provider.add_provider(prov) end - format = opts[:format] - format = :p12 if format.nil? - sFormat = format - case format + case opts[:format] when :p12, :pkcs12 - PkernelJce::GConf.instance.glog.debug "Loading PKCS12 keystore" - ks = java.security.KeyStore.getInstance("PKCS12",prov) - sFormat = :p12 - when :jks - PkernelJce::GConf.instance.glog.debug "Loading JKS keystore" - ks = java.security.KeyStore.getInstance("JKS") - sFormat = :jks + stype = "PKCS12" + when :jks, :java + stype = "jks" + when :bks + stype = "bks" else - PkernelJce::GConf.instance.glog.debug "Loading '#{format}' keystore" - if prov.nil? - ks = java.security.KeyStore.getInstance(format) - else - ks = java.security.KeyStore.getInstance(format, prov) - end - sFormat = format + PkernelJce::GConf.instance.glog.debug "No keystore type given. Default to pkcs12 keystore" + stype = "PKCS12" end - result = { } + if prov.nil? or stype == "jks" + PkernelJce::GConf.instance.glog.debug "Loading #{stype} keystore with null provider" + ks = java.security.KeyStore.getInstance(stype) + else + PkernelJce::GConf.instance.glog.debug "Loading #{stype} keystore with provider #{prov.name}" + ks = java.security.KeyStore.getInstance(stype,prov) + end + pass = opts[:password] if pass.nil? or pass.empty? - PkernelJce::GConf.instance.glog.warn "Password is not given to dump identity. Random password shall be generated." - pass = SecureRandom.hex(8) + if block + pass = block.call(:prompt_pass) + end + end + + passLen = opts[:password_length] || 8 + # cannot be too small... + if (passLen/2) < 8 + passLen = 8 + else + passLen = passLen/2 + end + + if pass.nil? or pass.empty? + PkernelJce::GConf.instance.glog.warn "Password is not given to dump identity. Random password of #{passLen*2} characters shall be generated." + pass = SecureRandom.hex(passLen) result[:password] = pass - #raise PkernelJce::Error, "Password should not be empty for identity storage" end chain = id.chain.map do |c| if c.java_kind_of?(org.bouncycastle.cert.X509CertificateHolder) c.to_java_cert else c end end - name = opts[:key_name] || "Pkernel JCE" + name = opts[:key_name] || opts[:keyName] || opts[:keyname] || Certificate.get_subject_fields(id.certificate,[:cn])[:cn][0] ks.load(nil,nil) ks.setKeyEntry(name, id.privKey, pass.to_java.toCharArray, chain.to_java(java.security.cert.Certificate)) - baos = java.io.ByteArrayOutputStream.new - file = opts[:file] - if file.nil? or file.empty? - ks.store(baos, pass.to_java.toCharArray) - #baos.toByteArray - result[:bin] = baos.toByteArray - else - fos = java.io.FileOutputStream.new(file) - ks.store(fos, pass.to_java.toCharArray) - fos.flush - fos.close - - result[:file] = file - end - + baos = java.io.ByteArrayOutputStream.new + ks.store(baos, pass.to_java.toCharArray) + result[:bin] = baos.toByteArray + result + end + # dump_keystore - def load(opts = {}) + def load_keystore(opts = { }, &block) + result = { } + prov = opts[:provider] - if prov.nil? - prov = PkernelJce::Provider.add_default - else + if not prov.nil? prov = PkernelJce::Provider.add_provider(prov) end format = opts[:format] format = :p12 if format.nil? sFormat = format - case format + case format.to_sym when :p12, :pkcs12 PkernelJce::GConf.instance.glog.debug "Loading PKCS12 keystore" - ks = java.security.KeyStore.getInstance("PKCS12",prov) - sFormat = :p12 + pprov = PkernelJce::Provider.add_default + ks = java.security.KeyStore.getInstance("PKCS12",pprov) + result[:ks_format] = :p12 when :jks PkernelJce::GConf.instance.glog.debug "Loading JKS keystore" ks = java.security.KeyStore.getInstance("JKS") - sFormat = :jks + result[:ks_format] = :jks + + when :pk8, :pkcs8, :p8 + else PkernelJce::GConf.instance.glog.debug "Loading '#{format}' keystore" if prov.nil? ks = java.security.KeyStore.getInstance(format.to_s) else ks = java.security.KeyStore.getInstance(format.to_s, prov) end + + result[:ks_format] = format end - pass = opts[:password] || '' + pass = opts[:password] + + if (pass.nil? or pass.empty?) and block + pass = block.call(:prompt_pass) + end file = opts[:file] bin = opts[:bin] baos = java.io.ByteArrayOutputStream.new - if not file.nil? or not file.empty? + if not (file.nil? or file.empty?) fis = java.io.FileInputStream.new(file) ks.load(fis,pass.to_java.toCharArray) fis.close - elsif bin.nil? + elsif not bin.nil? ks.load(java.io.ByteArrayInputStream.new(bin),pass.to_java.toCharArray) else - raise PkernelJce::Error, "No file or bin is given to load identity" + raise PkernelJce::Error, "No file or bin in keystore format is given to load identity" end - - name = opts[:key_name] || ks.aliases.to_a[0] - - key = ks.getKey(name,pass.to_java.toCharArray) - cert = ks.getCertificate(name) - chain = ks.getCertificateChain(name) - id = Pkernel::Identity.new( { privKey: key, certificate: cert, chain: chain } ) - id + keyname = opts[:keyname] || opts[:keyName] || opts[:key_name] + + if not (keyname.empty? or keyname.nil?) + result[:ks_alias] = keyname + else + aliases = ks.aliases.to_a + if aliases.length == 0 + raise PkernelJce::Error, "No alias available from the keystore" + end + + if aliases.length > 1 + # more the 1 aliases + # to ensure the loading is correct, call block or error + if block + result[:ks_alias] = block.call(:multiple_aliases, aliases) + else + raise PkernelJce::Error, "Multiple aliases exist in the keystores. Please provide keyname to pick one or provide a block for processing" + end + else + result[:ks_alias] = aliases.first + end + end + + result[:key] = ks.getKey(result[:ks_alias],pass.to_java.toCharArray) + result[:cert] = ks.getCertificate(result[:ks_alias]) + result[:chain] = ks.getCertificateChain(result[:ks_alias]) + + result end + # load_keystore + + def dump_pem(id,opts,&block) + + result = { } + baos = java.io.ByteArrayOutputStream.new + + pass = opts[:password] + if (pass.nil? or pass.empty?) and block + pass = block.call(:prompt_pass) + end + + PkernelJce::Provider.add_default + + writer = org.bouncycastle.openssl.PEMWriter.new(java.io.OutputStreamWriter.new(baos)) + if pass.nil? or pass.empty? + PkernelJce::GConf.instance.glog.debug "Constructing plain PEM..." + writer.writeObject(id.privKey) + else + PkernelJce::GConf.instance.glog.debug "Constructing encrypted PEM..." + encryptor = org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder.new("AES-256-GCM").build(pass.to_java.toCharArray) + writer.writeObject(id.privKey,encryptor) + end + writer.flush + writer.close + result[:bin] = baos.toByteArray + + baos.reset + writer = org.bouncycastle.openssl.PEMWriter.new(java.io.OutputStreamWriter.new(baos)) + writer.writeObject(id.certificate) + writer.flush + writer.close + result[:cert_bin] = baos.toByteArray + + baos.reset + writer = org.bouncycastle.openssl.PEMWriter.new(java.io.OutputStreamWriter.new(baos)) + writer.writeObject(id.pubKey) + writer.flush + writer.close + result[:pubKey_bin] = baos.toByteArray + + cc = [] + id.chain.each do |c| + baos.reset + writer = org.bouncycastle.openssl.PEMWriter.new(java.io.OutputStreamWriter.new(baos)) + writer.writeObject(c) + writer.flush + writer.close + cc << baos.toByteArray + end + result[:ca_bin] = cc + + result + + end + # dump_pem + + def load_pem(opts, &block) + res = { } + file = opts[:file] + bin = opts[:bin] + + prov = PkernelJce::Provider.add_default + + converter = org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.new.setProvider prov + + if not (file.nil? or file.empty?) + reader = org.bouncycastle.openssl.PEMParser.new(java.io.InputStreamReader.new(java.io.FileInputStream.new(file))) + elsif not (bin.nil? or bin.empty?) + reader = org.bouncycastle.openssl.PEMParser.new(java.io.InputStreamReader.new(java.io.ByteArrayInputStream.new(IoUtils.ensure_java_bytes(bin)))) + else + raise PkernelJce::Error, "No file or bin in PEM format is given to load identity" + end + + obj = reader.readObject + if obj.java_kind_of? org.bouncycastle.openssl.PEMKeyPair + res[:keypair] = converter.getKeyPair(obj) + res[:key] = PkernelJce::KeyPair.private_key(res[:keypair]) + + elsif obj.java_kind_of? org.bouncycastle.openssl.PEMEncryptedKeyPair + + PkernelJce::GConf.instance.glog.debug "Loading encrypted PEM..." + pass = opts[:password] + if (pass.nil? or pass.empty?) and block + pass = block.call(:prompt_pass) + end + + if pass.nil? or pass.empty? + raise PkernelJce::Error, "The given key file to load is password protected but password is not given. Process shall abort." + end + + decryptor = org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder.new.setProvider(prov).build(pass.to_java.toCharArray) + res[:keypair] = converter.getKeyPair(obj.decryptKeyPair(decryptor)) + res[:key] = PkernelJce::KeyPair.private_key(res[:keypair]) + + elsif obj.java_kind_of? org.bouncycastle.asn1.pkcs.PrivateKeyInfo + res[:key] = converter.getPrivateKey obj + else + raise PkernelJce::Error, "Unknown object for further processing #{obj.class}" + end + + if not (opts[:cert_file].nil? or opts[:cert_file].empty?) + reader = org.bouncycastle.openssl.PEMParser.new(java.io.InputStreamReader.new(java.io.FileInputStream.new(opts[:cert_file]))) + res[:cert] = reader.readObject + elsif not (opts[:cert_bin].nil? or opts[:cert_bin].empty?) + reader = org.bouncycastle.openssl.PEMParser.new(java.io.InputStreamReader.new(java.io.ByteArrayInputStream.new(IoUtils.ensure_java_bytes(opts[:cert_bin])))) + res[:cert] = reader.readObject + end + + if not (opts[:ca_file].nil? or opts[:ca_file].empty?) + reader = org.bouncycastle.openssl.PEMParser.new(java.io.InputStreamReader.new(java.io.FileInputStream.new(opts[:ca_file]))) + res[:chain] = [] + o = reader.readObject + while(o != nil) + res[:chain] << o + o = reader.readObject + end + elsif not (opts[:ca_bin].nil? or opts[:ca_bin].empty?) + reader = org.bouncycastle.openssl.PEMParser.new(java.io.InputStreamReader.new(java.io.ByteArrayInputStream.new(IoUtils.ensure_java_bytes(opts[:ca_bin])))) + res[:chain] = [] + o = reader.readObject + while(o != nil) + res[:chain] << o + o = reader.readObject + end + end + + res + + end + # load_pem + + # dump_pk8() + def dump_pk8(id, opts, &block) + + PkernelJce::GConf.instance.glog.debug "Dump to PKCS8 format..." + + result = { } + + pass = opts[:password] + if (pass.nil? or pass.empty?) and block + pass = block.call(:prompt_pass) + end + + prov = opts[:provider] + if not prov.nil? + prov = PkernelJce::Provider.add_provider(prov) + end + + # private key only + # remove file parameter if given + # because we want the dump to be available here + keyDumpPara = opts.clone + keyDumpPara.delete(:file) + # KeyPair already using the PKCS8 format for output... might as well just reuse... + rres = PkernelJce::KeyPairEngine.dump(id.keypair, keyDumpPara, &block) + + result[:bin] = rres + + result[:cert_bin] = PkernelJce::CertificateEngine.dump(id.certificate, { outForm: :pem }) + + cc = [] + id.chain.each do |c| + cc << PkernelJce::CertificateEngine.dump(c, { outForm: :pem }) + end + + result[:ca_bin] = cc + + result + + end + # dump_pk8 + + def load_pk8(opts, &block) + + res = { } + pass = opts[:password] + if (pass.nil? or pass.empty?) and block + pass = block.call(:prompt_pass) + end + + prov = opts[:provider] + if not prov.nil? + prov = PkernelJce::Provider.add_provider(prov) + end + + key = PkernelJce::KeyPairEngine.load(opts) + + res[:key] = key + + certPara = { } + certPara[:file] = opts[:cert_file] if not (opts[:cert_file].nil? or opts[:cert_file].empty?) + certPara[:bin] = opts[:cert_bin] if not (opts[:cert_bin].nil? or opts[:cert_file].empty?) + res[:cert] = PkernelJce::CertificateEngine.load(certPara) + + chainPara = { multiple: true } + chainPara[:file] = opts[:ca_file] if not (opts[:ca_file].nil? or opts[:ca_file].empty?) + chainPara[:bin] = opts[:ca_bin] if not (opts[:ca_bin].nil? or opts[:ca_bin].empty?) + res[:chain] = PkernelJce::CertificateEngine.load(chainPara) + + res + + end + # load_pk8 end # end IdentityFactory