jdk/test/javax/security/sasl/Sasl/ClientServerTest.java
changeset 31724 f6f1365b416f
equal deleted inserted replaced
31723:2e16a59cc5cb 31724:f6f1365b416f
       
     1 /*
       
     2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 import java.io.Closeable;
       
    25 import java.io.IOException;
       
    26 import java.io.ObjectInputStream;
       
    27 import java.io.ObjectOutputStream;
       
    28 import java.io.Serializable;
       
    29 import java.net.ServerSocket;
       
    30 import java.net.Socket;
       
    31 import java.net.UnknownHostException;
       
    32 import java.util.ArrayList;
       
    33 import java.util.Arrays;
       
    34 import java.util.HashMap;
       
    35 import java.util.Map;
       
    36 import java.util.StringJoiner;
       
    37 import javax.security.auth.callback.Callback;
       
    38 import javax.security.auth.callback.CallbackHandler;
       
    39 import javax.security.auth.callback.NameCallback;
       
    40 import javax.security.auth.callback.PasswordCallback;
       
    41 import javax.security.auth.callback.UnsupportedCallbackException;
       
    42 import javax.security.sasl.AuthorizeCallback;
       
    43 import javax.security.sasl.RealmCallback;
       
    44 import javax.security.sasl.RealmChoiceCallback;
       
    45 import javax.security.sasl.Sasl;
       
    46 import javax.security.sasl.SaslClient;
       
    47 import javax.security.sasl.SaslException;
       
    48 import javax.security.sasl.SaslServer;
       
    49 
       
    50 /*
       
    51  * @test
       
    52  * @bug 8049814
       
    53  * @summary JAVA SASL server and client tests with CRAM-MD5 and
       
    54  *          DIGEST-MD5 mechanisms. The tests try different QOP values on
       
    55  *          client and server side.
       
    56  * @modules java.security.sasl/javax.security.sasl
       
    57  */
       
    58 public class ClientServerTest {
       
    59 
       
    60     private static final int DELAY = 100;
       
    61     private static final String LOCALHOST = "localhost";
       
    62     private static final String DIGEST_MD5 = "DIGEST-MD5";
       
    63     private static final String CRAM_MD5 = "CRAM-MD5";
       
    64     private static final String PROTOCOL = "saslservice";
       
    65     private static final String USER_ID = "sasltester";
       
    66     private static final String PASSWD = "password";
       
    67     private static final String QOP_AUTH = "auth";
       
    68     private static final String QOP_AUTH_CONF = "auth-conf";
       
    69     private static final String QOP_AUTH_INT = "auth-int";
       
    70     private static final String AUTHID_SASL_TESTER = "sasl_tester";
       
    71     private static final ArrayList<String> SUPPORT_MECHS = new ArrayList<>();
       
    72 
       
    73     static {
       
    74         SUPPORT_MECHS.add(DIGEST_MD5);
       
    75         SUPPORT_MECHS.add(CRAM_MD5);
       
    76     }
       
    77 
       
    78     public static void main(String[] args) throws Exception {
       
    79         String[] allQops = { QOP_AUTH_CONF, QOP_AUTH_INT, QOP_AUTH };
       
    80         String[] twoQops = { QOP_AUTH_INT, QOP_AUTH };
       
    81         String[] authQop = { QOP_AUTH };
       
    82         String[] authIntQop = { QOP_AUTH_INT };
       
    83         String[] authConfQop = { QOP_AUTH_CONF };
       
    84         String[] emptyQop = {};
       
    85 
       
    86         boolean success = true;
       
    87 
       
    88         success &= runTest("", CRAM_MD5, new String[] { QOP_AUTH },
       
    89                 new String[] { QOP_AUTH }, false);
       
    90         success &= runTest("", DIGEST_MD5, new String[] { QOP_AUTH },
       
    91                 new String[] { QOP_AUTH }, false);
       
    92         success &= runTest(AUTHID_SASL_TESTER, DIGEST_MD5,
       
    93                 new String[] { QOP_AUTH }, new String[] { QOP_AUTH }, false);
       
    94         success &= runTest("", DIGEST_MD5, allQops, authQop, false);
       
    95         success &= runTest("", DIGEST_MD5, allQops, authIntQop, false);
       
    96         success &= runTest("", DIGEST_MD5, allQops, authConfQop, false);
       
    97         success &= runTest("", DIGEST_MD5, twoQops, authQop, false);
       
    98         success &= runTest("", DIGEST_MD5, twoQops, authIntQop, false);
       
    99         success &= runTest("", DIGEST_MD5, twoQops, authConfQop, true);
       
   100         success &= runTest("", DIGEST_MD5, authIntQop, authQop, true);
       
   101         success &= runTest("", DIGEST_MD5, authConfQop, authQop, true);
       
   102         success &= runTest("", DIGEST_MD5, authConfQop, emptyQop, true);
       
   103         success &= runTest("", DIGEST_MD5, authIntQop, emptyQop, true);
       
   104         success &= runTest("", DIGEST_MD5, authQop, emptyQop, true);
       
   105 
       
   106         if (!success) {
       
   107             throw new RuntimeException("At least one test case failed");
       
   108         }
       
   109 
       
   110         System.out.println("Test passed");
       
   111     }
       
   112 
       
   113     private static boolean runTest(String authId, String mech,
       
   114             String[] clientQops, String[] serverQops, boolean expectException)
       
   115             throws Exception {
       
   116 
       
   117         System.out.println("AuthId:" + authId
       
   118                 + " mechanism:" + mech
       
   119                 + " clientQops: " + Arrays.toString(clientQops)
       
   120                 + " serverQops: " + Arrays.toString(serverQops)
       
   121                 + " expect exception:" + expectException);
       
   122 
       
   123         try (Server server = Server.start(LOCALHOST, authId, serverQops)) {
       
   124             new Client(LOCALHOST, server.getPort(), mech, authId, clientQops)
       
   125                     .run();
       
   126             if (expectException) {
       
   127                 System.out.println("Expected exception not thrown");
       
   128                 return false;
       
   129             }
       
   130         } catch (SaslException e) {
       
   131             if (!expectException) {
       
   132                 System.out.println("Unexpected exception: " + e);
       
   133                 return false;
       
   134             }
       
   135             System.out.println("Expected exception: " + e);
       
   136         }
       
   137 
       
   138         return true;
       
   139     }
       
   140 
       
   141     static enum SaslStatus {
       
   142         SUCCESS, FAILURE, CONTINUE
       
   143     }
       
   144 
       
   145     static class Message implements Serializable {
       
   146 
       
   147         private final SaslStatus status;
       
   148         private final byte[] data;
       
   149 
       
   150         public Message(SaslStatus status, byte[] data) {
       
   151             this.status = status;
       
   152             this.data = data;
       
   153         }
       
   154 
       
   155         public SaslStatus getStatus() {
       
   156             return status;
       
   157         }
       
   158 
       
   159         public byte[] getData() {
       
   160             return data;
       
   161         }
       
   162     }
       
   163 
       
   164     static class SaslPeer {
       
   165 
       
   166         final String host;
       
   167         final String mechanism;
       
   168         final String qop;
       
   169         final CallbackHandler callback;
       
   170 
       
   171         SaslPeer(String host, String authId, String... qops) {
       
   172             this(host, null, authId, qops);
       
   173         }
       
   174 
       
   175         SaslPeer(String host, String mechanism, String authId, String... qops) {
       
   176             this.host = host;
       
   177             this.mechanism = mechanism;
       
   178 
       
   179             StringJoiner sj = new StringJoiner(",");
       
   180             for (String q : qops) {
       
   181                 sj.add(q);
       
   182             }
       
   183             qop = sj.toString();
       
   184 
       
   185             callback = new TestCallbackHandler(USER_ID, PASSWD, host, authId);
       
   186         }
       
   187 
       
   188         Message getMessage(Object ob) {
       
   189             if (!(ob instanceof Message)) {
       
   190                 throw new RuntimeException("Expected an instance of Message");
       
   191             }
       
   192             return (Message) ob;
       
   193         }
       
   194     }
       
   195 
       
   196     static class Server extends SaslPeer implements Runnable, Closeable {
       
   197 
       
   198         private volatile boolean ready = false;
       
   199         private volatile ServerSocket ssocket;
       
   200 
       
   201         static Server start(String host, String authId, String[] serverQops)
       
   202                 throws UnknownHostException {
       
   203             Server server = new Server(host, authId, serverQops);
       
   204             Thread thread = new Thread(server);
       
   205             thread.setDaemon(true);
       
   206             thread.start();
       
   207 
       
   208             while (!server.ready) {
       
   209                 try {
       
   210                     Thread.sleep(DELAY);
       
   211                 } catch (InterruptedException e) {
       
   212                     throw new RuntimeException(e);
       
   213                 }
       
   214             }
       
   215 
       
   216             return server;
       
   217         }
       
   218 
       
   219         Server(String host, String authId, String... qops) {
       
   220             super(host, authId, qops);
       
   221         }
       
   222 
       
   223         int getPort() {
       
   224             return ssocket.getLocalPort();
       
   225         }
       
   226 
       
   227         private void processConnection(SaslEndpoint endpoint)
       
   228                 throws SaslException, IOException, ClassNotFoundException {
       
   229             System.out.println("process connection");
       
   230             endpoint.send(SUPPORT_MECHS);
       
   231             Object o = endpoint.receive();
       
   232             if (!(o instanceof String)) {
       
   233                 throw new RuntimeException("Received unexpected object: " + o);
       
   234             }
       
   235             String mech = (String) o;
       
   236             SaslServer saslServer = createSaslServer(mech);
       
   237             Message msg = getMessage(endpoint.receive());
       
   238             while (!saslServer.isComplete()) {
       
   239                 byte[] data = processData(msg.getData(), endpoint,
       
   240                         saslServer);
       
   241                 if (saslServer.isComplete()) {
       
   242                     System.out.println("server is complete");
       
   243                     endpoint.send(new Message(SaslStatus.SUCCESS, data));
       
   244                 } else {
       
   245                     System.out.println("server continues");
       
   246                     endpoint.send(new Message(SaslStatus.CONTINUE, data));
       
   247                     msg = getMessage(endpoint.receive());
       
   248                 }
       
   249             }
       
   250         }
       
   251 
       
   252         private byte[] processData(byte[] data, SaslEndpoint endpoint,
       
   253                 SaslServer server) throws SaslException, IOException {
       
   254             try {
       
   255                 return server.evaluateResponse(data);
       
   256             } catch (SaslException e) {
       
   257                 endpoint.send(new Message(SaslStatus.FAILURE, null));
       
   258                 System.out.println("Error while processing data");
       
   259                 throw e;
       
   260             }
       
   261         }
       
   262 
       
   263         private SaslServer createSaslServer(String mechanism)
       
   264                 throws SaslException {
       
   265             Map<String, String> props = new HashMap<>();
       
   266             props.put(Sasl.QOP, qop);
       
   267             return Sasl.createSaslServer(mechanism, PROTOCOL, host, props,
       
   268                     callback);
       
   269         }
       
   270 
       
   271         @Override
       
   272         public void run() {
       
   273             try (ServerSocket ss = new ServerSocket(0)) {
       
   274                 ssocket = ss;
       
   275                 System.out.println("server started on port " + getPort());
       
   276                 ready = true;
       
   277                 Socket socket = ss.accept();
       
   278                 try (SaslEndpoint endpoint = new SaslEndpoint(socket)) {
       
   279                     System.out.println("server accepted connection");
       
   280                     processConnection(endpoint);
       
   281                 }
       
   282             } catch (Exception e) {
       
   283                 // ignore it for now, client will throw an exception
       
   284             }
       
   285         }
       
   286 
       
   287         @Override
       
   288         public void close() throws IOException {
       
   289             if (!ssocket.isClosed()) {
       
   290                 ssocket.close();
       
   291             }
       
   292         }
       
   293     }
       
   294 
       
   295     static class Client extends SaslPeer {
       
   296 
       
   297         private final int port;
       
   298 
       
   299         Client(String host, int port, String mech, String authId,
       
   300                 String... qops) {
       
   301             super(host, mech, authId, qops);
       
   302             this.port = port;
       
   303         }
       
   304 
       
   305         public void run() throws Exception {
       
   306             System.out.println("Host:" + host + " port: "
       
   307                     + port);
       
   308             try (SaslEndpoint endpoint = SaslEndpoint.create(host, port)) {
       
   309                 negotiateMechanism(endpoint);
       
   310                 SaslClient client = createSaslClient();
       
   311                 byte[] data = new byte[0];
       
   312                 if (client.hasInitialResponse()) {
       
   313                     data = client.evaluateChallenge(data);
       
   314                 }
       
   315                 endpoint.send(new Message(SaslStatus.CONTINUE, data));
       
   316                 Message msg = getMessage(endpoint.receive());
       
   317                 while (!client.isComplete()
       
   318                         && msg.getStatus() != SaslStatus.FAILURE) {
       
   319                     switch (msg.getStatus()) {
       
   320                         case CONTINUE:
       
   321                             System.out.println("client continues");
       
   322                             data = client.evaluateChallenge(msg.getData());
       
   323                             endpoint.send(new Message(SaslStatus.CONTINUE,
       
   324                                     data));
       
   325                             msg = getMessage(endpoint.receive());
       
   326                             break;
       
   327                         case SUCCESS:
       
   328                             System.out.println("client succeeded");
       
   329                             data = client.evaluateChallenge(msg.getData());
       
   330                             if (data != null) {
       
   331                                 throw new SaslException("data should be null");
       
   332                             }
       
   333                             break;
       
   334                         default:
       
   335                             throw new RuntimeException("Wrong status:"
       
   336                                     + msg.getStatus());
       
   337                     }
       
   338                 }
       
   339 
       
   340                 if (msg.getStatus() == SaslStatus.FAILURE) {
       
   341                     throw new RuntimeException("Status is FAILURE");
       
   342                 }
       
   343             }
       
   344 
       
   345             System.out.println("Done");
       
   346         }
       
   347 
       
   348         private SaslClient createSaslClient() throws SaslException {
       
   349             Map<String, String> props = new HashMap<>();
       
   350             props.put(Sasl.QOP, qop);
       
   351             return Sasl.createSaslClient(new String[] {mechanism}, USER_ID,
       
   352                     PROTOCOL, host, props, callback);
       
   353         }
       
   354 
       
   355         private void negotiateMechanism(SaslEndpoint endpoint)
       
   356                 throws ClassNotFoundException, IOException {
       
   357             Object o = endpoint.receive();
       
   358             if (o instanceof ArrayList) {
       
   359                 ArrayList list = (ArrayList) o;
       
   360                 if (!list.contains(mechanism)) {
       
   361                     throw new RuntimeException(
       
   362                             "Server does not support specified mechanism:"
       
   363                                     + mechanism);
       
   364                 }
       
   365             } else {
       
   366                 throw new RuntimeException(
       
   367                         "Expected an instance of ArrayList, but received " + o);
       
   368             }
       
   369 
       
   370             endpoint.send(mechanism);
       
   371         }
       
   372 
       
   373     }
       
   374 
       
   375     static class SaslEndpoint implements AutoCloseable {
       
   376 
       
   377         private final Socket socket;
       
   378         private ObjectInputStream input;
       
   379         private ObjectOutputStream output;
       
   380 
       
   381         static SaslEndpoint create(String host, int port) throws IOException {
       
   382             return new SaslEndpoint(new Socket(host, port));
       
   383         }
       
   384 
       
   385         SaslEndpoint(Socket socket) throws IOException {
       
   386             this.socket = socket;
       
   387         }
       
   388 
       
   389         private ObjectInputStream getInput() throws IOException {
       
   390             if (input == null && socket != null) {
       
   391                 input = new ObjectInputStream(socket.getInputStream());
       
   392             }
       
   393             return input;
       
   394         }
       
   395 
       
   396         private ObjectOutputStream getOutput() throws IOException {
       
   397             if (output == null && socket != null) {
       
   398                 output = new ObjectOutputStream(socket.getOutputStream());
       
   399             }
       
   400             return output;
       
   401         }
       
   402 
       
   403         public Object receive() throws IOException, ClassNotFoundException {
       
   404             return getInput().readObject();
       
   405         }
       
   406 
       
   407         public void send(Object obj) throws IOException {
       
   408             getOutput().writeObject(obj);
       
   409             getOutput().flush();
       
   410         }
       
   411 
       
   412         @Override
       
   413         public void close() throws IOException {
       
   414             if (socket != null && !socket.isClosed()) {
       
   415                 socket.close();
       
   416             }
       
   417         }
       
   418 
       
   419     }
       
   420 
       
   421     static class TestCallbackHandler implements CallbackHandler {
       
   422 
       
   423         private final String userId;
       
   424         private final char[] passwd;
       
   425         private final String realm;
       
   426         private String authId;
       
   427 
       
   428         TestCallbackHandler(String userId, String passwd, String realm,
       
   429                 String authId) {
       
   430             this.userId = userId;
       
   431             this.passwd = passwd.toCharArray();
       
   432             this.realm = realm;
       
   433             this.authId = authId;
       
   434         }
       
   435 
       
   436         @Override
       
   437         public void handle(Callback[] callbacks) throws IOException,
       
   438                 UnsupportedCallbackException {
       
   439             for (Callback callback : callbacks) {
       
   440                 if (callback instanceof NameCallback) {
       
   441                     System.out.println("NameCallback");
       
   442                     ((NameCallback) callback).setName(userId);
       
   443                 } else if (callback instanceof PasswordCallback) {
       
   444                     System.out.println("PasswordCallback");
       
   445                     ((PasswordCallback) callback).setPassword(passwd);
       
   446                 } else if (callback instanceof RealmCallback) {
       
   447                     System.out.println("RealmCallback");
       
   448                     ((RealmCallback) callback).setText(realm);
       
   449                 } else if (callback instanceof RealmChoiceCallback) {
       
   450                     System.out.println("RealmChoiceCallback");
       
   451                     RealmChoiceCallback choice = (RealmChoiceCallback) callback;
       
   452                     if (realm == null) {
       
   453                         choice.setSelectedIndex(choice.getDefaultChoice());
       
   454                     } else {
       
   455                         String[] choices = choice.getChoices();
       
   456                         for (int j = 0; j < choices.length; j++) {
       
   457                             if (realm.equals(choices[j])) {
       
   458                                 choice.setSelectedIndex(j);
       
   459                                 break;
       
   460                             }
       
   461                         }
       
   462                     }
       
   463                 } else if (callback instanceof AuthorizeCallback) {
       
   464                     System.out.println("AuthorizeCallback");
       
   465                     ((AuthorizeCallback) callback).setAuthorized(true);
       
   466                     if (authId == null || authId.trim().length() == 0) {
       
   467                         authId = userId;
       
   468                     }
       
   469                     ((AuthorizeCallback) callback).setAuthorizedID(authId);
       
   470                 } else {
       
   471                     throw new UnsupportedCallbackException(callback);
       
   472                 }
       
   473             }
       
   474         }
       
   475     }
       
   476 
       
   477 }