/* * Created on 15 Jun 2006 * Created by Paul Gardner * Copyright (C) 2006 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.core.security.impl; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.spec.AlgorithmParameterSpec; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import org.minicastle.jce.provider.JCEIESCipher; import org.minicastle.jce.spec.IEKeySpec; import org.minicastle.jce.spec.IESParameterSpec; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.RandomUtils; import org.gudy.azureus2.core3.util.SystemTime; import com.aelitis.azureus.core.security.CryptoECCUtils; import com.aelitis.azureus.core.security.CryptoHandler; import com.aelitis.azureus.core.security.CryptoManager; import com.aelitis.azureus.core.security.CryptoManagerException; import com.aelitis.azureus.core.security.CryptoManagerPasswordException; import com.aelitis.azureus.core.security.CryptoManagerPasswordHandler; import com.aelitis.azureus.core.security.CryptoSTSEngine; public class CryptoHandlerECC implements CryptoHandler { private static final String DEFAULT_PASSWORD = ""; private static final Long DEFAULT_TIMEOUT = Long.MAX_VALUE; private static final int TIMEOUT_DEFAULT_SECS = 60*60; private CryptoManagerImpl manager; private String CONFIG_PREFIX = CryptoManager.CRYPTO_CONFIG_PREFIX + "ecc."; private PrivateKey use_method_private_key; private PublicKey use_method_public_key; private long last_unlock_time; protected CryptoHandlerECC( CryptoManagerImpl _manager, int _instance_id ) { manager = _manager; CONFIG_PREFIX += _instance_id + "."; // migration away from system managed keys if ( getDefaultPasswordHandlerType() != CryptoManagerPasswordHandler.HANDLER_TYPE_USER ){ COConfigurationManager.setParameter( CONFIG_PREFIX + "default_pwtype", CryptoManagerPasswordHandler.HANDLER_TYPE_USER ); } if ( getCurrentPasswordType() == CryptoManagerPasswordHandler.HANDLER_TYPE_SYSTEM || COConfigurationManager.getByteParameter( CONFIG_PREFIX + "publickey", null ) == null ){ try{ createAndStoreKeys( manager.setPassword( CryptoManager.HANDLER_ECC, CryptoManagerPasswordHandler.HANDLER_TYPE_USER, DEFAULT_PASSWORD.toCharArray(), DEFAULT_TIMEOUT )); Debug.outNoStack( "Successfully migrated key management" ); }catch( Throwable e ){ Debug.out( "Failed to migrate key management", e ); } } } public int getType() { return( CryptoManager.HANDLER_ECC ); } public void unlock() throws CryptoManagerException { getMyPrivateKey( "unlock" ); } public synchronized boolean isUnlocked() { return( use_method_private_key != null ); } public void lock() { boolean changed = false; synchronized( this ){ changed = use_method_private_key != null; use_method_private_key = null; } if ( changed ){ manager.lockChanged( this ); } } public int getUnlockTimeoutSeconds() { return( COConfigurationManager.getIntParameter( CONFIG_PREFIX + "timeout", TIMEOUT_DEFAULT_SECS )); } public void setUnlockTimeoutSeconds( int secs ) { COConfigurationManager.setParameter( CONFIG_PREFIX + "timeout", secs ); } public byte[] sign( byte[] data, String reason ) throws CryptoManagerException { PrivateKey priv = getMyPrivateKey( reason ); Signature sig = CryptoECCUtils.getSignature( priv ); try{ sig.update( data ); return( sig.sign()); }catch( Throwable e ){ throw( new CryptoManagerException( "Signature failed", e )); } } public boolean verify( byte[] public_key, byte[] data, byte[] signature ) throws CryptoManagerException { PublicKey pub = CryptoECCUtils.rawdataToPubkey( public_key ); Signature sig = CryptoECCUtils.getSignature( pub ); try{ sig.update( data ); return( sig.verify( signature )); }catch( Throwable e ){ throw( new CryptoManagerException( "Signature failed", e )); } } public byte[] encrypt( byte[] other_public_key, byte[] data, String reason ) throws CryptoManagerException { try{ IEKeySpec key_spec = new IEKeySpec( getMyPrivateKey( reason ), CryptoECCUtils.rawdataToPubkey( other_public_key )); byte[] d = new byte[16]; byte[] e = new byte[16]; RandomUtils.nextSecureBytes( d ); RandomUtils.nextSecureBytes( e ); IESParameterSpec param = new IESParameterSpec( d, e, 128); InternalECIES cipher = new InternalECIES(); cipher.internalEngineInit( Cipher.ENCRYPT_MODE, key_spec, param, null ); byte[] encrypted = cipher.internalEngineDoFinal(data, 0, data.length ); byte[] result = new byte[32+encrypted.length]; System.arraycopy( d, 0, result, 0, 16 ); System.arraycopy( e, 0, result, 16, 16 ); System.arraycopy( encrypted, 0, result, 32, encrypted.length ); return( result ); }catch( CryptoManagerException e ){ throw( e ); }catch( Throwable e){ throw( new CryptoManagerException( "Encrypt failed", e )); } } public byte[] decrypt( byte[] other_public_key, byte[] data, String reason ) throws CryptoManagerException { try{ IEKeySpec key_spec = new IEKeySpec( getMyPrivateKey( reason ), CryptoECCUtils.rawdataToPubkey( other_public_key )); byte[] d = new byte[16]; byte[] e = new byte[16]; System.arraycopy( data, 0, d, 0, 16 ); System.arraycopy( data, 16, e, 0, 16 ); IESParameterSpec param = new IESParameterSpec( d, e, 128); InternalECIES cipher = new InternalECIES(); cipher.internalEngineInit( Cipher.DECRYPT_MODE, key_spec, param, null ); return( cipher.internalEngineDoFinal( data, 32, data.length - 32 )); }catch( CryptoManagerException e ){ throw( e ); }catch( Throwable e){ throw( new CryptoManagerException( "Decrypt failed", e )); } } public CryptoSTSEngine getSTSEngine( String reason ) throws CryptoManagerException { return( new CryptoSTSEngineImpl( getMyPublicKey( reason, true ), getMyPrivateKey( reason ))); } public byte[] peekPublicKey() { try{ return( CryptoECCUtils.keyToRawdata( getMyPublicKey( "peek", false ))); }catch( Throwable e ){ return( null ); } } public byte[] getPublicKey( String reason ) throws CryptoManagerException { return( CryptoECCUtils.keyToRawdata( getMyPublicKey( reason, true ))); } public byte[] getEncryptedPrivateKey( String reason ) throws CryptoManagerException { getMyPrivateKey( reason ); byte[] pk = COConfigurationManager.getByteParameter( CONFIG_PREFIX + "privatekey", null ); if ( pk == null ){ throw( new CryptoManagerException( "Private key unavailable" )); } int pw_type = getCurrentPasswordType(); byte[] res = new byte[pk.length+1]; res[0] = (byte)pw_type; System.arraycopy( pk, 0, res, 1, pk.length ); return( res ); } public void recoverKeys( byte[] public_key, byte[] encrypted_private_key_and_type ) throws CryptoManagerException { boolean lock_changed = false; synchronized( this ){ lock_changed = use_method_private_key != null; use_method_private_key = null; use_method_public_key = null; manager.clearPassword( CryptoManager.HANDLER_ECC, CryptoManagerPasswordHandler.HANDLER_TYPE_ALL ); COConfigurationManager.setParameter( CONFIG_PREFIX + "publickey", public_key ); int type = (int)encrypted_private_key_and_type[0]&0xff; COConfigurationManager.setParameter( CONFIG_PREFIX + "pwtype", type ); byte[] encrypted_private_key = new byte[encrypted_private_key_and_type.length-1]; System.arraycopy( encrypted_private_key_and_type, 1, encrypted_private_key, 0, encrypted_private_key.length ); COConfigurationManager.setParameter( CONFIG_PREFIX + "privatekey", encrypted_private_key ); COConfigurationManager.save(); } manager.keyChanged( this ); if ( lock_changed ){ manager.lockChanged( this ); } } public void resetKeys( String reason ) throws CryptoManagerException { boolean lock_changed = false; synchronized( this ){ lock_changed = use_method_private_key != null; use_method_private_key = null; use_method_public_key = null; manager.clearPassword( CryptoManager.HANDLER_ECC, CryptoManagerPasswordHandler.HANDLER_TYPE_ALL ); COConfigurationManager.removeParameter( CONFIG_PREFIX + "publickey" ); COConfigurationManager.removeParameter( CONFIG_PREFIX + "privatekey" ); COConfigurationManager.save(); } if ( lock_changed ){ manager.lockChanged( this ); } try{ createAndStoreKeys( "resetting keys" ); }catch( CryptoManagerException e ){ manager.keyChanged( this ); throw( e ); } } protected PrivateKey getMyPrivateKey( String reason ) throws CryptoManagerException { boolean lock_change = false; try{ synchronized( this ){ if ( use_method_private_key != null ){ int timeout_secs = getUnlockTimeoutSeconds(); if ( timeout_secs > 0 ){ if ( SystemTime.getCurrentTime() - last_unlock_time >= timeout_secs * 1000 ){ lock_change = true; use_method_private_key = null; } } } if ( use_method_private_key != null ){ return( use_method_private_key ); } } final byte[] encoded = COConfigurationManager.getByteParameter( CONFIG_PREFIX + "privatekey", null ); if ( encoded == null ){ return((PrivateKey)createAndStoreKeys( reason )[1]); }else{ CryptoManagerImpl.passwordDetails password_details = manager.getPassword( CryptoManager.HANDLER_ECC, CryptoManagerPasswordHandler.ACTION_DECRYPT, reason, new CryptoManagerImpl.passwordTester() { public boolean testPassword( char[] password ) { try{ manager.decryptWithPBE( encoded, password ); return( true ); }catch( Throwable e ){ return( false ); } } }, getCurrentPasswordType()); synchronized( this ){ boolean ok = false; try{ use_method_private_key = CryptoECCUtils.rawdataToPrivkey( manager.decryptWithPBE( encoded, password_details.getPassword())); lock_change = true; last_unlock_time = SystemTime.getCurrentTime(); if ( !checkKeysOK( reason )){ throw( new CryptoManagerPasswordException( true, "Password incorrect" )); } ok = true; }catch( CryptoManagerException e ){ throw( e ); }catch( Throwable e ){ throw( new CryptoManagerException( "Password incorrect", e )); }finally{ if ( !ok ){ manager.clearPassword( CryptoManager.HANDLER_ECC, CryptoManagerPasswordHandler.HANDLER_TYPE_ALL ); lock_change = true; use_method_private_key = null; } } } if ( use_method_private_key == null ){ throw( new CryptoManagerException( "Failed to get private key" )); } return( use_method_private_key ); } }finally{ if ( lock_change ){ manager.lockChanged( this ); } } } protected boolean checkKeysOK( String reason ) throws CryptoManagerException { byte[] test_data = "test".getBytes(); return( verify( CryptoECCUtils.keyToRawdata( getMyPublicKey( reason, true )), test_data, sign( test_data, reason ))); } protected PublicKey getMyPublicKey( String reason, boolean create_if_needed ) throws CryptoManagerException { boolean create_new = false; synchronized( this ){ if ( use_method_public_key == null ){ byte[] key_bytes = COConfigurationManager.getByteParameter( CONFIG_PREFIX + "publickey", null ); if ( key_bytes == null ){ if ( create_if_needed ){ create_new = true; }else{ return( null ); } }else{ use_method_public_key = CryptoECCUtils.rawdataToPubkey( key_bytes ); } } if ( !create_new ){ if ( use_method_public_key == null ){ throw( new CryptoManagerException( "Failed to get public key" )); } return( use_method_public_key ); } } return((PublicKey)createAndStoreKeys( reason )[0] ); } public int getDefaultPasswordHandlerType() { return( COConfigurationManager.getIntParameter( CONFIG_PREFIX + "default_pwtype", CryptoManagerPasswordHandler.HANDLER_TYPE_USER )); } public void setDefaultPasswordHandlerType( int new_type ) throws CryptoManagerException { String reason = "Changing password handler"; boolean have_existing_keys = COConfigurationManager.getByteParameter( CONFIG_PREFIX + "privatekey", null ) != null; // ensure we unlock the private key so we can then re-persist it with new password if ( have_existing_keys ){ if ( new_type == getCurrentPasswordType()){ return; } getMyPrivateKey( reason ); CryptoManagerImpl.passwordDetails password_details = manager.getPassword( CryptoManager.HANDLER_ECC, CryptoManagerPasswordHandler.ACTION_ENCRYPT, reason, null, new_type ); synchronized( this ){ if ( use_method_private_key == null ){ throw( new CryptoManagerException( "Private key not available" )); } byte[] priv_raw = CryptoECCUtils.keyToRawdata( use_method_private_key ); byte[] priv_enc = manager.encryptWithPBE( priv_raw, password_details.getPassword()); COConfigurationManager.setParameter( CONFIG_PREFIX + "privatekey", priv_enc ); COConfigurationManager.setParameter( CONFIG_PREFIX + "pwtype", password_details.getHandlerType()); COConfigurationManager.setParameter( CONFIG_PREFIX + "default_pwtype", password_details.getHandlerType()); COConfigurationManager.save(); } }else{ // not much to do as keys not yet created synchronized( this ){ if ( COConfigurationManager.getByteParameter( CONFIG_PREFIX + "privatekey", null ) == null ){ COConfigurationManager.setParameter( CONFIG_PREFIX + "default_pwtype", new_type ); COConfigurationManager.save(); } } } } protected Key[] createAndStoreKeys( String reason ) throws CryptoManagerException { CryptoManagerImpl.passwordDetails password_details = manager.getPassword( CryptoManager.HANDLER_ECC, CryptoManagerPasswordHandler.ACTION_ENCRYPT, reason, null, getDefaultPasswordHandlerType()); return( createAndStoreKeys( password_details )); } protected Key[] createAndStoreKeys( CryptoManagerImpl.passwordDetails password_details ) throws CryptoManagerException { try{ synchronized( this ){ if ( use_method_public_key == null || use_method_private_key == null ){ KeyPair keys = CryptoECCUtils.createKeys(); use_method_public_key = keys.getPublic(); use_method_private_key = keys.getPrivate(); last_unlock_time = SystemTime.getCurrentTime(); COConfigurationManager.setParameter( CONFIG_PREFIX + "publickey", CryptoECCUtils.keyToRawdata( use_method_public_key )); byte[] priv_raw = CryptoECCUtils.keyToRawdata( use_method_private_key ); byte[] priv_enc = manager.encryptWithPBE( priv_raw, password_details.getPassword()); COConfigurationManager.setParameter( CONFIG_PREFIX + "privatekey", priv_enc ); COConfigurationManager.setParameter( CONFIG_PREFIX + "pwtype", password_details.getHandlerType()); COConfigurationManager.save(); } return( new Key[]{ use_method_public_key, use_method_private_key }); } }finally{ manager.keyChanged( this ); manager.lockChanged( this ); } } public boolean verifyPublicKey( byte[] encoded ) { try{ CryptoECCUtils.rawdataToPubkey( encoded ); // we can't actually verify the key size as although it should be 192 bits // it can be less due to leading bits being 0 return( true ); }catch( Throwable e ){ return( false ); } } public String exportKeys() throws CryptoManagerException { return( "id: " + Base32.encode(manager.getSecureID()) + "\r\n" + "public: " + Base32.encode(getPublicKey( "Key export" )) + "\r\n" + "private: " + Base32.encode(getEncryptedPrivateKey( "Key export" ))); } public boolean importKeys( String str ) throws CryptoManagerException { String reason = "Key import"; byte[] existing_id = manager.getSecureID(); byte[] existing_public_key = peekPublicKey(); byte[] existing_private_key = existing_public_key==null?null:getEncryptedPrivateKey( reason ); byte[] recovered_id = null; byte[] recovered_public_key = null; byte[] recovered_private_key = null; String[] bits = str.split( "\n" ); for (int i=0;i<bits.length;i++){ String bit = bits[i].trim(); if ( bit.length() == 0 ){ continue; } String[] x = bit.split(":"); if ( x.length != 2 ){ continue; } String lhs = x[0].trim(); String rhs = x[1].trim(); byte[] rhs_val = Base32.decode( rhs ); if ( lhs.equals( "id" )){ recovered_id = rhs_val; }else if ( lhs.equals( "public" )){ recovered_public_key = rhs_val; }else if ( lhs.equals( "private" )){ recovered_private_key = rhs_val; } } if ( recovered_id == null || recovered_public_key == null || recovered_private_key == null ){ throw( new CryptoManagerException( "Invalid input file" )); } boolean ok = false; boolean result = false; try{ result = !Arrays.equals( existing_id, recovered_id ); if ( result ){ manager.setSecureID( recovered_id ); } recoverKeys( recovered_public_key, recovered_private_key ); if ( !checkKeysOK( reason )){ throw( new CryptoManagerException( "Invalid key pair" )); } ok = true; }finally{ if ( !ok ){ result = false; manager.setSecureID( existing_id ); if ( existing_public_key != null ){ recoverKeys( existing_public_key, existing_private_key ); } } } return( result ); } protected int getCurrentPasswordType() { return((int)COConfigurationManager.getIntParameter( CONFIG_PREFIX + "pwtype", CryptoManagerPasswordHandler.HANDLER_TYPE_USER )); } class InternalECIES extends JCEIESCipher.ECIES { // we use this class to obtain compatability with BC public void internalEngineInit( int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random ) throws InvalidKeyException, InvalidAlgorithmParameterException { engineInit(opmode, key, params, random); } protected byte[] internalEngineDoFinal( byte[] input, int inputOffset, int inputLen ) throws IllegalBlockSizeException, BadPaddingException { return engineDoFinal(input, inputOffset, inputLen); } } }