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 }