diff options
Diffstat (limited to 'src/main/model/x501')
-rw-r--r-- | src/main/model/x501/AttributeTypeAndValue.java | 9 | ||||
-rw-r--r-- | src/main/model/x501/Name.java | 107 |
2 files changed, 111 insertions, 5 deletions
diff --git a/src/main/model/x501/AttributeTypeAndValue.java b/src/main/model/x501/AttributeTypeAndValue.java index 54b3352..179d6ff 100644 --- a/src/main/model/x501/AttributeTypeAndValue.java +++ b/src/main/model/x501/AttributeTypeAndValue.java @@ -72,11 +72,16 @@ public class AttributeTypeAndValue extends ASN1Object { /** * EFFECTS: Return in TYPE=Value format. Type will be either x.x.x.x.x or human-readable strings like CN. Value is - * input-defined. + * input-defined. ',' '+' '=' will be escaped. */ @Override public String toString() { - return type.toString() + "=" + value.toString(); + return type.toString().replace(",", "\\,") + .replace("=", "\\=") + .replace("+", "\\+") + + "=" + value.toString().replace(",", "\\,") + .replace("=", "\\=") + .replace("+", "\\+"); } public ObjectIdentifier getType() { diff --git a/src/main/model/x501/Name.java b/src/main/model/x501/Name.java index 19cde56..7477005 100644 --- a/src/main/model/x501/Name.java +++ b/src/main/model/x501/Name.java @@ -1,8 +1,6 @@ package model.x501; -import model.asn1.ASN1Object; -import model.asn1.Encodable; -import model.asn1.Tag; +import model.asn1.*; import model.asn1.exceptions.ParseException; import model.asn1.parsing.BytesReader; @@ -53,6 +51,109 @@ public class Name extends ASN1Object { } /** + * EFFECTS: Parse OID after last KV and clear context if input is '='. Otherwise add to context. + * Throws {@link ParseException} if input is '+' or ',', or if the oid cannot be recognized. + * MODIFIES: context + */ + private static ObjectIdentifier handleKey(char c, List<Character> context) throws ParseException { + if (c == '=') { + if (context.isEmpty()) { + throw new ParseException("Unterminated key"); + } + final ObjectIdentifier oid = new ObjectIdentifier(ObjectIdentifier.TAG, null, + ObjectIdentifier.getKnown( + context.stream().map(Object::toString).collect(Collectors.joining("")))); + context.clear(); + return oid; + } else if (c == '+' || c == ',') { + throw new ParseException("Unterminated key part: " + context); + } else { + context.add(c); + return null; + } + } + + /** + * EFFECTS: Parse KV after '='. Clear context. + * Throws {@link ParseException} if context is empty. + * MODIFIES: context + * REQUIRES: curKey to be a valid OID + */ + private static AttributeTypeAndValue flushKV(ObjectIdentifier curKey, List<Character> context) + throws ParseException { + if (context.isEmpty()) { + throw new ParseException("Unterminated value"); + } + final AttributeTypeAndValue tv = new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null, curKey, + new PrintableString(PrintableString.TAG, null, + context.stream().map(Object::toString).collect(Collectors.joining("")))); + context.clear(); + return tv; + } + + /** + * EFFECTS: Handle value after =, optionally flush to rdns after ',', or add to curListKT if after '+'. Clears + * context if flushed, otherwise add to context. Returns whether switch to the state of reading key. + * Throws {@link ParseException} if c is '=' or context is empty. + * MODIFIES: context, curListKT, rdns + * REQUIRES: curKey to be a valid OID + */ + private static boolean handleValue(char c, List<Character> context, List<AttributeTypeAndValue> curListKT, + ObjectIdentifier curKey, List<RelativeDistinguishedName> rdns) + throws ParseException { + if (c == ',') { + if (context.isEmpty()) { + throw new ParseException("Unterminated value"); + } + curListKT.add(flushKV(curKey, context)); + rdns.add(new RelativeDistinguishedName(ASN1Object.TAG_SET, null, + curListKT.toArray(AttributeTypeAndValue[]::new))); + curListKT.clear(); + return true; + } else if (c == '+') { + curListKT.add(flushKV(curKey, context)); + return true; + } else if (c == '=') { + throw new ParseException("Unterminated value part: " + context); + } else { + context.add(c); + return false; + } + } + + /** + * EFFECTS: Parse the given DN string into structural X.509 RDN Sequence. + * Character literals = + , must be escaped. + * Values will always be PrintableString. + * Throws {@link ParseException} if invalid. + */ + public static Name parseString(String dn) throws ParseException { + char state = 0; // 0 - Key, 1 - Value; MSB: Escaped + List<RelativeDistinguishedName> rdns = new ArrayList<>(); + List<AttributeTypeAndValue> curListKT = new ArrayList<>(); + ObjectIdentifier curKey = null; + List<Character> context = new ArrayList<>(); + for (char c : (dn + ",").toCharArray()) { + if ((state >> 7) == 1) { + context.add(c); + state &= 127; + continue; + } else if (c == '\\') { + state |= 128; + continue; + } + if (state == 0) { + if ((curKey = handleKey(c, context)) != null) { + state = 1; + } + } else if (handleValue(c, context, curListKT, curKey, rdns)) { + state = 0; + } + } + return new Name(ASN1Object.TAG_SEQUENCE, null, rdns.toArray(RelativeDistinguishedName[]::new)); + } + + /** * EFFECTS: Encode the SEQUENCE OF into DER, keep order. RDNs will be encoded one-by-one. */ @Override |