Java tutorial
/* * Copyright 2013-2015, Juha Lindfors. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.openremote.beehive.account.client; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.Security; import java.security.cert.Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManagerFactory; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.bouncycastle.util.encoders.Base64; import org.openremote.base.Version; import org.openremote.base.exception.InitializationException; import org.openremote.logging.Hierarchy; import org.openremote.logging.Logger; import org.openremote.security.KeyManager; import org.openremote.security.PrivateKeyManager; import org.openremote.security.SecurityProvider; import org.openremote.security.TrustStore; import org.openremote.model.User; import org.openremote.beehive.account.model.CustomerFulfillment; import org.openremote.beehive.account.model.UserRegistration; /** * TODO * * @author Juha Lindfors */ public class AccountManagerClient { // Constants ------------------------------------------------------------------------------------ public static final URI REST_ROOT_PATH = URI.create("rest"); public static final URI RPC_REST_PATH = URI.create("rpc"); public static final URI SERVICE_PATH = URI.create("accountmanager"); public static final KeyManager.Storage DEFAULT_TRUST_STORE_FORMAT = KeyManager.Storage.JCEKS; public static final String DEFAULT_PRIVATE_KEYSTORE_FILENAME = "tomcat-private.keystore"; public static final String DEFAULT_TRUSTSTORE_FILENAME = "client.truststore"; public static final Charset UTF8 = Charset.forName("UTF-8"); // Class Members -------------------------------------------------------------------------------- private static final Logger log = Logger.getInstance(Log.CLIENT); public static void main(String... args) throws Exception { Security.addProvider(SecurityProvider.BC.getProviderInstance()); if (args.length == 0) { System.err.println(""); System.err.println("Use --help argument to list possible arguments for this command."); System.err.println(""); } if (args[0].equalsIgnoreCase("--generate-keys")) { generateKeys(args); } if (args[0].equalsIgnoreCase("--deployment-test")) { executeTestRequests(args); } else { System.err.println(); System.err.println("Unknown option '" + args[0] + "', use --help for valid options."); System.err.println(); } } private static void executeTestRequests(String... args) throws Exception { if (args.length < 3) { System.err.println(""); System.err.println("Attribute --deployment-test requires additional arguments:"); System.err.println(" [--deployment-test <url> <username:password>]"); System.err.println(""); return; } String username; String password; try { username = args[2].substring(0, args[2].indexOf(":")); password = args[2].substring(args[2].indexOf(":") + 1, args[2].length()); } catch (IndexOutOfBoundsException e) { System.err.println(); System.err.println("Unable to parse <username:password> from '" + args[2] + "'."); System.err.println(); return; } AccountManagerClient client = new AccountManagerClient(new URL(args[1]), username, password.getBytes(UTF8)); File userHomeDir = new File(System.getProperty("user.home")); File trustStoreLocation = new File(userHomeDir, "client.truststore"); client.setCertificateTrustStore(trustStoreLocation.toURI()); System.out.println(""); System.out.println(""); System.out.println("Executing Deployment Test to " + args[1] + " with user " + username); String user = "johndoe"; System.out.println(); System.out.println("Creating new user account for " + user); System.out.println(); Response response = client .create(new UserRegistration(user, "email@host.domain", "Smb9324$#@#@$".getBytes(UTF8))); System.out.println(response.getStatus() + ": " + response.getStatusInfo()); System.out.println(); System.out.println("Retrieving account info for user " + user); System.out.println(); response = client.retrieveAccountInfo(user); System.out.println(response.getStatus() + ": " + response.getStatusInfo()); System.out.println(); System.out.println("Deleting " + user); System.out.println(); response = client.delete(user); System.out.println(response.getStatus() + ": " + response.getStatusInfo()); } private static void generateKeys(String... args) throws KeyManager.KeyManagerException { if (args.length < 2) { System.err.println(""); System.err.println("Attribute --generate-keys requires password as second argument."); System.err.println(" [--generate-keys <password> [--alias <alias>]]"); System.err.println(""); return; } String alias = "tomcat"; if (args.length > 2) { if (args[2].equalsIgnoreCase("--alias")) { if (args.length < 4) { System.err.println(""); System.err.println("Attribute --alias requires a second argument:"); System.err.println(" [--generate-keys <password> [--alias <alias>]]"); System.err.println(""); return; } alias = args[3]; } } // Store private key store in current work directory... Certificate cert = createPrivateKeyStore(alias, args[1]); // Store the certificate to a client trust store... createClientTrustStore(alias, cert); System.out.println(); System.out.println("Trusted Tomcat Client Certificate: "); System.out.println(); System.out.println(cert); System.out.println(); System.out.println("Tomcat private key store saved to " + DEFAULT_PRIVATE_KEYSTORE_FILENAME); System.out.println("Client trust store saved to " + DEFAULT_TRUSTSTORE_FILENAME); System.out.println(); } private static void createClientTrustStore(String alias, Certificate cert) throws KeyManager.KeyManagerException { File workingDirectory = new File(System.getProperty("user.dir")); URI trustStoreLocation = new File(workingDirectory, DEFAULT_TRUSTSTORE_FILENAME).toURI(); TrustStore trustStore = TrustStore.create(trustStoreLocation, DEFAULT_TRUST_STORE_FORMAT); trustStore.addTrustedCertificate(alias, cert); } private static Certificate createPrivateKeyStore(String alias, String password) throws KeyManager.KeyManagerException { File workingDirectory = new File(System.getProperty("user.dir")); URI privateKeyStoreLocation = new File(workingDirectory, DEFAULT_PRIVATE_KEYSTORE_FILENAME).toURI(); char[] storePassword = password.toCharArray(); // Create store and add a new private key... PrivateKeyManager privatekey = PrivateKeyManager.create(privateKeyStoreLocation, storePassword, KeyManager.Storage.JCEKS); System.out.println(); System.out.println("Generating new key for alias '" + alias + "'..."); System.out.println(); // TODO : switch to EC keys... return privatekey.addKey(alias, storePassword, KeyManager.AsymmetricKeyAlgorithm.RSA); } // Instance Fields ------------------------------------------------------------------------------ private URL serviceRootURL; private byte[] credentials; private String username; private URI trustStoreLocation = null; private String httpsProtocolJcaName = null; //private WebTarget serviceEndpoint; // Constructors --------------------------------------------------------------------------------- /** * TODO * * @param serviceRootURL * @param username * @param credentials */ public AccountManagerClient(URL serviceRootURL, String username, byte[] credentials) { this.serviceRootURL = serviceRootURL; this.username = username; this.credentials = credentials; } // Public Instance Methods ---------------------------------------------------------------------- public Response create(User user) { WebTarget target = constructTargetBase(createClient()).path("users"); Entity<String> jsonEntity = Entity.entity(user.toJSONString(), MediaType.APPLICATION_JSON); return sendPost(target, jsonEntity); } public Response create(UserRegistration user) { WebTarget target = constructTargetBase(createClient()).path("users"); Entity<String> jsonEntity = Entity.entity(user.toJSONString(), MediaType.APPLICATION_JSON); return sendPost(target, jsonEntity); } public Response create(CustomerFulfillment fulfillment) { WebTarget target = constructTargetBase(createClient()).path("users"); Entity<String> jsonEntity = Entity.entity(fulfillment.toJSONString(), CustomerFulfillment.JSON_HTTP_CONTENT_TYPE); return sendPost(target, jsonEntity); } public Response retrieveAccountInfo(String username) { WebTarget target = constructTargetBase(createClient()).path("users/" + username + "/accounts"); return sendGet(target); } public Response delete(String username) { WebTarget target = constructTargetBase(createClient()).path("users/" + username); return sendDelete(target); } public AccountManagerClient setCertificateTrustStore(URI storeLocation) { this.trustStoreLocation = storeLocation; return this; } public AccountManagerClient createCertificateTrustStore(URI storeLocation, Certificate cert) throws InitializationException { // TODO try { TrustStore trustStore = TrustStore.create(storeLocation, KeyManager.Storage.JCEKS); trustStore.addTrustedCertificate("single-certificate-store", cert); setCertificateTrustStore(storeLocation); } catch (Exception exception) { throw new InitializationException("Creating client''s certificate trust store failed : {0}", exception, exception.getMessage()); } return this; } public AccountManagerClient createTemporaryCertificateTrustStore(Certificate cert) throws InitializationException { File workDir = new File(System.getProperty("user.dir")); File temp = new File(workDir, "cert.truststore"); temp.deleteOnExit(); return createCertificateTrustStore(temp.toURI(), cert); } public AccountManagerClient setHttpsProtocol(String jcaProtocolName) { this.httpsProtocolJcaName = jcaProtocolName; return this; } public AccountManagerClient setHttpsProtocol(HttpsProtocol protocol) { return setHttpsProtocol(protocol.getJCAName()); } // Private Instance Methods --------------------------------------------------------------------- private Response sendGet(WebTarget target) { Invocation.Builder invocationBuilder = target.request(); authenticate(invocationBuilder); return invocationBuilder.get(); } private Response sendPost(WebTarget target, Entity content) { // TODO : catch javax.ws.rs.ProcessingException Invocation.Builder invocationBuilder = target.request(); authenticate(invocationBuilder); return invocationBuilder.post(content); } private void authenticate(Invocation.Builder invocation) { invocation.header("Authorization", "Basic " + new String(Base64.encode((username + ":" + new String(credentials)).getBytes(UTF8)))); } private Response sendDelete(WebTarget target) { Invocation.Builder invocationBuilder = target.request(); authenticate(invocationBuilder); return invocationBuilder.delete(); } // // private Client createClient(File trustStore) throws InitializationException // { // ClientBuilder builder = ClientBuilder.newBuilder().hostnameVerifier(new HostnameVerifier() // { // public boolean verify(String s, SSLSession sslSession) // { // // TODO // // return true; // } // }); // // try // { // KeyStore keystore = KeyStore.getInstance(DEFAULT_TRUST_STORE_FORMAT.getStorageName()); // BufferedInputStream bin = new BufferedInputStream(new FileInputStream(trustStore)); // // keystore.load(bin, null); // // builder = builder.trustStore(keystore); // } // // catch (Exception exception) // { // throw new InitializationException("Error loading trust store: {0}", exception.getMessage()); // } // // return builder.build(); // } // private WebTarget constructTargetBase(Client client) { WebTarget target = client.target(serviceRootURL.toString()).path(REST_ROOT_PATH.toString()) .path(RPC_REST_PATH.toString()).path(SERVICE_PATH.toString()).path(new VersionPath().toString()); return target; } private Client createClient() { //System.setProperty("javax.net.ssl.trustStore", "/Users/juha/testTrustStore"); ClientBuilder builder = ClientBuilder.newBuilder().hostnameVerifier(new HostnameVerifier() { public boolean verify(String s, SSLSession sslSession) { // TODO return true; //To change body of implemented methods use File | Settings | File Templates. } }); // set up a null trust manager factory -- if a trust store is not configured in this client // the null reference will return default trust factories for SSL context... TrustManagerFactory tmf = null; if (trustStoreLocation != null) { // If a trust store has been configured for this client, attempt to load the keystore and // initialize it for the SSL context... try { KeyStore trustedKeyCertificates = KeyStore.getInstance(DEFAULT_TRUST_STORE_FORMAT.getStorageName()); FileInputStream fis = new FileInputStream(new File(trustStoreLocation)); trustedKeyCertificates.load(fis, KeyManager.EMPTY_KEY_PASSWORD); tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustedKeyCertificates); } catch (FileNotFoundException exception) { throw new ClientConfigurationException( "Configured trusted certificate store was not found at ''{0}'': {1}", exception, trustStoreLocation, exception.getMessage()); } catch (Exception exception) { throw new ClientConfigurationException("Error establishing a trust store: {0}", exception.getMessage()); } } try { SSLContext ssl = selectHttpsProtocol(); ssl.init(null, (tmf == null) ? null : tmf.getTrustManagers(), null /* default secure random */); builder.sslContext(ssl); } catch (KeyManagementException exception) { throw new ClientConfigurationException("Failed to initialize HTTPS SSL/TLS context : {0}", exception, exception.getMessage()); } return builder.build(); } private SSLContext selectHttpsProtocol() { if (httpsProtocolJcaName != null) { try { return SSLContext.getInstance(httpsProtocolJcaName); } catch (NoSuchAlgorithmException exception) { throw new ClientConfigurationException("Configured HTTPS algorithm ''{0}'' is not available: {1}", httpsProtocolJcaName, exception.getMessage()); } } try { return SSLContext.getInstance(HttpsProtocol.TLS_V1_2.getJCAName()); } catch (NoSuchAlgorithmException exception) { log.debug("TLS 1.2 not available, trying TLS v1.1..."); try { return SSLContext.getInstance(HttpsProtocol.TLS_V1_1.getJCAName()); } catch (NoSuchAlgorithmException exception2) { log.debug("TLS 1.1 not available, trying TLS v1.0..."); try { // For some reason at least Oracle JDK 6 seems to require this property to be set // before TLS v1.0 is working purely without SSLv2Hello in handshake, not sure why. // OpenJDK 6 includes TLS v1.1 so avoids this execution branch... System.setProperty("https.protocols", "TLSv1"); return SSLContext.getInstance(HttpsProtocol.TLS_V1_0.getJCAName()); } catch (NoSuchAlgorithmException exception3) { throw new ClientConfigurationException( "HTTPS TLS protocols not available in the current runtime " + "(tried TLS 1.0, TLS 1.1, TLS 1.2)"); } } } } // Nested Enums --------------------------------------------------------------------------------- private enum HttpsProtocol { TLS_V1_0("TLSv1"), TLS_V1_1("TLSv1.1"), TLS_V1_2("TLSv1.2"); private String jcaName; private HttpsProtocol(String jcaStandardName) { this.jcaName = jcaStandardName; } private String getJCAName() { return jcaName; } } public enum Log implements Hierarchy { CLIENT("Client"); private String name; private Log(String name) { this.name = name; } @Override public String getCanonicalLogHierarchyName() { return "OpenRemote.AccountManager." + name; } } // Nested Classes ------------------------------------------------------------------------------- private static class VersionPath extends Version { private VersionPath() { super(2, 0, 0); } @Override public String toString() { return super.majorVersion + "/" + super.minorVersion + "/" + super.bugfixVersion; } } }