1 module hunt.security.x509.X509CRLEntryImpl; 2 3 import hunt.security.cert.CRLReason; 4 import hunt.security.x509.CRLExtensions; 5 import hunt.security.x509.Extension; 6 import hunt.security.x509.SerialNumber; 7 import hunt.security.x500.X500Principal; 8 import hunt.security.x509.X509CRLEntry; 9 import hunt.security.x509.X509Extension; 10 11 import hunt.security.util.DerEncoder; 12 import hunt.security.util.DerValue; 13 import hunt.security.util.DerOutputStream; 14 import hunt.security.util.ObjectIdentifier; 15 16 import hunt.Exceptions; 17 import hunt.util.Comparator; 18 import hunt.text.Common; 19 import hunt.util.StringBuilder; 20 21 import hunt.collection; 22 23 import std.conv; 24 import std.datetime; 25 import std.format; 26 27 28 /** 29 * <p>Abstract class for a revoked certificate in a CRL. 30 * This class is for each entry in the <code>revokedCertificates</code>, 31 * so it deals with the inner <em>SEQUENCE</em>. 32 * The ASN.1 definition for this is: 33 * <pre> 34 * revokedCertificates SEQUENCE OF SEQUENCE { 35 * userCertificate CertificateSerialNumber, 36 * revocationDate ChoiceOfTime, 37 * crlEntryExtensions Extensions OPTIONAL 38 * -- if present, must be v2 39 * } OPTIONAL 40 * 41 * CertificateSerialNumber ::= INTEGER 42 * 43 * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension 44 * 45 * Extension ::= SEQUENCE { 46 * extnId OBJECT IDENTIFIER, 47 * critical BOOLEAN DEFAULT FALSE, 48 * extnValue OCTET STRING 49 * -- contains a DER encoding of a value 50 * -- of the type registered for use with 51 * -- the extnId object identifier value 52 * } 53 * </pre> 54 * 55 * @author Hemma Prafullchandra 56 */ 57 58 class X509CRLEntryImpl : X509CRLEntry { 59 60 private SerialNumber serialNumber = null; 61 private Date revocationDate; 62 private CRLExtensions extensions = null; 63 private byte[] revokedCert = null; 64 private X500Principal certIssuer; 65 66 private enum bool isExplicit = false; 67 private enum long YR_2050 = 2524636800000L; 68 69 /** 70 * Constructs a revoked certificate entry using the given 71 * serial number and revocation date. 72 * 73 * @param num the serial number of the revoked certificate. 74 * @param date the Date on which revocation took place. 75 */ 76 this(BigInteger num, Date date) { 77 this.serialNumber = new SerialNumber(num); 78 this.revocationDate = date; 79 } 80 81 /** 82 * Constructs a revoked certificate entry using the given 83 * serial number, revocation date and the entry 84 * extensions. 85 * 86 * @param num the serial number of the revoked certificate. 87 * @param date the Date on which revocation took place. 88 * @param crlEntryExts the extensions for this entry. 89 */ 90 this(BigInteger num, Date date, 91 CRLExtensions crlEntryExts) { 92 this.serialNumber = new SerialNumber(num); 93 this.revocationDate = date; 94 this.extensions = crlEntryExts; 95 } 96 97 /** 98 * Unmarshals a revoked certificate from its encoded form. 99 * 100 * @param revokedCert the encoded bytes. 101 * @exception CRLException on parsing errors. 102 */ 103 this(byte[] revokedCert) { 104 try { 105 parse(new DerValue(revokedCert)); 106 } catch (IOException e) { 107 this.revokedCert = null; 108 throw new CRLException("Parsing error: " ~ e.toString()); 109 } 110 } 111 112 /** 113 * Unmarshals a revoked certificate from its encoded form. 114 * 115 * @param derVal the DER value containing the revoked certificate. 116 * @exception CRLException on parsing errors. 117 */ 118 this(DerValue derValue) { 119 try { 120 parse(derValue); 121 } catch (IOException e) { 122 revokedCert = null; 123 throw new CRLException("Parsing error: " ~ e.toString()); 124 } 125 } 126 127 /** 128 * Returns true if this revoked certificate entry has 129 * extensions, otherwise false. 130 * 131 * @return true if this CRL entry has extensions, otherwise 132 * false. 133 */ 134 override bool hasExtensions() { 135 return (extensions !is null); 136 } 137 138 /** 139 * Encodes the revoked certificate to an output stream. 140 * 141 * @param outStrm an output stream to which the encoded revoked 142 * certificate is written. 143 * @exception CRLException on encoding errors. 144 */ 145 void encode(DerOutputStream outStrm) { 146 try { 147 if (revokedCert is null) { 148 DerOutputStream tmp = new DerOutputStream(); 149 // sequence { serialNumber, revocationDate, extensions } 150 serialNumber.encode(tmp); 151 152 // if (revocationDate.getTime() < YR_2050) { 153 // tmp.putUTCTime(revocationDate); 154 // } else { 155 // tmp.putGeneralizedTime(revocationDate); 156 // } 157 implementationMissing(); 158 if (extensions !is null) 159 extensions.encode(tmp, isExplicit); 160 161 DerOutputStream seq = new DerOutputStream(); 162 seq.write(DerValue.tag_Sequence, tmp); 163 164 revokedCert = seq.toByteArray(); 165 } 166 outStrm.write(revokedCert); 167 } catch (IOException e) { 168 throw new CRLException("Encoding error: " ~ e.toString()); 169 } 170 } 171 172 /** 173 * Returns the ASN.1 DER-encoded form of this CRL Entry, 174 * which corresponds to the inner SEQUENCE. 175 * 176 * @exception CRLException if an encoding error occurs. 177 */ 178 override byte[] getEncoded() { 179 return getEncoded0().dup; 180 } 181 182 // Called internally to avoid clone 183 private byte[] getEncoded0() { 184 if (revokedCert is null) 185 this.encode(new DerOutputStream()); 186 return revokedCert; 187 } 188 189 override 190 X500Principal getCertificateIssuer() { 191 return certIssuer; 192 } 193 194 void setCertificateIssuer(X500Principal crlIssuer, X500Principal certIssuer) { 195 if (crlIssuer == certIssuer) { 196 this.certIssuer = null; 197 } else { 198 this.certIssuer = certIssuer; 199 } 200 } 201 202 /** 203 * Gets the serial number from this X509CRLEntry, 204 * i.e. the <em>userCertificate</em>. 205 * 206 * @return the serial number. 207 */ 208 override BigInteger getSerialNumber() { 209 return serialNumber.getNumber(); 210 } 211 212 /** 213 * Gets the revocation date from this X509CRLEntry, 214 * the <em>revocationDate</em>. 215 * 216 * @return the revocation date. 217 */ 218 override Date getRevocationDate() { 219 return Date(revocationDate.dayOfGregorianCal); 220 } 221 222 /** 223 * This method is the overridden implementation of the getRevocationReason 224 * method in X509CRLEntry. It is better performance-wise since it returns 225 * cached values. 226 */ 227 override 228 CRLReason getRevocationReason() { 229 // Extension ext = getExtension(PKIXExtensions.ReasonCode_Id); 230 // if (ext is null) { 231 // return null; 232 // } 233 234 implementationMissing(); 235 // CRLReasonCodeExtension rcExt = cast(CRLReasonCodeExtension) ext; 236 // return rcExt.getReasonCode(); 237 return CRLReason.UNSPECIFIED; 238 } 239 240 241 /** 242 * get Reason Code from CRL entry. 243 * 244 * @returns int or null, if no such extension 245 * @throws IOException on error 246 */ 247 int getReasonCode() { 248 implementationMissing(); 249 return 0; 250 // Object obj = getExtension(PKIXExtensions.ReasonCode_Id); 251 // if (obj is null) 252 // return null; 253 // CRLReasonCodeExtension reasonCode = cast(CRLReasonCodeExtension)obj; 254 // return reasonCode.get(CRLReasonCodeExtension.REASON); 255 } 256 257 /** 258 * Returns a printable string of this revoked certificate. 259 * 260 * @return value of this revoked certificate in a printable form. 261 */ 262 override 263 string toString() { 264 StringBuilder sb = new StringBuilder(); 265 266 sb.append(serialNumber.toString()); 267 sb.append(" On: " ~ revocationDate.toString()); 268 if (certIssuer !is null) { 269 sb.append("\n Certificate issuer: " ~ certIssuer.toString()); 270 } 271 implementationMissing(); 272 // if (extensions !is null) { 273 // Extension[] exts = extensions.getAllExtensions(); 274 275 // sb.append("\n CRL Entry Extensions: " ~ exts.length); 276 // for (size_t i = 0; i < exts.length; i++) { 277 // sb.append("\n [" ~ (i+1).to!string() ~ "]: "); 278 // Extension ext = exts[i]; 279 // try { 280 // if (OIDMap.getClass(ext.getExtensionId()) is null) { 281 // sb.append(ext.toString()); 282 // byte[] extValue = ext.getExtensionValue(); 283 // if (extValue !is null) { 284 // DerOutputStream stream = new DerOutputStream(); 285 // stream.putOctetString(extValue); 286 // extValue = stream.toByteArray(); 287 // // HexDumpEncoder enc = new HexDumpEncoder(); 288 // sb.append("Extension unknown: " 289 // ~ "DER encoded OCTET string =\n" 290 // + format("%(%02X%)", extValue) ~ "\n"); 291 // } 292 // } else 293 // sb.append(ext.toString()); //sub-class exists 294 // } catch (Exception e) { 295 // sb.append(", Error parsing this extension"); 296 // } 297 // } 298 // } 299 sb.append("\n"); 300 return sb.toString(); 301 } 302 303 /** 304 * Return true if a critical extension is found that is 305 * not supported, otherwise return false. 306 */ 307 bool hasUnsupportedCriticalExtension() { 308 if (extensions is null) 309 return false; 310 return extensions.hasUnsupportedCriticalExtension(); 311 } 312 313 /** 314 * Gets a Set of the extension(s) marked CRITICAL in this 315 * X509CRLEntry. In the returned set, each extension is 316 * represented by its OID string. 317 * 318 * @return a set of the extension oid strings in the 319 * Object that are marked critical. 320 */ 321 Set!string getCriticalExtensionOIDs() { 322 if (extensions is null) { 323 return null; 324 } 325 Set!string extSet = new TreeSet!string(); 326 foreach (Extension ex ; extensions.getAllExtensions()) { 327 if (ex.isCritical()) { 328 extSet.add(ex.getExtensionId().toString()); 329 } 330 } 331 return extSet; 332 } 333 334 /** 335 * Gets a Set of the extension(s) marked NON-CRITICAL in this 336 * X509CRLEntry. In the returned set, each extension is 337 * represented by its OID string. 338 * 339 * @return a set of the extension oid strings in the 340 * Object that are marked critical. 341 */ 342 Set!string getNonCriticalExtensionOIDs() { 343 if (extensions is null) { 344 return null; 345 } 346 Set!string extSet = new TreeSet!string(); 347 foreach (Extension ex ; extensions.getAllExtensions()) { 348 if (!ex.isCritical()) { 349 extSet.add(ex.getExtensionId().toString()); 350 } 351 } 352 return extSet; 353 } 354 355 /** 356 * Gets the DER encoded OCTET string for the extension value 357 * (<em>extnValue</em>) identified by the passed in oid string. 358 * The <code>oid</code> string is 359 * represented by a set of positive whole number separated 360 * by ".", that means,<br> 361 * <positive whole number>.<positive whole number>.<positive 362 * whole number>.<...> 363 * 364 * @param oid the Object Identifier value for the extension. 365 * @return the DER encoded octet string of the extension value. 366 */ 367 byte[] getExtensionValue(string oid) { 368 if (extensions is null) 369 return null; 370 implementationMissing(); 371 return null; 372 373 // try { 374 // string extAlias = OIDMap.getName(new ObjectIdentifier(oid)); 375 // Extension crlExt = null; 376 377 // if (extAlias is null) { // may be unknown 378 // ObjectIdentifier findOID = new ObjectIdentifier(oid); 379 // Extension ex = null; 380 // ObjectIdentifier inCertOID; 381 // for (Enumeration!Extension e = extensions.getElements(); 382 // e.hasMoreElements();) { 383 // ex = e.nextElement(); 384 // inCertOID = ex.getExtensionId(); 385 // if (inCertOID.opEquals(cast(Object)findOID)) { 386 // crlExt = ex; 387 // break; 388 // } 389 // } 390 // } else 391 // crlExt = extensions.get(extAlias); 392 // if (crlExt is null) 393 // return null; 394 // byte[] extData = crlExt.getExtensionValue(); 395 // if (extData is null) 396 // return null; 397 398 // DerOutputStream stream = new DerOutputStream(); 399 // stream.putOctetString(extData); 400 // return stream.toByteArray(); 401 // } catch (Exception e) { 402 // return null; 403 // } 404 } 405 406 /** 407 * get an extension 408 * 409 * @param oid ObjectIdentifier of extension desired 410 * @returns Extension of type <extension> or null, if not found 411 */ 412 Extension getExtension(ObjectIdentifier oid) { 413 if (extensions is null) 414 return null; 415 416 // following returns null if no such OID in map 417 //XXX consider cloning this 418 // return extensions.get(OIDMap.getName(oid)); 419 implementationMissing(); 420 return null; 421 422 } 423 424 private void parse(DerValue derVal) { 425 426 if (derVal.tag != DerValue.tag_Sequence) { 427 throw new CRLException("Invalid encoded RevokedCertificate, " ~ 428 "starting sequence tag missing."); 429 } 430 // if (derVal.data.available() == 0) 431 // throw new CRLException("No data encoded for RevokedCertificates"); 432 433 // revokedCert = derVal.toByteArray(); 434 // // serial number 435 // DerInputStream stream = derVal.toDerInputStream(); 436 // DerValue val = stream.getDerValue(); 437 // this.serialNumber = new SerialNumber(val); 438 439 // // revocationDate 440 // int nextByte = derVal.data.peekByte(); 441 // if (cast(byte)nextByte == DerValue.tag_UtcTime) { 442 // this.revocationDate = derVal.data.getUTCTime(); 443 // } else if (cast(byte)nextByte == DerValue.tag_GeneralizedTime) { 444 // this.revocationDate = derVal.data.getGeneralizedTime(); 445 // } else 446 // throw new CRLException("Invalid encoding for revocation date"); 447 448 // if (derVal.data.available() == 0) 449 // return; // no extensions 450 451 // // crlEntryExtensions 452 // this.extensions = new CRLExtensions(derVal.toDerInputStream()); 453 implementationMissing(); 454 } 455 456 /** 457 * Utility method to convert an arbitrary instance of X509CRLEntry 458 * to a X509CRLEntryImpl. Does a cast if possible, otherwise reparses 459 * the encoding. 460 */ 461 static X509CRLEntryImpl toImpl(X509CRLEntry entry) 462 { 463 X509CRLEntryImpl impl = cast(X509CRLEntryImpl)entry; 464 if (impl !is null) { 465 return impl; 466 } else { 467 return new X509CRLEntryImpl(entry.getEncoded()); 468 } 469 } 470 471 /** 472 * Returns the CertificateIssuerExtension 473 * 474 * @return the CertificateIssuerExtension, or null if it does not exist 475 */ 476 // CertificateIssuerExtension getCertificateIssuerExtension() { 477 // return cast(CertificateIssuerExtension) 478 // getExtension(PKIXExtensions.CertificateIssuer_Id); 479 // } 480 481 /** 482 * Returns all extensions for this entry in a map 483 * @return the extension map, can be empty, but not null 484 */ 485 // Map!(string, CertExtension) getExtensions() { 486 // if (extensions is null) { 487 // return Collections.emptyMap!(string, CertExtension)(); 488 // } 489 // Extension[] exts = extensions.getAllExtensions(); 490 // Map!(string, CertExtension) map = new TreeMap!(string, CertExtension)(); 491 // foreach (Extension ext ; exts) { 492 // map.put(ext.getId(), ext); 493 // } 494 // return map; 495 // } 496 497 // override 498 int compareTo(X509CRLEntryImpl that) { 499 // auto v = getSerialNumber() - that.getSerialNumber(); 500 int compSerial = compare(getSerialNumber(), that.getSerialNumber()); 501 // if(v>0) compSerial = 1; 502 // else if(v <0) compSerial = -1; 503 504 if (compSerial != 0) { 505 return compSerial; 506 } 507 try { 508 byte[] thisEncoded = this.getEncoded0(); 509 byte[] thatEncoded = that.getEncoded0(); 510 for (size_t i=0; i<thisEncoded.length && i<thatEncoded.length; i++) { 511 int a = thisEncoded[i] & 0xff; 512 int b = thatEncoded[i] & 0xff; 513 if (a != b) return a-b; 514 } 515 return cast(int)(thisEncoded.length -thatEncoded.length); 516 } catch (CRLException ce) { 517 return -1; 518 } 519 } 520 }