/***** 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.ByteArrayInputStream; import java.io.IOException; import java.io.StringWriter; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.DSAPublicKey; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.joda.time.DateTime; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyBoolean; import org.jruby.RubyClass; import org.jruby.RubyFixnum; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.RubyTime; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.ext.openssl.impl.ASN1Registry; import org.jruby.ext.openssl.x509store.PEMInputOutput; import org.jruby.ext.openssl.x509store.X509AuxCertificate; import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.builtin.Variable; import org.jruby.runtime.component.VariableEntry; import org.jruby.util.ByteList; import static org.jruby.ext.openssl.X509._X509; import static org.jruby.ext.openssl.X509Extension.newExtension; import static org.jruby.ext.openssl.X509CRL.extensions_to_text; import static org.jruby.ext.openssl.StringHelper.appendGMTDateTime; import static org.jruby.ext.openssl.StringHelper.appendLowerHexValue; import static org.jruby.ext.openssl.StringHelper.lowerHexBytes; import static org.jruby.ext.openssl.OpenSSL.debug; import static org.jruby.ext.openssl.OpenSSL.debugStackTrace; /** * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a> */ public class X509Cert extends RubyObject { private static final long serialVersionUID = -6524431607032364369L; private static ObjectAllocator X509CERT_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new X509Cert(runtime, klass); } }; public static void createX509Cert(final Ruby runtime, final RubyModule X509) { RubyClass Certificate = X509.defineClassUnder("Certificate", runtime.getObject(), X509CERT_ALLOCATOR); RubyClass OpenSSLError = runtime.getModule("OpenSSL").getClass("OpenSSLError"); X509.defineClassUnder("CertificateError", OpenSSLError, OpenSSLError.getAllocator()); Certificate.defineAnnotatedMethods(X509Cert.class); } public X509Cert(Ruby runtime, RubyClass type) { super(runtime, type); } private X509Cert(Ruby runtime) { super(runtime, _Certificate(runtime)); } private IRubyObject subject; private IRubyObject issuer; private BigInteger serial = BigInteger.ZERO; private RubyTime not_before; private RubyTime not_after; private IRubyObject sig_alg; private IRubyObject version; private X509Certificate cert; private transient PKey public_key; // lazy initialized private final List<X509Extension> extensions = new ArrayList<X509Extension>(); private boolean changed = true; final X509AuxCertificate getAuxCert() { if ( cert == null ) return null; if ( cert instanceof X509AuxCertificate ) { return (X509AuxCertificate) cert; } return new X509AuxCertificate(cert); } public static IRubyObject wrap(Ruby runtime, Certificate cert) throws CertificateEncodingException { return wrap(runtime.getCurrentContext(), cert.getEncoded()); } static X509Cert wrap(ThreadContext context, Certificate cert) throws CertificateEncodingException { return wrap(context, cert.getEncoded()); } // this is the javax.security counterpart of the previous wrap method public static IRubyObject wrap(Ruby runtime, javax.security.cert.Certificate cert) throws javax.security.cert.CertificateEncodingException { return wrap(runtime.getCurrentContext(), cert.getEncoded()); } static X509Cert wrap(ThreadContext context, javax.security.cert.Certificate cert) throws javax.security.cert.CertificateEncodingException { return wrap(context, cert.getEncoded()); } static X509Cert wrap(final ThreadContext context, final byte[] encoded) { //final Ruby runtime = context.runtime; //final RubyString enc = StringHelper.newString(runtime, encoded); //return _Certificate(runtime).callMethod(context, "new", enc); final X509Cert cert = new X509Cert(context.runtime); cert.initialize(context, encoded); return cert; } @JRubyMethod(name="initialize", optional = 1, visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args, final Block unusedBlock) { if ( args.length == 0 ) { this.subject = X509Name.newName(context.runtime); this.issuer = X509Name.newName(context.runtime); return this; } final RubyString str = StringHelper.readPossibleDERInput(context, args[0]); final ByteList bytes = str.getByteList(); initialize(context, bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize()); return this; } private void initialize(final ThreadContext context, final byte[] encoded) { initialize(context, encoded, 0, encoded.length); } private void initialize(final ThreadContext context, final byte[] encoded, final int offset, final int length) { final Ruby runtime = context.runtime; byte[] bytes = StringHelper.readX509PEM(encoded, offset, length); try { final ByteArrayInputStream bis = new ByteArrayInputStream(bytes); cert = (X509Certificate) SecurityHelper.getCertificateFactory("X.509").generateCertificate(bis); } catch (CertificateException e) { throw newCertificateError(runtime, e); } if ( cert == null ) { throw newCertificateError(runtime, (String) null); } set_serial( RubyNumeric.str2inum(runtime, runtime.newString(cert.getSerialNumber().toString()), 10) ); set_not_before( context, RubyTime.newTime( runtime, cert.getNotBefore().getTime() ) ); set_not_after( context, RubyTime.newTime( runtime, cert.getNotAfter().getTime() ) ); this.subject = X509Name.newName(runtime, cert.getSubjectX500Principal()); this.issuer = X509Name.newName(runtime, cert.getIssuerX500Principal()); this.version = RubyFixnum.newFixnum(runtime, cert.getVersion() - 1); String sigAlgorithm = cert.getSigAlgOID(); if ( sigAlgorithm == null ) sigAlgorithm = cert.getSigAlgName(); // e.g. SHA256withRSA else { sigAlgorithm = ASN1.oid2name(runtime, new ASN1ObjectIdentifier(sigAlgorithm), true); if ( sigAlgorithm == null ) { sigAlgorithm = "itu-t"; // MRI compability ... the "crazy" parts // for some certificates that MRI parses, // we get getSigAlgOID() == getSigAlgName() == "0.0" if ( cert.getSigAlgName() != null && ! cert.getSigAlgOID().equals(cert.getSigAlgName()) ) { sigAlgorithm = cert.getSigAlgName(); // not sure if it makes any sense } } } // "hot" path e.g. sha256WithRSAEncryption this.sig_alg = RubyString.newString(runtime, sigAlgorithm); final Set<String> criticalExtOIDs = cert.getCriticalExtensionOIDs(); if ( criticalExtOIDs != null ) { for ( final String extOID : criticalExtOIDs ) { addExtension(context, extOID, true); } } final Set<String> nonCriticalExtOIDs = cert.getNonCriticalExtensionOIDs(); if ( nonCriticalExtOIDs != null ) { for ( final String extOID : nonCriticalExtOIDs ) { addExtension(context, extOID, false); } } changed = false; } private void addExtension(final ThreadContext context, final String extOID, final boolean critical) { try { final byte[] extValue = cert.getExtensionValue(extOID); if ( extValue == null ) return; final X509Extension[] extension = newExtension(context, extOID, extValue, critical); for ( int i = 0; i < extension.length; i++ ) this.extensions.add( extension[i] ); } catch (IOException e) { throw newCertificateError(context.runtime, e); } } private static RubyClass _CertificateError(final Ruby runtime) { return _X509(runtime).getClass("CertificateError"); } static RubyClass _Certificate(final Ruby runtime) { return _X509(runtime).getClass("Certificate"); } public static RaiseException newCertificateError(final Ruby runtime, Exception e) { return Utils.newError(runtime, _CertificateError(runtime), e); } static RaiseException newCertificateError(final Ruby runtime, String msg) { return Utils.newError(runtime, _CertificateError(runtime), msg); } @Override @JRubyMethod(visibility = Visibility.PRIVATE) public IRubyObject initialize_copy(IRubyObject obj) { if ( this == obj ) return this; checkFrozen(); return this; } @JRubyMethod public IRubyObject to_der() { try { return StringHelper.newString(getRuntime(), cert.getEncoded()); } catch (CertificateEncodingException ex) { throw newCertificateError(getRuntime(), ex); } } @JRubyMethod(name = {"to_pem", "to_s"}) public IRubyObject to_pem() { final StringWriter str = new StringWriter(); try { PEMInputOutput.writeX509Certificate(str, getAuxCert()); return getRuntime().newString( str.toString() ); } catch (IOException ex) { throw getRuntime().newIOErrorFromException(ex); } } @JRubyMethod public IRubyObject to_text(final ThreadContext context) { final Ruby runtime = context.runtime; final char[] S20 = StringHelper.S20; final StringBuilder text = new StringBuilder(240); text.append("Certificate:\n"); text.append(S20,0,4).append("Data:\n"); final int version = this.version == null ? 0 : RubyNumeric.fix2int(this.version); text.append(S20,0,8).append("Version: ").append( version + 1 ). append(" (0x").append( Integer.toString( version, 16 ) ).append(")\n"); // <= 0x1122334455667788 printed on same line as : // Serial Number: 1234605616436508552 (0x1122334455667788) // but 0x112233445566778899 ends up : // Serial Number: // 11:22:33:44:55:66:77:88:99 text.append(S20,0,8).append("Serial Number:"); if ( serial.compareTo( new BigInteger("FFFFFFFFFFFFFFFF", 16) ) > 0 ) { text.append('\n'); text.append(S20,0,12).append( lowerHexBytes(serial.toByteArray(), 1) ).append('\n'); } else { text.append(' ').append(serial.toString(10)).append(' '); text.append('(').append("0x").append(serial.toString(16)).append(')').append('\n'); } text.append(S20,0,4).append("Signature Algorithm: ").append( signature_algorithm() ).append('\n'); //final RubyString issuer = issuer().asString(); ByteList bytes = issuer.getByteList(); //StringHelper.gsub(runtime, bytes, (byte) '/', (byte) ' '); //if ( bytes.charAt(0) == ' ' ) bytes.setBegin(bytes.getBegin() + 1); text.append(S20,0,8).append("Issuer: ").append( issuer ).append('\n'); text.append(S20,0,8).append("Validity\n"); text.append(S20,0,12).append("Not Before: "); appendGMTDateTime( text, getNotBefore() ).append('\n'); text.append(S20,0,12).append("Not After : "); appendGMTDateTime( text, getNotAfter() ).append('\n'); text.append(S20,0,8).append("Subject: ").append( subject() ).append('\n'); text.append(S20,0,8).append("Subject Public Key Info:\n"); final PublicKey publicKey = getPublicKey(); text.append(S20,0,12).append("Public Key Algorithm: ").append(publicKey.getAlgorithm()).append('\n'); if ( "RSA".equals( publicKey.getAlgorithm() ) ) { final RSAPublicKey rsaKey = ((RSAPublicKey) publicKey); text.append(S20,0,16).append("Public-Key: (").append( rsaKey.getModulus().bitLength() ).append(" bit)\n"); text.append(S20,0,16).append("Modulus:\n"); appendLowerHexValue(text, rsaKey.getModulus().toByteArray(), 20, 45); final BigInteger exponent = rsaKey.getPublicExponent(); text.append(S20,0,16).append("Exponent: ").append(exponent). append(" (0x").append( exponent.toString(16) ).append(")\n"); } else if ( "DSA".equals( publicKey.getAlgorithm() ) ) { final DSAPublicKey dsaKey = ((DSAPublicKey) publicKey); text.append(S20,0,16).append("Public-Key: (").append( dsaKey.getY().bitLength() ).append(" bit)\n"); text.append(S20,0,16).append("TODO: not-implemented (PR HOME-WORK)").append('\n'); // left-TODO } else { text.append(S20,0,16).append("TODO: not-implemented (PRs WELCOME!)").append('\n'); // left-TODO } if ( extensions != null && extensions.size() > 0 ) { text.append(S20,0,8).append("X509v3 extensions:\n"); extensions_to_text(context, extensions, text, 12); } text.append(S20,0,4).append("Signature Algorithm: ").append( signature_algorithm() ).append('\n'); appendLowerHexValue(text, getSignature(), 9, 54); return RubyString.newString( runtime, text ); } @Override @JRubyMethod @SuppressWarnings("unchecked") public IRubyObject inspect() { final ArrayList<Variable<String>> varList = new ArrayList<Variable<String>>(5); varList.add(new VariableEntry<String>( "subject", subject().isNil() ? "nil" : subject().asString().toString() )); varList.add(new VariableEntry<String>( "issuer", issuer().isNil() ? "nil" : issuer().asString().toString() )); varList.add(new VariableEntry<String>( "serial", serial().isNil() ? "nil" : serial().asString().toString() )); varList.add(new VariableEntry<String>( "not_before", not_before().isNil() ? "nil" : not_before().toString() )); varList.add(new VariableEntry<String>( "not_after", not_after().isNil() ? "nil" : not_after().toString() )); return ObjectSupport.inspect(this, (List) varList); } @JRubyMethod public IRubyObject version() { return version != null ? version : ( version = getRuntime().newFixnum(0) ); } @JRubyMethod(name = "version=") public IRubyObject set_version(final IRubyObject version) { if ( ! version().equals(version) ) { this.changed = true; } return this.version = version; } @JRubyMethod public IRubyObject signature_algorithm() { return sig_alg; } private byte[] getSignature() { return cert.getSignature(); } BigInteger getSerial() { return serial; } @JRubyMethod public IRubyObject serial() { return BN.newBN(getRuntime(), serial); } @JRubyMethod(name = "serial=") public IRubyObject set_serial(final IRubyObject serial) { final String serialStr = serial.asString().toString(); final BigInteger serialInt; if ( serialStr.equals("0") ) { // MRI compatibility: allow 0 serial number serialInt = BigInteger.ONE; } else { serialInt = new BigInteger(serialStr); } this.changed = ! serialInt.equals(this.serial); //generator.setSerialNumber( serialInt.abs() ); this.serial = serialInt; return serial; } X509Name getSubject() { return ((X509Name) subject); } @JRubyMethod public IRubyObject subject() { return subject; } @JRubyMethod(name = "subject=") public IRubyObject set_subject(final IRubyObject subject) { if ( ! subject.equals(this.subject) ) this.changed = true; return this.subject = subject; } X509Name getIssuer() { return ((X509Name) issuer); } @JRubyMethod public IRubyObject issuer() { return issuer; } @JRubyMethod(name = "issuer=") public IRubyObject set_issuer(final IRubyObject issuer) { if ( ! issuer.equals(this.issuer) ) this.changed = true; return this.issuer = issuer; } @JRubyMethod public IRubyObject not_before() { return not_before == null ? getRuntime().getNil() : not_before; } @JRubyMethod(name = "not_before=") public IRubyObject set_not_before(final ThreadContext context, final IRubyObject time) { changed = true; not_before = (RubyTime) time.callMethod(context, "getutc"); not_before.setMicroseconds(0); return time; } DateTime getNotBefore() { return not_before == null ? null : not_before.getDateTime(); } @JRubyMethod public IRubyObject not_after() { return not_after == null ? getRuntime().getNil() : not_after; } @JRubyMethod(name = "not_after=") public IRubyObject set_not_after(final ThreadContext context, final IRubyObject time) { changed = true; not_after = (RubyTime) time.callMethod(context, "getutc"); not_after.setMicroseconds(0); return time; } DateTime getNotAfter() { return not_after == null ? null : not_after.getDateTime(); } @JRubyMethod public IRubyObject public_key(final ThreadContext context) { if ( public_key == null ) initializePublicKey(); return public_key.callMethod(context, "public_key"); } @JRubyMethod(name = "public_key=") public IRubyObject set_public_key(IRubyObject public_key) { if ( ! ( public_key instanceof PKey ) ) { throw getRuntime().newTypeError("OpenSSL::PKey::PKey expected but got " + public_key.getMetaClass().getName()); } if ( ! public_key.equals(this.public_key) ) { this.changed = true; } return this.public_key = (PKey) public_key; } private PublicKey getPublicKey() { if ( public_key == null ) initializePublicKey(); return public_key.getPublicKey(); } private void initializePublicKey() throws RaiseException { final Ruby runtime = getRuntime(); final boolean changed = this.changed; if ( cert == null ) { throw newCertificateError(runtime, "no certificate"); } final PublicKey publicKey = cert.getPublicKey(); final String algorithm = publicKey.getAlgorithm(); if ( "RSA".equalsIgnoreCase(algorithm) ) { //if ( public_key == null ) { // throw new IllegalStateException("no public key encoded data"); //} set_public_key( PKeyRSA.newInstance(runtime, publicKey) ); } else if ( "DSA".equalsIgnoreCase(algorithm) ) { //if ( public_key == null ) { // throw new IllegalStateException("no public key encoded data"); //} set_public_key( PKeyDSA.newInstance(runtime, publicKey) ); } else { String message = "unsupported algorithm"; if ( algorithm != null ) message += " '" + algorithm + "'"; throw newCertificateError(runtime, message); } this.changed = changed; } @JRubyMethod public IRubyObject sign(final ThreadContext context, final IRubyObject key, final IRubyObject digest) { final Ruby runtime = context.runtime; // Have to obey some artificial constraints of the OpenSSL implementation. Stupid. final String keyAlg = ((PKey) key).getAlgorithm(); final String digAlg; final String digName; if (digest instanceof Digest) { digAlg = ((Digest) digest).getShortAlgorithm(); digName = ((Digest) digest).name().toString(); } else { digAlg = digest.asJavaString(); digName = null; } if( ( "DSA".equalsIgnoreCase(keyAlg) && "MD5".equalsIgnoreCase(digAlg) ) || ( "RSA".equalsIgnoreCase(keyAlg) && "DSS1".equals(digName) ) ) { throw newCertificateError(runtime, "signature_algorithm not supported"); } org.bouncycastle.x509.X509V3CertificateGenerator builder = getCertificateBuilder(); for ( X509Extension ext : uniqueExtensions() ) { try { final byte[] bytes = ext.getRealValueEncoded(); builder.addExtension(ext.getRealObjectID().getId(), ext.isRealCritical(), bytes); } catch (IOException ioe) { throw runtime.newIOErrorFromException(ioe); } } builder.setSignatureAlgorithm(digAlg + "WITH" + keyAlg); // "SHA1WITHRSA" try { cert = builder.generate( ((PKey) key).getPrivateKey() ); } catch (GeneralSecurityException e) { throw newCertificateError(runtime, e); } if (cert == null) throw newCertificateError(runtime, (String) null); String name = ASN1Registry.o2a(cert.getSigAlgOID()); if ( name == null ) name = cert.getSigAlgOID(); this.sig_alg = runtime.newString(name); this.changed = false; return this; } private org.bouncycastle.x509.X509V3CertificateGenerator getCertificateBuilder() { org.bouncycastle.x509.X509V3CertificateGenerator generator = new org.bouncycastle.x509.X509V3CertificateGenerator(); generator.setSerialNumber( serial.abs() ); if ( subject != null ) generator.setSubjectDN( ((X509Name) subject).getRealName() ); if ( issuer != null ) generator.setIssuerDN( ((X509Name) issuer).getRealName() ); generator.setNotBefore( not_before.getJavaDate() ); generator.setNotAfter( not_after.getJavaDate() ); generator.setPublicKey( getPublicKey() ); return generator; } //private transient org.bouncycastle.x509.X509V3CertificateGenerator generator; @JRubyMethod public RubyBoolean verify(final IRubyObject key) { final Ruby runtime = getRuntime(); if ( changed ) return runtime.getFalse(); try { cert.verify(((PKey) key).getPublicKey()); return runtime.getTrue(); } catch (CertificateException e) { debug(runtime, "Certificate#verify failed: ", e); throw newCertificateError(runtime, e); } catch (NoSuchAlgorithmException e) { debugStackTrace(runtime, e); throw newCertificateError(runtime, e); } catch (NoSuchProviderException e) { debugStackTrace(runtime, e); throw newCertificateError(runtime, e); } catch (SignatureException e) { debug(runtime, "Certificate#verify failed: ", e); return runtime.getFalse(); } catch (InvalidKeyException e) { debug(runtime, "Certificate#verify failed: ", e); return runtime.getFalse(); } } @JRubyMethod public RubyBoolean check_private_key(final IRubyObject key) { final PublicKey certPublicKey = cert.getPublicKey(); if ( certPublicKey.equals( ((PKey) key).getPublicKey() ) ) { return getRuntime().getTrue(); } return getRuntime().getFalse(); } @JRubyMethod public RubyArray extensions() { @SuppressWarnings("unchecked") final List<IRubyObject> extensions = (List) this.extensions; return getRuntime().newArray( extensions ); } @SuppressWarnings("unchecked") @JRubyMethod(name = "extensions=") public IRubyObject set_extensions(final IRubyObject array) { extensions.clear(); // RubyArray is a List : extensions.addAll( (List<X509Extension>) array ); return array; } @JRubyMethod public IRubyObject add_extension(final IRubyObject ext) { changed = true; extensions.add((X509Extension) ext); return ext; } private Collection<X509Extension> uniqueExtensions() { final Map<ASN1ObjectIdentifier, X509Extension> unique = new LinkedHashMap<ASN1ObjectIdentifier, X509Extension>(); for ( X509Extension current : this.extensions ) { final ASN1ObjectIdentifier oid = current.getRealObjectID(); final X509Extension existing = unique.get( oid ); if ( existing == null ) { unique.put( oid, current ); continue; } // NOTE: dealing with Java API limits here since it does not // handle multiple OID mappings to a sequence out of the box // commonly used e.g. with subjectAltName || issuserAltName : if ( "2.5.29.17".equals( oid.getId() ) || "2.5.29.18".equals( oid.getId() ) ) { final ASN1EncodableVector vec = new ASN1EncodableVector(); try { GeneralName[] n1 = extRealNames(existing); for ( int i = 0; i < n1.length; i++ ) vec.add( n1[i] ); GeneralName[] n2 = extRealNames(current); for ( int i = 0; i < n2.length; i++ ) vec.add( n2[i] ); GeneralNames nn = GeneralNames.getInstance(new DLSequence(vec)); final X509Extension existingDup = existing.clone(); existingDup.setRealValue( nn ); unique.put( oid, existingDup ); } catch (IOException ex) { throw getRuntime().newIOErrorFromException(ex); } continue; } // TODO do we need special care for any others here ?!? final ASN1EncodableVector vec = new ASN1EncodableVector(); try { final ASN1Encodable existingValue = existing.getRealValue(); if ( existingValue instanceof ASN1Sequence ) { final ASN1Sequence seq = (ASN1Sequence) existingValue; for ( int i = 0; i < seq.size(); i++ ) { vec.add( seq.getObjectAt(i) ); } } else { vec.add(existingValue); } vec.add( current.getRealValue() ); // existing.setRealValue( new DLSequence(vec) ); final X509Extension existingDup = existing.clone(); existingDup.setRealValue( new DLSequence(vec) ); unique.put( oid, existingDup ); } catch (IOException ex) { throw getRuntime().newIOErrorFromException(ex); } } return unique.values(); } private static GeneralName[] extRealNames(final X509Extension extension) throws IOException { final ASN1Encodable value = extension.getRealValue(); if ( value instanceof GeneralName ) { return new GeneralName[] { (GeneralName) value }; } return GeneralNames.getInstance( value ).getNames(); } @Override public Object toJava(Class target) { if ( target.isAssignableFrom(X509Certificate.class) ) { if ( target == X509AuxCertificate.class ) return getAuxCert(); return cert; } return super.toJava(target); } }// X509Cert