1 module hunt.security.x509.Extension; 2 3 import hunt.security.cert.Extension; 4 5 import hunt.security.util.DerValue; 6 import hunt.security.util.DerInputStream; 7 import hunt.security.util.DerOutputStream; 8 import hunt.security.util.ObjectIdentifier; 9 10 import hunt.stream.Common; 11 import hunt.Exceptions; 12 13 /** 14 * Represent a X509 Extension Attribute. 15 * 16 * <p>Extensions are additional attributes which can be inserted in a X509 17 * v3 certificate. For example a "Driving License Certificate" could have 18 * the driving license number as a extension. 19 * 20 * <p>Extensions are represented as a sequence of the extension identifier 21 * (Object Identifier), a bool flag stating whether the extension is to 22 * be treated as being critical and the extension value itself (this is again 23 * a DER encoding of the extension value). 24 * <pre> 25 * ASN.1 definition of Extension: 26 * Extension ::= SEQUENCE { 27 * ExtensionId OBJECT IDENTIFIER, 28 * critical BOOLEAN DEFAULT FALSE, 29 * extensionValue OCTET STRING 30 * } 31 * </pre> 32 * All subclasses need to implement a constructor of the form 33 * <pre> 34 * <subclass> (Boolean, Object) 35 * </pre> 36 * where the Object is typically an array of DER encoded bytes. 37 * <p> 38 * @author Amit Kapoor 39 * @author Hemma Prafullchandra 40 */ 41 class Extension : CertExtension { 42 43 protected ObjectIdentifier extensionId = null; 44 protected bool critical = false; 45 protected byte[] extensionValue = null; 46 47 /** 48 * Default constructor. Used only by sub-classes. 49 */ 50 this() { } 51 52 /** 53 * Constructs an extension from a DER encoded array of bytes. 54 */ 55 this(DerValue derVal) { 56 57 implementationMissing(false); 58 DerInputStream stream; // = derVal.toDerInputStream(); 59 60 // Object identifier 61 extensionId = new ObjectIdentifier(stream); // stream.getOID(); 62 63 // If the criticality flag was false, it will not have been encoded. 64 // DerValue val = stream.getDerValue(); 65 // if (val.tag == DerValue.tag_Boolean) { 66 // critical = val.getBoolean(); 67 68 // // Extension value (DER encoded) 69 // val = stream.getDerValue(); 70 // extensionValue = val.getOctetString(); 71 // } else { 72 // critical = false; 73 // extensionValue = val.getOctetString(); 74 // } 75 } 76 77 /** 78 * Constructs an Extension from individual components of ObjectIdentifier, 79 * criticality and the DER encoded OctetString. 80 * 81 * @param extensionId the ObjectIdentifier of the extension 82 * @param critical the bool indicating if the extension is critical 83 * @param extensionValue the DER encoded octet string of the value. 84 */ 85 this(ObjectIdentifier extensionId, bool critical, 86 byte[] extensionValue) { 87 this.extensionId = extensionId; 88 this.critical = critical; 89 // passed in a DER encoded octet string, strip off the tag 90 // and length 91 DerValue inDerVal = new DerValue(extensionValue); 92 this.extensionValue = inDerVal.getOctetString(); 93 } 94 95 /** 96 * Constructs an Extension from another extension. To be used for 97 * creating decoded subclasses. 98 * 99 * @param ext the extension to create from. 100 */ 101 this(Extension ext) { 102 this.extensionId = ext.extensionId; 103 this.critical = ext.critical; 104 this.extensionValue = ext.extensionValue; 105 } 106 107 /** 108 * Constructs an Extension from individual components of ObjectIdentifier, 109 * criticality and the raw encoded extension value. 110 * 111 * @param extensionId the ObjectIdentifier of the extension 112 * @param critical the bool indicating if the extension is critical 113 * @param rawExtensionValue the raw DER-encoded extension value (this 114 * is not the encoded OctetString). 115 */ 116 static Extension newExtension(ObjectIdentifier extensionId, 117 bool critical, byte[] rawExtensionValue) { 118 Extension ext = new Extension(); 119 ext.extensionId = extensionId; 120 ext.critical = critical; 121 ext.extensionValue = rawExtensionValue; 122 return ext; 123 } 124 125 void encode(OutputStream stream) { 126 if (stream is null) { 127 throw new NullPointerException(); 128 } 129 130 DerOutputStream dos1 = new DerOutputStream(); 131 DerOutputStream dos2 = new DerOutputStream(); 132 133 // dos1.putOID(extensionId); 134 extensionId.encode(dos1); 135 if (critical) { 136 dos1.putBoolean(critical); 137 } 138 dos1.putOctetString(extensionValue); 139 140 dos2.write(DerValue.tag_Sequence, dos1); 141 stream.write(dos2.toByteArray()); 142 } 143 144 /** 145 * Write the extension to the DerOutputStream. 146 * 147 * @param stream the DerOutputStream to write the extension to. 148 * @exception IOException on encoding errors 149 */ 150 void encode(DerOutputStream stream) { 151 152 if (extensionId is null) 153 throw new IOException("Null OID to encode for the extension!"); 154 if (extensionValue is null) 155 throw new IOException("No value to encode for the extension!"); 156 157 DerOutputStream dos = new DerOutputStream(); 158 159 // dos.putOID(extensionId); 160 extensionId.encode(dos); 161 if (critical) 162 dos.putBoolean(critical); 163 dos.putOctetString(extensionValue); 164 165 stream.write(DerValue.tag_Sequence, dos); 166 } 167 168 /** 169 * Returns true if extension is critical. 170 */ 171 bool isCritical() { 172 return critical; 173 } 174 175 /** 176 * Returns the ObjectIdentifier of the extension. 177 */ 178 ObjectIdentifier getExtensionId() { 179 return extensionId; 180 } 181 182 byte[] getValue() { 183 return extensionValue.dup; 184 } 185 186 /** 187 * Returns the extension value as an byte array for further processing. 188 * Note, this is the raw DER value of the extension, not the DER 189 * encoded octet string which is in the certificate. 190 * This method does not return a clone; it is the responsibility of the 191 * caller to clone the array if necessary. 192 */ 193 byte[] getExtensionValue() { 194 return extensionValue; 195 } 196 197 string getId() { 198 return extensionId.toString(); 199 } 200 201 /** 202 * Returns the Extension in user readable form. 203 */ 204 override string toString() { 205 string s = "ObjectId: " ~ extensionId.toString(); 206 if (critical) { 207 s ~= " Criticality=true\n"; 208 } else { 209 s ~= " Criticality=false\n"; 210 } 211 return (s); 212 } 213 214 // Value to mix up the hash 215 private enum int hashMagic = 31; 216 217 /** 218 * Returns a hashcode value for this Extension. 219 * 220 * @return the hashcode value. 221 */ 222 override size_t toHash() @trusted nothrow { 223 size_t h = 0; 224 if (extensionValue !is null) { 225 byte[] val = extensionValue; 226 size_t len = val.length; 227 while (len > 0) 228 h += len * val[--len]; 229 } 230 h = h * hashMagic + extensionId.toHash(); 231 h = h * hashMagic + (critical?1231:1237); 232 return h; 233 } 234 235 /** 236 * Compares this Extension for equality with the specified 237 * object. If the <code>other</code> object is an 238 * <code>instanceof</code> <code>Extension</code>, then 239 * its encoded form is retrieved and compared with the 240 * encoded form of this Extension. 241 * 242 * @param other the object to test for equality with this Extension. 243 * @return true iff the other object is of type Extension, and the 244 * criticality flag, object identifier and encoded extension value of 245 * the two Extensions match, false otherwise. 246 */ 247 override bool opEquals(Object other) { 248 if (this is other) 249 return true; 250 Extension otherExt = cast(Extension) other; 251 if(otherExt is null) 252 return false; 253 if (critical != otherExt.critical) 254 return false; 255 if (!extensionId.opEquals(otherExt.extensionId)) 256 return false; 257 return extensionValue == otherExt.extensionValue; 258 } 259 } 260 261 alias X509Extension = Extension;