/* * (C) Copyright IBM Corp. 2010 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb.apps; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashSet; import java.util.Hashtable; import java.util.Set; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import com.ibm.gaiandb.GaianDBConfig; import com.ibm.gaiandb.GaianNode; import sun.misc.BASE64Encoder; public class SecurityClientAgent { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2010"; private static final boolean IS_SECURITY_EXCLUDED_FROM_RELEASE = GaianNode.IS_SECURITY_EXCLUDED_FROM_RELEASE; public static final String GDB_CREDENTIALS = "GDB_CREDENTIALS"; private static final String USR_SUFFIX = "_USR"; private static final String PWD_SUFFIX = "_PWD"; public static final String KEY_ALGORITHM_RSA = "RSA"; public static final String CHECKSUM_ALGORITHM_MD5 = "MD5"; public static final String CHECKSUM_ALGORITHM_SHA1 = "SHA1"; public static final int ENCRYPTED_BLOCK_NUMBYTES_RSA = 64; private static KeyFactory keyFactory = null; private static Cipher cipher = null; // Code below is used to set and get the access credentials column which is a concatenation of all encrypted usr,pwd,checksum blocks. private Hashtable<String, byte[]> pubKeys = new Hashtable<String, byte[]>(); private Hashtable<String, String> remoteAccessCredentials = new Hashtable<String, String>(); public void setRemoteAccessCredentials( String nodeID, String usr, String pwd ) { if ( IS_SECURITY_EXCLUDED_FROM_RELEASE ) return; remoteAccessCredentials.put( nodeID + USR_SUFFIX, usr ); remoteAccessCredentials.put( nodeID + PWD_SUFFIX, pwd ); } public void retainAllRemoteAccessCredentialsForNodes( Set<String> nodes ) { if ( IS_SECURITY_EXCLUDED_FROM_RELEASE ) return; Set<String> keysToRetain = new HashSet<String>(); for ( String node : nodes ) { keysToRetain.add( node + USR_SUFFIX ); keysToRetain.add( node + PWD_SUFFIX ); } Set<String> allKeysBackedByMap = remoteAccessCredentials.keySet(); allKeysBackedByMap.retainAll(keysToRetain); } public void removeRemoteAccessCredentials( String nodeID ) { if ( IS_SECURITY_EXCLUDED_FROM_RELEASE ) return; remoteAccessCredentials.remove( nodeID + USR_SUFFIX ); remoteAccessCredentials.remove( nodeID + PWD_SUFFIX ); } // public void clearRemoteAccessCredentials() { // if ( IS_SECURITY_EXCLUDED_FROM_RELEASE ) return; // remoteAccessCredentials.clear(); // } public boolean isSecurityCredentialsSpecified() { if ( IS_SECURITY_EXCLUDED_FROM_RELEASE ) return false; return !remoteAccessCredentials.isEmpty(); } // Only use this if the calling code is running in the same JVM as the Derby network server. public void refreshPublicKeysFromServers() throws SQLException { if ( IS_SECURITY_EXCLUDED_FROM_RELEASE ) return; refreshPublicKeysFromServers( GaianDBConfig.getEmbeddedDerbyConnection().createStatement() ); } private static final int PUBLIC_KEYS_VALIDITY_PERIOD_MS = 10000; // 10 seconds private long lastPublicKeysRefreshTime = 0; public void refreshPublicKeysFromServers( Statement stmt ) throws SQLException { if ( IS_SECURITY_EXCLUDED_FROM_RELEASE ) return; if ( lastPublicKeysRefreshTime + PUBLIC_KEYS_VALIDITY_PERIOD_MS > System.currentTimeMillis() ) return; lastPublicKeysRefreshTime = System.currentTimeMillis(); // must be done before the sql call or we get an infinite loop String getPublicKeysSQL = "select gdb_node, pkey from "+ "new com.ibm.db2j.GaianQuery('select gPublicKey() pkey from sysibm.sysdummy1','with_provenance') Q"; ResultSet rs = stmt.executeQuery(getPublicKeysSQL); while ( rs.next() ) pubKeys.put( rs.getString(1), rs.getBytes(2) ); } public String getEncryptedCredentialsValueInBase64( String sql ) throws Exception { if ( IS_SECURITY_EXCLUDED_FROM_RELEASE ) return null; byte[] queryChecksumBytes = getChecksum(sql.getBytes(), CHECKSUM_ALGORITHM_MD5); // 1 set of credentials taking NUMBYTES_RSA bytes for each recorded usr/pwd pair int credNumBytes = ENCRYPTED_BLOCK_NUMBYTES_RSA * remoteAccessCredentials.size()/2; byte[] credentials = new byte[ credNumBytes ]; int i = 0; for ( String nodeID : pubKeys.keySet() ) { String usr = remoteAccessCredentials.get( nodeID + USR_SUFFIX ); String pwd = remoteAccessCredentials.get( nodeID + PWD_SUFFIX ); if ( null == usr || null == pwd ) continue; // no credentials for this server. byte[] decrypted = joinByteArrays( new byte[] { (byte) usr.length() }, usr.getBytes(), new byte[] { (byte) pwd.length() }, pwd.getBytes(), new byte[] { (byte) queryChecksumBytes.length }, queryChecksumBytes ); byte[] encrypted = encrypt( decrypted, deriveRSAPublicKey(pubKeys.get(nodeID)) ); // System.out.println("New credentials block before encryption: " + new String(decrypted)); // System.out.println("New credentials block after encryption (length " + encrypted.length + "): " + new String(encrypted)); if ( encrypted.length != ENCRYPTED_BLOCK_NUMBYTES_RSA ) throw new Exception("Invalid length for encrypted credentials for " + nodeID + ": " + encrypted.length); System.arraycopy(encrypted, 0, credentials, i, encrypted.length); i += encrypted.length; } return new BASE64Encoder().encode(credentials).replaceAll("[\r\n]", ""); // remember to remove the inserted CRLFs } private static PublicKey deriveRSAPublicKey( byte[] publicKeyBytes ) throws NoSuchAlgorithmException, InvalidKeySpecException { if ( null == keyFactory ) keyFactory = KeyFactory.getInstance(KEY_ALGORITHM_RSA); return keyFactory.generatePublic( new X509EncodedKeySpec(publicKeyBytes) ); } private static byte[] encrypt( byte[] decrypted, Key publicKey ) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { if ( null == cipher ) cipher = Cipher.getInstance(KEY_ALGORITHM_RSA); cipher.init(Cipher.ENCRYPT_MODE, publicKey); //System.out.println("Max encrypted byte[] length: " + cipher.getOutputSize( decrypted.length ) ); byte[] encrypted = cipher.doFinal(decrypted); return encrypted; } private static byte[] getChecksum( byte[] input, String algo ) throws NoSuchAlgorithmException { MessageDigest checksum = MessageDigest.getInstance(algo); return checksum.digest(input); } private static byte[] joinByteArrays( byte[]... byteArrays ) { int resultSize = 0; for ( byte[] b : byteArrays ) resultSize += b.length; byte[] result = new byte[ resultSize ]; int i=0; for ( byte[] b : byteArrays ) { System.arraycopy(b, 0, result, i, b.length); i+=b.length; } return result; } }