package org.jivesoftware.spark.otrplug.impl;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import org.bouncycastle.util.encoders.Base64;
import net.java.otr4j.OtrKeyManager;
import net.java.otr4j.OtrKeyManagerListener;
import net.java.otr4j.OtrKeyManagerStore;
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.crypto.OtrCryptoException;
import net.java.otr4j.session.SessionID;
/**
* An implementation of the OtrKeyManager provided by otr4j. It stores the local
* key chain
*
* @author Bergunde Holger
*
*/
public class MyOtrKeyManager implements OtrKeyManager {
private OtrKeyManagerStore store;
/**
* Use this consturctor if you want to use your own implementation for key
* storage
*
* @param store
* Imlementation of OtrKeyManagerStore
*/
public MyOtrKeyManager(OtrKeyManagerStore store) {
this.store = store;
}
/**
* Inner class, own implemented key sotrage
*
* @author Bergunde Holger
*
*/
class DefaultPropertiesStore implements OtrKeyManagerStore {
private final Properties properties = new Properties();
private String filepath;
public DefaultPropertiesStore(String filepath) throws IOException {
if (filepath == null || filepath.length() < 1)
throw new IllegalArgumentException();
this.filepath = filepath;
properties.clear();
InputStream in = new BufferedInputStream(new FileInputStream(getConfigurationFile()));
try {
properties.load(in);
} finally {
in.close();
}
}
private File getConfigurationFile() throws IOException {
File configFile = new File(filepath);
if (!configFile.exists())
configFile.createNewFile();
return configFile;
}
public void setProperty(String id, boolean value) {
properties.setProperty(id, "true");
try {
this.store();
} catch (Exception e) {
e.printStackTrace();
}
}
private void store() throws FileNotFoundException, IOException {
OutputStream out = new FileOutputStream(getConfigurationFile());
properties.store(out, null);
out.close();
}
public void setProperty(String id, byte[] value) {
properties.setProperty(id, new String(Base64.encode(value)));
try {
this.store();
} catch (Exception e) {
e.printStackTrace();
}
}
public void removeProperty(String id) {
properties.remove(id);
try {
this.store();
} catch (Exception e) {
e.printStackTrace();
}
}
public byte[] getPropertyBytes(String id) {
String value = properties.getProperty(id);
if (value == null)
return null;
return Base64.decode(value);
}
public boolean getPropertyBoolean(String id, boolean defaultValue) {
try {
return Boolean.valueOf(properties.get(id).toString());
} catch (Exception e) {
return defaultValue;
}
}
}
/**
* Use this consturctor if key manager should use his own implementation of
* key storage
*
* @param filepath
* file where the keys should be stored
* @throws IOException
*/
public MyOtrKeyManager(String filepath) throws IOException {
this.store = new DefaultPropertiesStore(filepath);
}
private List<OtrKeyManagerListener> listeners = new Vector<OtrKeyManagerListener>();
/**
* Adds listener to key manager
*/
public void addListener(OtrKeyManagerListener l) {
synchronized (listeners) {
if (!listeners.contains(l))
listeners.add(l);
}
}
/**
* Remove listener from key manager
*/
public void removeListener(OtrKeyManagerListener l) {
synchronized (listeners) {
listeners.remove(l);
}
}
/**
* Generate a local key pair. Be careful. If there is already an key pair,
* it will override it
*
* @param sessionID
* the sessionID that is identified with the local machine
*/
public void generateLocalKeyPair(SessionID sessionID) {
if (sessionID == null)
return;
String accountID = sessionID.getAccountID();
KeyPair keyPair;
try {
keyPair = KeyPairGenerator.getInstance("DSA").genKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return;
}
// Store Public Key.
PublicKey pubKey = keyPair.getPublic();
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey.getEncoded());
this.store.setProperty(accountID + ".publicKey", x509EncodedKeySpec.getEncoded());
// Store Private Key.
PrivateKey privKey = keyPair.getPrivate();
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privKey.getEncoded());
this.store.setProperty(accountID + ".privateKey", pkcs8EncodedKeySpec.getEncoded());
}
/**
*
* Returns the local finger print for specified session. If there is no
* finger print you might generate one.
*
* @return the local finger print for this sessionID
*/
public String getLocalFingerprint(SessionID sessionID) {
KeyPair keyPair = loadLocalKeyPair(sessionID);
if (keyPair == null)
return null;
PublicKey pubKey = keyPair.getPublic();
try {
return new OtrCryptoEngineImpl().getFingerprint(pubKey);
} catch (OtrCryptoException e) {
e.printStackTrace();
return null;
}
}
/**
* Return remote finger print for specified sessionID.
*
* @return finger print for remote contact
*/
public String getRemoteFingerprint(SessionID sessionID) {
PublicKey remotePublicKey = loadRemotePublicKey(sessionID);
if (remotePublicKey == null)
return null;
try {
return new OtrCryptoEngineImpl().getFingerprint(remotePublicKey);
} catch (OtrCryptoException e) {
e.printStackTrace();
return null;
}
}
/**
* check if the specified sessionID is verified for this machine
*
*/
public boolean isVerified(SessionID sessionID) {
if (sessionID == null)
return false;
return this.store.getPropertyBoolean(sessionID.getUserID() + ".publicKey.verified", false);
}
/**
* Returns the key pair (private and public key) for the local machine
*
* @param sessionID
* sessionID for currect machine
*/
public KeyPair loadLocalKeyPair(SessionID sessionID) {
if (sessionID == null)
return null;
String accountID = sessionID.getAccountID();
// Load Private Key.
byte[] b64PrivKey = this.store.getPropertyBytes(accountID + ".privateKey");
if (b64PrivKey == null)
return null;
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(b64PrivKey);
// Load Public Key.
byte[] b64PubKey = this.store.getPropertyBytes(accountID + ".publicKey");
if (b64PubKey == null)
return null;
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64PubKey);
PublicKey publicKey;
PrivateKey privateKey;
// Generate KeyPair.
KeyFactory keyFactory;
try {
keyFactory = KeyFactory.getInstance("DSA");
publicKey = keyFactory.generatePublic(publicKeySpec);
privateKey = keyFactory.generatePrivate(privateKeySpec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
} catch (InvalidKeySpecException e) {
e.printStackTrace();
return null;
}
return new KeyPair(publicKey, privateKey);
}
/**
* Loads the public key for the specified sessionID. If there is no key
* stored, you will get 'null'
*/
public PublicKey loadRemotePublicKey(SessionID sessionID) {
if (sessionID == null)
return null;
String userID = sessionID.getUserID();
byte[] b64PubKey = this.store.getPropertyBytes(userID + ".publicKey");
if (b64PubKey == null)
return null;
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64PubKey);
// Generate KeyPair.
KeyFactory keyFactory;
try {
keyFactory = KeyFactory.getInstance("DSA");
return keyFactory.generatePublic(publicKeySpec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
} catch (InvalidKeySpecException e) {
e.printStackTrace();
return null;
}
}
/**
* Stores the public key for a specified user from sessionID
*
* @param sessionID
* sessionID to identifiy the owner of the key
* @param pubKey
* the key which should be stored
*/
public void savePublicKey(SessionID sessionID, PublicKey pubKey) {
if (sessionID == null)
return;
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey.getEncoded());
String userID = sessionID.getUserID();
this.store.setProperty(userID + ".publicKey", x509EncodedKeySpec.getEncoded());
this.store.removeProperty(userID + ".publicKey.verified");
}
/**
* Removes the verification for the specified sessionID
*/
public void unverify(SessionID sessionID) {
if (sessionID == null)
return;
if (!isVerified(sessionID))
return;
this.store.removeProperty(sessionID.getUserID() + ".publicKey.verified");
for (OtrKeyManagerListener l : listeners)
l.verificationStatusChanged(sessionID);
}
/**
* Verify the specified sessionID
*/
public void verify(SessionID sessionID) {
if (sessionID == null)
return;
if (this.isVerified(sessionID))
return;
this.store.setProperty(sessionID.getUserID() + ".publicKey.verified", true);
for (OtrKeyManagerListener l : listeners)
l.verificationStatusChanged(sessionID);
}
}