From 7cb752aaf746dc0b473afeb9e892b7fbc12666c5 Mon Sep 17 00:00:00 2001 From: Roberto Tyley Date: Mon, 14 Jul 2014 22:38:01 +0100 Subject: Execute become-spongy.sh https://github.com/rtyley/spongycastle/blob/3040af/become-spongy.sh --- .../asn1/x500/AttributeTypeAndValue.java | 72 +++ .../spongycastle/asn1/x500/DirectoryString.java | 125 +++++ .../main/java/org/spongycastle/asn1/x500/RDN.java | 119 +++++ .../java/org/spongycastle/asn1/x500/X500Name.java | 326 ++++++++++++ .../spongycastle/asn1/x500/X500NameBuilder.java | 87 ++++ .../org/spongycastle/asn1/x500/X500NameStyle.java | 79 +++ .../asn1/x500/style/AbstractX500NameStyle.java | 192 +++++++ .../asn1/x500/style/BCStrictStyle.java | 36 ++ .../org/spongycastle/asn1/x500/style/BCStyle.java | 349 +++++++++++++ .../spongycastle/asn1/x500/style/IETFUtils.java | 572 +++++++++++++++++++++ .../spongycastle/asn1/x500/style/RFC4519Style.java | 248 +++++++++ .../asn1/x500/style/X500NameTokenizer.java | 90 ++++ 12 files changed, 2295 insertions(+) create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/AttributeTypeAndValue.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/DirectoryString.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/RDN.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/X500Name.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/X500NameBuilder.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/X500NameStyle.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/style/AbstractX500NameStyle.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/style/BCStrictStyle.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/style/BCStyle.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/style/IETFUtils.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/style/RFC4519Style.java create mode 100644 core/src/main/java/org/spongycastle/asn1/x500/style/X500NameTokenizer.java (limited to 'core/src/main/java/org/spongycastle/asn1/x500') diff --git a/core/src/main/java/org/spongycastle/asn1/x500/AttributeTypeAndValue.java b/core/src/main/java/org/spongycastle/asn1/x500/AttributeTypeAndValue.java new file mode 100644 index 00000000..57fa4c25 --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/AttributeTypeAndValue.java @@ -0,0 +1,72 @@ +package org.spongycastle.asn1.x500; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DERSequence; + +public class AttributeTypeAndValue + extends ASN1Object +{ + private ASN1ObjectIdentifier type; + private ASN1Encodable value; + + private AttributeTypeAndValue(ASN1Sequence seq) + { + type = (ASN1ObjectIdentifier)seq.getObjectAt(0); + value = (ASN1Encodable)seq.getObjectAt(1); + } + + public static AttributeTypeAndValue getInstance(Object o) + { + if (o instanceof AttributeTypeAndValue) + { + return (AttributeTypeAndValue)o; + } + else if (o != null) + { + return new AttributeTypeAndValue(ASN1Sequence.getInstance(o)); + } + + throw new IllegalArgumentException("null value in getInstance()"); + } + + public AttributeTypeAndValue( + ASN1ObjectIdentifier type, + ASN1Encodable value) + { + this.type = type; + this.value = value; + } + + public ASN1ObjectIdentifier getType() + { + return type; + } + + public ASN1Encodable getValue() + { + return value; + } + + /** + *
+     * AttributeTypeAndValue ::= SEQUENCE {
+     *           type         OBJECT IDENTIFIER,
+     *           value        ANY DEFINED BY type }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public ASN1Primitive toASN1Primitive() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(type); + v.add(value); + + return new DERSequence(v); + } +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/DirectoryString.java b/core/src/main/java/org/spongycastle/asn1/x500/DirectoryString.java new file mode 100644 index 00000000..8e61383b --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/DirectoryString.java @@ -0,0 +1,125 @@ +package org.spongycastle.asn1.x500; + +import org.spongycastle.asn1.ASN1Choice; +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1String; +import org.spongycastle.asn1.ASN1TaggedObject; +import org.spongycastle.asn1.DERBMPString; +import org.spongycastle.asn1.DERPrintableString; +import org.spongycastle.asn1.DERT61String; +import org.spongycastle.asn1.DERUTF8String; +import org.spongycastle.asn1.DERUniversalString; + +public class DirectoryString + extends ASN1Object + implements ASN1Choice, ASN1String +{ + private ASN1String string; + + public static DirectoryString getInstance(Object o) + { + if (o == null || o instanceof DirectoryString) + { + return (DirectoryString)o; + } + + if (o instanceof DERT61String) + { + return new DirectoryString((DERT61String)o); + } + + if (o instanceof DERPrintableString) + { + return new DirectoryString((DERPrintableString)o); + } + + if (o instanceof DERUniversalString) + { + return new DirectoryString((DERUniversalString)o); + } + + if (o instanceof DERUTF8String) + { + return new DirectoryString((DERUTF8String)o); + } + + if (o instanceof DERBMPString) + { + return new DirectoryString((DERBMPString)o); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + o.getClass().getName()); + } + + public static DirectoryString getInstance(ASN1TaggedObject o, boolean explicit) + { + if (!explicit) + { + throw new IllegalArgumentException("choice item must be explicitly tagged"); + } + + return getInstance(o.getObject()); + } + + private DirectoryString( + DERT61String string) + { + this.string = string; + } + + private DirectoryString( + DERPrintableString string) + { + this.string = string; + } + + private DirectoryString( + DERUniversalString string) + { + this.string = string; + } + + private DirectoryString( + DERUTF8String string) + { + this.string = string; + } + + private DirectoryString( + DERBMPString string) + { + this.string = string; + } + + public DirectoryString(String string) + { + this.string = new DERUTF8String(string); + } + + public String getString() + { + return string.getString(); + } + + public String toString() + { + return string.getString(); + } + + /** + *
+     *  DirectoryString ::= CHOICE {
+     *    teletexString               TeletexString (SIZE (1..MAX)),
+     *    printableString             PrintableString (SIZE (1..MAX)),
+     *    universalString             UniversalString (SIZE (1..MAX)),
+     *    utf8String                  UTF8String (SIZE (1..MAX)),
+     *    bmpString                   BMPString (SIZE (1..MAX))  }
+     * 
+ */ + public ASN1Primitive toASN1Primitive() + { + return ((ASN1Encodable)string).toASN1Primitive(); + } +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/RDN.java b/core/src/main/java/org/spongycastle/asn1/x500/RDN.java new file mode 100644 index 00000000..5e70cfb3 --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/RDN.java @@ -0,0 +1,119 @@ +package org.spongycastle.asn1.x500; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.DERSet; + +public class RDN + extends ASN1Object +{ + private ASN1Set values; + + private RDN(ASN1Set values) + { + this.values = values; + } + + public static RDN getInstance(Object obj) + { + if (obj instanceof RDN) + { + return (RDN)obj; + } + else if (obj != null) + { + return new RDN(ASN1Set.getInstance(obj)); + } + + return null; + } + + /** + * Create a single valued RDN. + * + * @param oid RDN type. + * @param value RDN value. + */ + public RDN(ASN1ObjectIdentifier oid, ASN1Encodable value) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(oid); + v.add(value); + + this.values = new DERSet(new DERSequence(v)); + } + + public RDN(AttributeTypeAndValue attrTAndV) + { + this.values = new DERSet(attrTAndV); + } + + /** + * Create a multi-valued RDN. + * + * @param aAndVs attribute type/value pairs making up the RDN + */ + public RDN(AttributeTypeAndValue[] aAndVs) + { + this.values = new DERSet(aAndVs); + } + + public boolean isMultiValued() + { + return this.values.size() > 1; + } + + /** + * Return the number of AttributeTypeAndValue objects in this RDN, + * + * @return size of RDN, greater than 1 if multi-valued. + */ + public int size() + { + return this.values.size(); + } + + public AttributeTypeAndValue getFirst() + { + if (this.values.size() == 0) + { + return null; + } + + return AttributeTypeAndValue.getInstance(this.values.getObjectAt(0)); + } + + public AttributeTypeAndValue[] getTypesAndValues() + { + AttributeTypeAndValue[] tmp = new AttributeTypeAndValue[values.size()]; + + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = AttributeTypeAndValue.getInstance(values.getObjectAt(i)); + } + + return tmp; + } + + /** + *
+     * RelativeDistinguishedName ::=
+     *                     SET OF AttributeTypeAndValue
+
+     * AttributeTypeAndValue ::= SEQUENCE {
+     *        type     AttributeType,
+     *        value    AttributeValue }
+     * 
+ * @return this object as an ASN1Primitive type + */ + public ASN1Primitive toASN1Primitive() + { + return values; + } +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/X500Name.java b/core/src/main/java/org/spongycastle/asn1/x500/X500Name.java new file mode 100644 index 00000000..a5e01b31 --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/X500Name.java @@ -0,0 +1,326 @@ +package org.spongycastle.asn1.x500; + +import java.util.Enumeration; + +import org.spongycastle.asn1.ASN1Choice; +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.ASN1TaggedObject; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.x500.style.BCStyle; + +/** + *
+ *     Name ::= CHOICE {
+ *                       RDNSequence }
+ *
+ *     RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ *     RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
+ *
+ *     AttributeTypeAndValue ::= SEQUENCE {
+ *                                   type  OBJECT IDENTIFIER,
+ *                                   value ANY }
+ * 
+ */ +public class X500Name + extends ASN1Object + implements ASN1Choice +{ + private static X500NameStyle defaultStyle = BCStyle.INSTANCE; + + private boolean isHashCodeCalculated; + private int hashCodeValue; + + private X500NameStyle style; + private RDN[] rdns; + + public X500Name(X500NameStyle style, X500Name name) + { + this.rdns = name.rdns; + this.style = style; + } + + /** + * Return a X500Name based on the passed in tagged object. + * + * @param obj tag object holding name. + * @param explicit true if explicitly tagged false otherwise. + * @return the X500Name + */ + public static X500Name getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + // must be true as choice item + return getInstance(ASN1Sequence.getInstance(obj, true)); + } + + public static X500Name getInstance( + Object obj) + { + if (obj instanceof X500Name) + { + return (X500Name)obj; + } + else if (obj != null) + { + return new X500Name(ASN1Sequence.getInstance(obj)); + } + + return null; + } + + public static X500Name getInstance( + X500NameStyle style, + Object obj) + { + if (obj instanceof X500Name) + { + return getInstance(style, ((X500Name)obj).toASN1Primitive()); + } + else if (obj != null) + { + return new X500Name(style, ASN1Sequence.getInstance(obj)); + } + + return null; + } + + /** + * Constructor from ASN1Sequence + * + * the principal will be a list of constructed sets, each containing an (OID, String) pair. + */ + private X500Name( + ASN1Sequence seq) + { + this(defaultStyle, seq); + } + + private X500Name( + X500NameStyle style, + ASN1Sequence seq) + { + this.style = style; + this.rdns = new RDN[seq.size()]; + + int index = 0; + + for (Enumeration e = seq.getObjects(); e.hasMoreElements();) + { + rdns[index++] = RDN.getInstance(e.nextElement()); + } + } + + public X500Name( + RDN[] rDNs) + { + this(defaultStyle, rDNs); + } + + public X500Name( + X500NameStyle style, + RDN[] rDNs) + { + this.rdns = rDNs; + this.style = style; + } + + public X500Name( + String dirName) + { + this(defaultStyle, dirName); + } + + public X500Name( + X500NameStyle style, + String dirName) + { + this(style.fromString(dirName)); + + this.style = style; + } + + /** + * return an array of RDNs in structure order. + * + * @return an array of RDN objects. + */ + public RDN[] getRDNs() + { + RDN[] tmp = new RDN[this.rdns.length]; + + System.arraycopy(rdns, 0, tmp, 0, tmp.length); + + return tmp; + } + + /** + * return an array of OIDs contained in the attribute type of each RDN in structure order. + * + * @return an array, possibly zero length, of ASN1ObjectIdentifiers objects. + */ + public ASN1ObjectIdentifier[] getAttributeTypes() + { + int count = 0; + + for (int i = 0; i != rdns.length; i++) + { + RDN rdn = rdns[i]; + + count += rdn.size(); + } + + ASN1ObjectIdentifier[] res = new ASN1ObjectIdentifier[count]; + + count = 0; + + for (int i = 0; i != rdns.length; i++) + { + RDN rdn = rdns[i]; + + if (rdn.isMultiValued()) + { + AttributeTypeAndValue[] attr = rdn.getTypesAndValues(); + for (int j = 0; j != attr.length; j++) + { + res[count++] = attr[j].getType(); + } + } + else if (rdn.size() != 0) + { + res[count++] = rdn.getFirst().getType(); + } + } + + return res; + } + + /** + * return an array of RDNs containing the attribute type given by OID in structure order. + * + * @param attributeType the type OID we are looking for. + * @return an array, possibly zero length, of RDN objects. + */ + public RDN[] getRDNs(ASN1ObjectIdentifier attributeType) + { + RDN[] res = new RDN[rdns.length]; + int count = 0; + + for (int i = 0; i != rdns.length; i++) + { + RDN rdn = rdns[i]; + + if (rdn.isMultiValued()) + { + AttributeTypeAndValue[] attr = rdn.getTypesAndValues(); + for (int j = 0; j != attr.length; j++) + { + if (attr[j].getType().equals(attributeType)) + { + res[count++] = rdn; + break; + } + } + } + else + { + if (rdn.getFirst().getType().equals(attributeType)) + { + res[count++] = rdn; + } + } + } + + RDN[] tmp = new RDN[count]; + + System.arraycopy(res, 0, tmp, 0, tmp.length); + + return tmp; + } + + public ASN1Primitive toASN1Primitive() + { + return new DERSequence(rdns); + } + + public int hashCode() + { + if (isHashCodeCalculated) + { + return hashCodeValue; + } + + isHashCodeCalculated = true; + + hashCodeValue = style.calculateHashCode(this); + + return hashCodeValue; + } + + /** + * test for equality - note: case is ignored. + */ + public boolean equals(Object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj instanceof X500Name || obj instanceof ASN1Sequence)) + { + return false; + } + + ASN1Primitive derO = ((ASN1Encodable)obj).toASN1Primitive(); + + if (this.toASN1Primitive().equals(derO)) + { + return true; + } + + try + { + return style.areEqual(this, new X500Name(ASN1Sequence.getInstance(((ASN1Encodable)obj).toASN1Primitive()))); + } + catch (Exception e) + { + return false; + } + } + + public String toString() + { + return style.toString(this); + } + + /** + * Set the default style for X500Name construction. + * + * @param style an X500NameStyle + */ + public static void setDefaultStyle(X500NameStyle style) + { + if (style == null) + { + throw new NullPointerException("cannot set style to null"); + } + + defaultStyle = style; + } + + /** + * Return the current default style. + * + * @return default style for X500Name construction. + */ + public static X500NameStyle getDefaultStyle() + { + return defaultStyle; + } +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/X500NameBuilder.java b/core/src/main/java/org/spongycastle/asn1/x500/X500NameBuilder.java new file mode 100644 index 00000000..6640eff9 --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/X500NameBuilder.java @@ -0,0 +1,87 @@ +package org.spongycastle.asn1.x500; + +import java.util.Vector; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x500.style.BCStyle; + +public class X500NameBuilder +{ + private X500NameStyle template; + private Vector rdns = new Vector(); + + public X500NameBuilder() + { + this(BCStyle.INSTANCE); + } + + public X500NameBuilder(X500NameStyle template) + { + this.template = template; + } + + public X500NameBuilder addRDN(ASN1ObjectIdentifier oid, String value) + { + this.addRDN(oid, template.stringToValue(oid, value)); + + return this; + } + + public X500NameBuilder addRDN(ASN1ObjectIdentifier oid, ASN1Encodable value) + { + rdns.addElement(new RDN(oid, value)); + + return this; + } + + public X500NameBuilder addRDN(AttributeTypeAndValue attrTAndV) + { + rdns.addElement(new RDN(attrTAndV)); + + return this; + } + + public X500NameBuilder addMultiValuedRDN(ASN1ObjectIdentifier[] oids, String[] values) + { + ASN1Encodable[] vals = new ASN1Encodable[values.length]; + + for (int i = 0; i != vals.length; i++) + { + vals[i] = template.stringToValue(oids[i], values[i]); + } + + return addMultiValuedRDN(oids, vals); + } + + public X500NameBuilder addMultiValuedRDN(ASN1ObjectIdentifier[] oids, ASN1Encodable[] values) + { + AttributeTypeAndValue[] avs = new AttributeTypeAndValue[oids.length]; + + for (int i = 0; i != oids.length; i++) + { + avs[i] = new AttributeTypeAndValue(oids[i], values[i]); + } + + return addMultiValuedRDN(avs); + } + + public X500NameBuilder addMultiValuedRDN(AttributeTypeAndValue[] attrTAndVs) + { + rdns.addElement(new RDN(attrTAndVs)); + + return this; + } + + public X500Name build() + { + RDN[] vals = new RDN[rdns.size()]; + + for (int i = 0; i != vals.length; i++) + { + vals[i] = (RDN)rdns.elementAt(i); + } + + return new X500Name(template, vals); + } +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/X500NameStyle.java b/core/src/main/java/org/spongycastle/asn1/x500/X500NameStyle.java new file mode 100644 index 00000000..8c716170 --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/X500NameStyle.java @@ -0,0 +1,79 @@ +package org.spongycastle.asn1.x500; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; + +/** + * It turns out that the number of standard ways the fields in a DN should be + * encoded into their ASN.1 counterparts is rapidly approaching the + * number of machines on the internet. By default the X500Name class + * will produce UTF8Strings in line with the current recommendations (RFC 3280). + *

+ */ +public interface X500NameStyle +{ + /** + * Convert the passed in String value into the appropriate ASN.1 + * encoded object. + * + * @param oid the OID associated with the value in the DN. + * @param value the value of the particular DN component. + * @return the ASN.1 equivalent for the value. + */ + ASN1Encodable stringToValue(ASN1ObjectIdentifier oid, String value); + + /** + * Return the OID associated with the passed in name. + * + * @param attrName the string to match. + * @return an OID + */ + ASN1ObjectIdentifier attrNameToOID(String attrName); + + /** + * Return an array of RDN generated from the passed in String. + * @param dirName the String representation. + * @return an array of corresponding RDNs. + */ + RDN[] fromString(String dirName); + + /** + * Return true if the two names are equal. + * + * @param name1 first name for comparison. + * @param name2 second name for comparison. + * @return true if name1 = name 2, false otherwise. + */ + boolean areEqual(X500Name name1, X500Name name2); + + /** + * Calculate a hashCode for the passed in name. + * + * @param name the name the hashCode is required for. + * @return the calculated hashCode. + */ + int calculateHashCode(X500Name name); + + /** + * Convert the passed in X500Name to a String. + * @param name the name to convert. + * @return a String representation. + */ + String toString(X500Name name); + + /** + * Return the display name for toString() associated with the OID. + * + * @param oid the OID of interest. + * @return the name displayed in toString(), null if no mapping provided. + */ + String oidToDisplayName(ASN1ObjectIdentifier oid); + + /** + * Return the acceptable names in a String DN that map to OID. + * + * @param oid the OID of interest. + * @return an array of String aliases for the OID, zero length if there are none. + */ + String[] oidToAttrNames(ASN1ObjectIdentifier oid); +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/style/AbstractX500NameStyle.java b/core/src/main/java/org/spongycastle/asn1/x500/style/AbstractX500NameStyle.java new file mode 100644 index 00000000..dfab0377 --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/style/AbstractX500NameStyle.java @@ -0,0 +1,192 @@ +package org.spongycastle.asn1.x500.style; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERUTF8String; +import org.spongycastle.asn1.x500.AttributeTypeAndValue; +import org.spongycastle.asn1.x500.RDN; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x500.X500NameStyle; + +/** + * This class provides some default behavior and common implementation for a + * X500NameStyle. It should be easily extendable to support implementing the + * desired X500NameStyle. + */ +public abstract class AbstractX500NameStyle + implements X500NameStyle +{ + + /** + * Tool function to shallow copy a Hashtable. + * + * @param paramsMap table to copy + * @return the copy of the table + */ + public static Hashtable copyHashTable(Hashtable paramsMap) + { + Hashtable newTable = new Hashtable(); + + Enumeration keys = paramsMap.keys(); + while (keys.hasMoreElements()) + { + Object key = keys.nextElement(); + newTable.put(key, paramsMap.get(key)); + } + + return newTable; + } + + private int calcHashCode(ASN1Encodable enc) + { + String value = IETFUtils.valueToString(enc); + value = IETFUtils.canonicalize(value); + return value.hashCode(); + } + + public int calculateHashCode(X500Name name) + { + int hashCodeValue = 0; + RDN[] rdns = name.getRDNs(); + + // this needs to be order independent, like equals + for (int i = 0; i != rdns.length; i++) + { + if (rdns[i].isMultiValued()) + { + AttributeTypeAndValue[] atv = rdns[i].getTypesAndValues(); + + for (int j = 0; j != atv.length; j++) + { + hashCodeValue ^= atv[j].getType().hashCode(); + hashCodeValue ^= calcHashCode(atv[j].getValue()); + } + } + else + { + hashCodeValue ^= rdns[i].getFirst().getType().hashCode(); + hashCodeValue ^= calcHashCode(rdns[i].getFirst().getValue()); + } + } + + return hashCodeValue; + } + + + /** + * For all string values starting with '#' is assumed, that these are + * already valid ASN.1 objects encoded in hex. + *

+ * All other string values are send to + * {@link AbstractX500NameStyle#encodeStringValue(ASN1ObjectIdentifier, String)}. + *

+ * Subclasses should overwrite + * {@link AbstractX500NameStyle#encodeStringValue(ASN1ObjectIdentifier, String)} + * to change the encoding of specific types. + * + * @param oid the DN name of the value. + * @param value the String representation of the value. + */ + public ASN1Encodable stringToValue(ASN1ObjectIdentifier oid, String value) + { + if (value.length() != 0 && value.charAt(0) == '#') + { + try + { + return IETFUtils.valueFromHexString(value, 1); + } + catch (IOException e) + { + throw new RuntimeException("can't recode value for oid " + oid.getId()); + } + } + + if (value.length() != 0 && value.charAt(0) == '\\') + { + value = value.substring(1); + } + + return encodeStringValue(oid, value); + } + + /** + * Encoded every value into a UTF8String. + *

+ * Subclasses should overwrite + * this method to change the encoding of specific types. + *

+ * + * @param oid the DN oid of the value + * @param value the String representation of the value + * @return a the value encoded into a ASN.1 object. Never returns null. + */ + protected ASN1Encodable encodeStringValue(ASN1ObjectIdentifier oid, String value) + { + return new DERUTF8String(value); + } + + public boolean areEqual(X500Name name1, X500Name name2) + { + RDN[] rdns1 = name1.getRDNs(); + RDN[] rdns2 = name2.getRDNs(); + + if (rdns1.length != rdns2.length) + { + return false; + } + + boolean reverse = false; + + if (rdns1[0].getFirst() != null && rdns2[0].getFirst() != null) + { + reverse = !rdns1[0].getFirst().getType().equals(rdns2[0].getFirst().getType()); // guess forward + } + + for (int i = 0; i != rdns1.length; i++) + { + if (!foundMatch(reverse, rdns1[i], rdns2)) + { + return false; + } + } + + return true; + } + + private boolean foundMatch(boolean reverse, RDN rdn, RDN[] possRDNs) + { + if (reverse) + { + for (int i = possRDNs.length - 1; i >= 0; i--) + { + if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i])) + { + possRDNs[i] = null; + return true; + } + } + } + else + { + for (int i = 0; i != possRDNs.length; i++) + { + if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i])) + { + possRDNs[i] = null; + return true; + } + } + } + + return false; + } + + protected boolean rdnAreEqual(RDN rdn1, RDN rdn2) + { + return IETFUtils.rDNAreEqual(rdn1, rdn2); + } +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/style/BCStrictStyle.java b/core/src/main/java/org/spongycastle/asn1/x500/style/BCStrictStyle.java new file mode 100644 index 00000000..23609e72 --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/style/BCStrictStyle.java @@ -0,0 +1,36 @@ +package org.spongycastle.asn1.x500.style; + +import org.spongycastle.asn1.x500.RDN; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x500.X500NameStyle; + +/** + * Variation of BCStyle that insists on strict ordering for equality + * and hashCode comparisons + */ +public class BCStrictStyle + extends BCStyle +{ + public static final X500NameStyle INSTANCE = new BCStrictStyle(); + + public boolean areEqual(X500Name name1, X500Name name2) + { + RDN[] rdns1 = name1.getRDNs(); + RDN[] rdns2 = name2.getRDNs(); + + if (rdns1.length != rdns2.length) + { + return false; + } + + for (int i = 0; i != rdns1.length; i++) + { + if (!rdnAreEqual(rdns1[i], rdns2[i])) + { + return false; + } + } + + return true; + } +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/style/BCStyle.java b/core/src/main/java/org/spongycastle/asn1/x500/style/BCStyle.java new file mode 100644 index 00000000..cb92a0bd --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/style/BCStyle.java @@ -0,0 +1,349 @@ +package org.spongycastle.asn1.x500.style; + +import java.util.Hashtable; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1GeneralizedTime; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERIA5String; +import org.spongycastle.asn1.DERPrintableString; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x500.RDN; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x500.X500NameStyle; +import org.spongycastle.asn1.x509.X509ObjectIdentifiers; + +public class BCStyle + extends AbstractX500NameStyle +{ + /** + * country code - StringType(SIZE(2)) + */ + public static final ASN1ObjectIdentifier C = new ASN1ObjectIdentifier("2.5.4.6"); + + /** + * organization - StringType(SIZE(1..64)) + */ + public static final ASN1ObjectIdentifier O = new ASN1ObjectIdentifier("2.5.4.10"); + + /** + * organizational unit name - StringType(SIZE(1..64)) + */ + public static final ASN1ObjectIdentifier OU = new ASN1ObjectIdentifier("2.5.4.11"); + + /** + * Title + */ + public static final ASN1ObjectIdentifier T = new ASN1ObjectIdentifier("2.5.4.12"); + + /** + * common name - StringType(SIZE(1..64)) + */ + public static final ASN1ObjectIdentifier CN = new ASN1ObjectIdentifier("2.5.4.3"); + + /** + * device serial number name - StringType(SIZE(1..64)) + */ + public static final ASN1ObjectIdentifier SN = new ASN1ObjectIdentifier("2.5.4.5"); + + /** + * street - StringType(SIZE(1..64)) + */ + public static final ASN1ObjectIdentifier STREET = new ASN1ObjectIdentifier("2.5.4.9"); + + /** + * device serial number name - StringType(SIZE(1..64)) + */ + public static final ASN1ObjectIdentifier SERIALNUMBER = SN; + + /** + * locality name - StringType(SIZE(1..64)) + */ + public static final ASN1ObjectIdentifier L = new ASN1ObjectIdentifier("2.5.4.7"); + + /** + * state, or province name - StringType(SIZE(1..64)) + */ + public static final ASN1ObjectIdentifier ST = new ASN1ObjectIdentifier("2.5.4.8"); + + /** + * Naming attributes of type X520name + */ + public static final ASN1ObjectIdentifier SURNAME = new ASN1ObjectIdentifier("2.5.4.4"); + public static final ASN1ObjectIdentifier GIVENNAME = new ASN1ObjectIdentifier("2.5.4.42"); + public static final ASN1ObjectIdentifier INITIALS = new ASN1ObjectIdentifier("2.5.4.43"); + public static final ASN1ObjectIdentifier GENERATION = new ASN1ObjectIdentifier("2.5.4.44"); + public static final ASN1ObjectIdentifier UNIQUE_IDENTIFIER = new ASN1ObjectIdentifier("2.5.4.45"); + + /** + * businessCategory - DirectoryString(SIZE(1..128) + */ + public static final ASN1ObjectIdentifier BUSINESS_CATEGORY = new ASN1ObjectIdentifier( + "2.5.4.15"); + + /** + * postalCode - DirectoryString(SIZE(1..40) + */ + public static final ASN1ObjectIdentifier POSTAL_CODE = new ASN1ObjectIdentifier( + "2.5.4.17"); + + /** + * dnQualifier - DirectoryString(SIZE(1..64) + */ + public static final ASN1ObjectIdentifier DN_QUALIFIER = new ASN1ObjectIdentifier( + "2.5.4.46"); + + /** + * RFC 3039 Pseudonym - DirectoryString(SIZE(1..64) + */ + public static final ASN1ObjectIdentifier PSEUDONYM = new ASN1ObjectIdentifier( + "2.5.4.65"); + + + /** + * RFC 3039 DateOfBirth - GeneralizedTime - YYYYMMDD000000Z + */ + public static final ASN1ObjectIdentifier DATE_OF_BIRTH = new ASN1ObjectIdentifier( + "1.3.6.1.5.5.7.9.1"); + + /** + * RFC 3039 PlaceOfBirth - DirectoryString(SIZE(1..128) + */ + public static final ASN1ObjectIdentifier PLACE_OF_BIRTH = new ASN1ObjectIdentifier( + "1.3.6.1.5.5.7.9.2"); + + /** + * RFC 3039 Gender - PrintableString (SIZE(1)) -- "M", "F", "m" or "f" + */ + public static final ASN1ObjectIdentifier GENDER = new ASN1ObjectIdentifier( + "1.3.6.1.5.5.7.9.3"); + + /** + * RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166 + * codes only + */ + public static final ASN1ObjectIdentifier COUNTRY_OF_CITIZENSHIP = new ASN1ObjectIdentifier( + "1.3.6.1.5.5.7.9.4"); + + /** + * RFC 3039 CountryOfResidence - PrintableString (SIZE (2)) -- ISO 3166 + * codes only + */ + public static final ASN1ObjectIdentifier COUNTRY_OF_RESIDENCE = new ASN1ObjectIdentifier( + "1.3.6.1.5.5.7.9.5"); + + + /** + * ISIS-MTT NameAtBirth - DirectoryString(SIZE(1..64) + */ + public static final ASN1ObjectIdentifier NAME_AT_BIRTH = new ASN1ObjectIdentifier("1.3.36.8.3.14"); + + /** + * RFC 3039 PostalAddress - SEQUENCE SIZE (1..6) OF + * DirectoryString(SIZE(1..30)) + */ + public static final ASN1ObjectIdentifier POSTAL_ADDRESS = new ASN1ObjectIdentifier("2.5.4.16"); + + /** + * RFC 2256 dmdName + */ + public static final ASN1ObjectIdentifier DMD_NAME = new ASN1ObjectIdentifier("2.5.4.54"); + + /** + * id-at-telephoneNumber + */ + public static final ASN1ObjectIdentifier TELEPHONE_NUMBER = X509ObjectIdentifiers.id_at_telephoneNumber; + + /** + * id-at-name + */ + public static final ASN1ObjectIdentifier NAME = X509ObjectIdentifiers.id_at_name; + + /** + * Email address (RSA PKCS#9 extension) - IA5String. + *

Note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here. + */ + public static final ASN1ObjectIdentifier EmailAddress = PKCSObjectIdentifiers.pkcs_9_at_emailAddress; + + /** + * more from PKCS#9 + */ + public static final ASN1ObjectIdentifier UnstructuredName = PKCSObjectIdentifiers.pkcs_9_at_unstructuredName; + public static final ASN1ObjectIdentifier UnstructuredAddress = PKCSObjectIdentifiers.pkcs_9_at_unstructuredAddress; + + /** + * email address in Verisign certificates + */ + public static final ASN1ObjectIdentifier E = EmailAddress; + + /* + * others... + */ + public static final ASN1ObjectIdentifier DC = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.25"); + + /** + * LDAP User id. + */ + public static final ASN1ObjectIdentifier UID = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.1"); + + /** + * default look up table translating OID values into their common symbols following + * the convention in RFC 2253 with a few extras + */ + private static final Hashtable DefaultSymbols = new Hashtable(); + + /** + * look up table translating common symbols into their OIDS. + */ + private static final Hashtable DefaultLookUp = new Hashtable(); + + static + { + DefaultSymbols.put(C, "C"); + DefaultSymbols.put(O, "O"); + DefaultSymbols.put(T, "T"); + DefaultSymbols.put(OU, "OU"); + DefaultSymbols.put(CN, "CN"); + DefaultSymbols.put(L, "L"); + DefaultSymbols.put(ST, "ST"); + DefaultSymbols.put(SN, "SERIALNUMBER"); + DefaultSymbols.put(EmailAddress, "E"); + DefaultSymbols.put(DC, "DC"); + DefaultSymbols.put(UID, "UID"); + DefaultSymbols.put(STREET, "STREET"); + DefaultSymbols.put(SURNAME, "SURNAME"); + DefaultSymbols.put(GIVENNAME, "GIVENNAME"); + DefaultSymbols.put(INITIALS, "INITIALS"); + DefaultSymbols.put(GENERATION, "GENERATION"); + DefaultSymbols.put(UnstructuredAddress, "unstructuredAddress"); + DefaultSymbols.put(UnstructuredName, "unstructuredName"); + DefaultSymbols.put(UNIQUE_IDENTIFIER, "UniqueIdentifier"); + DefaultSymbols.put(DN_QUALIFIER, "DN"); + DefaultSymbols.put(PSEUDONYM, "Pseudonym"); + DefaultSymbols.put(POSTAL_ADDRESS, "PostalAddress"); + DefaultSymbols.put(NAME_AT_BIRTH, "NameAtBirth"); + DefaultSymbols.put(COUNTRY_OF_CITIZENSHIP, "CountryOfCitizenship"); + DefaultSymbols.put(COUNTRY_OF_RESIDENCE, "CountryOfResidence"); + DefaultSymbols.put(GENDER, "Gender"); + DefaultSymbols.put(PLACE_OF_BIRTH, "PlaceOfBirth"); + DefaultSymbols.put(DATE_OF_BIRTH, "DateOfBirth"); + DefaultSymbols.put(POSTAL_CODE, "PostalCode"); + DefaultSymbols.put(BUSINESS_CATEGORY, "BusinessCategory"); + DefaultSymbols.put(TELEPHONE_NUMBER, "TelephoneNumber"); + DefaultSymbols.put(NAME, "Name"); + + DefaultLookUp.put("c", C); + DefaultLookUp.put("o", O); + DefaultLookUp.put("t", T); + DefaultLookUp.put("ou", OU); + DefaultLookUp.put("cn", CN); + DefaultLookUp.put("l", L); + DefaultLookUp.put("st", ST); + DefaultLookUp.put("sn", SN); + DefaultLookUp.put("serialnumber", SN); + DefaultLookUp.put("street", STREET); + DefaultLookUp.put("emailaddress", E); + DefaultLookUp.put("dc", DC); + DefaultLookUp.put("e", E); + DefaultLookUp.put("uid", UID); + DefaultLookUp.put("surname", SURNAME); + DefaultLookUp.put("givenname", GIVENNAME); + DefaultLookUp.put("initials", INITIALS); + DefaultLookUp.put("generation", GENERATION); + DefaultLookUp.put("unstructuredaddress", UnstructuredAddress); + DefaultLookUp.put("unstructuredname", UnstructuredName); + DefaultLookUp.put("uniqueidentifier", UNIQUE_IDENTIFIER); + DefaultLookUp.put("dn", DN_QUALIFIER); + DefaultLookUp.put("pseudonym", PSEUDONYM); + DefaultLookUp.put("postaladdress", POSTAL_ADDRESS); + DefaultLookUp.put("nameofbirth", NAME_AT_BIRTH); + DefaultLookUp.put("countryofcitizenship", COUNTRY_OF_CITIZENSHIP); + DefaultLookUp.put("countryofresidence", COUNTRY_OF_RESIDENCE); + DefaultLookUp.put("gender", GENDER); + DefaultLookUp.put("placeofbirth", PLACE_OF_BIRTH); + DefaultLookUp.put("dateofbirth", DATE_OF_BIRTH); + DefaultLookUp.put("postalcode", POSTAL_CODE); + DefaultLookUp.put("businesscategory", BUSINESS_CATEGORY); + DefaultLookUp.put("telephonenumber", TELEPHONE_NUMBER); + DefaultLookUp.put("name", NAME); + } + + /** + * Singleton instance. + */ + public static final X500NameStyle INSTANCE = new BCStyle(); + + protected final Hashtable defaultLookUp; + protected final Hashtable defaultSymbols; + + protected BCStyle() + { + defaultSymbols = copyHashTable(DefaultSymbols); + defaultLookUp = copyHashTable(DefaultLookUp); + } + + protected ASN1Encodable encodeStringValue(ASN1ObjectIdentifier oid, + String value) { + if (oid.equals(EmailAddress) || oid.equals(DC)) + { + return new DERIA5String(value); + } + else if (oid.equals(DATE_OF_BIRTH)) // accept time string as well as # (for compatibility) + { + return new ASN1GeneralizedTime(value); + } + else if (oid.equals(C) || oid.equals(SN) || oid.equals(DN_QUALIFIER) + || oid.equals(TELEPHONE_NUMBER)) + { + return new DERPrintableString(value); + } + + return super.encodeStringValue(oid, value); + } + + public String oidToDisplayName(ASN1ObjectIdentifier oid) + { + return (String)DefaultSymbols.get(oid); + } + + public String[] oidToAttrNames(ASN1ObjectIdentifier oid) + { + return IETFUtils.findAttrNamesForOID(oid, defaultLookUp); + } + + public ASN1ObjectIdentifier attrNameToOID(String attrName) + { + return IETFUtils.decodeAttrName(attrName, defaultLookUp); + } + + public RDN[] fromString(String dirName) + { + return IETFUtils.rDNsFromString(dirName, this); + } + + public String toString(X500Name name) + { + StringBuffer buf = new StringBuffer(); + boolean first = true; + + RDN[] rdns = name.getRDNs(); + + for (int i = 0; i < rdns.length; i++) + { + if (first) + { + first = false; + } + else + { + buf.append(','); + } + + IETFUtils.appendRDN(buf, rdns[i], defaultSymbols); + } + + return buf.toString(); + } + + +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/style/IETFUtils.java b/core/src/main/java/org/spongycastle/asn1/x500/style/IETFUtils.java new file mode 100644 index 00000000..14961e4c --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/style/IETFUtils.java @@ -0,0 +1,572 @@ +package org.spongycastle.asn1.x500.style; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1String; +import org.spongycastle.asn1.DERUniversalString; +import org.spongycastle.asn1.x500.AttributeTypeAndValue; +import org.spongycastle.asn1.x500.RDN; +import org.spongycastle.asn1.x500.X500NameBuilder; +import org.spongycastle.asn1.x500.X500NameStyle; +import org.spongycastle.util.Strings; +import org.spongycastle.util.encoders.Hex; + +public class IETFUtils +{ + private static String unescape(String elt) + { + if (elt.length() == 0 || (elt.indexOf('\\') < 0 && elt.indexOf('"') < 0)) + { + return elt.trim(); + } + + char[] elts = elt.toCharArray(); + boolean escaped = false; + boolean quoted = false; + StringBuffer buf = new StringBuffer(elt.length()); + int start = 0; + + // if it's an escaped hash string and not an actual encoding in string form + // we need to leave it escaped. + if (elts[0] == '\\') + { + if (elts[1] == '#') + { + start = 2; + buf.append("\\#"); + } + } + + boolean nonWhiteSpaceEncountered = false; + int lastEscaped = 0; + char hex1 = 0; + + for (int i = start; i != elts.length; i++) + { + char c = elts[i]; + + if (c != ' ') + { + nonWhiteSpaceEncountered = true; + } + + if (c == '"') + { + if (!escaped) + { + quoted = !quoted; + } + else + { + buf.append(c); + } + escaped = false; + } + else if (c == '\\' && !(escaped || quoted)) + { + escaped = true; + lastEscaped = buf.length(); + } + else + { + if (c == ' ' && !escaped && !nonWhiteSpaceEncountered) + { + continue; + } + if (escaped && isHexDigit(c)) + { + if (hex1 != 0) + { + buf.append((char)(convertHex(hex1) * 16 + convertHex(c))); + escaped = false; + hex1 = 0; + continue; + } + hex1 = c; + continue; + } + buf.append(c); + escaped = false; + } + } + + if (buf.length() > 0) + { + while (buf.charAt(buf.length() - 1) == ' ' && lastEscaped != (buf.length() - 1)) + { + buf.setLength(buf.length() - 1); + } + } + + return buf.toString(); + } + + private static boolean isHexDigit(char c) + { + return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'); + } + + private static int convertHex(char c) + { + if ('0' <= c && c <= '9') + { + return c - '0'; + } + if ('a' <= c && c <= 'f') + { + return c - 'a' + 10; + } + return c - 'A' + 10; + } + + public static RDN[] rDNsFromString(String name, X500NameStyle x500Style) + { + X500NameTokenizer nTok = new X500NameTokenizer(name); + X500NameBuilder builder = new X500NameBuilder(x500Style); + + while (nTok.hasMoreTokens()) + { + String token = nTok.nextToken(); + + if (token.indexOf('+') > 0) + { + X500NameTokenizer pTok = new X500NameTokenizer(token, '+'); + X500NameTokenizer vTok = new X500NameTokenizer(pTok.nextToken(), '='); + + String attr = vTok.nextToken(); + + if (!vTok.hasMoreTokens()) + { + throw new IllegalArgumentException("badly formatted directory string"); + } + + String value = vTok.nextToken(); + ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr.trim()); + + if (pTok.hasMoreTokens()) + { + Vector oids = new Vector(); + Vector values = new Vector(); + + oids.addElement(oid); + values.addElement(unescape(value)); + + while (pTok.hasMoreTokens()) + { + vTok = new X500NameTokenizer(pTok.nextToken(), '='); + + attr = vTok.nextToken(); + + if (!vTok.hasMoreTokens()) + { + throw new IllegalArgumentException("badly formatted directory string"); + } + + value = vTok.nextToken(); + oid = x500Style.attrNameToOID(attr.trim()); + + + oids.addElement(oid); + values.addElement(unescape(value)); + } + + builder.addMultiValuedRDN(toOIDArray(oids), toValueArray(values)); + } + else + { + builder.addRDN(oid, unescape(value)); + } + } + else + { + X500NameTokenizer vTok = new X500NameTokenizer(token, '='); + + String attr = vTok.nextToken(); + + if (!vTok.hasMoreTokens()) + { + throw new IllegalArgumentException("badly formatted directory string"); + } + + String value = vTok.nextToken(); + ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr.trim()); + + builder.addRDN(oid, unescape(value)); + } + } + + return builder.build().getRDNs(); + } + + private static String[] toValueArray(Vector values) + { + String[] tmp = new String[values.size()]; + + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = (String)values.elementAt(i); + } + + return tmp; + } + + private static ASN1ObjectIdentifier[] toOIDArray(Vector oids) + { + ASN1ObjectIdentifier[] tmp = new ASN1ObjectIdentifier[oids.size()]; + + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = (ASN1ObjectIdentifier)oids.elementAt(i); + } + + return tmp; + } + + public static String[] findAttrNamesForOID( + ASN1ObjectIdentifier oid, + Hashtable lookup) + { + int count = 0; + for (Enumeration en = lookup.elements(); en.hasMoreElements();) + { + if (oid.equals(en.nextElement())) + { + count++; + } + } + + String[] aliases = new String[count]; + count = 0; + + for (Enumeration en = lookup.keys(); en.hasMoreElements();) + { + String key = (String)en.nextElement(); + if (oid.equals(lookup.get(key))) + { + aliases[count++] = key; + } + } + + return aliases; + } + + public static ASN1ObjectIdentifier decodeAttrName( + String name, + Hashtable lookUp) + { + if (Strings.toUpperCase(name).startsWith("OID.")) + { + return new ASN1ObjectIdentifier(name.substring(4)); + } + else if (name.charAt(0) >= '0' && name.charAt(0) <= '9') + { + return new ASN1ObjectIdentifier(name); + } + + ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)lookUp.get(Strings.toLowerCase(name)); + if (oid == null) + { + throw new IllegalArgumentException("Unknown object id - " + name + " - passed to distinguished name"); + } + + return oid; + } + + public static ASN1Encodable valueFromHexString( + String str, + int off) + throws IOException + { + byte[] data = new byte[(str.length() - off) / 2]; + for (int index = 0; index != data.length; index++) + { + char left = str.charAt((index * 2) + off); + char right = str.charAt((index * 2) + off + 1); + + data[index] = (byte)((convertHex(left) << 4) | convertHex(right)); + } + + return ASN1Primitive.fromByteArray(data); + } + + public static void appendRDN( + StringBuffer buf, + RDN rdn, + Hashtable oidSymbols) + { + if (rdn.isMultiValued()) + { + AttributeTypeAndValue[] atv = rdn.getTypesAndValues(); + boolean firstAtv = true; + + for (int j = 0; j != atv.length; j++) + { + if (firstAtv) + { + firstAtv = false; + } + else + { + buf.append('+'); + } + + IETFUtils.appendTypeAndValue(buf, atv[j], oidSymbols); + } + } + else + { + IETFUtils.appendTypeAndValue(buf, rdn.getFirst(), oidSymbols); + } + } + + public static void appendTypeAndValue( + StringBuffer buf, + AttributeTypeAndValue typeAndValue, + Hashtable oidSymbols) + { + String sym = (String)oidSymbols.get(typeAndValue.getType()); + + if (sym != null) + { + buf.append(sym); + } + else + { + buf.append(typeAndValue.getType().getId()); + } + + buf.append('='); + + buf.append(valueToString(typeAndValue.getValue())); + } + + public static String valueToString(ASN1Encodable value) + { + StringBuffer vBuf = new StringBuffer(); + + if (value instanceof ASN1String && !(value instanceof DERUniversalString)) + { + String v = ((ASN1String)value).getString(); + if (v.length() > 0 && v.charAt(0) == '#') + { + vBuf.append("\\" + v); + } + else + { + vBuf.append(v); + } + } + else + { + try + { + vBuf.append("#" + bytesToString(Hex.encode(value.toASN1Primitive().getEncoded(ASN1Encoding.DER)))); + } + catch (IOException e) + { + throw new IllegalArgumentException("Other value has no encoded form"); + } + } + + int end = vBuf.length(); + int index = 0; + + if (vBuf.length() >= 2 && vBuf.charAt(0) == '\\' && vBuf.charAt(1) == '#') + { + index += 2; + } + + while (index != end) + { + if ((vBuf.charAt(index) == ',') + || (vBuf.charAt(index) == '"') + || (vBuf.charAt(index) == '\\') + || (vBuf.charAt(index) == '+') + || (vBuf.charAt(index) == '=') + || (vBuf.charAt(index) == '<') + || (vBuf.charAt(index) == '>') + || (vBuf.charAt(index) == ';')) + { + vBuf.insert(index, "\\"); + index++; + end++; + } + + index++; + } + + int start = 0; + if (vBuf.length() > 0) + { + while (vBuf.length() > start && vBuf.charAt(start) == ' ') + { + vBuf.insert(start, "\\"); + start += 2; + } + } + + int endBuf = vBuf.length() - 1; + + while (endBuf >= 0 && vBuf.charAt(endBuf) == ' ') + { + vBuf.insert(endBuf, '\\'); + endBuf--; + } + + return vBuf.toString(); + } + + private static String bytesToString( + byte[] data) + { + char[] cs = new char[data.length]; + + for (int i = 0; i != cs.length; i++) + { + cs[i] = (char)(data[i] & 0xff); + } + + return new String(cs); + } + + public static String canonicalize(String s) + { + String value = Strings.toLowerCase(s.trim()); + + if (value.length() > 0 && value.charAt(0) == '#') + { + ASN1Primitive obj = decodeObject(value); + + if (obj instanceof ASN1String) + { + value = Strings.toLowerCase(((ASN1String)obj).getString().trim()); + } + } + + value = stripInternalSpaces(value); + + return value; + } + + private static ASN1Primitive decodeObject(String oValue) + { + try + { + return ASN1Primitive.fromByteArray(Hex.decode(oValue.substring(1))); + } + catch (IOException e) + { + throw new IllegalStateException("unknown encoding in name: " + e); + } + } + + public static String stripInternalSpaces( + String str) + { + StringBuffer res = new StringBuffer(); + + if (str.length() != 0) + { + char c1 = str.charAt(0); + + res.append(c1); + + for (int k = 1; k < str.length(); k++) + { + char c2 = str.charAt(k); + if (!(c1 == ' ' && c2 == ' ')) + { + res.append(c2); + } + c1 = c2; + } + } + + return res.toString(); + } + + public static boolean rDNAreEqual(RDN rdn1, RDN rdn2) + { + if (rdn1.isMultiValued()) + { + if (rdn2.isMultiValued()) + { + AttributeTypeAndValue[] atvs1 = rdn1.getTypesAndValues(); + AttributeTypeAndValue[] atvs2 = rdn2.getTypesAndValues(); + + if (atvs1.length != atvs2.length) + { + return false; + } + + for (int i = 0; i != atvs1.length; i++) + { + if (!atvAreEqual(atvs1[i], atvs2[i])) + { + return false; + } + } + } + else + { + return false; + } + } + else + { + if (!rdn2.isMultiValued()) + { + return atvAreEqual(rdn1.getFirst(), rdn2.getFirst()); + } + else + { + return false; + } + } + + return true; + } + + private static boolean atvAreEqual(AttributeTypeAndValue atv1, AttributeTypeAndValue atv2) + { + if (atv1 == atv2) + { + return true; + } + + if (atv1 == null) + { + return false; + } + + if (atv2 == null) + { + return false; + } + + ASN1ObjectIdentifier o1 = atv1.getType(); + ASN1ObjectIdentifier o2 = atv2.getType(); + + if (!o1.equals(o2)) + { + return false; + } + + String v1 = IETFUtils.canonicalize(IETFUtils.valueToString(atv1.getValue())); + String v2 = IETFUtils.canonicalize(IETFUtils.valueToString(atv2.getValue())); + + if (!v1.equals(v2)) + { + return false; + } + + return true; + } +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/style/RFC4519Style.java b/core/src/main/java/org/spongycastle/asn1/x500/style/RFC4519Style.java new file mode 100644 index 00000000..83825e6c --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/style/RFC4519Style.java @@ -0,0 +1,248 @@ +package org.spongycastle.asn1.x500.style; + +import java.util.Hashtable; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERIA5String; +import org.spongycastle.asn1.DERPrintableString; +import org.spongycastle.asn1.x500.RDN; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x500.X500NameStyle; + +public class RFC4519Style + extends AbstractX500NameStyle +{ + public static final ASN1ObjectIdentifier businessCategory = new ASN1ObjectIdentifier("2.5.4.15"); + public static final ASN1ObjectIdentifier c = new ASN1ObjectIdentifier("2.5.4.6"); + public static final ASN1ObjectIdentifier cn = new ASN1ObjectIdentifier("2.5.4.3"); + public static final ASN1ObjectIdentifier dc = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.25"); + public static final ASN1ObjectIdentifier description = new ASN1ObjectIdentifier("2.5.4.13"); + public static final ASN1ObjectIdentifier destinationIndicator = new ASN1ObjectIdentifier("2.5.4.27"); + public static final ASN1ObjectIdentifier distinguishedName = new ASN1ObjectIdentifier("2.5.4.49"); + public static final ASN1ObjectIdentifier dnQualifier = new ASN1ObjectIdentifier("2.5.4.46"); + public static final ASN1ObjectIdentifier enhancedSearchGuide = new ASN1ObjectIdentifier("2.5.4.47"); + public static final ASN1ObjectIdentifier facsimileTelephoneNumber = new ASN1ObjectIdentifier("2.5.4.23"); + public static final ASN1ObjectIdentifier generationQualifier = new ASN1ObjectIdentifier("2.5.4.44"); + public static final ASN1ObjectIdentifier givenName = new ASN1ObjectIdentifier("2.5.4.42"); + public static final ASN1ObjectIdentifier houseIdentifier = new ASN1ObjectIdentifier("2.5.4.51"); + public static final ASN1ObjectIdentifier initials = new ASN1ObjectIdentifier("2.5.4.43"); + public static final ASN1ObjectIdentifier internationalISDNNumber = new ASN1ObjectIdentifier("2.5.4.25"); + public static final ASN1ObjectIdentifier l = new ASN1ObjectIdentifier("2.5.4.7"); + public static final ASN1ObjectIdentifier member = new ASN1ObjectIdentifier("2.5.4.31"); + public static final ASN1ObjectIdentifier name = new ASN1ObjectIdentifier("2.5.4.41"); + public static final ASN1ObjectIdentifier o = new ASN1ObjectIdentifier("2.5.4.10"); + public static final ASN1ObjectIdentifier ou = new ASN1ObjectIdentifier("2.5.4.11"); + public static final ASN1ObjectIdentifier owner = new ASN1ObjectIdentifier("2.5.4.32"); + public static final ASN1ObjectIdentifier physicalDeliveryOfficeName = new ASN1ObjectIdentifier("2.5.4.19"); + public static final ASN1ObjectIdentifier postalAddress = new ASN1ObjectIdentifier("2.5.4.16"); + public static final ASN1ObjectIdentifier postalCode = new ASN1ObjectIdentifier("2.5.4.17"); + public static final ASN1ObjectIdentifier postOfficeBox = new ASN1ObjectIdentifier("2.5.4.18"); + public static final ASN1ObjectIdentifier preferredDeliveryMethod = new ASN1ObjectIdentifier("2.5.4.28"); + public static final ASN1ObjectIdentifier registeredAddress = new ASN1ObjectIdentifier("2.5.4.26"); + public static final ASN1ObjectIdentifier roleOccupant = new ASN1ObjectIdentifier("2.5.4.33"); + public static final ASN1ObjectIdentifier searchGuide = new ASN1ObjectIdentifier("2.5.4.14"); + public static final ASN1ObjectIdentifier seeAlso = new ASN1ObjectIdentifier("2.5.4.34"); + public static final ASN1ObjectIdentifier serialNumber = new ASN1ObjectIdentifier("2.5.4.5"); + public static final ASN1ObjectIdentifier sn = new ASN1ObjectIdentifier("2.5.4.4"); + public static final ASN1ObjectIdentifier st = new ASN1ObjectIdentifier("2.5.4.8"); + public static final ASN1ObjectIdentifier street = new ASN1ObjectIdentifier("2.5.4.9"); + public static final ASN1ObjectIdentifier telephoneNumber = new ASN1ObjectIdentifier("2.5.4.20"); + public static final ASN1ObjectIdentifier teletexTerminalIdentifier = new ASN1ObjectIdentifier("2.5.4.22"); + public static final ASN1ObjectIdentifier telexNumber = new ASN1ObjectIdentifier("2.5.4.21"); + public static final ASN1ObjectIdentifier title = new ASN1ObjectIdentifier("2.5.4.12"); + public static final ASN1ObjectIdentifier uid = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.1"); + public static final ASN1ObjectIdentifier uniqueMember = new ASN1ObjectIdentifier("2.5.4.50"); + public static final ASN1ObjectIdentifier userPassword = new ASN1ObjectIdentifier("2.5.4.35"); + public static final ASN1ObjectIdentifier x121Address = new ASN1ObjectIdentifier("2.5.4.24"); + public static final ASN1ObjectIdentifier x500UniqueIdentifier = new ASN1ObjectIdentifier("2.5.4.45"); + + /** + * default look up table translating OID values into their common symbols following + * the convention in RFC 2253 with a few extras + */ + private static final Hashtable DefaultSymbols = new Hashtable(); + + /** + * look up table translating common symbols into their OIDS. + */ + private static final Hashtable DefaultLookUp = new Hashtable(); + + static + { + DefaultSymbols.put(businessCategory, "businessCategory"); + DefaultSymbols.put(c, "c"); + DefaultSymbols.put(cn, "cn"); + DefaultSymbols.put(dc, "dc"); + DefaultSymbols.put(description, "description"); + DefaultSymbols.put(destinationIndicator, "destinationIndicator"); + DefaultSymbols.put(distinguishedName, "distinguishedName"); + DefaultSymbols.put(dnQualifier, "dnQualifier"); + DefaultSymbols.put(enhancedSearchGuide, "enhancedSearchGuide"); + DefaultSymbols.put(facsimileTelephoneNumber, "facsimileTelephoneNumber"); + DefaultSymbols.put(generationQualifier, "generationQualifier"); + DefaultSymbols.put(givenName, "givenName"); + DefaultSymbols.put(houseIdentifier, "houseIdentifier"); + DefaultSymbols.put(initials, "initials"); + DefaultSymbols.put(internationalISDNNumber, "internationalISDNNumber"); + DefaultSymbols.put(l, "l"); + DefaultSymbols.put(member, "member"); + DefaultSymbols.put(name, "name"); + DefaultSymbols.put(o, "o"); + DefaultSymbols.put(ou, "ou"); + DefaultSymbols.put(owner, "owner"); + DefaultSymbols.put(physicalDeliveryOfficeName, "physicalDeliveryOfficeName"); + DefaultSymbols.put(postalAddress, "postalAddress"); + DefaultSymbols.put(postalCode, "postalCode"); + DefaultSymbols.put(postOfficeBox, "postOfficeBox"); + DefaultSymbols.put(preferredDeliveryMethod, "preferredDeliveryMethod"); + DefaultSymbols.put(registeredAddress, "registeredAddress"); + DefaultSymbols.put(roleOccupant, "roleOccupant"); + DefaultSymbols.put(searchGuide, "searchGuide"); + DefaultSymbols.put(seeAlso, "seeAlso"); + DefaultSymbols.put(serialNumber, "serialNumber"); + DefaultSymbols.put(sn, "sn"); + DefaultSymbols.put(st, "st"); + DefaultSymbols.put(street, "street"); + DefaultSymbols.put(telephoneNumber, "telephoneNumber"); + DefaultSymbols.put(teletexTerminalIdentifier, "teletexTerminalIdentifier"); + DefaultSymbols.put(telexNumber, "telexNumber"); + DefaultSymbols.put(title, "title"); + DefaultSymbols.put(uid, "uid"); + DefaultSymbols.put(uniqueMember, "uniqueMember"); + DefaultSymbols.put(userPassword, "userPassword"); + DefaultSymbols.put(x121Address, "x121Address"); + DefaultSymbols.put(x500UniqueIdentifier, "x500UniqueIdentifier"); + + DefaultLookUp.put("businesscategory", businessCategory); + DefaultLookUp.put("c", c); + DefaultLookUp.put("cn", cn); + DefaultLookUp.put("dc", dc); + DefaultLookUp.put("description", description); + DefaultLookUp.put("destinationindicator", destinationIndicator); + DefaultLookUp.put("distinguishedname", distinguishedName); + DefaultLookUp.put("dnqualifier", dnQualifier); + DefaultLookUp.put("enhancedsearchguide", enhancedSearchGuide); + DefaultLookUp.put("facsimiletelephonenumber", facsimileTelephoneNumber); + DefaultLookUp.put("generationqualifier", generationQualifier); + DefaultLookUp.put("givenname", givenName); + DefaultLookUp.put("houseidentifier", houseIdentifier); + DefaultLookUp.put("initials", initials); + DefaultLookUp.put("internationalisdnnumber", internationalISDNNumber); + DefaultLookUp.put("l", l); + DefaultLookUp.put("member", member); + DefaultLookUp.put("name", name); + DefaultLookUp.put("o", o); + DefaultLookUp.put("ou", ou); + DefaultLookUp.put("owner", owner); + DefaultLookUp.put("physicaldeliveryofficename", physicalDeliveryOfficeName); + DefaultLookUp.put("postaladdress", postalAddress); + DefaultLookUp.put("postalcode", postalCode); + DefaultLookUp.put("postofficebox", postOfficeBox); + DefaultLookUp.put("preferreddeliverymethod", preferredDeliveryMethod); + DefaultLookUp.put("registeredaddress", registeredAddress); + DefaultLookUp.put("roleoccupant", roleOccupant); + DefaultLookUp.put("searchguide", searchGuide); + DefaultLookUp.put("seealso", seeAlso); + DefaultLookUp.put("serialnumber", serialNumber); + DefaultLookUp.put("sn", sn); + DefaultLookUp.put("st", st); + DefaultLookUp.put("street", street); + DefaultLookUp.put("telephonenumber", telephoneNumber); + DefaultLookUp.put("teletexterminalidentifier", teletexTerminalIdentifier); + DefaultLookUp.put("telexnumber", telexNumber); + DefaultLookUp.put("title", title); + DefaultLookUp.put("uid", uid); + DefaultLookUp.put("uniquemember", uniqueMember); + DefaultLookUp.put("userpassword", userPassword); + DefaultLookUp.put("x121address", x121Address); + DefaultLookUp.put("x500uniqueidentifier", x500UniqueIdentifier); + + // TODO: need to add correct matching for equality comparisons. + } + + /** + * Singleton instance. + */ + public static final X500NameStyle INSTANCE = new RFC4519Style(); + + protected final Hashtable defaultLookUp; + protected final Hashtable defaultSymbols; + + protected RFC4519Style() + { + defaultSymbols = copyHashTable(DefaultSymbols); + defaultLookUp = copyHashTable(DefaultLookUp); + } + + protected ASN1Encodable encodeStringValue(ASN1ObjectIdentifier oid, + String value) { + if (oid.equals(dc)) + { + return new DERIA5String(value); + } + else if (oid.equals(c) || oid.equals(serialNumber) || oid.equals(dnQualifier) + || oid.equals(telephoneNumber)) + { + return new DERPrintableString(value); + } + + return super.encodeStringValue(oid, value); + } + + public String oidToDisplayName(ASN1ObjectIdentifier oid) + { + return (String)DefaultSymbols.get(oid); + } + + public String[] oidToAttrNames(ASN1ObjectIdentifier oid) + { + return IETFUtils.findAttrNamesForOID(oid, defaultLookUp); + } + + public ASN1ObjectIdentifier attrNameToOID(String attrName) + { + return IETFUtils.decodeAttrName(attrName, defaultLookUp); + } + + // parse backwards + public RDN[] fromString(String dirName) + { + RDN[] tmp = IETFUtils.rDNsFromString(dirName, this); + RDN[] res = new RDN[tmp.length]; + + for (int i = 0; i != tmp.length; i++) + { + res[res.length - i - 1] = tmp[i]; + } + + return res; + } + + // convert in reverse + public String toString(X500Name name) + { + StringBuffer buf = new StringBuffer(); + boolean first = true; + + RDN[] rdns = name.getRDNs(); + + for (int i = rdns.length - 1; i >= 0; i--) + { + if (first) + { + first = false; + } + else + { + buf.append(','); + } + + IETFUtils.appendRDN(buf, rdns[i], defaultSymbols); + } + + return buf.toString(); + } + + +} diff --git a/core/src/main/java/org/spongycastle/asn1/x500/style/X500NameTokenizer.java b/core/src/main/java/org/spongycastle/asn1/x500/style/X500NameTokenizer.java new file mode 100644 index 00000000..65ce9865 --- /dev/null +++ b/core/src/main/java/org/spongycastle/asn1/x500/style/X500NameTokenizer.java @@ -0,0 +1,90 @@ +package org.spongycastle.asn1.x500.style; + +/** + * class for breaking up an X500 Name into it's component tokens, ala + * java.util.StringTokenizer. We need this class as some of the + * lightweight Java environment don't support classes like + * StringTokenizer. + */ +public class X500NameTokenizer +{ + private String value; + private int index; + private char separator; + private StringBuffer buf = new StringBuffer(); + + public X500NameTokenizer( + String oid) + { + this(oid, ','); + } + + public X500NameTokenizer( + String oid, + char separator) + { + this.value = oid; + this.index = -1; + this.separator = separator; + } + + public boolean hasMoreTokens() + { + return (index != value.length()); + } + + public String nextToken() + { + if (index == value.length()) + { + return null; + } + + int end = index + 1; + boolean quoted = false; + boolean escaped = false; + + buf.setLength(0); + + while (end != value.length()) + { + char c = value.charAt(end); + + if (c == '"') + { + if (!escaped) + { + quoted = !quoted; + } + buf.append(c); + escaped = false; + } + else + { + if (escaped || quoted) + { + buf.append(c); + escaped = false; + } + else if (c == '\\') + { + buf.append(c); + escaped = true; + } + else if (c == separator) + { + break; + } + else + { + buf.append(c); + } + } + end++; + } + + index = end; + + return buf.toString(); + } +} -- cgit v1.2.3