Skip to content

Commit 2e7ff4d

Browse files
committed
[fix] handle X509::Name type conversion
resolve GH-206
1 parent b29cb61 commit 2e7ff4d

File tree

2 files changed

+327
-5
lines changed

2 files changed

+327
-5
lines changed

src/main/java/org/jruby/ext/openssl/X509Name.java

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,21 @@
4949
import org.bouncycastle.asn1.ASN1Primitive;
5050
import org.bouncycastle.asn1.ASN1String;
5151
import org.bouncycastle.asn1.BERTags;
52+
import org.bouncycastle.asn1.DERBMPString;
53+
import org.bouncycastle.asn1.DERBitString;
54+
import org.bouncycastle.asn1.DERGeneralString;
55+
import org.bouncycastle.asn1.DERGeneralizedTime;
56+
import org.bouncycastle.asn1.DERGraphicString;
57+
import org.bouncycastle.asn1.DERIA5String;
58+
import org.bouncycastle.asn1.DERNumericString;
59+
import org.bouncycastle.asn1.DEROctetString;
60+
import org.bouncycastle.asn1.DERPrintableString;
61+
import org.bouncycastle.asn1.DERT61String;
62+
import org.bouncycastle.asn1.DERUTCTime;
5263
import org.bouncycastle.asn1.DERUTF8String;
64+
import org.bouncycastle.asn1.DERUniversalString;
65+
import org.bouncycastle.asn1.DERVideotexString;
66+
import org.bouncycastle.asn1.DERVisibleString;
5367
import org.bouncycastle.asn1.DLSequence;
5468
import org.bouncycastle.asn1.DLSet;
5569
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
@@ -58,15 +72,13 @@
5872
import org.bouncycastle.asn1.x500.X500NameBuilder;
5973
import org.bouncycastle.asn1.x500.style.BCStyle;
6074
import org.bouncycastle.asn1.x509.X509DefaultEntryConverter;
61-
import org.bouncycastle.asn1.x509.X509NameEntryConverter;
6275

6376
import org.jruby.Ruby;
6477
import org.jruby.RubyArray;
6578
import org.jruby.RubyBoolean;
6679
import org.jruby.RubyClass;
6780
import org.jruby.RubyFixnum;
6881
import org.jruby.RubyHash;
69-
import org.jruby.RubyInteger;
7082
import org.jruby.RubyModule;
7183
import org.jruby.RubyNumeric;
7284
import org.jruby.RubyObject;
@@ -246,16 +258,61 @@ private void addType(final Ruby runtime, final ASN1Encodable value) {
246258
this.types.add(type);
247259
}
248260

261+
/**
262+
* @param oid
263+
* @param value
264+
* @param type expected to be legit at this point
265+
* @throws RuntimeException
266+
*/
249267
private void addEntry(ASN1ObjectIdentifier oid, RubyString value, final int type) throws RuntimeException {
250268
this.name = null;
251269
this.canonicalName = null;
270+
this.values.add(NAME_ENTRY_CONVERTER.convertValueFor(oid, value, type));
252271
this.oids.add(oid);
253-
this.values.add( getNameEntryConverted().getConvertedValue(oid, value.toString()) );
254272
this.types.add(type);
255273
}
256274

