FileDocCategorySizeDatePackage
X509Certificate.javaAPI DocphoneME MR2 API (J2ME)70128Wed May 02 18:00:26 BST 2007com.sun.midp.pki

X509Certificate

public class X509Certificate extends Object implements Certificate
This class implements methods for creating X.509 certificates and accessing their attributes such as subject/issuer names, public keys and validity information. Publicly visible methods methods are modeled after those in the X509Certificate classes from J2SE (standard edition) but there are some differences and these are documented below.

NOTE: For now, only X.509 certificates containing RSA public keys and signed either using md2WithRSA, md5WithRSA, or sha-1WithRSA are supported. This version of the implementation is unable to parse certificates containing DSA keys or signed using DSA. Certificates containing RSA keys but signed using an unsupported algorithm can be parsed but cannot be verified. Not all version 3 extensions are supported (only subjectAltName, basicConstraints, keyUsage and extendedKeyUsage are recognized) but if an unrecognized extension is marked critical, an error notification is generated.

Fields Summary
public static final byte
NO_ERROR
Indicates a no error condition.
public static final int
MISSING_PATH_LENGTH_CONSTRAINT
Indicates that no information is available on the pathLengthConstraint associated with this certificate (this could happen if the certifiate is a v1 or v2 cert or a v3 cert without basicConstraints or a non-CA v3 certificate).
public static final int
UNLIMITED_CERT_CHAIN_LENGTH
Indicates there is no limit to the server certificate chain length.
private static final int
MAX_NAME_LENGTH
We expect issuer/subject names to fit within these many bytes.
private static final byte
ANY_STRING_TYPE
ASN ANY_STRING type used in certificate parsing (0x00).
private static final byte
INTEGER_TYPE
ASN INTEGER type used in certificate parsing (0x02).
private static final byte
BITSTRING_TYPE
ASN BIT STRING type used in certificate parsing (0x03).
private static final byte
OCTETSTR_TYPE
ASN OCTET STRING type used in certificate parsing (0x04).
private static final byte
OID_TYPE
ASN OBJECT ID type used in certificate parsing (0x06).
private static final byte
UTF8STR_TYPE
ASN UTF8 STRING type used in certificate parsing (0x0c).
private static final byte
UNIVSTR_TYPE
ASN UNICODE STRING type used in certificate parsing (0x12).
private static final byte
PRINTSTR_TYPE
ASN PRINT STRING type used in certificate parsing (0x13).
private static final byte
TELETEXSTR_TYPE
ASN TELETEX STRING type used in certificate parsing (0x14).
private static final byte
IA5STR_TYPE
ASN IA5 STRING type used in certificate parsing (0x16).
private static final byte
SEQUENCE_TYPE
ASN SEQUENCE type used in certificate parsing (0x30).
private static final byte
SET_TYPE
ASN SET type used in certificate parsing (0x31).
public static final byte
TYPE_EMAIL_ADDRESS
Email address (rfc 822) alternative name type code.
public static final byte
TYPE_DNS_NAME
DNS name alternative name type code.
public static final byte
TYPE_URI
URI alternative name type code.
public static final int
DIGITAL_SIG_KEY_USAGE
Bit mask for digital signature key usage.
public static final int
NON_REPUDIATION_KEY_USAGE
Bit mask for non repudiation key usage.
public static final int
KEY_ENCIPHER_KEY_USAGE
Bit mask for key encipherment key usage.
public static final int
DATA_ENCIPHER_KEY_USAGE
Bit mask for data encipherment key usage.
public static final int
KEY_AGREEMENT_KEY_USAGE
Bit mask for key agreement key usage.
public static final int
CERT_SIGN_KEY_USAGE
Bit mask for key certificate sign key usage.
public static final int
CRL_SIGN_KEY_USAGE
Bit mask for CRL sign key usage.
public static final int
ENCIPHER_ONLY_KEY_USAGE
Bit mask for encipher only key usage.
public static final int
DECIPHER_ONLY_KEY_USAGE
Bit mask for decipher only key usage.
public static final int
SERVER_AUTH_EXT_KEY_USAGE
Bit mask server auth for extended key usage.
public static final int
CLIENT_AUTH_EXT_KEY_USAGE
Bit mask client auth for extended key usage.
public static final int
CODE_SIGN_EXT_KEY_USAGE
Bit code signing mask for extended key usage.
public static final int
EMAIL_EXT_KEY_USAGE
Bit email protection mask for extended key usage.
public static final int
IPSEC_END_SYS_EXT_KEY_USAGE
Bit IPSEC end system mask for extended key usage.
public static final int
IPSEC_TUNNEL_EXT_KEY_USAGE
Bit IPSEC tunnel mask for extended key usage.
public static final int
IPSEC_USER_EXT_KEY_USAGE
Bit IPSEC user mask for extended key usage.
public static final int
TIME_STAMP_EXT_KEY_USAGE
Bit time stamping mask for extended key usage.
private static final int
UTC_LENGTH
The validity period is contained in thirteen bytes yymmddhhmmss followed by 'Z' (for zulu ie GMT), if yy < 50 assume 20yy else 19yy.
private static final char[]
nameAttr
Maps byte codes that follow id-at (0x55 0x04) to corresponding name component tags (e.g. Commom Name, or CN, is 0x55, 0x04, 0x03 and Country, or C, is 0x55, 0x04, 0x06). See getName. See X.520 for the OIDs and RFC 1779 for the printable labels. Place holders for unknown labels have a 0 as the first char.
private static final char[]
EMAIL_ATTR_LABEL
Email attribute label in bytes. "EmailAddress"
private static final byte[]
EMAIL_ATTR_OID
Email attribute object identifier.
private static final byte[]
PKCS1Seq
Includes DER encoding for OID 1.2.840.113549.1.1.
private static final byte
NONE
Uknown algorithm (-1).
private static final byte
RSA_ENCRYPTION
RAS ENCRYPTION (0x01).
private static final byte
MD2_RSA
MD2_RSA algorithm (0x02).
private static final byte
MD4_RSA
MD4_RSA algorithm (0x03).
private static final byte
MD5_RSA
MD4_RSA algorithm (0x04).
private static final byte
SHA1_RSA
SHA1_RSA algorithm (0x05).
private static final byte[]
PREFIX_MD2
Expected prefix in decrypted value when MD2 hash is used for signing 30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 02 05 00 04 10 see verify().
private static final byte[]
PREFIX_MD5
Expected prefix in decrypted value when MD5 hash is used for signing 30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 05 05 00 04 10 see verify().
private static final byte[]
PREFIX_SHA1
Expected prefix in decrypted value when SHA-1 hash is used for signing 30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14.
private static final byte[]
NullSeq
ASN encoding for NULL.
private static final byte[]
ValiditySeq
This is how the encoding of validity information begins.
private static final byte[]
UTCSeq
This is how the encoding of UTCTime begins.
private static final byte[]
ID_KP
Includes DER encoding for id-kp (key purpose).
private boolean
selfSigned
True iff subject matches issuer.
private byte
version
X.509 version. For more readable code the version field starts a 1.
private byte[]
fp
MD5 fingerprint of the certificate.
private String
serialNumber
Certificate serial number.
private String
subject
Certificate subject.
private String
issuer
Certificate issuer.
private long
from
Beginning of certificate validity period.
private long
until
End of certificate validity period.
private RSAPublicKey
pubKey
Certificate RSA Public key.
private int
idx
Index inside encoding.
private byte[]
enc
Contains Certificate DER encoding.
private int
TBSStart
Offset where TBSCertificate starts.
private int
TBSLen
Length of TBSCertificate.
private byte
sigAlg
Algorithm used to sign the cert.
private byte[]
signature
Issuer signature on certificate.
private byte[]
TBSCertHash
Hash of TBSCertificate.
private boolean
badExt
True iff cert has unrecognized critical extension.
private byte
subAltNameType
format of the subject alternative name, 2 means a DNS name
private Object
subAltName
subject alternative name
private boolean
hasBC
does the cert include BasicConstaints.
private boolean
isCA
CA value in BasicConstraints.
private int
pLenConstr
Path Length constriant from Basic constraints.
private int
keyUsage
Collection of keyUsage bits.
private int
extKeyUsage
Collection of extended keyUsage bits.
private static final String[]
KEY_USAGE
Array of purpose strings describing key usage role.
Constructors Summary
private X509Certificate()
Private constructor

    
       
      
    
