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      * &lt;positive whole number&gt;.&lt;positive whole number&gt;.&lt;positive
362      * whole number&gt;.&lt;...&gt;
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 }