require 'pkernel' require_relative 'provider' require_relative 'csr' require_relative 'keypair' # Adding more functions to the parent class # # Identity # Identity is abstraction consist of keypair + certificate, stored separately # module Pkernel class 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) elsif not @certificate.nil? @key = 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 else # key is not nil... 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? @certificate = Certificate.ensure_java_cert(@certificate) end @certificate end # In java world, JCE/JCA provides switchable engine to call if it is software/hardware # This provider is tightly related to private key. # Since private key is encapsulated in this object, might as well keep the pointer here. # Whoever want to use the private key, also should check the provider to load correct # signing engine def provider=(val) if not val.nil? if val.is_a?(String) and not val.empty? @provider = PkernelJce::Provider.add_provider(val) else @provider = PkernelJce::Provider.add_provider(val) end end end 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 def build_from_components(key, cert = nil, chain = [], provider = nil) if key.nil? raise PkernelJce::Error, "Key cannot be nil to build identity" end id = Pkernel::Identity.new( { key: key, certificate: cert, chain: chain } ) if cert.nil? class_eval do include PkernelJce::IdentityManagement end else c = PkernelJce::Certificate.ensure_java_cert(cert) if PkernelJce::Certificate.is_issuer_cert?(c) class_eval do include PkernelJce::IdentityIssuer include PkernelJce::IdentityManagement end else class_eval do include PkernelJce::IdentityManagement end end end id.provider = provider id end alias_method :build, :build_from_components # end build_from_components 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 case opts[:format] when :p12, :pkcs12 stype = "PKCS12" when :jks, :java stype = "jks" when :bks stype = "bks" else PkernelJce::GConf.instance.glog.debug "No keystore type given. Default to pkcs12 keystore" stype = "PKCS12" end 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? 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 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] || 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 ks.store(baos, pass.to_java.toCharArray) result[:bin] = baos.toByteArray result end # dump_keystore def load_keystore(opts = { }, &block) result = { } prov = opts[:provider] if not prov.nil? prov = PkernelJce::Provider.add_provider(prov) end format = opts[:format] format = :p12 if format.nil? sFormat = format case format.to_sym when :p12, :pkcs12 PkernelJce::GConf.instance.glog.debug "Loading PKCS12 keystore" 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") 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] 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 file.empty?) fis = java.io.FileInputStream.new(file) ks.load(fis,pass.to_java.toCharArray) fis.close elsif not bin.nil? ks.load(java.io.ByteArrayInputStream.new(bin),pass.to_java.toCharArray) else raise PkernelJce::Error, "No file or bin in keystore format is given to load identity" end 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 module IdentityIssuer #def issue_cert(identity, opts = {}) # src = opts[:source] # if src.nil? or src.empty? # raise PkernelJce::Error, "Issue cert requires source key indicating either from CSR or Owner structure" # end # conf = opts[:config] # if conf.nil? or conf.empty? # raise PkernelJce::Error, "Config for certificate generation is not given!" # end # if src[:csr_file].nil? # csrBin = PkernelJce::CSRCore.load({ file: src[:csr_file] }) # elsif src[:csr].nil? # owner = Pkernel::Certificate::Owner.load_from_csr(src[:csr]) # elsif src[:owner].nil? # # else # raise PkernelJce::Error, "No CSR or Owner is given to issue certificate" # end # issuerKey = PkernelJce::KeyPair.private_key(identity.privKey) # conf[:owner] = owner # cert = @cdriver.generate(conf) do |v| # case v # when :signAlgo # PkernelJce::KeyPair.derive_signing_algo(issuerKey, "SHA256") # when :issuerKey # issuerKey # when :issuerCert # when :keyUsage # when :extKeyUsage # end # end #end end # end IdentityIssuer module IdentityManagement def destroy PkernelJce::GConf.instance.glog.warn "Destroy not implemented for JCE context" end end class IdentityEngine extend IdentityFactory end end