ccrypto-java 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.java-version +1 -1
- data/.release_history.yml +4 -0
- data/.ruby-version +1 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +68 -53
- data/Rakefile +2 -1
- data/bin/console +14 -0
- data/jars/bcjmail-jdk18on-172.jar +0 -0
- data/jars/bcmail-jdk18on-172.jar +0 -0
- data/jars/bcpg-jdk18on-172.1.jar +0 -0
- data/jars/bcpkix-jdk18on-172.jar +0 -0
- data/jars/bcprov-ext-jdk18on-172.jar +0 -0
- data/jars/bcprov-jdk18on-172.jar +0 -0
- data/jars/bctls-jdk18on-172.jar +0 -0
- data/jars/bcutil-jdk18on-172.jar +0 -0
- data/lib/ccrypto/java/bc_const_mapping.rb +42 -0
- data/lib/ccrypto/java/data_conversion.rb +23 -2
- data/lib/ccrypto/java/engines/argon2_engine.rb +95 -0
- data/lib/ccrypto/java/engines/asn1_engine.rb +2 -1
- data/lib/ccrypto/java/engines/bcrypt_engine.rb +56 -0
- data/lib/ccrypto/java/engines/cipher_engine.rb +462 -130
- data/lib/ccrypto/java/engines/compression_engine.rb +7 -28
- data/lib/ccrypto/java/engines/crystal_dilithium_engine.rb +226 -0
- data/lib/ccrypto/java/engines/crystal_kyber_engine.rb +260 -0
- data/lib/ccrypto/java/engines/decompression_engine.rb +5 -4
- data/lib/ccrypto/java/engines/digest_engine.rb +221 -139
- data/lib/ccrypto/java/engines/ecc_engine.rb +249 -96
- data/lib/ccrypto/java/engines/ed25519_engine.rb +211 -0
- data/lib/ccrypto/java/engines/hkdf_engine.rb +82 -23
- data/lib/ccrypto/java/engines/hmac_engine.rb +98 -23
- data/lib/ccrypto/java/engines/pbkdf2_engine.rb +82 -33
- data/lib/ccrypto/java/engines/pkcs7_engine.rb +44 -33
- data/lib/ccrypto/java/engines/rsa_engine.rb +85 -31
- data/lib/ccrypto/java/engines/scrypt_engine.rb +12 -3
- data/lib/ccrypto/java/engines/secret_key_engine.rb +77 -12
- data/lib/ccrypto/java/engines/secret_sharing_engine.rb +17 -2
- data/lib/ccrypto/java/engines/x25519_engine.rb +249 -0
- data/lib/ccrypto/java/engines/x509_csr_engine.rb +141 -0
- data/lib/ccrypto/java/engines/x509_engine.rb +365 -71
- data/lib/ccrypto/java/ext/secret_key.rb +37 -25
- data/lib/ccrypto/java/ext/x509_cert.rb +429 -5
- data/lib/ccrypto/java/ext/x509_csr.rb +151 -0
- data/lib/ccrypto/java/jce_provider.rb +0 -11
- data/lib/ccrypto/java/keystore/jce_keystore.rb +205 -0
- data/lib/ccrypto/java/keystore/jks_keystore.rb +52 -0
- data/lib/ccrypto/java/keystore/keystore.rb +97 -0
- data/lib/ccrypto/java/keystore/pem_keystore.rb +147 -0
- data/lib/ccrypto/java/keystore/pkcs12_keystore.rb +56 -0
- data/lib/ccrypto/java/utils/comparator.rb +25 -2
- data/lib/ccrypto/java/version.rb +1 -1
- data/lib/ccrypto/java.rb +46 -0
- data/lib/ccrypto/provider.rb +139 -3
- metadata +40 -24
- data/ccrypto-java.gemspec +0 -44
- data/jars/bcmail-jdk15on-165.jar +0 -0
- data/jars/bcpg-jdk15on-165.jar +0 -0
- data/jars/bcpkix-jdk15on-165.jar +0 -0
- data/jars/bcprov-ext-jdk15on-165.jar +0 -0
- data/jars/bcprov-jdk15on-165.jar +0 -0
- data/jars/bctls-jdk15on-165.jar +0 -0
- data/lib/ccrypto/java/keybundle_store/pkcs12.rb +0 -125
@@ -0,0 +1,151 @@
|
|
1
|
+
|
2
|
+
#require 'pry'
|
3
|
+
|
4
|
+
require_relative '../data_conversion'
|
5
|
+
|
6
|
+
module Ccrypto
|
7
|
+
class X509CSR
|
8
|
+
include TR::CondUtils
|
9
|
+
|
10
|
+
include Java::DataConversion
|
11
|
+
|
12
|
+
include TeLogger::TeLogHelper
|
13
|
+
teLogger_tag :j_x509csr
|
14
|
+
|
15
|
+
attr_reader :request_subject_full
|
16
|
+
|
17
|
+
def initialize(csr)
|
18
|
+
@nativeCSR = csr
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_bin
|
22
|
+
@nativeCSR.encoded
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_pem
|
26
|
+
|
27
|
+
baos = java.io.ByteArrayOutputStream.new
|
28
|
+
|
29
|
+
writer = org.bouncycastle.openssl.jcajce.JcaPEMWriter.new(java.io.OutputStreamWriter.new(baos))
|
30
|
+
|
31
|
+
begin
|
32
|
+
writer.writeObject(@nativeCSR)
|
33
|
+
ensure
|
34
|
+
writer.flush
|
35
|
+
writer.close
|
36
|
+
end
|
37
|
+
|
38
|
+
baos.toByteArray
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def csr_info
|
43
|
+
if @csrInfo.nil?
|
44
|
+
@csrInfo = parseCSR(@nativeCSR)
|
45
|
+
end
|
46
|
+
@csrInfo
|
47
|
+
end
|
48
|
+
|
49
|
+
def parseCSR(csrBin)
|
50
|
+
|
51
|
+
case csrBin
|
52
|
+
when ::Java::byte[]
|
53
|
+
csr = org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest.new(csrBin)
|
54
|
+
|
55
|
+
when String
|
56
|
+
# this assumed input is a PEM formatted content
|
57
|
+
reader = org.bouncycastle.openssl.PEMParser.new(java.io.InputStreamReader.new(java.io.ByteArrayInputStream.new(to_java_bytes(csrBin))))
|
58
|
+
csr = reader.readObject
|
59
|
+
raise X509CSRException, "Given CSR string to load does not encapsulate a CSR structure. It is read as '#{csr.class.name}'" if not csr.is_a?(org.bouncycastle.pkcs::PKCS10CertificationRequest)
|
60
|
+
|
61
|
+
when Ccrypto::X509CSR
|
62
|
+
csr = csrBin.nativeCSR
|
63
|
+
|
64
|
+
else
|
65
|
+
raise X509CSRException, "Unknown how to handle CSR of format #{csrBin.class}"
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
cvProv = org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder.new.build(csr.getSubjectPublicKeyInfo)
|
71
|
+
# 18 Apr 2023
|
72
|
+
# Not all curves are supported under X509 I think. At least those not well known curve such as B-163 from BC
|
73
|
+
# not able to verify the signature
|
74
|
+
raise X509CSRSignatureInvalid, "CSR signature is not valid" if not csr.isSignatureValid(cvProv)
|
75
|
+
|
76
|
+
certProfile = Ccrypto::X509::CertProfile.new
|
77
|
+
|
78
|
+
@request_subject_full = csr.getSubject.to_s
|
79
|
+
subj = csr.getSubject.to_s
|
80
|
+
subj.split(",").each do |e|
|
81
|
+
ee = e.split("=")
|
82
|
+
case ee[0]
|
83
|
+
when "CN"
|
84
|
+
certProfile.owner_name = ee[1]
|
85
|
+
when "O"
|
86
|
+
certProfile.org = ee[1]
|
87
|
+
when "OU"
|
88
|
+
certProfile.org_unit = ee[1]
|
89
|
+
when "E"
|
90
|
+
certProfile.email = ee[1]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
pubKeyParam = org.bouncycastle.crypto.util.PublicKeyFactory.createKey(csr.subject_public_key_info)
|
96
|
+
curveName = org.bouncycastle.asn1.x9.ECNamedCurveTable.get_name(pubKeyParam.parameters.name)
|
97
|
+
|
98
|
+
spec = org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util.convertToSpec(pubKeyParam.getParameters)
|
99
|
+
point= org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util.convertPoint(pubKeyParam.getQ)
|
100
|
+
pubKeySpec = java.security.spec.ECPublicKeySpec.new(point, spec)
|
101
|
+
pubKey = java.security.KeyFactory.getInstance("EC", Ccrypto::Java::JCEProvider::DEFProv).generatePublic(pubKeySpec)
|
102
|
+
certProfile.public_key = Ccrypto::Java::ECCPublicKey.new(pubKey, curveName)
|
103
|
+
|
104
|
+
csr.attributes.each do |att|
|
105
|
+
|
106
|
+
ext = org.bouncycastle.asn1.x509.Extensions.getInstance(att.getAttrValues.getObjectAt(0))
|
107
|
+
|
108
|
+
gns = org.bouncycastle.asn1.x509.GeneralNames.fromExtensions(ext,org.bouncycastle.asn1.x509.Extension.subjectAlternativeName)
|
109
|
+
gns.getNames.each do |n|
|
110
|
+
#p n.getTagNo
|
111
|
+
case n.getTagNo
|
112
|
+
when org.bouncycastle.asn1.x509.GeneralName.dNSName
|
113
|
+
certProfile.dns_name = n.getName.to_s
|
114
|
+
when org.bouncycastle.asn1.x509.GeneralName.iPAddress
|
115
|
+
val = org.bouncycastle.asn1.DEROctetString.getInstance(n.getName.toASN1Primitive).getOctets
|
116
|
+
begin
|
117
|
+
certProfile.ip_addr = java.net.InetAddress.getByAddress(val).getHostAddress
|
118
|
+
rescue java.net.UnknownHostException => ex
|
119
|
+
certProfile.ip_addr = "Error decoding IP address : #{ex.message}"
|
120
|
+
teLogger.error "Failed to decode IP address from CSR"
|
121
|
+
teLogger.error ex.message
|
122
|
+
teLogger.error ex.backtrace.join("\n")
|
123
|
+
end
|
124
|
+
when org.bouncycastle.asn1.x509.GeneralName.uniformResourceIdentifier
|
125
|
+
certProfile.uri = n.getName.to_s
|
126
|
+
when org.bouncycastle.asn1.x509.GeneralName.rfc822Name
|
127
|
+
certProfile.email = n.getName.to_s
|
128
|
+
when org.bouncycastle.asn1.x509.GeneralName.otherName
|
129
|
+
ext = org.bouncycastle.asn1.x509.Extension.getInstance(n.getName)
|
130
|
+
certProfile.custom_extension[ext.extnId.to_s] = { value: ext.extnValue.octets.to_s, critical: ext.critical?, type: :string }
|
131
|
+
else
|
132
|
+
teLogger.debug "Unknown field tag no #{n.getTagNo}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
ext.oids.each do |o|
|
137
|
+
v = ext.getExtension(o)
|
138
|
+
next if v.extnId.to_s == org.bouncycastle.asn1.x509.Extension.subjectAlternativeName.to_s
|
139
|
+
|
140
|
+
certProfile.custom_extension[v.extnId.to_s] = { value: v.extnValue.octets.to_s, critical: v.critical?, type: :string }
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
certProfile
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
@@ -36,17 +36,6 @@ module Ccrypto
|
|
36
36
|
add_provider
|
37
37
|
end
|
38
38
|
|
39
|
-
#def set_default_provider(prov)
|
40
|
-
|
41
|
-
# case prov
|
42
|
-
# when String
|
43
|
-
# when java.security.Provider
|
44
|
-
# add_provider(prov) if not is_provider_registered?(prov)
|
45
|
-
# @defProvider = prov
|
46
|
-
# end
|
47
|
-
|
48
|
-
#end
|
49
|
-
|
50
39
|
end
|
51
40
|
end
|
52
41
|
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
|
2
|
+
require_relative 'keystore'
|
3
|
+
|
4
|
+
module Ccrypto
|
5
|
+
module Java
|
6
|
+
module Keystore
|
7
|
+
|
8
|
+
class JCEKeystore
|
9
|
+
include TR::CondUtils
|
10
|
+
include DataConversion
|
11
|
+
|
12
|
+
class KeystoreException < StandardError; end
|
13
|
+
|
14
|
+
def self.from_keystore(bin, storeType, &block)
|
15
|
+
|
16
|
+
raise KeystoreException, "block is required" if not block
|
17
|
+
|
18
|
+
logger = block.call(:logger) || TeLogger::TLogger.new(STDOUT)
|
19
|
+
|
20
|
+
prov = block.call(:jce_provider)
|
21
|
+
|
22
|
+
if not_empty?(prov)
|
23
|
+
logger.debug "Keystore type '#{storeType}' with provider #{prov}"
|
24
|
+
ks = java.security.KeyStore.getInstance(storeType, prov)
|
25
|
+
else
|
26
|
+
logger.debug "Keystore type '#{storeType}' with nil provider"
|
27
|
+
ks = java.security.KeyStore.getInstance(storeType)
|
28
|
+
end
|
29
|
+
|
30
|
+
pass = block.call(:store_pass)
|
31
|
+
name = block.call(:key_name)
|
32
|
+
|
33
|
+
inForm = block.call(:in_format)
|
34
|
+
case inForm
|
35
|
+
when :b64
|
36
|
+
inp = from_b64(bin)
|
37
|
+
when :hex
|
38
|
+
inp = from_hex(bin)
|
39
|
+
else
|
40
|
+
inp = bin
|
41
|
+
end
|
42
|
+
|
43
|
+
bbin = to_java_bytes(inp)
|
44
|
+
|
45
|
+
ks.load(java.io.ByteArrayInputStream.new(bbin),pass.to_java.toCharArray)
|
46
|
+
|
47
|
+
|
48
|
+
logger.debug "Aliases found from keystore : #{ks.aliases.to_a.join(", ")}"
|
49
|
+
logger.debug "Given key name : #{name}"
|
50
|
+
|
51
|
+
# on situation there are multiple aliases, which is an error condition
|
52
|
+
# all subsequent chain and userCert shall be nil
|
53
|
+
name = ks.aliases.to_a.first if is_empty?(name)
|
54
|
+
|
55
|
+
logger.debug "Alias used to select cert and key : #{name}"
|
56
|
+
|
57
|
+
userCert = Ccrypto::X509Cert.new(ks.getCertificate(name))
|
58
|
+
raise KeystoreException, "User certificate is nil. Alias used to retrieve user certificate is '#{name}'. Aliases detected from the keystore are: #{ks.aliases.to_a.join(", ")}" if userCert.nil?
|
59
|
+
chain = ks.get_certificate_chain(name)
|
60
|
+
if not chain.nil?
|
61
|
+
chain = chain.collect { |c| Ccrypto::X509Cert.new(c) }
|
62
|
+
else
|
63
|
+
chain = []
|
64
|
+
end
|
65
|
+
|
66
|
+
key = ks.getKey(name, pass.to_java.toCharArray)
|
67
|
+
raise KeystoreException, "Private key is nil. Alias used to retrieve private key is '#{name}'. Aliases detected from the keystore are: #{ks.aliases.to_a.join(", ")}" if key.nil?
|
68
|
+
kp = java.security.KeyPair.new(userCert.getPublicKey, key)
|
69
|
+
case key
|
70
|
+
when java.security.interfaces.ECPrivateKey
|
71
|
+
param = java.security.AlgorithmParameters.getInstance("EC")
|
72
|
+
param.init(key.params)
|
73
|
+
oid = param.getParameterSpec(java.security.spec.ECGenParameterSpec.java_class).name
|
74
|
+
curve = org.bouncycastle.asn1.x9.ECNamedCurveTable.getName(org.bouncycastle.asn1.ASN1ObjectIdentifier.new(oid))
|
75
|
+
logger.debug "Recover curve info : #{curve}"
|
76
|
+
conf = Ccrypto::Java::ECCEngine.find_curve(curve)
|
77
|
+
logger.debug "Found config : #{conf}"
|
78
|
+
[Ccrypto::Java::ECCKeyBundle.new(kp, conf), userCert, chain]
|
79
|
+
|
80
|
+
when org.bouncycastle.jcajce.provider.asymmetric.ec::BCECPrivateKey
|
81
|
+
curve = key.params.name
|
82
|
+
logger.debug "Recover curve info : #{curve}"
|
83
|
+
conf = Ccrypto::Java::ECCEngine.find_curve(curve)
|
84
|
+
logger.debug "Found config : #{conf}"
|
85
|
+
[Ccrypto::Java::ECCKeyBundle.new(kp, conf), userCert, chain]
|
86
|
+
when java.security.interfaces.RSAPrivateKey
|
87
|
+
[Ccrypto::Java::RSAKeyBundle.new(kp), userCert, chain]
|
88
|
+
else
|
89
|
+
raise KeystoreException, "Unknown key type #{key}"
|
90
|
+
end
|
91
|
+
|
92
|
+
end # from_keystore
|
93
|
+
|
94
|
+
def self.to_keystore(storeType, &block)
|
95
|
+
raise KeystoreException, "Block is required" if not block
|
96
|
+
|
97
|
+
logger = block.call(:logger) || TeLogger::TLogger.new(STDOUT)
|
98
|
+
|
99
|
+
logger.debug "storetype #{storeType}"
|
100
|
+
|
101
|
+
prov = block.call(:jce_provider)
|
102
|
+
|
103
|
+
pkcs12NewCipher = false
|
104
|
+
if storeType == "PKCS12"
|
105
|
+
pkcs12NewCipher = block.call(:pkcs12_new_cipher?)
|
106
|
+
if pkcs12NewCipher == true
|
107
|
+
storeType = "JKS"
|
108
|
+
prov = nil
|
109
|
+
logger.debug "PKCS12 new cipher is on. Write to JKS first."
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if not_empty?(prov)
|
114
|
+
logger.debug "Generating keystore of #{storeType} from provider #{prov}"
|
115
|
+
ks = java.security.KeyStore.getInstance(storeType, prov)
|
116
|
+
else
|
117
|
+
logger.debug "Generating keystore of #{storeType} using SUN provider"
|
118
|
+
ks = java.security.KeyStore.getInstance(storeType)
|
119
|
+
end
|
120
|
+
|
121
|
+
ks.load(nil,nil)
|
122
|
+
|
123
|
+
gcert = block.call(:cert)
|
124
|
+
raise KeystoreException, "#{storeType.upcase} requires the owner's X.509 certificate" if is_empty?(gcert)
|
125
|
+
|
126
|
+
ca = block.call(:cert_chain) || block.call(:certchain) || [gcert]
|
127
|
+
ca = [gcert] if ca.nil?
|
128
|
+
ca = ca.unshift(gcert) if not ca.first.equal?(gcert)
|
129
|
+
ca = ca.collect { |c|
|
130
|
+
Ccrypto::X509Cert.to_java_cert(c)
|
131
|
+
}
|
132
|
+
|
133
|
+
logger.debug "Cert Chain : #{ca.inspect}"
|
134
|
+
|
135
|
+
pass = block.call(:store_pass)
|
136
|
+
raise KeystoreException, "Password is required" if is_empty?(pass)
|
137
|
+
|
138
|
+
name = block.call(:key_name)
|
139
|
+
name = "Keystore for #{gcert.owner.name}" if is_empty?(name)
|
140
|
+
|
141
|
+
keypair = block.call(:keypair)
|
142
|
+
raise KeystoreException, "Keypair is required" if is_empty?(keypair)
|
143
|
+
|
144
|
+
logger.debug "Setting key entry with name : #{name}"
|
145
|
+
ks.setKeyEntry(name, keypair.private, pass.to_java.toCharArray, ca.to_java(java.security.cert.Certificate))
|
146
|
+
|
147
|
+
baos = java.io.ByteArrayOutputStream.new
|
148
|
+
ks.store(baos, pass.to_java.toCharArray)
|
149
|
+
res = baos.toByteArray
|
150
|
+
|
151
|
+
output = block.call(:output)
|
152
|
+
if not_empty?(output)
|
153
|
+
if pkcs12NewCipher
|
154
|
+
randName = SecureRandom.uuid
|
155
|
+
File.open(randName,"wb") do |f|
|
156
|
+
f.write res
|
157
|
+
end
|
158
|
+
|
159
|
+
# temporary until BC or JCE has way to completely support this to be able to load from OpenSSL
|
160
|
+
`keytool -importkeystore -alias "#{name}" -destalias "#{name}" -srckeystore #{randName} -destkeystore "#{output}" -srcstoretype JKS -deststoretype PKCS12 -srcstorepass "#{pass}" -deststorepass "#{pass}" -noprompt`
|
161
|
+
|
162
|
+
FileUtils.rm(randName)
|
163
|
+
else
|
164
|
+
File.open(output,"wb") do |f|
|
165
|
+
f.write res
|
166
|
+
end
|
167
|
+
end
|
168
|
+
else
|
169
|
+
outForm = block.call(:out_format)
|
170
|
+
case outForm
|
171
|
+
when :b64
|
172
|
+
bres = to_b64(res)
|
173
|
+
when :hex
|
174
|
+
bres = to_hex(res)
|
175
|
+
else
|
176
|
+
bres = res
|
177
|
+
end
|
178
|
+
|
179
|
+
if pkcs12NewCipher
|
180
|
+
randName = SecureRandom.uuid
|
181
|
+
File.open(randName,"wb") do |f|
|
182
|
+
f.write bres
|
183
|
+
end
|
184
|
+
|
185
|
+
# temporary until BC or JCE has way to completely support this to be able to load from OpenSSL
|
186
|
+
`keytool -importkeystore -alias "#{name}" -destalias "#{name}" -srckeystore #{randName} -destkeystore "#{output}" -srcstoretype JKS -deststoretype PKCS12 -srcstorepass "#{pass}" -deststorepass "#{pass}" -noprompt`
|
187
|
+
|
188
|
+
bcres = File.read(randName)
|
189
|
+
FileUtils.rm(randName)
|
190
|
+
|
191
|
+
bcres
|
192
|
+
else
|
193
|
+
bres
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end # to_keystore
|
199
|
+
|
200
|
+
|
201
|
+
end # class JCEKeystore
|
202
|
+
|
203
|
+
end # module Keystore
|
204
|
+
end # module Java
|
205
|
+
end # module Ccrypto
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
require_relative 'jce_keystore'
|
3
|
+
|
4
|
+
module Ccrypto
|
5
|
+
module Java
|
6
|
+
module Keystore
|
7
|
+
|
8
|
+
class JKSKeystore
|
9
|
+
include TR::CondUtils
|
10
|
+
include DataConversion
|
11
|
+
|
12
|
+
def self.from_jks(bin, &block)
|
13
|
+
|
14
|
+
raise Ccrypto::Keystore::KeystoreException, "block is required" if not block
|
15
|
+
|
16
|
+
JCEKeystore.from_keystore(bin, "JKS") do |k|
|
17
|
+
case k
|
18
|
+
when :logger
|
19
|
+
logger
|
20
|
+
else
|
21
|
+
block.call(k)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end # from_jks
|
26
|
+
|
27
|
+
def self.to_jks(&block)
|
28
|
+
|
29
|
+
raise Ccrypto::Keystore::KeystoreException, "Block is required" if not block
|
30
|
+
|
31
|
+
JCEKeystore.to_keystore("JKS") do |k|
|
32
|
+
case k
|
33
|
+
when :logger
|
34
|
+
logger
|
35
|
+
else
|
36
|
+
block.call(k)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end # to_jks
|
41
|
+
|
42
|
+
|
43
|
+
private
|
44
|
+
def self.logger
|
45
|
+
Ccrypto::Java.logger(:jks)
|
46
|
+
end
|
47
|
+
|
48
|
+
end # class PKCS12Keystore
|
49
|
+
|
50
|
+
end # module Keystore
|
51
|
+
end # module Java
|
52
|
+
end # module Ccrypto
|
@@ -0,0 +1,97 @@
|
|
1
|
+
|
2
|
+
require 'ccrypto/keystore'
|
3
|
+
require_relative 'pkcs12_keystore'
|
4
|
+
require_relative 'jks_keystore'
|
5
|
+
|
6
|
+
module Ccrypto
|
7
|
+
module Java
|
8
|
+
module Keystore
|
9
|
+
|
10
|
+
def self.map_keystore_type(key)
|
11
|
+
case key.to_sym
|
12
|
+
when :pkcs12, :p12, :PKCS12, :P12, :Pkcs12
|
13
|
+
:pkcs12
|
14
|
+
when :jks, :Jks, :JKS, :java
|
15
|
+
:jks
|
16
|
+
else
|
17
|
+
raise Ccrypto::Keystore::KeystoreException, "Unsupported keystore type '#{key}'"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.load_keystore(bin, eng, &block)
|
22
|
+
mEng = map_keystore_type(eng)
|
23
|
+
case mEng
|
24
|
+
when :pkcs12
|
25
|
+
PKCS12Keystore.from_p12(bin, &block)
|
26
|
+
when :jks
|
27
|
+
JKSKeystore.from_jks(bin, &block)
|
28
|
+
else
|
29
|
+
raise Ccrypto::Keystore::KeystoreException, "Unsupported keystore type '#{eng}'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.load_keystore_file(path, eng, &block)
|
34
|
+
raise Ccrypto::Keystore::KeystoreException, "Given keystore path to load '#{path}' does not exist" if not File.exist?(path)
|
35
|
+
raise Ccrypto::Keystore::KeystoreException, "Given keystore path to load '#{path}' cannot be read" if not File.readable?(path)
|
36
|
+
load_keystore(File.read(path), eng, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.convert_keystore(bin, fromEng, toEng, &block)
|
40
|
+
srcEng = map_keystore_type(fromEng)
|
41
|
+
mtEng = map_keystore_type(toEng)
|
42
|
+
kp, cert, chain = load_keystore(bin, srcEng) do |opt|
|
43
|
+
case opt
|
44
|
+
when :store_pass
|
45
|
+
block.call(:source_store_pass)
|
46
|
+
else
|
47
|
+
block.call(opt)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
outEng = map_keystore_type(mtEng)
|
52
|
+
case outEng
|
53
|
+
when :pkcs12
|
54
|
+
PKCS12Keystore.to_p12 do |k|
|
55
|
+
case k
|
56
|
+
when :keypair
|
57
|
+
kp
|
58
|
+
when :cert
|
59
|
+
cert
|
60
|
+
when :cert_chain
|
61
|
+
chain
|
62
|
+
when :store_pass
|
63
|
+
block.call(:dest_store_pass)
|
64
|
+
else
|
65
|
+
block.call(k)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
when :jks
|
69
|
+
JKSKeystore.to_jks do |k|
|
70
|
+
case k
|
71
|
+
when :keypair
|
72
|
+
kp
|
73
|
+
when :cert
|
74
|
+
cert
|
75
|
+
when :cert_chain
|
76
|
+
chain
|
77
|
+
when :store_pass
|
78
|
+
block.call(:dest_store_pass)
|
79
|
+
else
|
80
|
+
block.call(k)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
else
|
85
|
+
raise Ccrypto::Keystore::KeystoreException, "Unsupported keystore type '#{eng}'"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.convert_keystore_file(path, fromEng, toEng, &block)
|
90
|
+
raise Ccrypto::Keystore::KeystoreException, "Given keystore path to load '#{path}' does not exist" if not File.exist?(path)
|
91
|
+
raise Ccrypto::Keystore::KeystoreException, "Given keystore path to load '#{path}' cannot be read" if not File.readable?(path)
|
92
|
+
convert_keystore(File.read(path), fromEng, toEng, &block)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Ccrypto
|
4
|
+
module Java
|
5
|
+
module Keystore
|
6
|
+
|
7
|
+
class PEMKeystore
|
8
|
+
include TR::CondUtils
|
9
|
+
include DataConversion
|
10
|
+
|
11
|
+
def self.to_pem(&block)
|
12
|
+
|
13
|
+
raise KeystoreException, "Block is required" if not block
|
14
|
+
|
15
|
+
pass = block.call(:store_pass)
|
16
|
+
raise KeystoreException, "Password is required" if is_empty?(pass)
|
17
|
+
|
18
|
+
keypair = block.call(:keypair)
|
19
|
+
raise KeystoreException, "Keypair is required" if is_empty?(keypair)
|
20
|
+
|
21
|
+
output = block.call(:output)
|
22
|
+
if not_empty?(output)
|
23
|
+
ext = File.extname(output)
|
24
|
+
bname = File.basename(output, ext)
|
25
|
+
privPath = File.join("#{bname}_priv#{ext}")
|
26
|
+
pubPath = File.join("#{bname}_pub#{ext}")
|
27
|
+
|
28
|
+
privHead, privFoot, pubHead, pubFoot = marker(keypair)
|
29
|
+
File.open(privPath,"wb") do |f|
|
30
|
+
f.write privHead
|
31
|
+
f.write to_b64(keypair.private.to_bin)
|
32
|
+
f.write privFoot
|
33
|
+
end
|
34
|
+
|
35
|
+
File.open(pubPath,"wb") do |f|
|
36
|
+
f.write pubHead
|
37
|
+
f.write to_b64(keypair.public.to_bin)
|
38
|
+
f.write pubFoot
|
39
|
+
end
|
40
|
+
|
41
|
+
else
|
42
|
+
[to_b64(keypair.private.to_bin),to_b64(keypair.public.to_bin)]
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end # def to_pem
|
47
|
+
|
48
|
+
def self.from_pem(str)
|
49
|
+
|
50
|
+
raise KeystoreException, "block is required" if not block
|
51
|
+
|
52
|
+
case check_keytype(str)
|
53
|
+
when :ecc
|
54
|
+
cont = str.lines[1..-2].join
|
55
|
+
Ccrypto::Java::ECCPrivateKey.to_key(from_b64(cont))
|
56
|
+
when :rsa
|
57
|
+
when :ed25519
|
58
|
+
when :x25519
|
59
|
+
when :crystal_dilithium
|
60
|
+
when :crystal_kyber
|
61
|
+
else
|
62
|
+
raise KeystoreException, "Unable to derive keytype from input : '#{str}'"
|
63
|
+
end
|
64
|
+
|
65
|
+
case key
|
66
|
+
when java.security.interfaces.ECPrivateKey
|
67
|
+
param = java.security.AlgorithmParameters.getInstance("EC")
|
68
|
+
param.init(key.params)
|
69
|
+
oid = param.getParameterSpec(java.security.spec.ECGenParameterSpec.java_class).name
|
70
|
+
curve = org.bouncycastle.asn1.x9.ECNamedCurveTable.getName(org.bouncycastle.asn1.ASN1ObjectIdentifier.new(oid))
|
71
|
+
logger.debug "Recover curve info : #{curve}"
|
72
|
+
conf = Ccrypto::Java::ECCEngine.find_curve(curve)
|
73
|
+
logger.debug "Found config : #{conf}"
|
74
|
+
[Ccrypto::Java::ECCKeyBundle.new(kp, conf), userCert, chain]
|
75
|
+
|
76
|
+
when org.bouncycastle.jcajce.provider.asymmetric.ec::BCECPrivateKey
|
77
|
+
curve = key.params.name
|
78
|
+
logger.debug "Recover curve info : #{curve}"
|
79
|
+
conf = Ccrypto::Java::ECCEngine.find_curve(curve)
|
80
|
+
logger.debug "Found config : #{conf}"
|
81
|
+
[Ccrypto::Java::ECCKeyBundle.new(kp, conf), userCert, chain]
|
82
|
+
when java.security.interfaces.RSAPrivateKey
|
83
|
+
[Ccrypto::Java::RSAKeyBundle.new(kp), userCert, chain]
|
84
|
+
else
|
85
|
+
raise KeystoreException, "Unknown key type #{key}"
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
def self.logger
|
93
|
+
Ccrypto::Java.logger(:pem_ks)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.marker(keypair)
|
97
|
+
case keypair
|
98
|
+
when ECCKeybundle
|
99
|
+
keytype = "ECC"
|
100
|
+
when RSAKeybundle
|
101
|
+
keytype = "RSA"
|
102
|
+
when ED25519KeyBundle
|
103
|
+
keytype = "ED25519"
|
104
|
+
when X25519KeyBundle
|
105
|
+
keytype = "X25519"
|
106
|
+
when CrystalDilithiumKeyBundle
|
107
|
+
keytype = "CRYSTAL DILITHIUM"
|
108
|
+
when CrystalKyberKeyBundle
|
109
|
+
keytype = "CRYSTAL KYBER"
|
110
|
+
else
|
111
|
+
raise Error, "Unsupported keypair type '#{keypair.class.name}'"
|
112
|
+
end
|
113
|
+
|
114
|
+
[
|
115
|
+
"-----BEGIN #{keytype} PRIVATE KEY-----\n",
|
116
|
+
"\n-----END #{keytype} PRIVATE KEY-----\n",
|
117
|
+
"-----BEGIN #{keytype} PUBLIC KEY-----\n",
|
118
|
+
"\n-----END #{keytype} PUBLIC KEY-----\n",
|
119
|
+
]
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.check_keytype(cont)
|
123
|
+
|
124
|
+
case cont
|
125
|
+
when /ECC/
|
126
|
+
:ecc
|
127
|
+
when /RSA/
|
128
|
+
:rsa
|
129
|
+
when /ED25519/
|
130
|
+
:ed25519
|
131
|
+
when /X25519/
|
132
|
+
:x25519
|
133
|
+
when /DILITHIUM/
|
134
|
+
:crystal_dilithium
|
135
|
+
when /KYBER/
|
136
|
+
:crystal_kyber
|
137
|
+
else
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
|
141
|
+
end # def check_keytype
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|