package org.openslx.satellitedaemon;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Properties;
import java.util.Random;
import org.apache.log4j.Logger;
import org.openslx.encryption.AsymKeyHolder;
import org.openslx.satellitedaemon.filetransfer.ThriftConnection;
import org.openslx.satellitedaemon.util.Util;
public class Identity
{
private static Logger log = Logger.getLogger( Identity.class );
private static final Properties properties = new Properties();
private static AsymKeyHolder akh = null;
public static String getOrganizationName()
{
return properties.getProperty( "ORGANIZATION_NAME" );
}
private static BigInteger getModulus()
{
return toBigInt( properties.getProperty( "MODULUS" ) );
}
private static BigInteger getPublicExponent()
{
return toBigInt( properties.getProperty( "PUBLIC_EXPONENT" ) );
}
private static BigInteger getPrivateExponent()
{
return toBigInt( properties.getProperty( "PRIVATE_EXPONENT" ) );
}
/**
* Load properties
*/
static {
InputStreamReader stream = null;
try {
// Load all entries of the config file into properties
stream = new InputStreamReader(
new FileInputStream( "config/identity.properties" ), StandardCharsets.UTF_8 );
properties.load( stream );
stream.close();
} catch ( IOException e ) {
log.error( "Could not load identity.properties. Exiting." );
System.exit( 2 );
} finally {
Util.streamClose( stream );
}
Util.notNullOrEmptyFatal( getOrganizationName(), "Organiziation Name must not be empty!" );
try {
akh = new AsymKeyHolder( getPrivateExponent(), getPublicExponent(), getModulus() );
} catch ( InvalidKeySpecException e ) {
log.error( "InvalidKeySpecException", e );
} catch ( NoSuchAlgorithmException e ) {
log.error( "NoSuchAlgorithmException", e );
}
}
/**
* Get private key for this server. If none exists yet, create a new one.
*
* @return
*/
public static PrivateKey getPrivateKey()
{
if ( akh != null ) {
return akh.getPrivateKey();
}
akh = new AsymKeyHolder();
return akh.getPrivateKey();
}
/**
* Get public key for this server. If none exists yet, create a new one.
*
* @return
*/
public static PublicKey getPublicKey()
{
if ( akh != null )
return akh.getPublicKey();
akh = new AsymKeyHolder();
return akh.getPublicKey();
}
/**
* Get bit - length of key.
*
* @return
*/
public static int keySize( BigInteger modulus )
{
return modulus.bitLength();
}
/**
* Checks if given modulus, private exponent and public exponent are valid
* values for key pair. Idea is to encrypt and decrypt random text and compare
* the result with initial text.
*
* @param mod
* @param privExp
* @param pubExp
* @return True, if mod, privExp and pubExp are valid values.
*/
public static boolean isValidKeyPair( BigInteger mod, BigInteger privExp, BigInteger pubExp )
{
// First check given values (modulus, privExp, pubExp).
if ( ( mod == null ) || ( privExp == null ) || ( pubExp == null ) ) {
log.error( "Given arguments not valid: got NULL for modulus, private or public exponent." );
return false;
}
// Testing encryption and description with given public and private key.
// Idea: creating random text for encrypting and decrypting again.
Random rnd = new Random();
int size = rnd.nextInt( keySize( mod ) - 1 );
BigInteger text = new BigInteger( size, rnd );
// Encrypt.
BigInteger cipher = text.modPow( pubExp, mod );
// Decrypt again.
BigInteger decrypted = cipher.modPow( privExp, mod );
boolean isPassed = text.equals( decrypted );
return isPassed;
}
/**
* Generate new identity with given organization name and new key pair.
* Write new identity to "config/identity.properties".
*
* @param organizationName
* @return true, if successful.
*/
public static boolean generateIdentity( String organizationName )
{
// generate new key pair.
Identity.akh = new AsymKeyHolder();
return writeIdToFile( organizationName, akh.getModulus(), akh.getPrivateExponent(), akh.getPublicExponent() );
}
/**
* Import given identity with organization name, modulus, private and public
* exponent and store this identity to "config/identity.properties".
*
* @param organizationName
* @param modulus
* @param privateExp
* @param publicExp
* @return true, if successful.
*/
public static boolean importIdentity( String organizationName, BigInteger modulus, BigInteger privateExp, BigInteger publicExp )
{
return writeIdToFile( organizationName, modulus, privateExp, publicExp );
}
/**
* Submit new satellite - ipAddress to master with organizationId, ipAddress
* and key - information.
* @param ipAddress
* @return true, if successful.
*/
public static boolean submitKey( String ipAddress )
{
RSAPublicKey pubKey = (RSAPublicKey)getPublicKey();
RSAPrivateKey privKey = (RSAPrivateKey)getPrivateKey();
assert ( pubKey.getModulus() == privKey.getModulus() );
if ( !Identity.isValidKeyPair(
privKey.getModulus(),
privKey.getPrivateExponent(),
pubKey.getPublicExponent() ) )
return false;
return ThriftConnection.registerSatellite(
getOrganizationName(),
ipAddress,
pubKey.getModulus().toString(),
pubKey.getPublicExponent().toString() );
}
/**
* Update already existing satellite - ipAddress in master - Db.
* @param ipAddress
* @return true, if successful.
*/
public static boolean updateAddress( String ipAddress )
{
return ThriftConnection.updateSatelliteAddress( ipAddress );
}
/**
* Write given organization name, modulus, public and private exponent to
* "config/identity.properties".
*
* @param organizationName
* @param modulus
* @param privateExp
* @param publicExp
* @return true, if successful.
*/
private static boolean writeIdToFile( String organizationName, BigInteger modulus, BigInteger privateExp, BigInteger publicExp )
{
File configFile = new File( "config/identity.properties" );
FileOutputStream stream = null;
try {
stream = new FileOutputStream( configFile );
} catch ( FileNotFoundException e ) {
log.error( "FileNotFoundException", e );
return false;
}
// create strings for writing to file.
String orgNameString = "ORGANIZATION_NAME=" + organizationName + "\n";
String modString = "MODULUS=" + modulus.toString() + "\n";
String privExpString = "PRIVATE_EXPONENT=" + privateExp.toString() + "\n";
String pubExpString = "PUBLIC_EXPONENT=" + publicExp.toString() + "\n";
try {
stream.write( orgNameString.getBytes() );
stream.write( modString.getBytes() );
stream.write( privExpString.getBytes() );
stream.write( pubExpString.getBytes() );
return true;
} catch ( IOException e ) {
log.error( "IOException", e );
return false;
} finally {
try {
stream.close();
} catch ( IOException e ) {
}
}
}
/**
* Check modulus, privExp and pubExp for not being null.
*
* @return
*/
private static boolean checkMembers()
{
return ( ( getModulus() != null ) &&
( getPrivateExponent() != null ) && ( getPublicExponent() != null ) );
}
/**
* Get BigInteger of read String number.
*
* @param str
* @return
*/
private static BigInteger toBigInt( String str )
{
try {
return new BigInteger( str );
} catch ( Exception e ) {
return null;
}
}
}