Java tutorial
/***** 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.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import org.bouncycastle.asn1.ASN1Object; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1String; import org.bouncycastle.asn1.BERTags; import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.asn1.DLSet; import org.bouncycastle.asn1.x500.AttributeTypeAndValue; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.X509DefaultEntryConverter; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; import org.jruby.RubyFixnum; import org.jruby.RubyHash; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.ext.openssl.x509store.Name; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; /** * * TODO member variables and methods are based on BC X509 way of doing things (now deprecated). Change * it to do it the X500 way, with RDN and X500NameBuilder. * * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a> */ @JRubyClass(name = "X509Name", include = "Comparable") public class X509Name extends RubyObject { private static final long serialVersionUID = -226196051911335103L; private static ObjectAllocator X509NAME_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new X509Name(runtime, klass); } }; public static void createX509Name(Ruby runtime, RubyModule mX509) { RubyClass cX509Name = mX509.defineClassUnder("Name", runtime.getObject(), X509NAME_ALLOCATOR); RubyClass openSSLError = runtime.getModule("OpenSSL").getClass("OpenSSLError"); mX509.defineClassUnder("NameError", openSSLError, openSSLError.getAllocator()); cX509Name.defineAnnotatedMethods(X509Name.class); cX509Name.setConstant("COMPAT", runtime.newFixnum(COMPAT)); cX509Name.setConstant("RFC2253", runtime.newFixnum(RFC2253)); cX509Name.setConstant("ONELINE", runtime.newFixnum(ONELINE)); cX509Name.setConstant("MULTILINE", runtime.newFixnum(MULTILINE)); cX509Name.setConstant("DEFAULT_OBJECT_TYPE", runtime.newFixnum(BERTags.UTF8_STRING)); RubyHash hash = new RubyHash(runtime, runtime.newFixnum(BERTags.UTF8_STRING)); hash.op_aset(runtime.getCurrentContext(), runtime.newString("C"), runtime.newFixnum(BERTags.PRINTABLE_STRING)); hash.op_aset(runtime.getCurrentContext(), runtime.newString("countryName"), runtime.newFixnum(BERTags.PRINTABLE_STRING)); hash.op_aset(runtime.getCurrentContext(), runtime.newString("serialNumber"), runtime.newFixnum(BERTags.PRINTABLE_STRING)); hash.op_aset(runtime.getCurrentContext(), runtime.newString("dnQualifier"), runtime.newFixnum(BERTags.PRINTABLE_STRING)); hash.op_aset(runtime.getCurrentContext(), runtime.newString("DC"), runtime.newFixnum(BERTags.IA5_STRING)); hash.op_aset(runtime.getCurrentContext(), runtime.newString("domainComponent"), runtime.newFixnum(BERTags.IA5_STRING)); hash.op_aset(runtime.getCurrentContext(), runtime.newString("emailAddress"), runtime.newFixnum(BERTags.IA5_STRING)); cX509Name.setConstant("OBJECT_TYPE_TEMPLATE", hash); cX509Name.includeModule(runtime.getComparable()); } public static final int COMPAT = 0; public static final int RFC2253 = 17892119; public static final int ONELINE = 8520479; public static final int MULTILINE = 44302342; public X509Name(Ruby runtime, RubyClass type) { super(runtime, type); oids = new ArrayList<Object>(); values = new ArrayList<Object>(); types = new ArrayList<Object>(); } private List<Object> oids; private List<Object> values; private List<Object> types; void addEntry(Object oid, Object value, Object type) { oids.add(oid); values.add(value); types.add(type); } public static X509Name create(Ruby runtime, org.bouncycastle.asn1.x500.X500Name realName) { X509Name name = new X509Name(runtime, Utils.getClassFromPath(runtime, "OpenSSL::X509::Name")); name.fromASN1Sequence((ASN1Sequence) realName.toASN1Primitive()); return name; } void fromASN1Sequence(ASN1Sequence seq) { oids = new ArrayList<Object>(); values = new ArrayList<Object>(); types = new ArrayList<Object>(); if (seq != null) { for (Enumeration enumRdn = seq.getObjects(); enumRdn.hasMoreElements();) { ASN1Object element = (ASN1Object) enumRdn.nextElement(); if (element instanceof RDN) { fromRDNElement(element); } else if (element instanceof ASN1Sequence) { fromASN1Sequence(element); } else { fromASN1Set(element); } } } } private void fromRDNElement(Object element) { RDN rdn = (RDN) element; for (AttributeTypeAndValue tv : rdn.getTypesAndValues()) { oids.add(tv.getType()); if (tv.getValue() instanceof ASN1String) { values.add(((ASN1String) tv.getValue()).getString()); } else { values.add(null); //TODO really? } types.add(getRuntime().newFixnum(ASN1.idForClass(tv.getValue().getClass()))); } } private void fromASN1Set(Object element) { ASN1Set typeAndValue = ASN1Set.getInstance(element); for (Enumeration enumRdn = typeAndValue.getObjects(); enumRdn.hasMoreElements();) { fromASN1Sequence(enumRdn.nextElement()); } } private void fromASN1Sequence(Object element) { ASN1Sequence typeAndValue = ASN1Sequence.getInstance(element); oids.add(typeAndValue.getObjectAt(0)); if (typeAndValue.getObjectAt(1) instanceof ASN1String) { values.add(((ASN1String) typeAndValue.getObjectAt(1)).getString()); } else { values.add(null); } types.add(getRuntime().newFixnum(ASN1.idForClass(typeAndValue.getObjectAt(1).getClass()))); } @JRubyMethod public IRubyObject initialize(ThreadContext context) { return this; } @JRubyMethod public IRubyObject initialize(ThreadContext context, IRubyObject str_or_dn) { return initialize(context, str_or_dn, context.nil); } @JRubyMethod public IRubyObject initialize(ThreadContext context, IRubyObject dn, IRubyObject template) { Ruby runtime = context.runtime; if (dn instanceof RubyArray) { RubyArray ary = (RubyArray) dn; if (template.isNil()) { template = runtime.getClassFromPath("OpenSSL::X509::Name").getConstant("OBJECT_TYPE_TEMPLATE"); } for (int i = 0; i < ary.size(); i++) { IRubyObject obj = ary.eltOk(i); if (!(obj instanceof RubyArray)) { throw runtime.newTypeError(obj, runtime.getArray()); } RubyArray arr = (RubyArray) obj; IRubyObject entry0, entry1, entry2; entry0 = arr.size() > 0 ? arr.eltOk(0) : context.nil; entry1 = arr.size() > 1 ? arr.eltOk(1) : context.nil; entry2 = arr.size() > 2 ? arr.eltOk(2) : context.nil; if (entry2.isNil()) entry2 = template.callMethod(context, "[]", entry0); if (entry2.isNil()) entry2 = runtime.getClassFromPath("OpenSSL::X509::Name").getConstant("DEFAULT_OBJECT_TYPE"); add_entry(context, entry0, entry1, entry2); } } else { try { byte[] bytes = OpenSSLImpl.to_der_if_possible(dn).convertToString().getBytes(); ASN1InputStream is = new ASN1InputStream(bytes); ASN1Sequence seq = (ASN1Sequence) is.readObject(); //StringBuilder b = new StringBuilder(); //printASN(seq, b); fromASN1Sequence(seq); } catch (IOException e) { //Do not catch Exception. Want to see nullpointer stacktrace. throw newX509NameError(runtime, e.getClass().getName() + ":" + e.getLocalizedMessage()); } } return this; } private void printASN(org.bouncycastle.asn1.ASN1Encodable obj, StringBuilder b) { printASN(obj, 0, b); } private void printASN(org.bouncycastle.asn1.ASN1Encodable obj, int indent, StringBuilder b) { if (obj instanceof org.bouncycastle.asn1.ASN1Sequence) { for (int i = 0; i < indent; i++) { b.append(" "); } b.append("- Sequence:"); for (java.util.Enumeration enm = ((org.bouncycastle.asn1.ASN1Sequence) obj).getObjects(); enm .hasMoreElements();) { printASN((org.bouncycastle.asn1.ASN1Encodable) enm.nextElement(), indent + 1, b); } } else if (obj instanceof org.bouncycastle.asn1.ASN1Set) { for (int i = 0; i < indent; i++) { b.append(" "); } b.append("- Set:"); for (java.util.Enumeration enm = ((org.bouncycastle.asn1.ASN1Set) obj).getObjects(); enm .hasMoreElements();) { printASN((org.bouncycastle.asn1.ASN1Encodable) enm.nextElement(), indent + 1, b); } } else { for (int i = 0; i < indent; i++) { b.append(" "); } if (obj instanceof org.bouncycastle.asn1.ASN1String) { b.append("- ").append(obj).append("=").append(((org.bouncycastle.asn1.ASN1String) obj).getString()) .append("[").append(obj.getClass()).append("]"); } else { b.append("- ").append(obj).append("[").append(obj.getClass()).append("]"); } } } private ASN1ObjectIdentifier getObjectIdentifier(String nameOrOid) { Object val1 = ASN1.getOIDLookup(getRuntime()).get(nameOrOid.toLowerCase()); if (null != val1) { return (ASN1ObjectIdentifier) val1; } ASN1ObjectIdentifier val2 = new ASN1ObjectIdentifier(nameOrOid); return val2; } @JRubyMethod public IRubyObject add_entry(ThreadContext context, IRubyObject oid, IRubyObject value) { return add_entry(context, oid, value, context.nil); } @JRubyMethod public IRubyObject add_entry(ThreadContext context, IRubyObject _oid, IRubyObject _value, IRubyObject _type) { Ruby runtime = context.runtime; String oid = _oid.convertToString().toString(); String value = _value.convertToString().toString(); IRubyObject type = !_type.isNil() ? _type : runtime.getClassFromPath("OpenSSL::X509::Name").getConstant("OBJECT_TYPE_TEMPLATE") .callMethod(context, "[]", _oid); ASN1ObjectIdentifier oid_v; try { oid_v = getObjectIdentifier(oid); } catch (IllegalArgumentException e) { throw newX509NameError(getRuntime(), "invalid field name: " + e.getMessage()); } if (null == oid_v) { throw newX509NameError(getRuntime(), null); } oids.add(oid_v); values.add(value); types.add(type); return this; } @JRubyMethod(name = "to_s", rest = true) public IRubyObject _to_s(IRubyObject[] args) { /* Should follow parameters like this: if 0 (COMPAT): irb(main):025:0> x.to_s(OpenSSL::X509::Name::COMPAT) => "CN=ola.bini, O=sweden/streetAddress=sweden, O=sweden/2.5.4.43343=sweden" irb(main):026:0> x.to_s(OpenSSL::X509::Name::ONELINE) => "CN = ola.bini, O = sweden, streetAddress = sweden, O = sweden, 2.5.4.43343 = sweden" irb(main):027:0> x.to_s(OpenSSL::X509::Name::MULTILINE) => "commonName = ola.bini\norganizationName = sweden\nstreetAddress = sweden\norganizationName = sweden\n2.5.4.43343 = sweden" irb(main):028:0> x.to_s(OpenSSL::X509::Name::RFC2253) => "2.5.4.43343=#0C0673776564656E,O=sweden,streetAddress=sweden,O=sweden,CN=ola.bini" else => /CN=ola.bini/O=sweden/streetAddress=sweden/O=sweden/2.5.4.43343=sweden */ int flag = -1; if (args.length > 0 && !args[0].isNil()) { flag = RubyNumeric.fix2int(args[0]); } StringBuilder sb = new StringBuilder(); Map<ASN1ObjectIdentifier, String> lookup = ASN1.getSymLookup(getRuntime()); Iterator<Object> oiter = null; Iterator<Object> viter = null; if (flag == RFC2253) { List<Object> ao = new ArrayList<Object>(oids); List<Object> av = new ArrayList<Object>(values); java.util.Collections.reverse(ao); java.util.Collections.reverse(av); oiter = ao.iterator(); viter = av.iterator(); } else { oiter = oids.iterator(); viter = values.iterator(); } String sep = ""; for (; oiter.hasNext();) { ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) oiter.next(); String val = (String) viter.next(); String outOid = lookup.get(oid); if (null == outOid) { outOid = oid.toString(); } if (flag == RFC2253) { sb.append(sep).append(outOid).append("=").append(val); sep = ","; } else { sb.append("/").append(outOid).append("=").append(val); } } return getRuntime().newString(sb.toString()); } @Override @JRubyMethod public RubyArray to_a() { List<IRubyObject> entries = new ArrayList<IRubyObject>(); Map<ASN1ObjectIdentifier, String> lookup = ASN1.getSymLookup(getRuntime()); Iterator<Object> oiter = oids.iterator(); Iterator<Object> viter = values.iterator(); Iterator<Object> titer = types.iterator(); for (; oiter.hasNext();) { ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) oiter.next(); String val = (String) viter.next(); String outOid = lookup.get(oid); if (null == outOid) { outOid = "UNDEF"; } IRubyObject type = (IRubyObject) titer.next(); entries.add(getRuntime().newArrayNoCopy( new IRubyObject[] { getRuntime().newString(outOid), getRuntime().newString(val), type })); } return getRuntime().newArray(entries); } @JRubyMethod(name = { "cmp", "<=>" }) public IRubyObject cmp(IRubyObject other) { if (eql_p(other).isTrue()) { return RubyFixnum.zero(getRuntime()); } // TODO: huh? return RubyFixnum.one(getRuntime()); } org.bouncycastle.asn1.x509.X509Name getRealName() { return new org.bouncycastle.asn1.x509.X509Name(new Vector<Object>(oids), new Vector<Object>(values)); } X500Name getX500Name() { return X500Name.getInstance(getRealName().toASN1Primitive()); } @Override @JRubyMethod(name = "eql?") public IRubyObject eql_p(IRubyObject other) { if (!(other instanceof X509Name)) { return getRuntime().getFalse(); } X509Name o = (X509Name) other; org.bouncycastle.asn1.x509.X509Name nm = new org.bouncycastle.asn1.x509.X509Name(new Vector<Object>(oids), new Vector<Object>(values)); org.bouncycastle.asn1.x509.X509Name o_nm = new org.bouncycastle.asn1.x509.X509Name( new Vector<Object>(o.oids), new Vector<Object>(o.values)); return nm.equals(o_nm) ? getRuntime().getTrue() : getRuntime().getFalse(); } @Override @JRubyMethod public RubyFixnum hash() { Name name = new Name(getX500Name()); return getRuntime().newFixnum(name.hash()); } @JRubyMethod public IRubyObject to_der() { DLSequence seq = null; if (oids.size() > 0) { ASN1EncodableVector vec = new ASN1EncodableVector(); ASN1EncodableVector sVec = new ASN1EncodableVector(); ASN1ObjectIdentifier lstOid = null; for (int i = 0; i != oids.size(); i++) { ASN1EncodableVector v = new ASN1EncodableVector(); ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) oids.get(i); v.add(oid); String str = (String) values.get(i); v.add(convert(oid, str, RubyNumeric.fix2int(((RubyFixnum) types.get(i))))); if (lstOid == null) { sVec.add(new DLSequence(v)); } else { vec.add(new DLSet(sVec)); sVec = new ASN1EncodableVector(); sVec.add(new DLSequence(v)); } lstOid = oid; } vec.add(new DLSet(sVec)); seq = new DLSequence(vec); } else { seq = new DLSequence(); } try { return RubyString.newString(getRuntime(), seq.getEncoded(ASN1Encoding.DER)); } catch (IOException ex) { throw newX509NameError(getRuntime(), ex.getMessage()); } } private ASN1Primitive convert(ASN1ObjectIdentifier oid, String value, int type) { try { Class<? extends ASN1Encodable> clzz = ASN1.classForId(type); if (clzz != null) { java.lang.reflect.Constructor<?> ctor = clzz.getConstructor(new Class[] { String.class }); if (null != ctor) { return (ASN1Primitive) ctor.newInstance(new Object[] { value }); } } return new X509DefaultEntryConverter().getConvertedValue(oid, value); } catch (Exception e) { throw newX509NameError(getRuntime(), e.getMessage()); } } private static RaiseException newX509NameError(Ruby runtime, String message) { return Utils.newError(runtime, "OpenSSL::X509::NameError", message); } }// X509Name