/* * 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.geode.internal.cache.tier.sockets; import static org.apache.geode.distributed.ConfigurationProperties.*; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.EOFException; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.math.BigInteger; import java.net.Socket; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.Principal; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLSocket; import org.apache.geode.CancelCriterion; import org.apache.geode.DataSerializer; import org.apache.geode.GemFireConfigException; import org.apache.geode.InternalGemFireException; import org.apache.geode.cache.GatewayConfigurationException; import org.apache.geode.cache.client.PoolFactory; import org.apache.geode.cache.client.ServerRefusedConnectionException; import org.apache.geode.cache.client.internal.Connection; import org.apache.geode.distributed.DistributedMember; import org.apache.geode.distributed.DistributedSystem; import org.apache.geode.distributed.internal.DM; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.distributed.internal.InternalDistributedSystem; import org.apache.geode.distributed.internal.LonerDistributionManager; import org.apache.geode.distributed.internal.ServerLocation; import org.apache.geode.distributed.internal.membership.InternalDistributedMember; import org.apache.geode.internal.ClassLoadUtil; import org.apache.geode.internal.HeapDataOutputStream; import org.apache.geode.internal.InternalDataSerializer; import org.apache.geode.internal.InternalInstantiator; import org.apache.geode.internal.Version; import org.apache.geode.internal.VersionedDataInputStream; import org.apache.geode.internal.VersionedDataOutputStream; import org.apache.geode.internal.cache.tier.Acceptor; import org.apache.geode.internal.cache.tier.ClientHandShake; import org.apache.geode.internal.cache.tier.ConnectionProxy; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.lang.StringUtils; import org.apache.geode.internal.logging.InternalLogWriter; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.security.IntegratedSecurityService; import org.apache.geode.internal.security.SecurityService; import org.apache.geode.pdx.internal.PeerTypeRegistration; import org.apache.geode.security.AuthInitialize; import org.apache.geode.security.AuthenticationFailedException; import org.apache.geode.security.AuthenticationRequiredException; import org.apache.geode.security.Authenticator; import org.apache.geode.security.GemFireSecurityException; import org.apache.logging.log4j.Logger; public class HandShake implements ClientHandShake { private static final Logger logger = LogService.getLogger(); protected static final byte REPLY_OK = (byte) 59; protected static final byte REPLY_REFUSED = (byte) 60; protected static final byte REPLY_INVALID = (byte) 61; protected static final byte REPLY_EXCEPTION_AUTHENTICATION_REQUIRED = (byte) 62; protected static final byte REPLY_EXCEPTION_AUTHENTICATION_FAILED = (byte) 63; protected static final byte REPLY_EXCEPTION_DUPLICATE_DURABLE_CLIENT = (byte) 64; protected static final byte REPLY_WAN_CREDENTIALS = (byte) 65; protected static final byte REPLY_AUTH_NOT_REQUIRED = (byte) 66; public static final byte REPLY_SERVER_IS_LOCATOR = (byte) 67; private static SecurityService securityService = IntegratedSecurityService.getSecurityService(); private byte code; private int clientReadTimeout = PoolFactory.DEFAULT_READ_TIMEOUT; private boolean isRead = false; protected final DistributedSystem system; final protected ClientProxyMembershipID id; private Properties credentials; private Version clientVersion; /** * Used at client side, indicates whether the 'delta-propagation' property is enabled on the DS * this client is connected to. This variable is used to decide whether to send delta bytes or * full value to the server for a delta-update operation. */ private static boolean deltaEnabledOnServer = true; // Security mode flags /** No credentials being sent */ public static final byte CREDENTIALS_NONE = (byte) 0; /** Credentials being sent without encryption on the wire */ public static final byte CREDENTIALS_NORMAL = (byte) 1; /** Credentials being sent with Diffie-Hellman key encryption */ public static final byte CREDENTIALS_DHENCRYPT = (byte) 2; public static final byte SECURITY_MULTIUSER_NOTIFICATIONCHANNEL = (byte) 3; private byte appSecureMode = (byte) 0; private PublicKey clientPublicKey = null; private String clientSKAlgo = null; private boolean multiuserSecureMode = false; // Parameters for the Diffie-Hellman key exchange private static final BigInteger dhP = new BigInteger("13528702063991073999718992897071702177131142188276542919088770094024269" + "73079899070080419278066109785292538223079165925365098181867673946" + "34756714063947534092593553024224277712367371302394452615862654308" + "11180902979719649450105660478776364198726078338308557022096810447" + "3500348898008043285865193451061481841186553"); private static final BigInteger dhG = new BigInteger("13058345680719715096166513407513969537624553636623932169016704425008150" + "56576152779768716554354314319087014857769741104157332735258102835" + "93126577393912282416840649805564834470583437473176415335737232689" + "81480201869671811010996732593655666464627559582258861254878896534" + "1273697569202082715873518528062345259949959"); private static final int dhL = 1023; private static PrivateKey dhPrivateKey = null; private static PublicKey dhPublicKey = null; private static String dhSKAlgo = null; // Members for server authentication using digital signature private static String certificateFilePath = null; private static HashMap certificateMap = null; private static String privateKeyAlias = null; private static String privateKeySubject = null; private static PrivateKey privateKeyEncrypt = null; private static String privateKeySignAlgo = null; private static SecureRandom random = null; public static final String PUBLIC_KEY_FILE_PROP = "security-client-kspath"; public static final String PUBLIC_KEY_PASSWD_PROP = "security-client-kspasswd"; public static final String PRIVATE_KEY_FILE_PROP = "security-server-kspath"; public static final String PRIVATE_KEY_ALIAS_PROP = "security-server-ksalias"; public static final String PRIVATE_KEY_PASSWD_PROP = "security-server-kspasswd"; /** @since GemFire 5.7 */ public static final byte CONFLATION_DEFAULT = 0; /** @since GemFire 5.7 */ public static final byte CONFLATION_ON = 1; /** @since GemFire 5.7 */ public static final byte CONFLATION_OFF = 2; /** @since GemFire 5.7 */ private byte clientConflation = CONFLATION_DEFAULT; /** * @since GemFire 6.0.3 List of per client property override bits. */ private byte[] overrides = null; /** * Test hooks for per client conflation * * @since GemFire 5.7 */ public static byte clientConflationForTesting = 0; public static boolean setClientConflationForTesting = false; /** * Test hook for client version support * * @since GemFire 5.7 */ private static Version currentClientVersion = ConnectionProxy.VERSION; /** * Another test hook, holding a version ordinal that is higher than CURRENT */ private static short overrideClientVersion = -1; /** Constructor used for mocking */ protected HandShake() { system = null; id = null; } /** * HandShake Constructor used by server side connection */ public HandShake(Socket sock, int timeout, DistributedSystem sys, Version clientVersion, byte communicationMode) throws IOException, AuthenticationRequiredException { this.clientVersion = clientVersion; this.system = sys; // SocketChannel sc = sock.getChannel(); /* * if (sc != null) { } else */ { int soTimeout = -1; try { soTimeout = sock.getSoTimeout(); sock.setSoTimeout(timeout); InputStream is = sock.getInputStream(); int valRead = is.read(); // this.code = (byte)is.read(); if (valRead == -1) { throw new EOFException( LocalizedStrings.HandShake_HANDSHAKE_EOF_REACHED_BEFORE_CLIENT_CODE_COULD_BE_READ .toLocalizedString()); } this.code = (byte) valRead; if (this.code != REPLY_OK) { throw new IOException( LocalizedStrings.HandShake_HANDSHAKE_REPLY_CODE_IS_NOT_OK.toLocalizedString()); } try { DataInputStream dis = new DataInputStream(is); DataOutputStream dos = new DataOutputStream(sock.getOutputStream()); this.clientReadTimeout = dis.readInt(); if (clientVersion.compareTo(Version.CURRENT) < 0) { // versioned streams allow object serialization code to deal with older clients dis = new VersionedDataInputStream(dis, clientVersion); dos = new VersionedDataOutputStream(dos, clientVersion); } this.id = ClientProxyMembershipID.readCanonicalized(dis); // Note: credentials should always be the last piece in handshake for // Diffie-Hellman key exchange to work if (clientVersion.compareTo(Version.GFE_603) >= 0) { setOverrides(new byte[] {dis.readByte()}); } else { setClientConflation(dis.readByte()); } // Hitesh if (this.clientVersion.compareTo(Version.GFE_65) < 0 || communicationMode == Acceptor.GATEWAY_TO_GATEWAY) { this.credentials = readCredentials(dis, dos, sys); } else { this.credentials = this.readCredential(dis, dos, sys); } } catch (IOException ioe) { this.code = -2; throw ioe; } catch (ClassNotFoundException cnfe) { this.code = -3; throw new IOException( LocalizedStrings.HandShake_CLIENTPROXYMEMBERSHIPID_CLASS_COULD_NOT_BE_FOUND_WHILE_DESERIALIZING_THE_OBJECT .toLocalizedString()); } } finally { if (soTimeout != -1) { try { sock.setSoTimeout(soTimeout); } catch (IOException ignore) { } } } } } public final Version getClientVersion() { return this.clientVersion; } /** * Client-side handshake. This form of HandShake can communicate with a server */ public HandShake(ClientProxyMembershipID id, DistributedSystem sys) { this.id = id; this.code = REPLY_OK; this.system = sys; setOverrides(); this.credentials = null; } public void updateProxyID(InternalDistributedMember idm) { this.id.updateID(idm); } /** * Clone a HandShake to be used in creating other connections */ public HandShake(HandShake handShake) { this.appSecureMode = handShake.appSecureMode; this.clientConflation = handShake.clientConflation; this.clientPublicKey = null; this.clientReadTimeout = handShake.clientReadTimeout; this.clientSKAlgo = null; this.clientVersion = handShake.clientVersion; this.code = handShake.code; this.credentials = handShake.credentials; this.isRead = handShake.isRead; this.multiuserSecureMode = handShake.multiuserSecureMode; this.overrides = handShake.overrides; this.system = handShake.system; this.id = handShake.id; // create new one this._decrypt = null; this._encrypt = null; } // used by the client side private byte setClientConflation() { byte result = CONFLATION_DEFAULT; String clientConflationValue = this.system.getProperties().getProperty(CONFLATE_EVENTS); if (DistributionConfig.CLIENT_CONFLATION_PROP_VALUE_ON .equalsIgnoreCase(clientConflationValue)) { result = CONFLATION_ON; } else if (DistributionConfig.CLIENT_CONFLATION_PROP_VALUE_OFF .equalsIgnoreCase(clientConflationValue)) { result = CONFLATION_OFF; } return result; } // used by the server side private void setClientConflation(byte value) { this.clientConflation = value; switch (this.clientConflation) { case CONFLATION_DEFAULT: case CONFLATION_OFF: case CONFLATION_ON: break; default: throw new IllegalArgumentException("Illegal clientConflation"); } } // used by the client side private void setOverrides() { this.clientConflation = setClientConflation(); // As of May 2009 ( GFE 6.0 ): // Note that this.clientVersion is used by server side for accepting // handshakes. // Client side handshake code uses this.currentClientVersion which can be // set via tests. if (HandShake.currentClientVersion.compareTo(Version.GFE_603) >= 0) { byte override = 0; /* * override = this.notifyBySubscriptionOverride; override = (byte)((override << 2) | * this.removeUnresponsiveClientOverride); override = (byte)((override << 2) | * this.clientConflation); */ override = this.clientConflation; this.overrides = new byte[] {override}; } } // used by the server side private void setOverrides(byte[] values) { byte override = values[0]; setClientConflation(((byte) (override & 0x03))); /* * override = (byte)(override >>> 2); setRemoveUnresponsiveClientOverride(((byte)(override & * 0x03))); override = (byte)(override >>> 2); setNotifyBySubscriptionOverride(((byte)(override * & 0x03))); */ } // used by CacheClientNotifier's handshake reading code public static byte[] extractOverrides(byte[] values) { byte override = values[0]; byte[] overrides = new byte[1]; for (int item = 0; item < overrides.length; item++) { overrides[item] = (byte) (override & 0x03); override = (byte) (override >>> 2); } return overrides; } public static void setVersionForTesting(short ver) { if (ver > Version.CURRENT_ORDINAL) { overrideClientVersion = ver; } else { currentClientVersion = Version.fromOrdinalOrCurrent(ver); overrideClientVersion = -1; } } /** * client-to-server handshake. Nothing is sent to the server prior to invoking this method. */ private byte write(DataOutputStream dos, DataInputStream dis, byte communicationMode, int replyCode, int readTimeout, List ports, Properties p_credentials, DistributedMember member, boolean isCallbackConnection) throws IOException { HeapDataOutputStream hdos = new HeapDataOutputStream(32, Version.CURRENT); byte acceptanceCode = -1; try { hdos.writeByte(communicationMode); if (overrideClientVersion > 0) { // for testing Version.writeOrdinal(hdos, overrideClientVersion, true); } else { Version.writeOrdinal(hdos, currentClientVersion.ordinal(), true); } hdos.writeByte(replyCode); if (ports != null) { hdos.writeInt(ports.size()); for (int i = 0; i < ports.size(); i++) { hdos.writeInt(Integer.parseInt((String) ports.get(i))); } } else { hdos.writeInt(readTimeout); } // we do not know the receiver's version at this point, but the on-wire // form of InternalDistributedMember changed in 9.0, so we must serialize // it using the previous version DataOutput idOut = new VersionedDataOutputStream(hdos, Version.GFE_82); DataSerializer.writeObject(this.id, idOut); if (currentClientVersion.compareTo(Version.GFE_603) >= 0) { for (int bytes = 0; bytes < this.overrides.length; bytes++) { hdos.writeByte(this.overrides[bytes]); } } else { // write the client conflation setting byte if (setClientConflationForTesting) { hdos.writeByte(clientConflationForTesting); } else { hdos.writeByte(this.clientConflation); } } if (isCallbackConnection || communicationMode == Acceptor.GATEWAY_TO_GATEWAY) { if (isCallbackConnection && this.multiuserSecureMode && communicationMode != Acceptor.GATEWAY_TO_GATEWAY) { hdos.writeByte(SECURITY_MULTIUSER_NOTIFICATIONCHANNEL); hdos.flush(); dos.write(hdos.toByteArray()); dos.flush(); } else { writeCredentials(dos, dis, p_credentials, ports != null, member, hdos); } } else { String authInitMethod = this.system.getProperties().getProperty(SECURITY_CLIENT_AUTH_INIT); acceptanceCode = writeCredential(dos, dis, authInitMethod, ports != null, member, hdos); } } finally { hdos.close(); } return acceptanceCode; } public void writeCredentials(DataOutputStream dos, DataInputStream dis, Properties p_credentials, boolean isNotification, DistributedMember member) throws IOException, GemFireSecurityException { HeapDataOutputStream hdos = new HeapDataOutputStream(32, Version.CURRENT); try { writeCredentials(dos, dis, p_credentials, isNotification, member, hdos); } finally { hdos.close(); } } /** * This assumes that authentication is the last piece of info in handshake */ public void writeCredentials(DataOutputStream dos, DataInputStream dis, Properties p_credentials, boolean isNotification, DistributedMember member, HeapDataOutputStream heapdos) throws IOException, GemFireSecurityException { if (p_credentials == null) { // No credentials indicator heapdos.writeByte(CREDENTIALS_NONE); heapdos.flush(); dos.write(heapdos.toByteArray()); dos.flush(); return; } if (dhSKAlgo == null || dhSKAlgo.length() == 0) { // Normal credentials without encryption indicator heapdos.writeByte(CREDENTIALS_NORMAL); DataSerializer.writeProperties(p_credentials, heapdos); heapdos.flush(); dos.write(heapdos.toByteArray()); dos.flush(); return; } try { InternalLogWriter securityLogWriter = (InternalLogWriter) this.system.getSecurityLogWriter(); securityLogWriter.fine("HandShake: using Diffie-Hellman key exchange with algo " + dhSKAlgo); boolean requireAuthentication = (certificateFilePath != null && certificateFilePath.length() > 0); if (requireAuthentication) { securityLogWriter .fine("HandShake: server authentication using digital " + "signature required"); } // Credentials with encryption indicator heapdos.writeByte(CREDENTIALS_DHENCRYPT); heapdos.writeBoolean(requireAuthentication); // Send the symmetric encryption algorithm name DataSerializer.writeString(dhSKAlgo, heapdos); // Send the DH public key byte[] keyBytes = dhPublicKey.getEncoded(); DataSerializer.writeByteArray(keyBytes, heapdos); byte[] clientChallenge = null; if (requireAuthentication) { // Authentication of server should be with the client supplied // challenge clientChallenge = new byte[64]; random.nextBytes(clientChallenge); DataSerializer.writeByteArray(clientChallenge, heapdos); } heapdos.flush(); dos.write(heapdos.toByteArray()); dos.flush(); // Expect the alias and signature in the reply byte acceptanceCode = dis.readByte(); if (acceptanceCode != REPLY_OK && acceptanceCode != REPLY_AUTH_NOT_REQUIRED) { // Ignore the useless data dis.readByte(); dis.readInt(); if (!isNotification) { DataSerializer.readByteArray(dis); } readMessage(dis, dos, acceptanceCode, member); } else if (acceptanceCode == REPLY_OK) { // Get the public key of the other side keyBytes = DataSerializer.readByteArray(dis); if (requireAuthentication) { String subject = DataSerializer.readString(dis); byte[] signatureBytes = DataSerializer.readByteArray(dis); if (!certificateMap.containsKey(subject)) { throw new AuthenticationFailedException( LocalizedStrings.HandShake_HANDSHAKE_FAILED_TO_FIND_PUBLIC_KEY_FOR_SERVER_WITH_SUBJECT_0 .toLocalizedString(subject)); } // Check the signature with the public key X509Certificate cert = (X509Certificate) certificateMap.get(subject); Signature sig = Signature.getInstance(cert.getSigAlgName()); sig.initVerify(cert); sig.update(clientChallenge); // Check the challenge string if (!sig.verify(signatureBytes)) { throw new AuthenticationFailedException( "Mismatch in client " + "challenge bytes. Malicious server?"); } securityLogWriter .fine("HandShake: Successfully verified the " + "digital signature from server"); } byte[] challenge = DataSerializer.readByteArray(dis); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFact = KeyFactory.getInstance("DH"); // PublicKey pubKey = keyFact.generatePublic(x509KeySpec); this.clientPublicKey = keyFact.generatePublic(x509KeySpec); HeapDataOutputStream hdos = new HeapDataOutputStream(Version.CURRENT); try { DataSerializer.writeProperties(p_credentials, hdos); // Also add the challenge string DataSerializer.writeByteArray(challenge, hdos); // byte[] encBytes = encrypt.doFinal(hdos.toByteArray()); byte[] encBytes = encryptBytes(hdos.toByteArray(), getEncryptCipher(dhSKAlgo, this.clientPublicKey)); DataSerializer.writeByteArray(encBytes, dos); } finally { hdos.close(); } } } catch (IOException ex) { throw ex; } catch (GemFireSecurityException ex) { throw ex; } catch (Exception ex) { throw new AuthenticationFailedException("HandShake failed in Diffie-Hellman key exchange", ex); } dos.flush(); } /** * This method writes what readCredential() method expects to read. (Note the use of singular * credential). It is similar to writeCredentials(), except that it doesn't write * credential-properties. */ public byte writeCredential(DataOutputStream dos, DataInputStream dis, String authInit, boolean isNotification, DistributedMember member, HeapDataOutputStream heapdos) throws IOException, GemFireSecurityException { if (!this.multiuserSecureMode && (authInit == null || authInit.length() == 0)) { // No credentials indicator heapdos.writeByte(CREDENTIALS_NONE); heapdos.flush(); dos.write(heapdos.toByteArray()); dos.flush(); return -1; } if (dhSKAlgo == null || dhSKAlgo.length() == 0) { // Normal credentials without encryption indicator heapdos.writeByte(CREDENTIALS_NORMAL); this.appSecureMode = CREDENTIALS_NORMAL; // DataSerializer.writeProperties(p_credentials, heapdos); heapdos.flush(); dos.write(heapdos.toByteArray()); dos.flush(); return -1; } byte acceptanceCode = -1; try { InternalLogWriter securityLogWriter = (InternalLogWriter) this.system.getSecurityLogWriter(); securityLogWriter.fine("HandShake: using Diffie-Hellman key exchange with algo " + dhSKAlgo); boolean requireAuthentication = (certificateFilePath != null && certificateFilePath.length() > 0); if (requireAuthentication) { securityLogWriter .fine("HandShake: server authentication using digital " + "signature required"); } // Credentials with encryption indicator heapdos.writeByte(CREDENTIALS_DHENCRYPT); this.appSecureMode = CREDENTIALS_DHENCRYPT; heapdos.writeBoolean(requireAuthentication); // Send the symmetric encryption algorithm name DataSerializer.writeString(dhSKAlgo, heapdos); // Send the DH public key byte[] keyBytes = dhPublicKey.getEncoded(); DataSerializer.writeByteArray(keyBytes, heapdos); byte[] clientChallenge = null; if (requireAuthentication) { // Authentication of server should be with the client supplied // challenge clientChallenge = new byte[64]; random.nextBytes(clientChallenge); DataSerializer.writeByteArray(clientChallenge, heapdos); } heapdos.flush(); dos.write(heapdos.toByteArray()); dos.flush(); // Expect the alias and signature in the reply acceptanceCode = dis.readByte(); if (acceptanceCode != REPLY_OK && acceptanceCode != REPLY_AUTH_NOT_REQUIRED) { // Ignore the useless data dis.readByte(); dis.readInt(); if (!isNotification) { DataSerializer.readByteArray(dis); } readMessage(dis, dos, acceptanceCode, member); } else if (acceptanceCode == REPLY_OK) { // Get the public key of the other side keyBytes = DataSerializer.readByteArray(dis); if (requireAuthentication) { String subject = DataSerializer.readString(dis); byte[] signatureBytes = DataSerializer.readByteArray(dis); if (!certificateMap.containsKey(subject)) { throw new AuthenticationFailedException( LocalizedStrings.HandShake_HANDSHAKE_FAILED_TO_FIND_PUBLIC_KEY_FOR_SERVER_WITH_SUBJECT_0 .toLocalizedString(subject)); } // Check the signature with the public key X509Certificate cert = (X509Certificate) certificateMap.get(subject); Signature sig = Signature.getInstance(cert.getSigAlgName()); sig.initVerify(cert); sig.update(clientChallenge); // Check the challenge string if (!sig.verify(signatureBytes)) { throw new AuthenticationFailedException( "Mismatch in client " + "challenge bytes. Malicious server?"); } securityLogWriter .fine("HandShake: Successfully verified the " + "digital signature from server"); } // Read server challenge bytes byte[] serverChallenge = DataSerializer.readByteArray(dis); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFact = KeyFactory.getInstance("DH"); // PublicKey pubKey = keyFact.generatePublic(x509KeySpec); this.clientPublicKey = keyFact.generatePublic(x509KeySpec); HeapDataOutputStream hdos = new HeapDataOutputStream(Version.CURRENT); try { // Add the challenge string DataSerializer.writeByteArray(serverChallenge, hdos); // byte[] encBytes = encrypt.doFinal(hdos.toByteArray()); byte[] encBytes = encryptBytes(hdos.toByteArray(), getEncryptCipher(dhSKAlgo, this.clientPublicKey)); DataSerializer.writeByteArray(encBytes, dos); } finally { hdos.close(); } } } catch (IOException ex) { throw ex; } catch (GemFireSecurityException ex) { throw ex; } catch (Exception ex) { throw new AuthenticationFailedException("HandShake failed in Diffie-Hellman key exchange", ex); } dos.flush(); return acceptanceCode; } public byte[] encryptBytes(byte[] data) throws Exception { if (this.appSecureMode == CREDENTIALS_DHENCRYPT) { String algo = null; if (this.clientSKAlgo != null) { algo = this.clientSKAlgo; } else { algo = dhSKAlgo; } return encryptBytes(data, getEncryptCipher(algo, this.clientPublicKey)); } else { return data; } } static public byte[] encryptBytes(byte[] data, Cipher encrypt) throws Exception { try { byte[] encBytes = encrypt.doFinal(data); return encBytes; } catch (Exception ex) { throw ex; } } private Cipher _encrypt; private Cipher getEncryptCipher(String dhSKAlgo, PublicKey publicKey) throws Exception { try { if (_encrypt == null) { KeyAgreement ka = KeyAgreement.getInstance("DH"); ka.init(dhPrivateKey); ka.doPhase(publicKey, true); Cipher encrypt; int keysize = getKeySize(dhSKAlgo); int blocksize = getBlockSize(dhSKAlgo); if (keysize == -1 || blocksize == -1) { SecretKey sKey = ka.generateSecret(dhSKAlgo); encrypt = Cipher.getInstance(dhSKAlgo); encrypt.init(Cipher.ENCRYPT_MODE, sKey); } else { String dhAlgoStr = getDhAlgoStr(dhSKAlgo); byte[] sKeyBytes = ka.generateSecret(); SecretKeySpec sks = new SecretKeySpec(sKeyBytes, 0, keysize, dhAlgoStr); IvParameterSpec ivps = new IvParameterSpec(sKeyBytes, keysize, blocksize); encrypt = Cipher.getInstance(dhAlgoStr + "/CBC/PKCS5Padding"); encrypt.init(Cipher.ENCRYPT_MODE, sks, ivps); } _encrypt = encrypt; } } catch (Exception ex) { throw ex; } return _encrypt; } /** * Throws AuthenticationRequiredException if authentication is required but there are no * credentials. */ static void throwIfMissingRequiredCredentials(boolean requireAuthentication, boolean hasCredentials) { if (requireAuthentication && !hasCredentials) { throw new AuthenticationRequiredException( LocalizedStrings.HandShake_NO_SECURITY_CREDENTIALS_ARE_PROVIDED.toLocalizedString()); } } // This assumes that authentication is the last piece of info in handshake public Properties readCredential(DataInputStream dis, DataOutputStream dos, DistributedSystem system) throws GemFireSecurityException, IOException { Properties credentials = null; boolean requireAuthentication = securityService.isClientSecurityRequired(); try { byte secureMode = dis.readByte(); throwIfMissingRequiredCredentials(requireAuthentication, secureMode != CREDENTIALS_NONE); if (secureMode == CREDENTIALS_NORMAL) { this.appSecureMode = CREDENTIALS_NORMAL; /* * if (requireAuthentication) { credentials = DataSerializer.readProperties(dis); } else { * DataSerializer.readProperties(dis); // ignore the credentials } */ } else if (secureMode == CREDENTIALS_DHENCRYPT) { this.appSecureMode = CREDENTIALS_DHENCRYPT; boolean sendAuthentication = dis.readBoolean(); InternalLogWriter securityLogWriter = (InternalLogWriter) system.getSecurityLogWriter(); // Get the symmetric encryption algorithm to be used // String skAlgo = DataSerializer.readString(dis); this.clientSKAlgo = DataSerializer.readString(dis); // Get the public key of the other side byte[] keyBytes = DataSerializer.readByteArray(dis); byte[] challenge = null; // PublicKey pubKey = null; if (requireAuthentication) { // Generate PublicKey from encoded form X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFact = KeyFactory.getInstance("DH"); this.clientPublicKey = keyFact.generatePublic(x509KeySpec); // Send the public key to other side keyBytes = dhPublicKey.getEncoded(); challenge = new byte[64]; random.nextBytes(challenge); // If the server has to also authenticate itself then // sign the challenge from client. if (sendAuthentication) { // Get the challenge string from client byte[] clientChallenge = DataSerializer.readByteArray(dis); if (privateKeyEncrypt == null) { throw new AuthenticationFailedException( LocalizedStrings.HandShake_SERVER_PRIVATE_KEY_NOT_AVAILABLE_FOR_CREATING_SIGNATURE .toLocalizedString()); } // Sign the challenge from client and send it to the client Signature sig = Signature.getInstance(privateKeySignAlgo); sig.initSign(privateKeyEncrypt); sig.update(clientChallenge); byte[] signedBytes = sig.sign(); dos.writeByte(REPLY_OK); DataSerializer.writeByteArray(keyBytes, dos); // DataSerializer.writeString(privateKeyAlias, dos); DataSerializer.writeString(privateKeySubject, dos); DataSerializer.writeByteArray(signedBytes, dos); securityLogWriter.fine("HandShake: sent the signed client challenge"); } else { // These two lines should not be moved before the if{} statement in // a common block for both if...then...else parts. This is to handle // the case when an AuthenticationFailedException is thrown by the // if...then part when sending the signature. dos.writeByte(REPLY_OK); DataSerializer.writeByteArray(keyBytes, dos); } // Now send the server challenge DataSerializer.writeByteArray(challenge, dos); securityLogWriter.fine("HandShake: sent the public key and challenge"); dos.flush(); // Read and decrypt the credentials byte[] encBytes = DataSerializer.readByteArray(dis); Cipher c = getDecryptCipher(this.clientSKAlgo, this.clientPublicKey); byte[] credentialBytes = decryptBytes(encBytes, c); ByteArrayInputStream bis = new ByteArrayInputStream(credentialBytes); DataInputStream dinp = new DataInputStream(bis); // credentials = DataSerializer.readProperties(dinp);//Hitesh: we don't send in handshake // now byte[] challengeRes = DataSerializer.readByteArray(dinp); // Check the challenge string if (!Arrays.equals(challenge, challengeRes)) { throw new AuthenticationFailedException( LocalizedStrings.HandShake_MISMATCH_IN_CHALLENGE_BYTES_MALICIOUS_CLIENT .toLocalizedString()); } dinp.close(); } else { if (sendAuthentication) { // Read and ignore the client challenge DataSerializer.readByteArray(dis); } dos.writeByte(REPLY_AUTH_NOT_REQUIRED); dos.flush(); } } } catch (IOException ex) { throw ex; } catch (GemFireSecurityException ex) { throw ex; } catch (Exception ex) { throw new AuthenticationFailedException( LocalizedStrings.HandShake_FAILURE_IN_READING_CREDENTIALS.toLocalizedString(), ex); } return credentials; } public byte[] decryptBytes(byte[] data) throws Exception { if (this.appSecureMode == CREDENTIALS_DHENCRYPT) { String algo = null; if (this.clientSKAlgo != null) { algo = this.clientSKAlgo; } else { algo = dhSKAlgo; } Cipher c = getDecryptCipher(algo, this.clientPublicKey); return decryptBytes(data, c); } else { return data; } } static public byte[] decryptBytes(byte[] data, Cipher decrypt) throws Exception { try { byte[] decrptBytes = decrypt.doFinal(data); return decrptBytes; } catch (Exception ex) { throw ex; } } private Cipher _decrypt = null; private Cipher getDecryptCipher(String dhSKAlgo, PublicKey publicKey) throws Exception { if (_decrypt == null) { try { KeyAgreement ka = KeyAgreement.getInstance("DH"); ka.init(dhPrivateKey); ka.doPhase(publicKey, true); Cipher decrypt; int keysize = getKeySize(dhSKAlgo); int blocksize = getBlockSize(dhSKAlgo); if (keysize == -1 || blocksize == -1) { SecretKey sKey = ka.generateSecret(dhSKAlgo); decrypt = Cipher.getInstance(dhSKAlgo); decrypt.init(Cipher.DECRYPT_MODE, sKey); } else { String algoStr = getDhAlgoStr(dhSKAlgo); byte[] sKeyBytes = ka.generateSecret(); SecretKeySpec sks = new SecretKeySpec(sKeyBytes, 0, keysize, algoStr); IvParameterSpec ivps = new IvParameterSpec(sKeyBytes, keysize, blocksize); decrypt = Cipher.getInstance(algoStr + "/CBC/PKCS5Padding"); decrypt.init(Cipher.DECRYPT_MODE, sks, ivps); } _decrypt = decrypt; } catch (Exception ex) { throw ex; } } return _decrypt; } /** * Populate the available server public keys into a local static HashMap. This method is not * thread safe. */ public static void initCertsMap(Properties props) throws Exception { certificateMap = new HashMap(); certificateFilePath = props.getProperty(PUBLIC_KEY_FILE_PROP); if (certificateFilePath != null && certificateFilePath.length() > 0) { KeyStore ks = KeyStore.getInstance("JKS"); String keyStorePass = props.getProperty(PUBLIC_KEY_PASSWD_PROP); char[] passPhrase = (keyStorePass != null ? keyStorePass.toCharArray() : null); FileInputStream keystorefile = new FileInputStream(certificateFilePath); try { ks.load(keystorefile, passPhrase); } finally { keystorefile.close(); } Enumeration aliases = ks.aliases(); while (aliases.hasMoreElements()) { String alias = (String) aliases.nextElement(); Certificate cert = ks.getCertificate(alias); if (cert instanceof X509Certificate) { String subject = ((X509Certificate) cert).getSubjectDN().getName(); certificateMap.put(subject, cert); } } } } /** * Load the private key of the server. This method is not thread safe. */ public static void initPrivateKey(Properties props) throws Exception { String privateKeyFilePath = props.getProperty(PRIVATE_KEY_FILE_PROP); privateKeyAlias = ""; privateKeyEncrypt = null; if (privateKeyFilePath != null && privateKeyFilePath.length() > 0) { KeyStore ks = KeyStore.getInstance("PKCS12"); privateKeyAlias = props.getProperty(PRIVATE_KEY_ALIAS_PROP); if (privateKeyAlias == null) { privateKeyAlias = ""; } String keyStorePass = props.getProperty(PRIVATE_KEY_PASSWD_PROP); char[] passPhrase = (keyStorePass != null ? keyStorePass.toCharArray() : null); FileInputStream privateKeyFile = new FileInputStream(privateKeyFilePath); try { ks.load(privateKeyFile, passPhrase); } finally { privateKeyFile.close(); } Key key = ks.getKey(privateKeyAlias, passPhrase); Certificate keyCert = ks.getCertificate(privateKeyAlias); if (key instanceof PrivateKey && keyCert instanceof X509Certificate) { privateKeyEncrypt = (PrivateKey) key; privateKeySignAlgo = ((X509Certificate) keyCert).getSigAlgName(); privateKeySubject = ((X509Certificate) keyCert).getSubjectDN().getName(); } } } /** * Initialize the Diffie-Hellman keys. This method is not thread safe */ public static void initDHKeys(DistributionConfig config) throws Exception { dhSKAlgo = config.getSecurityClientDHAlgo(); dhPrivateKey = null; dhPublicKey = null; // Initialize the keys when either the host is a client that has // non-blank setting for DH symmetric algo, or this is a server // that has authenticator defined. if ((dhSKAlgo != null && dhSKAlgo.length() > 0) || securityService.isClientSecurityRequired()) { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DH"); DHParameterSpec dhSpec = new DHParameterSpec(dhP, dhG, dhL); keyGen.initialize(dhSpec); KeyPair keypair = keyGen.generateKeyPair(); // Get the generated public and private keys dhPrivateKey = keypair.getPrivate(); dhPublicKey = keypair.getPublic(); random = new SecureRandom(); // Force the random generator to seed itself. byte[] someBytes = new byte[48]; random.nextBytes(someBytes); } } public void accept(OutputStream out, InputStream in, byte epType, int qSize, byte communicationMode, Principal principal) throws IOException { DataOutputStream dos = new DataOutputStream(out); DataInputStream dis; if (clientVersion.compareTo(Version.CURRENT) < 0) { dis = new VersionedDataInputStream(in, clientVersion); dos = new VersionedDataOutputStream(dos, clientVersion); } else { dis = new DataInputStream(in); } // Write ok reply if (communicationMode == Acceptor.GATEWAY_TO_GATEWAY && principal != null) { dos.writeByte(REPLY_WAN_CREDENTIALS); } else { dos.writeByte(REPLY_OK);// byte 59 } // additional byte of wan site needs to send for Gateway BC if (communicationMode == Acceptor.GATEWAY_TO_GATEWAY) { Version.writeOrdinal(dos, ServerHandShakeProcessor.currentServerVersion.ordinal(), true); } dos.writeByte(epType); dos.writeInt(qSize); // Write the server's member DistributedMember member = this.system.getDistributedMember(); ServerHandShakeProcessor.writeServerMember(member, dos); // Write no message dos.writeUTF(""); // Write delta-propagation property value if this is not WAN. if (communicationMode != Acceptor.GATEWAY_TO_GATEWAY && this.clientVersion.compareTo(Version.GFE_61) >= 0) { dos.writeBoolean(((InternalDistributedSystem) this.system).getConfig().getDeltaPropagation()); } // Neeraj: Now if the communication mode is GATEWAY_TO_GATEWAY // and principal not equal to null then send the credentials also if (communicationMode == Acceptor.GATEWAY_TO_GATEWAY && principal != null) { sendCredentialsForWan(dos, dis); } // Write the distributed system id if this is a 6.6 or greater client // on the remote side of the gateway if (communicationMode == Acceptor.GATEWAY_TO_GATEWAY && this.clientVersion.compareTo(Version.GFE_66) >= 0 && ServerHandShakeProcessor.currentServerVersion.compareTo(Version.GFE_66) >= 0) { dos.writeByte(((InternalDistributedSystem) this.system).getDistributionManager() .getDistributedSystemId()); } if ((communicationMode == Acceptor.GATEWAY_TO_GATEWAY) && this.clientVersion.compareTo(Version.GFE_80) >= 0 && ServerHandShakeProcessor.currentServerVersion.compareTo(Version.GFE_80) >= 0) { int pdxSize = PeerTypeRegistration.getPdxRegistrySize(); dos.writeInt(pdxSize); } // Flush dos.flush(); } /** * Return fake, temporary DistributedMember to represent the other vm this vm is connecting to * * @param sock the socket this handshake is operating on * @return temporary id to reprent the other vm */ private DistributedMember getIDForSocket(Socket sock) { return new InternalDistributedMember(sock.getInetAddress(), sock.getPort(), false); } /** * Client-side handshake with a Server */ public ServerQueueStatus handshakeWithServer(Connection conn, ServerLocation location, byte communicationMode) throws IOException, AuthenticationRequiredException, AuthenticationFailedException, ServerRefusedConnectionException { try { ServerQueueStatus serverQStatus = null; Socket sock = conn.getSocket(); DataOutputStream dos = new DataOutputStream(sock.getOutputStream()); final InputStream in = sock.getInputStream(); DataInputStream dis = new DataInputStream(in); DistributedMember member = getIDForSocket(sock); // if running in a loner system, use the new port number in the ID to // help differentiate from other clients DM dm = ((InternalDistributedSystem) this.system).getDistributionManager(); InternalDistributedMember idm = dm.getDistributionManagerId(); synchronized (idm) { if (idm.getPort() == 0 && dm instanceof LonerDistributionManager) { int port = sock.getLocalPort(); ((LonerDistributionManager) dm).updateLonerPort(port); updateProxyID(dm.getDistributionManagerId()); } } if (communicationMode == Acceptor.GATEWAY_TO_GATEWAY) { this.credentials = getCredentials(member); } byte intermediateAcceptanceCode = write(dos, dis, communicationMode, REPLY_OK, this.clientReadTimeout, null, this.credentials, member, false); String authInit = this.system.getProperties().getProperty(SECURITY_CLIENT_AUTH_INIT); if (communicationMode != Acceptor.GATEWAY_TO_GATEWAY && intermediateAcceptanceCode != REPLY_AUTH_NOT_REQUIRED && (authInit != null && authInit.length() != 0)) { location.compareAndSetRequiresCredentials(true); } // Read the acceptance code byte acceptanceCode = dis.readByte(); if (acceptanceCode == (byte) 21 && !(sock instanceof SSLSocket)) { // This is likely the case of server setup with SSL and client not using // SSL throw new AuthenticationRequiredException( LocalizedStrings.HandShake_SERVER_EXPECTING_SSL_CONNECTION.toLocalizedString()); } if (acceptanceCode == REPLY_SERVER_IS_LOCATOR) { throw new GemFireConfigException("Improperly configured client detected. " + "Server at " + location + " is actually a locator. Use addPoolLocator to configure locators."); } // Successful handshake for GATEWAY_TO_GATEWAY mode sets the peer version in connection if (communicationMode == Acceptor.GATEWAY_TO_GATEWAY && !(acceptanceCode == REPLY_EXCEPTION_AUTHENTICATION_REQUIRED || acceptanceCode == REPLY_EXCEPTION_AUTHENTICATION_FAILED)) { short wanSiteVersion = Version.readOrdinal(dis); conn.setWanSiteVersion(wanSiteVersion); // establish a versioned stream for the other site, if necessary if (wanSiteVersion < Version.CURRENT_ORDINAL) { dis = new VersionedDataInputStream(dis, Version.fromOrdinalOrCurrent(wanSiteVersion)); } } // No need to check for return value since DataInputStream already throws // EOFException in case of EOF byte epType = dis.readByte(); int qSize = dis.readInt(); // Read the server member member = readServerMember(dis); serverQStatus = new ServerQueueStatus(epType, qSize, member); // Read the message (if any) readMessage(dis, dos, acceptanceCode, member); // Read delta-propagation property value from server. // [sumedh] Static variable below? Client can connect to different // DSes with different values of this. It shoule be a member variable. if (communicationMode != Acceptor.GATEWAY_TO_GATEWAY && currentClientVersion.compareTo(Version.GFE_61) >= 0) { deltaEnabledOnServer = dis.readBoolean(); } // validate that the remote side has a different distributed system id. if (communicationMode == Acceptor.GATEWAY_TO_GATEWAY && Version.GFE_66.compareTo(conn.getWanSiteVersion()) <= 0 && currentClientVersion.compareTo(Version.GFE_66) >= 0) { int remoteDistributedSystemId = in.read(); int localDistributedSystemId = ((InternalDistributedSystem) system).getDistributionManager().getDistributedSystemId(); if (localDistributedSystemId >= 0 && localDistributedSystemId == remoteDistributedSystemId) { throw new GatewayConfigurationException( "Remote WAN site's distributed system id " + remoteDistributedSystemId + " matches this sites distributed system id " + localDistributedSystemId); } } // Read the PDX registry size from the remote size if (communicationMode == Acceptor.GATEWAY_TO_GATEWAY && Version.GFE_80.compareTo(conn.getWanSiteVersion()) <= 0 && currentClientVersion.compareTo(Version.GFE_80) >= 0) { int remotePdxSize = dis.readInt(); serverQStatus.setPdxSize(remotePdxSize); } return serverQStatus; } catch (IOException ex) { CancelCriterion stopper = this.system.getCancelCriterion(); stopper.checkCancelInProgress(null); throw ex; } } /** * Used by client-side CacheClientUpdater to handshake with a server in order to receive messages * generated by subscriptions (register-interest, continuous query) */ public ServerQueueStatus handshakeWithSubscriptionFeed(Socket sock, boolean isPrimary) throws IOException, AuthenticationRequiredException, AuthenticationFailedException, ServerRefusedConnectionException, ClassNotFoundException { ServerQueueStatus sqs = null; try { DataOutputStream dos = new DataOutputStream(sock.getOutputStream()); final InputStream in = sock.getInputStream(); DataInputStream dis = new DataInputStream(in); DistributedMember member = getIDForSocket(sock); if (!this.multiuserSecureMode) { this.credentials = getCredentials(member); } byte mode = isPrimary ? Acceptor.PRIMARY_SERVER_TO_CLIENT : Acceptor.SECONDARY_SERVER_TO_CLIENT; write(dos, dis, mode, REPLY_OK, 0, new ArrayList(), this.credentials, member, true); // Wait here for a reply before continuing. This ensures that the client // updater is registered with the server before continuing. byte acceptanceCode = dis.readByte(); if (acceptanceCode == (byte) 21 && !(sock instanceof SSLSocket)) { // This is likely the case of server setup with SSL and client not using // SSL throw new AuthenticationRequiredException( LocalizedStrings.HandShake_SERVER_EXPECTING_SSL_CONNECTION.toLocalizedString()); } // No need to check for return value since DataInputStream already throws // EOFException in case of EOF byte qType = dis.readByte(); // read and ignore qSize flag int qSize = dis.readInt(); sqs = new ServerQueueStatus(qType, qSize, member); // Read the message (if any) readMessage(dis, dos, acceptanceCode, member); // [sumedh] nothing more to be done for older clients used in tests // there is a difference in serializer map registration for >= 6.5.1.6 // clients but that is not used in tests if (currentClientVersion.compareTo(Version.GFE_61) < 0) { return sqs; } HashMap instantiatorMap = DataSerializer.readHashMap(dis); for (Iterator itr = instantiatorMap.entrySet().iterator(); itr.hasNext();) { Map.Entry instantiator = (Map.Entry) itr.next(); Integer id = (Integer) instantiator.getKey(); ArrayList instantiatorArguments = (ArrayList) instantiator.getValue(); InternalInstantiator.register((String) instantiatorArguments.get(0), (String) instantiatorArguments.get(1), id, false); } HashMap dataSerializersMap = DataSerializer.readHashMap(dis); for (Iterator itr = dataSerializersMap.entrySet().iterator(); itr.hasNext();) { Map.Entry dataSerializer = (Map.Entry) itr.next(); Integer id = (Integer) dataSerializer.getKey(); InternalDataSerializer.register((String) dataSerializer.getValue(), false, null, null, id); } HashMap<Integer, ArrayList<String>> dsToSupportedClassNames = DataSerializer.readHashMap(dis); InternalDataSerializer.updateSupportedClassesMap(dsToSupportedClassNames); } catch (IOException ex) { CancelCriterion stopper = this.system.getCancelCriterion(); stopper.checkCancelInProgress(null); throw ex; } catch (ClassNotFoundException ex) { CancelCriterion stopper = this.system.getCancelCriterion(); stopper.checkCancelInProgress(null); throw ex; } return sqs; } protected DistributedMember readServerMember(DataInputStream p_dis) throws IOException { byte[] memberBytes = DataSerializer.readByteArray(p_dis); ByteArrayInputStream bais = new ByteArrayInputStream(memberBytes); DataInputStream dis = new DataInputStream(bais); Version v = InternalDataSerializer.getVersionForDataStreamOrNull(p_dis); if (v != null) { dis = new VersionedDataInputStream(dis, v); } try { return DataSerializer.readObject(dis); } catch (EOFException e) { throw e; } catch (Exception e) { throw new InternalGemFireException( LocalizedStrings.HandShake_UNABLE_TO_DESERIALIZE_MEMBER.toLocalizedString(), e); } } protected void readMessage(DataInputStream dis, DataOutputStream dos, byte acceptanceCode, DistributedMember member) throws IOException, AuthenticationRequiredException, AuthenticationFailedException, ServerRefusedConnectionException { String message = dis.readUTF(); if (message.length() == 0 && acceptanceCode != REPLY_WAN_CREDENTIALS) { return; // success } switch (acceptanceCode) { case REPLY_EXCEPTION_AUTHENTICATION_REQUIRED: throw new AuthenticationRequiredException(message); case REPLY_EXCEPTION_AUTHENTICATION_FAILED: throw new AuthenticationFailedException(message); case REPLY_EXCEPTION_DUPLICATE_DURABLE_CLIENT: throw new ServerRefusedConnectionException(member, message); case REPLY_WAN_CREDENTIALS: checkIfAuthenticWanSite(dis, dos, member); break; default: throw new ServerRefusedConnectionException(member, message); } } public byte getCode() { return this.code; } public boolean isRead() { return this.isRead; } public boolean isOK() { return getCode() == REPLY_OK; } public void setClientReadTimeout(int clientReadTimeout) { this.clientReadTimeout = clientReadTimeout; } public int getClientReadTimeout() { return this.clientReadTimeout; } public void setMultiuserSecureMode(boolean bool) { this.multiuserSecureMode = bool; } public boolean isMultiuserSecureMode() { return this.multiuserSecureMode; } /** * Indicates whether some other object is "equal to" this one. * * @param other the reference object with which to compare. * @return true if this object is the same as the obj argument; false otherwise. */ @Override public boolean equals(Object other) { if (other == this) return true; // if (other == null) return false; if (!(other instanceof HandShake)) return false; final HandShake that = (HandShake) other; if (this.id.isSameDSMember(that.id) && this.code == that.code) { return true; } else { return false; } } /** * Returns a hash code for the object. This method is supported for the benefit of hashtables such * as those provided by java.util.Hashtable. * * @return the integer 0 if description is null; otherwise a unique integer. */ @Override public int hashCode() { int result = 17; final int mult = 37; /* * if (this.identity != null && this.identity.length > 0) { for (int i = 0; i < * this.identity.length; i++) { result = mult * result + (int) this.identity[i]; } } */ result = this.id.hashCode(); result = mult * result + this.code; return result; } @Override public String toString() { StringBuffer buf = new StringBuffer().append("HandShake@").append(System.identityHashCode(this)) .append(" code: ").append(this.code); if (this.id != null) { buf.append(" identity: "); /* * for(int i=0; i<this.identity.length; ++i) { buf.append(this.identity[i]); } */ buf.append(this.id.toString()); } return buf.toString(); } public ClientProxyMembershipID getMembership() { return this.id; } public static Properties getCredentials(String authInitMethod, Properties securityProperties, DistributedMember server, boolean isPeer, InternalLogWriter logWriter, InternalLogWriter securityLogWriter) throws AuthenticationRequiredException { Properties credentials = null; // if no authInit, Try to extract the credentials directly from securityProps if (StringUtils.isBlank(authInitMethod)) { return SecurityService.getCredentials(securityProperties); } // if authInit exists try { AuthInitialize auth = SecurityService.getObjectOfType(authInitMethod, AuthInitialize.class); auth.init(logWriter, securityLogWriter); try { credentials = auth.getCredentials(securityProperties, server, isPeer); } finally { auth.close(); } } catch (GemFireSecurityException ex) { throw ex; } catch (Exception ex) { throw new AuthenticationRequiredException( LocalizedStrings.HandShake_FAILED_TO_ACQUIRE_AUTHINITIALIZE_METHOD_0 .toLocalizedString(authInitMethod), ex); } return credentials; } private Properties getCredentials(DistributedMember member) { String authInitMethod = this.system.getProperties().getProperty(SECURITY_CLIENT_AUTH_INIT); return getCredentials(authInitMethod, this.system.getSecurityProperties(), member, false, (InternalLogWriter) this.system.getLogWriter(), (InternalLogWriter) this.system.getSecurityLogWriter()); } // This assumes that authentication is the last piece of info in handshake public static Properties readCredentials(DataInputStream dis, DataOutputStream dos, DistributedSystem system) throws GemFireSecurityException, IOException { boolean requireAuthentication = securityService.isClientSecurityRequired(); Properties credentials = null; try { byte secureMode = dis.readByte(); throwIfMissingRequiredCredentials(requireAuthentication, secureMode != CREDENTIALS_NONE); if (secureMode == CREDENTIALS_NORMAL) { if (requireAuthentication) { credentials = DataSerializer.readProperties(dis); } else { DataSerializer.readProperties(dis); // ignore the credentials } } else if (secureMode == CREDENTIALS_DHENCRYPT) { boolean sendAuthentication = dis.readBoolean(); InternalLogWriter securityLogWriter = (InternalLogWriter) system.getSecurityLogWriter(); // Get the symmetric encryption algorithm to be used String skAlgo = DataSerializer.readString(dis); // Get the public key of the other side byte[] keyBytes = DataSerializer.readByteArray(dis); byte[] challenge = null; PublicKey pubKey = null; if (requireAuthentication) { // Generate PublicKey from encoded form X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFact = KeyFactory.getInstance("DH"); pubKey = keyFact.generatePublic(x509KeySpec); // Send the public key to other side keyBytes = dhPublicKey.getEncoded(); challenge = new byte[64]; random.nextBytes(challenge); // If the server has to also authenticate itself then // sign the challenge from client. if (sendAuthentication) { // Get the challenge string from client byte[] clientChallenge = DataSerializer.readByteArray(dis); if (privateKeyEncrypt == null) { throw new AuthenticationFailedException( LocalizedStrings.HandShake_SERVER_PRIVATE_KEY_NOT_AVAILABLE_FOR_CREATING_SIGNATURE .toLocalizedString()); } // Sign the challenge from client and send it to the client Signature sig = Signature.getInstance(privateKeySignAlgo); sig.initSign(privateKeyEncrypt); sig.update(clientChallenge); byte[] signedBytes = sig.sign(); dos.writeByte(REPLY_OK); DataSerializer.writeByteArray(keyBytes, dos); // DataSerializer.writeString(privateKeyAlias, dos); DataSerializer.writeString(privateKeySubject, dos); DataSerializer.writeByteArray(signedBytes, dos); securityLogWriter.fine("HandShake: sent the signed client challenge"); } else { // These two lines should not be moved before the if{} statement in // a common block for both if...then...else parts. This is to handle // the case when an AuthenticationFailedException is thrown by the // if...then part when sending the signature. dos.writeByte(REPLY_OK); DataSerializer.writeByteArray(keyBytes, dos); } // Now send the server challenge DataSerializer.writeByteArray(challenge, dos); securityLogWriter.fine("HandShake: sent the public key and challenge"); dos.flush(); // Read and decrypt the credentials byte[] encBytes = DataSerializer.readByteArray(dis); KeyAgreement ka = KeyAgreement.getInstance("DH"); ka.init(dhPrivateKey); ka.doPhase(pubKey, true); Cipher decrypt; int keysize = getKeySize(skAlgo); int blocksize = getBlockSize(skAlgo); if (keysize == -1 || blocksize == -1) { SecretKey sKey = ka.generateSecret(skAlgo); decrypt = Cipher.getInstance(skAlgo); decrypt.init(Cipher.DECRYPT_MODE, sKey); } else { String algoStr = getDhAlgoStr(skAlgo); byte[] sKeyBytes = ka.generateSecret(); SecretKeySpec sks = new SecretKeySpec(sKeyBytes, 0, keysize, algoStr); IvParameterSpec ivps = new IvParameterSpec(sKeyBytes, keysize, blocksize); decrypt = Cipher.getInstance(algoStr + "/CBC/PKCS5Padding"); decrypt.init(Cipher.DECRYPT_MODE, sks, ivps); } byte[] credentialBytes = decrypt.doFinal(encBytes); ByteArrayInputStream bis = new ByteArrayInputStream(credentialBytes); DataInputStream dinp = new DataInputStream(bis); credentials = DataSerializer.readProperties(dinp); byte[] challengeRes = DataSerializer.readByteArray(dinp); // Check the challenge string if (!Arrays.equals(challenge, challengeRes)) { throw new AuthenticationFailedException( LocalizedStrings.HandShake_MISMATCH_IN_CHALLENGE_BYTES_MALICIOUS_CLIENT .toLocalizedString()); } dinp.close(); } else { if (sendAuthentication) { // Read and ignore the client challenge DataSerializer.readByteArray(dis); } dos.writeByte(REPLY_AUTH_NOT_REQUIRED); dos.flush(); } } else if (secureMode == SECURITY_MULTIUSER_NOTIFICATIONCHANNEL) { // hitesh there will be no credential CCP will get credential(Principal) using // ServerConnection.. logger.debug("readCredential where multiuser mode creating callback connection"); } } catch (IOException ex) { throw ex; } catch (GemFireSecurityException ex) { throw ex; } catch (Exception ex) { throw new AuthenticationFailedException( LocalizedStrings.HandShake_FAILURE_IN_READING_CREDENTIALS.toLocalizedString(), ex); } return credentials; } /** * this could return either a Subject or a Principal depending on if it's integrated security or * not */ public static Object verifyCredentials(String authenticatorMethod, Properties credentials, Properties securityProperties, InternalLogWriter logWriter, InternalLogWriter securityLogWriter, DistributedMember member) throws AuthenticationRequiredException, AuthenticationFailedException { if (!AcceptorImpl.isAuthenticationRequired()) { return null; } Authenticator auth = null; try { if (AcceptorImpl.isIntegratedSecurity()) { return securityService.login(credentials); } else { Method instanceGetter = ClassLoadUtil.methodFromName(authenticatorMethod); auth = (Authenticator) instanceGetter.invoke(null, (Object[]) null); auth.init(securityProperties, logWriter, securityLogWriter); return auth.authenticate(credentials, member); } } catch (AuthenticationFailedException ex) { throw ex; } catch (Exception ex) { throw new AuthenticationFailedException(ex.getMessage(), ex); } finally { if (auth != null) auth.close(); } } public Object verifyCredentials() throws AuthenticationRequiredException, AuthenticationFailedException { String methodName = this.system.getProperties().getProperty(SECURITY_CLIENT_AUTHENTICATOR); return verifyCredentials(methodName, this.credentials, this.system.getSecurityProperties(), (InternalLogWriter) this.system.getLogWriter(), (InternalLogWriter) this.system.getSecurityLogWriter(), this.id.getDistributedMember()); } public void sendCredentialsForWan(OutputStream out, InputStream in) { try { Properties wanCredentials = getCredentials(this.id.getDistributedMember()); DataOutputStream dos = new DataOutputStream(out); DataInputStream dis = new DataInputStream(in); writeCredentials(dos, dis, wanCredentials, false, this.system.getDistributedMember()); } // The exception while getting the credentials is just logged as severe catch (Exception e) { this.system.getSecurityLogWriter().convertToLogWriterI18n().severe( LocalizedStrings.HandShake_AN_EXCEPTION_WAS_THROWN_WHILE_SENDING_WAN_CREDENTIALS_0, e.getLocalizedMessage()); } } private void checkIfAuthenticWanSite(DataInputStream dis, DataOutputStream dos, DistributedMember member) throws GemFireSecurityException, IOException { if (this.credentials == null) { return; } String authenticator = this.system.getProperties().getProperty(SECURITY_CLIENT_AUTHENTICATOR); Properties peerWanProps = readCredentials(dis, dos, this.system); verifyCredentials(authenticator, peerWanProps, this.system.getSecurityProperties(), (InternalLogWriter) this.system.getLogWriter(), (InternalLogWriter) this.system.getSecurityLogWriter(), member); } private static int getKeySize(String skAlgo) { // skAlgo contain both algo and key size info int colIdx = skAlgo.indexOf(':'); String algoStr; int algoKeySize = 0; if (colIdx >= 0) { algoStr = skAlgo.substring(0, colIdx); algoKeySize = Integer.parseInt(skAlgo.substring(colIdx + 1)); } else { algoStr = skAlgo; } int keysize = -1; if (algoStr.equalsIgnoreCase("DESede")) { keysize = 24; } else if (algoStr.equalsIgnoreCase("Blowfish")) { keysize = algoKeySize > 128 ? algoKeySize / 8 : 16; } else if (algoStr.equalsIgnoreCase("AES")) { keysize = (algoKeySize != 192 && algoKeySize != 256) ? 16 : algoKeySize / 8; } return keysize; } private static String getDhAlgoStr(String skAlgo) { int colIdx = skAlgo.indexOf(':'); String algoStr; if (colIdx >= 0) { algoStr = skAlgo.substring(0, colIdx); } else { algoStr = skAlgo; } return algoStr; } private static int getBlockSize(String skAlgo) { int blocksize = -1; String algoStr = getDhAlgoStr(skAlgo); if (algoStr.equalsIgnoreCase("DESede")) { blocksize = 8; } else if (algoStr.equalsIgnoreCase("Blowfish")) { blocksize = 8; } else if (algoStr.equalsIgnoreCase("AES")) { blocksize = 16; } return blocksize; } public Version getVersion() { return this.clientVersion; } public static boolean isDeltaEnabledOnServer() { return deltaEnabledOnServer; } public boolean hasCredentials() { return this.credentials != null; } }