diff options
Diffstat (limited to 'libraries/spongycastle/core/src/main/java/org/spongycastle/asn1/x500/style/IETFUtils.java')
-rw-r--r-- | libraries/spongycastle/core/src/main/java/org/spongycastle/asn1/x500/style/IETFUtils.java | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/libraries/spongycastle/core/src/main/java/org/spongycastle/asn1/x500/style/IETFUtils.java b/libraries/spongycastle/core/src/main/java/org/spongycastle/asn1/x500/style/IETFUtils.java new file mode 100644 index 000000000..14961e4c9 --- /dev/null +++ b/libraries/spongycastle/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; + } +} |