public X509Certificate(byte ver, byte[] rawSerialNumber, String sub, String iss, long notBefore, long notAfter, byte[] mod, byte[] exp, byte[] chash, int pLen)
Creates an X.509 certificate with the specified attributes. This constructor is only used for creating trusted certificates.
NOTE: All signature related values in these certificates (such as the signing algorithm and signature) are set to null and invoking methods that access signature information, e.g. verify() and getSigAlgName() can produce unexpected errors.

param
ver byte containing X.509 version starting a 0
param
rawSerialNumber byte array containing the serial number
param
sub subject name
param
iss issuer name
param
notBefore start of validity period expressed in milliseconds since midnight Jan 1, 1970 UTC
param
notAfter end of validity period expressed as above
param
mod modulus associated with the RSA Public Key
param
exp exponent associated with the RSA Public Key
param
chash 16-byte MD5 hash of the certificate's ASN.1 DER encoding
param
pLen Is the pathLenConstraint associated with a version 3 certificate. This parameter is ignored for v1 and v2 certificates. If a v3 certificate does not have basicConstraints or is not a CA cert, callers should pass MISSING_PATH_LENGTH_CONSTRAINT. If the v3 certificate has basicConstraints, CA is set but pathLenConstraint is missing (indicating no limit on the certificate chain), callers should pass UNLIMITED_CERT_CHAIN_LENGTH.
exception
Exception in case of a problem with RSA public key parameters

            version = ver;
            serialNumber = Utils.hexEncode(rawSerialNumber, 0,
                                           rawSerialNumber.length);

            /*
             * We are paranoid so we don't just assign a reference as in
             * fp = chash; subject = sub; issuer = iss;
             */ 
            if (chash != null) {
                fp = new byte[chash.length];
                System.arraycopy(chash, 0, fp, 0, chash.length);
            }

            subject = new String(sub);
            issuer = new String(iss);
            from = notBefore;
            until = notAfter;
            sigAlg = NONE;
          
            if (subject.compareTo(issuer) == 0) {
                selfSigned = true;
            }

            pubKey = new RSAPublicKey(mod, exp);

            if ((ver == 3) && (pLen != MISSING_PATH_LENGTH_CONSTRAINT)) {
                hasBC = isCA = true;
                pLenConstr = pLen;
            }
        
Methods Summary
public voidcheckExtensions()
Checks if a certificate has any (version 3) extensions that were not properly processed and continued use of this certificate may be inconsistent with the issuer's intent. This may happen, for example, if the certificate has unrecognized critical extensions.

exception
CertificateException with a reason ofr BAD_EXTENSIONS if there are any bad extensions

        if (badExt) {
            throw new CertificateException(this,
                CertificateException.BAD_EXTENSIONS);
        }
    
private static voidcheckKeyUsageAndValidity(com.sun.midp.pki.X509Certificate cert, int keyUsage, int extKeyUsage)
Check the key usage, extended key usage and validity of a certificate.

param
cert certificate to check
param
keyUsage -1 to not check the key usage extension, or a key usage bit mask to check for if the extension is present
param
extKeyUsage -1 to not check the extended key usage extension, or a extended key usage bit mask to check for if the extension is present
exception
CertificateException if there is an error

        int certKeyUsage;

        // Check if this certificate has any bad extensions
        cert.checkExtensions();

        certKeyUsage = cert.getKeyUsage();
        if (keyUsage != -1 && certKeyUsage != -1 &&
                (certKeyUsage & keyUsage) != keyUsage) {
            throw new CertificateException(cert,
                 CertificateException.INAPPROPRIATE_KEY_USAGE);
        }            

        certKeyUsage = cert.getExtKeyUsage();
        if (extKeyUsage != -1 && certKeyUsage != -1 &&
                (certKeyUsage & extKeyUsage) != extKeyUsage) {
            throw new CertificateException(cert,
                 CertificateException.INAPPROPRIATE_KEY_USAGE);
        }            

        cert.checkValidity();
    
public voidcheckValidity()
Checks if the certificate is currently valid. It is if the current date and time are within the certificate's validity period.

exception
CertificateException with a reason of EXPIRED or NOT_YET_VALID

        checkValidity(System.currentTimeMillis());
    
public voidcheckValidity(long time)
Checks if the certificate is valid on the specified time. It is if the specified time is within the certificate's validity period.
NOTE: The standard edition provides a method with this name but it throws different types of exceptions rather than returning error codes.

param
time the time in milliseconds for which a certificate's validity is to be checked
exception
CertificateException with a reason of EXPIRED or NOT_YET_VALID

        if (time < from) {
            throw new CertificateException(this,
                                           CertificateException.NOT_YET_VALID);
        }

        if (time > until) {
            throw new CertificateException(this,
                                           CertificateException.EXPIRED);
        }
    
