1package org.bouncycastle.jce.provider;
2
3import java.io.ByteArrayOutputStream;
4import java.io.IOException;
5import java.math.BigInteger;
6import java.security.InvalidKeyException;
7import java.security.NoSuchAlgorithmException;
8import java.security.NoSuchProviderException;
9import java.security.Principal;
10import java.security.Provider;
11import java.security.PublicKey;
12import java.security.Security;
13import java.security.Signature;
14import java.security.SignatureException;
15import java.security.cert.Certificate;
16import java.security.cert.CertificateEncodingException;
17import java.security.cert.CertificateException;
18import java.security.cert.CertificateExpiredException;
19import java.security.cert.CertificateNotYetValidException;
20import java.security.cert.CertificateParsingException;
21import java.security.cert.X509Certificate;
22import java.util.ArrayList;
23import java.util.Collections;
24import java.util.Date;
25import java.util.Enumeration;
26import java.util.HashSet;
27import java.util.List;
28import java.util.Set;
29
30import javax.security.auth.x500.X500Principal;
31
32import org.bouncycastle.asn1.ASN1Encodable;
33import org.bouncycastle.asn1.ASN1Encoding;
34import org.bouncycastle.asn1.ASN1InputStream;
35import org.bouncycastle.asn1.ASN1ObjectIdentifier;
36import org.bouncycastle.asn1.ASN1OutputStream;
37import org.bouncycastle.asn1.ASN1Primitive;
38import org.bouncycastle.asn1.ASN1Sequence;
39import org.bouncycastle.asn1.DERBitString;
40import org.bouncycastle.asn1.DERIA5String;
41import org.bouncycastle.asn1.DERNull;
42import org.bouncycastle.asn1.DERObjectIdentifier;
43import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
44import org.bouncycastle.asn1.misc.NetscapeCertType;
45import org.bouncycastle.asn1.misc.NetscapeRevocationURL;
46import org.bouncycastle.asn1.misc.VerisignCzagExtension;
47import org.bouncycastle.asn1.util.ASN1Dump;
48import org.bouncycastle.asn1.x500.X500Name;
49import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
50import org.bouncycastle.asn1.x509.BasicConstraints;
51import org.bouncycastle.asn1.x509.KeyUsage;
52import org.bouncycastle.asn1.x509.X509CertificateStructure;
53import org.bouncycastle.asn1.x509.X509Extension;
54import org.bouncycastle.asn1.x509.X509Extensions;
55import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
56import org.bouncycastle.jce.X509Principal;
57import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
58import org.bouncycastle.util.Arrays;
59import org.bouncycastle.util.encoders.Hex;
60
61public class X509CertificateObject
62    extends X509Certificate
63    implements PKCS12BagAttributeCarrier
64{
65    private X509CertificateStructure    c;
66    private BasicConstraints            basicConstraints;
67    private boolean[]                   keyUsage;
68    private boolean                     hashValueSet;
69    private int                         hashValue;
70
71    private PKCS12BagAttributeCarrier   attrCarrier = new PKCS12BagAttributeCarrierImpl();
72
73    public X509CertificateObject(
74        X509CertificateStructure    c)
75        throws CertificateParsingException
76    {
77        this.c = c;
78
79        try
80        {
81            byte[]  bytes = this.getExtensionBytes("2.5.29.19");
82
83            if (bytes != null)
84            {
85                basicConstraints = BasicConstraints.getInstance(ASN1Primitive.fromByteArray(bytes));
86            }
87        }
88        catch (Exception e)
89        {
90            throw new CertificateParsingException("cannot construct BasicConstraints: " + e);
91        }
92
93        try
94        {
95            byte[] bytes = this.getExtensionBytes("2.5.29.15");
96            if (bytes != null)
97            {
98                DERBitString    bits = DERBitString.getInstance(ASN1Primitive.fromByteArray(bytes));
99
100                bytes = bits.getBytes();
101                int length = (bytes.length * 8) - bits.getPadBits();
102
103                keyUsage = new boolean[(length < 9) ? 9 : length];
104
105                for (int i = 0; i != length; i++)
106                {
107                    keyUsage[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
108                }
109            }
110            else
111            {
112                keyUsage = null;
113            }
114        }
115        catch (Exception e)
116        {
117            throw new CertificateParsingException("cannot construct KeyUsage: " + e);
118        }
119    }
120
121    public void checkValidity()
122        throws CertificateExpiredException, CertificateNotYetValidException
123    {
124        this.checkValidity(new Date());
125    }
126
127    public void checkValidity(
128        Date    date)
129        throws CertificateExpiredException, CertificateNotYetValidException
130    {
131        if (date.getTime() > this.getNotAfter().getTime())  // for other VM compatibility
132        {
133            throw new CertificateExpiredException("certificate expired on " + c.getEndDate().getTime());
134        }
135
136        if (date.getTime() < this.getNotBefore().getTime())
137        {
138            throw new CertificateNotYetValidException("certificate not valid till " + c.getStartDate().getTime());
139        }
140    }
141
142    public int getVersion()
143    {
144        return c.getVersion();
145    }
146
147    public BigInteger getSerialNumber()
148    {
149        return c.getSerialNumber().getValue();
150    }
151
152    public Principal getIssuerDN()
153    {
154        try
155        {
156            return new X509Principal(X500Name.getInstance(c.getIssuer().getEncoded()));
157        }
158        catch (IOException e)
159        {
160            return null;
161        }
162    }
163
164    public X500Principal getIssuerX500Principal()
165    {
166        try
167        {
168            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
169            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
170
171            aOut.writeObject(c.getIssuer());
172
173            return new X500Principal(bOut.toByteArray());
174        }
175        catch (IOException e)
176        {
177            throw new IllegalStateException("can't encode issuer DN");
178        }
179    }
180
181    public Principal getSubjectDN()
182    {
183        return new X509Principal(X500Name.getInstance(c.getSubject().toASN1Primitive()));
184    }
185
186    public X500Principal getSubjectX500Principal()
187    {
188        try
189        {
190            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
191            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
192
193            aOut.writeObject(c.getSubject());
194
195            return new X500Principal(bOut.toByteArray());
196        }
197        catch (IOException e)
198        {
199            throw new IllegalStateException("can't encode issuer DN");
200        }
201    }
202
203    public Date getNotBefore()
204    {
205        return c.getStartDate().getDate();
206    }
207
208    public Date getNotAfter()
209    {
210        return c.getEndDate().getDate();
211    }
212
213    public byte[] getTBSCertificate()
214        throws CertificateEncodingException
215    {
216        try
217        {
218            return c.getTBSCertificate().getEncoded(ASN1Encoding.DER);
219        }
220        catch (IOException e)
221        {
222            throw new CertificateEncodingException(e.toString());
223        }
224    }
225
226    public byte[] getSignature()
227    {
228        return c.getSignature().getBytes();
229    }
230
231    /**
232     * return a more "meaningful" representation for the signature algorithm used in
233     * the certficate.
234     */
235    public String getSigAlgName()
236    {
237        Provider    prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
238
239        if (prov != null)
240        {
241            String      algName = prov.getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
242
243            if (algName != null)
244            {
245                return algName;
246            }
247        }
248
249        Provider[] provs = Security.getProviders();
250
251        //
252        // search every provider looking for a real algorithm
253        //
254        for (int i = 0; i != provs.length; i++)
255        {
256            String algName = provs[i].getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
257            if (algName != null)
258            {
259                return algName;
260            }
261        }
262
263        return this.getSigAlgOID();
264    }
265
266    /**
267     * return the object identifier for the signature.
268     */
269    public String getSigAlgOID()
270    {
271        return c.getSignatureAlgorithm().getObjectId().getId();
272    }
273
274    /**
275     * return the signature parameters, or null if there aren't any.
276     */
277    public byte[] getSigAlgParams()
278    {
279        if (c.getSignatureAlgorithm().getParameters() != null)
280        {
281            try
282            {
283                return c.getSignatureAlgorithm().getParameters().toASN1Primitive().getEncoded(ASN1Encoding.DER);
284            }
285            catch (IOException e)
286            {
287                return null;
288            }
289        }
290        else
291        {
292            return null;
293        }
294    }
295
296    public boolean[] getIssuerUniqueID()
297    {
298        DERBitString    id = c.getTBSCertificate().getIssuerUniqueId();
299
300        if (id != null)
301        {
302            byte[]          bytes = id.getBytes();
303            boolean[]       boolId = new boolean[bytes.length * 8 - id.getPadBits()];
304
305            for (int i = 0; i != boolId.length; i++)
306            {
307                boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
308            }
309
310            return boolId;
311        }
312
313        return null;
314    }
315
316    public boolean[] getSubjectUniqueID()
317    {
318        DERBitString    id = c.getTBSCertificate().getSubjectUniqueId();
319
320        if (id != null)
321        {
322            byte[]          bytes = id.getBytes();
323            boolean[]       boolId = new boolean[bytes.length * 8 - id.getPadBits()];
324
325            for (int i = 0; i != boolId.length; i++)
326            {
327                boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
328            }
329
330            return boolId;
331        }
332
333        return null;
334    }
335
336    public boolean[] getKeyUsage()
337    {
338        return keyUsage;
339    }
340
341    public List getExtendedKeyUsage()
342        throws CertificateParsingException
343    {
344        byte[]  bytes = this.getExtensionBytes("2.5.29.37");
345
346        if (bytes != null)
347        {
348            try
349            {
350                ASN1InputStream dIn = new ASN1InputStream(bytes);
351                ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
352                List            list = new ArrayList();
353
354                for (int i = 0; i != seq.size(); i++)
355                {
356                    list.add(((DERObjectIdentifier)seq.getObjectAt(i)).getId());
357                }
358
359                return Collections.unmodifiableList(list);
360            }
361            catch (Exception e)
362            {
363                throw new CertificateParsingException("error processing extended key usage extension");
364            }
365        }
366
367        return null;
368    }
369
370    public int getBasicConstraints()
371    {
372        if (basicConstraints != null)
373        {
374            if (basicConstraints.isCA())
375            {
376                if (basicConstraints.getPathLenConstraint() == null)
377                {
378                    return Integer.MAX_VALUE;
379                }
380                else
381                {
382                    return basicConstraints.getPathLenConstraint().intValue();
383                }
384            }
385            else
386            {
387                return -1;
388            }
389        }
390
391        return -1;
392    }
393
394    public Set getCriticalExtensionOIDs()
395    {
396        if (this.getVersion() == 3)
397        {
398            Set             set = new HashSet();
399            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
400
401            if (extensions != null)
402            {
403                Enumeration     e = extensions.oids();
404
405                while (e.hasMoreElements())
406                {
407                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
408                    X509Extension       ext = extensions.getExtension(oid);
409
410                    if (ext.isCritical())
411                    {
412                        set.add(oid.getId());
413                    }
414                }
415
416                return set;
417            }
418        }
419
420        return null;
421    }
422
423    private byte[] getExtensionBytes(String oid)
424    {
425        X509Extensions exts = c.getTBSCertificate().getExtensions();
426
427        if (exts != null)
428        {
429            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
430            if (ext != null)
431            {
432                return ext.getValue().getOctets();
433            }
434        }
435
436        return null;
437    }
438
439    public byte[] getExtensionValue(String oid)
440    {
441        X509Extensions exts = c.getTBSCertificate().getExtensions();
442
443        if (exts != null)
444        {
445            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
446
447            if (ext != null)
448            {
449                try
450                {
451                    return ext.getValue().getEncoded();
452                }
453                catch (Exception e)
454                {
455                    throw new IllegalStateException("error parsing " + e.toString());
456                }
457            }
458        }
459
460        return null;
461    }
462
463    public Set getNonCriticalExtensionOIDs()
464    {
465        if (this.getVersion() == 3)
466        {
467            Set             set = new HashSet();
468            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
469
470            if (extensions != null)
471            {
472                Enumeration     e = extensions.oids();
473
474                while (e.hasMoreElements())
475                {
476                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
477                    X509Extension       ext = extensions.getExtension(oid);
478
479                    if (!ext.isCritical())
480                    {
481                        set.add(oid.getId());
482                    }
483                }
484
485                return set;
486            }
487        }
488
489        return null;
490    }
491
492    public boolean hasUnsupportedCriticalExtension()
493    {
494        if (this.getVersion() == 3)
495        {
496            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
497
498            if (extensions != null)
499            {
500                Enumeration     e = extensions.oids();
501
502                while (e.hasMoreElements())
503                {
504                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
505                    String              oidId = oid.getId();
506
507                    if (oidId.equals(RFC3280CertPathUtilities.KEY_USAGE)
508                     || oidId.equals(RFC3280CertPathUtilities.CERTIFICATE_POLICIES)
509                     || oidId.equals(RFC3280CertPathUtilities.POLICY_MAPPINGS)
510                     || oidId.equals(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY)
511                     || oidId.equals(RFC3280CertPathUtilities.CRL_DISTRIBUTION_POINTS)
512                     || oidId.equals(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT)
513                     || oidId.equals(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR)
514                     || oidId.equals(RFC3280CertPathUtilities.POLICY_CONSTRAINTS)
515                     || oidId.equals(RFC3280CertPathUtilities.BASIC_CONSTRAINTS)
516                     || oidId.equals(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME)
517                     || oidId.equals(RFC3280CertPathUtilities.NAME_CONSTRAINTS))
518                    {
519                        continue;
520                    }
521
522                    X509Extension       ext = extensions.getExtension(oid);
523
524                    if (ext.isCritical())
525                    {
526                        return true;
527                    }
528                }
529            }
530        }
531
532        return false;
533    }
534
535    public PublicKey getPublicKey()
536    {
537        try
538        {
539            return BouncyCastleProvider.getPublicKey(c.getSubjectPublicKeyInfo());
540        }
541        catch (IOException e)
542        {
543            return null;   // should never happen...
544        }
545    }
546
547    // BEGIN android-changed
548    private byte[] encoded;
549    // END android-changed
550    public byte[] getEncoded()
551        throws CertificateEncodingException
552    {
553        try
554        {
555            // BEGIN android-changed
556            if (encoded == null) {
557                encoded = c.getEncoded(ASN1Encoding.DER);
558            }
559            return encoded;
560            // END android-changed
561        }
562        catch (IOException e)
563        {
564            throw new CertificateEncodingException(e.toString());
565        }
566    }
567
568    public boolean equals(
569        Object o)
570    {
571        if (o == this)
572        {
573            return true;
574        }
575
576        if (!(o instanceof Certificate))
577        {
578            return false;
579        }
580
581        Certificate other = (Certificate)o;
582
583        try
584        {
585            byte[] b1 = this.getEncoded();
586            byte[] b2 = other.getEncoded();
587
588            return Arrays.areEqual(b1, b2);
589        }
590        catch (CertificateEncodingException e)
591        {
592            return false;
593        }
594    }
595
596    public synchronized int hashCode()
597    {
598        if (!hashValueSet)
599        {
600            hashValue = calculateHashCode();
601            hashValueSet = true;
602        }
603
604        return hashValue;
605    }
606
607    private int calculateHashCode()
608    {
609        try
610        {
611            int hashCode = 0;
612            byte[] certData = this.getEncoded();
613            for (int i = 1; i < certData.length; i++)
614            {
615                 hashCode += certData[i] * i;
616            }
617            return hashCode;
618        }
619        catch (CertificateEncodingException e)
620        {
621            return 0;
622        }
623    }
624
625    public void setBagAttribute(
626        ASN1ObjectIdentifier oid,
627        ASN1Encodable        attribute)
628    {
629        attrCarrier.setBagAttribute(oid, attribute);
630    }
631
632    public ASN1Encodable getBagAttribute(
633        DERObjectIdentifier oid)
634    {
635        return attrCarrier.getBagAttribute(oid);
636    }
637
638    public Enumeration getBagAttributeKeys()
639    {
640        return attrCarrier.getBagAttributeKeys();
641    }
642
643    public String toString()
644    {
645        StringBuffer    buf = new StringBuffer();
646        String          nl = System.getProperty("line.separator");
647
648        buf.append("  [0]         Version: ").append(this.getVersion()).append(nl);
649        buf.append("         SerialNumber: ").append(this.getSerialNumber()).append(nl);
650        buf.append("             IssuerDN: ").append(this.getIssuerDN()).append(nl);
651        buf.append("           Start Date: ").append(this.getNotBefore()).append(nl);
652        buf.append("           Final Date: ").append(this.getNotAfter()).append(nl);
653        buf.append("            SubjectDN: ").append(this.getSubjectDN()).append(nl);
654        buf.append("           Public Key: ").append(this.getPublicKey()).append(nl);
655        buf.append("  Signature Algorithm: ").append(this.getSigAlgName()).append(nl);
656
657        byte[]  sig = this.getSignature();
658
659        buf.append("            Signature: ").append(new String(Hex.encode(sig, 0, 20))).append(nl);
660        for (int i = 20; i < sig.length; i += 20)
661        {
662            if (i < sig.length - 20)
663            {
664                buf.append("                       ").append(new String(Hex.encode(sig, i, 20))).append(nl);
665            }
666            else
667            {
668                buf.append("                       ").append(new String(Hex.encode(sig, i, sig.length - i))).append(nl);
669            }
670        }
671
672        X509Extensions  extensions = c.getTBSCertificate().getExtensions();
673
674        if (extensions != null)
675        {
676            Enumeration     e = extensions.oids();
677
678            if (e.hasMoreElements())
679            {
680                buf.append("       Extensions: \n");
681            }
682
683            while (e.hasMoreElements())
684            {
685                DERObjectIdentifier     oid = (DERObjectIdentifier)e.nextElement();
686                X509Extension           ext = extensions.getExtension(oid);
687
688                if (ext.getValue() != null)
689                {
690                    byte[]                  octs = ext.getValue().getOctets();
691                    ASN1InputStream         dIn = new ASN1InputStream(octs);
692                    buf.append("                       critical(").append(ext.isCritical()).append(") ");
693                    try
694                    {
695                        if (oid.equals(X509Extension.basicConstraints))
696                        {
697                            buf.append(BasicConstraints.getInstance(dIn.readObject())).append(nl);
698                        }
699                        else if (oid.equals(X509Extension.keyUsage))
700                        {
701                            buf.append(new KeyUsage((DERBitString)dIn.readObject())).append(nl);
702                        }
703                        else if (oid.equals(MiscObjectIdentifiers.netscapeCertType))
704                        {
705                            buf.append(new NetscapeCertType((DERBitString)dIn.readObject())).append(nl);
706                        }
707                        else if (oid.equals(MiscObjectIdentifiers.netscapeRevocationURL))
708                        {
709                            buf.append(new NetscapeRevocationURL((DERIA5String)dIn.readObject())).append(nl);
710                        }
711                        else if (oid.equals(MiscObjectIdentifiers.verisignCzagExtension))
712                        {
713                            buf.append(new VerisignCzagExtension((DERIA5String)dIn.readObject())).append(nl);
714                        }
715                        else
716                        {
717                            buf.append(oid.getId());
718                            buf.append(" value = ").append(ASN1Dump.dumpAsString(dIn.readObject())).append(nl);
719                            //buf.append(" value = ").append("*****").append(nl);
720                        }
721                    }
722                    catch (Exception ex)
723                    {
724                        buf.append(oid.getId());
725                   //     buf.append(" value = ").append(new String(Hex.encode(ext.getValue().getOctets()))).append(nl);
726                        buf.append(" value = ").append("*****").append(nl);
727                    }
728                }
729                else
730                {
731                    buf.append(nl);
732                }
733            }
734        }
735
736        return buf.toString();
737    }
738
739    public final void verify(
740        PublicKey   key)
741        throws CertificateException, NoSuchAlgorithmException,
742        InvalidKeyException, NoSuchProviderException, SignatureException
743    {
744        Signature   signature;
745        String      sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
746
747        try
748        {
749            signature = Signature.getInstance(sigName, BouncyCastleProvider.PROVIDER_NAME);
750        }
751        catch (Exception e)
752        {
753            signature = Signature.getInstance(sigName);
754        }
755
756        checkSignature(key, signature);
757    }
758
759    public final void verify(
760        PublicKey   key,
761        String      sigProvider)
762        throws CertificateException, NoSuchAlgorithmException,
763        InvalidKeyException, NoSuchProviderException, SignatureException
764    {
765        String    sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
766        Signature signature = Signature.getInstance(sigName, sigProvider);
767
768        checkSignature(key, signature);
769    }
770
771    private void checkSignature(
772        PublicKey key,
773        Signature signature)
774        throws CertificateException, NoSuchAlgorithmException,
775            SignatureException, InvalidKeyException
776    {
777        if (!isAlgIdEqual(c.getSignatureAlgorithm(), c.getTBSCertificate().getSignature()))
778        {
779            throw new CertificateException("signature algorithm in TBS cert not same as outer cert");
780        }
781
782        ASN1Encodable params = c.getSignatureAlgorithm().getParameters();
783
784        // TODO This should go after the initVerify?
785        X509SignatureUtil.setSignatureParameters(signature, params);
786
787        signature.initVerify(key);
788
789        signature.update(this.getTBSCertificate());
790
791        if (!signature.verify(this.getSignature()))
792        {
793            throw new SignatureException("certificate does not verify with supplied key");
794        }
795    }
796
797    private boolean isAlgIdEqual(AlgorithmIdentifier id1, AlgorithmIdentifier id2)
798    {
799        if (!id1.getObjectId().equals(id2.getObjectId()))
800        {
801            return false;
802        }
803
804        if (id1.getParameters() == null)
805        {
806            if (id2.getParameters() != null && !id2.getParameters().equals(DERNull.INSTANCE))
807            {
808                return false;
809            }
810
811            return true;
812        }
813
814        if (id2.getParameters() == null)
815        {
816            if (id1.getParameters() != null && !id1.getParameters().equals(DERNull.INSTANCE))
817            {
818                return false;
819            }
820
821            return true;
822        }
823
824        return id1.getParameters().equals(id2.getParameters());
825    }
826}
827