/*
*
*
* Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.midp.pki;
import java.io.IOException;
import java.util.*;
import javax.microedition.pki.*;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;
import com.sun.midp.crypto.*;
/**
* 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. <P />
* 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.
* <P />
*/
public class X509Certificate implements Certificate {
/** Indicates a no error condition. */
public static final byte NO_ERROR = 0;
/**
* 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 MISSING_PATH_LENGTH_CONSTRAINT = -1;
/** Indicates there is no limit to the server certificate chain length. */
public static final int UNLIMITED_CERT_CHAIN_LENGTH = 65535;
/** We expect issuer/subject names to fit within these many bytes. */
private static final int MAX_NAME_LENGTH = 300;
/** ASN ANY_STRING type used in certificate parsing (0x00). */
private static final byte ANY_STRING_TYPE = 0x00; // our own impl
// private static final byte BOOLEAN_TYPE = 0x01 ?????
/** ASN INTEGER type used in certificate parsing (0x02). */
private static final byte INTEGER_TYPE = 0x02;
/** ASN BIT STRING type used in certificate parsing (0x03). */
private static final byte BITSTRING_TYPE = 0x03;
/** ASN OCTET STRING type used in certificate parsing (0x04). */
private static final byte OCTETSTR_TYPE = 0x04;
/** ASN OBJECT ID type used in certificate parsing (0x06). */
private static final byte OID_TYPE = 0x06;
/** ASN UTF8 STRING type used in certificate parsing (0x0c). */
private static final byte UTF8STR_TYPE = 0x0c;
/** ASN UNICODE STRING type used in certificate parsing (0x12). */
private static final byte UNIVSTR_TYPE = 0x12;
/** ASN PRINT STRING type used in certificate parsing (0x13). */
private static final byte PRINTSTR_TYPE = 0x13;
/** ASN TELETEX STRING type used in certificate parsing (0x14). */
private static final byte TELETEXSTR_TYPE = 0x14;
// private static final byte BMPSTR_TYPE = 0x??
/** ASN IA5 STRING type used in certificate parsing (0x16). */
private static final byte IA5STR_TYPE = 0x16; // Used for EmailAddress
/** ASN SEQUENCE type used in certificate parsing (0x30). */
private static final byte SEQUENCE_TYPE = 0x30;
/** ASN SET type used in certificate parsing (0x31). */
private static final byte SET_TYPE = 0x31;
/** Email address (rfc 822) alternative name type code. */
public static final byte TYPE_EMAIL_ADDRESS = 1;
/** DNS name alternative name type code. */
public static final byte TYPE_DNS_NAME = 2;
/** URI alternative name type code. */
public static final byte TYPE_URI = 6;
/** Bit mask for digital signature key usage. */
public static final int DIGITAL_SIG_KEY_USAGE = 0x00000001;
/** Bit mask for non repudiation key usage. */
public static final int NON_REPUDIATION_KEY_USAGE = 0x00000002;
/** Bit mask for key encipherment key usage. */
public static final int KEY_ENCIPHER_KEY_USAGE = 0x00000004;
/** Bit mask for data encipherment key usage. */
public static final int DATA_ENCIPHER_KEY_USAGE = 0x00000008;
/** Bit mask for key agreement key usage. */
public static final int KEY_AGREEMENT_KEY_USAGE = 0x00000010;
/** Bit mask for key certificate sign key usage. */
public static final int CERT_SIGN_KEY_USAGE = 0x00000020;
/** Bit mask for CRL sign key usage. */
public static final int CRL_SIGN_KEY_USAGE = 0x00000040;
/** Bit mask for encipher only key usage. */
public static final int ENCIPHER_ONLY_KEY_USAGE = 0x00000080;
/** Bit mask for decipher only key usage. */
public static final int DECIPHER_ONLY_KEY_USAGE = 0x00000100;
/** Bit mask server auth for extended key usage. */
public static final int SERVER_AUTH_EXT_KEY_USAGE = 0x00000002;
/** Bit mask client auth for extended key usage. */
public static final int CLIENT_AUTH_EXT_KEY_USAGE = 0x00000004;
/** Bit code signing mask for extended key usage. */
public static final int CODE_SIGN_EXT_KEY_USAGE = 0x00000008;
/** Bit email protection mask for extended key usage. */
public static final int EMAIL_EXT_KEY_USAGE = 0x00000010;
/** Bit IPSEC end system mask for extended key usage. */
public static final int IPSEC_END_SYS_EXT_KEY_USAGE = 0x00000020;
/** Bit IPSEC tunnel mask for extended key usage. */
public static final int IPSEC_TUNNEL_EXT_KEY_USAGE = 0x00000040;
/** Bit IPSEC user mask for extended key usage. */
public static final int IPSEC_USER_EXT_KEY_USAGE = 0x00000080;
/** Bit time stamping mask for extended key usage. */
public static final int TIME_STAMP_EXT_KEY_USAGE = 0x00000100;
/**
* 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 int UTC_LENGTH = 13;
/**
* 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[][] nameAttr = {
{ 0 },
{ 0 },
{ 0 },
{ 'C', 'N' }, // Common name: id-at 3
{ 'S', 'N'}, // Surname: id-at 4
{ 0 },
{ 'C'}, // Country: id-at 6
{ 'L'}, // Locality: id-at 7
{ 'S', 'T'}, // State or province: id-at 8
{ 'S', 'T', 'R', 'E', 'E', 'T'}, // Street address: id-at 9
{ 'O'}, // Organization: id-at 10
{ 'O', 'U'}, // Organization unit: id-at 11
};
/** Email attribute label in bytes. "EmailAddress" */
private static final char[] EMAIL_ATTR_LABEL = {
'E', 'm', 'a', 'i', 'l', 'A', 'd', 'd', 'r', 'e', 's', 's'
};
/** Email attribute object identifier. */
private static final byte[] EMAIL_ATTR_OID = {
(byte)0x2a, (byte)0x86, (byte)0x48, (byte)0x86, (byte)0xf7,
(byte)0x0d, (byte)0x01, (byte)0x09, (byte)0x01
};
/** Includes DER encoding for OID 1.2.840.113549.1.1. */
private static final byte[] PKCS1Seq = {
(byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
(byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
(byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
};
/*
* These signature algorithms are encoded as PKCS1Seq followed by
* a single byte with the corresponding value shown below, e.g.
* md5WithRSAEncryption OBJECT IDENTIFIER ::= {
* iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1)
* pkcs-1(1) 4
* }
*/
/** Uknown algorithm (-1). */
private static final byte NONE = -1;
/** RAS ENCRYPTION (0x01). */
private static final byte RSA_ENCRYPTION = 0x01;
/** MD2_RSA algorithm (0x02). */
private static final byte MD2_RSA = 0x02;
/** MD4_RSA algorithm (0x03). */
private static final byte MD4_RSA = 0x03;
/** MD4_RSA algorithm (0x04). */
private static final byte MD5_RSA = 0x04;
/** SHA1_RSA algorithm (0x05). */
private static final byte SHA1_RSA = 0x05;
/**
* 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_MD2 = {
(byte) 0x30, (byte) 0x20, (byte) 0x30, (byte) 0x0c,
(byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86,
(byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
(byte) 0x02, (byte) 0x02, (byte) 0x05, (byte) 0x00,
(byte) 0x04, (byte) 0x10
};
/**
* 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_MD5 = {
(byte) 0x30, (byte) 0x20, (byte) 0x30, (byte) 0x0c,
(byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86,
(byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
(byte) 0x02, (byte) 0x05, (byte) 0x05, (byte) 0x00,
(byte) 0x04, (byte) 0x10
};
/**
* 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[] PREFIX_SHA1 = {
(byte) 0x30, (byte) 0x21, (byte) 0x30, (byte) 0x09,
(byte) 0x06, (byte) 0x05, (byte) 0x2b, (byte) 0x0e,
(byte) 0x03, (byte) 0x02, (byte) 0x1a, (byte) 0x05,
(byte) 0x00, (byte) 0x04, (byte) 0x14
};
/** ASN encoding for NULL. */
private static final byte[] NullSeq = {
(byte) 0x05, (byte) 0x00
};
/** This is how the encoding of validity information begins. */
private static final byte[] ValiditySeq = {
(byte) 0x30, (byte) 0x1e
};
/** This is how the encoding of UTCTime begins. */
private static final byte[] UTCSeq = {
(byte) 0x17, (byte) 0x0d
};
/** Includes DER encoding for id-kp (key purpose). */
private static final byte[] ID_KP = {
(byte) 0x2b, (byte) 0x06, (byte) 0x01, (byte) 0x05,
(byte) 0x05, (byte) 0x07, (byte) 0x03
};
/** True iff subject matches issuer. */
private boolean selfSigned;
/** X.509 version. For more readable code the version field starts a 1. */
private byte version = 1;
/** MD5 fingerprint of the certificate. */
private byte[] fp = null;
/** Certificate serial number. */
private String serialNumber;
/** Certificate subject. */
private String subject;
/** Certificate issuer. */
private String issuer;
/** Beginning of certificate validity period. */
private long from = 0;
/** End of certificate validity period. */
private long until = 0;
/** Certificate RSA Public key. */
private RSAPublicKey pubKey = null;
// The following fields are only meaningful in certificates created
// by fully parsing the DER encoding. They are meaningless on
// certificates created using the Certificate constructor below.
/** Index inside encoding. */
private int idx = 0;
/** Contains Certificate DER encoding. */
private byte[] enc = null;
/** Offset where TBSCertificate starts. */
private int TBSStart = 0;
/** Length of TBSCertificate. */
private int TBSLen = 0;
/** Algorithm used to sign the cert. */
private byte sigAlg = NONE;
/** Issuer signature on certificate. */
private byte[] signature = null;
/** Hash of TBSCertificate. */
private byte[] TBSCertHash = null;
/** True iff cert has unrecognized critical extension. */
private boolean badExt = false;
/** Alternate name. */
/** format of the subject alternative name, 2 means a DNS name */
private byte subAltNameType;
/** subject alternative name */
private Object subAltName;
/** does the cert include BasicConstaints. */
private boolean hasBC = false;
/** CA value in BasicConstraints. */
private boolean isCA = false;
/** Path Length constriant from Basic constraints. */
private int pLenConstr = MISSING_PATH_LENGTH_CONSTRAINT;
/** Collection of keyUsage bits. */
private int keyUsage = -1;
/** Collection of extended keyUsage bits. */
private int extKeyUsage = -1;
/** Private constructor */
private X509Certificate() {
}
/**
* Creates an X.509 certificate with the specified attributes.
* This constructor is only used for creating trusted certificates.
* <BR />
* <B>NOTE:</B> 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.
* <P />
* @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
*/
public X509Certificate(byte ver, byte[] rawSerialNumber, String sub,
String iss, long notBefore, long notAfter,
byte[] mod, byte[] exp, byte[] chash,
int pLen)
throws Exception {
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;
}
}
/**
* Matches the contents of buf against this certificates DER
* encoding (enc) starting at the current offset (idx).
* <P />
* @param buf buffer whose contents are to be matched against the
* certificate encoding
* @exception Exception if the match fails
*/
private void match(byte[] buf) throws Exception {
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");
}
}
/**
* Matches the specified ASN type against this certificates DER
* encoding (enc) starting at the current offset (idx) and returns
* its encoded length.
* <P />
* @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
*/
private int getLen(byte type) throws IOException {
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");
}
/**
* Expects to see a PKCS1 algorithm identifier in the DER encoding
* (enc) starting at the current offset (idx).
* <P />
* @return a single-byte algorithm identifier, e.g. MD5_RSA, MD2_RSA
* @exception IOException if an error is encountered during parsing
*/
private byte getAlg() throws IOException {
byte val;
try {
match(PKCS1Seq);
val = enc[idx++];
match(NullSeq);
return val;
} catch (Exception e) {
throw new IOException("Algorithm Id parsing failed");
}
}
/**
* Parses a SubjectName or IssuerName in the DER encoding
* (enc) starting at the current offset (idx) and ending
* at end.
* <P />
* @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
*/
private String getName(int end) throws IOException {
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");
}
/**
* Gets a string representation of the UTC time whose DER ecnoding
* is contained in the specified buffer.
* <P />
* @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
*/
private static long getUTCTime(byte[] buf, int off) throws IOException {
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();
}
/**
* Parses X.509v3 extensions in the certificate encoding until
* the specified index.
* <p />
* @param end index of the last byte in the certificate encoding
* to be processed
* @exception IOException in case of parsing problems
*/
private void parseExtensions(int end) throws IOException {
/*
* 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");
}
} // Done processing extensions
/**
* Creates a certificate by parsing the ASN.1 DER X.509 certificate
* encoding in the specified buffer.<BR />
* <B>NOTE:</B> In the standard edition, equivalent functionality
* is provided by CertificateFactory.generateCertificate(InputStream).
* <P />
* @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
*/
public static X509Certificate generateCertificate(byte[] buf, int off,
int len) throws IOException {
/*
* 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());
}
}
/**
* 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
*/
public static String[] verifyChain(Vector certs, int keyUsage,
int extKeyUsage, CertStore certStore)
throws CertificateException {
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);
}
/**
* 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
*/
private static void checkKeyUsageAndValidity(X509Certificate cert,
int keyUsage, int extKeyUsage) throws CertificateException {
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();
}
/**
* Gets the MD5 fingerprint of this certificate.<BR />
* <b>NOTE:</b> 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.
* <P />
* @return a byte array containing this certificate's MD5 hash
*/
public byte[] getFingerprint() {
byte[] res = new byte[16];
if (fp != null) System.arraycopy(fp, 0, res, 0, res.length);
return res;
}
/**
* Gets the name of this certificate's issuer. <BR />
* <B>NOTE:</B> The corresponding method in the standard edition
* is getIssuerDN() and returns a Principal.
* <P />
* @return a string containing this certificate's issuer in
* user-friendly form
*/
public String getIssuer() {
return issuer;
}
/**
* Gets the name of this certificate's subject. <BR />
* <B>NOTE:</B> The corresponding method in the standard edition
* is getSubjectDN() and returns a Principal.
* <P />
* @return a string containing this certificate's subject in
* user-friendly form
*/
public String getSubject() {
return subject;
}
/**
* Gets the NotBefore date from the certificate's validity period.
* <P />
* @return a date before which the certificate is not valid
*/
public long getNotBefore() {
return from;
}
/**
* Gets the NotAfter date from the certificate's validity period.
*
* @return a date after which the certificate is not valid (expiration
* date)
*/
public long getNotAfter() {
return until;
}
/**
* 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
*/
public void checkExtensions() throws CertificateException {
if (badExt) {
throw new CertificateException(this,
CertificateException.BAD_EXTENSIONS);
}
}
/**
* 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
*/
public void checkValidity() throws CertificateException {
checkValidity(System.currentTimeMillis());
}
/**
* Checks if the certificate is valid on the specified time. It is
* if the specified time is within the certificate's validity
* period. <BR />
* <B>NOTE:</B> The standard edition provides a method with this
* name but it throws different types of exceptions rather than
* returning error codes.
* <P />
* @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
*/
public void checkValidity(long time) throws CertificateException {
if (time < from) {
throw new CertificateException(this,
CertificateException.NOT_YET_VALID);
}
if (time > until) {
throw new CertificateException(this,
CertificateException.EXPIRED);
}
}
/**
* Get the type of the <CODE>Certificate</CODE>.
* @return The type of the <CODE>Certificate</CODE>;
* the value MUST NOT be <CODE>NULL</CODE>.
*/
public String getType() {
return "X.509";
}
/**
* Gets the public key from this certificate.
* <P />
* @return the public key contained in the certificate
*
* @exception CertificateException if public key is not a supported type
* (could not be parsed).
*/
public PublicKey getPublicKey() throws CertificateException {
if (pubKey == null) {
throw new CertificateException(this,
CertificateException.UNSUPPORTED_PUBLIC_KEY_TYPE);
}
return pubKey;
}
/**
* 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
*/
public String getVersion() {
return Integer.toString(version);
}
/**
* Gets the certificate constraints path length from the
* <code>BasicConstraints</code> extension. <P />
*
* The <code>BasicConstraints</code> 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
* <code>pathLenConstraint</code> field (see below) is meaningful only
* if <code>cA</code> 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. <P />
*
* Note that for RFC 2459 this extension is always marked critical
* if <code>cA</code> is TRUE, meaning this certificate belongs to a
* Certificate Authority. <P />
*
* The ASN.1 definition for this is:
* <PRE>
* BasicConstraints ::= SEQUENCE {
* cA BOOLEAN DEFAULT FALSE,
* pathLenConstraint INTEGER (0..MAX) OPTIONAL
* }
* </PRE>
*
* @return MISSING_PATH_LENGTH_CONSTRAINT if the
* <code>BasicConstraints</code> extension is absent or the subject
* of the certificate is not a CA. If the subject of the certificate
* is a CA and <code>pathLenConstraint</code> does not appear,
* <code>UNLIMITED_CERT_CHAIN_LENGTH</code> 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
* <code>pathLenConstraint</code> is returned.
*/
public int getBasicConstraints() {
if (isCA) {
return pLenConstr;
} else {
return MISSING_PATH_LENGTH_CONSTRAINT;
}
}
/**
* 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: <BR />
* <TABLE>
* <TR><TD>digitalSignature</TD> <TD>0</TD> </TR>
* <TR><TD>nonRepudiation</TD> <TD>1</TD> </TR>
* <TR><TD>keyEncipherment</TD> <TD>2</TD> </TR>
* <TR><TD>dataEncipherment</TD> <TD>3</TD> </TR>
* <TR><TD>keyAgreement</TD> <TD>4</TD> </TR>
* <TR><TD>keyCertSign</TD> <TD>5</TD> </TR>
* <TR><TD>cRLSign</TD> <TD>6</TD> </TR>
* <TR><TD>encipherOnly</TD> <TD>7</TD> </TR>
* <TR><TD>decipherOnly</TD> <TD>8</TD> </TR>
* </TABLE>
* <P />
* @return a bitvector indicating approved key usage of the certificate
* public key, -1 if a KeyUsage extension is not present.
*/
public int getKeyUsage() {
return keyUsage;
}
/**
* 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: <BR />
* <TABLE>
* <TR><TD>serverAuth</TD> <TD>1</TD> </TR>
* <TR><TD>clientAuth</TD> <TD>2</TD> </TR>
* <TR><TD>codeSigning</TD> <TD>3</TD> </TR>
* <TR><TD>emailProtection</TD> <TD>4</TD> </TR>
* <TR><TD>ipsecEndSystem</TD> <TD>5</TD> </TR>
* <TR><TD>ipsecTunnel</TD> <TD>6</TD> </TR>
* <TR><TD>ipsecUser</TD> <TD>7</TD> </TR>
* <TR><TD>timeStamping</TD> <TD>8</TD> </TR>
* </TABLE>
* <P />
* @return a bitvector indicating extended usage of the certificate
* public key, -1 if a critical extendedKeyUsage extension is not present.
*/
public int getExtKeyUsage() {
return extKeyUsage;
}
/**
* Gets the type of subject alternative name.
*
* @return type of subject alternative name
*/
public int getSubjectAltNameType() {
return subAltNameType;
}
/**
* Gets the subject alternative name or null if it was not in the
* certificate.
*
* @return type of subject alternative name or null
*/
public Object getSubjectAltName() {
return subAltName;
}
/**
* Gets the printable form of the serial number of this
* <CODE>Certificate</CODE>.
* If the serial number within the <CODE>certificate</CODE>
* 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; <CODE>NULL</CODE> is returned
* if there is no serial number.
*/
public String getSerialNumber() {
return serialNumber;
}
/**
* 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
*/
public void verify(PublicKey k) throws CertificateException {
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);
}
/**
* Gets the name of the algorithm used to sign the certificate.
* <P />
* @return the name of signature algorithm
*/
public String getSigAlgName() {
/*
* 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 + ")");
}
/** Array of purpose strings describing key usage role. */
private static final String[] KEY_USAGE = {
"digitalSignature", // 0
"nonRepudiation", // 1
"keyEncipherment", // 2
"dataEncipherment", // 3
"keyAgreement", // 4
"keyCertSign", // 5
"cRLSign", // 6
"encipherOnly", // 7
"decipherOnly", // 8
// below are for the extended key usage extension
"9", "10", "11", "12", "13", "14", "15", "16", // 9-16
"serverAuth", // 17
"clientAuth", // 18
"codeSigning", // 19
"emailProtection", // 20
"ipsecEndSystem", // 21
"ipsecTunnel", // 22
"ipsecUser", // 23
"timeStamping" // 24
};
/**
* Converts a Date object to a string containing the corresponding
* date.<br />
* <b>NOTE:</b> This is here only because the J2ME date class does not
* implement toString() in any meaningful way.
* <p />
* @param date Date object to be converted
* @return a string representation of the Date object in
* the form "month/day/year hour:min:sec"
*/
private static String date2str(Date date) {
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;
}
/**
* Returns a string representation of this certificate.
* <p />
* @return a human readable string repesentation of this certificate
*/
public String toString() {
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();
}
}
|