257-
private static X509NameEntryConverter getNameEntryConverted() {
258-
return new X509DefaultEntryConverter();
275+
private static final X509NameEntryConverterImpl NAME_ENTRY_CONVERTER = new X509NameEntryConverterImpl();
276+
277+
private static class X509NameEntryConverterImpl extends X509DefaultEntryConverter {
278+
279+
ASN1Primitive convertValueFor(final ASN1ObjectIdentifier oid, final RubyString value, final int type) {
280+
switch (type) {
281+
case ASN1.BIT_STRING:
282+
return new DERBitString(value.getBytes());
283+
case ASN1.OCTET_STRING:
284+
return new DEROctetString(value.getBytes());
285+
case ASN1.UTF8STRING:
286+
return new DERUTF8String(value.asJavaString());
287+
case ASN1.NUMERICSTRING:
288+
return new DERNumericString(value.asJavaString()); // validate?
289+
case ASN1.PRINTABLESTRING:
290+
return new DERPrintableString(value.asJavaString());
291+
case ASN1.T61STRING:
292+
return new DERT61String(value.asJavaString());
293+
case ASN1.VIDEOTEXSTRING:
294+
return new DERVideotexString(value.getBytes());
295+
case ASN1.IA5STRING:
296+
return new DERIA5String(value.asJavaString());
297+
case ASN1.GENERALIZEDTIME:
298+
return new DERGeneralizedTime(value.asJavaString());
299+
case ASN1.UTCTIME:
300+
return new DERUTCTime(value.asJavaString());
301+
case ASN1.GRAPHICSTRING:
302+
return new DERGraphicString(value.getBytes());
303+
//case ASN1.ISO64STRING:
304+
//return new DERVisibleString(value.asJavaString());
305+
case ASN1.GENERALSTRING:
306+
return new DERGeneralString(value.asJavaString());
307+
case ASN1.UNIVERSALSTRING:
308+
return new DERUniversalString(value.getBytes());
309+
case ASN1.BMPSTRING:
310+
return new DERBMPString(value.asJavaString());
311+
}
312+
313+
return super.getConvertedValue(oid, value.toString());
314+
}
315+
259316
}
260317

261318
@Override

src/test/ruby/x509/test_x509name.rb

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,269 @@ def test_hash_old
118118
assert_equal 3294068023, name.hash_old
119119
end
120120

121+
def setup
122+
super
123+
@obj_type_tmpl = Hash.new(OpenSSL::ASN1::PRINTABLESTRING)
124+
@obj_type_tmpl.update(OpenSSL::X509::Name::OBJECT_TYPE_TEMPLATE)
125+
end
126+
127+
def test_s_new
128+
dn = [ ["C", "JP"], ["O", "example"], ["CN", "www.example.jp"] ]
129+
name = OpenSSL::X509::Name.new(dn)
130+
ary = name.to_a
131+
assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s)
132+
assert_equal("C", ary[0][0])
133+
assert_equal("O", ary[1][0])
134+
assert_equal("CN", ary[2][0])
135+
assert_equal("JP", ary[0][1])
136+
assert_equal("example", ary[1][1])
137+
assert_equal("www.example.jp", ary[2][1])
138+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2])
139+
assert_equal(OpenSSL::ASN1::UTF8STRING, ary[1][2])
140+
assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
141+
142+
dn = [
143+
["countryName", "JP"],
144+
["organizationName", "example"],
145+
["commonName", "www.example.jp"]
146+
]
147+
name = OpenSSL::X509::Name.new(dn)
148+
ary = name.to_a
149+
assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s)
150+
assert_equal("C", ary[0][0])
151+
assert_equal("O", ary[1][0])
152+
assert_equal("CN", ary[2][0])
153+
assert_equal("JP", ary[0][1])
154+
assert_equal("example", ary[1][1])
155+
assert_equal("www.example.jp", ary[2][1])
156+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2])
157+
assert_equal(OpenSSL::ASN1::UTF8STRING, ary[1][2])
158+
assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
159+
160+
name = OpenSSL::X509::Name.new(dn, @obj_type_tmpl)
161+
ary = name.to_a
162+
assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s)
163+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2])
164+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[1][2])
165+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2])
166+
167+
dn = [
168+
["countryName", "JP", OpenSSL::ASN1::PRINTABLESTRING],
169+
["organizationName", "example", OpenSSL::ASN1::PRINTABLESTRING],
170+
["commonName", "www.example.jp", OpenSSL::ASN1::PRINTABLESTRING]
171+
]
172+
name = OpenSSL::X509::Name.new(dn)
173+
ary = name.to_a
174+
assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s)
175+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2])
176+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[1][2])
177+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2])
178+
179+
dn = [
180+
["DC", "org"],
181+
["DC", "ruby-lang"],
182+
["CN", "GOTOU Yuuzou"],
183+
["emailAddress", "[email protected]"],
184+
["serialNumber", "123"],
185+
]
186+
name = OpenSSL::X509::Name.new(dn)
187+
ary = name.to_a
188+
assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/[email protected]/serialNumber=123", name.to_s)
189+
assert_equal("DC", ary[0][0])
190+
assert_equal("DC", ary[1][0])
191+
assert_equal("CN", ary[2][0])
192+
assert_equal("emailAddress", ary[3][0])
193+
assert_equal("serialNumber", ary[4][0])
194+
assert_equal("org", ary[0][1])
195+
assert_equal("ruby-lang", ary[1][1])
196+
assert_equal("GOTOU Yuuzou", ary[2][1])
197+
assert_equal("[email protected]", ary[3][1])
198+
assert_equal("123", ary[4][1])
199+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
200+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
201+
assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
202+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[3][2])
203+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[4][2])
204+
205+
name_from_der = OpenSSL::X509::Name.new(name.to_der)
206+
assert_equal(name_from_der.to_s, name.to_s)
207+
assert_equal(name_from_der.to_a, name.to_a)
208+
assert_equal(name_from_der.to_der, name.to_der)
209+
end
210+
211+
def test_unrecognized_oid_parse_encode_equality
212+
dn = [ ["1.2.3.4.5.6.7.8.9.7.5.3.2", "Unknown OID1"],
213+
["1.1.2.3.5.8.13.21.35", "Unknown OID2"],
214+
["C", "US"],
215+
["postalCode", "60602"],
216+
["ST", "Illinois"],
217+
["L", "Chicago"],
218+
#["street", "123 Fake St"],
219+
["O", "Some Company LLC"],
220+
["CN", "mydomain.com"] ]
221+
222+
name1 = OpenSSL::X509::Name.new(dn)
223+
name2 = OpenSSL::X509::Name.parse(name1.to_s)
224+
assert_equal(name1.to_s, name2.to_s)
225+
assert_equal(name1.to_a, name2.to_a)
226+
end
227+
228+
def test_s_parse
229+
dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org/1.2.3.4.5.6=A=BCD"
230+
name = OpenSSL::X509::Name.parse(dn)
231+
assert_equal(dn, name.to_s)
232+
ary = name.to_a
233+
assert_equal [
234+
["DC", "org", OpenSSL::ASN1::IA5STRING],
235+
["DC", "ruby-lang", OpenSSL::ASN1::IA5STRING],
236+
["CN", "www.ruby-lang.org", OpenSSL::ASN1::UTF8STRING],
237+
["1.2.3.4.5.6", "A=BCD", OpenSSL::ASN1::UTF8STRING],
238+
], ary
239+
240+
dn2 = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org, 1.2.3.4.5.6=A=BCD"
241+
name = OpenSSL::X509::Name.parse(dn2)
242+
assert_equal(dn, name.to_s)
243+
assert_equal ary, name.to_a
244+
245+
name = OpenSSL::X509::Name.parse(dn2, @obj_type_tmpl)
246+
ary = name.to_a
247+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
248+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
249+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2])
250+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[3][2])
251+
end
252+
253+
def test_s_parse_rfc2253
254+
scanner = OpenSSL::X509::Name::RFC2253DN.method(:scan)
255+
256+
assert_equal([["C", "JP"]], scanner.call("C=JP"))
257+
assert_equal([
258+
["DC", "org"],
259+
["DC", "ruby-lang"],
260+
["CN", "GOTOU Yuuzou"],
261+
["emailAddress", "[email protected]"],
262+
],
263+
scanner.call(
264+
"[email protected],CN=GOTOU Yuuzou,"+
265+
"DC=ruby-lang,DC=org")
266+
)
267+
268+
dn = "CN=www.ruby-lang.org,DC=ruby-lang,DC=org"
269+
name = OpenSSL::X509::Name.parse_rfc2253(dn)
270+
assert_equal(dn, name.to_s(OpenSSL::X509::Name::RFC2253))
271+
ary = name.to_a
272+
assert_equal("DC", ary[0][0])
273+
assert_equal("DC", ary[1][0])
274+
assert_equal("CN", ary[2][0])
275+
assert_equal("org", ary[0][1])
276+
assert_equal("ruby-lang", ary[1][1])
277+
assert_equal("www.ruby-lang.org", ary[2][1])
278+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
279+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
280+
assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
281+
end
282+
283+
def test_add_entry
284+
dn = [
285+
["DC", "org"],
286+
["DC", "ruby-lang"],
287+
["CN", "GOTOU Yuuzou"],
288+
["emailAddress", "[email protected]"],
289+
["serialNumber", "123"],
290+
]
291+
name = OpenSSL::X509::Name.new
292+
dn.each{|attr| name.add_entry(*attr) }
293+
ary = name.to_a
294+
assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/[email protected]/serialNumber=123", name.to_s)
295+
assert_equal("DC", ary[0][0])
296+
assert_equal("DC", ary[1][0])
297+
assert_equal("CN", ary[2][0])
298+
assert_equal("emailAddress", ary[3][0])
299+
assert_equal("serialNumber", ary[4][0])
300+
assert_equal("org", ary[0][1])
301+
assert_equal("ruby-lang", ary[1][1])
302+
assert_equal("GOTOU Yuuzou", ary[2][1])
303+
assert_equal("[email protected]", ary[3][1])
304+
assert_equal("123", ary[4][1])
305+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
306+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
307+
assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
308+
assert_equal(OpenSSL::ASN1::IA5STRING, ary[3][2])
309+
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[4][2])
310+
end
311+
312+
def test_add_entry_street
313+
# openssl/crypto/objects/obj_mac.h 1.83
314+
dn = [
315+
["DC", "org"],
316+
["DC", "ruby-lang"],
317+
["CN", "GOTOU Yuuzou"],
318+
["emailAddress", "[email protected]"],
319+
["serialNumber", "123"],
320+
["street", "Namiki"],
321+
]
322+
name = OpenSSL::X509::Name.new
323+
dn.each{|attr| name.add_entry(*attr) }
324+
ary = name.to_a
325+
assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/[email protected]/serialNumber=123/street=Namiki", name.to_s)
326+
assert_equal("Namiki", ary[5][1])
327+
end
328+
329+
###
330+
331+
def test_integration
332+
key = OpenSSL::PKey::RSA.new(4096)
333+
334+
subject = "/C=FR/ST=IDF/L=PARIS/O=Company/CN=myhost.example"
335+
336+
cert = OpenSSL::X509::Certificate.new
337+
338+
fields = []
339+
OpenSSL::X509::Name.parse(subject).to_a.each do |field|
340+
fields << [field[0], field[1], OpenSSL::ASN1::PRINTABLESTRING]
341+
end
342+
343+
subject_x509 = OpenSSL::X509::Name.new(fields)
344+
345+
assert_equal '#<OpenSSL::X509::Name CN=myhost.example,O=Company,L=PARIS,ST=IDF,C=FR>', subject_x509.inspect
346+
347+
cert.subject = cert.issuer = subject_x509
348+
349+
cert.not_before = Time.now
350+
cert.not_after = Time.now + 365*24*60*60
351+
cert.public_key = key.public_key
352+
cert.serial = 0x0
353+
cert.version = 2
354+
355+
ef = OpenSSL::X509::ExtensionFactory.new
356+
ef.subject_certificate = ef.issuer_certificate = cert
357+
358+
cert.add_extension ef.create_extension('basicConstraints', 'CA:FALSE', true)
359+
cert.add_extension ef.create_extension('keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
360+
cert.add_extension ef.create_extension('subjectKeyIdentifier', 'hash')
361+
cert.add_extension ef.create_extension('authorityKeyIdentifier', 'keyid:always,issuer:always')
362+
363+
cert.sign key, OpenSSL::Digest::SHA256.new
364+
365+
asn1 = OpenSSL::ASN1.decode(cert.to_der)
366+
367+
print_asn_strings(asn1)
368+
end
369+
370+
private
371+
372+
def print_asn_strings(obj, depth = 0)
373+
if obj.respond_to? :each
374+
obj.each do |item|
375+
print_asn_strings(item, depth + 1)
376+
end
377+
else
378+
# printf("%-40s %s\n", obj.value, obj.class)
379+
assert_equal OpenSSL::ASN1::PrintableString, obj.class if (
380+
obj.class.to_s.match(/String/) && obj.class != OpenSSL::ASN1::BitString
381+
)
382+
end
383+
nil
384+
end
385+
121386
end

0 commit comments

Comments
 (0)