Skip to content

Commit 943a55d

Browse files
committed
Encode Netscape IA5String extensions correctly (fixes #349)
create_ext has no special case for nsComment (OID 2.16.840.1.113730.1.13) and other Netscape IA5String extensions, so they fall through to the generic else branch which double-wraps the value in OCTET STRINGs: OCTET STRING { OCTET STRING { raw ASCII } } CRuby's native OpenSSL bindings produce the correct encoding: OCTET STRING { IA5String "..." } The malformed encoding causes BouncyCastle to crash when parsing certificates containing these extensions, as it tries to interpret the raw ASCII bytes as ASN.1 structures. Add isNetscapeIA5StringExtension() covering all Netscape extensions defined as IA5String (nsBaseUrl, nsRevocationUrl, nsCaRevocationUrl, nsRenewalUrl, nsCaPolicyUrl, nsSslServerName, nsComment). nsCertType is excluded as it is a BIT STRING already handled separately.
1 parent a4b2a1a commit 943a55d

File tree

2 files changed

+46
-0
lines changed

2 files changed

+46
-0
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ else if (id.equals("2.5.29.31")) { // crlDistributionPoints
211211
else if (id.equals("1.3.6.1.5.5.7.1.1")) { // authorityInfoAccess
212212
value = parseAuthorityInfoAccess(valuex);
213213
}
214+
else if (isNetscapeIA5StringExtension(id)) {
215+
value = new DEROctetString(new DERIA5String(valuex).getEncoded(ASN1Encoding.DER));
216+
}
214217
else {
215218
value = new DEROctetString(new DEROctetString(ByteList.plain(valuex)).getEncoded(ASN1Encoding.DER));
216219
}
@@ -385,6 +388,25 @@ private DERBitString parseNsCertType(String oid, String valuex) {
385388
return new DERBitString(new byte[]{v}, unused);
386389
}
387390

391+
/**
392+
* Returns true for Netscape extensions whose value is an IA5String.
393+
* nsCertType (2.16.840.1.113730.1.1) is a BIT STRING handled separately.
394+
*/
395+
private static boolean isNetscapeIA5StringExtension(final String oid) {
396+
switch (oid) {
397+
case "2.16.840.1.113730.1.2": // nsBaseUrl
398+
case "2.16.840.1.113730.1.3": // nsRevocationUrl
399+
case "2.16.840.1.113730.1.4": // nsCaRevocationUrl
400+
case "2.16.840.1.113730.1.7": // nsRenewalUrl
401+
case "2.16.840.1.113730.1.8": // nsCaPolicyUrl
402+
case "2.16.840.1.113730.1.12": // nsSslServerName
403+
case "2.16.840.1.113730.1.13": // nsComment
404+
return true;
405+
default:
406+
return false;
407+
}
408+
}
409+
388410
private static DLSequence parseBasicConstrains(final String valuex) {
389411
final String[] val = StringHelper.split(valuex, ',');
390412
final ASN1EncodableVector vec = new ASN1EncodableVector();

src/test/ruby/x509/test_x509ext.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,30 @@ def test_subject_alt_name_sign_to_pem_through_cert
331331
assert_equal "DNS:test.example.com, DNS:test2.example.com, DNS:example.com, DNS:www.example.com", ext.value
332332
end
333333

334+
def test_ns_comment_der_encoding
335+
ef = OpenSSL::X509::ExtensionFactory.new
336+
ext = ef.create_extension("nsComment", "my test comment")
337+
338+
# Value round-trips correctly
339+
assert_equal "my test comment", ext.value
340+
341+
# Inner encoding must be IA5String (tag 0x16), not OCTET STRING (tag 0x04)
342+
seq = OpenSSL::ASN1.decode(ext.to_der)
343+
inner = OpenSSL::ASN1.decode(seq.value[-1].value)
344+
assert_instance_of OpenSSL::ASN1::IA5String, inner
345+
assert_equal "my test comment", inner.value
346+
end
347+
348+
def test_ns_base_url_der_encoding
349+
ef = OpenSSL::X509::ExtensionFactory.new
350+
ext = ef.create_extension("nsBaseUrl", "https://example.com/")
351+
352+
seq = OpenSSL::ASN1.decode(ext.to_der)
353+
inner = OpenSSL::ASN1.decode(seq.value[-1].value)
354+
assert_instance_of OpenSSL::ASN1::IA5String, inner
355+
assert_equal "https://example.com/", inner.value
356+
end
357+
334358
def subject_alt_name(domains)
335359
ef = OpenSSL::X509::ExtensionFactory.new
336360
ef.create_extension("subjectAltName", domains.split(',').map { |d| "DNS: #{d}" }.join(', '))

0 commit comments

Comments
 (0)