| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| package org.apache.hadoop.hdfs.protocol.datatransfer.sasl; |
| |
| import static org.apache.hadoop.hdfs.client.HdfsClientConfigKeys.DFS_ENCRYPT_DATA_TRANSFER_CIPHER_SUITES_KEY; |
| import static org.apache.hadoop.hdfs.client.HdfsClientConfigKeys.DFS_ENCRYPT_DATA_OVERWRITE_DOWNSTREAM_NEW_QOP_KEY; |
| import static org.apache.hadoop.hdfs.protocol.datatransfer.sasl.DataTransferSaslUtil.*; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import java.io.ByteArrayInputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.InetAddress; |
| import java.net.Socket; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import javax.crypto.SecretKey; |
| import javax.security.auth.callback.Callback; |
| import javax.security.auth.callback.CallbackHandler; |
| import javax.security.auth.callback.NameCallback; |
| import javax.security.auth.callback.PasswordCallback; |
| import javax.security.auth.callback.UnsupportedCallbackException; |
| import javax.security.sasl.RealmCallback; |
| import javax.security.sasl.RealmChoiceCallback; |
| |
| import javax.security.sasl.Sasl; |
| import org.apache.commons.codec.binary.Base64; |
| import org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.crypto.CipherOption; |
| import org.apache.hadoop.crypto.CipherSuite; |
| import org.apache.hadoop.hdfs.net.EncryptedPeer; |
| import org.apache.hadoop.hdfs.net.Peer; |
| import org.apache.hadoop.hdfs.protocol.DatanodeID; |
| import org.apache.hadoop.hdfs.protocol.datatransfer.IOStreamPair; |
| import org.apache.hadoop.hdfs.protocol.datatransfer.TrustedChannelResolver; |
| import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier; |
| import org.apache.hadoop.hdfs.security.token.block.DataEncryptionKey; |
| import org.apache.hadoop.security.SaslPropertiesResolver; |
| import org.apache.hadoop.security.SecurityUtil; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.security.token.SecretManager; |
| import org.apache.hadoop.security.token.Token; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.base.Charsets; |
| import com.google.common.collect.Lists; |
| |
| /** |
| * Negotiates SASL for DataTransferProtocol on behalf of a client. There are |
| * two possible supported variants of SASL negotiation: either a general-purpose |
| * negotiation supporting any quality of protection, or a specialized |
| * negotiation that enforces privacy as the quality of protection using a |
| * cryptographically strong encryption key. |
| * |
| * This class is used in both the HDFS client and the DataNode. The DataNode |
| * needs it, because it acts as a client to other DataNodes during write |
| * pipelines and block transfers. |
| */ |
| @InterfaceAudience.Private |
| public class SaslDataTransferClient { |
| |
| private static final Logger LOG = LoggerFactory.getLogger( |
| SaslDataTransferClient.class); |
| |
| private final Configuration conf; |
| private final AtomicBoolean fallbackToSimpleAuth; |
| private final SaslPropertiesResolver saslPropsResolver; |
| private final TrustedChannelResolver trustedChannelResolver; |
| |
| // Store the most recent successfully negotiated QOP, |
| // for testing purpose only |
| private String targetQOP; |
| |
| /** |
| * Creates a new SaslDataTransferClient. This constructor is used in cases |
| * where it is not relevant to track if a secure client did a fallback to |
| * simple auth. For intra-cluster connections between data nodes in the same |
| * cluster, we can assume that all run under the same security configuration. |
| * |
| * @param conf the configuration |
| * @param saslPropsResolver for determining properties of SASL negotiation |
| * @param trustedChannelResolver for identifying trusted connections that do |
| * not require SASL negotiation |
| */ |
| public SaslDataTransferClient(Configuration conf, |
| SaslPropertiesResolver saslPropsResolver, |
| TrustedChannelResolver trustedChannelResolver) { |
| this(conf, saslPropsResolver, trustedChannelResolver, null); |
| } |
| |
| /** |
| * Creates a new SaslDataTransferClient. |
| * |
| * @param conf the configuration |
| * @param saslPropsResolver for determining properties of SASL negotiation |
| * @param trustedChannelResolver for identifying trusted connections that do |
| * not require SASL negotiation |
| * @param fallbackToSimpleAuth checked on each attempt at general SASL |
| * handshake, if true forces use of simple auth |
| */ |
| public SaslDataTransferClient(Configuration conf, |
| SaslPropertiesResolver saslPropsResolver, |
| TrustedChannelResolver trustedChannelResolver, |
| AtomicBoolean fallbackToSimpleAuth) { |
| this.conf = conf; |
| this.fallbackToSimpleAuth = fallbackToSimpleAuth; |
| this.saslPropsResolver = saslPropsResolver; |
| this.trustedChannelResolver = trustedChannelResolver; |
| } |
| |
| /** |
| * Sends client SASL negotiation for a newly allocated socket if required. |
| * |
| * @param socket connection socket |
| * @param underlyingOut connection output stream |
| * @param underlyingIn connection input stream |
| * @param encryptionKeyFactory for creation of an encryption key |
| * @param accessToken connection block access token |
| * @param datanodeId ID of destination DataNode |
| * @return new pair of streams, wrapped after SASL negotiation |
| * @throws IOException for any error |
| */ |
| public IOStreamPair newSocketSend(Socket socket, OutputStream underlyingOut, |
| InputStream underlyingIn, DataEncryptionKeyFactory encryptionKeyFactory, |
| Token<BlockTokenIdentifier> accessToken, DatanodeID datanodeId) |
| throws IOException { |
| // The encryption key factory only returns a key if encryption is enabled. |
| DataEncryptionKey encryptionKey = !trustedChannelResolver.isTrusted() ? |
| encryptionKeyFactory.newDataEncryptionKey() : null; |
| IOStreamPair ios = send(socket.getInetAddress(), underlyingOut, |
| underlyingIn, encryptionKey, accessToken, datanodeId, null); |
| return ios != null ? ios : new IOStreamPair(underlyingIn, underlyingOut); |
| } |
| |
| /** |
| * Sends client SASL negotiation for a peer if required. |
| * |
| * @param peer connection peer |
| * @param encryptionKeyFactory for creation of an encryption key |
| * @param accessToken connection block access token |
| * @param datanodeId ID of destination DataNode |
| * @return new pair of streams, wrapped after SASL negotiation |
| * @throws IOException for any error |
| */ |
| public Peer peerSend(Peer peer, DataEncryptionKeyFactory encryptionKeyFactory, |
| Token<BlockTokenIdentifier> accessToken, DatanodeID datanodeId) |
| throws IOException { |
| IOStreamPair ios = checkTrustAndSend(getPeerAddress(peer), |
| peer.getOutputStream(), peer.getInputStream(), encryptionKeyFactory, |
| accessToken, datanodeId); |
| // TODO: Consider renaming EncryptedPeer to SaslPeer. |
| return ios != null ? new EncryptedPeer(peer, ios) : peer; |
| } |
| |
| /** |
| * Sends client SASL negotiation for a socket if required. |
| * |
| * @param socket connection socket |
| * @param underlyingOut connection output stream |
| * @param underlyingIn connection input stream |
| * @param encryptionKeyFactory for creation of an encryption key |
| * @param accessToken connection block access token |
| * @param datanodeId ID of destination DataNode |
| * @return new pair of streams, wrapped after SASL negotiation |
| * @throws IOException for any error |
| */ |
| public IOStreamPair socketSend(Socket socket, OutputStream underlyingOut, |
| InputStream underlyingIn, DataEncryptionKeyFactory encryptionKeyFactory, |
| Token<BlockTokenIdentifier> accessToken, DatanodeID datanodeId) |
| throws IOException { |
| return socketSend(socket, underlyingOut, underlyingIn, encryptionKeyFactory, |
| accessToken, datanodeId, null); |
| } |
| |
| public IOStreamPair socketSend( |
| Socket socket, OutputStream underlyingOut, InputStream underlyingIn, |
| DataEncryptionKeyFactory encryptionKeyFactory, |
| Token<BlockTokenIdentifier> accessToken, DatanodeID datanodeId, |
| SecretKey secretKey) |
| throws IOException { |
| IOStreamPair ios = checkTrustAndSend(socket.getInetAddress(), underlyingOut, |
| underlyingIn, encryptionKeyFactory, accessToken, datanodeId, |
| secretKey); |
| return ios != null ? ios : new IOStreamPair(underlyingIn, underlyingOut); |
| } |
| |
| /** |
| * Checks if an address is already trusted and then sends client SASL |
| * negotiation if required. |
| * |
| * @param addr connection address |
| * @param underlyingOut connection output stream |
| * @param underlyingIn connection input stream |
| * @param encryptionKeyFactory for creation of an encryption key |
| * @param accessToken connection block access token |
| * @param datanodeId ID of destination DataNode |
| * @return new pair of streams, wrapped after SASL negotiation |
| * @throws IOException for any error |
| */ |
| private IOStreamPair checkTrustAndSend(InetAddress addr, |
| OutputStream underlyingOut, InputStream underlyingIn, |
| DataEncryptionKeyFactory encryptionKeyFactory, |
| Token<BlockTokenIdentifier> accessToken, DatanodeID datanodeId) |
| throws IOException { |
| return checkTrustAndSend(addr, underlyingOut, underlyingIn, |
| encryptionKeyFactory, accessToken, datanodeId, null); |
| } |
| |
| private IOStreamPair checkTrustAndSend( |
| InetAddress addr, OutputStream underlyingOut, InputStream underlyingIn, |
| DataEncryptionKeyFactory encryptionKeyFactory, |
| Token<BlockTokenIdentifier> accessToken, DatanodeID datanodeId, |
| SecretKey secretKey) |
| throws IOException { |
| boolean localTrusted = trustedChannelResolver.isTrusted(); |
| boolean remoteTrusted = trustedChannelResolver.isTrusted(addr); |
| LOG.info("SASL encryption trust check: localHostTrusted = {}, " |
| + "remoteHostTrusted = {}", localTrusted, remoteTrusted); |
| if (!localTrusted || !remoteTrusted) { |
| // The encryption key factory only returns a key if encryption is enabled. |
| DataEncryptionKey encryptionKey = |
| encryptionKeyFactory.newDataEncryptionKey(); |
| return send(addr, underlyingOut, underlyingIn, encryptionKey, accessToken, |
| datanodeId, secretKey); |
| } else { |
| LOG.debug( |
| "SASL client skipping handshake on trusted connection for addr = {}, " |
| + "datanodeId = {}", addr, datanodeId); |
| return null; |
| } |
| } |
| |
| /** |
| * Sends client SASL negotiation if required. Determines the correct type of |
| * SASL handshake based on configuration. |
| * |
| * @param addr connection address |
| * @param underlyingOut connection output stream |
| * @param underlyingIn connection input stream |
| * @param encryptionKey for an encrypted SASL handshake |
| * @param accessToken connection block access token |
| * @param datanodeId ID of destination DataNode |
| * @return new pair of streams, wrapped after SASL negotiation |
| * @throws IOException for any error |
| */ |
| private IOStreamPair send(InetAddress addr, OutputStream underlyingOut, |
| InputStream underlyingIn, DataEncryptionKey encryptionKey, |
| Token<BlockTokenIdentifier> accessToken, DatanodeID datanodeId, |
| SecretKey secretKey) |
| throws IOException { |
| if (encryptionKey != null) { |
| LOG.debug("SASL client doing encrypted handshake for addr = {}, " |
| + "datanodeId = {}", addr, datanodeId); |
| return getEncryptedStreams(addr, underlyingOut, underlyingIn, |
| encryptionKey, accessToken, secretKey); |
| } else if (!UserGroupInformation.isSecurityEnabled()) { |
| LOG.debug("SASL client skipping handshake in unsecured configuration for " |
| + "addr = {}, datanodeId = {}", addr, datanodeId); |
| return null; |
| } else if (SecurityUtil.isPrivilegedPort(datanodeId.getXferPort())) { |
| LOG.debug( |
| "SASL client skipping handshake in secured configuration with " |
| + "privileged port for addr = {}, datanodeId = {}", |
| addr, datanodeId); |
| return null; |
| } else if (fallbackToSimpleAuth != null && fallbackToSimpleAuth.get()) { |
| LOG.debug( |
| "SASL client skipping handshake in secured configuration with " |
| + "unsecured cluster for addr = {}, datanodeId = {}", |
| addr, datanodeId); |
| return null; |
| } else if (saslPropsResolver != null) { |
| LOG.debug( |
| "SASL client doing general handshake for addr = {}, datanodeId = {}", |
| addr, datanodeId); |
| return getSaslStreams(addr, underlyingOut, underlyingIn, |
| accessToken, secretKey); |
| } else { |
| // It's a secured cluster using non-privileged ports, but no SASL. The |
| // only way this can happen is if the DataNode has |
| // ignore.secure.ports.for.testing configured so this is a rare edge case. |
| LOG.debug("SASL client skipping handshake in secured configuration with " |
| + "no SASL protection configured for addr = {}, datanodeId = {}", |
| addr, datanodeId); |
| return null; |
| } |
| } |
| |
| /** |
| * Sends client SASL negotiation for specialized encrypted handshake. |
| * |
| * @param addr connection address |
| * @param underlyingOut connection output stream |
| * @param underlyingIn connection input stream |
| * @param encryptionKey for an encrypted SASL handshake |
| * @return new pair of streams, wrapped after SASL negotiation |
| * @throws IOException for any error |
| */ |
| private IOStreamPair getEncryptedStreams(InetAddress addr, |
| OutputStream underlyingOut, InputStream underlyingIn, |
| DataEncryptionKey encryptionKey, |
| Token<BlockTokenIdentifier> accessToken, |
| SecretKey secretKey) |
| throws IOException { |
| Map<String, String> saslProps = createSaslPropertiesForEncryption( |
| encryptionKey.encryptionAlgorithm); |
| if (secretKey != null) { |
| LOG.debug("DataNode overwriting downstream QOP" + |
| saslProps.get(Sasl.QOP)); |
| updateToken(accessToken, secretKey, saslProps); |
| } |
| |
| LOG.debug("Client using encryption algorithm {}", |
| encryptionKey.encryptionAlgorithm); |
| |
| String userName = getUserNameFromEncryptionKey(encryptionKey); |
| char[] password = encryptionKeyToPassword(encryptionKey.encryptionKey); |
| CallbackHandler callbackHandler = new SaslClientCallbackHandler(userName, |
| password); |
| return doSaslHandshake(addr, underlyingOut, underlyingIn, userName, |
| saslProps, callbackHandler, accessToken); |
| } |
| |
| /** |
| * The SASL username for an encrypted handshake consists of the keyId, |
| * blockPoolId, and nonce with the first two encoded as Strings, and the third |
| * encoded using Base64. The fields are each separated by a single space. |
| * |
| * @param encryptionKey the encryption key to encode as a SASL username. |
| * @return encoded username containing keyId, blockPoolId, and nonce |
| */ |
| private static String getUserNameFromEncryptionKey( |
| DataEncryptionKey encryptionKey) { |
| return encryptionKey.keyId + NAME_DELIMITER + |
| encryptionKey.blockPoolId + NAME_DELIMITER + |
| new String(Base64.encodeBase64(encryptionKey.nonce, false), |
| Charsets.UTF_8); |
| } |
| |
| /** |
| * Sets user name and password when asked by the client-side SASL object. |
| */ |
| private static final class SaslClientCallbackHandler |
| implements CallbackHandler { |
| |
| private final char[] password; |
| private final String userName; |
| |
| /** |
| * Creates a new SaslClientCallbackHandler. |
| * |
| * @param userName SASL user name |
| * @param password SASL password |
| */ |
| public SaslClientCallbackHandler(String userName, char[] password) { |
| this.password = password; |
| this.userName = userName; |
| } |
| |
| @Override |
| public void handle(Callback[] callbacks) throws IOException, |
| UnsupportedCallbackException { |
| NameCallback nc = null; |
| PasswordCallback pc = null; |
| RealmCallback rc = null; |
| for (Callback callback : callbacks) { |
| if (callback instanceof NameCallback) { |
| nc = (NameCallback) callback; |
| } else if (callback instanceof PasswordCallback) { |
| pc = (PasswordCallback) callback; |
| } else if (callback instanceof RealmCallback) { |
| rc = (RealmCallback) callback; |
| } else if (!(callback instanceof RealmChoiceCallback)) { |
| throw new UnsupportedCallbackException(callback, |
| "Unrecognized SASL client callback"); |
| } |
| } |
| if (nc != null) { |
| nc.setName(userName); |
| } |
| if (pc != null) { |
| pc.setPassword(password); |
| } |
| if (rc != null) { |
| rc.setText(rc.getDefaultText()); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| public String getTargetQOP() { |
| return targetQOP; |
| } |
| |
| /** |
| * Sends client SASL negotiation for general-purpose handshake. |
| * |
| * @param addr connection address |
| * @param underlyingOut connection output stream |
| * @param underlyingIn connection input stream |
| * @param accessToken connection block access token |
| * @return new pair of streams, wrapped after SASL negotiation |
| * @throws IOException for any error |
| */ |
| private IOStreamPair getSaslStreams(InetAddress addr, |
| OutputStream underlyingOut, InputStream underlyingIn, |
| Token<BlockTokenIdentifier> accessToken, |
| SecretKey secretKey) |
| throws IOException { |
| Map<String, String> saslProps = saslPropsResolver.getClientProperties(addr); |
| |
| // secretKey != null only happens when this is called by DN |
| // sending to downstream DN. If called from client, this will be null, |
| // as there is no key for client to generate mac instance. |
| // So that, if a different QOP is desired for inter-DN communication, |
| // the check below will use new QOP to create a secret, which includes |
| // the new QOP. |
| if (secretKey != null) { |
| String newQOP = conf |
| .get(DFS_ENCRYPT_DATA_OVERWRITE_DOWNSTREAM_NEW_QOP_KEY); |
| if (newQOP != null) { |
| saslProps.put(Sasl.QOP, newQOP); |
| } |
| LOG.debug("DataNode overwriting downstream QOP " + |
| saslProps.get(Sasl.QOP)); |
| updateToken(accessToken, secretKey, saslProps); |
| } |
| targetQOP = saslProps.get(Sasl.QOP); |
| String userName = buildUserName(accessToken); |
| char[] password = buildClientPassword(accessToken); |
| CallbackHandler callbackHandler = new SaslClientCallbackHandler(userName, |
| password); |
| return doSaslHandshake(addr, underlyingOut, underlyingIn, userName, |
| saslProps, callbackHandler, accessToken); |
| } |
| |
| private void updateToken(Token<BlockTokenIdentifier> accessToken, |
| SecretKey secretKey, Map<String, String> saslProps) |
| throws IOException { |
| byte[] newSecret = saslProps.get(Sasl.QOP).getBytes(Charsets.UTF_8); |
| BlockTokenIdentifier bkid = accessToken.decodeIdentifier(); |
| bkid.setHandshakeMsg(newSecret); |
| byte[] bkidBytes = bkid.getBytes(); |
| accessToken.setPassword( |
| SecretManager.createPassword(bkidBytes, secretKey)); |
| accessToken.setID(bkidBytes); |
| } |
| |
| /** |
| * Builds the client's user name for the general-purpose handshake, consisting |
| * of the base64-encoded serialized block access token identifier. Note that |
| * this includes only the token identifier, not the token itself, which would |
| * include the password. The password is a shared secret, and we must not |
| * write it on the network during the SASL authentication exchange. |
| * |
| * @param blockToken for block access |
| * @return SASL user name |
| */ |
| private static String buildUserName(Token<BlockTokenIdentifier> blockToken) { |
| return new String(Base64.encodeBase64(blockToken.getIdentifier(), false), |
| Charsets.UTF_8); |
| } |
| |
| /** |
| * Calculates the password on the client side for the general-purpose |
| * handshake. The password consists of the block access token's password. |
| * |
| * @param blockToken for block access |
| * @return SASL password |
| */ |
| private char[] buildClientPassword(Token<BlockTokenIdentifier> blockToken) { |
| return new String(Base64.encodeBase64(blockToken.getPassword(), false), |
| Charsets.UTF_8).toCharArray(); |
| } |
| |
| /** |
| * This method actually executes the client-side SASL handshake. |
| * |
| * @param addr connection address |
| * @param underlyingOut connection output stream |
| * @param underlyingIn connection input stream |
| * @param userName SASL user name |
| * @param saslProps properties of SASL negotiation |
| * @param callbackHandler for responding to SASL callbacks |
| * @return new pair of streams, wrapped after SASL negotiation |
| * @throws IOException for any error |
| */ |
| private IOStreamPair doSaslHandshake(InetAddress addr, |
| OutputStream underlyingOut, InputStream underlyingIn, String userName, |
| Map<String, String> saslProps, CallbackHandler callbackHandler, |
| Token<BlockTokenIdentifier> accessToken) throws IOException { |
| |
| DataOutputStream out = new DataOutputStream(underlyingOut); |
| DataInputStream in = new DataInputStream(underlyingIn); |
| |
| SaslParticipant sasl= SaslParticipant.createClientSaslParticipant(userName, |
| saslProps, callbackHandler); |
| |
| out.writeInt(SASL_TRANSFER_MAGIC_NUMBER); |
| out.flush(); |
| |
| try { |
| // Start of handshake - "initial response" in SASL terminology. |
| // The handshake secret can be null, this happens when client is running |
| // a new version but the cluster does not have this feature. |
| // In which case there will be no encrypted secret sent from NN. |
| BlockTokenIdentifier blockTokenIdentifier = |
| accessToken.decodeIdentifier(); |
| if (blockTokenIdentifier != null) { |
| byte[] handshakeSecret = |
| accessToken.decodeIdentifier().getHandshakeMsg(); |
| if (handshakeSecret == null || handshakeSecret.length == 0) { |
| LOG.debug("Handshake secret is null, " |
| + "sending without handshake secret."); |
| sendSaslMessage(out, new byte[0]); |
| } else { |
| LOG.debug("Sending handshake secret."); |
| BlockTokenIdentifier identifier = new BlockTokenIdentifier(); |
| identifier.readFields(new DataInputStream( |
| new ByteArrayInputStream(accessToken.getIdentifier()))); |
| String bpid = identifier.getBlockPoolId(); |
| sendSaslMessageHandshakeSecret(out, new byte[0], |
| handshakeSecret, bpid); |
| } |
| } else { |
| LOG.debug("Block token id is null, sending without handshake secret."); |
| sendSaslMessage(out, new byte[0]); |
| } |
| |
| // step 1 |
| byte[] remoteResponse = readSaslMessage(in); |
| byte[] localResponse = sasl.evaluateChallengeOrResponse(remoteResponse); |
| List<CipherOption> cipherOptions = null; |
| String cipherSuites = conf.get( |
| DFS_ENCRYPT_DATA_TRANSFER_CIPHER_SUITES_KEY); |
| if (requestedQopContainsPrivacy(saslProps)) { |
| // Negotiate cipher suites if configured. Currently, the only supported |
| // cipher suite is AES/CTR/NoPadding, but the protocol allows multiple |
| // values for future expansion. |
| if (cipherSuites != null && !cipherSuites.isEmpty()) { |
| if (!cipherSuites.equals(CipherSuite.AES_CTR_NOPADDING.getName())) { |
| throw new IOException(String.format("Invalid cipher suite, %s=%s", |
| DFS_ENCRYPT_DATA_TRANSFER_CIPHER_SUITES_KEY, cipherSuites)); |
| } |
| CipherOption option = new CipherOption(CipherSuite.AES_CTR_NOPADDING); |
| cipherOptions = Lists.newArrayListWithCapacity(1); |
| cipherOptions.add(option); |
| } |
| } |
| sendSaslMessageAndNegotiationCipherOptions(out, localResponse, |
| cipherOptions); |
| |
| // step 2 (client-side only) |
| SaslResponseWithNegotiatedCipherOption response = |
| readSaslMessageAndNegotiatedCipherOption(in); |
| localResponse = sasl.evaluateChallengeOrResponse(response.payload); |
| assert localResponse == null; |
| |
| // SASL handshake is complete |
| checkSaslComplete(sasl, saslProps); |
| |
| CipherOption cipherOption = null; |
| if (sasl.isNegotiatedQopPrivacy()) { |
| // Unwrap the negotiated cipher option |
| cipherOption = unwrap(response.cipherOption, sasl); |
| if (LOG.isDebugEnabled()) { |
| if (cipherOption == null) { |
| // No cipher suite is negotiated |
| if (cipherSuites != null && !cipherSuites.isEmpty()) { |
| // the client accepts some cipher suites, but the server does not. |
| LOG.debug("Client accepts cipher suites {}, " |
| + "but server {} does not accept any of them", |
| cipherSuites, addr.toString()); |
| } |
| } else { |
| LOG.debug("Client using cipher suite {} with server {}", |
| cipherOption.getCipherSuite().getName(), addr.toString()); |
| } |
| } |
| } |
| |
| // If negotiated cipher option is not null, we will use it to create |
| // stream pair. |
| return cipherOption != null ? createStreamPair( |
| conf, cipherOption, underlyingOut, underlyingIn, false) : |
| sasl.createStreamPair(out, in); |
| } catch (IOException ioe) { |
| sendGenericSaslErrorMessage(out, ioe.getMessage()); |
| throw ioe; |
| } |
| } |
| } |