diff --git a/src/main/java/grith/jgrith/credential/KerberosCredential.java b/src/main/java/grith/jgrith/credential/KerberosCredential.java new file mode 100644 index 0000000..aab750c --- /dev/null +++ b/src/main/java/grith/jgrith/credential/KerberosCredential.java @@ -0,0 +1,181 @@ +package grith.jgrith.credential; + +import grith.jgrith.cred.AbstractCred; +import grith.jgrith.cred.Cred; +import grith.jgrith.credential.Credential.PROPERTY; +import grith.jgrith.kerberos.SimpleMyProxyClient; + +import java.io.IOException; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.Security; +import java.util.HashMap; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.ietf.jgss.GSSCredential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class KerberosCredential extends AbstractCred implements Cred { + + public static final String DEFAULT_SERVICE_NAME = "myproxy"; + public static final String DEFAULT_REALM = "NESI.ORG.NZ"; + public static final String DEFAULT_HOST = "myproxyca.nesi.org.nz"; + + public static final int DEFAULT_KERBEROS_LIFETIME = 8 * 3600; + public static final int MYPROXY_PORT = 7512; + public static final String JAAS_SERVICE_NAME = "JaasGrisu"; + + static final Logger myLogger = LoggerFactory + .getLogger(KerberosCredential.class.getName()); + + private String principalName; + private String myproxyServiceName; + private String myproxycaDN; + private String myproxycaRealm; + private String password; + + static { + String jaasConf = KerberosCredential.class.getResource("/jaas.conf").toExternalForm(); + System.setProperty("java.security.auth.login.config", jaasConf); + System.setProperty("java.security.krb5.conf","/home/yhal003/projects/jgrith/target/classes/krb5.conf"); + System.setProperty("sun.security.krb5.debug", "true"); + } + + public KerberosCredential(String principalName, String proxyServiceName, + String myproxycaDN, String myproxycaRealm, String password) { + this.principalName = principalName; + this.myproxyServiceName = proxyServiceName; + this.myproxycaDN = myproxycaDN; + this.myproxycaRealm = myproxycaRealm; + this.password = password; + } + + public KerberosCredential(String principalName, String myproxycaDN, + String myproxycaRealm, String password) { + this(principalName, DEFAULT_SERVICE_NAME, myproxycaDN, myproxycaRealm, + password); + } + + public KerberosCredential(String principalName, String password){ + this(principalName, DEFAULT_SERVICE_NAME, DEFAULT_HOST, DEFAULT_REALM, password); + } + + public static void main(String[] args) { + System.out.println("test"); + Security.addProvider(new BouncyCastleProvider()); + KerberosCredential kc = new KerberosCredential( + "yhal003@NESI.ORG.NZ", "*****"); + GSSCredential cred = kc.createGSSCredentialInstance(); + } + + @Override + public GSSCredential createGSSCredentialInstance() { + + try { + LoginContext lc = new LoginContext(JAAS_SERVICE_NAME, + new SimpleCallbackHandler()); + lc.login(); + System.out.println(lc.getSubject()); + GSSCredential result = Subject.doAs(lc.getSubject(), + new GetCertificateAction()); + + return result; + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + // TODO Auto-generated method stub + return null; + } + + @Override + protected void initCred(Map config) { + + } + + @Override + public boolean isRenewable() { + // not sure how to do that yet. + return false; + } + + class SimpleCallbackHandler implements CallbackHandler { + + @Override + public void handle(Callback[] cs) throws IOException, + UnsupportedCallbackException { + for (Callback c : cs) { + if (c instanceof NameCallback) { + ((NameCallback) c).setName(principalName); + } else if (c instanceof PasswordCallback) { + ((PasswordCallback) c).setPassword(password.toCharArray()); + } + } + + } + + } + + class GetCertificateAction implements + PrivilegedExceptionAction { + + @Override + public GSSCredential run() throws PrivilegedActionException { + + try { + + SaslClient client = getSaslClient(); + + SimpleMyProxyClient myproxy = SimpleMyProxyClient.create( + myproxycaDN, MYPROXY_PORT); + + myproxy.connect(); + // myproxy.sendGetCommand(principalName, 1000000); + myproxy.sendGetCommand(getUsername(), 10000); + myproxy.doSasl(client); + GSSCredential gssCred = myproxy.getCredential(); + + return gssCred; + + // TODO Auto-generated method stub + } catch (Exception sax) { + sax.printStackTrace(); + myLogger.error("cannot create kerberos credential", sax); + throw new PrivilegedActionException(sax); + } + } + + } + + private String getUsername() { + if (principalName.contains(myproxycaRealm)){ + return principalName.replace("@" + myproxycaRealm, ""); + } + return principalName; + } + + private SaslClient getSaslClient() throws SaslException { + + final Map map = new HashMap(); + map.put(Sasl.QOP, "auth"); + final SaslClient client = Sasl.createSaslClient( + new String[] { "GSSAPI" }, getUsername(), myproxyServiceName, + myproxycaDN, map, null); + return client; + } + +} diff --git a/src/main/java/grith/jgrith/kerberos/MyProxyAuthException.java b/src/main/java/grith/jgrith/kerberos/MyProxyAuthException.java new file mode 100644 index 0000000..79d79e1 --- /dev/null +++ b/src/main/java/grith/jgrith/kerberos/MyProxyAuthException.java @@ -0,0 +1,14 @@ +package grith.jgrith.kerberos; + +public class MyProxyAuthException extends Exception { + private static final long serialVersionUID = 1900255085586758233L; + + public MyProxyAuthException(Exception e){ + super(e); + } + + public MyProxyAuthException(String m){ + + } + +} diff --git a/src/main/java/grith/jgrith/kerberos/MyProxyProtocolException.java b/src/main/java/grith/jgrith/kerberos/MyProxyProtocolException.java new file mode 100644 index 0000000..f4c9628 --- /dev/null +++ b/src/main/java/grith/jgrith/kerberos/MyProxyProtocolException.java @@ -0,0 +1,11 @@ +package grith.jgrith.kerberos; + +// thrown when library cannot parse output from MyProxy CA +public class MyProxyProtocolException extends Exception { + private static final long serialVersionUID = 8573410994590112652L; + + public MyProxyProtocolException(Exception e){ + super(e); + } + +} diff --git a/src/main/java/grith/jgrith/kerberos/MyProxySSLFactoryUtil.java b/src/main/java/grith/jgrith/kerberos/MyProxySSLFactoryUtil.java new file mode 100644 index 0000000..c16fb0c --- /dev/null +++ b/src/main/java/grith/jgrith/kerberos/MyProxySSLFactoryUtil.java @@ -0,0 +1,114 @@ +package grith.jgrith.kerberos; + +import javax.net.ssl.SSLSession; + +import java.security.InvalidKeyException; +import java.security.KeyManagementException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.security.cert.X509Certificate; +import java.util.Date; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; + +import org.bouncycastle.jce.PKCS10CertificationRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author yhal003 + * This monstrosity is just to create ssl sockets that do not verify certificates. + * Will have to change it. + */ +public class MyProxySSLFactoryUtil { + + private static SSLSocketFactory factory; + + public static SSLSocketFactory getFactory(){ + return factory; + } + + static final Logger myLogger = LoggerFactory + .getLogger(MyProxySSLFactoryUtil.class.getName()); + + static { + try { + final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] certs, + String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, + String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + + } + } }; + + SSLContext sc = SSLContext.getInstance("TLS"); + HostnameVerifier hv = new HostnameVerifier() { + @Override + public boolean verify(String arg0, SSLSession arg1) { + return true; + } + + }; + + sc.init(new KeyManager[0], trustAllCerts, + new java.security.SecureRandom()); + factory = sc.getSocketFactory(); + } catch (Exception e) { + + } + } + + public static KeyPair generateKeyPair() { + + KeyPairGenerator keyPairGenerator = null; + try { + keyPairGenerator = KeyPairGenerator + .getInstance("RSA", "BC"); + } catch (Exception e) { + myLogger.error("cannot generate key pair",e); + return null; + } + keyPairGenerator.initialize(2048, new SecureRandom()); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + return keyPair; + } + + public static PKCS10CertificationRequest generateRequest(KeyPair keyPair){ + + X500Principal subjectName = new X500Principal( + "CN=myproxyca doesn't care about it anyway"); + + try { + PKCS10CertificationRequest kpGen = new PKCS10CertificationRequest( + "SHA512withRSA", subjectName, keyPair.getPublic(), + null, keyPair.getPrivate()); + return kpGen; + } catch (Exception e) { + myLogger.error("cannot generate certificate request", e); + return null; + } + } + + +} diff --git a/src/main/java/grith/jgrith/kerberos/SimpleMyProxyClient.java b/src/main/java/grith/jgrith/kerberos/SimpleMyProxyClient.java new file mode 100644 index 0000000..6b75f8f --- /dev/null +++ b/src/main/java/grith/jgrith/kerberos/SimpleMyProxyClient.java @@ -0,0 +1,245 @@ +package grith.jgrith.kerberos; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ProtocolException; +import java.net.Socket; +import java.security.KeyPair; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import javax.security.sasl.AuthenticationException; +import javax.security.sasl.SaslClient; + +import org.apache.commons.lang.ArrayUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.globus.gsi.GlobusCredential; +import org.globus.gsi.gssapi.GlobusGSSCredentialImpl; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SimpleMyProxyClient { + + private String hostname; + private int port; + private Socket socket; + + public final static int SUCCESS = 0; + public final static int FAILURE = 1; + public final static int AUTH_REQUIRED = 2; + + public final static String VERSION = "MYPROXYv2"; + + public final static byte[] START_SASL = new byte[] { 3, 0, 0, 0, 0, 0 }; + public final static byte[] CONTINUE_SASL = new byte[] { 3, 0, 0, 0 }; + public final static byte[] START_SESSION = new byte[] {0x30}; + + + static final Logger myLogger = LoggerFactory + .getLogger(SimpleMyProxyClient.class.getName()); + + + public static SimpleMyProxyClient create(String hostname, int port){ + SimpleMyProxyClient c = new SimpleMyProxyClient(hostname,port); + return c; + } + + private SimpleMyProxyClient(String hostname, int port){ + this.hostname = hostname; + this.port = port; + } + + public void connect() throws IOException { + this.socket = MyProxySSLFactoryUtil.getFactory().createSocket(this.hostname, this.port); + OutputStream out = socket.getOutputStream(); + + out.write(START_SESSION); + out.flush(); + } + + public void disconnect() { + try { + if (this.socket != null) { + socket.close(); + } + } catch (IOException io) { + myLogger.error("myproxy client could not disconnect ",io); + } + } + + private Socket getSocket() throws IOException { + if (socket == null ) { + throw new IOException("myproxy client not connected. use .connect() method "); + } + return this.socket; + } + + public int sendGetCommand(String username, long lifetime) throws IOException { + Map request = new HashMap(); + request.put("VERSION", VERSION); + request.put("COMMAND", "0"); + request.put("USERNAME", username); + request.put("PASSPHRASE", ""); + request.put("LIFETIME", "" + lifetime); + + OutputStream out = getSocket().getOutputStream(); + InputStream in = getSocket().getInputStream(); + + + out.write(packMyProxy(request)); + Map reply = unpackMyProxy(readReply()); + + return getResponse(reply); + + + } + + public void doSasl(SaslClient client) throws IOException, + AuthenticationException, ProtocolException { + OutputStream out = getSocket().getOutputStream(); + InputStream in = getSocket().getInputStream(); + + out.write(START_SASL); + out.flush(); + + byte[] buffer = new byte[2048]; + int length = in.read(buffer); + Map reply = unpackMyProxy(Arrays.copyOfRange(buffer, 0, + length)); + + try { + int response = Integer.parseInt(reply.get("RESPONSE")); + if (!reply.get("AUTHORIZATION_DATA").startsWith("SASL")) { + throw new ProtocolException("SASL not supported"); + } + System.out.println("got response"); + byte[] challenge = client.hasInitialResponse() ? client + .evaluateChallenge(new byte[] {}) : null; + byte[] saslRequest = ArrayUtils.addAll( + ArrayUtils.add("GSSAPI".getBytes(), (byte) 0), challenge); + + while (true) { + + sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder(); + sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder(); + + String inputEncodedString = encoder.encode(saslRequest); + byte[] inputEncoded = inputEncodedString.replace("\n", "") + .getBytes(); + byte[] completeSasl = ArrayUtils.addAll(CONTINUE_SASL, inputEncoded); + + out.write(completeSasl); + out.flush(); + + reply = unpackMyProxy(readReply()); + response = Integer.parseInt(reply.get("RESPONSE")); + + if (response != AUTH_REQUIRED) { + break; + } + String challengeString = reply.get("AUTHORIZATION_DATA") + .substring(5); + byte[] decodedChallenge = decoder.decodeBuffer(challengeString); + challenge = client.evaluateChallenge(decodedChallenge); + saslRequest = challenge; + + } + + if (response == SUCCESS) { + return; // success + } else { + throw new AuthenticationException("SASL auth failed"); + } + + } catch (NumberFormatException nx) { + throw new ProtocolException("Cannot parse myproxy RESPONSE status "); + } catch (NullPointerException ex) { + ex.printStackTrace(); + throw new ProtocolException( + "Cannot parse myproxy output. Are you sure " + + getSocket().getInetAddress().getHostName() + + " is myproxy server?"); + } + + } + + private void sendBytes(byte[] bs) throws IOException { + getSocket().getOutputStream().write(bs); + } + + private X509Certificate readCertificate() throws IOException, CertificateException { + InputStream in = getSocket().getInputStream(); + in.skip(1); + CertificateFactory cf = CertificateFactory.getInstance("X.509",new BouncyCastleProvider()); + X509Certificate cert = (X509Certificate)cf.generateCertificate(in); + return cert; + } + + + + private static byte[] packMyProxy(Map request){ + StringBuffer result = new StringBuffer(""); + for (String key: request.keySet()){ + result.append(key + "=" + request.get(key) + "\n"); + } + result.append('\00'); + + return result.toString().getBytes(); + } + + private static Map unpackMyProxy(byte[] response){ + final Map result = new HashMap(); + final String responseString = new String(response); + + for (String line: responseString.split("\n")){ + String[] props = line.split("="); + if (props.length == 2){ + result.put(props[0], props[1]); + } + } + return result; + } + + private int getResponse(Map reply) throws IOException { + try { + return Integer.parseInt(reply.get("RESPONSE")); + } catch (Exception n) { + throw new IOException("cannot parse output",n); + } + } + + private byte[] readReply() throws IOException { + InputStream in = getSocket().getInputStream(); + + ArrayList v = new ArrayList(); + byte b = 0; + do { + b = (byte) in.read(); + v.add(b); + } while (b != 0); + + return ArrayUtils.toPrimitive(v.toArray(new Byte[] {})); + } + + public GSSCredential getCredential() throws IOException, CertificateException, GSSException { + KeyPair kp = MyProxySSLFactoryUtil.generateKeyPair(); + byte[] request = MyProxySSLFactoryUtil.generateRequest(kp).getDEREncoded(); + sendBytes(ArrayUtils.add(request, (byte) 0)); + X509Certificate cert = readCertificate(); + + GlobusCredential cred = new GlobusCredential( + kp.getPrivate(), new X509Certificate[] { cert }); + GSSCredential gssCred = new GlobusGSSCredentialImpl(cred, + GSSCredential.INITIATE_AND_ACCEPT); + + return gssCred; + } +} diff --git a/src/main/resources/jaas.conf b/src/main/resources/jaas.conf new file mode 100644 index 0000000..7cc402d --- /dev/null +++ b/src/main/resources/jaas.conf @@ -0,0 +1,7 @@ +/** Login Configuration for the JaasAcn and + ** JaasAzn Applications + **/ + +JaasSample { + com.sun.security.auth.module.Krb5LoginModule required; +}; diff --git a/src/main/resources/krb5.conf b/src/main/resources/krb5.conf new file mode 100644 index 0000000..2c1710d --- /dev/null +++ b/src/main/resources/krb5.conf @@ -0,0 +1,137 @@ +[libdefaults] + default_realm = NESI.ORG.NZ + +# The following krb5.conf variables are only for MIT Kerberos. + krb4_config = /etc/krb.conf + krb4_realms = /etc/krb.realms + kdc_timesync = 1 + ccache_type = 4 + forwardable = true + proxiable = true + +# The following encryption type specification will be used by MIT Kerberos +# if uncommented. In general, the defaults in the MIT Kerberos code are +# correct and overriding these specifications only serves to disable new +# encryption types as they are added, creating interoperability problems. +# +# Thie only time when you might need to uncomment these lines and change +# the enctypes is if you have local software that will break on ticket +# caches containing ticket encryption types it doesn't know about (such as +# old versions of Sun Java). + +# default_tgs_enctypes = des3-hmac-sha1 +# default_tkt_enctypes = des3-hmac-sha1 +# permitted_enctypes = des3-hmac-sha1 + +# The following libdefaults parameters are only for Heimdal Kerberos. + v4_instance_resolve = false + v4_name_convert = { + host = { + rcmd = host + ftp = ftp + } + plain = { + something = something-else + } + } + fcc-mit-ticketflags = true + +[realms] + NESI.ORG.NZ = { + kdc = kerberos.nesi.org.nz + admin_server = kerberos.nesi.org.nz + } + ATHENA.MIT.EDU = { + kdc = kerberos.mit.edu:88 + kdc = kerberos-1.mit.edu:88 + kdc = kerberos-2.mit.edu:88 + admin_server = kerberos.mit.edu + default_domain = mit.edu + } + MEDIA-LAB.MIT.EDU = { + kdc = kerberos.media.mit.edu + admin_server = kerberos.media.mit.edu + } + ZONE.MIT.EDU = { + kdc = casio.mit.edu + kdc = seiko.mit.edu + admin_server = casio.mit.edu + } + MOOF.MIT.EDU = { + kdc = three-headed-dogcow.mit.edu:88 + kdc = three-headed-dogcow-1.mit.edu:88 + admin_server = three-headed-dogcow.mit.edu + } + CSAIL.MIT.EDU = { + kdc = kerberos-1.csail.mit.edu + kdc = kerberos-2.csail.mit.edu + admin_server = kerberos.csail.mit.edu + default_domain = csail.mit.edu + krb524_server = krb524.csail.mit.edu + } + IHTFP.ORG = { + kdc = kerberos.ihtfp.org + admin_server = kerberos.ihtfp.org + } + GNU.ORG = { + kdc = kerberos.gnu.org + kdc = kerberos-2.gnu.org + kdc = kerberos-3.gnu.org + admin_server = kerberos.gnu.org + } + 1TS.ORG = { + kdc = kerberos.1ts.org + admin_server = kerberos.1ts.org + } + GRATUITOUS.ORG = { + kdc = kerberos.gratuitous.org + admin_server = kerberos.gratuitous.org + } + DOOMCOM.ORG = { + kdc = kerberos.doomcom.org + admin_server = kerberos.doomcom.org + } + ANDREW.CMU.EDU = { + kdc = vice28.fs.andrew.cmu.edu + kdc = vice2.fs.andrew.cmu.edu + kdc = vice11.fs.andrew.cmu.edu + kdc = vice12.fs.andrew.cmu.edu + admin_server = vice28.fs.andrew.cmu.edu + default_domain = andrew.cmu.edu + } + CS.CMU.EDU = { + kdc = kerberos.cs.cmu.edu + kdc = kerberos-2.srv.cs.cmu.edu + admin_server = kerberos.cs.cmu.edu + } + DEMENTIA.ORG = { + kdc = kerberos.dementia.org + kdc = kerberos2.dementia.org + admin_server = kerberos.dementia.org + } + stanford.edu = { + kdc = krb5auth1.stanford.edu + kdc = krb5auth2.stanford.edu + kdc = krb5auth3.stanford.edu + master_kdc = krb5auth1.stanford.edu + admin_server = krb5-admin.stanford.edu + default_domain = stanford.edu + } + +[domain_realm] + .mit.edu = ATHENA.MIT.EDU + mit.edu = ATHENA.MIT.EDU + .media.mit.edu = MEDIA-LAB.MIT.EDU + media.mit.edu = MEDIA-LAB.MIT.EDU + .csail.mit.edu = CSAIL.MIT.EDU + csail.mit.edu = CSAIL.MIT.EDU + .whoi.edu = ATHENA.MIT.EDU + whoi.edu = ATHENA.MIT.EDU + .stanford.edu = stanford.edu + .slac.stanford.edu = SLAC.STANFORD.EDU + + grid.uoa.nesi.org.nz = NESI.ORG.NZ + +[login] + krb4_convert = true + krb4_get_tickets = false