private static java.lang.Stringdate2str(java.util.Date date)
Converts a Date object to a string containing the corresponding date.
NOTE: This is here only because the J2ME date class does not implement toString() in any meaningful way.

param
date Date object to be converted
return
a string representation of the Date object in the form "month/day/year hour:min:sec"

    
                                                                       
         
        Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        c.setTime(date);
        String d = (c.get(Calendar.MONTH) + 1) + "/" +
            c.get(Calendar.DAY_OF_MONTH) + "/" +
            c.get(Calendar.YEAR) + " " +
            c.get(Calendar.HOUR_OF_DAY) + ":" +
            c.get(Calendar.MINUTE) + ":" + 
            c.get(Calendar.SECOND);
        return d;
    
public static com.sun.midp.pki.X509CertificategenerateCertificate(byte[] buf, int off, int len)
Creates a certificate by parsing the ASN.1 DER X.509 certificate encoding in the specified buffer.
NOTE: In the standard edition, equivalent functionality is provided by CertificateFactory.generateCertificate(InputStream).

param
buf byte array to be read
param
off offset within the byte array
param
len number of bytes to be read
return
a certificate object corresponding to the DER encoding or null (in case of an encoding problem)
exception
IOException if there is a parsing error

        /*
         * force bad parameter errors now, so later we can consider any out of
         * bounds errors to be parsing errors
         */
	int test = buf[off] + buf[len - 1] + buf[off + len - 1];

        try {
            int start = 0;
            int size = 0;
            byte[] hash = new byte[16]; // for MD5 fingerprint
            X509Certificate res = null;
            int publicKeyLen;
            int publicKeyPos;
            int modulusPos;
            int modulusLen;
            int exponentPos;
            int exponentLen;

            // Compute the MD5 fingerprint
            MessageDigest md = MessageDigest.getInstance("MD5");

            md.update(buf, off, len);
            md.digest(hash, 0, hash.length);
            
            /*
             * Create a new certificate and fill its attributes by parsing 
             * the DER encoding
             */ 
            res = new X509Certificate();

            // Prepare to parse this certificate
            res.idx = 0;
            // Set the encoding
            res.enc = new byte[len];
            System.arraycopy(buf, off, res.enc, 0, len);
            // ... and the fingerprint
            res.fp = new byte[hash.length];
            System.arraycopy(hash, 0, res.fp, 0, hash.length);
        
	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "-------- Begin Certificate -------");
	    }
            /*
             * A Certificate is a sequence of a TBSCertificate, a signature
             * algorithm identifier and the signature
             */ 
            res.getLen(SEQUENCE_TYPE);
            // Now read the TBS certificate
            res.TBSStart = res.idx;
            size = res.getLen(SEQUENCE_TYPE);
	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "-------- Begin TBSCertificate -------");
	    }

            int sigAlgIdx = res.idx + size;
            res.TBSLen = sigAlgIdx - res.TBSStart;
            // Now parse the version
            if ((res.enc[res.idx] & 0xf0) == 0xa0) {
                res.idx++;
		if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		    Logging.report(Logging.INFORMATION,
                                   LogChannels.LC_SECURITY,
				   "Version info: " + 
				   Utils.hexEncode(res.enc, (res.idx + 1), 
						   res.enc[res.idx]));
		}
                size = (res.enc[res.idx++] & 0xff);
                if (res.idx + size > res.enc.length) { 
                    throw new IOException("Version info too long");
                }

                res.version = (byte)(res.enc[res.idx + (size - 1)]);
                res.idx += size;
            } else {
                res.version = 1;  // No explicit version value
            }
            
            // Expect the serial number coded as an integer
            size = res.getLen(INTEGER_TYPE);
            res.serialNumber = Utils.hexEncode(res.enc, res.idx, size);
            res.idx += size;
            
            // Expect the signature AlgorithmIdentifier
            byte id = res.getAlg();
	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "Algorithm Id: " + id);
	    }
            // Expect the issuer name
            start = res.idx;
            size = res.getLen(SEQUENCE_TYPE);
            int end = res.idx + size;
            try {
                res.issuer = res.getName(end);
		if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		    Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
				   "Issuer: " + res.issuer);
		    
		}
            } catch (Exception e) {
                throw new IOException("Could not parse issuer name");
            }
            
            // Validity is a sequence of two UTCTime values
            try {
                res.match(ValiditySeq);
                // get start time
                res.match(UTCSeq);
                res.from = getUTCTime(res.enc, res.idx);
                res.idx += UTC_LENGTH;
                // get end time
                res.match(UTCSeq);
                res.until = getUTCTime(res.enc, res.idx);
                res.idx += UTC_LENGTH;
            } catch (Exception e) {
                throw new IOException("Could not parse validity information"
                                      + "caught " + e);
            }
            
            // Expect the subject name
            start = res.idx;
            size = res.getLen(SEQUENCE_TYPE);
            end = res.idx + size;
	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "Subject: " +
			       Utils.hexEncode(res.enc, start, size));
	    }
            if (size != 0) {
                try {
                    res.subject = res.getName(end);
		    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
			Logging.report(Logging.INFORMATION,
                                       LogChannels.LC_SECURITY,
				       "Subject: " + res.subject);
		    }
                } catch (Exception e) {
                    throw new IOException("Could not parse subject name");
                }
            }  // NOTE: the subject can be null (empty sequence) if
            // subjectAltName is present
            
            // Parse the subject public key information
	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "SubjectPublicKeyInfo follows");
	    }

            publicKeyLen = res.getLen(SEQUENCE_TYPE);
            publicKeyPos = res.idx;

            // Match the algorithm Id
            id = res.getAlg();
	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "Public Key Algorithm: " + id);
	    }

            if (id != RSA_ENCRYPTION) {
                // skip the public key
                res.idx = publicKeyPos + publicKeyLen;
            }

            // Get the bit string
            res.getLen(BITSTRING_TYPE);
            if (res.enc[res.idx++] != 0x00) {
                throw new IOException("Bitstring error while parsing public " +
                                      "key information");
            }

            res.getLen(SEQUENCE_TYPE);
            size = res.getLen(INTEGER_TYPE);
            if (res.enc[res.idx] == (byte) 0x00) {
                // strip off the sign byte
                size--;
                res.idx++;
            }
            
            // Build the RSAPublicKey
            modulusPos = res.idx;
            modulusLen = size;

            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "Modulus:  " +
			       Utils.hexEncode(res.enc, modulusPos,
                                               modulusLen));
	    }

            res.idx += size;

            size = res.getLen(INTEGER_TYPE);
            if (res.enc[res.idx] == (byte) 0x00) {
                // strip off the sign byte
                size--;
                res.idx++;
            }

            exponentPos = res.idx;
            exponentLen = size;

            res.pubKey = new RSAPublicKey(res.enc, modulusPos, modulusLen,
                                          res.enc, exponentPos, exponentLen);

	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "Exponent: " +
			       Utils.hexEncode(res.enc, exponentPos,
                                               exponentLen));
	    }

            res.idx += size;
            if (res.idx != sigAlgIdx) {
                if (res.version < 3) { 
                    throw new IOException(
                        "Unexpected extensions in old version cert");
                } else {
                    res.parseExtensions(sigAlgIdx);
                }
            }
            
            // get the signatureAlgorithm
            res.sigAlg = res.getAlg();

	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "Signature Algorithm: " + 
			       res.getSigAlgName());
	    }

            /*
             * If this is a supported signature algorithm, compute and save
             * the hash of TBSCertificate. A null TBSCertHash indicates
             * the use of an unsupported signature algorithm (see verify())
             */
            md = null;
            if (res.sigAlg == MD2_RSA) {            
                md = MessageDigest.getInstance("MD2");
            } else if (res.sigAlg == MD5_RSA) {            
                md = MessageDigest.getInstance("MD5");
            } else if (res.sigAlg == SHA1_RSA) {
                md = MessageDigest.getInstance("SHA-1");
            }
                 
            if (md != null) {
                res.TBSCertHash = new byte[md.getDigestLength()];
                md.update(buf, off + res.TBSStart, res.TBSLen);
                md.digest(res.TBSCertHash, 0, res.TBSCertHash.length);
            }

            // get the signature
            size = res.getLen(BITSTRING_TYPE);
            if (res.enc[res.idx++] != 0x00) {
                throw new IOException("Bitstring error in signature parsing");
            }
            
            /*
             * We pad the signature to a multiple of 8-bytes before storing
             * since we only support RSA modulus lengths that are multiples
             * of 8 bytes and the two should match for decryption to succeed.
             */ 
            int sigLen = (((size - 1) + 7) >>> 3) << 3;
            res.signature = new byte[sigLen];
            System.arraycopy(res.enc, res.idx, res.signature, 
                             (sigLen - (size - 1)), (size - 1));

	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       sigLen + "-byte signature: " + 
			       Utils.hexEncode(res.signature));
	    }
            return res;
        } catch (IndexOutOfBoundsException e) {
            throw new IOException("Bad length detected in cert DER");
        } catch (GeneralSecurityException e) {
            throw new IOException(e.toString());
        }
    
