/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Alexander Y. Kleymenov * @version $Revision$ */ package org.apache.harmony.security.provider.cert; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.cert.CRL; import java.security.cert.CRLException; import java.security.cert.CertPath; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactorySpi; import java.security.cert.X509CRL; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.harmony.luni.util.Base64; import org.apache.harmony.security.asn1.ASN1Constants; import org.apache.harmony.security.asn1.BerInputStream; import org.apache.harmony.security.internal.nls.Messages; import org.apache.harmony.security.pkcs7.ContentInfo; import org.apache.harmony.security.pkcs7.SignedData; import org.apache.harmony.security.x509.CertificateList; /** * X509 Certificate Factory Service Provider Interface Implementation. * It supports CRLs and Certificates in (PEM) ASN.1 DER encoded form, * and Certification Paths in PkiPath and PKCS7 formats. * For Certificates and CRLs factory maintains the caching * mechanisms allowing to speed up repeated Certificate/CRL * generation. * @see Cache */ public class X509CertFactoryImpl extends CertificateFactorySpi { // number of leading/trailing bytes used for cert hash computation private static int CERT_CACHE_SEED_LENGTH = 28; // certificate cache private static Cache CERT_CACHE = new Cache(CERT_CACHE_SEED_LENGTH); // number of leading/trailing bytes used for crl hash computation private static int CRL_CACHE_SEED_LENGTH = 24; // crl cache private static Cache CRL_CACHE = new Cache(CRL_CACHE_SEED_LENGTH); /** * Default constructor. * Creates the instance of Certificate Factory SPI ready for use. */ public X509CertFactoryImpl() { } /** * Generates the X.509 certificate from the data in the stream. * The data in the stream can be either in ASN.1 DER encoded X.509 * certificate, or PEM (Base64 encoding bounded by * "-----BEGIN CERTIFICATE-----" at the beginning and * "-----END CERTIFICATE-----" at the end) representation * of the former encoded form. * * Before the generation the encoded form is looked up in * the cache. If the cache contains the certificate with requested encoded * form it is returned from it, otherwise it is generated by ASN.1 * decoder. * * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificate(InputStream) * method documentation for more info */ public Certificate engineGenerateCertificate(InputStream inStream) throws CertificateException { if (inStream == null) { throw new CertificateException(Messages.getString("security.153")); //$NON-NLS-1$ } try { if (!inStream.markSupported()) { // create the mark supporting wrapper inStream = new RestoringInputStream(inStream); } // mark is needed to recognize the format of the provided encoding // (ASN.1 or PEM) inStream.mark(1); // check whether the provided certificate is in PEM encoded form if (inStream.read() == '-') { // decode PEM, retrieve CRL return getCertificate(decodePEM(inStream, CERT_BOUND_SUFFIX)); } else { inStream.reset(); // retrieve CRL return getCertificate(inStream); } } catch (IOException e) { throw new CertificateException(e); } } /** * Generates the collection of the certificates on the base of provided * via input stream encodings. * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificates(InputStream) * method documentation for more info */ public Collection engineGenerateCertificates(InputStream inStream) throws CertificateException { if (inStream == null) { throw new CertificateException(Messages.getString("security.153")); //$NON-NLS-1$ } ArrayList result = new ArrayList(); try { if (!inStream.markSupported()) { // create the mark supporting wrapper inStream = new RestoringInputStream(inStream); } // if it is PEM encoded form this array will contain the encoding // so ((it is PEM) <-> (encoding != null)) byte[] encoding = null; // The following by SEQUENCE ASN.1 tag, used for // recognizing the data format // (is it PKCS7 ContentInfo structure, X.509 Certificate, or // unsupported encoding) int second_asn1_tag = -1; inStream.mark(1); int ch; while ((ch = inStream.read()) != -1) { // check if it is PEM encoded form if (ch == '-') { // beginning of PEM encoding ('-' char) // decode PEM chunk and store its content (ASN.1 encoding) encoding = decodePEM(inStream, FREE_BOUND_SUFFIX); } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30) encoding = null; inStream.reset(); // prepare for data format determination inStream.mark(CERT_CACHE_SEED_LENGTH); } else { // unsupported data if (result.size() == 0) { throw new CertificateException( Messages.getString("security.15F")); //$NON-NLS-1$ } else { // it can be trailing user data, // so keep it in the stream inStream.reset(); return result; } } // Check the data format BerInputStream in = (encoding == null) ? new BerInputStream(inStream) : new BerInputStream(encoding); // read the next ASN.1 tag second_asn1_tag = in.next(); // inStream position changed if (encoding == null) { // keep whole structure in the stream inStream.reset(); } // check if it is a TBSCertificate structure if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) { if (result.size() == 0) { // there were not read X.509 Certificates, so // break the cycle and check // whether it is PKCS7 structure break; } else { // it can be trailing user data, // so return what we already read return result; } } else { if (encoding == null) { result.add(getCertificate(inStream)); } else { result.add(getCertificate(encoding)); } } // mark for the next iteration inStream.mark(1); } if (result.size() != 0) { // some Certificates have been read return result; } else if (ch == -1) { throw new CertificateException( Messages.getString("security.155")); //$NON-NLS-1$ } // else: check if it is PKCS7 if (second_asn1_tag == ASN1Constants.TAG_OID) { // it is PKCS7 ContentInfo structure, so decode it ContentInfo info = (ContentInfo) ((encoding != null) ? ContentInfo.ASN1.decode(encoding) : ContentInfo.ASN1.decode(inStream)); // retrieve SignedData SignedData data = info.getSignedData(); if (data == null) { throw new CertificateException( Messages.getString("security.154")); //$NON-NLS-1$ } List certs = data.getCertificates(); if (certs != null) { for (int i = 0; i < certs.size(); i++) { result.add(new X509CertImpl( (org.apache.harmony.security.x509.Certificate) certs.get(i))); } } return result; } // else: Unknown data format throw new CertificateException( Messages.getString("security.15F")); //$NON-NLS-1$ } catch (IOException e) { throw new CertificateException(e); } } /** * @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream) * method documentation for more info */ public CRL engineGenerateCRL(InputStream inStream) throws CRLException { if (inStream == null) { throw new CRLException(Messages.getString("security.153")); //$NON-NLS-1$ } try { if (!inStream.markSupported()) { // Create the mark supporting wrapper // Mark is needed to recognize the format // of provided encoding form (ASN.1 or PEM) inStream = new RestoringInputStream(inStream); } inStream.mark(1); // check whether the provided crl is in PEM encoded form if (inStream.read() == '-') { // decode PEM, retrieve CRL return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX)); } else { inStream.reset(); // retrieve CRL return getCRL(inStream); } } catch (IOException e) { throw new CRLException(e); } } /** * @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream) * method documentation for more info */ public Collection engineGenerateCRLs(InputStream inStream) throws CRLException { if (inStream == null) { throw new CRLException(Messages.getString("security.153")); //$NON-NLS-1$ } ArrayList result = new ArrayList(); try { if (!inStream.markSupported()) { inStream = new RestoringInputStream(inStream); } // if it is PEM encoded form this array will contain the encoding // so ((it is PEM) <-> (encoding != null)) byte[] encoding = null; // The following by SEQUENCE ASN.1 tag, used for // recognizing the data format // (is it PKCS7 ContentInfo structure, X.509 CRL, or // unsupported encoding) int second_asn1_tag = -1; inStream.mark(1); int ch; while ((ch = inStream.read()) != -1) { // check if it is PEM encoded form if (ch == '-') { // beginning of PEM encoding ('-' char) // decode PEM chunk and store its content (ASN.1 encoding) encoding = decodePEM(inStream, FREE_BOUND_SUFFIX); } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30) encoding = null; inStream.reset(); // prepare for data format determination inStream.mark(CRL_CACHE_SEED_LENGTH); } else { // unsupported data if (result.size() == 0) { throw new CRLException( Messages.getString("security.15F")); //$NON-NLS-1$ } else { // it can be trailing user data, // so keep it in the stream inStream.reset(); return result; } } // Check the data format BerInputStream in = (encoding == null) ? new BerInputStream(inStream) : new BerInputStream(encoding); // read the next ASN.1 tag second_asn1_tag = in.next(); if (encoding == null) { // keep whole structure in the stream inStream.reset(); } // check if it is a TBSCertList structure if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) { if (result.size() == 0) { // there were not read X.509 CRLs, so // break the cycle and check // whether it is PKCS7 structure break; } else { // it can be trailing user data, // so return what we already read return result; } } else { if (encoding == null) { result.add(getCRL(inStream)); } else { result.add(getCRL(encoding)); } } inStream.mark(1); } if (result.size() != 0) { // the stream was read out return result; } else if (ch == -1) { throw new CRLException( Messages.getString("security.155")); //$NON-NLS-1$ } // else: check if it is PKCS7 if (second_asn1_tag == ASN1Constants.TAG_OID) { // it is PKCS7 ContentInfo structure, so decode it ContentInfo info = (ContentInfo) ((encoding != null) ? ContentInfo.ASN1.decode(encoding) : ContentInfo.ASN1.decode(inStream)); // retrieve SignedData SignedData data = info.getSignedData(); if (data == null) { throw new CRLException( Messages.getString("security.154")); //$NON-NLS-1$ } List crls = data.getCRLs(); if (crls != null) { for (int i = 0; i < crls.size(); i++) { result.add(new X509CRLImpl( (CertificateList) crls.get(i))); } } return result; } // else: Unknown data format throw new CRLException( Messages.getString("security.15F")); //$NON-NLS-1$ } catch (IOException e) { throw new CRLException(e); } } /** * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream) * method documentation for more info */ public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException { if (inStream == null) { throw new CertificateException( Messages.getString("security.153")); //$NON-NLS-1$ } return engineGenerateCertPath(inStream, "PkiPath"); //$NON-NLS-1$ } /** * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream,String) * method documentation for more info */ public CertPath engineGenerateCertPath( InputStream inStream, String encoding) throws CertificateException { if (inStream == null) { throw new CertificateException( Messages.getString("security.153")); //$NON-NLS-1$ } if (!inStream.markSupported()) { inStream = new RestoringInputStream(inStream); } try { inStream.mark(1); int ch; // check if it is PEM encoded form if ((ch = inStream.read()) == '-') { // decode PEM chunk into ASN.1 form and decode CertPath object return X509CertPathImpl.getInstance( decodePEM(inStream, FREE_BOUND_SUFFIX), encoding); } else if (ch == 0x30) { // ASN.1 Sequence inStream.reset(); // decode ASN.1 form return X509CertPathImpl.getInstance(inStream, encoding); } else { throw new CertificateException( Messages.getString("security.15F")); //$NON-NLS-1$ } } catch (IOException e) { throw new CertificateException(e); } } /** * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List) * method documentation for more info */ public CertPath engineGenerateCertPath(List certificates) throws CertificateException { return new X509CertPathImpl(certificates); } /** * @see java.security.cert.CertificateFactorySpi#engineGetCertPathEncodings() * method documentation for more info */ public Iterator engineGetCertPathEncodings() { return X509CertPathImpl.encodings.iterator(); } // --------------------------------------------------------------------- // ------------------------ Staff methods ------------------------------ // --------------------------------------------------------------------- private static byte[] pemBegin; private static byte[] pemClose; /** * Code describing free format for PEM boundary suffix: * "^-----BEGIN.*\n" at the beginning, and
* "\n-----END.*(EOF|\n)$" at the end. */ private static byte[] FREE_BOUND_SUFFIX = null; /** * Code describing PEM boundary suffix for X.509 certificate: * "^-----BEGIN CERTIFICATE-----\n" at the beginning, and
* "\n-----END CERTIFICATE-----" at the end. */ private static byte[] CERT_BOUND_SUFFIX; static { // Initialise statics try { pemBegin = "-----BEGIN".getBytes("UTF-8"); //$NON-NLS-1$ //$NON-NLS-2$ pemClose = "-----END".getBytes("UTF-8"); //$NON-NLS-1$ //$NON-NLS-2$ CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes("UTF-8"); //$NON-NLS-1$ //$NON-NLS-2$ } catch (UnsupportedEncodingException e) { throw new RuntimeException(e.getMessage()); } } /** * Method retrieves the PEM encoded data from the stream * and returns its decoded representation. * Method checks correctness of PEM boundaries. It supposes that * the first '-' of the opening boundary has already been read from * the stream. So first of all it checks that the leading bytes * are equal to "-----BEGIN" boundary prefix. Than if boundary_suffix * is not null, it checks that next bytes equal to boundary_suffix * + new line char[s] ([CR]LF). * If boundary_suffix parameter is null, method supposes free suffix * format and skips any bytes until the new line.
* After the opening boundary has been read and checked, the method * read Base64 encoded data until closing PEM boundary is not reached.
* Than it checks closing boundary - it should start with new line + * "-----END" + boundary_suffix. If boundary_suffix is null, * any characters are skipped until the new line.
* After this any trailing new line characters are skipped from the stream, * Base64 encoding is decoded and returned. * @param inStream the stream containing the PEM encoding. * @param boundary_suffix the suffix of expected PEM multipart * boundary delimiter.
* If it is null, that any character sequences are accepted. * @throws IOException If PEM boundary delimiter does not comply * with expected or some I/O or decoding problems occur. */ private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix) throws IOException { int ch; // the char to be read // check and skip opening boundary delimiter // (first '-' is supposed as already read) for (int i=1; i= 0) { // current position in the buffer int cur = pos % BUFF_SIZE; // check whether the buffer contains the data to be read if (cur < bar) { // return the data from the buffer pos++; return buff[cur]; } // check whether buffer has free space if (cur != end) { // it has, so read the data from the wrapped stream // and place it in the buffer buff[cur] = inStream.read(); bar = cur+1; pos++; return buff[cur]; } else { // buffer if full and can not operate // any more, so invalidate the mark position // and turn off the using of buffer pos = -1; } } // buffer is not used, so return the data from the wrapped stream return inStream.read(); } @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(byte[] b, int off, int len) throws IOException { int read_b; int i; for (i=0; i= 0) { pos = (end + 1) % BUFF_SIZE; } else { throw new IOException( Messages.getString("security.15A")); //$NON-NLS-1$ } } @Override public long skip(long n) throws IOException { if (pos >= 0) { long i = 0; int av = available(); if (av < n) { n = av; } while ((i < n) && (read() != -1)) { i++; } return i; } else { return inStream.skip(n); } } } }