1 module hunt.security.x509.X509CertificatePair;
2 
3 import hunt.security.cert.X509Certificate;
4 
5 import hunt.security.util.Cache;
6 import hunt.security.util.DerValue;
7 import hunt.security.util.ReferenceQueue;
8 
9 import hunt.Exceptions;
10 
11 /**
12  * This class represents an X.509 Certificate Pair object, which is primarily
13  * used to hold a pair of cross certificates issued between Certification
14  * Authorities. The ASN.1 structure is listed below. The forward certificate
15  * of the CertificatePair contains a certificate issued to this CA by another
16  * CA. The reverse certificate of the CertificatePair contains a certificate
17  * issued by this CA to another CA. When both the forward and the reverse
18  * certificates are present in the CertificatePair, the issuer name in one
19  * certificate shall match the subject name in the other and vice versa, and
20  * the subject key in one certificate shall be capable of verifying the
21  * digital signature on the other certificate and vice versa.  If a subject
22  * key in one certificate does not contain required key algorithm
23  * parameters, then the signature check involving that key is not done.<p>
24  *
25  * The ASN.1 syntax for this object is:
26  * <pre>
27  * CertificatePair      ::=     SEQUENCE {
28  *      forward [0]     Certificate OPTIONAL,
29  *      reverse [1]     Certificate OPTIONAL
30  *                      -- at least one of the pair shall be present -- }
31  * </pre><p>
32  *
33  * This structure uses EXPLICIT tagging. References: Annex A of
34  * X.509(2000), X.509(1997).
35  *
36  * @author      Sean Mullan
37  * @since       1.4
38  */
39 
40 class X509CertificatePair {
41 
42     /* ASN.1 explicit tags */
43     private enum byte TAG_FORWARD = 0;
44     private enum byte TAG_REVERSE = 1;
45 
46     private X509Certificate forward;
47     private X509Certificate reverse;
48     private byte[] encoded;
49 
50     private static Cache!(Object, X509CertificatePair) cache;
51 
52     static this()
53     {
54         cache = newSoftMemoryCache!(Object, X509CertificatePair)(750);
55     }
56 
57     /**
58      * Creates an empty instance of X509CertificatePair.
59      */
60     this() {}
61 
62     /**
63      * Creates an instance of X509CertificatePair. At least one of
64      * the pair must be non-null.
65      *
66      * @param forward The forward component of the certificate pair
67      *          which represents a certificate issued to this CA by other CAs.
68      * @param reverse The reverse component of the certificate pair
69      *          which represents a certificate issued by this CA to other CAs.
70      * @throws CertificateException If an exception occurs.
71      */
72     this(X509Certificate forward, X509Certificate reverse)
73                 {
74         if (forward is null && reverse is null) {
75             throw new CertificateException("at least one of certificate pair "
76                 ~ "must be non-null");
77         }
78 
79         this.forward = forward;
80         this.reverse = reverse;
81 
82         checkPair();
83     }
84 
85     /**
86      * Create a new X509CertificatePair from its encoding.
87      *
88      * For internal use only, external code should use generateCertificatePair.
89      */
90     private this(byte[] encoded) {
91         try {
92             parse(new DerValue(encoded));
93             this.encoded = encoded;
94         } catch (IOException ex) {
95             throw new CertificateException(ex.toString());
96         }
97         checkPair();
98     }
99 
100     /**
101      * Clear the cache for debugging.
102      */
103     static void clearCache() {
104         cache.clear();
105     }
106 
107     // /**
108     //  * Create a X509CertificatePair from its encoding. Uses cache lookup
109     //  * if possible.
110     //  */
111     // static synchronized X509CertificatePair generateCertificatePair
112     //         (byte[] encoded) {
113     //     Object key = new Cache.EqualByteArray(encoded);
114     //     X509CertificatePair pair = cache.get(key);
115     //     if (pair !is null) {
116     //         return pair;
117     //     }
118     //     pair = new X509CertificatePair(encoded);
119     //     key = new Cache.EqualByteArray(pair.encoded);
120     //     cache.put(key, pair);
121     //     return pair;
122     // }
123 
124     // /**
125     //  * Sets the forward component of the certificate pair.
126     //  */
127     // void setForward(X509Certificate cert) {
128     //     checkPair();
129     //     forward = cert;
130     // }
131 
132     // /**
133     //  * Sets the reverse component of the certificate pair.
134     //  */
135     // void setReverse(X509Certificate cert) {
136     //     checkPair();
137     //     reverse = cert;
138     // }
139 
140     // /**
141     //  * Returns the forward component of the certificate pair.
142     //  *
143     //  * @return The forward certificate, or null if not set.
144     //  */
145     // X509Certificate getForward() {
146     //     return forward;
147     // }
148 
149     // /**
150     //  * Returns the reverse component of the certificate pair.
151     //  *
152     //  * @return The reverse certificate, or null if not set.
153     //  */
154     // X509Certificate getReverse() {
155     //     return reverse;
156     // }
157 
158     // /**
159     //  * Return the DER encoded form of the certificate pair.
160     //  *
161     //  * @return The encoded form of the certificate pair.
162     //  * @throws CerticateEncodingException If an encoding exception occurs.
163     //  */
164     // byte[] getEncoded() throws CertificateEncodingException {
165     //     try {
166     //         if (encoded is null) {
167     //             DerOutputStream tmp = new DerOutputStream();
168     //             emit(tmp);
169     //             encoded = tmp.toByteArray();
170     //         }
171     //     } catch (IOException ex) {
172     //         throw new CertificateEncodingException(ex.toString());
173     //     }
174     //     return encoded;
175     // }
176 
177     // /**
178     //  * Return a printable representation of the certificate pair.
179     //  *
180     //  * @return A string describing the contents of the pair.
181     //  */
182     // override
183     // string toString() {
184     //     StringBuilder sb = new StringBuilder();
185     //     sb.append("X.509 Certificate Pair: [\n");
186     //     if (forward !is null)
187     //         sb.append("  Forward: ").append(forward).append("\n");
188     //     if (reverse !is null)
189     //         sb.append("  Reverse: ").append(reverse).append("\n");
190     //     sb.append("]");
191     //     return sb.toString();
192     // }
193 
194     /* Parse the encoded bytes */
195     private void parse(DerValue val)
196     {
197         if (val.tag != DerValue.tag_Sequence) {
198             throw new IOException
199                 ("Sequence tag missing for X509CertificatePair");
200         }
201 
202         // while (val.data !is null && val.data.available() != 0) {
203         //     DerValue opt = val.data.getDerValue();
204         //     short tag = (byte) (opt.tag & 0x01f);
205         //     switch (tag) {
206         //         case TAG_FORWARD:
207         //             if (opt.isContextSpecific() && opt.isConstructed()) {
208         //                 if (forward !is null) {
209         //                     throw new IOException("Duplicate forward "
210         //                         ~ "certificate in X509CertificatePair");
211         //                 }
212         //                 opt = opt.data.getDerValue();
213         //                 forward = X509Factory.intern
214         //                                 (new X509CertImpl(opt.toByteArray()));
215         //             }
216         //             break;
217         //         case TAG_REVERSE:
218         //             if (opt.isContextSpecific() && opt.isConstructed()) {
219         //                 if (reverse !is null) {
220         //                     throw new IOException("Duplicate reverse "
221         //                         ~ "certificate in X509CertificatePair");
222         //                 }
223         //                 opt = opt.data.getDerValue();
224         //                 reverse = X509Factory.intern
225         //                                 (new X509CertImpl(opt.toByteArray()));
226         //             }
227         //             break;
228         //         default:
229         //             throw new IOException("Invalid encoding of "
230         //                 ~ "X509CertificatePair");
231         //     }
232         // }
233         // if (forward is null && reverse is null) {
234         //     throw new CertificateException("at least one of certificate pair "
235         //         ~ "must be non-null");
236         // }
237         implementationMissing();
238     }
239 
240     // /* Translate to encoded bytes */
241     // private void emit(DerOutputStream outputStream)
242     //    , CertificateEncodingException
243     // {
244     //     DerOutputStream tagged = new DerOutputStream();
245 
246     //     if (forward !is null) {
247     //         DerOutputStream tmp = new DerOutputStream();
248     //         tmp.putDerValue(new DerValue(forward.getEncoded()));
249     //         tagged.write(DerValue.createTag(DerValue.TAG_CONTEXT,
250     //                      true, TAG_FORWARD), tmp);
251     //     }
252 
253     //     if (reverse !is null) {
254     //         DerOutputStream tmp = new DerOutputStream();
255     //         tmp.putDerValue(new DerValue(reverse.getEncoded()));
256     //         tagged.write(DerValue.createTag(DerValue.TAG_CONTEXT,
257     //                      true, TAG_REVERSE), tmp);
258     //     }
259 
260     //     outputStream.write(DerValue.tag_Sequence, tagged);
261     // }
262 
263     /*
264      * Check for a valid certificate pair
265      */
266     private void checkPair() {
267 
268         /* if either of pair is missing, return w/o error */
269         if (forward is null || reverse is null) {
270             return;
271         }
272         implementationMissing();
273         /*
274          * If both elements of the pair are present, check that they
275          * are a valid pair.
276          */
277         // X500Principal fwSubject = forward.getSubjectX500Principal();
278         // X500Principal fwIssuer = forward.getIssuerX500Principal();
279         // X500Principal rvSubject = reverse.getSubjectX500Principal();
280         // X500Principal rvIssuer = reverse.getIssuerX500Principal();
281         // if (!fwIssuer.equals(rvSubject) || !rvIssuer.equals(fwSubject)) {
282         //     throw new CertificateException("subject and issuer names in "
283         //         ~ "forward and reverse certificates do not match");
284         // }
285 
286         // /* check signatures unless key parameters are missing */
287         // try {
288         //     PublicKey pk = reverse.getPublicKey();
289         //     if (!(pk instanceof DSAPublicKey) ||
290         //                 ((DSAPublicKey)pk).getParams() !is null) {
291         //         forward.verify(pk);
292         //     }
293         //     pk = forward.getPublicKey();
294         //     if (!(pk instanceof DSAPublicKey) ||
295         //                 ((DSAPublicKey)pk).getParams() !is null) {
296         //         reverse.verify(pk);
297         //     }
298         // } catch (GeneralSecurityException e) {
299         //     throw new CertificateException("invalid signature: "
300         //         + e.getMessage());
301         // }
302     }
303 }