package org.openstack.atlas.util.ca.zeus; import org.openstack.atlas.util.ca.primitives.bcextenders.HackedProviderAccessor; import java.security.spec.InvalidKeySpecException; import java.util.HashMap; import java.util.Map; import java.security.KeyPair; import java.security.PublicKey; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import org.bouncycastle.jce.provider.JCERSAPrivateCrtKey; import org.bouncycastle.jce.provider.JCERSAPublicKey; import org.bouncycastle.jce.provider.X509CertificateObject; import org.openstack.atlas.util.ca.CertUtils; import org.openstack.atlas.util.ca.PemUtils; import org.openstack.atlas.util.ca.exceptions.PemException; import org.openstack.atlas.util.ca.exceptions.X509PathBuildException; import org.openstack.atlas.util.ca.primitives.PemBlock; import org.openstack.atlas.util.ca.primitives.RsaConst; import org.openstack.atlas.util.ca.util.ResponseWithExcpetions; import org.openstack.atlas.util.ca.util.StaticHelpers; import org.openstack.atlas.util.ca.util.X509BuiltPath; import org.openstack.atlas.util.ca.util.X509PathBuilder; import org.openstack.atlas.util.ca.util.X509ReaderWriter; public class ZeusUtils { private static final String USASCII = "US-ASCII"; private static final String KEYREQUIRED = "Key Required"; private static final String CERTREQUIRED = "Cert Required"; private static final String MISSINGUSASCII = String.format("Missing charset %s", USASCII); private static final String ERRORDECODINGKEY = "Error Decoding Key"; private static final String ERRORDECODINGCERT = "Error Decoding Cert"; static { RsaConst.init(); } private Set<X509CertificateObject> roots; public ZeusUtils() { roots = new HashSet<X509CertificateObject>(); } public ZeusUtils(Set<X509CertificateObject> roots) { this.roots = roots; } public ZeusCrtFile buildZeusCrtFile(String keyStr, String userCrtStr, String intermediates, boolean useLbaasValidation) { Date date = new Date(System.currentTimeMillis()); if (!useLbaasValidation) { return buildZeusCrtFile(keyStr, userCrtStr, intermediates, date); } else { return buildZeusCrtFileLbassValidation(keyStr, userCrtStr, intermediates); } } public ZeusCrtFile buildZeusCrtFileLbassValidation(String userKeyStr, String userCrtStr, String intermediates) { ZeusCrtFile zcf = new ZeusCrtFile(); List<ErrorEntry> errors = zcf.getErrors(); Map<X509CertificateObject, Integer> lineMap = new HashMap<X509CertificateObject, Integer>(); String zkey = ""; String zcrt = ""; String msg; KeyPair userKey = parseKey(userKeyStr, zcf.getErrors()); X509CertificateObject userCrt = decodeCrt(userCrtStr, zcf); List<X509CertificateObject> imdCrts = decodeImd(lineMap, intermediates, zcf); // Verify key matches cert if both are Present if (!zcf.containsErrorTypes(ErrorType.UNREADABLE_CERT, ErrorType.UNREADABLE_KEY)) { List<ErrorEntry> keyMatchErrors; keyMatchErrors = CertUtils.validateKeyMatchesCrt(userKey, userCrt); errors.addAll(keyMatchErrors); } // Verify user Crt is in valid date range Date now = new Date(System.currentTimeMillis()); if (userCrt != null && CertUtils.isCertExpired(userCrt, now)) { Date after = userCrt.getNotAfter(); String errorMsg = invalidDateMessage("User cert expired on", after); errors.add(new ErrorEntry(ErrorType.EXPIRED_CERT, errorMsg, true, null)); } if (userCrt != null && CertUtils.isCertPremature(userCrt, now)) { Date before = userCrt.getNotBefore(); String errorMsg = invalidDateMessage("User cert isn't valid till", before); errors.add(new ErrorEntry(ErrorType.PREMATURE_CERT, errorMsg, false, null)); } // If their is a chain veify that the top of the chain signs the users crt if (!imdCrts.isEmpty() && userCrt != null) { X509CertificateObject subjectCrt = userCrt; X509CertificateObject issuerCrt = imdCrts.get(0); List<ErrorEntry> crtSignErrors = CertUtils.verifyIssuerAndSubjectCert(issuerCrt, subjectCrt, true); if (ErrorEntry.hasFatal(crtSignErrors)) { if (lineMap.containsKey(issuerCrt)) { int issuerLineNum = lineMap.get(issuerCrt).intValue(); msg = String.format("Error the cert at line %d of the Chain file does not sign the main cert", issuerLineNum); errors.add(new ErrorEntry(ErrorType.SIGNATURE_ERROR, msg, true, null)); } else { msg = String.format("Error the cert at the top of the chain file does not sign the main cert"); errors.add(new ErrorEntry(ErrorType.SIGNATURE_ERROR, msg, true, null)); } } errors.addAll(crtSignErrors); } ArrayList<ErrorEntry> chainSignErrors = new ArrayList<ErrorEntry>(); for (int i = 1; i < imdCrts.size(); i++) { X509CertificateObject subjectCrt = imdCrts.get(i - 1); X509CertificateObject issuerCrt = imdCrts.get(i); List<ErrorEntry> crtSignErrors = CertUtils.verifyIssuerAndSubjectCert(issuerCrt, subjectCrt, false); if (ErrorEntry.hasFatal(crtSignErrors)) { if (lineMap.containsKey(issuerCrt) && lineMap.containsKey(subjectCrt)) { int issuerLineNum = lineMap.get(issuerCrt).intValue(); int subjectLineNum = lineMap.get(subjectCrt).intValue(); msg = String.format("Error chain out of order Certificate at line %d does not sign crt at line %d", issuerLineNum, subjectLineNum); errors.add(new ErrorEntry(ErrorType.SIGNATURE_ERROR, msg, true, null)); } else { msg = String.format("Error chain out of order"); errors.add(new ErrorEntry(ErrorType.SIGNATURE_ERROR, msg, true, null)); } } errors.addAll(crtSignErrors); } // If there where no errors build the full ZCF object if (!ErrorEntry.hasFatal(errors)) { StringBuilder sb = new StringBuilder(4096); try { zkey = PemUtils.toPemString(userKey); } catch (PemException ex) { errors.add(new ErrorEntry(ErrorType.COULDENT_ENCODE_KEY, "Error encodeing users key", true, ex)); } try { sb.append(PemUtils.toPemString(userCrt)); } catch (PemException ex) { errors.add(new ErrorEntry(ErrorType.COULDENT_ENCODE_CERT, "Error encodeing users Crt", true, ex)); } for (X509CertificateObject x509obj : imdCrts) { try { String x509Str = PemUtils.toPemString(x509obj); sb.append(x509Str); } catch (PemException ex) { if (lineMap.containsKey(x509obj)) { int x509lineNum = lineMap.get(x509obj).intValue(); msg = String.format("Error encodeing chain crt at line %d", x509lineNum); errors.add(new ErrorEntry(ErrorType.COULDENT_ENCODE_CERT, msg, true, ex)); } else { errors.add(new ErrorEntry(ErrorType.COULDENT_ENCODE_CERT, "Error encodeing chain crt", true, ex)); } } } // Also append a NO_PATH_TO_ROOT error if the cert has no known root if (!ErrorEntry.hasFatal(errors)) { Set<X509CertificateObject> imdSet = new HashSet<X509CertificateObject>(imdCrts); X509PathBuilder<X509CertificateObject> pathBuilder = new X509PathBuilder<X509CertificateObject>(roots, imdSet); try { X509BuiltPath<X509CertificateObject> builtPath = pathBuilder.buildPath(userCrt); } catch (X509PathBuildException ex) { // Make NO PATH to ROOT a non fatal error errors.add(new ErrorEntry(ErrorType.NO_PATH_TO_ROOT, "Chain has no path to root", false, ex)); } } // If there are still no errors we can set the key and crt if (!ErrorEntry.hasFatal(errors)) { zcrt = sb.toString(); zcf.setPrivate_key(zkey); zcf.setPublic_cert(zcrt); } } return zcf; } public ZeusCrtFile buildZeusCrtFile(String keyStr, String userCrtStr, String intermediates, Date date) { ZeusCrtFile zcf = new ZeusCrtFile(); List<ErrorEntry> errors = zcf.getErrors(); List<PemBlock> blocks; KeyPair kp = null; X509CertificateObject userCrt = null; Object obj; // Read Key kp = parseKey(keyStr, errors); userCrt = parseCert(userCrtStr, errors); if (userCrt != null) { if (CertUtils.isCertExpired(userCrt, date)) { Date after = userCrt.getNotAfter(); String errorMsg = invalidDateMessage("User cert expired on", after); errors.add(new ErrorEntry(ErrorType.EXPIRED_CERT, errorMsg, true, null)); } if (CertUtils.isCertPremature(userCrt, date)) { Date before = userCrt.getNotBefore(); String errorMsg = invalidDateMessage("User cert isn't valid till", before); errors.add(new ErrorEntry(ErrorType.PREMATURE_CERT, errorMsg, false, null)); } } // Check key and cert match if (kp != null && userCrt != null) { PublicKey userKey = userCrt.getPublicKey(); List<ErrorEntry> keyCrtErrors = CertUtils.validateKeyMatchesCert((JCERSAPublicKey) kp.getPublic(), userCrt); if (keyCrtErrors.size() > 0) { errors.addAll(keyCrtErrors); return zcf; } } // Retrieve Intermediates. Set<X509CertificateObject> intermediateCerts = new HashSet<X509CertificateObject>(); if (intermediates != null) { intermediateCerts = parseIntermediateCerts(intermediates, errors); } if (userCrt != null) { X509PathBuilder<X509CertificateObject> pathBuilder = new X509PathBuilder<X509CertificateObject>(roots, intermediateCerts); X509BuiltPath<X509CertificateObject> builtPath; try { builtPath = pathBuilder.buildPath(userCrt, date); } catch (X509PathBuildException ex) { errors.add(new ErrorEntry(ErrorType.NO_PATH_TO_ROOT, "No Path to root", false, ex)); return zcf; } StringBuilder zcrtString = new StringBuilder(RsaConst.PAGESIZE); List<ErrorEntry> certWriteErrors = new ArrayList<ErrorEntry>(); for (X509CertificateObject x509obj : builtPath.getPath()) { try { String x509String = PemUtils.toPemString(x509obj); zcrtString.append(x509String); } catch (PemException ex) { certWriteErrors.add(new ErrorEntry(ErrorType.COULDENT_ENCODE_CERT, "Coulden't encode intermediate certififacte", true, ex)); } if (certWriteErrors.size() > 0) { errors.addAll(certWriteErrors); return zcf; } } zcf.setPublic_cert(zcrtString.toString()); } if (kp != null) { try { String privKey = PemUtils.toPemString(kp); zcf.setPrivate_key(privKey); } catch (PemException ex) { errors.add(new ErrorEntry(ErrorType.COULDENT_ENCODE_KEY, ex.getMessage(), true, ex)); return zcf; } } return zcf; } public Set<X509CertificateObject> getRoots() { return roots; } public void setRoots(Set<X509CertificateObject> roots) { this.roots = roots; } public static KeyPair parseKey(String keyIn, List<ErrorEntry> errors) { KeyPair kp = null; List<PemBlock> blocks = PemUtils.parseMultiPem(keyIn); Object obj; if (blocks.size() < 1) { errors.add(new ErrorEntry(ErrorType.UNREADABLE_KEY, KEYREQUIRED, true, null)); return kp; } if (blocks.size() > 1) { errors.add(new ErrorEntry(ErrorType.UNREADABLE_KEY, "Multiple pem blocks used in Key", true, null)); return kp; } obj = blocks.get(0).getDecodedObject(); if (obj == null) { errors.add(new ErrorEntry(ErrorType.UNREADABLE_KEY, "Unable to parse pemblock to RSA Key", true, null)); return kp; } if (obj instanceof JCERSAPrivateCrtKey) { try { obj = HackedProviderAccessor.newKeyPair((JCERSAPrivateCrtKey) obj); } catch (InvalidKeySpecException ex) { errors.add(new ErrorEntry(ErrorType.UNREADABLE_KEY, "Error while attempting to convert key from PKCS8 to PKCS1", true, ex)); } } if (!(obj instanceof KeyPair)) { String msg = String.format("%s keyobject was an unstance of %s but was expecting a %s", ERRORDECODINGKEY, obj.getClass().getName(), className(KeyPair.class)); errors.add(new ErrorEntry(ErrorType.UNREADABLE_KEY, msg, true, null)); return kp; } kp = (KeyPair) obj; if (!(kp.getPublic() instanceof JCERSAPublicKey)) { String msg = String.format("%s Error decoding public portion of key. Objected decoded to class %s but was expecting %s", ERRORDECODINGKEY, obj.getClass().getName(), className(JCERSAPublicKey.class)); errors.add(new ErrorEntry(ErrorType.UNREADABLE_KEY, msg, true, null)); kp = null; return kp; } if (!(kp.getPrivate() instanceof JCERSAPrivateCrtKey)) { String msg = String.format("%s Error decoding private portion of key. Object decoded to class %s but was expecting %s", ERRORDECODINGKEY, obj.getClass().getName(), className(JCERSAPrivateCrtKey.class)); errors.add(new ErrorEntry(ErrorType.UNREADABLE_KEY, msg, true, null)); kp = null; return kp; } return kp; } public static X509CertificateObject parseCert(String certIn, List<ErrorEntry> errors) { X509CertificateObject x509obj = null; List<PemBlock> blocks = PemUtils.parseMultiPem(certIn); Object obj; if (blocks.size() < 1) { errors.add(new ErrorEntry(ErrorType.UNREADABLE_CERT, CERTREQUIRED, true, null)); return x509obj; } if (blocks.size() > 1) { errors.add(new ErrorEntry(ErrorType.UNREADABLE_CERT, "userCrt contains more then one pem block", true, null)); return x509obj; } obj = blocks.get(0).getDecodedObject(); if ((obj == null) || !(obj instanceof X509CertificateObject)) { String msg = String.format("%s Certificate decoded to class %s but was expecting %s", ERRORDECODINGCERT, obj.getClass().getName(), className(X509CertificateObject.class)); errors.add(new ErrorEntry(ErrorType.UNREADABLE_CERT, msg, true, null)); return x509obj; } x509obj = (X509CertificateObject) obj; return x509obj; } public static Set<X509CertificateObject> parseIntermediateCerts(String intermediates, List<ErrorEntry> errors) { ResponseWithExcpetions<Set<X509CertificateObject>> resp = X509ReaderWriter.readSet(intermediates); Set<X509CertificateObject> intermediateCerts = resp.getReturnObject(); for (Throwable th : resp.getExceptions()) { errors.add(new ErrorEntry(ErrorType.UNREADABLE_CERT, th.getMessage(), false, th)); } return intermediateCerts; } private static String invalidDateMessage(String premsg, Date dateEdge) { String edge = StaticHelpers.getDateString(dateEdge); String msg = String.format("%s %s", premsg, edge); return msg; } private static List<X509CertificateObject> decodeImd(Map<X509CertificateObject, Integer> lineMap, String imdStr, ZeusCrtFile zcf) { List<ErrorEntry> errors = zcf.getErrors(); ErrorEntry errorEntry; List<X509CertificateObject> imdCrts = new ArrayList<X509CertificateObject>(); X509CertificateObject x509obj; List<PemBlock> blocks = PemUtils.parseMultiPem(imdStr); String msg; if (imdStr == null || imdStr.length() == 0) { return imdCrts; } for (PemBlock block : blocks) { Object obj = block.getDecodedObject(); if (obj == null) { msg = String.format("Empty object at line starting at line %d", block.getLineNum()); errorEntry = new ErrorEntry(ErrorType.UNREADABLE_CERT, msg, true, null); errors.add(errorEntry); continue; } if (!(obj instanceof X509CertificateObject)) { msg = String.format("Object at line %d decoded to class %s but was expecting %s", block.getLineNum(), obj.getClass().getName(), className(X509CertificateObject.class)); errorEntry = new ErrorEntry(ErrorType.UNREADABLE_CERT, msg, true, null); errors.add(errorEntry); continue; } x509obj = (X509CertificateObject) obj; imdCrts.add(x509obj); lineMap.put(x509obj, new Integer(block.getLineNum())); } return imdCrts; } private static X509CertificateObject decodeCrt(String crtStr, ZeusCrtFile zcf) { X509CertificateObject crt; ErrorEntry errorEntry; List<ErrorEntry> errors = zcf.getErrors(); Object obj; try { obj = PemUtils.fromPemString(crtStr); if (!(obj instanceof X509CertificateObject)) { String msg = String.format("crt object decoded to class %s but was expecting X509CertificateObject", obj.getClass().getName(), className(X509CertificateObject.class)); errorEntry = new ErrorEntry(ErrorType.UNREADABLE_CERT, msg, true, null); errors.add(errorEntry); return null; } crt = (X509CertificateObject) obj; return crt; } catch (PemException ex) { errorEntry = new ErrorEntry(ErrorType.UNREADABLE_CERT, "Unable to read userCrt", true, ex); errors.add(errorEntry); return null; } } private static String className(Class clazz) { if (clazz == null) { return null; } return clazz.getName(); } }