All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.jmx.remote.opt.security.SASLClientHandler Maven / Gradle / Ivy

Go to download

A modification of the OpenDMK JMX remote implementation. Licensed under the Apache Software License v2.0 as permitted by the original CDDL license

There is a newer version: 1.2.1
Show newest version
/*
 * JPPF.
 * Copyright (C) 2005-2016 JPPF Team.
 * http://www.jppf.org
 *
 * 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.
 */
/*
 * @(#)file      SASLClientHandler.java
 * @(#)author    Sun Microsystems, Inc.
 * @(#)version   1.25
 * @(#)lastedit  07/03/08
 * @(#)build     @BUILD_TAG_PLACEHOLDER@
 *
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007 Sun Microsystems, Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the terms of either the GNU General
 * Public License Version 2 only ("GPL") or the Common Development and
 * Distribution License("CDDL")(collectively, the "License"). You may not use
 * this file except in compliance with the License. You can obtain a copy of the
 * License at http://opendmk.dev.java.net/legal_notices/licenses.txt or in the
 * LEGAL_NOTICES folder that accompanied this code. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file found at
 *     http://opendmk.dev.java.net/legal_notices/licenses.txt
 * or in the LEGAL_NOTICES folder that accompanied this code.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.
 *
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 *
 *       "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding
 *
 *       "[Contributor] elects to include this software in this distribution
 *        under the [CDDL or GPL Version 2] license."
 *
 * If you don't indicate a single choice of license, a recipient has the option
 * to distribute your version of this file under either the CDDL or the GPL
 * Version 2, or to extend the choice of license to its licensees as provided
 * above. However, if you add GPL Version 2 code and therefore, elected the
 * GPL Version 2 license, then the option applies only if the new code is made
 * subject to such option by the copyright holder.
 *
 */

package com.sun.jmx.remote.opt.security;

import java.io.IOException;
import java.net.Socket;
import java.util.*;

import javax.management.remote.generic.MessageConnection;
import javax.management.remote.message.*;
import javax.security.auth.callback.*;
import javax.security.sasl.*;

import com.sun.jmx.remote.generic.ProfileClient;
import com.sun.jmx.remote.opt.util.ClassLogger;
import com.sun.jmx.remote.socket.SocketConnectionIf;

/**
 * This class implements the client side SASL profile.
 */
public class SASLClientHandler implements ProfileClient {
  /**
   * 
   */
  private SaslClient saslClnt = null;
  /**
   * 
   */
  private boolean completed = false;
  /**
   * 
   */
  private boolean initialResponse = true;
  /**
   * 
   */
  private byte[] blob = null;
  /**
   * 
   */
  private Map env = null;
  /**
   * 
   */
  private MessageConnection mc = null;
  /**
   * 
   */
  private Socket socket = null;
  /**
   * 
   */
  private String mechanism = null;
  /**
   * 
   */
  private String profile = null;
  /**
   * 
   */
  private static final byte[] EMPTY = new byte[0];
  /**
   * 
   */
  private static final ClassLogger logger = new ClassLogger("javax.management.remote.misc", "SASLClientHandler");

  /**
   * 
   * @param profile .
   * @param env .
   */
  public SASLClientHandler(final String profile, final Map env) {
    this.env = env;
    this.profile = profile;
  }

  //---------------------------------------
  // ProfileClient interface implementation
  //---------------------------------------

  @Override
  @SuppressWarnings("unchecked")
  public void initialize(final MessageConnection mc) throws IOException {
    logger.trace("initialize", "starts");
    this.mc = mc;
    // Check if instance of SocketConnectionIf and retrieve underlying socket
    if (mc instanceof SocketConnectionIf) socket = ((SocketConnectionIf) mc).getSocket();
    else throw new IOException("Not an instance of SocketConnectionIf");

    // Prepare parameters for creating SASL client
    String mech = profile.substring(profile.indexOf("SASL/") + 5);
    String[] mechs = getSaslMechanismNames(mech);
    String authzId = (String) env.get("jmx.remote.sasl.authorization.id");
    String server = (String) env.get("jmx.remote.x.sasl.server.name");
    if (server == null) server = socket.getInetAddress().getHostName();
    if (logger.traceOn()) logger.trace("initialize", "mech=" + mech + "; mechs=" + Arrays.asList(mechs) + "; authzId=" + authzId + "; server=" + server);
    CallbackHandler cbh = null;
    if (env.containsKey("jmx.remote.sasl.callback.handler")) {
      cbh = (CallbackHandler) env.get("jmx.remote.sasl.callback.handler");
      if (logger.traceOn()) logger.trace("initialize", "found callback.handler property: " + cbh);
    } else {
      if (env.containsKey("jmx.remote.credentials")) {
        logger.trace("initialize", "found jmx.remote.credentials property");
        Object credso = env.get("jmx.remote.credentials");
        if (!(credso instanceof String[])) {
          if (logger.traceOn()) logger.trace("initialize", "...but it is not a String[]: " + credso);
        } else {
          String[] creds = (String[]) credso;
          if (creds.length != 2) {
            if (logger.traceOn()) logger.trace("initialize", "...but it does not have 2 " + "elements: " + Arrays.asList(creds));
          } else cbh = new UserPasswordCallbackHandler(creds[0], creds[1]);
        }
      }
    }
    // Create SASL client to use using SASL package
    saslClnt = Sasl.createSaslClient(mechs, authzId, "jmxmp", server, env, cbh);
    if (saslClnt == null) throw new IOException("Unable to create SASL client connection for " + "authentication mechanism [" + mech + "]");
    // Retrieve the SASL mechanism in use
    mechanism = saslClnt.getMechanismName();
  }

