/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.plugin.otr; import java.security.*; import java.security.spec.*; import java.util.*; import net.java.otr4j.crypto.*; import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.service.protocol.*; /** * * @author George Politis * @author Lyubomir Marinov */ public class ScOtrKeyManagerImpl implements ScOtrKeyManager { private final OtrConfigurator configurator = new OtrConfigurator(); private final List<ScOtrKeyManagerListener> listeners = new Vector<ScOtrKeyManagerListener>(); public void addListener(ScOtrKeyManagerListener l) { synchronized (listeners) { if (!listeners.contains(l)) listeners.add(l); } } /** * Gets a copy of the list of <tt>ScOtrKeyManagerListener</tt>s registered * with this instance which may safely be iterated without the risk of a * <tt>ConcurrentModificationException</tt>. * * @return a copy of the list of <tt>ScOtrKeyManagerListener<tt>s registered * with this instance which may safely be iterated without the risk of a * <tt>ConcurrentModificationException</tt> */ private ScOtrKeyManagerListener[] getListeners() { synchronized (listeners) { return listeners.toArray( new ScOtrKeyManagerListener[listeners.size()]); } } public void removeListener(ScOtrKeyManagerListener l) { synchronized (listeners) { listeners.remove(l); } } public void verify(OtrContact otrContact, String fingerprint) { if ((fingerprint == null) || otrContact == null) return; this.configurator.setProperty(otrContact.contact.getAddress() + fingerprint + ".fingerprint.verified", true); for (ScOtrKeyManagerListener l : getListeners()) l.contactVerificationStatusChanged(otrContact); } public void unverify(OtrContact otrContact, String fingerprint) { if ((fingerprint == null) || otrContact == null) return; this.configurator.setProperty(otrContact.contact.getAddress() + fingerprint + ".fingerprint.verified", false); for (ScOtrKeyManagerListener l : getListeners()) l.contactVerificationStatusChanged(otrContact); } public boolean isVerified(Contact contact, String fingerprint) { if (fingerprint == null || contact == null) return false; return this.configurator.getPropertyBoolean( contact.getAddress() + fingerprint + ".fingerprint.verified", false); } public List<String> getAllRemoteFingerprints(Contact contact) { if (contact == null) return null; /* * The following lines are needed for backward compatibility with old * versions of the otr plugin. Instead of lists of fingerprints the otr * plugin used to store one public key for every contact in the form of * "userID.publicKey=..." and one boolean property in the form of * "userID.publicKey.verified=...". In order not to loose these old * properties we have to convert them to match the new format. */ String userID = contact.getAddress(); byte[] b64PubKey = this.configurator.getPropertyBytes(userID + ".publicKey"); if (b64PubKey != null) { // We delete the old format property because we are going to convert // it in the new format this.configurator.removeProperty(userID + ".publicKey"); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64PubKey); KeyFactory keyFactory; try { keyFactory = KeyFactory.getInstance("DSA"); PublicKey pubKey = keyFactory.generatePublic(publicKeySpec); boolean isVerified = this.configurator.getPropertyBoolean(userID + ".publicKey.verified", false); // We also make sure to delete this old format property if it // exists. this.configurator.removeProperty(userID + ".publicKey.verified"); String fingerprint = getFingerprintFromPublicKey(pubKey); // Now we can store the old properties in the new format. if (isVerified) verify(OtrContactManager.getOtrContact(contact, null), fingerprint); else unverify(OtrContactManager.getOtrContact(contact, null), fingerprint); // Finally we append the new fingerprint to out stored list of // fingerprints. this.configurator.appendProperty( userID + ".fingerprints", fingerprint); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeySpecException e) { e.printStackTrace(); } } // Now we can safely return our list of fingerprints for this contact // without worrying that we missed an old format property. return this.configurator.getAppendedProperties( contact.getAddress() + ".fingerprints"); } public String getFingerprintFromPublicKey(PublicKey pubKey) { try { return new OtrCryptoEngineImpl().getFingerprint(pubKey); } catch (OtrCryptoException e) { e.printStackTrace(); return null; } } public String getLocalFingerprint(AccountID account) { KeyPair keyPair = loadKeyPair(account); if (keyPair == null) return null; PublicKey pubKey = keyPair.getPublic(); try { return new OtrCryptoEngineImpl().getFingerprint(pubKey); } catch (OtrCryptoException e) { e.printStackTrace(); return null; } } public byte[] getLocalFingerprintRaw(AccountID account) { KeyPair keyPair = loadKeyPair(account); if (keyPair == null) return null; PublicKey pubKey = keyPair.getPublic(); try { return new OtrCryptoEngineImpl().getFingerprintRaw(pubKey); } catch (OtrCryptoException e) { e.printStackTrace(); return null; } } public void saveFingerprint(Contact contact, String fingerprint) { if (contact == null) return; this.configurator.appendProperty(contact.getAddress() + ".fingerprints", fingerprint); this.configurator.setProperty(contact.getAddress() + fingerprint + ".fingerprint.verified", false); } public KeyPair loadKeyPair(AccountID account) { if (account == null) return null; String accountID = account.getAccountUniqueID(); // Load Private Key. byte[] b64PrivKey = this.configurator.getPropertyBytes(accountID + ".privateKey"); if (b64PrivKey == null) return null; PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(b64PrivKey); // Load Public Key. byte[] b64PubKey = this.configurator.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 void generateKeyPair(AccountID account) { if (account == null) return; String accountID = account.getAccountUniqueID(); 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.configurator.setProperty(accountID + ".publicKey", x509EncodedKeySpec.getEncoded()); // Store Private Key. PrivateKey privKey = keyPair.getPrivate(); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privKey.getEncoded()); this.configurator.setProperty(accountID + ".privateKey", pkcs8EncodedKeySpec.getEncoded()); } }