private bytegetAlg()
Expects to see a PKCS1 algorithm identifier in the DER encoding (enc) starting at the current offset (idx).

return
a single-byte algorithm identifier, e.g. MD5_RSA, MD2_RSA
exception
IOException if an error is encountered during parsing

        byte val;
        
        try {
            match(PKCS1Seq);
            val = enc[idx++];
            match(NullSeq);
            return val;
        } catch (Exception e) {
            throw new IOException("Algorithm Id parsing failed");
        }
    
public intgetBasicConstraints()
Gets the certificate constraints path length from the BasicConstraints extension.

The BasicConstraints extension identifies whether the subject of the certificate is a Certificate Authority (CA) and how deep a certification path may exist through the CA. The pathLenConstraint field (see below) is meaningful only if cA is set to TRUE. In this case, it gives the maximum number of CA certificates that may follow this certificate in a certification path. A value of zero indicates that only an end-entity certificate may follow in the path.

Note that for RFC 2459 this extension is always marked critical if cA is TRUE, meaning this certificate belongs to a Certificate Authority.

The ASN.1 definition for this is:

BasicConstraints ::= SEQUENCE {
cA BOOLEAN DEFAULT FALSE,
pathLenConstraint INTEGER (0..MAX) OPTIONAL
}

return
MISSING_PATH_LENGTH_CONSTRAINT if the BasicConstraints extension is absent or the subject of the certificate is not a CA. If the subject of the certificate is a CA and pathLenConstraint does not appear, UNLIMITED_CERT_CHAIN_LENGTH is returned to indicate that there is no limit to the allowed length of the certification path. In all other situations, the actual value of the pathLenConstraint is returned.

        if (isCA) {
            return pLenConstr;
        } else {
            return MISSING_PATH_LENGTH_CONSTRAINT;
        }
    
public intgetExtKeyUsage()
Gets a 32-bit bit vector (in the form of an integer) in which each position represents a purpose for which the public key in the certificate may be used (iff that bit is set). The correspondence between bit positions and purposes is as follows:
serverAuth 1
clientAuth 2
codeSigning 3
emailProtection 4
ipsecEndSystem 5
ipsecTunnel 6
ipsecUser 7
timeStamping 8

return
a bitvector indicating extended usage of the certificate public key, -1 if a critical extendedKeyUsage extension is not present.

        return extKeyUsage;
    
public byte[]getFingerprint()
Gets the MD5 fingerprint of this certificate.
NOTE: this implementation returns a byte array filled with zeros if there is no fingerprint associated with this certificate. This may happen if a null was passed to the X509Certificate constructor.

return
a byte array containing this certificate's MD5 hash

         byte[] res = new byte[16];
         if (fp != null) System.arraycopy(fp, 0, res, 0, res.length);
         return res;
    
public java.lang.StringgetIssuer()
Gets the name of this certificate's issuer.
NOTE: The corresponding method in the standard edition is getIssuerDN() and returns a Principal.

return
a string containing this certificate's issuer in user-friendly form

        return issuer;
    
public intgetKeyUsage()
Gets a 32-bit bit vector (in the form of an integer) in which each position represents a purpose for which the public key in the certificate may be used (iff that bit is set). The correspondence between bit positions and purposes is as follows:
digitalSignature 0
nonRepudiation 1
keyEncipherment 2
dataEncipherment 3
keyAgreement 4
keyCertSign 5
cRLSign 6
encipherOnly 7
decipherOnly 8

return
a bitvector indicating approved key usage of the certificate public key, -1 if a KeyUsage extension is not present.

        return keyUsage;
    
private intgetLen(byte type)
Matches the specified ASN type against this certificates DER encoding (enc) starting at the current offset (idx) and returns its encoded length.

