// --- BEGIN COPYRIGHT BLOCK --- // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; version 2 of the License. // // 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 for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // (C) 2007 Red Hat, Inc. // All rights reserved. // --- END COPYRIGHT BLOCK --- package netscape.security.x509; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; import java.math.BigInteger; import java.security.Principal; import java.security.PublicKey; import java.security.PrivateKey; import java.security.Security; import java.security.Signature; import java.security.NoSuchAlgorithmException; import java.security.InvalidKeyException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Set; import netscape.security.util.*; /** *
* An implmentation for X509 CRL (Certificate Revocation List). *
* The X.509 v2 CRL format is described below in ASN.1: *
**
* CertificateList ::= SEQUENCE { * tbsCertList TBSCertList, * signatureAlgorithm AlgorithmIdentifier, * signature BIT STRING } *
* A good description and profiling is provided in the IETF PKIX WG * draft, Part I: X.509 Certificate and CRL Profile, * <draft-ietf-pkix-ipki-part1-06.txt>. *
* The ASN.1 definition of tbsCertList
is:
*
* TBSCertList ::= SEQUENCE { * version Version OPTIONAL, * -- if present, must be v2 * signature AlgorithmIdentifier, * issuer Name, * thisUpdate ChoiceOfTime, * nextUpdate ChoiceOfTime OPTIONAL, * revokedCertificates SEQUENCE OF SEQUENCE { * userCertificate CertificateSerialNumber, * revocationDate ChoiceOfTime, * crlEntryExtensions Extensions OPTIONAL * -- if present, must be v2 * } OPTIONAL, * crlExtensions [0] EXPLICIT Extensions OPTIONAL * -- if present, must be v2 * } ** * @author Hemma Prafullchandra * @version 1.8 * @see X509CRL */ public class X509CRLImpl extends X509CRL { // CRL data, and its envelope private byte[] signedCRL; // DER encoded crl private byte[] signature; // raw signature bits private byte[] tbsCertList; // DER encoded "to-be-signed" CRL private AlgorithmId sigAlgId; // sig alg in CRL // crl information private int version; private AlgorithmId infoSigAlgId; // sig alg in "to-be-signed" crl private X500Name issuer; private Date thisUpdate = null; private Date nextUpdate = null; // private static final Hashtable revokedCerts = new Hashtable(); private Hashtable revokedCerts = new Hashtable(); // private static CRLExtensions extensions = null; private CRLExtensions extensions = null; private boolean entriesIncluded = true; private final static boolean isExplicit = true; private boolean readOnly = false; /** * Not to be used. As it would lead to cases of uninitialized * CRL objects. */ private X509CRLImpl() { } /** * Unmarshals an X.509 CRL from its encoded form, parsing the encoded * bytes. This form of constructor is used by agents which * need to examine and use CRL contents. Note that the buffer * must include only one CRL, and no "garbage" may be left at * the end. * * @param crlData the encoded bytes, with no trailing padding. * @exception CRLException on parsing errors. * @exception X509ExtensionException on extension handling errors. */ public X509CRLImpl(byte[] crlData) throws CRLException, X509ExtensionException { try { DerValue in = new DerValue(crlData); parse(in); signedCRL = crlData; } catch (IOException e) { throw new CRLException("Parsing error: " + e.getMessage()); } } public X509CRLImpl(byte[] crlData, boolean includeEntries) throws CRLException, X509ExtensionException { try { entriesIncluded = includeEntries; DerValue in = new DerValue(crlData); parse(in, includeEntries); signedCRL = crlData; } catch (IOException e) { throw new CRLException("Parsing error: " + e.getMessage()); } } /** * Unmarshals an X.509 CRL from an input stream. Only one CRL * is expected at the end of the input stream. * * @param inStrm an input stream holding at least one CRL * @exception CRLException on parsing errors. * @exception X509ExtensionException on extension handling errors. */ public X509CRLImpl(InputStream inStrm) throws CRLException, X509ExtensionException { try { DerValue val = new DerValue(inStrm); parse(val); signedCRL = val.toByteArray(); } catch (IOException e) { throw new CRLException("Parsing error: " + e.getMessage()); } } /** * Initial CRL constructor, no revoked certs, and no extensions. * * @param issuer the name of the CA issuing this CRL. * @param thisUpdate the Date of this issue. * @param nextUpdate the Date of the next CRL. */ public X509CRLImpl(X500Name issuer, Date thisDate, Date nextDate) { this.issuer = issuer; this.thisUpdate = thisDate; this.nextUpdate = nextDate; } /** * CRL constructor, revoked certs, no extensions. * * @param issuer the name of the CA issuing this CRL. * @param thisUpdate the Date of this issue. * @param nextUpdate the Date of the next CRL. * @param badCerts the array of revoked certificates. * * @exception CRLException on parsing/construction errors. * @exception X509ExtensionException on extension handling errors. */ public X509CRLImpl(X500Name issuer, Date thisDate, Date nextDate, RevokedCertificate[] badCerts) throws CRLException, X509ExtensionException { this.issuer = issuer; this.thisUpdate = thisDate; this.nextUpdate = nextDate; if (badCerts != null) { for (int i = 0; i < badCerts.length; i++) this.revokedCerts.put(badCerts[i].getSerialNumber(), badCerts[i]); } } /** * CRL constructor, revoked certs and extensions. * * @param issuer the name of the CA issuing this CRL. * @param thisUpdate the Date of this issue. * @param nextUpdate the Date of the next CRL. * @param badCerts the array of revoked certificates. * @param crlExts the CRL extensions. * * @exception CRLException on parsing/construction errors. * @exception X509ExtensionException on extension handling errors. */ public X509CRLImpl(X500Name issuer, Date thisDate, Date nextDate, RevokedCertificate[] badCerts, CRLExtensions crlExts) throws CRLException, X509ExtensionException { this.issuer = issuer; this.thisUpdate = thisDate; this.nextUpdate = nextDate; if (badCerts != null) { for (int i = 0; i < badCerts.length; i++) { if (badCerts[i] != null) { this.revokedCerts.put(badCerts[i].getSerialNumber(), badCerts[i]); if (badCerts[i].hasExtensions()) this.version = 1; } } } if (crlExts != null) { this.extensions = crlExts; this.version = 1; } } /** * CRL constructor, revoked certs and extensions. * This will be used by code that constructs CRL and uses * encodeInfo() in order to sign it using external means * (other than sign() method) * * @param issuer the name of the CA issuing this CRL. * @param sigAlg signing algorithm id * @param thisUpdate the Date of this issue. * @param nextUpdate the Date of the next CRL. * @param badCerts the array of revoked certificates. * @param crlExts the CRL extensions. */ public X509CRLImpl(X500Name issuer, AlgorithmId algId, Date thisDate, Date nextDate, RevokedCertificate[] badCerts, CRLExtensions crlExts) throws CRLException, X509ExtensionException { this(issuer,thisDate,nextDate,badCerts,crlExts); infoSigAlgId = algId; } /** * CRL constructor, revoked certs and extensions. * * @param issuer the name of the CA issuing this CRL. * @param sigAlg signing algorithm id * @param thisUpdate the Date of this issue. * @param nextUpdate the Date of the next CRL. * @param badCerts the hashtable of revoked certificates. * @param crlExts the CRL extensions. * * @exception CRLException on parsing/construction errors. * @exception X509ExtensionException on extension handling errors. */ public X509CRLImpl(X500Name issuer, AlgorithmId algId, Date thisDate, Date nextDate, Hashtable badCerts, CRLExtensions crlExts) throws CRLException, X509ExtensionException { this.issuer = issuer; this.thisUpdate = thisDate; this.nextUpdate = nextDate; this.revokedCerts = badCerts; if (crlExts != null) { this.extensions = crlExts; this.version = 1; } infoSigAlgId = algId; } /** * Returns the ASN.1 DER encoded form of this CRL. * * @exception CRLException if an encoding error occurs. */ public byte[] getEncoded() throws CRLException { if (signedCRL == null) throw new CRLException("Null CRL to encode"); byte[] dup = new byte[signedCRL.length]; System.arraycopy(signedCRL, 0, dup, 0, dup.length); return dup; } public boolean hasUnsupportedCriticalExtension() { // XXX NOT IMPLEMENTED return true; } /** * Encodes the "to-be-signed" CRL to the OutputStream. * * @param out the OutputStream to write to. * @exception CRLException on encoding errors. * @exception X509ExtensionException on extension encoding errors. */ public void encodeInfo(OutputStream out) throws CRLException, X509ExtensionException { try { DerOutputStream tmp = new DerOutputStream(); DerOutputStream rCerts = new DerOutputStream(); DerOutputStream seq = new DerOutputStream(); if (version != 0) // v2 crl encode version tmp.putInteger(new BigInt(version)); infoSigAlgId.encode(tmp); issuer.encode(tmp); // from 2050 should encode GeneralizedTime tmp.putUTCTime(thisUpdate); if (nextUpdate != null) tmp.putUTCTime(nextUpdate); if (! revokedCerts.isEmpty()) { for (Enumeration e = revokedCerts.elements(); e.hasMoreElements();) ((RevokedCertImpl)e.nextElement()).encode(rCerts); tmp.write(DerValue.tag_Sequence, rCerts); } if (extensions != null) extensions.encode(tmp, isExplicit); seq.write(DerValue.tag_Sequence, tmp); tbsCertList = seq.toByteArray(); out.write(tbsCertList); } catch (IOException e) { throw new CRLException("Encoding error: " + e.getMessage()); } } /** * Verifies that this CRL was signed using the * private key that corresponds to the specified public key. * * @param key the PublicKey used to carry out the verification. * * @exception NoSuchAlgorithmException on unsupported signature * algorithms. * @exception InvalidKeyException on incorrect key. * @exception NoSuchProviderException if there's no default provider. * @exception SignatureException on signature errors. * @exception CRLException on encoding errors. */ public void verify(PublicKey key) throws CRLException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { verify(key, null); } /** * Verifies that this CRL was signed using the * private key that corresponds to the specified public key, * and that the signature verification was computed by * the given provider. * * @param key the PublicKey used to carry out the verification. * @param sigProvider the name of the signature provider. * * @exception NoSuchAlgorithmException on unsupported signature * algorithms. * @exception InvalidKeyException on incorrect key. * @exception NoSuchProviderException on incorrect provider. * @exception SignatureException on signature errors. * @exception CRLException on encoding errors. */ public void verify(PublicKey key, String sigProvider) throws CRLException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { if (signedCRL == null) { throw new CRLException("Uninitialized CRL"); } Signature sigVerf = null; String sigAlg = sigAlgId.getName(); if (sigProvider.equals("Mozilla-JSS")) { if (sigAlg.equals("MD5withRSA")) { sigAlg = "MD5/RSA"; } else if (sigAlg.equals("MD2withRSA")) { sigAlg = "MD2/RSA"; } else if (sigAlg.equals("SHA1withRSA")) { sigAlg = "SHA1/RSA"; } else if (sigAlg.equals("SHA1withDSA")) { sigAlg = "SHA1/DSA"; } } sigVerf = Signature.getInstance(sigAlg, sigProvider); sigVerf.initVerify(key); if (tbsCertList == null) throw new CRLException("Uninitialized CRL"); sigVerf.update(tbsCertList, 0, tbsCertList.length); if (!sigVerf.verify(signature)) { throw new CRLException("Signature does not match."); } } /** * Encodes an X.509 CRL, and signs it using the key * passed. * * @param key the private key used for signing. * @param algorithm the name of the signature algorithm used. * * @exception NoSuchAlgorithmException on unsupported signature * algorithms. * @exception InvalidKeyException on incorrect key. * @exception NoSuchProviderException on incorrect provider. * @exception SignatureException on signature errors. * @exception CRLException if any mandatory data was omitted. * @exception X509ExtensionException on any extension errors. */ public void sign(PrivateKey key, String algorithm) throws CRLException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, X509ExtensionException { sign(key, algorithm, null); } /** * Encodes an X.509 CRL, and signs it using the key * passed. * * @param key the private key used for signing. * @param algorithm the name of the signature algorithm used. * @param provider the name of the provider. * * @exception NoSuchAlgorithmException on unsupported signature * algorithms. * @exception InvalidKeyException on incorrect key. * @exception NoSuchProviderException on incorrect provider. * @exception SignatureException on signature errors. * @exception CRLException if any mandatory data was omitted. * @exception X509ExtensionException on any extension errors. */ public void sign(PrivateKey key, String algorithm, String provider) throws CRLException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, X509ExtensionException { try { if (readOnly) throw new CRLException("cannot over-write existing CRL"); Signature sigEngine = null; if (provider == null) sigEngine = Signature.getInstance(algorithm); else sigEngine = Signature.getInstance(algorithm, provider); sigEngine.initSign(key); // in case the name is reset sigAlgId = AlgorithmId.get(sigEngine.getAlgorithm()); infoSigAlgId = sigAlgId; DerOutputStream out = new DerOutputStream(); DerOutputStream tmp = new DerOutputStream(); // encode crl info encodeInfo(tmp); // encode algorithm identifier sigAlgId.encode(tmp); // Create and encode the signature itself. sigEngine.update(tbsCertList, 0, tbsCertList.length); signature = sigEngine.sign(); tmp.putBitString(signature); // Wrap the signed data in a SEQUENCE { data, algorithm, sig } out.write(DerValue.tag_Sequence, tmp); signedCRL = out.toByteArray(); readOnly = true; } catch (IOException e) { throw new CRLException("Error while encoding data: " + e.getMessage()); } } /** * Returns a printable string of this CRL. * * @return value of this CRL in a printable form. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append("X.509 CRL v" + (version+1) + "\n"); sb.append("Signature Algorithm: " + sigAlgId.toString() + ", OID=" + (sigAlgId.getOID()).toString() + "\n"); sb.append("Issuer: " + issuer.toString() + "\n"); sb.append("\nThis Update: " + thisUpdate.toString() + "\n"); if (nextUpdate != null) sb.append("Next Update: " + nextUpdate.toString() + "\n"); if (revokedCerts.isEmpty()) sb.append("\nNO certificates have been revoked\n"); else { sb.append("\nRevoked Certificates:\n"); for (Enumeration e = revokedCerts.elements(); e.hasMoreElements();) sb.append(((RevokedCertificate)e.nextElement()).toString()); } if (extensions != null) { for (int i = 0; i < extensions.size(); i++) { sb.append("\nCRL Extension[" + i + "]: " + ((Extension)(extensions.elementAt(i))).toString()); } } netscape.security.util.PrettyPrintFormat pp = new netscape.security.util.PrettyPrintFormat(" ", 20); String signaturebits = pp.toHexString(signature); sb.append("\nSignature:\n" + signaturebits); return sb.toString(); } /** * Checks whether the given serial number is on this CRL. * * @param serialNumber the number to check for. * @return true if the given serial number is on this CRL, * false otherwise. */ public boolean isRevoked(BigInteger serialNumber) { if (revokedCerts == null || revokedCerts.isEmpty()) return false; return revokedCerts.containsKey(serialNumber); } public boolean isRevoked(Certificate cert) { if (cert == null) return false; if (cert instanceof X509Certificate) { return isRevoked(((X509Certificate)cert).getSerialNumber()); } else { return false; } } /** * Gets the version number from the CRL. * The ASN.1 definition for this is: *
* Version ::= INTEGER { v1(0), v2(1), v3(2) } * -- v3 does not apply to CRLs but appears for consistency * -- with definition of Version for certs ** @return the version number. */ public int getVersion() { return version; } /** * Gets the issuer distinguished name from this CRL. * The issuer name identifies the entity who has signed (and * issued the CRL). The issuer name field contains an * X.500 distinguished name (DN). * The ASN.1 definition for this is: *
* issuer Name * * Name ::= CHOICE { RDNSequence } * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName * RelativeDistinguishedName ::= * SET OF AttributeValueAssertion * * AttributeValueAssertion ::= SEQUENCE { * AttributeType, * AttributeValue } * AttributeType ::= OBJECT IDENTIFIER * AttributeValue ::= ANY ** The Name describes a hierarchical name composed of attributes, * such as country name, and corresponding values, such as US. * The type of the component AttributeValue is determined by the * AttributeType; in general it will be a directoryString. * A directoryString is usually one of PrintableString, * TeletexString or UniversalString. * @return the issuer name. */ public Principal getIssuerDN() { return (Principal)issuer; } /** * Gets the thisUpdate date from the CRL. * The ASN.1 definition for this is: * * @return the thisUpdate date from the CRL. */ public Date getThisUpdate() { return (new Date(thisUpdate.getTime())); } /** * Gets the nextUpdate date from the CRL. * * @return the nextUpdate date from the CRL, or null if * not present. */ public Date getNextUpdate() { if (nextUpdate == null) return null; return (new Date(nextUpdate.getTime())); } /** * Get the revoked certificate from the CRL by the serial * number provided. * * @return the revoked certificate or null if there is * no entry in the CRL marked with the provided serial number. * @see RevokedCertificate */ public X509CRLEntry getRevokedCertificate(BigInteger serialNumber) { if (revokedCerts == null || revokedCerts.isEmpty()) return null; RevokedCertificate badCert = (RevokedCertificate)revokedCerts.get(serialNumber); return badCert; } /** * Gets all the revoked certificates from the CRL. * A Set of RevokedCertificate. * * @return all the revoked certificates or null if there are * none. * @see RevokedCertificate */ public Set getRevokedCertificates() { if (revokedCerts == null || revokedCerts.isEmpty()) return null; else { ArraySet certSet = new ArraySet(); Collection badCerts = revokedCerts.values(); Object[] objs = badCerts.toArray(); for (int i = 0; i < objs.length; i++) certSet.add(objs[i]); return certSet; } } public Hashtable getListOfRevokedCertificates() { if (revokedCerts == null) return null; else return (Hashtable)revokedCerts.clone(); } public int getNumberOfRevokedCertificates() { if (revokedCerts == null) return -1; else return revokedCerts.size(); } /** * Gets the DER encoded CRL information, the *
tbsCertList
from this CRL.
* This can be used to verify the signature independently.
*
* @return the DER encoded CRL information.
* @exception CRLException on parsing errors.
* @exception X509ExtensionException on extension parsing errors.
*/
public byte[] getTBSCertList()
throws CRLException {
if (tbsCertList == null)
throw new CRLException("Uninitialized CRL");
byte[] dup = new byte[tbsCertList.length];
System.arraycopy(tbsCertList, 0, dup, 0, dup.length);
return dup;
}
/**
* Gets the raw Signature bits from the CRL.
*
* @return the signature.
*/
public byte[] getSignature() {
if (signature == null)
return null;
byte[] dup = new byte[signature.length];
System.arraycopy(signature, 0, dup, 0, dup.length);
return dup;
}
/**
* Gets the signature algorithm name for the CRL
* signature algorithm. For example, the string "SHA1withDSA".
* The ASN.1 definition for this is:
* * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, * parameters ANY DEFINED BY algorithm OPTIONAL } * -- contains a value of the type * -- registered for use with the * -- algorithm object identifier value ** * @return the signature algorithm name. */ public String getSigAlgName() { if (sigAlgId == null) return null; return sigAlgId.getName(); } /** * Gets the signature algorithm OID string from the CRL. * An OID is represented by a set of positive whole number separated * by ".", that means,
extnValue
) identified by the passed in oid String.
* The oid
string is
* represented by a set of positive whole number separated
* by ".", that means,