1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18/**
19* @author Alexander Y. Kleymenov
20* @version $Revision$
21*/
22
23package org.apache.harmony.security.provider.cert;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.math.BigInteger;
28import java.security.InvalidKeyException;
29import java.security.NoSuchAlgorithmException;
30import java.security.NoSuchProviderException;
31import java.security.Principal;
32import java.security.PublicKey;
33import java.security.Signature;
34import java.security.SignatureException;
35import java.security.cert.CertificateEncodingException;
36import java.security.cert.CertificateException;
37import java.security.cert.CertificateExpiredException;
38import java.security.cert.CertificateNotYetValidException;
39import java.security.cert.CertificateParsingException;
40import java.security.cert.X509Certificate;
41import java.util.Collection;
42import java.util.Date;
43import java.util.List;
44import java.util.Set;
45import javax.security.auth.x500.X500Principal;
46import org.apache.harmony.security.utils.AlgNameMapper;
47import org.apache.harmony.security.x509.Certificate;
48import org.apache.harmony.security.x509.Extension;
49import org.apache.harmony.security.x509.Extensions;
50import org.apache.harmony.security.x509.TBSCertificate;
51import org.apache.harmony.xnet.provider.jsse.OpenSSLProvider;
52
53/**
54 * This class is an implementation of X509Certificate. It wraps
55 * the instance of org.apache.harmony.security.x509.Certificate
56 * built on the base of provided ASN.1 DER encoded form of
57 * Certificate structure (as specified in RFC 3280
58 * http://www.ietf.org/rfc/rfc3280.txt).
59 * @see org.apache.harmony.security.x509.Certificate
60 * @see java.security.cert.X509Certificate
61 */
62public final class X509CertImpl extends X509Certificate {
63
64    /** @serial */
65    private static final long serialVersionUID = 2972248729446736154L;
66
67    /** the core object to be wrapped in X509Certificate */
68    private final Certificate certificate;
69
70    // to speed up access to the info, the following fields
71    // cache values retrieved from the certificate object,
72    // initialized using the "single-check idiom".
73    private final TBSCertificate tbsCert;
74    private final Extensions extensions;
75    private volatile long notBefore = -1;
76    private volatile long notAfter = -1;
77    private volatile BigInteger serialNumber;
78    private volatile X500Principal issuer;
79    private volatile X500Principal subject;
80    private volatile byte[] tbsCertificate;
81    private volatile byte[] signature;
82    private volatile String sigAlgName;
83    private volatile String sigAlgOID;
84    private volatile byte[] sigAlgParams;
85    // indicates whether the signature algorithm parameters are null
86    private volatile boolean nullSigAlgParams;
87    private volatile PublicKey publicKey;
88
89    // encoding of the certificate
90    private volatile byte[] encoding;
91
92    /**
93     * Constructs the instance on the base of ASN.1 encoded
94     * form of X.509 certificate provided via stream parameter.
95     * @param in input stream containing ASN.1 encoded form of certificate.
96     * @throws CertificateException if some decoding problems occur.
97     */
98    public X509CertImpl(InputStream in) throws CertificateException {
99        try {
100            // decode the Certificate object
101            this.certificate = (Certificate) Certificate.ASN1.decode(in);
102            // cache the values of TBSCertificate and Extensions
103            this.tbsCert = certificate.getTbsCertificate();
104            this.extensions = tbsCert.getExtensions();
105        } catch (IOException e) {
106            throw new CertificateException(e);
107        }
108    }
109
110    /**
111     * Constructs the instance on the base of existing Certificate object to
112     * be wrapped.
113     */
114    public X509CertImpl(Certificate certificate) {
115        this.certificate = certificate;
116        // cache the values of TBSCertificate and Extensions
117        this.tbsCert = certificate.getTbsCertificate();
118        this.extensions = tbsCert.getExtensions();
119    }
120
121    /**
122     * Constructs the instance on the base of ASN.1 encoded
123     * form of X.509 certificate provided via array of bytes.
124     * @param encoding byte array containing ASN.1 encoded form of certificate.
125     * @throws IOException if some decoding problems occur.
126     */
127    public X509CertImpl(byte[] encoding) throws IOException {
128        this((Certificate) Certificate.ASN1.decode(encoding));
129    }
130
131    public void checkValidity()
132            throws CertificateExpiredException, CertificateNotYetValidException {
133        checkValidity(System.currentTimeMillis());
134    }
135
136    public void checkValidity(Date date)
137            throws CertificateExpiredException, CertificateNotYetValidException {
138        checkValidity(date.getTime());
139    }
140
141    private void checkValidity(long time)
142            throws CertificateExpiredException, CertificateNotYetValidException {
143        if (time < getNotBeforeInternal()) {
144            throw new CertificateNotYetValidException("current time: " + new Date(time)
145                + ", validation time: " + new Date(getNotBeforeInternal()));
146        }
147        if (time > getNotAfterInternal()) {
148            throw new CertificateExpiredException("current time: " + new Date(time)
149                + ", expiration time: " + new Date(getNotAfterInternal()));
150        }
151    }
152
153    public int getVersion() {
154        return tbsCert.getVersion() + 1;
155    }
156
157    public BigInteger getSerialNumber() {
158        BigInteger result = serialNumber;
159        if (result == null) {
160            serialNumber = result = tbsCert.getSerialNumber();
161        }
162        return result;
163    }
164
165    public Principal getIssuerDN() {
166        return getIssuerX500Principal();
167    }
168
169    public X500Principal getIssuerX500Principal() {
170        X500Principal result = issuer;
171        if (result == null) {
172            // retrieve the issuer's principal
173            issuer = result = tbsCert.getIssuer().getX500Principal();
174        }
175        return result;
176    }
177
178    public Principal getSubjectDN() {
179        return getSubjectX500Principal();
180    }
181
182    public X500Principal getSubjectX500Principal() {
183        X500Principal result = subject;
184        if (result == null) {
185            // retrieve the subject's principal
186            subject = result = tbsCert.getSubject().getX500Principal();
187        }
188        return result;
189    }
190
191    public Date getNotBefore() {
192        return new Date(getNotBeforeInternal());
193    }
194
195    private long getNotBeforeInternal() {
196        long result = notBefore;
197        if (result == -1) {
198            notBefore = result = tbsCert.getValidity().getNotBefore().getTime();
199        }
200        return result;
201    }
202
203    public Date getNotAfter() {
204        return new Date(getNotAfterInternal());
205    }
206
207    private long getNotAfterInternal() {
208        long result = notAfter;
209        if (result == -1) {
210            notAfter = result = tbsCert.getValidity().getNotAfter().getTime();
211        }
212        return result;
213    }
214
215    public byte[] getTBSCertificate() throws CertificateEncodingException {
216        return getTbsCertificateInternal().clone();
217    }
218
219    private byte[] getTbsCertificateInternal() {
220        byte[] result = tbsCertificate;
221        if (result == null) {
222            tbsCertificate = result = tbsCert.getEncoded();
223        }
224        return result;
225    }
226
227    public byte[] getSignature() {
228        return getSignatureInternal().clone();
229    }
230
231    private byte[] getSignatureInternal() {
232        byte[] result = signature;
233        if (result == null) {
234            signature = result = certificate.getSignatureValue();
235        }
236        return result;
237    }
238
239    public String getSigAlgName() {
240        String result = sigAlgName;
241        if (result == null) {
242            String sigAlgOIDLocal = getSigAlgOID();
243            // retrieve the name of the signing algorithm
244            result = AlgNameMapper.map2AlgName(sigAlgOIDLocal);
245            if (result == null) {
246                // if could not be found, use OID as a name
247                result = sigAlgOIDLocal;
248            }
249            sigAlgName = result;
250        }
251        return result;
252    }
253
254    public String getSigAlgOID() {
255        String result = sigAlgOID;
256        if (result == null) {
257            // if info was not retrieved (and cached), do it:
258            sigAlgOID = result = tbsCert.getSignature().getAlgorithm();
259        }
260        return result;
261    }
262
263    public byte[] getSigAlgParams() {
264        if (nullSigAlgParams) {
265            return null;
266        }
267        byte[] result = sigAlgParams;
268        if (result == null) {
269            result = tbsCert.getSignature().getParameters();
270            if (result == null) {
271                nullSigAlgParams = true;
272                return null;
273            }
274            sigAlgParams = result;
275        }
276        return result;
277    }
278
279    public boolean[] getIssuerUniqueID() {
280        return tbsCert.getIssuerUniqueID();
281    }
282
283    public boolean[] getSubjectUniqueID() {
284        return tbsCert.getSubjectUniqueID();
285    }
286
287    public boolean[] getKeyUsage() {
288        if (extensions == null) {
289            return null;
290        }
291        return extensions.valueOfKeyUsage();
292    }
293
294    public List<String> getExtendedKeyUsage()
295                                throws CertificateParsingException {
296        if (extensions == null) {
297            return null;
298        }
299        try {
300            return extensions.valueOfExtendedKeyUsage();
301        } catch (IOException e) {
302            throw new CertificateParsingException(e);
303        }
304    }
305
306    public int getBasicConstraints() {
307        if (extensions == null) {
308            return Integer.MAX_VALUE;
309        }
310        return extensions.valueOfBasicConstrains();
311    }
312
313    public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
314        if (extensions == null) {
315            return null;
316        }
317        try {
318            // Retrieve the extension value from the cached extensions object
319            // This extension is not checked for correctness during
320            // certificate generation, so now it can throw exception
321            return extensions.valueOfSubjectAlternativeName();
322        } catch (IOException e) {
323            throw new CertificateParsingException(e);
324        }
325    }
326
327    /**
328     * @see java.security.cert.X509Certificate#getIssuerAlternativeNames()
329     * method documentation for more information.
330     */
331    public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException {
332        if (extensions == null) {
333            return null;
334        }
335        try {
336            // Retrieve the extension value from the cached extensions object
337            // This extension is not checked for correctness during
338            // certificate generation, so now it can throw exception
339            return extensions.valueOfIssuerAlternativeName();
340        } catch (IOException e) {
341            throw new CertificateParsingException(e);
342        }
343    }
344
345    @Override public byte[] getEncoded() throws CertificateEncodingException {
346        return getEncodedInternal().clone();
347    }
348    private byte[] getEncodedInternal() throws CertificateEncodingException {
349        byte[] result = encoding;
350        if (encoding == null) {
351            encoding = result = certificate.getEncoded();
352        }
353        return result;
354    }
355
356    @Override public PublicKey getPublicKey() {
357        PublicKey result = publicKey;
358        if (result == null) {
359            publicKey = result = tbsCert.getSubjectPublicKeyInfo().getPublicKey();
360        }
361        return result;
362    }
363
364    @Override public String toString() {
365        return certificate.toString();
366    }
367
368    @Override public void verify(PublicKey key)
369            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
370            NoSuchProviderException, SignatureException {
371
372        Signature signature;
373        try {
374            signature = Signature.getInstance(getSigAlgName(), OpenSSLProvider.PROVIDER_NAME);
375        } catch (NoSuchAlgorithmException ignored) {
376            signature = Signature.getInstance(getSigAlgName());
377        }
378        signature.initVerify(key);
379        // retrieve the encoding of the TBSCertificate structure
380        byte[] tbsCertificateLocal = getTbsCertificateInternal();
381        // compute and verify the signature
382        signature.update(tbsCertificateLocal, 0, tbsCertificateLocal.length);
383        if (!signature.verify(certificate.getSignatureValue())) {
384            throw new SignatureException("Signature was not verified");
385        }
386    }
387
388    @Override public void verify(PublicKey key, String sigProvider)
389            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
390            NoSuchProviderException, SignatureException {
391
392        Signature signature;
393        try {
394            if (sigProvider == null) {
395                signature = Signature.getInstance(getSigAlgName(), OpenSSLProvider.PROVIDER_NAME);
396            } else {
397                signature = Signature.getInstance(getSigAlgName(), sigProvider);
398            }
399        } catch (NoSuchAlgorithmException ignored) {
400            signature = Signature.getInstance(getSigAlgName(), sigProvider);
401        }
402        signature.initVerify(key);
403        // retrieve the encoding of the TBSCertificate structure
404        byte[] tbsCertificateLocal = getTbsCertificateInternal();
405        // compute and verify the signature
406        signature.update(tbsCertificateLocal, 0, tbsCertificateLocal.length);
407        if (!signature.verify(certificate.getSignatureValue())) {
408            throw new SignatureException("Signature was not verified");
409        }
410    }
411
412    @Override public Set<String> getNonCriticalExtensionOIDs() {
413        if (extensions == null) {
414            return null;
415        }
416        // retrieve the info from the cached extensions object
417        return extensions.getNonCriticalExtensions();
418    }
419
420    @Override public Set<String> getCriticalExtensionOIDs() {
421        if (extensions == null) {
422            return null;
423        }
424        // retrieve the info from the cached extensions object
425        return extensions.getCriticalExtensions();
426    }
427
428    @Override public byte[] getExtensionValue(String oid) {
429        if (extensions == null) {
430            return null;
431        }
432        // retrieve the info from the cached extensions object
433        Extension ext = extensions.getExtensionByOID(oid);
434        return (ext == null) ? null : ext.getRawExtnValue();
435    }
436
437    @Override public boolean hasUnsupportedCriticalExtension() {
438        if (extensions == null) {
439            return false;
440        }
441        // retrieve the info from the cached extensions object
442        return extensions.hasUnsupportedCritical();
443    }
444
445}
446