View Javadoc

1   package org.eparapher.core.crypto.cert;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.IOException;
5   import java.io.UnsupportedEncodingException;
6   import java.security.Principal;
7   import java.security.PublicKey;
8   import java.security.cert.CertificateParsingException;
9   import java.security.cert.X509Certificate;
10  import java.security.interfaces.RSAPublicKey;
11  import java.text.DateFormat;
12  import java.text.SimpleDateFormat;
13  import java.util.ArrayList;
14  import java.util.Collection;
15  import java.util.Collections;
16  import java.util.HashMap;
17  import java.util.Iterator;
18  import java.util.List;
19  
20  import org.apache.log4j.Logger;
21  import org.bouncycastle.asn1.ASN1InputStream;
22  import org.bouncycastle.asn1.ASN1OctetString;
23  import org.bouncycastle.asn1.ASN1Sequence;
24  import org.bouncycastle.asn1.ASN1TaggedObject;
25  import org.bouncycastle.asn1.DEREncodable;
26  import org.bouncycastle.asn1.DERIA5String;
27  import org.bouncycastle.asn1.DERObject;
28  import org.bouncycastle.asn1.DERObjectIdentifier;
29  import org.bouncycastle.asn1.DERSequence;
30  import org.bouncycastle.asn1.DERTaggedObject;
31  import org.bouncycastle.asn1.DERUTF8String;
32  import org.bouncycastle.asn1.x509.DistributionPoint;
33  import org.bouncycastle.asn1.x509.GeneralName;
34  import org.bouncycastle.asn1.x509.GeneralNames;
35  import org.bouncycastle.asn1.x509.X509Name;
36  import org.bouncycastle.jce.X509Principal;
37  import org.bouncycastle.jce.provider.JCEECPublicKey;
38  import org.bouncycastle.jce.provider.JDKDSAPublicKey;
39  import org.bouncycastle.util.encoders.Hex;
40  /**
41   * This class parse X509 certificate in order to show text informations for the end user.
42   * @author Arnault MICHEL
43   */
44  public class CertificateInfo {
45  
46      private static Logger log = Logger.getLogger(CertificateInfo.class);
47  
48      /** KeyUsage constants */
49      public static final int DIGITALSIGNATURE = 0;
50      public static final int NONREPUDIATION = 1;
51      public static final int KEYENCIPHERMENT = 2;
52      public static final int DATAENCIPHERMENT = 3;
53      public static final int KEYAGREEMENT = 4;
54      public static final int KEYCERTSIGN = 5;
55      public static final int CRLSIGN = 6;
56      public static final int ENCIPHERONLY = 7;
57      public static final int DECIPHERONLY = 8;
58  
59      public static final String[] KEYUSAGETEXTS = { 
60          "DIGITALSIGNATURE",
61          "NONREPUDIATION",
62          "KEYENCIPHERMENT",
63          "DATAENCIPHERMENT",
64          "KEYAGREEMENT",
65          "KEYCERTSIGN",
66          "CRLSIGN",
67          "ENCIPHERONLY",
68          "DECIPHERONLY" };
69  
70      /** Extended key usage constants */
71      public static final int ANYEXTENDEDKEYUSAGE = 0;
72      public static final int SERVERAUTH = 1;
73      public static final int CLIENTAUTH = 2;
74      public static final int CODESIGNING = 3;
75      public static final int EMAILPROTECTION = 4;
76      public static final int IPSECENDSYSTEM = 5;
77      public static final int IPSECTUNNEL = 6;
78      public static final int IPSECUSER = 7;
79      public static final int TIMESTAMPING = 8;
80      public static final int SMARTCARDLOGON = 9;
81      public static final int OCSPSIGNING = 10;
82  
83      public static final String[] EXTENDEDKEYUSAGEOIDSTRINGS = {
84          "1.3.6.1.5.5.7.3.0",
85          "1.3.6.1.5.5.7.3.1",
86          "1.3.6.1.5.5.7.3.2",
87          "1.3.6.1.5.5.7.3.3",
88          "1.3.6.1.5.5.7.3.4",
89          "1.3.6.1.5.5.7.3.5",
90          "1.3.6.1.5.5.7.3.6",
91          "1.3.6.1.5.5.7.3.7",
92          "1.3.6.1.5.5.7.3.8",
93          "1.3.6.1.4.1.311.20.2.2",
94          "1.3.6.1.5.5.7.3.9" };
95  
96      public static final String[] EXTENDEDKEYUSAGETEXTS = {
97          "ANYEXTENDEDKEYUSAGE",
98          "SERVERAUTH",
99          "CLIENTAUTH",
100         "CODESIGNING",
101         "EMAILPROTECTION",
102         "IPSECENDSYSTEM",
103         "IPSECTUNNEL",
104         "IPSECUSER",
105         "TIMESTAMPING",
106         "SMARTCARDLOGON",
107         "OCSPSIGNER" };
108 
109     private static final int SUBALTNAME_OTHERNAME = 0;
110     private static final int SUBALTNAME_RFC822NAME = 1;
111     private static final int SUBALTNAME_DNSNAME = 2;
112     private static final int SUBALTNAME_X400ADDRESS = 3;
113     private static final int SUBALTNAME_DIRECTORYNAME = 4;
114     private static final int SUBALTNAME_EDIPARTYNAME = 5;
115     private static final int SUBALTNAME_URI = 6;
116     private static final int SUBALTNAME_IPADDRESS = 7;
117     private static final int SUBALTNAME_REGISTREDID = 8;
118 
119     /** Microsoft altName for windows smart card logon */
120     public static final String UPN = "upn";
121     /** ObjectID for upn altName for windows smart card logon */
122     public static final String UPN_OBJECTID = "1.3.6.1.4.1.311.20.2.3";
123 
124     /** Microsoft altName for windows domain controller guid */
125     public static final String GUID = "guid";
126     /** ObjectID for upn altName for windows domain controller guid */
127     public static final String GUID_OBJECTID = "1.3.6.1.4.1.311.25.1";
128 
129     private static DateFormat completedateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
130     private static DateFormat simpledateFormat   = new SimpleDateFormat("MM/dd/yyyy");
131 
132     private X509Certificate certificate;
133     private X509Principal subjectdnfieldextractor, issuerdnfieldextractor;
134     private String subjectaltnamestring;
135     private String subjectdirattrstring;
136     private static HashMap<String, String> extendedkeyusageoidtotextmap;
137 
138     public CertificateInfo(X509Certificate certificate) {
139         this.certificate = certificate;
140 
141         subjectdnfieldextractor = new X509Principal(certificate.getSubjectDN().getName());
142         issuerdnfieldextractor = new X509Principal(certificate.getIssuerDN().getName());
143 
144         // Build HashMap of Extended KeyUsage OIDs (String) to Text representation (String)
145         if (extendedkeyusageoidtotextmap == null) {
146             extendedkeyusageoidtotextmap = new HashMap<String, String>();
147             for (int i = 0; i < EXTENDEDKEYUSAGETEXTS.length; i++)
148                 extendedkeyusageoidtotextmap.put(EXTENDEDKEYUSAGEOIDSTRINGS[i],
149                         EXTENDEDKEYUSAGETEXTS[i]);
150         }
151     }
152 
153     public String getSubjectAltName() {
154         if (subjectaltnamestring == null)
155             try {
156                 if (certificate.getSubjectAlternativeNames() != null) {
157                     subjectaltnamestring = "";
158 
159                     String separator = "";
160                     String guid = null;
161                     try {
162                         guid = getGuidAltName(certificate);
163                     } catch (IOException e) {
164                         subjectaltnamestring = e.getMessage();
165                     }
166                     if (guid != null) {
167                         subjectaltnamestring += separator + "GUID=" + guid;
168                         separator = ", ";
169                     }
170                     String upn = null;
171                     try {
172                         upn = getUPNAltName(certificate);
173                     } catch (IOException e) {
174                         subjectaltnamestring = e.getMessage();
175                     }
176                     if (upn != null) {
177                         subjectaltnamestring += separator + "UPN=" + upn;
178                         separator = ", ";
179                     }
180 
181                     Iterator iter = certificate.getSubjectAlternativeNames()
182                     .iterator();
183                     while (iter.hasNext()) {
184                         List next = (List) iter.next();
185                         int OID = ((Integer) next.get(0)).intValue();
186 
187                         switch (OID) {
188                             case SUBALTNAME_OTHERNAME:
189                                 // Already taken care of
190                                 Object obj = next.get(1);
191                                 if (obj!=null) {
192                                     subjectaltnamestring += separator + "OtherName=" + obj.toString();
193                                     separator = ", ";
194                                 }
195                                 break;
196                             case SUBALTNAME_RFC822NAME:
197                                 subjectaltnamestring += separator + "RFC822Name="
198                                 + (String) next.get(1);
199                                 separator = ", ";
200                                 break;
201                             case SUBALTNAME_DNSNAME:
202                                 subjectaltnamestring += separator + "DNSName="
203                                 + (String) next.get(1);
204                                 separator = ", ";
205                                 break;
206                             case SUBALTNAME_X400ADDRESS:
207                                 //TODO Implement X400ADDRESS
208                                 break;
209                             case SUBALTNAME_EDIPARTYNAME:
210                                 //TODO Implement EDIPARTYNAME
211                                 break;
212                             case SUBALTNAME_DIRECTORYNAME:
213                                 //TODO Implement EDIPARTYNAME
214                                 break;
215                             case SUBALTNAME_URI:
216                                 if (!subjectaltnamestring.equals(""))
217                                     subjectaltnamestring += ", ";
218                                 subjectaltnamestring += separator + "URI="
219                                 + (String) next.get(1);
220                                 separator = ", ";
221                                 break;
222                             case SUBALTNAME_IPADDRESS:
223                                 subjectaltnamestring += separator + "IPAddress="
224                                 + (String) next.get(1);
225                                 separator = ", ";
226                                 break;
227                             case SUBALTNAME_REGISTREDID:
228                                 //TODO implement REGISTREDID
229                                 break;
230                         }
231 
232                     }
233                 }
234             } catch (CertificateParsingException e) {
235                 subjectaltnamestring = e.getMessage();
236             }
237 
238             return subjectaltnamestring;
239     }
240 
241     /**
242      * Gets the Microsoft specific GUID altName, that is encoded as an octect string.
243      *
244      * @param cert certificate containing the extension
245      * @return String with the hex-encoded GUID byte array or null if the altName does not exist
246      */
247     public static String getGuidAltName(X509Certificate cert)
248     throws IOException, CertificateParsingException {
249         Collection altNames = cert.getSubjectAlternativeNames();
250         if (altNames != null) {
251             Iterator i = altNames.iterator();
252             while (i.hasNext()) {
253                 ASN1Sequence seq = getAltnameSequence((List) i.next());
254                 if (seq != null) {
255                     // First in sequence is the object identifier, that we must check
256                     DERObjectIdentifier id = DERObjectIdentifier
257                     .getInstance(seq.getObjectAt(0));
258                     if (id.getId().equals(GUID_OBJECTID)) {
259                         ASN1TaggedObject obj = (ASN1TaggedObject) seq
260                         .getObjectAt(1);
261                         ASN1OctetString str = ASN1OctetString.getInstance(obj
262                                 .getObject());
263                         return new String(Hex.encode(str.getOctets()));
264                     }
265                 }
266             }
267         }
268         return null;
269     } // getGuidAltName
270 
271     /**
272      * Gets the Microsoft specific UPN altName.
273      *
274      * @param cert certificate containing the extension
275      * @return String with the UPN name or null if the altName does not exist
276      */
277     public static String getUPNAltName(X509Certificate cert)
278     throws IOException, CertificateParsingException {
279         Collection altNames = cert.getSubjectAlternativeNames();
280         if (altNames != null) {
281             Iterator i = altNames.iterator();
282             while (i.hasNext()) {
283                 ASN1Sequence seq = getAltnameSequence((List) i.next());
284                 String ret = getUPNStringFromSequence(seq);
285                 if (ret != null)
286                     return ret;
287             }
288         }
289         return null;
290     } // getUPNAltName
291 
292     /** Helper method for the above method
293      */
294     private static String getUPNStringFromSequence(ASN1Sequence seq) {
295         if (seq != null) {
296             // First in sequence is the object identifier, that we must check
297             DERObjectIdentifier id = DERObjectIdentifier.getInstance(seq
298                     .getObjectAt(0));
299             if (id.getId().equals(UPN_OBJECTID)) {
300                 ASN1TaggedObject obj = (ASN1TaggedObject) seq.getObjectAt(1);
301                 DERUTF8String str = DERUTF8String.getInstance(obj.getObject());
302                 return str.getString();
303             }
304         }
305         return null;
306     }
307 
308     /** Helper for the above methods
309      */
310     private static ASN1Sequence getAltnameSequence(List listitem)
311     throws IOException {
312         Integer no = (Integer) listitem.get(0);
313         if (no.intValue() == 0) {
314             byte[] altName = (byte[]) listitem.get(1);
315             return getAltnameSequence(altName);
316         }
317         return null;
318     }
319 
320     private static ASN1Sequence getAltnameSequence(byte[] value)
321     throws IOException {
322         DERObject oct = null;
323         try {
324             oct = (new ASN1InputStream(new ByteArrayInputStream(value))
325             .readObject());
326         } catch (java.io.IOException e) {
327             log.error("Error on getting Alt Name as a DERSEquence : " + e.getLocalizedMessage(),e);
328         }
329         ASN1Sequence seq = ASN1Sequence.getInstance(oct);
330         return seq;
331     }
332 
333     public static String getKeyUsageAsText(X509Certificate certificate){
334         if (certificate == null)
335             return null;
336 
337         String kuText = "";
338         boolean[] keyusage = certificate.getKeyUsage();
339         if (keyusage == null) return "";
340         if (keyusage[0]) kuText += "digitalSignature";
341         if (keyusage[1]) kuText += (kuText.equals("")?"":", ") + "nonRepudiation";
342         if (keyusage[2]) kuText += (kuText.equals("")?"":", ") + "keyEncipherment";
343         if (keyusage[3]) kuText += (kuText.equals("")?"":", ") + "dataEncipherment";
344         if (keyusage[4]) kuText += (kuText.equals("")?"":", ") + "keyAgreement";
345         if (keyusage[5]) kuText += (kuText.equals("")?"":", ") + "keyCertSign";
346         if (keyusage[6]) kuText += (kuText.equals("")?"":", ") + "cRLSign";
347         if (keyusage[7]) kuText += (kuText.equals("")?"":", ") + "encipherOnly";
348         if (keyusage[8]) kuText += (kuText.equals("")?"":", ") + "decipherOnly";
349         return kuText;
350     }
351 
352     @SuppressWarnings("unchecked")
353     public static String getExtendedKeyUsageAsText(X509Certificate certificate){
354         java.util.List extendedkeyusage = null;
355 
356         HashMap<String, String>   extendedkeyusageoidtotextmap = null;
357         String[] EXTENDEDKEYUSAGEOIDSTRINGS = { "2.5.29.37.0",
358                 "1.3.6.1.5.5.7.3.0",
359                 "1.3.6.1.5.5.7.3.1",
360                 "1.3.6.1.5.5.7.3.2",
361                 "1.3.6.1.5.5.7.3.3",
362                 "1.3.6.1.5.5.7.3.4",
363                 "1.3.6.1.5.5.7.3.5",
364                 "1.3.6.1.5.5.7.3.6",
365                 "1.3.6.1.5.5.7.3.7",
366                 "1.3.6.1.5.5.7.3.8",
367                 "1.3.6.1.4.1.311.20.2.2",
368         "1.3.6.1.5.5.7.3.9"};
369 
370         String[] EXTENDEDKEYUSAGETEXTS = { "All usages",
371                 "All usages",
372                 "Server authentication",
373                 "Client authentication",
374                 "Code signing",
375                 "Email protection",
376                 "IPSec end system",
377                 "IPSec tunnel",
378                 "IPSec user",
379                 "Timestamping",
380                 "Smartcard Logon",
381         "OCSP signer"};
382 
383         // Build HashMap of Extended KeyUsage OIDs (String) to Text representation (String)
384         extendedkeyusageoidtotextmap = new HashMap<String, String>();
385         for(int i=0; i < EXTENDEDKEYUSAGETEXTS.length; i++)
386             extendedkeyusageoidtotextmap.put(EXTENDEDKEYUSAGEOIDSTRINGS[i], EXTENDEDKEYUSAGETEXTS[i]);
387         try{
388             extendedkeyusage = certificate.getExtendedKeyUsage();
389         } catch(java.security.cert.CertificateParsingException e){
390             log.error("certificate parsing exception" + e.getLocalizedMessage(),e);
391             return null;
392         }
393         if(extendedkeyusage == null)
394             extendedkeyusage = new java.util.ArrayList();
395 
396         /*String[] returnval = new String[extendedkeyusage.size()];
397         for(int i1=0; i1 < extendedkeyusage.size(); i1++){
398           returnval[i1] = (String) extendedkeyusageoidtotextmap.get(extendedkeyusage.get(i1));
399         }*/
400 
401         String returnval = "";
402         for(int i=0; i < extendedkeyusage.size(); i++)
403             returnval += (returnval.equals("")?"":", ") + extendedkeyusageoidtotextmap.get(extendedkeyusage.get(i));
404         return returnval;
405     }
406 
407     public static String getSubjectAsShortText(X509Certificate certificate) {
408         certificate = X509Util.getBCCertificate(certificate);
409         return getDNAsShortText(certificate.getSubjectDN());
410     }
411 
412     public static String getIssuerAsShortText(X509Certificate certificate) {
413         certificate = X509Util.getBCCertificate(certificate);
414         return getDNAsShortText(certificate.getIssuerDN());
415     }
416 
417     //TODO : need to be RFC compliant
418     public static String getDNAsShortText(Principal dn) {
419         X509Principal X509dn = new X509Principal(dn.getName());
420         //log.debug("extracting short name from dn " + X509dn);
421         if (X509dn!=null && X509dn.getValues(X509Principal.CN).size()>0)
422             return X509dn.getValues(X509Principal.CN).get(0).toString();
423         else {
424             String str_dn = dn.getName();
425             int last_equal = str_dn.lastIndexOf("=");
426             if (last_equal>=0)
427                 return str_dn.substring( last_equal+1, str_dn.length());
428             return str_dn;
429         }
430     }
431 
432     //TODO : Locale Date?
433     //private static DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL, Locale.getDefault());
434     public static String getNotBeforeAsText(X509Certificate certificate) {
435         return simpledateFormat.format( certificate.getNotBefore() );
436     }
437     public static String getNotBeforeAsFullText(X509Certificate certificate) {
438         return completedateFormat.format( certificate.getNotBefore() );
439     }
440     public static String getNotAfterAsText(X509Certificate certificate) {
441         return simpledateFormat.format( certificate.getNotAfter() );
442     }
443     public static String getNotAfterAsFullText(X509Certificate certificate) {
444         return completedateFormat.format( certificate.getNotAfter() );
445     }
446 
447     public static String getPublicKeyInfo(PublicKey pk) {
448         int keysize = 0;
449         String format = pk.getAlgorithm();
450         if (pk instanceof RSAPublicKey) {
451             RSAPublicKey rsapk = (RSAPublicKey) pk;
452             keysize = (rsapk.getModulus().toByteArray().length -1) * 8;
453         }
454         if (pk instanceof JCEECPublicKey) {
455             JCEECPublicKey ecpubkey = (JCEECPublicKey) pk;
456             keysize = ecpubkey.getQ().getX().getFieldSize();
457             //ECParameterSpec ecpspecs = ecpubkey.getParams();
458             format = "ECDSA";
459             //if ( ecpspecs instanceof ECNamedCurveSpec ) {
460             //	ECNamedCurveSpec ecncspec = (ECNamedCurveSpec) ecpspecs;
461             //	format += " (" + ecncspec.getName() + ")";
462             //}
463         }
464         if (pk instanceof JDKDSAPublicKey) {
465             JDKDSAPublicKey dsapubkey = (JDKDSAPublicKey) pk;
466             keysize = dsapubkey.getY().bitLength();
467         }
468 
469         return  format + " " + keysize + "bits";
470     }
471 
472     public static List<String> getSubjectAlternativeNames(X509Certificate certificate) {
473         List<String> identities = new ArrayList<String>();
474         try {
475             Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
476             // Check that the certificate includes the SubjectAltName extension
477             if (altNames == null)
478                 return Collections.emptyList();
479             // Use the type OtherName to search for the certified server name
480             for (List item : altNames) {
481                 Integer type = (Integer) item.get(0);
482                 if (type == 0)
483                     // Type OtherName found so return the associated value
484                     try {
485                         // Value is encoded using ASN.1 so decode it to get the server's identity
486                         ASN1InputStream decoder = new ASN1InputStream((byte[]) item.toArray()[1]);
487                         DEREncodable encoded = decoder.readObject();
488                         encoded = ((DERSequence) encoded).getObjectAt(1);
489                         encoded = ((DERTaggedObject) encoded).getObject();
490                         encoded = ((DERTaggedObject) encoded).getObject();
491                         String identity = ((DERUTF8String) encoded).getString();
492                         // Add the decoded server name to the list of identities
493                         identities.add(identity);
494                     }
495                 catch (UnsupportedEncodingException e) {
496                     log.error("Error decoding subjectAltName" + e.getLocalizedMessage(),e);
497                 }
498                 catch (Exception e) {
499                     log.error("Error decoding subjectAltName" + e.getLocalizedMessage(),e);
500                 }
501                 // Other types are not good for XMPP so ignore them
502                 log.warn("SubjectAltName of invalid type found: " + certificate);
503             }
504         }
505         catch (CertificateParsingException e) {
506             log.error("Error parsing SubjectAltName in certificate: " + certificate + "\r\nerror:" + e.getLocalizedMessage(),e);
507         }
508         return identities;
509     }
510 
511     public String getCDPAsText() {
512 
513         DistributionPoint[] cdp = null;
514         try {
515             cdp = X509Util.getCrlDistributionPoint(certificate);
516         } catch (CertificateParsingException e) {
517             log.error("Error while parsing CDP");
518         }
519         String returnvalue = "";
520         for (DistributionPoint distributionPoint : cdp) {
521             if (!returnvalue.equals(""))
522                 returnvalue += System.getProperty("line.separator");
523             if (distributionPoint.getCRLIssuer()!=null)
524                 returnvalue += distributionPoint.getCRLIssuer() + "=";
525 
526             GeneralNames cdpgns = GeneralNames.getInstance(distributionPoint.getDistributionPoint().getName());
527             GeneralName[] cdpgn = cdpgns.getNames();
528             for (GeneralName element : cdpgn)
529                 returnvalue += GeneralNameAsText(element);
530         }
531         return returnvalue;
532     }
533     public static String GeneralNameAsText(GeneralName gn) {
534 
535         StringBuffer buf = new StringBuffer();
536 
537         int tag = gn.getTagNo();
538         DEREncodable obj = gn.getName();
539 
540         switch (tag) {
541             case GeneralName.rfc822Name:
542                 buf.append("rfc822Name=");
543                 buf.append(DERIA5String.getInstance(obj).getString());
544                 break;
545             case GeneralName.dNSName:
546                 buf.append("dNSName=");
547                 buf.append(DERIA5String.getInstance(obj).getString());
548                 break;
549             case GeneralName.uniformResourceIdentifier:
550                 buf.append("URI=");
551                 buf.append(DERIA5String.getInstance(obj).getString());
552                 break;
553             case GeneralName.directoryName:
554                 buf.append("directoryName=");
555                 buf.append(X509Name.getInstance(obj).toString());
556                 break;
557             case GeneralName.ediPartyName:
558                 buf.append("ediPartyName=");
559                 buf.append(obj.toString());
560                 break;
561             case GeneralName.iPAddress:
562                 buf.append("IP=");
563                 buf.append(obj.toString());
564                 break;
565             case GeneralName.otherName:
566                 buf.append("otherName=");
567                 buf.append(obj.toString());
568                 break;
569             case GeneralName.registeredID:
570                 buf.append("registeredID=");
571                 buf.append(obj.toString());
572                 break;
573             case GeneralName.x400Address:
574                 buf.append("x400Address=");
575                 buf.append(obj.toString());
576                 break;
577             default:
578                 buf.append(gn.getTagNo()+"=");
579         }
580 
581         return buf.toString();
582     }
583 }