Java tutorial
/********************************************************************* * * Authors: Vincenzo Ciaschini - Vincenzo.Ciaschini@cnaf.infn.it * * Copyright (c) 2006 INFN-CNAF on behalf of the * EGEE project. * For license conditions see LICENSE * * Parts of this code may be based upon or even include verbatim pieces, * originally written by other people, in which case the original header * follows. * *********************************************************************/ package org.glite.voms; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.PublicKey; import java.security.Security; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import java.util.Stack; import java.util.TreeSet; import java.util.Vector; import org.apache.log4j.Logger; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.glite.voms.ac.ACTargets; import org.glite.voms.ac.AttributeCertificate; import org.glite.voms.ac.AttributeCertificateInfo; import org.glite.voms.ac.VOMSTrustStore; import org.glite.voms.ac.*; public class PKIVerifier { private static Logger logger = Logger.getLogger(PKIVerifier.class.getName()); public static final String SUBJECT_KEY_IDENTIFIER = "2.5.29.14"; public static final String AUTHORITY_KEY_IDENTIFIER = "2.5.29.35"; public static final String PROXYCERTINFO = "1.3.6.1.5.5.7.1.14"; public static final String PROXYCERTINFO_OLD = "1.3.6.1.4.1.3536.1.222"; public static final String BASIC_CONSTRAINTS_IDENTIFIER = "2.5.29.19"; public static final String KEY_USAGE_IDENTIFIER = "2.5.29.15"; public static final String TARGET = "2.5.29.55"; private static final String[] OIDs = { SUBJECT_KEY_IDENTIFIER, AUTHORITY_KEY_IDENTIFIER, PROXYCERTINFO, PROXYCERTINFO_OLD, BASIC_CONSTRAINTS_IDENTIFIER, KEY_USAGE_IDENTIFIER }; private static final String[] AC_OIDs = { TARGET }; private static final Set handledOIDs = new TreeSet(Arrays.asList(OIDs)); private static final Set handledACOIDs = new TreeSet(Arrays.asList(AC_OIDs)); private PKIStore caStore = null; private VOMSTrustStore vomsStore = null; static { if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } } /** * Initializes the verifier. * * @param vomsStore * the VOMSTrustStore object which represents the vomsdir store. * @param caStore * the PKIStore object which represents the CA store. */ public PKIVerifier(VOMSTrustStore vomsStore, PKIStore caStore) { this.vomsStore = vomsStore; this.caStore = caStore; } /** * Initializes the verifier. The CA store is initialized at: * "/etc/grid-security/certificates." * * @param vomsStore * the VOMSTrustStore object which represents the vomsdir store. * * @throws IOException * if there have been IO errors. * @throws CertificateException * if there have been problems parsing a certificate * @throws CRLException * if there have been problems parsing a CRL. */ public PKIVerifier(VOMSTrustStore vomsStore) throws IOException, CertificateException, CRLException { this(vomsStore, new PKIStore(PKIStore.DEFAULT_CADIR, PKIStore.TYPE_CADIR, true)); } /** * Initializes the verifier. * * If the VOMSDIR and CADIR system properties are set, those values * are used to initialize the voms and ca certificates trust stores. * Tipically, the VOMSDIR should point to a directory that contains * voms server certificates, while the CADIR should point to a * directory where CA certificates and crl are stored. * * If the system properties are not set, The CA store is initialized to: * "/etc/grid-security/certificates.", while the VOMS store is initialized * to "/etc/grid-security/vomsdir" (slash becomes backslash on windows). * * @throws IOException * if there have been IO errors. * @throws CertificateException * if there have been problems parsing a certificate * @throws CRLException * if there have been problems parsing a CRL. */ public PKIVerifier() throws IOException, CertificateException, CRLException { String vomsDir = System.getProperty("VOMSDIR"); String caDir = System.getProperty("CADIR"); if (vomsDir != null) vomsStore = new PKIStore(vomsDir, PKIStore.TYPE_VOMSDIR, true); else vomsStore = new PKIStore(PKIStore.DEFAULT_VOMSDIR, PKIStore.TYPE_VOMSDIR, true); if (caDir != null) caStore = new PKIStore(caDir, PKIStore.TYPE_CADIR, true); else caStore = new PKIStore(PKIStore.DEFAULT_CADIR, PKIStore.TYPE_CADIR, true); } /** * Cleans up resources allocated by the verifier. * * This method MUST be called prior to disposal of this object, otherwise * memory leaks and runaway threads will occur. */ public void cleanup() { if (vomsStore != null) vomsStore.stopRefresh(); if (caStore != null) caStore.stopRefresh(); vomsStore = null; caStore = null; } /** * Sets a new CAStore. * * @param store * the new CA store. */ public void setCAStore(PKIStore store) { if (caStore != null) { caStore.stopRefresh(); caStore = null; } caStore = store; } /** * Sets a new VOMSStore. * * @param store * the new VOMS store. */ public void setVOMSStore(VOMSTrustStore store) { if (vomsStore != null) { vomsStore.stopRefresh(); vomsStore = null; } vomsStore = store; } private static String getHostName() { try { InetAddress addr = InetAddress.getLocalHost(); return addr.getCanonicalHostName(); } catch (UnknownHostException e) { logger.error("Cannot discover hostName."); return ""; } } /** * Verifies an Attribute Certificate according to RFC 3281. * * @param ac * the Attribute Certificate to verify. * * @return true if the attribute certificate is verified, false otherwise. */ public boolean verify(AttributeCertificate ac) { if (ac == null || vomsStore == null) return false; AttributeCertificateInfo aci = ac.getAcinfo(); X509Certificate[] certificates = null; ACCerts certList = aci.getCertList(); LSCFile lsc = null; String voName = ac.getVO(); if (certList != null) lsc = vomsStore.getLSC(voName, ac.getHost()); logger.debug("LSC is: " + lsc); if (lsc != null) { boolean success = false; Vector dns = lsc.getDNLists(); Iterator dnIter = dns.iterator(); // First verify if LSC file applies; while (!success && dnIter.hasNext()) { boolean doBreak = false; while (dnIter.hasNext() && !doBreak) { Iterator certIter = certList.getCerts().iterator(); Vector realDNs = (Vector) dnIter.next(); Iterator realDNsIter = realDNs.iterator(); while (realDNsIter.hasNext() && certIter.hasNext() && !doBreak) { String dn = null; String is = null; try { dn = (String) realDNsIter.next(); is = (String) realDNsIter.next(); } catch (NoSuchElementException e) { doBreak = true; } X509Certificate cert = (X509Certificate) certIter.next(); String candidateDN = PKIUtils.getOpenSSLFormatPrincipal(cert.getSubjectDN()); String candidateIs = PKIUtils.getOpenSSLFormatPrincipal(cert.getIssuerDN()); logger.debug("dn is : " + dn); logger.debug("is is : " + is); logger.debug("canddn is : " + candidateDN); logger.debug("candis is : " + candidateIs); logger.debug("dn == canddn is " + dn.equals(candidateDN)); logger.debug("is == candis is " + is.equals(candidateIs)); if (!dn.equals(candidateDN) || !is.equals(candidateIs)) doBreak = true; } if (!doBreak && !realDNsIter.hasNext() && !certIter.hasNext()) success = true; } } if (success == true) { // LSC found. Now verifying certificate certificates = (X509Certificate[]) certList.getCerts().toArray(new X509Certificate[] {}); } } if (certificates == null) { // lsc check failed logger.debug("lsc check failed."); // System.out.println("Looking for certificates."); if (logger.isDebugEnabled()) logger.debug("Looking for hash: " + PKIUtils.getHash(ac.getIssuer()) + " for certificate: " + ac.getIssuer().getName()); X509Certificate[] candidates = vomsStore.getAACandidate(ac.getIssuer(), voName); if (candidates == null) logger.debug("No candidates found!"); else if (candidates.length != 0) { int i = 0; while (i < candidates.length) { X509Certificate currentCert = (X509Certificate) candidates[i]; PublicKey key = currentCert.getPublicKey(); if (logger.isDebugEnabled()) { logger.debug("Candidate: " + currentCert.getSubjectDN().getName()); logger.debug("Key class: " + key.getClass()); logger.debug("Key: " + key); byte[] data = key.getEncoded(); String str = "Key: "; for (int j = 0; j < data.length; j++) str += Integer.toHexString(data[j]) + " "; logger.debug(str); } if (ac.verifyCert(currentCert)) { logger.debug("Signature Verification OK"); certificates = new X509Certificate[1]; certificates[0] = currentCert; break; } else { logger.debug("Signature Verification false"); } i++; } } } if (certificates == null) { logger.error( "Cannot find usable certificates to validate the AC. Check that the voms server host certificate is in your vomsdir directory."); return false; } if (logger.isDebugEnabled()) { for (int l = 0; l < certificates.length; l++) logger.debug("Position: " + l + " value: " + certificates[l].getSubjectDN().getName()); } if (!verify(certificates)) { logger.error("Cannot verify issuer certificate chain for AC"); return false; } if (!ac.isValid()) { logger.error("Attribute Certificate not valid at current time."); return false; } // AC Targeting verification ACTargets targets = aci.getTargets(); if (targets != null) { String hostname = getHostName(); boolean success = false; Iterator i = targets.getTargets().iterator(); while (i.hasNext()) { String name = (String) i.next(); if (name.equals(hostname)) { success = true; break; } } if (!success) { logger.error("Targeting check failed!"); return false; } } // unhandled extensions check X509Extensions exts = aci.getExtensions(); if (exts != null) { Enumeration oids = exts.oids(); while (oids.hasMoreElements()) { DERObjectIdentifier oid = (DERObjectIdentifier) oids.nextElement(); X509Extension ext = exts.getExtension(oid); if (ext.isCritical() && !handledACOIDs.contains(oid)) { logger.error("Unknown critical extension discovered: " + oid.getId()); return false; } } } return true; } /** * Verifies an certificate chain according to RFC 3280. * * @param certs * the chain to verify. * * @return true if the chain is verified, false otherwise. */ public boolean verify(X509Certificate[] certs) { if (caStore == null) { logger.error("No Trust Anchor are known."); return false; } if (certs.length <= 0) { logger.error("Certificate verification: passed empty certificate array."); return false; } Hashtable certificates = caStore.getCAs(); Stack certStack = new Stack(); // First, build the certification path certStack.push(certs[0]); logger.info("Certificate verification: Verifying certificate '" + certs[0].getSubjectDN().getName() + "'"); X509Certificate currentCert = certs[0]; logger.debug("path length = " + certs.length); for (int i = 1; i < certs.length; i++) { if (logger.isDebugEnabled()) logger.debug("Checking: " + certs[i].getSubjectDN().getName()); if (PKIUtils.checkIssued(certs[i], certs[i - 1])) { logger.debug("Is issuer"); certStack.push(certs[i]); currentCert = certs[i]; if (logger.isDebugEnabled()) logger.debug("ELEMENT: " + currentCert.getSubjectDN().getName()); } logger.debug("Is not issuer"); } logger.debug("Before anchor searching."); X509Certificate candidate = null; if (logger.isDebugEnabled()) { Iterator j = certStack.iterator(); while (j.hasNext()) logger.debug("Content: " + ((X509Certificate) j.next()).getSubjectDN().getName()); } // replace self-signed certificate passed with one from store. if (PKIUtils.selfIssued(currentCert)) { String hash = PKIUtils.getHash(currentCert); Vector candidates = (Vector) certificates.get(hash); int index = -1; if (candidates != null && (index = candidates.indexOf(currentCert)) != -1) { certStack.pop(); candidate = (X509Certificate) candidates.elementAt(index); certStack.push(candidate); if (logger.isDebugEnabled()) logger.debug("ELEMENT: " + candidate.getSubjectDN().getName()); } else { logger.error("Certificate verification: self-signed certificate '" + candidate.getSubjectDN().getName() + "' not found among trusted certificates."); return false; } } else { candidate = null; logger.debug("Looking for anchor"); // now, complete the certification path. do { String hash = PKIUtils.getHash(currentCert.getIssuerX500Principal()); logger.debug("hash = " + hash); Vector candidates = (Vector) certificates.get(hash); if (candidates != null) { logger.debug("CANDIDATES: " + candidates); Iterator i = candidates.iterator(); while (i.hasNext()) { candidate = (X509Certificate) i.next(); if (logger.isDebugEnabled()) logger.debug("Candidate = " + candidate.getSubjectDN().getName()); if (PKIUtils.checkIssued(candidate, currentCert)) { certStack.push(candidate); currentCert = candidate; if (logger.isDebugEnabled()) logger.debug("ELEMENT: " + candidate.getSubjectDN().getName()); break; } else candidate = null; } } } while (candidate != null && !PKIUtils.selfIssued(currentCert)); } // no trust anchor found if (candidate == null) { logger.error("Certificate verification: no trust anchor found."); return false; } int currentLength = 0; boolean success = true; PublicKey candidatePublicKey = null; X509Certificate issuerCert = null; if (logger.isDebugEnabled()) { logger.debug("Constructed chain:"); Iterator j = certStack.iterator(); while (j.hasNext()) logger.debug("Content: " + ((X509Certificate) j.next()).getSubjectDN().getName()); } // now verifies it while (!certStack.isEmpty()) { currentCert = (X509Certificate) certStack.pop(); if (logger.isDebugEnabled()) logger.debug("VERIFYING : " + currentCert.getSubjectDN().getName()); if (PKIUtils.selfIssued(currentCert)) { if (currentLength != 0) { logger.error("Certificate verification: Self signed certificate not trust anchor"); logger.error("subject: " + currentCert.getSubjectDN().getName()); success = false; break; } else { // this is the trust anchor candidatePublicKey = currentCert.getPublicKey(); issuerCert = currentCert; } } logger.debug("Checking chain"); if (!currentCert.getIssuerX500Principal().equals(issuerCert.getSubjectX500Principal())) { logger.error("Certificate verification: issuing chain broken."); return false; } logger.debug("Checking validity"); try { currentCert.checkValidity(); } catch (CertificateExpiredException e) { logger.error("Certificate verification: certificate in chain expired. " + e.getMessage(), e); logger.error("Faulty certificate: " + currentCert.getSubjectDN().getName()); logger.error("End validity : " + currentCert.getNotAfter().toString()); return false; } catch (CertificateNotYetValidException e) { logger.error("Certificate verification: certificate in chain not yet valid. " + e.getMessage(), e); logger.error("Faulty certificate: " + currentCert.getSubjectDN().getName()); logger.error("Start validity : " + currentCert.getNotBefore().toString()); return false; } logger.debug("Checking key"); try { currentCert.verify(candidatePublicKey); } catch (Exception e) { logger.error("Certificate verification: cannot verify signature. " + e.getMessage(), e); logger.error("Faulty certificate: " + currentCert.getSubjectDN().getName()); return false; } logger.debug("Checking revoked"); if (isRevoked(currentCert, issuerCert)) { logger.error("Certificate verification: certificate in chain has been revoked."); logger.error("Faulty certificate: " + currentCert.getSubjectDN().getName()); return false; } boolean isCA = PKIUtils.isCA(issuerCert); logger.debug("Checking CA " + isCA); if (isCA) { if (!allowsPath(currentCert, issuerCert)) { logger.error("Certificate verification: subject '" + currentCert.getSubjectDN().getName() + "' not allowed by CA '" + issuerCert.getSubjectDN().getName() + "'"); return false; } // check path length int maxPath = currentCert.getBasicConstraints(); logger.debug("stack.size = " + certStack.size() + " maxPath = " + maxPath); if (maxPath != -1) { if (maxPath < certStack.size()) { logger.error("Certificate verification: Maximum certification path length exceeded."); success = false; break; } } } else { // not a ca. Maybe a proxy? logger.debug("Checking for Proxy"); if (!PKIUtils.isProxy(currentCert)) { logger.error("Certificate verification: Non-proxy, non-CA certificate issued a certificate."); return false; } } // check for unhandled critical extensions Set criticals = currentCert.getCriticalExtensionOIDs(); if (criticals != null) { if (!handledOIDs.containsAll(criticals)) { logger.error("Certificate verification: Certificate contain unhandled critical extensions."); return false; } } issuerCert = currentCert; candidatePublicKey = currentCert.getPublicKey(); currentLength++; } if (!success) return false; return true; } private boolean allowsPath(X509Certificate cert, X509Certificate issuer) { Hashtable signings = caStore.getSignings(); // System.out.println("CLASS IS: " + // signings.get(PKIUtils.getHash(issuer)).getClass()); SigningPolicy signCandidate = (SigningPolicy) signings.get(PKIUtils.getHash(issuer)); logger.debug("signCandidate is: " + signCandidate); if (signCandidate != null) { logger.debug("Class of issuer is : " + issuer.getClass()); logger.debug("Class of Subject is: " + issuer.getSubjectDN().getClass()); String issuerSubj = PKIUtils.getOpenSSLFormatPrincipal(issuer.getSubjectDN()); logger.debug("Subject is : " + issuerSubj); // if (true) return true; Vector nameVector = getAllNames(cert); if (nameVector == null) return false; Iterator i = nameVector.iterator(); while (i.hasNext()) { boolean matched = false; String certSubj = (String) i.next(); logger.debug("Looking for " + issuerSubj); int index = signCandidate.findIssuer(issuerSubj); while (index != -1) { logger.debug("Inside index"); signCandidate.setCurrent(index); if (signCandidate.getAccessIDCA().equals(issuerSubj)) { Vector subjects = signCandidate.getCondSubjects(); Iterator subjIter = subjects.iterator(); while (subjIter.hasNext()) { String subj = (String) subjIter.next(); logger.debug("Comparing certSubj: '" + certSubj + "' to '" + subj + "'"); subj = subj.replaceFirst("\\*", "\\.\\*"); if (certSubj.matches(subj)) { matched = true; logger.debug("Subject: '" + certSubj + "' matches with subject: '" + subj + "' from signing policy."); break; } logger.debug("Subject: '" + certSubj + "' does not match subject: '" + subj + "' from signing policy."); } } index = signCandidate.findIssuer(issuerSubj, index); } if (!matched) { nameVector.clear(); return false; } } } return true; } private Vector getAllNames(X509Certificate cert) { if (cert != null) { Vector v = new Vector(); v.add(PKIUtils.getOpenSSLFormatPrincipal(cert.getSubjectDN())); // Collection c; // try { // c = cert.getSubjectAlternativeNames(); // } // catch (CertificateParsingException e) { // logger.error("Error in encoding Subject Alternative Names extension! " + e.getMessage()); // v.clear(); // return null; // } // Iterator i = c.iterator(); // while (i.hasNext()) { // List l = (List)i.next(); // int type = ((Integer)l.get(0)).intValue(); // switch (type) { // case 1: case 2: case 6: // case 7: case 8: // v.add((String)l.get(1)); // break; // case 4: // String dn = (String)l.get(1); // X500Principal principal = new X500Principal(dn); // v.add(PKIUtils.getOpenSSLFormatPrincipal(principal)); // break; // case 0: case 3: case 5: // v.clear(); // return null; // default: // break; // } // } return v; } return null; } private boolean isRevoked(X509Certificate cert, X509Certificate issuer) { Hashtable crls = caStore.getCRLs(); Vector crllist = (Vector) (crls.get(PKIUtils.getHash(issuer))); boolean issued = false; if (crllist != null) { Iterator i = crllist.iterator(); while (i.hasNext()) { X509CRL candidateCRL = (X509CRL) i.next(); if (candidateCRL != null) { try { candidateCRL.verify(issuer.getPublicKey()); } catch (Exception e) { continue; } { if (candidateCRL.getCriticalExtensionOIDs() == null) { if (candidateCRL.getIssuerX500Principal().equals(issuer.getIssuerX500Principal())) { if (candidateCRL.getNextUpdate().compareTo(new Date()) >= 0 && candidateCRL.getThisUpdate().compareTo(new Date()) <= 0) { X509CRLEntry entry = candidateCRL.getRevokedCertificate(cert.getSerialNumber()); if (entry == null) { return false; } } } } issued = true; } } } } return issued; } }