Path: blob/master/test/jdk/javax/security/sasl/Sasl/ClientServerTest.java
41152 views
/*1* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.io.Closeable;24import java.io.IOException;25import java.io.ObjectInputStream;26import java.io.ObjectOutputStream;27import java.io.Serializable;28import java.net.ServerSocket;29import java.net.Socket;30import java.net.UnknownHostException;31import java.util.ArrayList;32import java.util.Arrays;33import java.util.HashMap;34import java.util.Map;35import java.util.StringJoiner;36import javax.security.auth.callback.Callback;37import javax.security.auth.callback.CallbackHandler;38import javax.security.auth.callback.NameCallback;39import javax.security.auth.callback.PasswordCallback;40import javax.security.auth.callback.UnsupportedCallbackException;41import javax.security.sasl.AuthorizeCallback;42import javax.security.sasl.RealmCallback;43import javax.security.sasl.RealmChoiceCallback;44import javax.security.sasl.Sasl;45import javax.security.sasl.SaslClient;46import javax.security.sasl.SaslException;47import javax.security.sasl.SaslServer;4849/*50* @test51* @bug 804981452* @summary JAVA SASL server and client tests with CRAM-MD5 and53* DIGEST-MD5 mechanisms. The tests try different QOP values on54* client and server side.55* @modules java.security.sasl/javax.security.sasl56*/57public class ClientServerTest {5859private static final int DELAY = 100;60private static final String LOCALHOST = "localhost";61private static final String DIGEST_MD5 = "DIGEST-MD5";62private static final String CRAM_MD5 = "CRAM-MD5";63private static final String PROTOCOL = "saslservice";64private static final String USER_ID = "sasltester";65private static final String PASSWD = "password";66private static final String QOP_AUTH = "auth";67private static final String QOP_AUTH_CONF = "auth-conf";68private static final String QOP_AUTH_INT = "auth-int";69private static final String AUTHID_SASL_TESTER = "sasl_tester";70private static final ArrayList<String> SUPPORT_MECHS = new ArrayList<>();7172static {73SUPPORT_MECHS.add(DIGEST_MD5);74SUPPORT_MECHS.add(CRAM_MD5);75}7677public static void main(String[] args) throws Exception {78String[] allQops = { QOP_AUTH_CONF, QOP_AUTH_INT, QOP_AUTH };79String[] twoQops = { QOP_AUTH_INT, QOP_AUTH };80String[] authQop = { QOP_AUTH };81String[] authIntQop = { QOP_AUTH_INT };82String[] authConfQop = { QOP_AUTH_CONF };83String[] emptyQop = {};8485boolean success = true;8687success &= runTest("", CRAM_MD5, new String[] { QOP_AUTH },88new String[] { QOP_AUTH }, false);89success &= runTest("", DIGEST_MD5, new String[] { QOP_AUTH },90new String[] { QOP_AUTH }, false);91success &= runTest(AUTHID_SASL_TESTER, DIGEST_MD5,92new String[] { QOP_AUTH }, new String[] { QOP_AUTH }, false);93success &= runTest("", DIGEST_MD5, allQops, authQop, false);94success &= runTest("", DIGEST_MD5, allQops, authIntQop, false);95success &= runTest("", DIGEST_MD5, allQops, authConfQop, false);96success &= runTest("", DIGEST_MD5, twoQops, authQop, false);97success &= runTest("", DIGEST_MD5, twoQops, authIntQop, false);98success &= runTest("", DIGEST_MD5, twoQops, authConfQop, true);99success &= runTest("", DIGEST_MD5, authIntQop, authQop, true);100success &= runTest("", DIGEST_MD5, authConfQop, authQop, true);101success &= runTest("", DIGEST_MD5, authConfQop, emptyQop, true);102success &= runTest("", DIGEST_MD5, authIntQop, emptyQop, true);103success &= runTest("", DIGEST_MD5, authQop, emptyQop, true);104105if (!success) {106throw new RuntimeException("At least one test case failed");107}108109System.out.println("Test passed");110}111112private static boolean runTest(String authId, String mech,113String[] clientQops, String[] serverQops, boolean expectException)114throws Exception {115116System.out.println("AuthId:" + authId117+ " mechanism:" + mech118+ " clientQops: " + Arrays.toString(clientQops)119+ " serverQops: " + Arrays.toString(serverQops)120+ " expect exception:" + expectException);121122try (Server server = Server.start(LOCALHOST, authId, serverQops)) {123new Client(LOCALHOST, server.getPort(), mech, authId, clientQops)124.run();125if (expectException) {126System.out.println("Expected exception not thrown");127return false;128}129} catch (SaslException e) {130if (!expectException) {131System.out.println("Unexpected exception: " + e);132return false;133}134System.out.println("Expected exception: " + e);135}136137return true;138}139140static enum SaslStatus {141SUCCESS, FAILURE, CONTINUE142}143144static class Message implements Serializable {145146private final SaslStatus status;147private final byte[] data;148149public Message(SaslStatus status, byte[] data) {150this.status = status;151this.data = data;152}153154public SaslStatus getStatus() {155return status;156}157158public byte[] getData() {159return data;160}161}162163static class SaslPeer {164165final String host;166final String mechanism;167final String qop;168final CallbackHandler callback;169170SaslPeer(String host, String authId, String... qops) {171this(host, null, authId, qops);172}173174SaslPeer(String host, String mechanism, String authId, String... qops) {175this.host = host;176this.mechanism = mechanism;177178StringJoiner sj = new StringJoiner(",");179for (String q : qops) {180sj.add(q);181}182qop = sj.toString();183184callback = new TestCallbackHandler(USER_ID, PASSWD, host, authId);185}186187Message getMessage(Object ob) {188if (!(ob instanceof Message)) {189throw new RuntimeException("Expected an instance of Message");190}191return (Message) ob;192}193}194195static class Server extends SaslPeer implements Runnable, Closeable {196197private volatile boolean ready = false;198private volatile ServerSocket ssocket;199200static Server start(String host, String authId, String[] serverQops)201throws UnknownHostException {202Server server = new Server(host, authId, serverQops);203Thread thread = new Thread(server);204thread.setDaemon(true);205thread.start();206207while (!server.ready) {208try {209Thread.sleep(DELAY);210} catch (InterruptedException e) {211throw new RuntimeException(e);212}213}214215return server;216}217218Server(String host, String authId, String... qops) {219super(host, authId, qops);220}221222int getPort() {223return ssocket.getLocalPort();224}225226private void processConnection(SaslEndpoint endpoint)227throws SaslException, IOException, ClassNotFoundException {228System.out.println("process connection");229endpoint.send(SUPPORT_MECHS);230Object o = endpoint.receive();231if (!(o instanceof String)) {232throw new RuntimeException("Received unexpected object: " + o);233}234String mech = (String) o;235SaslServer saslServer = createSaslServer(mech);236Message msg = getMessage(endpoint.receive());237while (!saslServer.isComplete()) {238byte[] data = processData(msg.getData(), endpoint,239saslServer);240if (saslServer.isComplete()) {241System.out.println("server is complete");242endpoint.send(new Message(SaslStatus.SUCCESS, data));243} else {244System.out.println("server continues");245endpoint.send(new Message(SaslStatus.CONTINUE, data));246msg = getMessage(endpoint.receive());247}248}249}250251private byte[] processData(byte[] data, SaslEndpoint endpoint,252SaslServer server) throws SaslException, IOException {253try {254return server.evaluateResponse(data);255} catch (SaslException e) {256endpoint.send(new Message(SaslStatus.FAILURE, null));257System.out.println("Error while processing data");258throw e;259}260}261262private SaslServer createSaslServer(String mechanism)263throws SaslException {264Map<String, String> props = new HashMap<>();265props.put(Sasl.QOP, qop);266return Sasl.createSaslServer(mechanism, PROTOCOL, host, props,267callback);268}269270@Override271public void run() {272try (ServerSocket ss = new ServerSocket(0)) {273ssocket = ss;274System.out.println("server started on port " + getPort());275ready = true;276Socket socket = ss.accept();277try (SaslEndpoint endpoint = new SaslEndpoint(socket)) {278System.out.println("server accepted connection");279processConnection(endpoint);280}281} catch (Exception e) {282// ignore it for now, client will throw an exception283}284}285286@Override287public void close() throws IOException {288if (!ssocket.isClosed()) {289ssocket.close();290}291}292}293294static class Client extends SaslPeer {295296private final int port;297298Client(String host, int port, String mech, String authId,299String... qops) {300super(host, mech, authId, qops);301this.port = port;302}303304public void run() throws Exception {305System.out.println("Host:" + host + " port: "306+ port);307try (SaslEndpoint endpoint = SaslEndpoint.create(host, port)) {308negotiateMechanism(endpoint);309SaslClient client = createSaslClient();310byte[] data = new byte[0];311if (client.hasInitialResponse()) {312data = client.evaluateChallenge(data);313}314endpoint.send(new Message(SaslStatus.CONTINUE, data));315Message msg = getMessage(endpoint.receive());316while (!client.isComplete()317&& msg.getStatus() != SaslStatus.FAILURE) {318switch (msg.getStatus()) {319case CONTINUE:320System.out.println("client continues");321data = client.evaluateChallenge(msg.getData());322endpoint.send(new Message(SaslStatus.CONTINUE,323data));324msg = getMessage(endpoint.receive());325break;326case SUCCESS:327System.out.println("client succeeded");328data = client.evaluateChallenge(msg.getData());329if (data != null) {330throw new SaslException("data should be null");331}332break;333default:334throw new RuntimeException("Wrong status:"335+ msg.getStatus());336}337}338339if (msg.getStatus() == SaslStatus.FAILURE) {340throw new RuntimeException("Status is FAILURE");341}342}343344System.out.println("Done");345}346347private SaslClient createSaslClient() throws SaslException {348Map<String, String> props = new HashMap<>();349props.put(Sasl.QOP, qop);350return Sasl.createSaslClient(new String[] {mechanism}, USER_ID,351PROTOCOL, host, props, callback);352}353354private void negotiateMechanism(SaslEndpoint endpoint)355throws ClassNotFoundException, IOException {356Object o = endpoint.receive();357if (o instanceof ArrayList) {358ArrayList list = (ArrayList) o;359if (!list.contains(mechanism)) {360throw new RuntimeException(361"Server does not support specified mechanism:"362+ mechanism);363}364} else {365throw new RuntimeException(366"Expected an instance of ArrayList, but received " + o);367}368369endpoint.send(mechanism);370}371372}373374static class SaslEndpoint implements AutoCloseable {375376private final Socket socket;377private ObjectInputStream input;378private ObjectOutputStream output;379380static SaslEndpoint create(String host, int port) throws IOException {381return new SaslEndpoint(new Socket(host, port));382}383384SaslEndpoint(Socket socket) throws IOException {385this.socket = socket;386}387388private ObjectInputStream getInput() throws IOException {389if (input == null && socket != null) {390input = new ObjectInputStream(socket.getInputStream());391}392return input;393}394395private ObjectOutputStream getOutput() throws IOException {396if (output == null && socket != null) {397output = new ObjectOutputStream(socket.getOutputStream());398}399return output;400}401402public Object receive() throws IOException, ClassNotFoundException {403return getInput().readObject();404}405406public void send(Object obj) throws IOException {407getOutput().writeObject(obj);408getOutput().flush();409}410411@Override412public void close() throws IOException {413if (socket != null && !socket.isClosed()) {414socket.close();415}416}417418}419420static class TestCallbackHandler implements CallbackHandler {421422private final String userId;423private final char[] passwd;424private final String realm;425private String authId;426427TestCallbackHandler(String userId, String passwd, String realm,428String authId) {429this.userId = userId;430this.passwd = passwd.toCharArray();431this.realm = realm;432this.authId = authId;433}434435@Override436public void handle(Callback[] callbacks) throws IOException,437UnsupportedCallbackException {438for (Callback callback : callbacks) {439if (callback instanceof NameCallback) {440System.out.println("NameCallback");441((NameCallback) callback).setName(userId);442} else if (callback instanceof PasswordCallback) {443System.out.println("PasswordCallback");444((PasswordCallback) callback).setPassword(passwd);445} else if (callback instanceof RealmCallback) {446System.out.println("RealmCallback");447((RealmCallback) callback).setText(realm);448} else if (callback instanceof RealmChoiceCallback) {449System.out.println("RealmChoiceCallback");450RealmChoiceCallback choice = (RealmChoiceCallback) callback;451if (realm == null) {452choice.setSelectedIndex(choice.getDefaultChoice());453} else {454String[] choices = choice.getChoices();455for (int j = 0; j < choices.length; j++) {456if (realm.equals(choices[j])) {457choice.setSelectedIndex(j);458break;459}460}461}462} else if (callback instanceof AuthorizeCallback) {463System.out.println("AuthorizeCallback");464((AuthorizeCallback) callback).setAuthorized(true);465if (authId == null || authId.trim().length() == 0) {466authId = userId;467}468((AuthorizeCallback) callback).setAuthorizedID(authId);469} else {470throw new UnsupportedCallbackException(callback);471}472}473}474}475476}477478479