/***** 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.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import javax.security.auth.x500.X500Principal;
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.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.X509DefaultEntryConverter;
import org.bouncycastle.asn1.x509.X509NameEntryConverter;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyHash;
import org.jruby.RubyInteger;
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.ext.openssl.x509store.Name;
import static org.jruby.ext.openssl.OpenSSL.*;
import static org.jruby.ext.openssl.X509._X509;
import static org.jruby.ext.openssl.StringHelper.newString;
/**
*
* 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>
*/
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(final Ruby runtime, final RubyModule _X509) {
RubyClass _Name = _X509.defineClassUnder("Name", runtime.getObject(), X509NAME_ALLOCATOR);
RubyClass _OpenSSLError = runtime.getModule("OpenSSL").getClass("OpenSSLError");
_X509.defineClassUnder("NameError", _OpenSSLError, _OpenSSLError.getAllocator());
_Name.defineAnnotatedMethods(X509Name.class);
_Name.includeModule(runtime.getComparable());
_Name.setConstant("COMPAT", runtime.newFixnum(COMPAT));
_Name.setConstant("RFC2253", runtime.newFixnum(RFC2253));
_Name.setConstant("ONELINE", runtime.newFixnum(ONELINE));
_Name.setConstant("MULTILINE", runtime.newFixnum(MULTILINE));
final RubyFixnum UTF8_STRING = runtime.newFixnum(BERTags.UTF8_STRING);
_Name.setConstant("DEFAULT_OBJECT_TYPE", UTF8_STRING);
final RubyFixnum PRINTABLE_STRING = runtime.newFixnum(BERTags.PRINTABLE_STRING);
final RubyFixnum IA5_STRING = runtime.newFixnum(BERTags.IA5_STRING);
final ThreadContext context = runtime.getCurrentContext();
final RubyHash hash = new RubyHash(runtime, UTF8_STRING);
hash.op_aset(context, newString(runtime, new byte[] { 'C' }), PRINTABLE_STRING);
final byte[] countryName = { 'c','o','u','n','t','r','y','N','a','m','e' };
hash.op_aset(context, newString(runtime, countryName), PRINTABLE_STRING);
final byte[] serialNumber = { 's','e','r','i','a','l','N','u','m','b','e','r' };
hash.op_aset(context, newString(runtime, serialNumber), PRINTABLE_STRING);
final byte[] dnQualifier = { 'd','n','Q','u','a','l','i','f','i','e','r' };
hash.op_aset(context, newString(runtime, dnQualifier), PRINTABLE_STRING);
hash.op_aset(context, newString(runtime, new byte[] { 'D','C' }), IA5_STRING);
final byte[] domainComponent = { 'd','o','m','a','i','n','C','o','m','p','o','n','e','n','t' };
hash.op_aset(context, newString(runtime, domainComponent), IA5_STRING);
final byte[] emailAddress = { 'e','m','a','i','l','A','d','d','r','e','s','s' };
hash.op_aset(context, newString(runtime, emailAddress), IA5_STRING);
_Name.setConstant("OBJECT_TYPE_TEMPLATE", hash);
}
static X509Name newName(final Ruby runtime) {
return new X509Name(runtime, _Name(runtime));
}
static X509Name newName(final Ruby runtime, final X500Principal principal) {
final X509Name name = newName(runtime);
name.fromASN1Sequence( principal.getEncoded() );
return name;
}
static X509Name newName(final Ruby runtime, org.bouncycastle.asn1.x500.X500Name realName) {
final X509Name name = newName(runtime);
name.fromASN1Sequence((ASN1Sequence) realName.toASN1Primitive());
return name;
}
@Deprecated
public static X509Name create(final Ruby runtime, org.bouncycastle.asn1.x500.X500Name realName) {
return newName(runtime, realName);
}
static RubyClass _Name(final Ruby runtime) {
return _X509(runtime).getClass("Name");
}
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<ASN1ObjectIdentifier>();
values = new ArrayList<ASN1Encodable>();
types = new ArrayList<RubyInteger>();
}
private final List<ASN1ObjectIdentifier> oids;
private final List<ASN1Encodable> values; // <ASN1String>
private final List<RubyInteger> types;
private transient X500Name name;
private void fromASN1Sequence(final byte[] encoded) {
try {
fromASN1Sequence((ASN1Sequence) new ASN1InputStream(encoded).readObject());
}
catch (IOException e) {
throw newNameError(getRuntime(), e.getClass().getName() + ":" + e.getMessage());
}
}
void fromASN1Sequence(final ASN1Sequence seq) {
oids.clear(); values.clear(); types.clear();
if ( seq != null ) {
for ( Enumeration e = seq.getObjects(); e.hasMoreElements(); ) {
ASN1Object element = (ASN1Object) e.nextElement();
if ( element instanceof RDN ) {
fromRDNElement((RDN) element);
}
else if ( element instanceof ASN1Sequence ) {
fromASN1Sequence(element);
}
else {
fromASN1Set(element);
}
}
}
}
private void fromRDNElement(final RDN rdn) {
final Ruby runtime = getRuntime();
for( AttributeTypeAndValue tv: rdn.getTypesAndValues() ) {
oids.add( tv.getType() );
final ASN1Encodable val = tv.getValue();
addValue( val );
addType( runtime, val );
}
}
private void fromASN1Set(final ASN1Object element) {
ASN1Set typeAndValue = ASN1Set.getInstance(element);
for ( int i = 0; i < typeAndValue.size(); i++ ) {
fromASN1Sequence( typeAndValue.getObjectAt(i) );
}
}
private void fromASN1Sequence(final ASN1Encodable element) {
ASN1Sequence typeAndValue = ASN1Sequence.getInstance(element);
oids.add( (ASN1ObjectIdentifier) typeAndValue.getObjectAt(0) );
final ASN1Encodable val = typeAndValue.getObjectAt(1);
addValue( val );
addType( getRuntime(), val );
}
private void addValue(final ASN1Encodable value) {
if ( value instanceof ASN1String ) {
this.values.add( value );
}
else {
warn(getRuntime().getCurrentContext(), this + " addValue() value not an ASN1 string = '" + value + "' (" + ( value == null ? "" : value.getClass().getName()) + ")");
this.values.add( value ); // TODO should not happen?!
}
}
@SuppressWarnings("unchecked")
private void addType(final Ruby runtime, final ASN1Encodable value) {
this.name = null; // NOTE: each fromX factory calls this ...
final Integer type = ASN1.typeId(value);
if ( type == null ) {
warn(runtime.getCurrentContext(), this + " addType() could not resolve type for: " +
value + " (" + (value == null ? "" : value.getClass().getName()) + ")");
((List) this.types).add( runtime.getNil() );
}
else {
this.types.add( runtime.newFixnum( type.intValue() ) );
}
}
private void addEntry(ASN1ObjectIdentifier oid, RubyString value, RubyInteger type)
throws IOException {
this.name = null;
this.oids.add(oid);
final ASN1Encodable convertedValue = getNameEntryConverted().
getConvertedValue(oid, value.toString()
);
this.values.add( convertedValue );
this.types.add(type);
}
private static X509NameEntryConverter getNameEntryConverted() {
return new X509DefaultEntryConverter();
}
@Override
@JRubyMethod(visibility = Visibility.PRIVATE)
public IRubyObject initialize(ThreadContext context) {
return this;
}
@JRubyMethod(visibility = Visibility.PRIVATE)
public IRubyObject initialize(ThreadContext context, IRubyObject str_or_dn) {
return initialize(context, str_or_dn, context.nil);
}
@JRubyMethod(visibility = Visibility.PRIVATE)
public IRubyObject initialize(final ThreadContext context, IRubyObject dn, IRubyObject template) {
final Ruby runtime = context.runtime;
if ( dn instanceof RubyArray ) {
RubyArray ary = (RubyArray) dn;
final RubyClass _Name = _Name(runtime);
if ( template.isNil() ) template = _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 = _Name.getConstant("DEFAULT_OBJECT_TYPE");
add_entry(context, entry0, entry1, entry2);
}
}
else {
IRubyObject enc = to_der_if_possible(context, dn);
fromASN1Sequence( enc.asString().getBytes() );
}
return this;
}
/*
private static void printASN(final ASN1Encodable obj, final StringBuilder out) {
printASN(obj, 0, out);
}
private static void printASN(final ASN1Encodable obj, final int indent, final StringBuilder out) {
for( int i = 0; i < indent; i++ ) out.append(' ');
if ( obj instanceof ASN1Sequence ) {
out.append("- Sequence:");
for ( Enumeration e = ((ASN1Sequence) obj).getObjects(); e.hasMoreElements(); ) {
printASN((ASN1Encodable) e.nextElement(), indent + 1, out);
}
}
else if ( obj instanceof ASN1Set ) {
out.append("- Set:");
for ( Enumeration e = ((ASN1Set) obj).getObjects(); e.hasMoreElements(); ) {
printASN((ASN1Encodable) e.nextElement(), indent + 1, out);
}
}
else {
if ( obj instanceof ASN1String ) {
out.append("- ").append(obj).
append('=').append( ((ASN1String) obj).getString() ).
append('[').append( obj.getClass().getName() ).append(']');
} else {
out.append("- ").append(obj).
append('[').append( obj.getClass().getName() ).append(']');
}
}
} */
@JRubyMethod
public IRubyObject add_entry(ThreadContext context, IRubyObject oid, IRubyObject value) {
return add_entry(context, oid, value, null);
}
@JRubyMethod
public IRubyObject add_entry(final ThreadContext context,
final IRubyObject oid, final IRubyObject value, IRubyObject type) {
final Ruby runtime = context.runtime;
final RubyString oidStr = oid.asString();
if ( type == null || type.isNil() ) type = getDefaultType(context, oidStr);
final ASN1ObjectIdentifier objectId;
try {
objectId = ASN1.getObjectID( runtime, oidStr.toString() );
}
catch (IllegalArgumentException e) {
throw newNameError(runtime, "invalid field name: " + oidStr, e);
}
// NOTE: won't reach here :
if ( objectId == null ) throw newNameError(runtime, "invalid field name");
try {
addEntry(objectId, value.asString(), (RubyInteger) type);
}
catch (IOException e) {
throw newNameError(runtime, "invalid value", e);
}
return this;
}
private static IRubyObject getDefaultType(final ThreadContext context, final RubyString oid) {
IRubyObject template = _Name(context.runtime).getConstant("OBJECT_TYPE_TEMPLATE");
if ( template instanceof RubyHash ) {
return ((RubyHash) template).op_aref(context, oid);
}
return template.callMethod(context, "[]", oid);
}
@SuppressWarnings("unchecked")
@JRubyMethod(name = "to_s", rest = true)
public IRubyObject to_s(IRubyObject[] args) {
final Ruby runtime = getRuntime();
int flag = 0;
if ( args.length > 0 && ! args[0].isNil() ) {
flag = RubyNumeric.fix2int( args[0] );
}
/* 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
*/
final Iterator<ASN1ObjectIdentifier> oidsIter;
final Iterator<Object> valuesIter;
if ( flag == RFC2253 ) {
ArrayList<ASN1ObjectIdentifier> reverseOids = new ArrayList<ASN1ObjectIdentifier>(oids);
ArrayList<Object> reverseValues = new ArrayList<Object>(values);
Collections.reverse(reverseOids);
Collections.reverse(reverseValues);
oidsIter = reverseOids.iterator();
valuesIter = reverseValues.iterator();
} else {
oidsIter = oids.iterator();
valuesIter = (Iterator) values.iterator();
}
final StringBuilder str = new StringBuilder(48); String sep = "";
while( oidsIter.hasNext() ) {
final ASN1ObjectIdentifier oid = oidsIter.next();
String oName = name(runtime, oid);
if ( oName == null ) oName = oid.toString();
final Object value = valuesIter.next();
switch(flag) {
case RFC2253:
str.append(sep).append(oName).append('=').append(value);
sep = ",";
break;
case ONELINE:
str.append(sep).append(oName).append(" = ").append(value);
sep = ",";
break;
case MULTILINE:
final Integer nid = ASN1.oid2nid(runtime, oid);
if ( nid != null ) {
final String ln = ASN1.nid2ln(runtime, nid);
if ( ln != null ) oName = ln;
} // TODO need indention :
str.append(sep).append(oName).append(" = ").append(value);
sep = "\n";
break;
case COMPAT:
default:
str.append('/').append(oName).append('=').append(value);
}
}
return runtime.newString( str.toString() );
}
@Override
@SuppressWarnings("unchecked")
@JRubyMethod
public IRubyObject inspect() {
return ObjectSupport.inspect(this, Collections.EMPTY_LIST);
}
@Override
@JRubyMethod
public RubyArray to_a() {
final Ruby runtime = getRuntime();
final RubyArray entries = runtime.newArray( oids.size() );
final Iterator<ASN1ObjectIdentifier> oidsIter = oids.iterator();
@SuppressWarnings("unchecked")
final Iterator<Object> valuesIter = (Iterator) values.iterator();
final Iterator<RubyInteger> typesIter = types.iterator();
while ( oidsIter.hasNext() ) {
final ASN1ObjectIdentifier oid = oidsIter.next();
String oName = name(runtime, oid);
if ( oName == null ) oName = oid.toString();
final String value = valuesIter.next().toString();
final IRubyObject type = typesIter.next();
final IRubyObject[] entry = new IRubyObject[] {
runtime.newString(oName), runtime.newString(value), type
};
entries.append( runtime.newArrayNoCopy(entry) );
}
return entries;
}
private static String name(final Ruby runtime, final ASN1ObjectIdentifier oid) {
return ASN1.oid2name(runtime, oid, true);
}
@Deprecated
@SuppressWarnings("unchecked")
org.bouncycastle.asn1.x509.X509Name getRealName() {
final java.util.Vector strValues = new java.util.Vector();
for ( ASN1Encodable value : values ) strValues.add( value.toString() );
return new org.bouncycastle.asn1.x509.X509Name(
new java.util.Vector<Object>(oids), strValues
);
}
final X500Name getX500Name() {
if ( name != null ) return name;
final X500NameBuilder builder = new X500NameBuilder( BCStyle.INSTANCE );
for ( int i = 0; i < oids.size(); i++ ) {
builder.addRDN( oids.get(i), values.get(i) );
}
return name = builder.build();
}
@JRubyMethod(name = { "cmp", "<=>" })
public RubyFixnum cmp(IRubyObject other) {
if ( equals(other) ) {
return RubyFixnum.zero( getRuntime() );
}
// TODO: do we really need cmp - if so what order huh?
if ( other instanceof X509Name ) {
final X509Name that = (X509Name) other;
final X500Name thisName = this.getX500Name();
final X500Name thatName = that.getX500Name();
int cmp = thisName.toString().compareTo( thatName.toString() );
return RubyFixnum.newFixnum( getRuntime(), cmp );
}
return RubyFixnum.one( getRuntime() );
}
@Override
public boolean equals(Object other) {
if ( this == other ) return true;
if ( other instanceof X509Name ) {
final X509Name that = (X509Name) other;
final X500Name thisName = this.getX500Name();
final X500Name thatName = that.getX500Name();
return thisName.equals(thatName);
}
return false;
}
@Override
public int hashCode() {
try {
return Name.hash( getX500Name() );
}
catch (IOException e) {
debugStackTrace(getRuntime(), e); return 0;
}
catch (RuntimeException e) {
debugStackTrace(getRuntime(), e); return 0;
}
// return 41 * this.oids.hashCode();
}
@JRubyMethod(name = "eql?")
public RubyBoolean eql_p(final ThreadContext context, final IRubyObject other) {
if ( ! (other instanceof X509Name) ) return getRuntime().getFalse();
return getRuntime().newBoolean( equals(other) );
}
@Override
public IRubyObject eql_p(final IRubyObject obj) {
return eql_p(getRuntime().getCurrentContext(), obj);
}
@Override
@JRubyMethod
public RubyFixnum hash() {
return getRuntime().newFixnum( hashCode() );
}
@JRubyMethod
public RubyString to_der(final ThreadContext context) {
final Ruby runtime = context.runtime;
final DLSequence seq;
if ( oids.size() > 0 ) {
ASN1EncodableVector vec = new ASN1EncodableVector();
ASN1EncodableVector sVec = new ASN1EncodableVector();
ASN1ObjectIdentifier lastOid = null;
for ( int i = 0; i != oids.size(); i++ ) {
final ASN1ObjectIdentifier oid = oids.get(i);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(oid);
// TODO DO NOT USE DL types !
//final String value = values.get(i);
//final int type = RubyNumeric.fix2int(types.get(i));
//v.add( convert(oid, value, type) );
v.add( values.get(i) );
if ( lastOid == null ) {
sVec.add(new DLSequence(v));
}
else {
vec.add(new DLSet(sVec));
sVec = new ASN1EncodableVector();
sVec.add(new DLSequence(v));
}
lastOid = oid;
}
vec.add(new DLSet(sVec));
seq = new DLSequence(vec);
} else {
seq = new DLSequence();
}
try {
return StringHelper.newString(runtime, seq.getEncoded(ASN1Encoding.DER));
}
catch (IOException e) {
throw newNameError(runtime, e);
}
}
private ASN1Primitive convert(ASN1ObjectIdentifier oid, String value, int type) {
final Class<? extends ASN1Encodable> clazz = ASN1.typeClass(type);
try {
if ( clazz != null ) {
Constructor<?> ctor = clazz.getConstructor(new Class[]{ String.class });
if (null != ctor) {
return (ASN1Primitive) ctor.newInstance(new Object[]{ value });
}
}
return new X509DefaultEntryConverter().getConvertedValue(oid, value);
}
catch (NoSuchMethodException e) {
throw newNameError(getRuntime(), e);
}
catch (InstantiationException e) {
throw newNameError(getRuntime(), e);
}
catch (IllegalAccessException e) {
throw newNameError(getRuntime(), e);
}
catch (IllegalArgumentException e) {
throw newNameError(getRuntime(), e);
}
catch (InvocationTargetException e) {
throw newNameError(getRuntime(), e.getTargetException());
}
catch (RuntimeException e) {
debugStackTrace(getRuntime(), e);
throw newNameError(getRuntime(), e);
}
}
private static RaiseException newNameError(Ruby runtime, String msg, Throwable e) {
return Utils.newError(runtime, _X509(runtime).getClass("NameError"), msg, e);
}
private static RaiseException newNameError(Ruby runtime, Throwable e) {
return Utils.newError(runtime, _X509(runtime).getClass("NameError"), e);
}
private static RaiseException newNameError(Ruby runtime, String message) {
return Utils.newError(runtime, _X509(runtime).getClass("NameError"), message);
}
}// X509Name