/***** BEGIN LICENSE BLOCK ***** * Version: EPL 1.0/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Eclipse Public * License Version 1.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.eclipse.org/legal/epl-v10.html * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * Copyright (C) 2006, 2007 Ola Bini <ola@ologix.com> * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the EPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the EPL, the GPL or the LGPL. ***** END LICENSE BLOCK *****/ package org.jruby.ext.openssl; import java.io.IOException; import java.lang.reflect.Field; import java.util.Hashtable; import java.util.Map; import org.bouncycastle.asn1.ASN1Boolean; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1String; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.BERTags; import org.bouncycastle.asn1.DERBoolean; import org.bouncycastle.asn1.DERIA5String; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERUniversalString; import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.asn1.x500.AttributeTypeAndValue; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.util.encoders.Hex; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; import org.jruby.RubyHash; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import org.jruby.util.ConvertBytes; import static org.jruby.ext.openssl.ASN1._ASN1; import static org.jruby.ext.openssl.X509._X509; import static org.jruby.ext.openssl.OpenSSL.*; import static org.jruby.ext.openssl.StringHelper.*; /** * OpenSSL::X509::Extension * @author kares */ public class X509Extension extends RubyObject { private static final long serialVersionUID = 6463713017143658305L; private static ObjectAllocator ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new X509Extension(runtime, klass); } }; public static void createX509Extension(final Ruby runtime, final RubyModule _X509) { // OpenSSL::X509 final RubyClass _OpenSSLError = runtime.getModule("OpenSSL").getClass("OpenSSLError"); _X509.defineClassUnder("ExtensionError", _OpenSSLError, _OpenSSLError.getAllocator()); RubyClass _Extension = _X509.defineClassUnder("Extension", runtime.getObject(), X509Extension.ALLOCATOR); _Extension.defineAnnotatedMethods(X509Extension.class); X509ExtensionFactory.createX509ExtensionFactory(runtime, _X509); } private ASN1ObjectIdentifier objectID; private Object value; private boolean critical; protected X509Extension(Ruby runtime, RubyClass type) { super(runtime, type); } static RubyClass _Extension(final Ruby runtime) { return _X509(runtime).getClass("Extension"); } static final byte[] critical__ = new byte[] { 'c','r','i','t','i','c','a','l',',',' ' }; static X509Extension newExtension(final ThreadContext context, final String oid, final java.security.cert.X509Extension ext, final boolean critical) throws IOException { final byte[] extValue = ext.getExtensionValue(oid); // DER encoded // TODO: wired. J9 returns null for an OID given in getNonCriticalExtensionOIDs() if ( extValue == null ) { warn(context, ext + " getExtensionValue returns null for '"+ oid +"'"); return null; } final Ruby runtime = context.runtime; final ASN1Encodable value = ASN1.readObject(extValue); return newExtension(runtime, ASN1.getObjectID(runtime, oid), value, critical); } static X509Extension[] newExtension(final ThreadContext context, final String oid, final byte[] extValue, final boolean critical) throws IOException { final Ruby runtime = context.runtime; final ASN1ObjectIdentifier objectId = ASN1.getObjectID(runtime, oid); final ASN1Encodable value = ASN1.readObject(extValue); if ( oid.equals("2.5.29.17") || oid.equals("2.5.29.18") ) { // subjectAltName || issuerAltName if ( value instanceof ASN1OctetString ) { // DEROctetString final ASN1Encodable oct = ASN1.readObject( ((ASN1OctetString) value).getOctets() ); if ( oct instanceof ASN1Sequence ) { final ASN1Sequence seq = (ASN1Sequence) oct; final X509Extension[] ext = new X509Extension[ seq.size() ]; for ( int i = 0; i < ext.length; i++ ) { ext[i] = newExtension(runtime, objectId, seq.getObjectAt(i), critical); } return ext; } // NOTE need to unwrap ((ASN1TaggedObject) oct).getObject() - likely not ?!? return new X509Extension[] { newExtension(runtime, objectId, oct, critical) }; } } return new X509Extension[] { newExtension(runtime, objectId, value, critical) }; } static X509Extension newExtension(final Ruby runtime, ASN1ObjectIdentifier objectId, final ASN1Encodable value, final boolean critical) { X509Extension ext = new X509Extension(runtime, _Extension(runtime)); ext.setRealObjectID(objectId); ext.setRealValue(value); ext.setRealCritical(critical); return ext; } static X509Extension newExtension(final Ruby runtime, ASN1ObjectIdentifier objectId, final Extension extension) { X509Extension ext = new X509Extension(runtime, _Extension(runtime)); ext.setRealObjectID(objectId); ext.setRealValue(extension.getParsedValue()); ext.setRealCritical(extension.isCritical()); return ext; } ASN1ObjectIdentifier getRealObjectID() { return objectID; } void setRealObjectID(ASN1ObjectIdentifier oid) { this.objectID = oid; } void setRealObjectID(final String oid) { setRealObjectID( ASN1.getObjectID(getRuntime(), oid) ); } final ASN1Encodable getRealValue() throws IOException { if ( value instanceof ASN1Encodable ) { return (ASN1Encodable) value; } if ( value instanceof ASN1.ASN1Data ) { return ((ASN1.ASN1Data) value).toASN1(getRuntime().getCurrentContext()); } if ( value == null ) throw new IllegalStateException("null extension value"); return ASN1.readObject( getRealValueEncoded() ); } final byte[] getRealValueEncoded() throws IOException { if ( value instanceof byte[] ) return (byte[]) value; if ( value instanceof RubyString ) return ((RubyString) value).getBytes(); if ( value instanceof String ) return ByteList.plain((String) value); if ( value instanceof ASN1OctetString ) { // initialize return ((ASN1OctetString) value).getOctets(); } return getRealValue().toASN1Primitive().getEncoded(ASN1Encoding.DER); } private IRubyObject getValue(final Ruby runtime) throws IOException { if ( value instanceof RubyString ) { return (RubyString) value; // explicitly set value } final ThreadContext context = runtime.getCurrentContext(); final byte[] enc = getRealValueEncoded(); IRubyObject extValue = runtime.newString( new ByteList(enc, false) ); extValue = ASN1.decodeImpl(context, _ASN1(runtime), extValue); return extValue.callMethod(context, "value"); } void setRealValue(final ASN1Encodable value) { if ( value == null ) { throw new IllegalStateException("null extension value"); } this.value = value; } //private void setRealValueEncoded(final byte[] value) { // this.value = value; //} boolean isRealCritical() { return critical; } void setRealCritical(boolean critical) { this.critical = critical; } @JRubyMethod(name = "initialize", rest = true, visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { if ( args.length == 1 ) { final byte[] bytes = to_der_if_possible(context, args[0]).asString().getBytes(); try { ASN1Sequence seq = (ASN1Sequence) ASN1.readObject(bytes); setRealObjectID( (ASN1ObjectIdentifier) seq.getObjectAt(0) ); final ASN1Encodable criticalOrValue = seq.getObjectAt(1); if ( criticalOrValue instanceof ASN1Boolean ) { setRealCritical( ((ASN1Boolean) criticalOrValue).isTrue() ); this.value = ( (DEROctetString) seq.getObjectAt(2) ).getOctets(); // byte[] } else if ( criticalOrValue instanceof DERBoolean ) { // NOTE: keep it due BC <= 1.50 setRealCritical( ((DERBoolean) criticalOrValue).isTrue() ); this.value = ( (DEROctetString) seq.getObjectAt(2) ).getOctets(); // byte[] } else { this.value = ( (DEROctetString) criticalOrValue ).getOctets(); // byte[] } } catch (IOException e) { throw newExtensionError(context.runtime, e); } } else if ( args.length > 1 ) { setRealObjectID( ASN1.getObjectID(context.runtime, args[0].toString()) ); this.value = args[1]; // a RubyString } else { // args.length < 1 throw context.runtime.newArgumentError("wrong number of arguments (0 for 1..3)"); } if ( args.length > 2 ) setRealCritical( args[2].isTrue() ); return this; } @Override public IRubyObject initialize_copy(final IRubyObject original) { if (this == original) return this; checkFrozen(); final X509Extension that = (X509Extension) original; this.value = that.value; this.objectID = that.objectID; this.critical = that.critical; return this; } @JRubyMethod public IRubyObject oid(final ThreadContext context) { return context.runtime.newString( oidSym(context.runtime) ); } private String oidSym(final Ruby runtime) { final String name = ASN1.oid2Sym(runtime, objectID, true); return name == null ? objectID.toString() : name; } @JRubyMethod(name = "oid=") public IRubyObject set_oid(final ThreadContext context, IRubyObject arg) { if ( arg instanceof RubyString ) { setRealObjectID( arg.toString() ); return arg; } throw context.runtime.newTypeError(arg, context.runtime.getString()); } private static final byte[] CA_ = {'C', 'A', ':'}; private static final byte[] TRUE = {'T', 'R', 'U', 'E'}; private static final byte[] FALSE = {'F', 'A', 'L', 'S', 'E'}; private static final byte[] _ = {}; private static final byte[] SEP = {',', ' '}; private static final byte[] Decipher_Only = {'D', 'e', 'c', 'i', 'p', 'h', 'e', 'r', ' ', 'O', 'n', 'l', 'y'}; private static final byte[] Digital_Signature = {'D', 'i', 'g', 'i', 't', 'a', 'l', ' ', 'S', 'i', 'g', 'n', 'a', 't', 'u', 'r', 'e'}; private static final byte[] Non_Repudiation = {'N', 'o', 'n', ' ', 'R', 'e', 'p', 'u', 'd', 'i', 'a', 't', 'i', 'o', 'n'}; private static final byte[] Key_Encipherment = {'K', 'e', 'y', ' ', 'E', 'n', 'c', 'i', 'p', 'h', 'e', 'r', 'm', 'e', 'n', 't'}; private static final byte[] Data_Encipherment = {'D', 'a', 't', 'a', ' ', 'E', 'n', 'c', 'i', 'p', 'h', 'e', 'r', 'm', 'e', 'n', 't'}; private static final byte[] Key_Agreement = {'K', 'e', 'y', ' ', 'A', 'g', 'r', 'e', 'e', 'm', 'e', 'n', 't'}; private static final byte[] Certificate_Sign = {'C', 'e', 'r', 't', 'i', 'f', 'i', 'c', 'a', 't', 'e', ' ', 'S', 'i', 'g', 'n'}; private static final byte[] CRL_Sign = {'C', 'R', 'L', ' ', 'S', 'i', 'g', 'n'}; private static final byte[] Encipher_Only = {'E', 'n', 'c', 'i', 'p', 'h', 'e', 'r', ' ', 'O', 'n', 'l', 'y'}; private static final byte[] SSL_Client = {'S', 'S', 'L', ' ', 'C', 'l', 'i', 'e', 'n', 't'}; private static final byte[] SSL_Server = {'S', 'S', 'L', ' ', 'S', 'e', 'r', 'v', 'e', 'r'}; private static final byte[] SSL_CA = {'S', 'S', 'L', ' ', 'C', 'A'}; private static final byte[] SMIME = {'S', '/', 'M', 'I', 'M', 'E'}; private static final byte[] SMIME_CA = {'S', '/', 'M', 'I', 'M', 'E', ' ', 'C', 'A'}; private static final byte[] Object_Signing = {'O', 'b', 'j', 'e', 'c', 't', ' ', 'S', 'i', 'g', 'n', 'i', 'n', 'g'}; private static final byte[] Object_Signing_CA = {'O', 'b', 'j', 'e', 'c', 't', ' ', 'S', 'i', 'g', 'n', 'i', 'n', 'g', ' ', 'C', 'A'}; private static final byte[] Unused = {'U', 'n', 'u', 's', 'e', 'd'}; private static final byte[] Unspecified = {'U', 'n', 's', 'p', 'e', 'c', 'i', 'f', 'i', 'e', 'd'}; //private static final byte[] Key_Compromise = { 'K','e','y',' ','C','o','m','p','r','o','m','i','s','e' }; //private static final byte[] CA_Compromise = { 'C','A',' ','C','o','m','p','r','o','m','i','s','e' }; //private static final byte[] Affiliation_Changed = { 'A','f','f','i','l','i','a','t','i','o','n',' ','C','h','a','n','g','e','d' }; private static final byte[] keyid_ = {'k', 'e', 'y', 'i', 'd', ':'}; @JRubyMethod public RubyString value(final ThreadContext context) { if ( this.value instanceof RubyString ) { // return the same as set return (RubyString) this.value; } final Ruby runtime = context.runtime; final String oid = getRealObjectID().getId(); try { if ( oid.equals("2.5.29.19") ) { // basicConstraints ASN1Sequence seq2 = (ASN1Sequence) ASN1.readObject( getRealValueEncoded() ); final ByteList val = new ByteList(32); if (seq2.size() > 0) { val.append(CA_); ASN1Encodable obj0 = seq2.getObjectAt(0); final boolean bool; if ( obj0 instanceof ASN1Boolean ) { bool = ((ASN1Boolean) obj0).isTrue(); } else { // NOTE: keep it due BC <= 1.50 bool = ((DERBoolean) obj0).isTrue(); } val.append( bool ? TRUE : FALSE ); } if (seq2.size() > 1) { val.append(", pathlen:".getBytes()); val.append(seq2.getObjectAt(1).toString().getBytes()); } return runtime.newString(val); } if ( oid.equals("2.5.29.15") ) { // keyUsage final byte[] enc = getRealValueEncoded(); byte b3 = 0; byte b2 = enc[2]; if ( enc.length > 3 ) b3 = enc[3]; final ByteList val = new ByteList(64); byte[] sep = _; if ((b2 & (byte) 128) != 0) { val.append(sep); val.append(Decipher_Only); sep = SEP; } if ((b3 & (byte) 128) != 0) { val.append(sep); val.append(Digital_Signature); sep = SEP; } if ((b3 & (byte) 64) != 0) { val.append(sep); val.append(Non_Repudiation); sep = SEP; } if ((b3 & (byte) 32) != 0) { val.append(sep); val.append(Key_Encipherment); sep = SEP; } if ((b3 & (byte) 16) != 0) { val.append(sep); val.append(Data_Encipherment); sep = SEP; } if ((b3 & (byte) 8) != 0) { val.append(sep); val.append(Key_Agreement); sep = SEP; } if ((b3 & (byte) 4) != 0) { val.append(sep); val.append(Certificate_Sign); sep = SEP; } if ((b3 & (byte) 2) != 0) { val.append(sep); val.append(CRL_Sign); sep = SEP; } if ((b3 & (byte) 1) != 0) { val.append(sep); val.append(Encipher_Only); // sep = SEP; } return runtime.newString(val); } if ( oid.equals("2.16.840.1.113730.1.1") ) { // nsCertType final byte b0 = getRealValueEncoded()[0]; final ByteList val = new ByteList(64); byte[] sep = _; if ((b0 & (byte) 128) != 0) { val.append(sep); val.append(SSL_Client); sep = SEP; } if ((b0 & (byte) 64) != 0) { val.append(sep); val.append(SSL_Server); sep = SEP; } if ((b0 & (byte) 32) != 0) { val.append(sep); val.append(SMIME); sep = SEP; } if ((b0 & (byte) 16) != 0) { val.append(sep); val.append(Object_Signing); sep = SEP; } if ((b0 & (byte) 8) != 0) { val.append(sep); val.append(Unused); sep = SEP; } if ((b0 & (byte) 4) != 0) { val.append(sep); val.append(SSL_CA); sep = SEP; } if ((b0 & (byte) 2) != 0) { val.append(sep); val.append(SMIME_CA); sep = SEP; } if ((b0 & (byte) 1) != 0) { val.append(sep); val.append(Object_Signing_CA); } return runtime.newString(val); } if ( oid.equals("2.5.29.14") ) { // subjectKeyIdentifier ASN1Encodable value = getRealValue(); if ( value instanceof ASN1OctetString ) { byte[] octets = ((ASN1OctetString) value).getOctets(); if ( octets.length > 0 && octets[0] == BERTags.OCTET_STRING ) { value = ASN1.readObject( octets ); // read nested octets } } return runtime.newString( hexBytes( keyidBytes(value.toASN1Primitive()), 0 ) ); } if ( oid.equals("2.5.29.35") ) { // authorityKeyIdentifier ASN1Encodable value = getRealValue(); if ( value instanceof ASN1OctetString ) { value = ASN1.readObject( ((ASN1OctetString) value).getOctets() ); } final ByteList val = new ByteList(72); val.append(keyid_); if ( value instanceof ASN1Sequence ) { final ASN1Sequence seq = (ASN1Sequence) value; final int size = seq.size(); if ( size == 0 ) return RubyString.newEmptyString(runtime); ASN1Primitive keyid = seq.getObjectAt(0).toASN1Primitive(); hexBytes( keyidBytes(keyid), val ).append('\n'); for ( int i = 1; i < size; i++ ) { final ASN1Encodable issuer = seq.getObjectAt(i); // NOTE: blindly got OpenSSL tests passing (likely in-complete) : if ( issuer instanceof ASN1TaggedObject ) { ASN1Primitive obj = ((ASN1TaggedObject) issuer).getObject(); switch( ((ASN1TaggedObject) issuer).getTagNo() ) { case 1 : if ( obj instanceof ASN1TaggedObject ) { formatGeneralName(GeneralName.getInstance(obj), val, true); } break; case 2 : // serial val.append(new byte[] { 's','e','r','i','a','l',':' }); hexBytes( ((ASN1OctetString) obj).getOctets(), val ); break; } } val.append('\n'); } return runtime.newString( val ); } hexBytes( keyidBytes(value.toASN1Primitive()), val ).append('\n'); return runtime.newString( val ); } if ( oid.equals("2.5.29.21") ) { // CRLReason final IRubyObject value = getValue(runtime); switch ( RubyNumeric.fix2int(value) ) { case 0: return runtime.newString(new ByteList(Unspecified)); case 1: return RubyString.newString(runtime, "Key Compromise"); case 2: return RubyString.newString(runtime, "CA Compromise"); case 3: return RubyString.newString(runtime, "Affiliation Changed"); case 4: return RubyString.newString(runtime, "Superseded"); case 5: return RubyString.newString(runtime, "Cessation Of Operation"); case 6: return RubyString.newString(runtime, "Certificate Hold"); case 8: return RubyString.newString(runtime, "Remove From CRL"); case 9: return RubyString.newString(runtime, "Privilege Withdrawn"); default: return runtime.newString(new ByteList(Unspecified)); } } if ( oid.equals("2.5.29.17") || oid.equals("2.5.29.18") ) { // subjectAltName || issuerAltName try { ASN1Encodable value = getRealValue(); final ByteList val = new ByteList(64); if ( value instanceof ASN1TaggedObject ) { formatGeneralName(GeneralName.getInstance(value), val, false); return runtime.newString( val ); } if ( value instanceof GeneralName ) { formatGeneralName((GeneralName) value, val, false); return runtime.newString( val ); } if ( value instanceof ASN1OctetString ) { // decoded octets will end up as an ASN1Sequence instance : value = ASN1.readObject( ((ASN1OctetString) value).getOctets()); } if ( value instanceof ASN1TaggedObject ) { // DERTaggedObject (issuerAltName wrapping) formatGeneralName(GeneralName.getInstance(value), val, false); return runtime.newString( val ); } final GeneralName[] names = GeneralNames.getInstance(value).getNames(); for ( int i = 0; i < names.length; i++ ) { boolean other = formatGeneralName(names[i], val, false); if ( i < names.length - 1 ) { if ( other ) val.append(';'); else val.append(','); } } return runtime.newString( val ); } catch (IllegalArgumentException e) { debugStackTrace(runtime, e); return rawValueAsString(context); } } if ( oid.equals("2.5.29.37") ) { // extendedKeyUsage final ByteList val = new ByteList(64); if ( this.value instanceof ASN1Sequence ) { // opt "short" path final ASN1Sequence seq = (ASN1Sequence) this.value; final int size = seq.size(); for ( int i = 0; i < size; i++ ) { ASN1Encodable o = seq.getObjectAt(i); String name = o.toString(); Integer nid = ASN1.oid2nid(runtime, new ASN1ObjectIdentifier(name)); if ( nid != null ) name = ASN1.nid2ln(runtime, nid); if ( name == null ) name = o.toString(); val.append( ByteList.plain(name) ); if ( i < size - 1 ) val.append(',').append(' '); } return runtime.newString( val ); } final IRubyObject value = getValue(runtime); if ( value instanceof RubyArray ) { final RubyArray arr = (RubyArray) value; final int size = arr.size(); for ( int i = 0; i < size; i++ ) { IRubyObject entry = arr.eltInternal(i); if ( "ObjectId".equals(entry.getMetaClass().getBaseName()) ) { entry = entry.callMethod(context, "ln"); } else if ( entry.respondsTo("value") ) { entry = entry.callMethod(context, "value"); } val.append( entry.asString().getByteList() ); if ( i < size - 1 ) val.append(',').append(' '); } } return runtime.newString( val ); } return rawValueAsString(context); } catch (IOException e) { debugStackTrace(runtime, e); throw newExtensionError(runtime, e); } } private RubyString rawValueAsString(final ThreadContext context) throws IOException { final Ruby runtime = context.runtime; final IRubyObject value = getValue(runtime); // e.g. [ ASN1::UTF8String, ... ] if ( value instanceof RubyArray ) { final RubyArray arr = (RubyArray) value; final ByteList strVal = new ByteList(64); final int len = arr.size(); for ( int i = 0; i < len; i++ ) { IRubyObject entry = arr.eltInternal(i); if ( entry.respondsTo("value") ) { entry = entry.callMethod(context, "value"); } strVal.append( entry.asString().getByteList() ); if ( i < len - 1 ) strVal.append(',').append(' '); } return runtime.newString(strVal); } return value.asString(); } private static byte[] keyidBytes(ASN1Primitive keyid) throws IOException { if ( keyid instanceof ASN1TaggedObject ) { keyid = ((ASN1TaggedObject) keyid).getObject(); } if ( keyid instanceof ASN1OctetString ) { return ((ASN1OctetString) keyid).getOctets(); } return keyid.getEncoded(ASN1Encoding.DER); } @SuppressWarnings("unchecked") private static boolean formatGeneralName(final GeneralName name, final ByteList out, final boolean slashed) { final ASN1Encodable obj = name.getName(); String val; boolean tagged = false; switch ( name.getTagNo() ) { case GeneralName.rfc822Name: if ( ! tagged ) out.append('e').append('m').append('a').append('i').append('l'). append(':'); tagged = true; case GeneralName.dNSName: if ( ! tagged ) out.append('D').append('N').append('S'). append(':'); tagged = true; case GeneralName.uniformResourceIdentifier: if ( ! tagged ) out.append('U').append('R').append('I'). append(':'); val = DERIA5String.getInstance(obj).getString(); out.append( ByteList.plain(val) ); break; case GeneralName.directoryName: out.append('D').append('i').append('r').append('N').append('a').append('m').append('e'). append(':'); final X500Name dirName = X500Name.getInstance(obj); if ( slashed ) { final RDN[] rdns = dirName.getRDNs(); final Hashtable defaultSymbols = getDefaultSymbols(); for (int i = 0; i < rdns.length; i++) { appendRDN(out.append('/'), rdns[i], defaultSymbols); } } else { out.append( ByteList.plain(dirName.toString()) ); } break; case GeneralName.iPAddress: out.append('I').append('P'). append(':'); final byte[] ip = ((ASN1OctetString) name.getName()).getOctets(); int len = ip.length; boolean ip4 = len == 4; for ( int i = 0; i < ip.length; i++ ) { out.append( ConvertBytes.intToCharBytes( ((int) ip[i]) & 0xff ) ); if ( i != len - 1 ) { if ( ip4 ) out.append('.'); else out.append(':').append(':'); } } break; case GeneralName.otherName: out.append('o').append('t').append('h').append('e').append('r').append('N').append('a').append('m').append('e'). append(':'); out.append( ByteList.plain( obj.toString() ) ); return true; //tagged = true; case GeneralName.registeredID: out.append('R').append('I').append('D'). append(':'); //tagged = true; default: out.append( ByteList.plain( obj.toString() ) ); } return false; } // re-invented IETFUtils.appendRDN related pieces : public static ByteList appendRDN(final ByteList out, final RDN rdn, final Map<ASN1ObjectIdentifier, String> oidSymbols) { if ( rdn.isMultiValued() ) { AttributeTypeAndValue[] atv = rdn.getTypesAndValues(); boolean firstAtv = true; for ( int j = 0; j != atv.length; j++ ) { if (firstAtv) firstAtv = false; else out.append('+'); appendTypeAndValue(out, atv[j], oidSymbols); } return out; } return appendTypeAndValue(out, rdn.getFirst(), oidSymbols); } private static ByteList appendTypeAndValue(final ByteList out, final AttributeTypeAndValue typeAndValue, final Map<ASN1ObjectIdentifier, String> oidSymbols) { ASN1ObjectIdentifier type = typeAndValue.getType(); final String sym = oidSymbols.get(type); if (sym != null) { out.append( ByteList.plain(sym) ); } else { out.append( ByteList.plain(type.getId()) ); } out.append('='); valueToString(typeAndValue.getValue(), out); return out; } private static void valueToString(final ASN1Encodable value, final ByteList out) { final int size = out.getRealSize(); if ( value instanceof ASN1String && !(value instanceof DERUniversalString) ) { final String str = ((ASN1String) value).getString(); if ( str.length() > 0 && str.charAt(0) == '#' ) { out.append('\\'); } out.append( ByteList.plain(str) ); } else { try { byte[] val = value.toASN1Primitive().getEncoded(ASN1Encoding.DER); out.append('#').append( Hex.encode(val) ); } catch (IOException e) { throw new IllegalArgumentException("Other value has no encoded form", e); } } int index = size; // 0 int end = out.getRealSize() - size; if (end >= 2 && out.charAt(index) == '\\' && out.charAt(index + 1) == '#') { index += 2; } while ( index <= end ) { final char c = out.charAt(index); if ( c == ',' || c == '"' || c == '\\' || c == '+' || c == '=' || c == '<' || c == '>' || c == ';' ) { out.insert(index, '\\'); index++; end++; } index++; } if ( out.getRealSize() - size > 0 ) { index = size; // 0 while ( out.charAt(index) == ' ' ) { out.insert(index, '\\'); index += 2; } } index = out.getRealSize() - 1; // length - 1 while ( index >= size && out.charAt(index) == ' ' ) { out.insert(index, '\\'); index--; } } private static Hashtable getDefaultSymbols() { try { Field field = BCStyle.class.getDeclaredField("DefaultSymbols"); field.setAccessible(true); return (Hashtable) field.get(null); } catch (NoSuchFieldException ex) { debug("getDefaultSymbols", ex); } catch (SecurityException ex) { debug("getDefaultSymbols", ex); } catch (IllegalAccessException ex) { debug("getDefaultSymbols", ex); } return new Hashtable(); } @JRubyMethod(name = "value=") public IRubyObject set_value(final ThreadContext context, IRubyObject arg) { if ( arg instanceof RubyString ) { this.value = arg; return arg; } throw context.runtime.newTypeError(arg, context.runtime.getString()); } @JRubyMethod(name = "critical?") public IRubyObject critical_p(final ThreadContext context) { return context.runtime.newBoolean(isRealCritical()); } @JRubyMethod(name = "critical=") public IRubyObject set_critical(final ThreadContext context, IRubyObject arg) { setRealCritical(arg.isTrue()); return arg; } @JRubyMethod public IRubyObject to_der() { try { final byte[] enc = toASN1Sequence().getEncoded(ASN1Encoding.DER); return StringHelper.newString(getRuntime(), enc); } catch (IOException e) { throw newExtensionError(getRuntime(), e); } } ASN1Sequence toASN1Sequence() throws IOException { final ASN1EncodableVector vec = new ASN1EncodableVector(); vec.add( getRealObjectID() ); if ( critical ) vec.add( DERBoolean.TRUE ); vec.add( new DEROctetString( getRealValueEncoded() ) ); return new DLSequence(vec); } // [ self.oid, self.value, self.critical? ] @JRubyMethod public RubyArray to_a(final ThreadContext context) { RubyArray array = RubyArray.newArray(context.runtime, 3); array.append(oid(context)); array.append(value(context)); array.append(critical_p(context)); return array; } // {"oid"=>self.oid,"value"=>self.value,"critical"=>self.critical? @JRubyMethod public RubyHash to_h(final ThreadContext context) { final Ruby runtime = context.runtime; RubyHash hash = RubyHash.newHash(runtime); hash.op_aset(context, newStringFrozen(runtime, "oid"), oid(context)); hash.op_aset(context, newStringFrozen(runtime, "value"), value(context)); hash.op_aset(context, newStringFrozen(runtime, "critical"), critical_p(context)); return hash; } // "oid = critical, value" @JRubyMethod public RubyString to_s(final ThreadContext context) { final Ruby runtime = context.runtime; final RubyString str = RubyString.newString(runtime, oidSym(runtime)); str.getByteList().append(' ').append('=').append(' '); if ( isRealCritical() ) str.getByteList().append(critical__); // self.value.gsub(/\n/, ", ") final RubyString value = value(context); value.callMethod(context, "gsub!", new IRubyObject[] { RubyString.newStringShared(runtime, StringHelper.NEW_LINE), RubyString.newStringShared(runtime, StringHelper.COMMA_SPACE) }); str.getByteList().append(value.getByteList()); return str; } @Override public X509Extension clone() { try { return (X509Extension) super.clone(); } catch (CloneNotSupportedException ex) { throw new RuntimeException(ex); } } @Override @SuppressWarnings(value = "unchecked") @JRubyMethod public IRubyObject inspect() { return ObjectSupport.inspect(this); } static RaiseException newExtensionError(Ruby runtime, Exception e) { return Utils.newError(runtime, _X509(runtime).getClass("ExtensionError"), e); } static RaiseException newExtensionError(Ruby runtime, String message) { return Utils.newError(runtime, _X509(runtime).getClass("ExtensionError"), message); } // our custom "internal" HEX helpers : private static boolean isHex(final char c) { return ('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'); } static boolean isHex(final String str) { for ( int i = 0; i < str.length(); i++ ) { if ( ! isHex(str.charAt(i)) ) return false; } return true; } static int upHex(final char c) { switch (c) { case '0' : return '0'; case '1' : return '1'; case '2' : return '2'; case '3' : return '3'; case '4' : return '4'; case '5' : return '5'; case '6' : return '6'; case '7' : return '7'; case '8' : return '8'; case '9' : return '9'; case 'A' : case 'a' : return 'A'; case 'B' : case 'b' : return 'B'; case 'C' : case 'c' : return 'C'; case 'D' : case 'd' : return 'D'; case 'E' : case 'e' : return 'E'; case 'F' : case 'f' : return 'F'; } return -1; } private static ByteList hexBytes(final byte[] data, final int off) { final int len = data.length - off; return hexBytes(data, off, len, new ByteList( len * 3 )); } private static ByteList hexBytes(final byte[] data, final ByteList out) { return hexBytes(data, 0, data.length, out); } //@SuppressWarnings("deprecation") //private static ByteList hexBytes(final ByteList data, final ByteList out) { // return hexBytes(data.bytes, data.begin, data.realSize, out); //} private static ByteList hexBytes(final byte[] data, final int off, final int len, final ByteList out) { boolean notFist = false; out.ensure( len * 3 - 1 ); for ( int i = off; i < (off + len); i++ ) { if ( notFist ) out.append(':'); final byte b = data[i]; out.append( HEX[ (b >> 4) & 0xF ] ); out.append( HEX[ b & 0xF ] ); notFist = true; } return out; } private static final char[] HEX = { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' }; }