Summary
X509ExtensionFactory#create_ext has special-case handling for several Netscape extension OIDs (e.g. nsCertType at line 196), but nsComment (OID 2.16.840.1.113730.1.13) is not handled and falls through to the else branch at line 214-215:
value = new DEROctetString(new DEROctetString(ByteList.plain(valuex)).getEncoded(ASN1Encoding.DER));
This produces a double-wrapped OCTET STRING containing raw ASCII bytes:
OCTET STRING { OCTET STRING { raw ASCII } }
DER: 04 2A 04 28 50 75 70 70 65 74 ...
CRuby's OpenSSL bindings correctly produce an IA5String via X509V3_EXT_nconf():
OCTET STRING { IA5String "Puppet Server Internal Certificate" }
DER: 04 24 16 22 50 75 70 70 65 74 ...
Impact
When a certificate with this malformed nsComment is parsed by BouncyCastle (e.g. during mTLS authentication in puppetserver), it crashes with:
java.io.IOException: corrupted stream - out of bounds length found: 117 >= 34
This happens because BouncyCastle successfully parses the outer OCTET STRING, then tries to interpret the inner raw ASCII bytes as ASN.1. The byte 0x50 (P) is interpreted as an ASN.1 tag and 0x75 (u = 117) as a length field, but only 34 bytes remain.
How we found this
We are building a Kubernetes operator (openvox-operator) that runs puppetserver ca setup inside the JRuby runtime of the puppetserver process, rather than via the CRuby-based CLI. This means the certificate extensions are created through JRuby-OpenSSL instead of CRuby's native OpenSSL bindings.
The generated CA server certificate (puppet.pem) contains an nsComment extension set by openvoxserver-ca:
["nsComment", "Puppet Server Internal Certificate", false]
When the operator later calls the PUT /puppet-ca/v1/certificate_status/{certname} endpoint to sign certificates via mTLS, the CA parses the client certificate extensions and hits the BouncyCastle crash.
As a workaround, we patch jvm-ssl-utils at build time to add a try-catch fallback in asn1ObjToObj (see slauger/openvox-operator#148).
How to reproduce
require 'openssl'
ef = OpenSSL::X509::ExtensionFactory.new
ext = ef.create_extension('nsComment', 'Puppet Server Internal Certificate', false)
puts ext.to_der.bytes.map { |b| '%02x' % b }.join(' ')
Under CRuby: the DER contains 16 22 (IA5String tag + length).
Under JRuby: the DER contains 04 28 (OCTET STRING tag + length) with raw ASCII.
Suggested fix
Add a case for nsComment (MiscObjectIdentifiers.netscapeCertComment) in the OID switch in create_ext, similar to the existing nsCertType handling, encoding the value as DERIA5String instead of falling through to the generic OCTET STRING path.
Summary
X509ExtensionFactory#create_exthas special-case handling for several Netscape extension OIDs (e.g.nsCertTypeat line 196), butnsComment(OID2.16.840.1.113730.1.13) is not handled and falls through to theelsebranch at line 214-215:This produces a double-wrapped OCTET STRING containing raw ASCII bytes:
CRuby's OpenSSL bindings correctly produce an IA5String via
X509V3_EXT_nconf():Impact
When a certificate with this malformed nsComment is parsed by BouncyCastle (e.g. during mTLS authentication in puppetserver), it crashes with:
This happens because BouncyCastle successfully parses the outer OCTET STRING, then tries to interpret the inner raw ASCII bytes as ASN.1. The byte
0x50(P) is interpreted as an ASN.1 tag and0x75(u= 117) as a length field, but only 34 bytes remain.How we found this
We are building a Kubernetes operator (openvox-operator) that runs
puppetserver ca setupinside the JRuby runtime of the puppetserver process, rather than via the CRuby-based CLI. This means the certificate extensions are created through JRuby-OpenSSL instead of CRuby's native OpenSSL bindings.The generated CA server certificate (
puppet.pem) contains an nsComment extension set by openvoxserver-ca:When the operator later calls the
PUT /puppet-ca/v1/certificate_status/{certname}endpoint to sign certificates via mTLS, the CA parses the client certificate extensions and hits the BouncyCastle crash.As a workaround, we patch jvm-ssl-utils at build time to add a try-catch fallback in
asn1ObjToObj(see slauger/openvox-operator#148).How to reproduce
Under CRuby: the DER contains
16 22(IA5String tag + length).Under JRuby: the DER contains
04 28(OCTET STRING tag + length) with raw ASCII.Suggested fix
Add a case for
nsComment(MiscObjectIdentifiers.netscapeCertComment) in the OID switch increate_ext, similar to the existingnsCertTypehandling, encoding the value asDERIA5Stringinstead of falling through to the generic OCTET STRING path.