param
type ASN type to be matched
return
the size in bytes of the sub-encoding associated with the given type
exception
IOException if the length is not formated correctly


        if ((enc[idx] == type) || 
            ((type == ANY_STRING_TYPE) && // ordered by likelihood of match
            ((enc[idx] == PRINTSTR_TYPE) || (enc[idx] == TELETEXSTR_TYPE) ||
            (enc[idx] == UTF8STR_TYPE) || (enc[idx] == IA5STR_TYPE) ||
            (enc[idx] == UNIVSTR_TYPE)))) {
            idx++;
            int size = (enc[idx++] & 0xff);
            if (size >= 128) {
                int tmp = size - 128;
                // NOTE: for now, all sizes must fit int two bytes
                if ((tmp > 2) || (idx + tmp > enc.length)) {
                    throw new IOException("getLen() err 1");
                } else {
                    size = 0;
                    while (tmp > 0) {
                        size = (size << 8) + (enc[idx++] & 0xff);
                        tmp--;
                    }
                }
            }
            return size;
        }

        throw new IOException("getLen() err 2");
    
private java.lang.StringgetName(int end)
Parses a SubjectName or IssuerName in the DER encoding (enc) starting at the current offset (idx) and ending at end.

param
end ending offset for the DER-encoded name
return
a human friendly string representation of the name
exception
IOException if an error is encountered during parsing

        byte[] name = new byte[MAX_NAME_LENGTH];
        int nameLen = 0;
        int len = 0;
        int cidx;   // index where the most recently seen name component starts
        int clen;   // Component length
        char[] label = null;
        int aidx;
        
        while (idx < end) {
            if (nameLen != 0) {
                // this is not the first time so insert a separator
                name[nameLen++] = (byte)';";
            }
            
            getLen(SET_TYPE);
            getLen(SEQUENCE_TYPE);

            /*
             * Save the start of name component, e.g CommonName
             * ... and its length
             */
            clen = getLen(OID_TYPE);
            cidx = idx;            
            idx += clen;

            /*
             * At this point we tag the name component, e.g. C= or hex
             * if unknown.
             */
            if ((clen == 3) && (enc[cidx] == 0x55) &&
                    (enc[cidx + 1] == 0x04)) {
                // begins with id-at, so try to see if we have a label
                aidx = enc[cidx + 2] & 0xFF;
                if ((aidx < nameAttr.length) && (nameAttr[aidx][0] != 0)) {
                    label = nameAttr[aidx];
                } else {
                    label = Utils.hexEncodeToChars(enc, cidx, clen);
                }
            } else if (Utils.byteMatch(enc, cidx, EMAIL_ATTR_OID, 0,
                       EMAIL_ATTR_OID.length)) {
                label = EMAIL_ATTR_LABEL;
            } else {
                label = Utils.hexEncodeToChars(enc, cidx, clen);
            }

            for (int i = 0; i < label.length; i++) {
                name[nameLen++] = (byte)label[i];
            }

            name[nameLen++] = (byte)'=";

            len = getLen(ANY_STRING_TYPE);

            if (len > 0) {
                for (int i = 0; i < len; i++) {
                    name[nameLen++] = enc[idx++];
                }
            }
        }

        return new String(name, 0, nameLen, "UTF-8");
    
public longgetNotAfter()
Gets the NotAfter date from the certificate's validity period.

return
a date after which the certificate is not valid (expiration date)

        return until;
    
public longgetNotBefore()
Gets the NotBefore date from the certificate's validity period.

return
a date before which the certificate is not valid

        return from;
    
public PublicKeygetPublicKey()
Gets the public key from this certificate.

return
the public key contained in the certificate
exception
CertificateException if public key is not a supported type (could not be parsed).

        if (pubKey == null) {
            throw new CertificateException(this,
                CertificateException.UNSUPPORTED_PUBLIC_KEY_TYPE);
        }

        return pubKey;
    
public java.lang.StringgetSerialNumber()
Gets the printable form of the serial number of this Certificate. If the serial number within the certificate is binary is should be formatted as a string using hexadecimal notation with each byte represented as two hex digits separated byte ":" (Unicode x3A). For example, 27:56:FA:80.

return
A string containing the serial number in user-friendly form; NULL is returned if there is no serial number.

        return serialNumber;
    
public java.lang.StringgetSigAlgName()
Gets the name of the algorithm used to sign the certificate.

return
the name of signature algorithm

        /* 
         * These are ordered to maximize the likelihood of an
         * early match, md5WithRSA seems the most common
         */ 
        if (sigAlg == MD5_RSA) 
            return ("MD5withRSA");
        else if (sigAlg == MD2_RSA) 
            return ("MD2withRSA");
        else if (sigAlg == SHA1_RSA) 
            return ("SHA1withRSA");
        else if (sigAlg == NONE)
            return ("None");
        else if (sigAlg == MD4_RSA)
            return ("MD4withRSA");
        else 
            return ("Unknown (" + sigAlg + ")");
    
public java.lang.StringgetSubject()
Gets the name of this certificate's subject.
NOTE: The corresponding method in the standard edition is getSubjectDN() and returns a Principal.

return
a string containing this certificate's subject in user-friendly form

        return subject;
    
public java.lang.ObjectgetSubjectAltName()
Gets the subject alternative name or null if it was not in the certificate.

return
type of subject alternative name or null

        return subAltName;
    
public intgetSubjectAltNameType()
Gets the type of subject alternative name.

return
type of subject alternative name

        return subAltNameType;
    
public java.lang.StringgetType()
Get the type of the Certificate.

return
The type of the Certificate; the value MUST NOT be NULL.

        return "X.509";
    
private static longgetUTCTime(byte[] buf, int off)
Gets a string representation of the UTC time whose DER ecnoding is contained in the specified buffer.