  @Override
  public ProfileMessage produceMessage() throws IOException {
    if (initialResponse) {
      blob = saslClnt.hasInitialResponse() ? saslClnt.evaluateChallenge(EMPTY) : EMPTY;
      initialResponse = false;
    }
    SASLMessage response = new SASLMessage(mechanism, SASLMessage.CONTINUE, blob);
    if (logger.traceOn()) {
      logger.trace("produceMessage", ">>>>> SASL client message <<<<<");
      logger.trace("produceMessage", "Profile Name : " + response.getProfileName());
      logger.trace("produceMessage", "Status : " + response.getStatus());
    }
    return response;
  }

  @Override
  public void consumeMessage(final ProfileMessage pm) throws IOException {
    if (!(pm instanceof SASLMessage)) throw new IOException("Unexpected profile message type: " + pm.getClass().getName());
    SASLMessage challenge = (SASLMessage) pm;
    if (logger.traceOn()) {
      logger.trace("consumeMessage", ">>>>> SASL server message <<<<<");
      logger.trace("consumeMessage", "Profile Name : " + challenge.getProfileName());
      logger.trace("consumeMessage", "Status : " + challenge.getStatus());
    }
    if (challenge.getStatus() != SASLMessage.CONTINUE && challenge.getStatus() != SASLMessage.COMPLETE)
      throw new IOException("Unexpected SASL status [" + challenge.getStatus() + "]");
    if (saslClnt.isComplete() && challenge.getStatus() == SASLMessage.COMPLETE) {
      completed = true;
      return;
    }
    if (saslClnt.isComplete() && challenge.getStatus() != SASLMessage.COMPLETE)
      throw new IOException("SASL authentication complete despite " + "the server claim for non-completion");
    if (!saslClnt.isComplete() && challenge.getStatus() == SASLMessage.COMPLETE) {
      blob = saslClnt.evaluateChallenge(challenge.getBlob());
      if (saslClnt.isComplete()) {
        completed = true;
        return;
      } else throw new IOException("SASL authentication not complete " + "despite the server claim for " + "completion");
    }
    if (!saslClnt.isComplete() && challenge.getStatus() != SASLMessage.COMPLETE) blob = saslClnt.evaluateChallenge(challenge.getBlob());
  }

  @Override
  public boolean isComplete() {
    return completed;
  }

  @Override
  public void activate() throws IOException {
    // If negotiated integrity or privacy
    //
    String qop = (String) saslClnt.getNegotiatedProperty(Sasl.QOP);
    if (qop != null && (qop.equalsIgnoreCase("auth-int") || qop.equalsIgnoreCase("auth-conf"))) {
      // Replace the current input/output streams in
      // MessageConnection by the SASL input/output streams
      //
      SASLInputStream saslis = new SASLInputStream(saslClnt, socket.getInputStream());
      SASLOutputStream saslos = new SASLOutputStream(saslClnt, socket.getOutputStream());
      ((SocketConnectionIf) mc).replaceStreams(saslis, saslos);
    }
  }

  @Override
  public void terminate() throws IOException {
    saslClnt.dispose();
  }

  @Override
  public String getName() {
    return profile;
  }

  /**
   * Returns an array of SASL mechanisms given a string of space separated SASL mechanism names.
   * @param str The non-null string containing the mechanism names
   * @return A non-null array of String; each element of the array contains a single mechanism name.
   */
  private static String[] getSaslMechanismNames(final String str) {
    StringTokenizer parser = new StringTokenizer(str);
    Vector mechanisms = new Vector<>(10);
    while (parser.hasMoreTokens()) {
      mechanisms.addElement(parser.nextToken());
    }
    return mechanisms.toArray(new String[mechanisms.size()]);
  }

  /**
   * Predefined CallbackHandler for when we get jmx.remote.credentials instead of jmx.remote.sasl.callback.handler. We make a valiant attempt not to hold on to the password any longer than we have to,
   * so that if someone can snoop our memory (perhaps by examining a core dump or the like) they can't fish out passwords. This probably isn't very successful, because the password is there in the
   * user's Map for at least as long as the handshake sequence lasts, and maybe for the entire lifetime of the connection.
   */
  private static class UserPasswordCallbackHandler implements CallbackHandler {
    /**
     * 
     */
    private String user;
    /**
     * 
     */
    private char[] pwchars;

    /**
     * 
     * @param user .
     * @param password .
     */
    UserPasswordCallbackHandler(final String user, final String password) {
      this.user = user;
      this.pwchars = password.toCharArray();
    }

    @Override
    public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
      for (int i = 0; i < callbacks.length; i++) {
        if (callbacks[i] instanceof NameCallback) {
          NameCallback ncb = (NameCallback) callbacks[i];
          ncb.setName(user);
        } else if (callbacks[i] instanceof PasswordCallback) {
          PasswordCallback pcb = (PasswordCallback) callbacks[i];
          pcb.setPassword(pwchars);
        } else {
          throw new UnsupportedCallbackException(callbacks[i]);
        }
      }
    }

    /**
     * 
     */
    private void clearPassword() {
      if (pwchars != null) {
        for (int i = 0; i < pwchars.length; i++)
          pwchars[i] = 0;
        pwchars = null;
      }
    }

    @Override
    protected void finalize() {
      clearPassword();
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy