[Swift-commit] cog r3447
swift at ci.uchicago.edu
swift at ci.uchicago.edu
Mon Aug 6 23:40:04 CDT 2012
------------------------------------------------------------------------
r3447 | hategan | 2012-08-06 23:37:37 -0500 (Mon, 06 Aug 2012) | 1 line
added an automatic CA/user cert/proxy generator which gets used when SSH is the boot handler
------------------------------------------------------------------------
Index: modules/provider-coaster/src/org/globus/cog/abstraction/impl/execution/coaster/AutoCA.java
===================================================================
--- modules/provider-coaster/src/org/globus/cog/abstraction/impl/execution/coaster/AutoCA.java (revision 0)
+++ modules/provider-coaster/src/org/globus/cog/abstraction/impl/execution/coaster/AutoCA.java (revision 3447)
@@ -0,0 +1,354 @@
+//----------------------------------------------------------------------
+//This code is developed as part of the Java CoG Kit project
+//The terms of the license can be found at http://www.cogkit.org/license
+//This message may not be removed or altered.
+//----------------------------------------------------------------------
+
+/*
+ * Created on Aug 5, 2012
+ */
+package org.globus.cog.abstraction.impl.execution.coaster;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEREncodable;
+import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.globus.cog.util.concurrent.FileLock;
+import org.globus.gsi.CertUtil;
+import org.globus.gsi.GSIConstants;
+import org.globus.gsi.GlobusCredential;
+import org.globus.gsi.OpenSSLKey;
+import org.globus.gsi.X509ExtensionSet;
+import org.globus.gsi.bc.BouncyCastleCertProcessingFactory;
+import org.globus.gsi.bc.BouncyCastleOpenSSLKey;
+import org.globus.util.Util;
+
+/**
+ * A class to automatically generate:
+ * - a CA key/cert pair
+ * - a user key/cert pair signed by the above CA
+ * - a proxy certificate signed by the above user key
+ *
+ * This is heavily inspired by http://code.google.com/p/java-simple-ca/
+ *
+ */
+public class AutoCA {
+ public static final Logger logger = Logger.getLogger(AutoCA.class);
+
+ public static final String CA_DIR = System.getProperty("user.home") + File.separator
+ + ".globus" + File.separator + "coasters";
+ public static final String CA_CRT_NAME_PREFIX = "CAcert";
+ public static final String CA_KEY_NAME_PREFIX = "CAkey";
+ public static final String USER_CRT_NAME_PREFIX = "usercert";
+ public static final String USER_KEY_NAME_PREFIX = "userkey";
+ public static final String PROXY_NAME_PREFIX = "proxy";
+
+ public static final String CA_CERT_ALGORITHM = "RSA";
+ public static final int CA_CERT_BITS = 1024;
+ public static final String CA_CERT_DN = "C=US,O=JavaCoG,OU=AutoCA,CN=Certificate Authority";
+ public static final String USER_CERT_DN = "C=US,O=JavaCoG,OU=AutoCA,CN=User";
+ public static final String CA_CERT_SIGNATURE_ALGORITHM = "SHA1WithRSAEncryption";
+
+ public static final long WEEK_IN_MS = 1000 * 3600 * 24 * 7;
+ public static final long CA_CERT_LIFETIME = 2 * WEEK_IN_MS;
+ public static final long MIN_CA_CERT_LIFETIME_LEFT = WEEK_IN_MS;
+
+ public static final int ID_BYTES = 4;
+
+ private static AutoCA instance;
+ private Info info;
+ private X509Certificate cert;
+ private X509V3CertificateGenerator gen;
+
+ public synchronized static AutoCA getInstance() {
+ if (instance == null) {
+ instance = new AutoCA();
+ }
+ return instance;
+ }
+
+ public AutoCA() {
+ gen = new X509V3CertificateGenerator();
+ }
+
+ public static class Info {
+ public final String proxyPath, caCertPath;
+
+ public Info(File proxyFile, File caCertFile) {
+ this(proxyFile.getAbsolutePath(), caCertFile.getAbsolutePath());
+ }
+
+ public Info(String proxyPath, String caCertPath) {
+ this.proxyPath = proxyPath;
+ this.caCertPath = caCertPath;
+ }
+ }
+
+ public Info createProxy() throws IOException, GeneralSecurityException {
+ ensureCACertsExist();
+ return info;
+ }
+
+ private void ensureCACertsExist() throws IOException, GeneralSecurityException {
+ // delete expired CAs, make a new one if the existing ones don't have
+ // at least MIN_CA_LIFETIME_LEFT
+ FileLock fl = new FileLock(CA_DIR);
+ try {
+ fl.lock();
+ }
+ catch (Exception e) {
+ logger.warn("Failed to lock CA dir", e);
+ }
+ try {
+ File[] certs = discoverProxies();
+ long now = System.currentTimeMillis();
+ long maxExpirationTime = 0;
+
+ for (File c : certs) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Checking certificate " + c);
+ }
+ X509Certificate cert = CertUtil.loadCertificate(c.getAbsolutePath());
+ long certExpirationTime = cert.getNotAfter().getTime();
+ if (certExpirationTime < now) {
+ // delete cert and key
+ if (logger.isInfoEnabled()) {
+ logger.info("Certificate expired. Deleting.");
+ }
+ deleteAll(getIndex(c));
+ }
+ if (certExpirationTime > maxExpirationTime) {
+ maxExpirationTime = certExpirationTime;
+ int index = getIndex(c);
+ this.info = new Info(makeFile(PROXY_NAME_PREFIX, index), makeFile(CA_CRT_NAME_PREFIX, index));
+ this.cert = cert;
+ }
+ }
+
+ if (now + MIN_CA_CERT_LIFETIME_LEFT > maxExpirationTime) {
+ int index = discoverNextIndex();
+ this.info = new Info(makeFile(PROXY_NAME_PREFIX, index), makeFile(CA_CRT_NAME_PREFIX, index));
+ if (logger.isInfoEnabled()) {
+ logger.info("No certificates with enough lifetime. Creating new certificate: " + info.proxyPath);
+ }
+ this.cert = createAll(index);
+ }
+ else {
+ if (logger.isInfoEnabled()) {
+ logger.info("Using certificate " + info.proxyPath + " with expiration date " + this.cert.getNotAfter());
+ }
+ }
+ }
+ finally {
+ fl.unlock();
+ }
+ }
+
+ private File makeFile(String prefix, int index) {
+ return new File(CA_DIR + File.separator + prefix + ".pem." + index);
+ }
+
+ private static final String[] ALL_NAMES = new String[] {CA_CRT_NAME_PREFIX,
+ CA_KEY_NAME_PREFIX, USER_CRT_NAME_PREFIX, USER_KEY_NAME_PREFIX, PROXY_NAME_PREFIX};
+ private void deleteAll(int index) {
+ for (String name : ALL_NAMES) {
+ new File(CA_DIR + File.separator + name + ".pem." + index).delete();
+ }
+ }
+
+ private int getIndex(File c) {
+ return Integer.parseInt(c.getName().substring(c.getName().lastIndexOf('.') + 1));
+ }
+
+ private int discoverNextIndex() throws GeneralSecurityException {
+ for (int i = 0; i < 10; i++) {
+ File f = new File(CA_DIR + File.separator + PROXY_NAME_PREFIX + ".pem." + i);
+ if (!f.exists()) {
+ return i;
+ }
+ }
+ throw new GeneralSecurityException("No slots found to save CA certificate");
+ }
+
+ private File[] discoverProxies() {
+ return new File(CA_DIR).listFiles(new FileFilter() {
+ public boolean accept(File f) {
+ return f.isFile() && f.getName().matches(PROXY_NAME_PREFIX + "\\.pem\\.[0-9]");
+ }
+ });
+ }
+
+ private X509Certificate createAll(int index) throws GeneralSecurityException, IOException {
+ logger.info("Generating CA key pair");
+ KeyPair ca = CertUtil.generateKeyPair(CA_CERT_ALGORITHM, CA_CERT_BITS);
+ OpenSSLKey caKey = new BouncyCastleOpenSSLKey(ca.getPrivate());
+ logger.info("Self-signing CA certificate");
+ X509Certificate caCert = genCert(ca.getPrivate(), ca.getPublic(), CA_CERT_DN, CA_CERT_DN, null);
+
+ logger.info("Generating user key pair");
+ KeyPair user = CertUtil.generateKeyPair(CA_CERT_ALGORITHM, CA_CERT_BITS);
+ OpenSSLKey userKey = new BouncyCastleOpenSSLKey(user.getPrivate());
+ logger.info("Signing user certificate");
+ X509Certificate userCert = genCert(ca.getPrivate(), user.getPublic(), USER_CERT_DN, CA_CERT_DN,
+ createExtensions(ca.getPublic(), user.getPublic()));
+ logger.info("Generating proxy certificate");
+ GlobusCredential proxy = makeProxy(user, userCert);
+
+ try {
+ logger.info("Writing keys, certificates, and proxy");
+ writeKey(caKey, makeFile(CA_KEY_NAME_PREFIX, index));
+ writeCert(caCert, makeFile(CA_CRT_NAME_PREFIX, index));
+ writeKey(userKey, makeFile(USER_KEY_NAME_PREFIX, index));
+ writeCert(userCert, makeFile(USER_CRT_NAME_PREFIX, index));
+ writeProxy(proxy, makeFile(PROXY_NAME_PREFIX, index));
+ }
+ catch (GeneralSecurityException e) {
+ deleteAll(index);
+ throw e;
+ }
+ return cert;
+ }
+
+ private Map<DERObjectIdentifier, DEREncodable> createExtensions(PublicKey caPub, PublicKey userPub) throws IOException {
+ Map<DERObjectIdentifier, DEREncodable> ext = new HashMap<DERObjectIdentifier, DEREncodable>();
+
+ // not a CA
+ ext.put(X509Extensions.BasicConstraints, new BasicConstraints(false));
+ // obvious
+ ext.put(X509Extensions.KeyUsage, new KeyUsage(KeyUsage.dataEncipherment | KeyUsage.digitalSignature));
+ ext.put(X509Extensions.SubjectKeyIdentifier, getSubjectKeyInfo(userPub));
+ ext.put(X509Extensions.AuthorityKeyIdentifier, getAuthorityKeyIdentifier(caPub));
+
+ return ext;
+ }
+
+
+
+ private DEREncodable getAuthorityKeyIdentifier(PublicKey caPub) throws IOException {
+ DERObject derKey = new ASN1InputStream(caPub.getEncoded()).readObject();
+ return new AuthorityKeyIdentifier(new SubjectPublicKeyInfo((ASN1Sequence) derKey));
+ }
+
+ private DEREncodable getSubjectKeyInfo(PublicKey userPub) throws IOException {
+ // convert key to bouncy castle format and get subject key identifier
+ DERObject derKey = new ASN1InputStream(userPub.getEncoded()).readObject();
+ return new SubjectKeyIdentifier(new SubjectPublicKeyInfo((ASN1Sequence) derKey));
+ }
+
+ private void signCert(X509Certificate userCert, OpenSSLKey caKey, X509Certificate caCert) {
+ gen.reset();
+ }
+
+ private GlobusCredential makeProxy(KeyPair kp, X509Certificate issuerCert) throws GeneralSecurityException {
+ BouncyCastleCertProcessingFactory factory = BouncyCastleCertProcessingFactory.getDefault();
+ KeyPair newKeyPair = CertUtil.generateKeyPair(CA_CERT_ALGORITHM, CA_CERT_BITS);
+
+ return factory.createCredential(new X509Certificate[] { issuerCert },
+ kp.getPrivate(), CA_CERT_BITS, (int) (CA_CERT_LIFETIME / 1000), GSIConstants.DELEGATION_FULL,
+ (X509ExtensionSet) null);
+ }
+
+ private void writeProxy(GlobusCredential proxy, File f) throws GeneralSecurityException {
+ try {
+ OutputStream fw = openStream(f);
+ try {
+ proxy.save(fw);
+ }
+ finally {
+ fw.close();
+ }
+ }
+ catch (Exception e) {
+ throw new GeneralSecurityException("Failed to save proxy certificate", e);
+ }
+ }
+
+ private OutputStream openStream(File f) throws SecurityException, IOException {
+ String path = f.getAbsolutePath();
+ File file = Util.createFile(path);
+ // set read only permissions
+ if (!Util.setOwnerAccessOnly(path)) {
+ logger.warn("Failed to set permissions on " + path);
+ }
+ return new FileOutputStream(file);
+ }
+
+ private void writeCert(X509Certificate cert, File f) throws GeneralSecurityException {
+ try {
+ OutputStream fw = openStream(f);
+ CertUtil.writeCertificate(fw, cert);
+ }
+ catch (Exception e) {
+ throw new GeneralSecurityException("Failed to save X509 certificate", e);
+ }
+ }
+
+ private X509Certificate genCert(PrivateKey signKey, PublicKey pubKey, String subjectDN, String issuerDN,
+ Map<DERObjectIdentifier, DEREncodable> ext) throws GeneralSecurityException {
+ gen.reset();
+ Date now = new Date();
+
+ gen.setSerialNumber(BigInteger.valueOf(0));
+ gen.setNotBefore(now);
+ gen.setNotAfter(new Date(now.getTime() + CA_CERT_LIFETIME));
+ gen.setIssuerDN(new X509Name(issuerDN));
+ gen.setSubjectDN(new X509Name(subjectDN));
+ gen.setPublicKey(pubKey);
+ gen.setSignatureAlgorithm(CA_CERT_SIGNATURE_ALGORITHM);
+
+ if (ext != null) {
+ for (Map.Entry<DERObjectIdentifier, DEREncodable> e : ext.entrySet()) {
+ gen.addExtension(e.getKey(), false, e.getValue());
+ }
+ }
+
+ try {
+ X509Certificate cert = gen.generateX509Certificate(signKey, "BC", new SecureRandom());
+ return cert;
+ }
+ catch (Exception e) {
+ throw new GeneralSecurityException("Failed to create X509 certificate", e);
+ }
+ }
+
+ private void writeKey(OpenSSLKey key, File f) throws GeneralSecurityException {
+ try {
+ OutputStream keyStream = openStream(f);
+ try {
+ key.writeTo(keyStream);
+ }
+ finally {
+ keyStream.close();
+ }
+ }
+ catch (Exception e) {
+ throw new GeneralSecurityException("Failed to save CA private key", e);
+ }
+ }
+}
Index: modules/provider-coaster/src/org/globus/cog/abstraction/impl/execution/coaster/JobSubmissionTaskHandler.java
===================================================================
--- modules/provider-coaster/src/org/globus/cog/abstraction/impl/execution/coaster/JobSubmissionTaskHandler.java (revision 3446)
+++ modules/provider-coaster/src/org/globus/cog/abstraction/impl/execution/coaster/JobSubmissionTaskHandler.java (working copy)
@@ -19,7 +19,6 @@
import org.globus.cog.abstraction.coaster.service.local.LocalRequestManager;
import org.globus.cog.abstraction.coaster.service.local.LocalService;
import org.globus.cog.abstraction.impl.common.AbstractDelegatedTaskHandler;
-import org.globus.cog.abstraction.impl.common.AbstractionFactory;
import org.globus.cog.abstraction.impl.common.StatusImpl;
import org.globus.cog.abstraction.impl.common.task.ExecutionServiceImpl;
import org.globus.cog.abstraction.impl.common.task.IllegalSpecException;
@@ -32,7 +31,6 @@
import org.globus.cog.abstraction.impl.common.task.TaskSubmissionException;
import org.globus.cog.abstraction.interfaces.ExecutionService;
import org.globus.cog.abstraction.interfaces.JobSpecification;
-import org.globus.cog.abstraction.interfaces.SecurityContext;
import org.globus.cog.abstraction.interfaces.Service;
import org.globus.cog.abstraction.interfaces.Status;
import org.globus.cog.abstraction.interfaces.Task;
@@ -117,7 +115,6 @@
ChannelException {
if (autostart) {
String provider = getBootHandlerProvider(task);
- cred = getCredentials(task);
url = ServiceManager.getDefault().reserveService(task, provider);
task.getService(0).setAttribute("coaster-url", url);
}
@@ -217,20 +214,6 @@
return js.getEnvironmentVariable("JAVA_HOME");
}
- private GSSCredential getCredentials(Task task) throws InvalidSecurityContextException {
- SecurityContext sc = task.getService(0).getSecurityContext();
- if (sc == null || sc.getCredentials() == null) {
- try {
- sc = AbstractionFactory.getSecurityContext("gt2", task.getService(0).getServiceContact());
- task.getService(0).setSecurityContext(sc);
- }
- catch (Exception e) {
- throw new InvalidSecurityContextException(e);
- }
- }
- return (GSSCredential) sc.getCredentials();
- }
-
public void errorReceived(Command cmd, String msg, Exception t) {
Status s = new StatusImpl(Status.FAILED, msg, t);
getTask().setStatus(s);
Index: modules/provider-coaster/src/org/globus/cog/abstraction/impl/execution/coaster/ServiceManager.java
===================================================================
--- modules/provider-coaster/src/org/globus/cog/abstraction/impl/execution/coaster/ServiceManager.java (revision 3446)
+++ modules/provider-coaster/src/org/globus/cog/abstraction/impl/execution/coaster/ServiceManager.java (working copy)
@@ -14,6 +14,7 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
+import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
@@ -53,6 +54,7 @@
import org.globus.cog.karajan.workflow.service.channels.KarajanChannel;
import org.globus.cog.karajan.workflow.service.commands.Command;
import org.globus.cog.karajan.workflow.service.commands.Command.Callback;
+import org.globus.common.CoGProperties;
import org.ietf.jgss.GSSCredential;
public class ServiceManager implements StatusListener {
@@ -164,11 +166,20 @@
try {
startLocalService();
final Task t = buildTask(service);
- setSecurityContext(t, sc, bootHandlerProvider);
+
t.addStatusListener(this);
if (logger.isDebugEnabled()) {
logger.debug("Starting coaster service on " + contact + ". Task is " + t);
}
+
+ boolean ssh = "ssh".equalsIgnoreCase(bootHandlerProvider);
+
+ if (ssh) {
+ setupGSIProxy();
+ }
+
+ setSecurityContext(t, sc, bootHandlerProvider);
+
boolean local = "local".equals(bootHandlerProvider);
if (LOCALJVM || (LOCALJVM_WHEN_LOCAL && local)) {
final String ls = getLocalServiceURL();
@@ -200,6 +211,12 @@
}
}
+ private void setupGSIProxy() throws IOException, GeneralSecurityException {
+ AutoCA.Info result = AutoCA.getInstance().createProxy();
+ CoGProperties.getDefault().setProxyFile(result.proxyPath);
+ CoGProperties.getDefault().setCaCertLocations(result.caCertPath);
+ }
+
private void setSecurityContext(Task t, SecurityContext sc, String provider)
throws InvalidProviderException, ProviderMethodException {
t.getService(0).setSecurityContext(AbstractionFactory.getSecurityContext(provider, t.getService(0).getServiceContact()));
More information about the Swift-commit
mailing list