param
buf buffer containing the DER encoding of UTC Time
param
off starting offset of the encoding inside buf
return
a string represntation of the UTC time in the form yy/mm/dd hh:mm:ss
exception
IOException if an error is encountered during parsing

        Calendar cal;
        int[] period = new int[6]; // year, month, day, hour, minute, second

        if (buf[off + UTC_LENGTH - 1] != (byte) 'Z") 
            throw new IOException("getUTCTime() err 1");
        for (int i = 0; i < 6; i++) {
            period[i] = 0;
            if ((buf[2*i + off] < (byte) '0") || 
                (buf[2*i + off] > (byte) '9"))
                throw new IOException("getUTCTime() err 2");
            period[i] = buf[2*i + off] - (int) '0";
            if ((buf[2*i + off + 1] < (byte) '0") ||
                (buf[2*i + off + 1] > (byte) '9"))
                throw new IOException("getUTCTime() err 3");
            period[i] = (period[i] * 10) + (buf[2*i + off + 1] - (int) '0");
        }

        if (period[0] < 50) {  // from rfc2459
            period[0] += 2000;
        } else {
            period[0] += 1900;
        }

        cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        cal.set(Calendar.YEAR, period[0]);
        cal.set(Calendar.MONTH, period[1] - 1);  // months go 0-11
        cal.set(Calendar.DAY_OF_MONTH, period[2]);
        cal.set(Calendar.HOUR_OF_DAY, period[3]);
        cal.set(Calendar.MINUTE, period[4]);
        cal.set(Calendar.SECOND, period[5]);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime().getTime();
    
public java.lang.StringgetVersion()
Gets the raw X.509 version number of this certificate. Version 1 is 0.

return
the X.509 logic version number (1, 2, 3) of the certificate

        return Integer.toString(version);
    
private voidmatch(byte[] buf)
Matches the contents of buf against this certificates DER encoding (enc) starting at the current offset (idx).

param
buf buffer whose contents are to be matched against the certificate encoding
exception
Exception if the match fails

        if (idx + buf.length < enc.length) {
            for (int i = 0; i < buf.length; i++) {
                if (enc[idx++] != buf[i]) 
                    throw new Exception("match() error 1");
            }
        } else {
            throw new Exception("match() error 2");
        }
    
private voidparseExtensions(int end)
Parses X.509v3 extensions in the certificate encoding until the specified index.

param
end index of the last byte in the certificate encoding to be processed
exception
IOException in case of parsing problems

        /*
         * NOTE: If one does not wish to support v3 extensions
         * at all (to save code), one can simply set badExt to
         * true and return -- the code that actually parses extensions
         * can be commented out
         */ 
        String extId = null;
        int extIdIdx = 0;
        int extIdLen = 0;
        boolean crit;
        int extValIdx = 0;
        int extValLen = 0;
        int tmp;
        
        getLen((byte) 0xa3);   // extensions start with 0xa3
        getLen(SEQUENCE_TYPE);
        while (idx < end) {
            extId = null;
            getLen(SEQUENCE_TYPE);
            extIdLen = getLen(OID_TYPE);
            extIdIdx = idx;
            idx += extIdLen;
            crit = false;
            if ((enc[idx] == 0x01) && (enc[idx + 1] == 0x01)) {
                idx += 2;
                crit = (enc[idx++] == (byte) 0xff) ? true : false;
            }
            extValLen = getLen(OCTETSTR_TYPE);
            extValIdx = idx;
            if ((enc[extIdIdx] == 0x55) && (enc[extIdIdx + 1] == 0x1d)) {
                // Do we recognize this? NOTE: id-ce is 0x55, 0x1d
                switch (enc[extIdIdx + 2] & 0xff) {
                case 0x0f:   // keyUsage = id-ce 15
                    extId = "KU";
                    if (keyUsage == -1) {
                        keyUsage = 0;
                    }

                    tmp = getLen(BITSTRING_TYPE) - 1;
                    int unused = enc[idx++]; // get unused bits in last octet
                    byte b = 0;

                    // process each bit in the bitstring starting with
                    // the most significant
                    for (int i = 0; i < ((tmp << 3) - unused); i++) {
                        if ((i % 8) == 0) {
                            b = enc[idx++];
                        }

                        if (b < 0) {
                            keyUsage |= 1 << i;
                        }

                        b = (byte) (b << 1);
                    }

                    break;
                    
                case 0x11:   // subAltName = id-ce 17
                    StringBuffer temp = new StringBuffer();
                    int start = idx + 4;
                    int length = extValLen - 4;
                    extId = "SAN";

                    /*
                     * First byte stores the type e.g. 1=rfc822Name(email), 
                     * 2=dNSName, 6=URI etc
                     */ 
                    subAltNameType = (byte) (enc[idx + 2] - 0x80);

                    switch (subAltNameType) {
                    case TYPE_EMAIL_ADDRESS:
                    case TYPE_DNS_NAME:
                    case TYPE_URI:
                        for (int i = 0; i < length; i++) {
                            temp.append((char)enc[start + i]);
                        }

                        subAltName = temp.toString();
                        break;

                    default:
                        subAltName = new byte[length];
                        for (int i = 0; i < length; i++) {
                            ((byte[])subAltName)[i] = enc[start + i];
                        }
                    }
                    
                    break;

                case 0x13:  // basicConstr = id-ce 19
                    hasBC = true;
                    extId = "BC";
                    tmp = getLen(SEQUENCE_TYPE);
                    if (tmp == 0) break;
                    // ca is encoded as an ASN boolean (default is false)
                    if ((enc[idx] == 0x01) && (enc[idx + 1] == 0x01) &&
                        (enc[idx + 2] == (byte) 0xff)) {
                        isCA = true;
                        idx += 3;
                    }

                    /*
                     * path length constraint is encoded as optional ASN
                     * integer
                     */
                    if ((enc[idx] == 0x02) && (enc[idx + 1] != 0)) {
                        tmp = getLen(INTEGER_TYPE);
                        pLenConstr = 0;
                        for (int i = 0; i < tmp; i++) {
                            pLenConstr = (pLenConstr << 16) + enc[idx + i];
                        }
                        idx += tmp;
                    } else {
                        if (isCA) pLenConstr = UNLIMITED_CERT_CHAIN_LENGTH;
                    }
                    break;
                    
                case 0x25:  // extendedKeyUsage = id-ce 37
                    extId = "EKU";
                    if (extKeyUsage == -1) {
                        extKeyUsage = 0;
                    }

                    getLen(SEQUENCE_TYPE);
                    int kuOidLen;
                    while (idx < extValIdx + extValLen) {
                        kuOidLen = getLen(OID_TYPE);
                        if ((kuOidLen == ID_KP.length + 1) &&
                            Utils.byteMatch(enc, idx, 
                                            ID_KP, 0, ID_KP.length) &&
                            (enc[idx + ID_KP.length] > 0) &&
                            (enc[idx + ID_KP.length] < 9)) {
                            extKeyUsage |= 
                                (1 << (enc[idx + ID_KP.length]));
                        } else {
                            if (crit) badExt = true;
                        }
                        idx += kuOidLen;
                    }

                    if (!crit) {
                        // ignore extended key usage if not critical
                        extKeyUsage = -1;
                    }

                    break;
                    /* 
                     * Extensions which we do not currently support include: 
                     * subjectDirectoryAttribute 0x09, 
                     * subjectKeyIdentifier 0x0e, privateKeyUsagePeriod 0x10,
                     * issuerAltName 0x12, cRLNumber 0x14, reasonCode 0x15,
                     * instructionCode 0x17, invalidityDate 0x18,
                     * deltaCRLIndicator 0x1b, issuingDistributionPoint 0x1c,
                     * certificateIssuer 0x1d, nameConstraints 0x1e,
                     * cRLDistributionPoints 0x1f, certificatePolicies 0x20,
                     * policyMappings 0x21, authorityKeyIdentifier 0x23,
                     * policyConstraints 0x24
                     */ 
                }
            }
            
            // For debugging only
	    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
		Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY,
			       "<Id: " + 
                               Utils.hexEncode(enc, extIdIdx, extIdLen) +
                               (crit ? ", critical, " : ", ") +
                               Utils.hexEncode(enc, extValIdx, extValLen) +
                               ">" + 
                               ((extId == null) ? " (Unrecognized)" : ""));
	    }
            
            if ((extId == null) && crit) badExt = true;

            idx = extValIdx + extValLen;
        }
        
        if (idx != end) {
            throw new IOException("Extension parsing problem");
        }

    
public java.lang.StringtoString()
Returns a string representation of this certificate.

return
a human readable string repesentation of this certificate

        StringBuffer tmp = new StringBuffer();
        
        tmp.append("[Type: ");
        tmp.append(getType());
        tmp.append("v");
        tmp.append(version);

        tmp.append("\n");
        tmp.append("Serial number: ");
        tmp.append(serialNumber);

        tmp.append("\n");
        tmp.append("Subject: ");
        tmp.append(subject);

        tmp.append("\n");
        tmp.append("Issuer: ");
        tmp.append(issuer);

        tmp.append("\n");
        tmp.append("Valid from ");
        tmp.append(date2str(new Date(getNotBefore())));
        tmp.append(" GMT until ");
        tmp.append(date2str(new Date(getNotAfter())));
        tmp.append(" GMT");

        // tmp.append("\n");
        // tmp.append(pubKey.toString());

        // tmp.append("\n");
        // tmp.append(TBSCertificate hash: ");
        // tmp.append(TBSCertHash == null ?
        //           "null" : Utils.hexEncode(TBSCertHash));

        tmp.append("\n");
        tmp.append("Signature Algorithm: ");
        tmp.append(getSigAlgName());

        if (subAltName != null) {
            tmp.append("\n");
            tmp.append("SubjectAltName: ");
            tmp.append(subAltName);
            tmp.append("(type ");
            tmp.append(subAltNameType);
            tmp.append(")");
        }

        if (keyUsage != -1) {
            tmp.append("\n");
            tmp.append("KeyUsage:");
            int t = (int) keyUsage;
            for (int i = 0; i < KEY_USAGE.length; i++) {
                if ((t & 0x01) == 0x01) {
                    tmp.append(" ");
                    tmp.append(KEY_USAGE[i]);
                }

                t = t >>> 1;
            }
        }

        if (hasBC) {
            tmp.append("\n");
            tmp.append("BasicConstraints: ");
            tmp.append(isCA ? "is a CA" : "not a CA");
            tmp.append(" (pathLengthConstraint ");
            if ((pLenConstr == MISSING_PATH_LENGTH_CONSTRAINT) ||
                    (pLenConstr == UNLIMITED_CERT_CHAIN_LENGTH)) {
                tmp.append("absent");
            } else {
                tmp.append(pLenConstr);
            }

            tmp.append(")");
        }

        // tmp.append("\n");
        // tmp.append("MD5 Fingerprint: ");
        // tmp.append(Utils.hexEncode(fp));
        tmp.append("]");
        return tmp.toString();
    
public voidverify(PublicKey k)
Checks if this certificate was signed using the private key corresponding to the specified public key.

param
k public key to be used for verifying certificate signature
exception
CertificateException if there is an error

        RSAPublicKey pk;

        if (!(k instanceof RSAPublicKey)) {
            throw new CertificateException("Issuer key not a public RSA",
                this, CertificateException.VERIFICATION_FAILED);
        }

        pk = (RSAPublicKey)k;

        /*
         * Since selfSigned certificates are stored without
         * TBSCertHash and signature fields (to save memory),
         * the only way to return anything meaningful is by
         * directly comparing the specified public key against
         * the certificate public key. This allows us to return
         * the right result even on certificates created using
         * the Certificate(...) constructor.
         * 
         * NOTE: We can comment this out to save code here and
         * in Key (and its subclasses) -- no need to define equals
         * The documentation already warns users not to invoke
         * verify() on certificates with null signature.
         */ 
        if (selfSigned) {
            if (pubKey.equals(pk)) {
                return;
            }

            throw new CertificateException("Bad self signed cert",
                this, CertificateException.VERIFICATION_FAILED);
        }

        if (signature == null) {
            throw new CertificateException(this,
                CertificateException.MISSING_SIGNATURE);
        }

        if (TBSCertHash == null) {
            throw new CertificateException(this,
                CertificateException.UNSUPPORTED_SIGALG);
        }
        
        int modLen = pk.getModulusLen();
        byte[] result = new byte[modLen];

        int val;
        
        /*
         * NOTE: We can not use the Signature class because, at this 
         * point, we do not have TBSCertificate (just its hash). The 
         * Signature class needs raw data and computes a hash internally.
         */ 
        try {
            Cipher rsa = Cipher.getInstance("RSA");

            rsa.init(Cipher.DECRYPT_MODE, pk);
            val = rsa.doFinal(signature, 0, signature.length, result, 0);
        } catch (Exception e) {
            throw new CertificateException(this,
                CertificateException.VERIFICATION_FAILED);
        }
        
        /*
         * NOTE: the decrypted value includes an ASN DER
         * encoding of
         * DigestInfo ::= SEQUENCE {
         *       digestAlgorithm DigestAlgorithmIdentifier,
         *       digest Digest }
         * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
         * Digest ::= OCTET STRING
         * 
         * For md2WithRSAEncryption, the decrypted value will be
         * 3020300c06082a864886f70d020205000410 followed by a 16-byte hash
         *
         * For md5WithRSAEncryption, the decrypted value will be
         * 3020300c06082a864886f70d020505000410 followed by a 16-byte hash
         *   30 20      32: SEQUENCE
         *   30 0c      12: . SEQUENCE
         *   06 08       8: . . OID 1.2.840.113549.2.5 (MD5 OID, rfc2313 pg 14)
         *                : 2a 86 48 86 f7 0d 02 05
         *   05 00       0: . . NULL (null parameters)
         *   04 10      16: . OCTET STRING
         *                :  <the hash gos here>
         *
         * Similarly, for SHA-1, the 20-byte hash will be preceded by
         * 3021300906052b0e03021a05000414
         * 30 21       33: SEQUENCE
         * 30 09        9: . SEQUENCE
         * 06 05        5: . . OID 1.3.14.3.2.26 (SHA-1 digest OID)
         *              0: 2b 0e 03 02 1a
         * 05 00        0: . . NULL (null parameters)
         * 04 14       20: . <20-byte hash>
         */ 
        if ((sigAlg == MD2_RSA) &&
            (val == (PREFIX_MD2.length + TBSCertHash.length)) &&
            Utils.byteMatch(result, 0, 
                            PREFIX_MD2, 0, PREFIX_MD2.length) &&
            Utils.byteMatch(result, PREFIX_MD2.length,
                            TBSCertHash, 0, TBSCertHash.length)) {
            return;
        }

        if ((sigAlg == MD5_RSA) &&
            (val == (PREFIX_MD5.length + TBSCertHash.length)) &&
            Utils.byteMatch(result, 0, 
                            PREFIX_MD5, 0, PREFIX_MD5.length) &&
            Utils.byteMatch(result, PREFIX_MD5.length,
                            TBSCertHash, 0, TBSCertHash.length)) {
            return;
        }

        if ((sigAlg == SHA1_RSA) &&
                 (val == (PREFIX_SHA1.length + TBSCertHash.length)) &&
                 Utils.byteMatch(result, 0, 
                                 PREFIX_SHA1, 0, PREFIX_SHA1.length) &&
                 Utils.byteMatch(result, PREFIX_SHA1.length,
                                 TBSCertHash, 0, TBSCertHash.length)) {
            return;
        }

        throw new CertificateException(this,
            CertificateException.VERIFICATION_FAILED);
    
public static java.lang.String[]verifyChain(java.util.Vector certs, int keyUsage, int extKeyUsage, CertStore certStore)
Verify a chain of certificates.

param
certs list of certificates with first being entity certificate and the last being the CA issued certificate.
param
keyUsage -1 to not check the key usage extension, or a key usage bit mask to check for if the extension is present
param
extKeyUsage -1 to not check the extended key usage extension, or a extended key usage bit mask to check for if the extension is present
param
certStore store of trusted CA certificates
return
authorization path: an array of names from most trusted to least trusted from the certificate chain
exception
CertificateException if there is an error verifying the chain

        X509Certificate cert;
        X509Certificate prevCert;
        PublicKey key;
        Vector keys;
        X509Certificate[] caCerts; // CA X509Certificates
        int maxPathLen = -1; // 0 means a chain of 1 so -1 means no chain
        int prevMaxPathLen;
        Vector subjectNames = new Vector();
        String[] authPath;

        // must be an enitity certificate
        cert = (X509Certificate)certs.elementAt(0);
        checkKeyUsageAndValidity(cert, keyUsage, extKeyUsage);

        for (int i = 1; ; i++) {
            // look up the public key of the certificate issurer
            caCerts = certStore.getCertificates(cert.getIssuer());
            if (caCerts != null) {
                // found a known CA no need to go on to the next cert
                if (caCerts[0].getSubject() != cert.getSubject()) {
                    subjectNames.addElement(caCerts[0].getSubject());
                }

                break;
            }
            
            if (i >= certs.size()) {
                throw new CertificateException(cert,
                    CertificateException.UNRECOGNIZED_ISSUER);
            }

            /* Save the name of subject. */
            subjectNames.addElement(cert.getSubject());

            prevCert = cert;
            cert = (X509Certificate)certs.elementAt(i);

            /*
             * This must be a CA so the key usage always is certficate
             * signing.
             */
            checkKeyUsageAndValidity(cert,
                X509Certificate.CERT_SIGN_KEY_USAGE, extKeyUsage);

            /*
             * This is a chain, check chain link:
             * the subject of this certificate must be the issuer of
             * the previous certificate
             */ 
            if (prevCert.getIssuer().compareTo(cert.getSubject()) != 0) {
                throw new CertificateException(prevCert,
                     CertificateException.BROKEN_CHAIN);
            }

            /*
             * Check if basicConstraints are satisfied. Note a zero
             * pathLength means we should have only processed one cert
             * so far.
             */ 
            prevMaxPathLen = maxPathLen;
            maxPathLen = cert.getBasicConstraints();

            if (maxPathLen != X509Certificate.UNLIMITED_CERT_CHAIN_LENGTH &&
                maxPathLen <= prevMaxPathLen) {
                if (cert.getSubject().equals(cert.getIssuer())) {
                    /*
                     * This cert is a redundent, self signed CA cert
                     * allowed to be at the end of the chain.
                     * These certificates may version 1, so will not
                     * have extensions. So this really should be the
                     * unrecognized issuer.
                     */
                    throw new CertificateException(prevCert,
                        CertificateException.UNRECOGNIZED_ISSUER);
                }

                if (maxPathLen ==
                    X509Certificate.MISSING_PATH_LENGTH_CONSTRAINT) {

                    throw new CertificateException(cert,
                         CertificateException.UNAUTHORIZED_INTERMEDIATE_CA);
                }

                /*
                 * An intermediate CA has over granted
                 * its given authority to the previous CA in the
                 * chain.
                 */
                throw new CertificateException(cert,
                     CertificateException.CERTIFICATE_CHAIN_TOO_LONG);
            }

            /* Save the most time intensive check for last. */
            prevCert.verify(cert.getPublicKey());
        }

        for (int i = 0; i < caCerts.length; i++) {
            try {
                cert.verify(caCerts[i].getPublicKey());
            } catch (CertificateException ce) {
                /*
                 * the exception will be when we get out side of the loop,
                 * if none of the other keys work
                 */
                continue;
            }

            // check the CA key for valid dates
            try {
                caCerts[i].checkValidity();
            } catch (CertificateException ce) {
                if (ce.getReason() == CertificateException.EXPIRED) {
                    /*
                     * Change the exception reason, so the
                     * application knows that the problem is with
                     * the device and not the server.
                     */
                    throw new CertificateException(caCerts[i],
                        CertificateException.ROOT_CA_EXPIRED);
                }
            
                throw ce;
            }

            // Success
            authPath = new String[subjectNames.size()];
            for (int j = subjectNames.size() - 1, k = 0; j >= 0;
                     j--, k++) {
                authPath[k] = (String)subjectNames.elementAt(j);
            }

            return authPath;
        }

        throw new CertificateException(cert,
            CertificateException.VERIFICATION_FAILED);