| /* |
| * 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.distributed.internal.membership.gms.messenger; |
| |
| import java.math.BigInteger; |
| import java.security.KeyFactory; |
| import java.security.KeyPair; |
| import java.security.KeyPairGenerator; |
| import java.security.PrivateKey; |
| import java.security.PublicKey; |
| import java.security.spec.X509EncodedKeySpec; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| 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 org.apache.geode.distributed.internal.membership.gms.GMSMembershipView; |
| import org.apache.geode.distributed.internal.membership.gms.Services; |
| import org.apache.geode.distributed.internal.membership.gms.api.MemberIdentifier; |
| import org.apache.geode.distributed.internal.membership.gms.locator.GMSLocator; |
| import org.apache.geode.internal.util.JavaWorkarounds; |
| |
| public final class GMSEncrypt { |
| // 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 final PrivateKey dhPrivateKey; |
| |
| private final PublicKey dhPublicKey; |
| |
| private final String dhSKAlgo; |
| |
| private Services services; |
| |
| private GMSMembershipView view; |
| |
| /** |
| * it keeps PK for peers |
| */ |
| private final Map<GMSMemberWrapper, byte[]> memberToPublicKey = |
| new ConcurrentHashMap<>(); |
| |
| private final ConcurrentHashMap<MemberIdentifier, GMSEncryptionCipherPool> peerEncryptors = |
| new ConcurrentHashMap<>(); |
| |
| private GMSEncryptionCipherPool clusterEncryptor; |
| |
| protected void installView(GMSMembershipView view) { |
| this.view = view; |
| this.view.setPublicKey(services.getJoinLeave().getMemberID(), getPublicKeyBytes()); |
| } |
| |
| void overrideInstallViewForTest(GMSMembershipView view) { |
| this.view = view; |
| } |
| |
| protected byte[] getClusterSecretKey() { |
| if (this.clusterEncryptor != null) { |
| return this.clusterEncryptor.getSecretBytes(); |
| } else { |
| return null; |
| } |
| } |
| |
| protected synchronized void initClusterSecretKey() throws Exception { |
| if (this.clusterEncryptor == null) { |
| this.clusterEncryptor = new GMSEncryptionCipherPool(this, generateSecret(dhPublicKey)); |
| } |
| } |
| |
| protected synchronized void setClusterKey(byte[] secretBytes) { |
| this.clusterEncryptor = new GMSEncryptionCipherPool(this, secretBytes); |
| } |
| |
| private byte[] getPublicKeyIfIAmLocator(MemberIdentifier mbr) { |
| GMSLocator locator = (GMSLocator) services.getLocator(); |
| if (locator != null) { |
| return locator.getPublicKey(mbr); |
| } |
| return null; |
| } |
| |
| GMSEncrypt(Services services, String dhSKAlgo) throws Exception { |
| this.services = services; |
| |
| this.dhSKAlgo = dhSKAlgo; |
| |
| // Initialize the keys when either the host is a peer that has |
| // non-blank setting for DH symmetric algo, or this is a server |
| // that has authenticator defined. |
| if ((this.dhSKAlgo != null && this.dhSKAlgo.length() > 0)) { |
| 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(); |
| } else { |
| dhPrivateKey = null; |
| dhPublicKey = null; |
| } |
| } |
| |
| byte[] decryptData(byte[] data, MemberIdentifier member) throws Exception { |
| return getPeerEncryptor(member).decryptBytes(data); |
| } |
| |
| byte[] encryptData(byte[] data, MemberIdentifier member) throws Exception { |
| return getPeerEncryptor(member).encryptBytes(data); |
| } |
| |
| |
| byte[] decryptData(byte[] data) throws Exception { |
| return clusterEncryptor.decryptBytes(data); |
| } |
| |
| byte[] decryptData(byte[] data, byte[] pkBytes) throws Exception { |
| GMSEncryptionCipherPool encryptor = new GMSEncryptionCipherPool(this, generateSecret(pkBytes)); |
| return encryptor.decryptBytes(data); |
| } |
| |
| byte[] encryptData(byte[] data) throws Exception { |
| return clusterEncryptor.encryptBytes(data); |
| } |
| |
| byte[] getPublicKeyBytes() { |
| return dhPublicKey.getEncoded(); |
| } |
| |
| private byte[] lookupKeyByMember(MemberIdentifier member) { |
| byte[] pk = memberToPublicKey.get(new GMSMemberWrapper(member)); |
| if (pk == null) { |
| pk = getPublicKeyIfIAmLocator(member); |
| } |
| if (pk == null) { |
| pk = (byte[]) view.getPublicKey(member); |
| } |
| if (pk == null) { |
| throw new IllegalStateException("unable to find public key for " + member); |
| } |
| return pk; |
| } |
| |
| protected byte[] getPublicKey(MemberIdentifier member) { |
| try { |
| MemberIdentifier localMbr = services.getMessenger().getMemberID(); |
| if (localMbr != null && localMbr.equals(member)) { |
| return this.dhPublicKey.getEncoded();// local one |
| } |
| return lookupKeyByMember(member); |
| } catch (Exception e) { |
| throw new RuntimeException("Not found public key for member " + member, e); |
| } |
| } |
| |
| protected void setPublicKey(byte[] publickey, MemberIdentifier mbr) { |
| try { |
| memberToPublicKey.put(new GMSMemberWrapper(mbr), publickey); |
| peerEncryptors.replace(mbr, new GMSEncryptionCipherPool(this, generateSecret(publickey))); |
| } catch (Exception e) { |
| throw new RuntimeException("Unable to create peer encryptor " + mbr, e); |
| } |
| } |
| |
| private GMSEncryptionCipherPool getPeerEncryptor(MemberIdentifier member) |
| throws Exception { |
| return JavaWorkarounds.computeIfAbsent(peerEncryptors, member, (mbr) -> { |
| try { |
| return new GMSEncryptionCipherPool(this, generateSecret(lookupKeyByMember(member))); |
| } catch (Exception ex) { |
| throw new RuntimeException(ex); |
| } |
| }); |
| } |
| |
| 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; |
| } |
| |
| Cipher getEncryptCipher(byte[] secretBytes) throws Exception { |
| Cipher encrypt = null; |
| |
| int keysize = getKeySize(dhSKAlgo); |
| int blocksize = getBlockSize(dhSKAlgo); |
| |
| if (keysize == -1 || blocksize == -1) { |
| SecretKeySpec sks = new SecretKeySpec(secretBytes, dhSKAlgo); |
| encrypt = Cipher.getInstance(dhSKAlgo); |
| encrypt.init(Cipher.ENCRYPT_MODE, sks); |
| } else { |
| String dhAlgoStr = getDhAlgoStr(dhSKAlgo); |
| |
| SecretKeySpec sks = new SecretKeySpec(secretBytes, 0, keysize, dhAlgoStr); |
| IvParameterSpec ivps = new IvParameterSpec(secretBytes, keysize, blocksize); |
| |
| encrypt = Cipher.getInstance(dhAlgoStr + "/CBC/PKCS5Padding"); |
| encrypt.init(Cipher.ENCRYPT_MODE, sks, ivps); |
| } |
| |
| return encrypt; |
| } |
| |
| Cipher getDecryptCipher(byte[] secretBytes) throws Exception { |
| Cipher decrypt; |
| |
| int keysize = getKeySize(dhSKAlgo); |
| int blocksize = getBlockSize(dhSKAlgo); |
| |
| if (keysize == -1 || blocksize == -1) { |
| SecretKeySpec sks = new SecretKeySpec(secretBytes, dhSKAlgo); |
| decrypt = Cipher.getInstance(dhSKAlgo); |
| decrypt.init(Cipher.DECRYPT_MODE, sks); |
| } else { |
| String algoStr = getDhAlgoStr(dhSKAlgo); |
| |
| SecretKeySpec sks = new SecretKeySpec(secretBytes, 0, keysize, algoStr); |
| IvParameterSpec ivps = new IvParameterSpec(secretBytes, keysize, blocksize); |
| |
| decrypt = Cipher.getInstance(algoStr + "/CBC/PKCS5Padding"); |
| decrypt.init(Cipher.DECRYPT_MODE, sks, ivps); |
| } |
| return decrypt; |
| } |
| |
| private byte[] generateSecret(byte[] peerKeyBytes) throws Exception { |
| X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(peerKeyBytes); |
| KeyFactory keyFact = KeyFactory.getInstance("DH"); |
| PublicKey peerKey = keyFact.generatePublic(x509KeySpec); |
| return generateSecret(dhSKAlgo, dhPrivateKey, peerKey); |
| } |
| |
| private byte[] generateSecret(PublicKey peerKey) throws Exception { |
| return generateSecret(dhSKAlgo, dhPrivateKey, peerKey); |
| } |
| |
| private static byte[] generateSecret(String dhSKAlgo, PrivateKey privateKey, |
| PublicKey otherPublicKey) throws Exception { |
| KeyAgreement ka = KeyAgreement.getInstance("DH"); |
| ka.init(privateKey); |
| ka.doPhase(otherPublicKey, true); |
| |
| int keysize = getKeySize(dhSKAlgo); |
| int blocksize = getBlockSize(dhSKAlgo); |
| |
| if (keysize == -1 || blocksize == -1) { |
| SecretKey sKey = ka.generateSecret(dhSKAlgo); |
| return sKey.getEncoded(); |
| } else { |
| return ka.generateSecret(); |
| } |
| } |
| } |