• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.conscrypt;
18 
19 import java.io.ByteArrayOutputStream;
20 import java.io.InputStream;
21 import java.math.BigInteger;
22 import java.security.InvalidKeyException;
23 import java.security.KeyFactory;
24 import java.security.NoSuchAlgorithmException;
25 import java.security.NoSuchProviderException;
26 import java.security.Principal;
27 import java.security.PublicKey;
28 import java.security.Signature;
29 import java.security.SignatureException;
30 import java.security.cert.Certificate;
31 import java.security.cert.CertificateEncodingException;
32 import java.security.cert.CertificateException;
33 import java.security.cert.CertificateExpiredException;
34 import java.security.cert.CertificateNotYetValidException;
35 import java.security.cert.CertificateParsingException;
36 import java.security.cert.X509Certificate;
37 import java.security.spec.InvalidKeySpecException;
38 import java.security.spec.X509EncodedKeySpec;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Calendar;
42 import java.util.Collection;
43 import java.util.Collections;
44 import java.util.Date;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Set;
48 import java.util.TimeZone;
49 import javax.crypto.BadPaddingException;
50 import javax.security.auth.x500.X500Principal;
51 import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
52 
53 public class OpenSSLX509Certificate extends X509Certificate {
54     private transient final long mContext;
55     private transient Integer mHashCode;
56 
OpenSSLX509Certificate(long ctx)57     OpenSSLX509Certificate(long ctx) {
58         mContext = ctx;
59     }
60 
fromX509DerInputStream(InputStream is)61     public static OpenSSLX509Certificate fromX509DerInputStream(InputStream is)
62             throws ParsingException {
63         @SuppressWarnings("resource")
64         final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
65 
66         try {
67             final long certCtx = NativeCrypto.d2i_X509_bio(bis.getBioContext());
68             if (certCtx == 0) {
69                 return null;
70             }
71             return new OpenSSLX509Certificate(certCtx);
72         } catch (Exception e) {
73             throw new ParsingException(e);
74         } finally {
75             bis.release();
76         }
77     }
78 
fromX509Der(byte[] encoded)79     public static OpenSSLX509Certificate fromX509Der(byte[] encoded) {
80         final long certCtx = NativeCrypto.d2i_X509(encoded);
81         if (certCtx == 0) {
82             return null;
83         }
84         return new OpenSSLX509Certificate(certCtx);
85     }
86 
fromPkcs7DerInputStream(InputStream is)87     public static List<OpenSSLX509Certificate> fromPkcs7DerInputStream(InputStream is)
88             throws ParsingException {
89         @SuppressWarnings("resource")
90         OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
91 
92         final long[] certRefs;
93         try {
94             certRefs = NativeCrypto.d2i_PKCS7_bio(bis.getBioContext(), NativeCrypto.PKCS7_CERTS);
95         } catch (Exception e) {
96             throw new ParsingException(e);
97         } finally {
98             bis.release();
99         }
100 
101         if (certRefs == null) {
102             return Collections.emptyList();
103         }
104 
105         final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
106                 certRefs.length);
107         for (int i = 0; i < certRefs.length; i++) {
108             if (certRefs[i] == 0) {
109                 continue;
110             }
111             certs.add(new OpenSSLX509Certificate(certRefs[i]));
112         }
113         return certs;
114     }
115 
fromX509PemInputStream(InputStream is)116     public static OpenSSLX509Certificate fromX509PemInputStream(InputStream is)
117             throws ParsingException {
118         @SuppressWarnings("resource")
119         final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
120 
121         try {
122             final long certCtx = NativeCrypto.PEM_read_bio_X509(bis.getBioContext());
123             if (certCtx == 0L) {
124                 return null;
125             }
126             return new OpenSSLX509Certificate(certCtx);
127         } catch (Exception e) {
128             throw new ParsingException(e);
129         } finally {
130             bis.release();
131         }
132     }
133 
fromPkcs7PemInputStream(InputStream is)134     public static List<OpenSSLX509Certificate> fromPkcs7PemInputStream(InputStream is)
135             throws ParsingException {
136         @SuppressWarnings("resource")
137         OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
138 
139         final long[] certRefs;
140         try {
141             certRefs = NativeCrypto.PEM_read_bio_PKCS7(bis.getBioContext(),
142                     NativeCrypto.PKCS7_CERTS);
143         } catch (Exception e) {
144             throw new ParsingException(e);
145         } finally {
146             bis.release();
147         }
148 
149         final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
150                 certRefs.length);
151         for (int i = 0; i < certRefs.length; i++) {
152             if (certRefs[i] == 0) {
153                 continue;
154             }
155             certs.add(new OpenSSLX509Certificate(certRefs[i]));
156         }
157         return certs;
158     }
159 
fromCertificate(Certificate cert)160     public static OpenSSLX509Certificate fromCertificate(Certificate cert)
161             throws CertificateEncodingException {
162         if (cert instanceof OpenSSLX509Certificate) {
163             return (OpenSSLX509Certificate) cert;
164         } else if (cert instanceof X509Certificate) {
165             return fromX509Der(cert.getEncoded());
166         } else {
167             throw new CertificateEncodingException("Only X.509 certificates are supported");
168         }
169     }
170 
171     @Override
getCriticalExtensionOIDs()172     public Set<String> getCriticalExtensionOIDs() {
173         String[] critOids =
174                 NativeCrypto.get_X509_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_CRITICAL);
175 
176         /*
177          * This API has a special case that if there are no extensions, we
178          * should return null. So if we have no critical extensions, we'll check
179          * non-critical extensions.
180          */
181         if ((critOids.length == 0)
182                 && (NativeCrypto.get_X509_ext_oids(mContext,
183                         NativeCrypto.EXTENSION_TYPE_NON_CRITICAL).length == 0)) {
184             return null;
185         }
186 
187         return new HashSet<String>(Arrays.asList(critOids));
188     }
189 
190     @Override
getExtensionValue(String oid)191     public byte[] getExtensionValue(String oid) {
192         return NativeCrypto.X509_get_ext_oid(mContext, oid);
193     }
194 
195     @Override
getNonCriticalExtensionOIDs()196     public Set<String> getNonCriticalExtensionOIDs() {
197         String[] nonCritOids =
198                 NativeCrypto.get_X509_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_NON_CRITICAL);
199 
200         /*
201          * This API has a special case that if there are no extensions, we
202          * should return null. So if we have no non-critical extensions, we'll
203          * check critical extensions.
204          */
205         if ((nonCritOids.length == 0)
206                 && (NativeCrypto.get_X509_ext_oids(mContext,
207                         NativeCrypto.EXTENSION_TYPE_CRITICAL).length == 0)) {
208             return null;
209         }
210 
211         return new HashSet<String>(Arrays.asList(nonCritOids));
212     }
213 
214     @Override
hasUnsupportedCriticalExtension()215     public boolean hasUnsupportedCriticalExtension() {
216         return (NativeCrypto.get_X509_ex_flags(mContext) & NativeConstants.EXFLAG_CRITICAL) != 0;
217     }
218 
219     @Override
checkValidity()220     public void checkValidity() throws CertificateExpiredException,
221             CertificateNotYetValidException {
222         checkValidity(new Date());
223     }
224 
225     @Override
checkValidity(Date date)226     public void checkValidity(Date date) throws CertificateExpiredException,
227             CertificateNotYetValidException {
228         if (getNotBefore().compareTo(date) > 0) {
229             throw new CertificateNotYetValidException("Certificate not valid until "
230                     + getNotBefore().toString() + " (compared to " + date.toString() + ")");
231         }
232 
233         if (getNotAfter().compareTo(date) < 0) {
234             throw new CertificateExpiredException("Certificate expired at "
235                     + getNotAfter().toString() + " (compared to " + date.toString() + ")");
236         }
237     }
238 
239     @Override
getVersion()240     public int getVersion() {
241         return (int) NativeCrypto.X509_get_version(mContext) + 1;
242     }
243 
244     @Override
getSerialNumber()245     public BigInteger getSerialNumber() {
246         return new BigInteger(NativeCrypto.X509_get_serialNumber(mContext));
247     }
248 
249     @Override
getIssuerDN()250     public Principal getIssuerDN() {
251         return getIssuerX500Principal();
252     }
253 
254     @Override
getSubjectDN()255     public Principal getSubjectDN() {
256         return getSubjectX500Principal();
257     }
258 
259     @Override
getNotBefore()260     public Date getNotBefore() {
261         Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
262         calendar.set(Calendar.MILLISECOND, 0);
263         NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_get_notBefore(mContext), calendar);
264         return calendar.getTime();
265     }
266 
267     @Override
getNotAfter()268     public Date getNotAfter() {
269         Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
270         calendar.set(Calendar.MILLISECOND, 0);
271         NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_get_notAfter(mContext), calendar);
272         return calendar.getTime();
273     }
274 
275     @Override
getTBSCertificate()276     public byte[] getTBSCertificate() throws CertificateEncodingException {
277         return NativeCrypto.get_X509_cert_info_enc(mContext);
278     }
279 
280     @Override
getSignature()281     public byte[] getSignature() {
282         return NativeCrypto.get_X509_signature(mContext);
283     }
284 
285     @Override
getSigAlgName()286     public String getSigAlgName() {
287         String oid = getSigAlgOID();
288         String algName = Platform.oidToAlgorithmName(oid);
289         if (algName != null) {
290             return algName;
291         }
292         return oid;
293     }
294 
295     @Override
getSigAlgOID()296     public String getSigAlgOID() {
297         return NativeCrypto.get_X509_sig_alg_oid(mContext);
298     }
299 
300     @Override
getSigAlgParams()301     public byte[] getSigAlgParams() {
302         return NativeCrypto.get_X509_sig_alg_parameter(mContext);
303     }
304 
305     @Override
getIssuerUniqueID()306     public boolean[] getIssuerUniqueID() {
307         return NativeCrypto.get_X509_issuerUID(mContext);
308     }
309 
310     @Override
getSubjectUniqueID()311     public boolean[] getSubjectUniqueID() {
312         return NativeCrypto.get_X509_subjectUID(mContext);
313     }
314 
315     @Override
getKeyUsage()316     public boolean[] getKeyUsage() {
317         final boolean[] kusage = NativeCrypto.get_X509_ex_kusage(mContext);
318         if (kusage == null) {
319             return null;
320         }
321 
322         if (kusage.length >= 9) {
323             return kusage;
324         }
325 
326         final boolean resized[] = new boolean[9];
327         System.arraycopy(kusage, 0, resized, 0, kusage.length);
328         return resized;
329     }
330 
331     @Override
getBasicConstraints()332     public int getBasicConstraints() {
333         if ((NativeCrypto.get_X509_ex_flags(mContext) & NativeConstants.EXFLAG_CA) == 0) {
334             return -1;
335         }
336 
337         final int pathLen = NativeCrypto.get_X509_ex_pathlen(mContext);
338         if (pathLen == -1) {
339             return Integer.MAX_VALUE;
340         }
341 
342         return pathLen;
343     }
344 
345     @Override
getEncoded()346     public byte[] getEncoded() throws CertificateEncodingException {
347         return NativeCrypto.i2d_X509(mContext);
348     }
349 
verifyOpenSSL(OpenSSLKey pkey)350     private void verifyOpenSSL(OpenSSLKey pkey) throws CertificateException,
351             NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
352             SignatureException {
353         try {
354             NativeCrypto.X509_verify(mContext, pkey.getNativeRef());
355         } catch (RuntimeException e) {
356             throw new CertificateException(e);
357         } catch (BadPaddingException e) {
358             throw new SignatureException();
359         }
360     }
361 
verifyInternal(PublicKey key, String sigProvider)362     private void verifyInternal(PublicKey key, String sigProvider) throws CertificateException,
363             NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
364             SignatureException {
365         final Signature sig;
366         if (sigProvider == null) {
367             sig = Signature.getInstance(getSigAlgName());
368         } else {
369             sig = Signature.getInstance(getSigAlgName(), sigProvider);
370         }
371 
372         sig.initVerify(key);
373         sig.update(getTBSCertificate());
374         if (!sig.verify(getSignature())) {
375             throw new SignatureException("signature did not verify");
376         }
377     }
378 
379     @Override
verify(PublicKey key)380     public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
381             InvalidKeyException, NoSuchProviderException, SignatureException {
382         if (key instanceof OpenSSLKeyHolder) {
383             OpenSSLKey pkey = ((OpenSSLKeyHolder) key).getOpenSSLKey();
384             verifyOpenSSL(pkey);
385             return;
386         }
387 
388         verifyInternal(key, null);
389     }
390 
391     @Override
verify(PublicKey key, String sigProvider)392     public void verify(PublicKey key, String sigProvider) throws CertificateException,
393             NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
394             SignatureException {
395         verifyInternal(key, sigProvider);
396     }
397 
398     @Override
toString()399     public String toString() {
400         ByteArrayOutputStream os = new ByteArrayOutputStream();
401         long bioCtx = NativeCrypto.create_BIO_OutputStream(os);
402         try {
403             NativeCrypto.X509_print_ex(bioCtx, mContext, 0, 0);
404             return os.toString();
405         } finally {
406             NativeCrypto.BIO_free_all(bioCtx);
407         }
408     }
409 
410     @Override
getPublicKey()411     public PublicKey getPublicKey() {
412         /* First try to generate the key from supported OpenSSL key types. */
413         try {
414             OpenSSLKey pkey = new OpenSSLKey(NativeCrypto.X509_get_pubkey(mContext));
415             return pkey.getPublicKey();
416         } catch (NoSuchAlgorithmException ignored) {
417         }
418 
419         /* Try generating the key using other Java providers. */
420         String oid = NativeCrypto.get_X509_pubkey_oid(mContext);
421         byte[] encoded = NativeCrypto.i2d_X509_PUBKEY(mContext);
422         try {
423             KeyFactory kf = KeyFactory.getInstance(oid);
424             return kf.generatePublic(new X509EncodedKeySpec(encoded));
425         } catch (NoSuchAlgorithmException ignored) {
426         } catch (InvalidKeySpecException ignored) {
427         }
428 
429         /*
430          * We couldn't find anything else, so just return a nearly-unusable
431          * X.509-encoded key.
432          */
433         return new X509PublicKey(oid, encoded);
434     }
435 
436     @Override
getIssuerX500Principal()437     public X500Principal getIssuerX500Principal() {
438         final byte[] issuer = NativeCrypto.X509_get_issuer_name(mContext);
439         return new X500Principal(issuer);
440     }
441 
442     @Override
getSubjectX500Principal()443     public X500Principal getSubjectX500Principal() {
444         final byte[] subject = NativeCrypto.X509_get_subject_name(mContext);
445         return new X500Principal(subject);
446     }
447 
448     @Override
getExtendedKeyUsage()449     public List<String> getExtendedKeyUsage() throws CertificateParsingException {
450         String[] extUsage = NativeCrypto.get_X509_ex_xkusage(mContext);
451         if (extUsage == null) {
452             return null;
453         }
454 
455         return Arrays.asList(extUsage);
456     }
457 
alternativeNameArrayToList(Object[][] altNameArray)458     private static Collection<List<?>> alternativeNameArrayToList(Object[][] altNameArray) {
459         if (altNameArray == null) {
460             return null;
461         }
462 
463         Collection<List<?>> coll = new ArrayList<List<?>>(altNameArray.length);
464         for (int i = 0; i < altNameArray.length; i++) {
465             coll.add(Collections.unmodifiableList(Arrays.asList(altNameArray[i])));
466         }
467 
468         return Collections.unmodifiableCollection(coll);
469     }
470 
471     @Override
getSubjectAlternativeNames()472     public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
473         return alternativeNameArrayToList(NativeCrypto.get_X509_GENERAL_NAME_stack(mContext,
474                 NativeCrypto.GN_STACK_SUBJECT_ALT_NAME));
475     }
476 
477     @Override
getIssuerAlternativeNames()478     public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException {
479         return alternativeNameArrayToList(NativeCrypto.get_X509_GENERAL_NAME_stack(mContext,
480                 NativeCrypto.GN_STACK_ISSUER_ALT_NAME));
481     }
482 
483     @Override
equals(Object other)484     public boolean equals(Object other) {
485         if (other instanceof OpenSSLX509Certificate) {
486             OpenSSLX509Certificate o = (OpenSSLX509Certificate) other;
487 
488             return NativeCrypto.X509_cmp(mContext, o.mContext) == 0;
489         }
490 
491         return super.equals(other);
492     }
493 
494     @Override
hashCode()495     public int hashCode() {
496         if (mHashCode != null) {
497             return mHashCode;
498         }
499         mHashCode = super.hashCode();
500         return mHashCode;
501     }
502 
503     /**
504      * Returns the raw pointer to the X509 context for use in JNI calls. The
505      * life cycle of this native pointer is managed by the
506      * {@code OpenSSLX509Certificate} instance and must not be destroyed or
507      * freed by users of this API.
508      */
getContext()509     public long getContext() {
510         return mContext;
511     }
512 
513     /**
514      * Delete an extension.
515      *
516      * A modified copy of the certificate is returned. The original object
517      * is unchanged.
518      * If the extension is not present, an unmodified copy is returned.
519      */
withDeletedExtension(String oid)520     public OpenSSLX509Certificate withDeletedExtension(String oid) {
521         OpenSSLX509Certificate copy = new OpenSSLX509Certificate(NativeCrypto.X509_dup(mContext));
522         NativeCrypto.X509_delete_ext(copy.getContext(), oid);
523         return copy;
524     }
525 
526     @Override
finalize()527     protected void finalize() throws Throwable {
528         try {
529             if (mContext != 0) {
530                 NativeCrypto.X509_free(mContext);
531             }
532         } finally {
533             super.finalize();
534         }
535     }
536 }
537