Working SPNEGO clients.
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 30 Jun 2022 18:07:40 +0000 (20:07 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 30 Jun 2022 18:07:40 +0000 (20:07 +0200)
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java
org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java
org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoHttpClient.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg

index b313222d516374e67b60ff9114b98fd9a1992530..6cc410ced0d07aff11369121d3687b880365bc8c 100644 (file)
@@ -4,24 +4,9 @@ import static org.argeo.cms.CmsMsg.password;
 import static org.argeo.cms.CmsMsg.username;
 
 import java.io.IOException;
-import java.net.Authenticator;
-import java.net.PasswordAuthentication;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpResponse.BodyHandler;
-import java.net.http.HttpResponse.BodyHandlers;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivilegedAction;
-import java.security.cert.X509Certificate;
 import java.util.List;
 import java.util.Locale;
 
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
 import javax.security.auth.Subject;
 import javax.security.auth.callback.Callback;
 import javax.security.auth.callback.CallbackHandler;
@@ -39,7 +24,6 @@ import org.argeo.api.cms.ux.CmsView;
 import org.argeo.cms.CmsMsg;
 import org.argeo.cms.LocaleUtils;
 import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.cms.servlet.ServletHttpRequest;
 import org.argeo.cms.servlet.ServletHttpResponse;
 import org.argeo.cms.swt.CmsStyles;
@@ -293,11 +277,6 @@ public class CmsLogin implements CmsStyles, CallbackHandler {
                        else
                                loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this);
                        loginContext.login();
-//                     try {
-//                             openHttpClient(loginContext.getSubject(), "id-internal.work.argeo.net");
-//                     } catch (Exception e) {
-//                             e.printStackTrace();
-//                     }
                        cmsView.authChange(loginContext);
                        return true;
                } catch (LoginException e) {
@@ -320,69 +299,6 @@ public class CmsLogin implements CmsStyles, CallbackHandler {
                // }
        }
 
-       private static HttpClient openHttpClient(Subject subject, String server) {
-               try {
-                       String domain = "WORK.ARGEO.ORG";
-                       // disable https check
-                       // jdk.internal.httpclient.disableHostnameVerification=true
-                       HttpClient client = HttpClient.newBuilder().sslContext(insecureContext())
-                                       .authenticator(new Authenticator() {
-                                               public PasswordAuthentication getPasswordAuthentication() {
-                                                       // I haven't checked getRequestingScheme() here, since for NTLM
-                                                       // and Negotiate, the usrname and password are all the same.
-                                                       System.err.println("Feeding username and password for " + getRequestingScheme());
-                                                       return (new PasswordAuthentication("mbaudier@" + domain, null));
-                                               }
-
-                                       }).build();
-
-                       String token = RemoteAuthUtils.getGssToken(subject, "HTTP/" + server + "@" + domain);
-
-                       HttpRequest request = HttpRequest.newBuilder(URI.create("https://" + server + "/ipa/session/json")).GET()
-                                       .header("Authorization", "Negotiate " + token).build();
-                       BodyHandler<String> bodyHandler = BodyHandlers.ofString();
-                       HttpResponse<String> response = client.send(request, bodyHandler);
-                       System.out.println(response.body());
-                       return client;
-
-                       // return client;
-//                     AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
-//                     HttpParams params = DefaultHttpParams.getDefaultParams();
-//                     ArrayList<String> schemes = new ArrayList<>();
-//                     schemes.add(SpnegoAuthScheme.NAME);
-//                     params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
-//                     params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
-//                     HttpClient httpClient = new HttpClient();
-//                     httpClient.executeMethod(new GetMethod(("https://" + server + "/ipa/session/json")));
-//                     return httpClient;
-               } catch (
-
-               Exception e) {
-                       throw new IllegalStateException("Cannot open client to IPA server " + server, e);
-               }
-
-       }
-
-       private static SSLContext insecureContext() {
-               TrustManager[] noopTrustManager = new TrustManager[] { new X509TrustManager() {
-                       public void checkClientTrusted(X509Certificate[] xcs, String string) {
-                       }
-
-                       public void checkServerTrusted(X509Certificate[] xcs, String string) {
-                       }
-
-                       public X509Certificate[] getAcceptedIssuers() {
-                               return null;
-                       }
-               } };
-               try {
-                       SSLContext sc = SSLContext.getInstance("ssl");
-                       sc.init(null, noopTrustManager, null);
-                       return sc;
-               } catch (KeyManagementException | NoSuchAlgorithmException e) {
-                       throw new IllegalStateException("Cannot create insecure SSL context ", e);
-               }
-       }
 
        protected void logout() {
                cmsView.logout();
index 0bb199dfdbfddcf4fb93eda4e4363834f9afa77b..4b4d291a120e7e379763f39409685b2a01999732 100644 (file)
@@ -20,7 +20,17 @@ import org.ietf.jgss.Oid;
 /** Remote authentication utilities. */
 public class RemoteAuthUtils {
        static final String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
-//     private static BundleContext bundleContext = FrameworkUtil.getBundle(RemoteAuthUtils.class).getBundleContext();
+       private final static Oid KERBEROS_OID;
+//     private final static Oid KERB_V5_OID, KRB5_PRINCIPAL_NAME_OID;
+       static {
+               try {
+                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
+//                     KERB_V5_OID = new Oid("1.2.840.113554.1.2.2");
+//                     KRB5_PRINCIPAL_NAME_OID = new Oid("1.2.840.113554.1.2.2.1");
+               } catch (GSSException e) {
+                       throw new IllegalStateException("Cannot create Kerberos OID", e);
+               }
+       }
 
        /**
         * Execute this supplier, using the CMS class loader as context classloader.
@@ -67,19 +77,12 @@ public class RemoteAuthUtils {
                return cmsSession;
        }
 
-       private final static Oid KERBEROS_OID;
-       static {
-               try {
-                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
-               } catch (GSSException e) {
-                       throw new IllegalStateException("Cannot create Kerberos OID", e);
-               }
-       }
-
-       public static String getGssToken(Subject subject, String serverPrinc) {
+       public static String getGssToken(Subject subject, String service, String server) {
                if (subject.getPrivateCredentials(KerberosTicket.class).isEmpty())
                        throw new IllegalArgumentException("Subject " + subject + " is not GSS authenticated.");
                return Subject.doAs(subject, (PrivilegedAction<String>) () -> {
+                       // !! different format than Kerberos
+                       String serverPrinc = service + "@" + server;
                        GSSContext context = null;
                        String tokenStr = null;
 
index 2dbad96d28d592bcb007d7e186ea6223c054f62c..dad0dad4be64a0600fd3d3298e6a3e5c8761efa4 100644 (file)
@@ -1,6 +1,5 @@
 package org.argeo.cms.auth;
 
-import java.lang.reflect.Method;
 import java.util.Map;
 
 import javax.security.auth.Subject;
@@ -11,10 +10,10 @@ import javax.security.auth.spi.LoginModule;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.ietf.jgss.GSSContext;
-import org.ietf.jgss.GSSCredential;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
+
+import com.sun.security.jgss.GSSUtil;
 
 /** SPNEGO login */
 public class SpnegoLoginModule implements LoginModule {
@@ -41,8 +40,21 @@ public class SpnegoLoginModule implements LoginModule {
                gssContext = checkToken(spnegoToken);
                if (gssContext == null)
                        return false;
-               else
+               else {
+//                     if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) {
+//                             try {
+//                                     GSSName name = gssContext.getSrcName();
+//                                     String username = name.toString();
+//                                     // TODO deal with connecting service
+//                                     // TODO generate IPA DN?
+//                                     username = username.substring(0, username.lastIndexOf('@'));
+//                                     sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, username);
+//                             } catch (GSSException e) {
+//                                     throw new IllegalStateException("Cannot retrieve SPNEGO name", e);
+//                             }
+//                     }
                        return true;
+               }
                // try {
                // String clientName = gssContext.getSrcName().toString();
                // String role = clientName.substring(clientName.indexOf('@') + 1);
@@ -63,14 +75,13 @@ public class SpnegoLoginModule implements LoginModule {
                        return false;
 
                try {
-                       Class<?> gssUtilsClass = Class.forName("com.sun.security.jgss.GSSUtil");
-                       Method createSubjectMethod = gssUtilsClass.getMethod("createSubject", GSSName.class, GSSCredential.class);
+//                     Class<?> gssUtilsClass = Class.forName("com.sun.security.jgss.GSSUtil");
+//                     Method createSubjectMethod = gssUtilsClass.getMethod("createSubject", GSSName.class, GSSCredential.class);
                        Subject gssSubject;
                        if (gssContext.getCredDelegState())
-                               gssSubject = (Subject) createSubjectMethod.invoke(null, gssContext.getSrcName(),
-                                               gssContext.getDelegCred());
+                               gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), gssContext.getDelegCred());
                        else
-                               gssSubject = (Subject) createSubjectMethod.invoke(null, gssContext.getSrcName(), null);
+                               gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), null);
                        subject.getPrincipals().addAll(gssSubject.getPrincipals());
                        subject.getPrivateCredentials().addAll(gssSubject.getPrivateCredentials());
                        return true;
index d72e695d5fb5209b3a2974fa989d0c8c406aa457..a8aa29bbbfb6487217e6365d3518d6108f4f6a29 100644 (file)
@@ -3,7 +3,6 @@ package org.argeo.cms.internal.http.client;
 import java.net.URL;
 import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
-import java.util.Base64;
 
 import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
@@ -11,7 +10,6 @@ import javax.security.auth.login.LoginContext;
 import org.apache.commons.httpclient.Credentials;
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.URIException;
 import org.apache.commons.httpclient.auth.AuthPolicy;
 import org.apache.commons.httpclient.auth.AuthScheme;
 import org.apache.commons.httpclient.auth.AuthenticationException;
@@ -20,25 +18,21 @@ import org.apache.commons.httpclient.auth.MalformedChallengeException;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.params.DefaultHttpParams;
 import org.apache.commons.httpclient.params.HttpParams;
-import org.ietf.jgss.GSSContext;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
-import org.ietf.jgss.Oid;
+import org.argeo.cms.auth.RemoteAuthUtils;
 
 /** Implementation of the SPNEGO auth scheme. */
 public class SpnegoAuthScheme implements AuthScheme {
 //     private final static Log log = LogFactory.getLog(SpnegoAuthScheme.class);
 
        public static final String NAME = "Negotiate";
-       private final static Oid KERBEROS_OID;
-       static {
-               try {
-                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
-               } catch (GSSException e) {
-                       throw new IllegalStateException("Cannot create Kerberos OID", e);
-               }
-       }
+//     private final static Oid KERBEROS_OID;
+//     static {
+//             try {
+//                     KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
+//             } catch (GSSException e) {
+//                     throw new IllegalStateException("Cannot create Kerberos OID", e);
+//             }
+//     }
 
        private final static String DEFAULT_KERBEROS_SERVICE = "HTTP";
 
@@ -93,43 +87,44 @@ public class SpnegoAuthScheme implements AuthScheme {
 
        @Override
        public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException {
-               GSSContext context = null;
-               String tokenStr = null;
+//             GSSContext context = null;
                String hostname;
                try {
                        hostname = method.getURI().getHost();
-               } catch (URIException e1) {
-                       throw new IllegalStateException("Cannot authenticate", e1);
-               }
-               String serverPrinc = DEFAULT_KERBEROS_SERVICE + "@" + hostname;
-
-               try {
-                       // Get service's principal name
-                       GSSManager manager = GSSManager.getInstance();
-                       GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID);
-
-                       // Get the context for authentication
-                       context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME);
-                       // context.requestMutualAuth(true); // Request mutual authentication
-                       // context.requestConf(true); // Request confidentiality
-                       context.requestCredDeleg(true);
-
-                       byte[] token = new byte[0];
-
-                       // token is ignored on the first call
-                       token = context.initSecContext(token, 0, token.length);
-
-                       // Send a token to the server if one was generated by
-                       // initSecContext
-                       if (token != null) {
-                               tokenStr = Base64.getEncoder().encodeToString(token);
-                               // complete=true;
-                       }
+                       String tokenStr = RemoteAuthUtils.getGssToken(null, DEFAULT_KERBEROS_SERVICE, hostname);
                        return "Negotiate " + tokenStr;
-               } catch (GSSException e) {
+               } catch (Exception e1) {
                        complete = true;
-                       throw new AuthenticationException("Cannot authenticate to " + serverPrinc, e);
+                       throw new AuthenticationException("Cannot authenticate " + method, e1);
                }
+//             String serverPrinc = DEFAULT_KERBEROS_SERVICE + "@" + hostname;
+//
+//             try {
+//                     // Get service's principal name
+//                     GSSManager manager = GSSManager.getInstance();
+//                     GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID);
+//
+//                     // Get the context for authentication
+//                     context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME);
+//                     // context.requestMutualAuth(true); // Request mutual authentication
+//                     // context.requestConf(true); // Request confidentiality
+//                     context.requestCredDeleg(true);
+//
+//                     byte[] token = new byte[0];
+//
+//                     // token is ignored on the first call
+//                     token = context.initSecContext(token, 0, token.length);
+//
+//                     // Send a token to the server if one was generated by
+//                     // initSecContext
+//                     if (token != null) {
+//                             tokenStr = Base64.getEncoder().encodeToString(token);
+//                             // complete=true;
+//                     }
+//             } catch (GSSException e) {
+//                     complete = true;
+//                     throw new AuthenticationException("Cannot authenticate to " + serverPrinc, e);
+//             }
        }
 
        public static void main(String[] args) {
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoHttpClient.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoHttpClient.java
new file mode 100644 (file)
index 0000000..674cfdf
--- /dev/null
@@ -0,0 +1,119 @@
+package org.argeo.cms.internal.http.client;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+
+import org.argeo.cms.auth.RemoteAuthUtils;
+
+public class SpnegoHttpClient {
+       public static void main(String[] args) throws MalformedURLException {
+               String principal = System.getProperty("javax.security.auth.login.name");
+               if (args.length == 0 || principal == null) {
+                       System.err.println("usage: java -Djavax.security.auth.login.name=<principal@REALM> "
+                                       + SpnegoHttpClient.class.getName() + " <url>");
+                       System.exit(1);
+                       return;
+               }
+               String url = args[0];
+               URL u = new URL(url);
+               String server = u.getHost();
+
+               URL jaasUrl = SpnegoAuthScheme.class.getResource("jaas.cfg");
+               System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
+               try {
+                       LoginContext lc = new LoginContext("SINGLE_USER");
+                       lc.login();
+
+//                     int responseCode = Subject.doAs(lc.getSubject(), new PrivilegedExceptionAction<Integer>() {
+//
+//                             public Integer run() throws Exception {
+
+//                                     InputStream ins = u.openConnection().getInputStream();
+//                                     BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
+//                                     String str;
+//                                     while ((str = reader.readLine()) != null)
+//                                             System.out.println(str);
+//                                     return 666;
+
+                       HttpClient httpClient = openHttpClient(lc.getSubject());
+                       String token = RemoteAuthUtils.getGssToken(lc.getSubject(), "HTTP", server);
+
+                       HttpRequest request = HttpRequest.newBuilder().uri(u.toURI()) //
+                                       .header("Authorization", "Negotiate " + token) //
+                                       .build();
+                       BodyHandler<String> bodyHandler = BodyHandlers.ofString();
+                       HttpResponse<String> response = httpClient.send(request, bodyHandler);
+                       System.out.println(response.body());
+                       int responseCode = response.statusCode();
+//                                     return response.statusCode();
+//                             }
+//                     });
+                       System.out.println("Reponse code: " + responseCode);
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       private static HttpClient openHttpClient(Subject subject) {
+               // disable https check
+               // jdk.internal.httpclient.disableHostnameVerification=true
+               HttpClient client = HttpClient.newBuilder().sslContext(insecureContext())
+//                             .authenticator(new Authenticator() {
+//                     public PasswordAuthentication getPasswordAuthentication() {
+//                             return null;
+//                     }
+//
+//             })
+                               .version(HttpClient.Version.HTTP_1_1).build();
+
+               return client;
+
+               // return client;
+//                     AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
+//                     HttpParams params = DefaultHttpParams.getDefaultParams();
+//                     ArrayList<String> schemes = new ArrayList<>();
+//                     schemes.add(SpnegoAuthScheme.NAME);
+//                     params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
+//                     params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
+//                     HttpClient httpClient = new HttpClient();
+//                     httpClient.executeMethod(new GetMethod(("https://" + server + "/ipa/session/json")));
+//                     return httpClient;
+
+       }
+
+       private static SSLContext insecureContext() {
+               TrustManager[] noopTrustManager = new TrustManager[] { new X509TrustManager() {
+                       public void checkClientTrusted(X509Certificate[] xcs, String string) {
+                       }
+
+                       public void checkServerTrusted(X509Certificate[] xcs, String string) {
+                       }
+
+                       public X509Certificate[] getAcceptedIssuers() {
+                               return null;
+                       }
+               } };
+               try {
+                       SSLContext sc = SSLContext.getInstance("ssl");
+                       sc.init(null, noopTrustManager, null);
+                       return sc;
+               } catch (KeyManagementException | NoSuchAlgorithmException e) {
+                       throw new IllegalStateException("Cannot create insecure SSL context ", e);
+               }
+       }
+
+}
index 4dd0e0d8e9e9f17fe70595be49a57b1f0f794a53..dc540ddb2b970f266b8da63c70d0c130fc7877e4 100644 (file)
@@ -1,4 +1,10 @@
 SINGLE_USER {
-    com.sun.security.auth.module.Krb5LoginModule optional
-     useTicketCache=true;
+    com.sun.security.auth.module.Krb5LoginModule required
+     useTicketCache=true
+     debug=true;
 };
+
+com.sun.security.jgss.krb5.initiate {
+    com.sun.security.auth.module.Krb5LoginModule
+     required useTicketCache=true;
+};
\ No newline at end of file