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