package net.java.otr4j; 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.Arrays; import java.util.List; import java.util.Properties; import java.util.Vector; import net.java.otr4j.crypto.OtrCryptoEngineImpl; import net.java.otr4j.crypto.OtrCryptoException; import net.java.otr4j.session.SessionID; import android.util.Base64; public class OtrKeyManagerDefaultImpl implements OtrKeyManager { private OtrKeyManagerStore store; public OtrKeyManagerDefaultImpl(OtrKeyManagerStore store) { this.store = store; } 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, Base64.encodeToString(value,Base64.NO_WRAP)); try { this.store(); } catch (Exception e) { e.printStackTrace(); } } public void removeProperty(String id) { properties.remove(id); } public byte[] getPropertyBytes(String id) { String value = properties.getProperty(id); return Base64.decode(value,Base64.NO_WRAP); } public boolean getPropertyBoolean(String id, boolean defaultValue) { try { return Boolean.valueOf(properties.get(id).toString()); } catch (Exception e) { return defaultValue; } } } public OtrKeyManagerDefaultImpl(String filepath) throws IOException { this.store = new DefaultPropertiesStore(filepath); } private List<OtrKeyManagerListener> listeners = new Vector<OtrKeyManagerListener>(); public void addListener(OtrKeyManagerListener l) { synchronized (listeners) { if (!listeners.contains(l)) listeners.add(l); } } public void removeListener(OtrKeyManagerListener l) { synchronized (listeners) { listeners.remove(l); } } public void generateLocalKeyPair(SessionID sessionID) { if (sessionID == null) return; String accountID = sessionID.getLocalUserId(); 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()); } 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; } } 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; } } public boolean isVerified(SessionID sessionID) { if (sessionID == null) return false; return this.store.getPropertyBoolean(sessionID.getLocalUserId() + ".publicKey.verified", false); } public KeyPair loadLocalKeyPair(SessionID sessionID) { if (sessionID == null) return null; String accountID = sessionID.getLocalUserId(); // 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); } public PublicKey loadRemotePublicKey(SessionID sessionID) { if (sessionID == null) return null; String userID = sessionID.getRemoteUserId(); 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; } } public void savePublicKey(SessionID sessionID, PublicKey pubKey) { if (sessionID == null) return; X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey.getEncoded()); String userID = sessionID.getRemoteUserId(); byte[] keyEnc = x509EncodedKeySpec.getEncoded(); String keyString = userID + ".publicKey"; byte[] keyExisting = store.getPropertyBytes(keyString); if (keyExisting != null && (!Arrays.equals(keyEnc, keyExisting))) { store.removeProperty(userID + ".publicKey.verified"); //remove any verified state store.setProperty(userID + ".publicKey", keyEnc); } } public void unverify(SessionID sessionID) { if (sessionID == null) return; if (!isVerified(sessionID)) return; this.store.removeProperty(sessionID.getRemoteUserId() + ".publicKey.verified"); for (OtrKeyManagerListener l : listeners) l.verificationStatusChanged(sessionID); } public void verify(SessionID sessionID) { if (sessionID == null) return; if (this.isVerified(sessionID)) return; store.setProperty(sessionID.getRemoteUserId() + ".publicKey.verified", true); for (OtrKeyManagerListener l : listeners) l.verificationStatusChanged(sessionID); } public void remoteVerifiedUs(SessionID sessionID) { if (sessionID == null) return; for (OtrKeyManagerListener l : listeners) l.remoteVerifiedUs(sessionID); } }