Wednesday, January 20, 2010

WBem, Wiseman, Windows, Java and Negotiate authentication

I was playing around with WBem and tried to access the Windows Remote stuff on my Vista laptop. To be special Mocrosoft is calling their WBem implementation Windows Remote (WinRM). Because it's complicate here is some good blog entry on how to make WinRM work for you.

Back to Wiseman: Denis Rachal has posted some code to start/stop a service using Wiseman and WinRM to the Wiseman's user's mailing list on 6/18/2009. The problem with Wiseman is that you need to set up your WinRM to use unencrypted communication and basic authentication. Because the encryption is relatively new and "invented" by MS called "HTTP-SPNEGO-session-encrypted" the RFC (Google cache -- as you might expect from MS:-) is kind-a cryptic...

But we can do something about the authentication and use Negotiate by using the SpnegoHttpUrlConnection implementation from the SPNEGO project. Make sure to read through all their pre-flight documentation to set up kerberos and so on (keyword: krb5.conf, login.conf) The account you use needs to be at least a local administrator and it is helpful to prefix the domain for the user name (MY_DOMAIN\MY-USERNAME) Once this works you can rework Denis' example and have it support the Negotiate protocol as I did:


/**
* Copyright (C) 2006-2009 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Authors: Denis Rachal (denis.rachal@hp.com)
*/
package com.hp.wsman.client.transport;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedActionException;
import java.security.SecureRandom;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.login.LoginException;
import javax.xml.bind.JAXBException;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

import org.ietf.jgss.GSSException;

import net.sourceforge.spnego.SpnegoHttpURLConnection;

import sun.misc.BASE64Encoder;

import com.sun.ws.management.Message;
import com.sun.ws.management.addressing.Addressing;
import com.sun.ws.management.transport.ContentType;

public final class HttpClient {

private static final Logger LOG = Logger.getLogger(HttpClient.class
.getName());
private static PasswordAuthentication auth;

private HttpClient() {
}

static {
System.setProperty("java.security.krb5.conf", "C:\\Users\\eichbege\\wiseman-client\\client\\src\\krb5.conf");
System.setProperty("sun.security.krb5.debug", "false");
System.setProperty("java.security.auth.login.config", "C:\\Users\\eichbege\\wiseman-client\\client\\src\\login.conf");
}

static class MyAuthenticator extends 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 auth;
}
}


public static void setPasswordAuthentication(
final PasswordAuthentication pauth) {
auth = pauth;
}


public static void setTrustManager(final X509TrustManager trustManager)
throws NoSuchAlgorithmException, KeyManagementException {

final TrustManager[] tm = { trustManager };
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tm, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext
.getSocketFactory());
}

public static void setHostnameVerifier(final HostnameVerifier hv) {
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}

