/** Create a X509CertificatePair from its encoding. Uses cache lookup if possible. */ public static synchronized X509CertificatePair generateCertificatePair(byte[] encoded) throws CertificateException { Object key = new Cache.EqualByteArray(encoded); X509CertificatePair pair = (X509CertificatePair) cache.get(key); if (pair != null) { return pair; } pair = new X509CertificatePair(encoded); key = new Cache.EqualByteArray(pair.encoded); cache.put(key, pair); return pair; }
/** * Generates an X.509 certificate object and initializes it with the data read from the input * stream <code>is</code>. * * @param is an input stream with the certificate data. * @return an X.509 certificate object initialized with the data from the input stream. * @exception CertificateException on parsing errors. */ public Certificate engineGenerateCertificate(InputStream is) throws CertificateException { if (is == null) { // clear the caches (for debugging) certCache.clear(); X509CertificatePair.clearCache(); throw new CertificateException("Missing input stream"); } try { byte[] encoding = readOneBlock(is); if (encoding != null) { X509CertImpl cert = getFromCache(certCache, encoding); if (cert != null) { return cert; } cert = new X509CertImpl(encoding); addToCache(certCache, cert.getEncodedInternal(), cert); return cert; } else { throw new IOException("Empty input"); } } catch (IOException ioe) { throw (CertificateException) new CertificateException("Could not parse certificate: " + ioe.toString()).initCause(ioe); } }
/** Add the X509CertImpl or X509CRLImpl to the cache. */ private static synchronized <V> void addToCache( Cache<Object, V> cache, byte[] encoding, V value) { if (encoding.length > ENC_MAX_LENGTH) { return; } Object key = new Cache.EqualByteArray(encoding); cache.put(key, value); }
/** * Generates an X.509 certificate revocation list (CRL) object and initializes it with the data * read from the given input stream <code>is</code>. * * @param is an input stream with the CRL data. * @return an X.509 CRL object initialized with the data from the input stream. * @exception CRLException on parsing errors. */ public CRL engineGenerateCRL(InputStream is) throws CRLException { if (is == null) { // clear the cache (for debugging) crlCache.clear(); throw new CRLException("Missing input stream"); } try { byte[] encoding = readOneBlock(is); if (encoding != null) { X509CRLImpl crl = getFromCache(crlCache, encoding); if (crl != null) { return crl; } crl = new X509CRLImpl(encoding); addToCache(crlCache, crl.getEncodedInternal(), crl); return crl; } else { throw new IOException("Empty input"); } } catch (IOException ioe) { throw new CRLException(ioe.getMessage()); } }
/** * This class defines a certificate factory for X.509 v3 certificates & certification paths, and * X.509 v2 certificate revocation lists (CRLs). * * @author Jan Luehe * @author Hemma Prafullchandra * @author Sean Mullan * @see java.security.cert.CertificateFactorySpi * @see java.security.cert.Certificate * @see java.security.cert.CertPath * @see java.security.cert.CRL * @see java.security.cert.X509Certificate * @see java.security.cert.X509CRL * @see sun.security.x509.X509CertImpl * @see sun.security.x509.X509CRLImpl */ public class X509Factory extends CertificateFactorySpi { public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; public static final String END_CERT = "-----END CERTIFICATE-----"; private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX private static final Cache<Object, X509CertImpl> certCache = Cache.newSoftMemoryCache(750); private static final Cache<Object, X509CRLImpl> crlCache = Cache.newSoftMemoryCache(750); /** * Generates an X.509 certificate object and initializes it with the data read from the input * stream <code>is</code>. * * @param is an input stream with the certificate data. * @return an X.509 certificate object initialized with the data from the input stream. * @exception CertificateException on parsing errors. */ public Certificate engineGenerateCertificate(InputStream is) throws CertificateException { if (is == null) { // clear the caches (for debugging) certCache.clear(); X509CertificatePair.clearCache(); throw new CertificateException("Missing input stream"); } try { byte[] encoding = readOneBlock(is); if (encoding != null) { X509CertImpl cert = getFromCache(certCache, encoding); if (cert != null) { return cert; } cert = new X509CertImpl(encoding); addToCache(certCache, cert.getEncodedInternal(), cert); return cert; } else { throw new IOException("Empty input"); } } catch (IOException ioe) { throw (CertificateException) new CertificateException("Could not parse certificate: " + ioe.toString()).initCause(ioe); } } /** * Read from the stream until length bytes have been read or EOF has been reached. Return the * number of bytes actually read. */ private static int readFully(InputStream in, ByteArrayOutputStream bout, int length) throws IOException { int read = 0; byte[] buffer = new byte[2048]; while (length > 0) { int n = in.read(buffer, 0, length < 2048 ? length : 2048); if (n <= 0) { break; } bout.write(buffer, 0, n); read += n; length -= n; } return read; } /** * Return an interned X509CertImpl for the given certificate. If the given X509Certificate or * X509CertImpl is already present in the cert cache, the cached object is returned. Otherwise, if * it is a X509Certificate, it is first converted to a X509CertImpl. Then the X509CertImpl is * added to the cache and returned. * * <p>Note that all certificates created via generateCertificate(InputStream) are already interned * and this method does not need to be called. It is useful for certificates that cannot be * created via generateCertificate() and for converting other X509Certificate implementations to * an X509CertImpl. */ public static synchronized X509CertImpl intern(X509Certificate c) throws CertificateException { if (c == null) { return null; } boolean isImpl = c instanceof X509CertImpl; byte[] encoding; if (isImpl) { encoding = ((X509CertImpl) c).getEncodedInternal(); } else { encoding = c.getEncoded(); } X509CertImpl newC = getFromCache(certCache, encoding); if (newC != null) { return newC; } if (isImpl) { newC = (X509CertImpl) c; } else { newC = new X509CertImpl(encoding); encoding = newC.getEncodedInternal(); } addToCache(certCache, encoding, newC); return newC; } /** * Return an interned X509CRLImpl for the given certificate. For more information, see * intern(X509Certificate). */ public static synchronized X509CRLImpl intern(X509CRL c) throws CRLException { if (c == null) { return null; } boolean isImpl = c instanceof X509CRLImpl; byte[] encoding; if (isImpl) { encoding = ((X509CRLImpl) c).getEncodedInternal(); } else { encoding = c.getEncoded(); } X509CRLImpl newC = getFromCache(crlCache, encoding); if (newC != null) { return newC; } if (isImpl) { newC = (X509CRLImpl) c; } else { newC = new X509CRLImpl(encoding); encoding = newC.getEncodedInternal(); } addToCache(crlCache, encoding, newC); return newC; } /** Get the X509CertImpl or X509CRLImpl from the cache. */ private static synchronized <K, V> V getFromCache(Cache<K, V> cache, byte[] encoding) { Object key = new Cache.EqualByteArray(encoding); return cache.get(key); } /** Add the X509CertImpl or X509CRLImpl to the cache. */ private static synchronized <V> void addToCache( Cache<Object, V> cache, byte[] encoding, V value) { if (encoding.length > ENC_MAX_LENGTH) { return; } Object key = new Cache.EqualByteArray(encoding); cache.put(key, value); } /** * Generates a <code>CertPath</code> object and initializes it with the data read from the <code> * InputStream</code> inStream. The data is assumed to be in the default encoding. * * @param inStream an <code>InputStream</code> containing the data * @return a <code>CertPath</code> initialized with the data from the <code>InputStream</code> * @exception CertificateException if an exception occurs while decoding * @since 1.4 */ public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException { if (inStream == null) { throw new CertificateException("Missing input stream"); } try { byte[] encoding = readOneBlock(inStream); if (encoding != null) { return new X509CertPath(new ByteArrayInputStream(encoding)); } else { throw new IOException("Empty input"); } } catch (IOException ioe) { throw new CertificateException(ioe.getMessage()); } } /** * Generates a <code>CertPath</code> object and initializes it with the data read from the <code> * InputStream</code> inStream. The data is assumed to be in the specified encoding. * * @param inStream an <code>InputStream</code> containing the data * @param encoding the encoding used for the data * @return a <code>CertPath</code> initialized with the data from the <code>InputStream</code> * @exception CertificateException if an exception occurs while decoding or the encoding requested * is not supported * @since 1.4 */ public CertPath engineGenerateCertPath(InputStream inStream, String encoding) throws CertificateException { if (inStream == null) { throw new CertificateException("Missing input stream"); } try { byte[] data = readOneBlock(inStream); if (data != null) { return new X509CertPath(new ByteArrayInputStream(data), encoding); } else { throw new IOException("Empty input"); } } catch (IOException ioe) { throw new CertificateException(ioe.getMessage()); } } /** * Generates a <code>CertPath</code> object and initializes it with a <code>List</code> of <code> * Certificate</code>s. * * <p>The certificates supplied must be of a type supported by the <code>CertificateFactory</code> * . They will be copied out of the supplied <code>List</code> object. * * @param certificates a <code>List</code> of <code>Certificate</code>s * @return a <code>CertPath</code> initialized with the supplied list of certificates * @exception CertificateException if an exception occurs * @since 1.4 */ public CertPath engineGenerateCertPath(List<? extends Certificate> certificates) throws CertificateException { return (new X509CertPath(certificates)); } /** * Returns an iteration of the <code>CertPath</code> encodings supported by this certificate * factory, with the default encoding first. * * <p>Attempts to modify the returned <code>Iterator</code> via its <code>remove</code> method * result in an <code>UnsupportedOperationException</code>. * * @return an <code>Iterator</code> over the names of the supported <code>CertPath</code> * encodings (as <code>String</code>s) * @since 1.4 */ public Iterator<String> engineGetCertPathEncodings() { return (X509CertPath.getEncodingsStatic()); } /** * Returns a (possibly empty) collection view of X.509 certificates read from the given input * stream <code>is</code>. * * @param is the input stream with the certificates. * @return a (possibly empty) collection view of X.509 certificate objects initialized with the * data from the input stream. * @exception CertificateException on parsing errors. */ public Collection<? extends java.security.cert.Certificate> engineGenerateCertificates( InputStream is) throws CertificateException { if (is == null) { throw new CertificateException("Missing input stream"); } try { return parseX509orPKCS7Cert(is); } catch (IOException ioe) { throw new CertificateException(ioe); } } /** * Generates an X.509 certificate revocation list (CRL) object and initializes it with the data * read from the given input stream <code>is</code>. * * @param is an input stream with the CRL data. * @return an X.509 CRL object initialized with the data from the input stream. * @exception CRLException on parsing errors. */ public CRL engineGenerateCRL(InputStream is) throws CRLException { if (is == null) { // clear the cache (for debugging) crlCache.clear(); throw new CRLException("Missing input stream"); } try { byte[] encoding = readOneBlock(is); if (encoding != null) { X509CRLImpl crl = getFromCache(crlCache, encoding); if (crl != null) { return crl; } crl = new X509CRLImpl(encoding); addToCache(crlCache, crl.getEncodedInternal(), crl); return crl; } else { throw new IOException("Empty input"); } } catch (IOException ioe) { throw new CRLException(ioe.getMessage()); } } /** * Returns a (possibly empty) collection view of X.509 CRLs read from the given input stream * <code>is</code>. * * @param is the input stream with the CRLs. * @return a (possibly empty) collection view of X.509 CRL objects initialized with the data from * the input stream. * @exception CRLException on parsing errors. */ public Collection<? extends java.security.cert.CRL> engineGenerateCRLs(InputStream is) throws CRLException { if (is == null) { throw new CRLException("Missing input stream"); } try { return parseX509orPKCS7CRL(is); } catch (IOException ioe) { throw new CRLException(ioe.getMessage()); } } /* * Parses the data in the given input stream as a sequence of DER * encoded X.509 certificates (in binary or base 64 encoded format) OR * as a single PKCS#7 encoded blob (in binary or base64 encoded format). */ private Collection<? extends java.security.cert.Certificate> parseX509orPKCS7Cert(InputStream is) throws CertificateException, IOException { Collection<X509CertImpl> coll = new ArrayList<>(); byte[] data = readOneBlock(is); if (data == null) { return new ArrayList<>(0); } try { PKCS7 pkcs7 = new PKCS7(data); X509Certificate[] certs = pkcs7.getCertificates(); // certs are optional in PKCS #7 if (certs != null) { return Arrays.asList(certs); } else { // no crls provided return new ArrayList<>(0); } } catch (ParsingException e) { while (data != null) { coll.add(new X509CertImpl(data)); data = readOneBlock(is); } } return coll; } /* * Parses the data in the given input stream as a sequence of DER encoded * X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7 * encoded blob (in binary or base 64 encoded format). */ private Collection<? extends java.security.cert.CRL> parseX509orPKCS7CRL(InputStream is) throws CRLException, IOException { Collection<X509CRLImpl> coll = new ArrayList<>(); byte[] data = readOneBlock(is); if (data == null) { return new ArrayList<>(0); } try { PKCS7 pkcs7 = new PKCS7(data); X509CRL[] crls = pkcs7.getCRLs(); // CRLs are optional in PKCS #7 if (crls != null) { return Arrays.asList(crls); } else { // no crls provided return new ArrayList<>(0); } } catch (ParsingException e) { while (data != null) { coll.add(new X509CRLImpl(data)); data = readOneBlock(is); } } return coll; } /** * Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded binary block or a * PEM-style BASE64-encoded ASCII data. In the latter case, it's de-BASE64'ed before return. * * <p>After the reading, the input stream pointer is after the BER block, or after the newline * character after the -----END SOMETHING----- line. * * @param is the InputStream * @returns byte block or null if end of stream * @throws IOException If any parsing error */ private static byte[] readOneBlock(InputStream is) throws IOException { // The first character of a BLOCK. int c = is.read(); if (c == -1) { return null; } if (c == DerValue.tag_Sequence) { ByteArrayOutputStream bout = new ByteArrayOutputStream(2048); bout.write(c); readBERInternal(is, bout, c); return bout.toByteArray(); } else { // Read BASE64 encoded data, might skip info at the beginning char[] data = new char[2048]; int pos = 0; // Step 1: Read until header is found int hyphen = (c == '-') ? 1 : 0; // count of consequent hyphens int last = (c == '-') ? -1 : c; // the char before hyphen while (true) { int next = is.read(); if (next == -1) { // We accept useless data after the last block, // say, empty lines. return null; } if (next == '-') { hyphen++; } else { hyphen = 0; last = next; } if (hyphen == 5 && (last == -1 || last == '\r' || last == '\n')) { break; } } // Step 2: Read the rest of header, determine the line end int end; StringBuffer header = new StringBuffer("-----"); while (true) { int next = is.read(); if (next == -1) { throw new IOException("Incomplete data"); } if (next == '\n') { end = '\n'; break; } if (next == '\r') { next = is.read(); if (next == -1) { throw new IOException("Incomplete data"); } if (next == '\n') { end = '\n'; } else { end = '\r'; data[pos++] = (char) next; } break; } header.append((char) next); } // Step 3: Read the data while (true) { int next = is.read(); if (next == -1) { throw new IOException("Incomplete data"); } if (next != '-') { data[pos++] = (char) next; if (pos >= data.length) { data = Arrays.copyOf(data, data.length + 1024); } } else { break; } } // Step 4: Consume the footer StringBuffer footer = new StringBuffer("-"); while (true) { int next = is.read(); // Add next == '\n' for maximum safety, in case endline // is not consistent. if (next == -1 || next == end || next == '\n') { break; } if (next != '\r') footer.append((char) next); } checkHeaderFooter(header.toString(), footer.toString()); return Base64.getMimeDecoder().decode(new String(data, 0, pos)); } } private static void checkHeaderFooter(String header, String footer) throws IOException { if (header.length() < 16 || !header.startsWith("-----BEGIN ") || !header.endsWith("-----")) { throw new IOException("Illegal header: " + header); } if (footer.length() < 14 || !footer.startsWith("-----END ") || !footer.endsWith("-----")) { throw new IOException("Illegal footer: " + footer); } String headerType = header.substring(11, header.length() - 5); String footerType = footer.substring(9, footer.length() - 5); if (!headerType.equals(footerType)) { throw new IOException("Header and footer do not match: " + header + " " + footer); } } /** * Read one BER data block. This method is aware of indefinite-length BER encoding and will read * all of the sub-sections in a recursive way * * @param is Read from this InputStream * @param bout Write into this OutputStream * @param tag Tag already read (-1 mean not read) * @returns The current tag, used to check EOC in indefinite-length BER * @throws IOException Any parsing error */ private static int readBERInternal(InputStream is, ByteArrayOutputStream bout, int tag) throws IOException { if (tag == -1) { // Not read before the call, read now tag = is.read(); if (tag == -1) { throw new IOException("BER/DER tag info absent"); } if ((tag & 0x1f) == 0x1f) { throw new IOException("Multi octets tag not supported"); } bout.write(tag); } int n = is.read(); if (n == -1) { throw new IOException("BER/DER length info ansent"); } bout.write(n); int length; if (n == 0x80) { // Indefinite-length encoding if ((tag & 0x20) != 0x20) { throw new IOException("Non constructed encoding must have definite length"); } while (true) { int subTag = readBERInternal(is, bout, -1); if (subTag == 0) { // EOC, end of indefinite-length section break; } } } else { if (n < 0x80) { length = n; } else if (n == 0x81) { length = is.read(); if (length == -1) { throw new IOException("Incomplete BER/DER length info"); } bout.write(length); } else if (n == 0x82) { int highByte = is.read(); int lowByte = is.read(); if (lowByte == -1) { throw new IOException("Incomplete BER/DER length info"); } bout.write(highByte); bout.write(lowByte); length = (highByte << 8) | lowByte; } else if (n == 0x83) { int highByte = is.read(); int midByte = is.read(); int lowByte = is.read(); if (lowByte == -1) { throw new IOException("Incomplete BER/DER length info"); } bout.write(highByte); bout.write(midByte); bout.write(lowByte); length = (highByte << 16) | (midByte << 8) | lowByte; } else if (n == 0x84) { int highByte = is.read(); int nextByte = is.read(); int midByte = is.read(); int lowByte = is.read(); if (lowByte == -1) { throw new IOException("Incomplete BER/DER length info"); } if (highByte > 127) { throw new IOException("Invalid BER/DER data (a little huge?)"); } bout.write(highByte); bout.write(nextByte); bout.write(midByte); bout.write(lowByte); length = (highByte << 24) | (nextByte << 16) | (midByte << 8) | lowByte; } else { // ignore longer length forms throw new IOException("Invalid BER/DER data (too huge?)"); } if (readFully(is, bout, length) != length) { throw new IOException("Incomplete BER/DER data"); } } return tag; } }
/** Get the X509CertImpl or X509CRLImpl from the cache. */ private static synchronized <K, V> V getFromCache(Cache<K, V> cache, byte[] encoding) { Object key = new Cache.EqualByteArray(encoding); return cache.get(key); }
/** * This class represents an X.509 Certificate Pair object, which is primarily used to hold a pair of * cross certificates issued between Certification Authorities. The ASN.1 structure is listed below. * The forward certificate of the CertificatePair contains a certificate issued to this CA by * another CA. The reverse certificate of the CertificatePair contains a certificate issued by this * CA to another CA. When both the forward and the reverse certificates are present in the * CertificatePair, the issuer name in one certificate shall match the subject name in the other and * vice versa, and the subject public key in one certificate shall be capable of verifying the * digital signature on the other certificate and vice versa. If a subject public key in one * certificate does not contain required key algorithm parameters, then the signature check * involving that key is not done. * * <p>The ASN.1 syntax for this object is: * * <pre> * CertificatePair ::= SEQUENCE { * forward [0] Certificate OPTIONAL, * reverse [1] Certificate OPTIONAL * -- at least one of the pair shall be present -- } * </pre> * * <p>This structure uses EXPLICIT tagging. References: Annex A of X.509(2000), X.509(1997). * * @author Sean Mullan * @since 1.4 */ public class X509CertificatePair { /* ASN.1 explicit tags */ private static final byte TAG_FORWARD = 0; private static final byte TAG_REVERSE = 1; private X509Certificate forward; private X509Certificate reverse; private byte[] encoded; private static final Cache cache = Cache.newSoftMemoryCache(750); /** Creates an empty instance of X509CertificatePair. */ public X509CertificatePair() {} /** * Creates an instance of X509CertificatePair. At least one of the pair must be non-null. * * @param forward The forward component of the certificate pair which represents a certificate * issued to this CA by other CAs. * @param reverse The reverse component of the certificate pair which represents a certificate * issued by this CA to other CAs. * @throws CertificateException If an exception occurs. */ public X509CertificatePair(X509Certificate forward, X509Certificate reverse) throws CertificateException { if (forward == null && reverse == null) { throw new CertificateException("at least one of certificate pair " + "must be non-null"); } this.forward = forward; this.reverse = reverse; checkPair(); } /** * Create a new X509CertificatePair from its encoding. * * <p>For internal use only, external code should use generateCertificatePair. */ private X509CertificatePair(byte[] encoded) throws CertificateException { try { parse(new DerValue(encoded)); this.encoded = encoded; } catch (IOException ex) { throw new CertificateException(ex.toString()); } checkPair(); } /** Clear the cache for debugging. */ public static synchronized void clearCache() { cache.clear(); } /** Create a X509CertificatePair from its encoding. Uses cache lookup if possible. */ public static synchronized X509CertificatePair generateCertificatePair(byte[] encoded) throws CertificateException { Object key = new Cache.EqualByteArray(encoded); X509CertificatePair pair = (X509CertificatePair) cache.get(key); if (pair != null) { return pair; } pair = new X509CertificatePair(encoded); key = new Cache.EqualByteArray(pair.encoded); cache.put(key, pair); return pair; } /** Sets the forward component of the certificate pair. */ public void setForward(X509Certificate cert) throws CertificateException { checkPair(); forward = cert; } /** Sets the reverse component of the certificate pair. */ public void setReverse(X509Certificate cert) throws CertificateException { checkPair(); reverse = cert; } /** * Returns the forward component of the certificate pair. * * @return The forward certificate, or null if not set. */ public X509Certificate getForward() { return forward; } /** * Returns the reverse component of the certificate pair. * * @return The reverse certificate, or null if not set. */ public X509Certificate getReverse() { return reverse; } /** * Return the DER encoded form of the certificate pair. * * @return The encoded form of the certificate pair. * @throws CerticateEncodingException If an encoding exception occurs. */ public byte[] getEncoded() throws CertificateEncodingException { try { if (encoded == null) { DerOutputStream tmp = new DerOutputStream(); emit(tmp); encoded = tmp.toByteArray(); } } catch (IOException ex) { throw new CertificateEncodingException(ex.toString()); } return encoded; } /** * Return a printable representation of the certificate pair. * * @return A String describing the contents of the pair. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append("X.509 Certificate Pair: [\n"); if (forward != null) sb.append(" Forward: " + forward + "\n"); if (reverse != null) sb.append(" Reverse: " + reverse + "\n"); sb.append("]"); return sb.toString(); } /* Parse the encoded bytes */ private void parse(DerValue val) throws IOException, CertificateException { if (val.tag != DerValue.tag_Sequence) { throw new IOException("Sequence tag missing for X509CertificatePair"); } while (val.data != null && val.data.available() != 0) { DerValue opt = val.data.getDerValue(); short tag = (byte) (opt.tag & 0x01f); switch (tag) { case TAG_FORWARD: if (opt.isContextSpecific() && opt.isConstructed()) { if (forward != null) { throw new IOException("Duplicate forward " + "certificate in X509CertificatePair"); } opt = opt.data.getDerValue(); forward = X509Factory.intern(new X509CertImpl(opt.toByteArray())); } break; case TAG_REVERSE: if (opt.isContextSpecific() && opt.isConstructed()) { if (reverse != null) { throw new IOException("Duplicate reverse " + "certificate in X509CertificatePair"); } opt = opt.data.getDerValue(); reverse = X509Factory.intern(new X509CertImpl(opt.toByteArray())); } break; default: throw new IOException("Invalid encoding of " + "X509CertificatePair"); } } if (forward == null && reverse == null) { throw new CertificateException("at least one of certificate pair " + "must be non-null"); } } /* Translate to encoded bytes */ private void emit(DerOutputStream out) throws IOException, CertificateEncodingException { DerOutputStream tagged = new DerOutputStream(); if (forward != null) { DerOutputStream tmp = new DerOutputStream(); tmp.putDerValue(new DerValue(forward.getEncoded())); tagged.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_FORWARD), tmp); } if (reverse != null) { DerOutputStream tmp = new DerOutputStream(); tmp.putDerValue(new DerValue(reverse.getEncoded())); tagged.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_REVERSE), tmp); } out.write(DerValue.tag_Sequence, tagged); } /* * Check for a valid certificate pair */ private void checkPair() throws CertificateException { /* if either of pair is missing, return w/o error */ if (forward == null || reverse == null) { return; } /* * If both elements of the pair are present, check that they * are a valid pair. */ X500Principal fwSubject = forward.getSubjectX500Principal(); X500Principal fwIssuer = forward.getIssuerX500Principal(); X500Principal rvSubject = reverse.getSubjectX500Principal(); X500Principal rvIssuer = reverse.getIssuerX500Principal(); if (!fwIssuer.equals(rvSubject) || !rvIssuer.equals(fwSubject)) { throw new CertificateException( "subject and issuer names in " + "forward and reverse certificates do not match"); } /* check signatures unless key parameters are missing */ try { PublicKey pk = reverse.getPublicKey(); if (!(pk instanceof DSAPublicKey) || ((DSAPublicKey) pk).getParams() != null) { forward.verify(pk); } pk = forward.getPublicKey(); if (!(pk instanceof DSAPublicKey) || ((DSAPublicKey) pk).getParams() != null) { reverse.verify(pk); } } catch (GeneralSecurityException e) { throw new CertificateException("invalid signature: " + e.getMessage()); } } }
/** Clear the cache for debugging. */ public static synchronized void clearCache() { cache.clear(); }