From 3d3c654c9d973c62ca22f1c9010bb2e7e1847d09 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Thu, 30 Jun 2022 20:07:40 +0200 Subject: [PATCH] Working SPNEGO clients. --- .../src/org/argeo/cms/swt/auth/CmsLogin.java | 84 ------------- .../org/argeo/cms/auth/RemoteAuthUtils.java | 25 ++-- .../org/argeo/cms/auth/SpnegoLoginModule.java | 29 +++-- .../http/client/SpnegoAuthScheme.java | 87 ++++++------- .../http/client/SpnegoHttpClient.java | 119 ++++++++++++++++++ .../argeo/cms/internal/http/client/jaas.cfg | 10 +- 6 files changed, 202 insertions(+), 152 deletions(-) create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoHttpClient.java diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java index b313222d5..6cc410ced 100644 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java @@ -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 bodyHandler = BodyHandlers.ofString(); - HttpResponse 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 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(); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java index 0bb199dfd..4b4d291a1 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java @@ -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) () -> { + // !! different format than Kerberos + String serverPrinc = service + "@" + server; GSSContext context = null; String tokenStr = null; diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java index 2dbad96d2..dad0dad4b 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java @@ -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; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java index d72e695d5..a8aa29bbb 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java @@ -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 index 000000000..674cfdf15 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoHttpClient.java @@ -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= " + + SpnegoHttpClient.class.getName() + " "); + 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() { +// +// 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 bodyHandler = BodyHandlers.ofString(); + HttpResponse 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 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); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg index 4dd0e0d8e..dc540ddb2 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg @@ -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 -- 2.30.2