private static HttpURLConnection initConnection(final String to,
final ContentType ct, Object msg) throws IOException {
if (to == null) {
throw new IllegalArgumentException("Required Element is missing: "
+ Addressing.TO);
}

if (auth != null) {
//Authenticator.setDefault(new MyAuthenticator());

// String encodedUserPassword = new BASE64Encoder().encode((auth
// .getUserName()
// + ":" + new String(auth.getPassword())).getBytes());
// conn.setRequestProperty("Authorization", "Basic "
// + encodedUserPassword);
}

final URL dest = new URL(to);
URLConnection conn = null;
SpnegoHttpURLConnection spnego = null;
try {
spnego = new SpnegoHttpURLConnection("custom-client", auth.getUserName(), auth.getPassword().toString());
spnego.setRequestMethod("POST");
spnego.setRequestProperty("Content-Type",
ct == null ? ContentType.DEFAULT_CONTENT_TYPE.toString() : ct
.toString());
spnego.setRequestProperty("User-Agent", "https://wiseman.dev.java.net");
spnego.setRequestProperty("Accept", ContentType.ACCEPTABLE_CONTENT_TYPES);
conn = spnego.connect(dest, transfer(msg));
} catch (GSSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (PrivilegedActionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (LoginException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SOAPException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

//Authenticator.setDefault(new MyAuthenticator());
//conn = dest.openConnection();

//


final HttpURLConnection http = (HttpURLConnection) conn;
//http.setRequestMethod("POST");

return http;
}

// type of data can be Message or byte[], others will throw
// IllegalArgumentException
private static ByteArrayOutputStream transfer(final Object data)
throws IOException, SOAPException, JAXBException {
ByteArrayOutputStream os = null;
try {
os = new ByteArrayOutputStream();
if (data instanceof Message) {
((Message) data).writeTo(os);
} else if (data instanceof SOAPMessage) {
((SOAPMessage) data).writeTo(os);
} else if (data instanceof byte[]) {
os.write((byte[]) data);
} else {
throw new IllegalArgumentException("Type of data not handled: "
+ data.getClass().getName());
}
return os;
} finally {
if (os != null) {
os.close();
}
}
}

public static Addressing sendRequest(final SOAPMessage msg,
final String destination) throws IOException, SOAPException,
JAXBException {

if (LOG.isLoggable(Level.FINE))
LOG.fine("\n" + msg + "\n");
final HttpURLConnection http = initRequest(destination, ContentType
.createFromEncoding((String) msg
.getProperty(SOAPMessage.CHARACTER_SET_ENCODING)), msg);
//transfer(http, msg);
final Addressing response = readResponse(http);
if (LOG.isLoggable(Level.FINE)) {
if (response.getBody().hasFault())
LOG.fine("\n" + response + "\n");
else
LOG.fine("\n" + response + "\n");
}
return response;
}

public static Addressing sendRequest(final SOAPMessage msg,
final String destination, Entry... headers)
throws IOException, SOAPException, JAXBException {

if (LOG.isLoggable(Level.FINE))
LOG.fine("\n" + msg + "\n");
final HttpURLConnection http = initRequest(destination, ContentType
.createFromEncoding((String) msg
.getProperty(SOAPMessage.CHARACTER_SET_ENCODING)), msg);
// if (headers != null) {
// for (Entry entry : headers) {
// http.setRequestProperty(entry.getKey(), entry.getValue());
// }
// }
//
// transfer(http, msg);
final Addressing response = readResponse(http);
if (LOG.isLoggable(Level.FINE)) {
if (response.getBody().hasFault())
LOG.fine("\n" + response + "\n");
else
LOG.fine("\n" + response + "\n");
}
return response;
}

public static Addressing sendRequest(final Addressing msg,
final Entry... headers) throws IOException,
JAXBException, SOAPException {

if (LOG.isLoggable(Level.FINE))
LOG.fine("\n" + msg + "\n");
final HttpURLConnection http = initRequest(msg.getTo(), msg
.getContentType(), msg);

// if (headers != null) {
// for (Entry entry : headers) {
// http.setRequestProperty(entry.getKey(), entry.getValue());
// }
// }

//transfer(http, msg);
final Addressing response = readResponse(http);
if (LOG.isLoggable(Level.FINE)) {
if (response.getBody().hasFault())
LOG.fine("\n" + response + "\n");
else
LOG.fine("\n" + response + "\n");
}
response.setXmlBinding(msg.getXmlBinding());
return response;
}

public static HttpURLConnection createHttpConnection(String destination,
Object data) throws SOAPException, JAXBException, IOException {
final HttpURLConnection http = initRequest(destination, null, null);

return http;
}

private static HttpURLConnection initRequest(final String destination,
final ContentType contentType, Object msg) throws IOException {

final HttpURLConnection http = initConnection(destination, contentType, msg);
return http;
}

private static Addressing readResponse(final HttpURLConnection http)
throws IOException, SOAPException {

final InputStream is;
final int response = http.getResponseCode();
if (response == HttpURLConnection.HTTP_OK) {
is = http.getInputStream();
} else if (response == HttpURLConnection.HTTP_BAD_REQUEST
|| response == HttpURLConnection.HTTP_INTERNAL_ERROR) {
// read the fault from the error stream
is = http.getErrorStream();
} else {
final String detail = http.getResponseMessage();
throw new IOException(detail == null ? Integer.toString(response)
: detail);
}

final String responseType = http.getContentType();
final ContentType contentType = ContentType
.createFromHttpContentType(responseType);
if (contentType == null || !contentType.isAcceptable()) {
// dump the first 4k bytes of the response for help in debugging
if (LOG.isLoggable(Level.INFO)) {
final byte[] buffer = new byte[4096];
final int nread = is.read(buffer);
if (nread > 0) {
final ByteArrayOutputStream bos = new ByteArrayOutputStream(
buffer.length);
bos.write(buffer, 0, nread);
LOG.info("Response discarded: "
+ new String(bos.toByteArray()));
}
}
throw new IOException(
"Content-Type of response is not acceptable: "
+ responseType);
}

final Addressing addr;
try {
addr = new Addressing(is);
} finally {
if (is != null) {
is.close();
}
}

addr.setContentType(contentType);

return addr;
}

public static int sendResponse(final String to, final byte[] bits,
final ContentType contentType) throws IOException, SOAPException,
JAXBException {
final HttpURLConnection http = initConnection(to, contentType, bits);
return http.getResponseCode();
}

public static int sendResponse(final Addressing msg) throws IOException,
SOAPException, JAXBException {
final HttpURLConnection http = initConnection(msg.getTo(), msg
.getContentType(), msg);
return http.getResponseCode();
}
}

Supposedly you could achieve the same with the standard HTTP connection from Java 6 (see tutorial) but I couldn't get to work. So who knows... anyway the SPNEGO project's solution worked for me with an unencrypted setup on Windows Vista.