/*
* 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());
}
}