package org.limewire.security.id;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPublicKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.limewire.io.GUID;
import org.limewire.io.InvalidDataException;
import org.limewire.security.SecurityUtils;
import org.limewire.util.Base32;
import org.limewire.util.StringUtils;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class SecureIdManagerImpl implements SecureIdManager {
/** 768-bit system-wide Diffie-Hellman parameters
* limewire base32 encoded
*/
private static final String DH_PARAMETER ="GCA4SATBACPJOPGPJPLAVQ5M37DLZKJ2MJOL5JFGQWUVF52ZEAZSNXJXWVCC7NVGWGVVEEPZBRLQ3GDOP7QZZDD2TP76T2D73UFCM3EYLSTBVYCV2FZNU7CVVQXM32YWZIM3FFJHC22EEED66XIVNHUHXF4TVH335RIQEYC732YJHTFBSNUNT4DJLCZT3NQBE564S76Y7FOOA5W7NTTWWTNX2GZLDNAFH4QKZDQ7NHZQJTKD4X4VULJP66EYLBRXZUCJFZRTRVNM4LLLAX3HEGRYFQ5GYJR73MDJGCQVKBJKZFC26OKTN325OWFD63DTAIBAF7Y";
private DHParameterSpec dhParamSpec;
private final SecureIdStore secureIdStore;
private volatile PrivateIdentity localIdentity;
@Inject
public SecureIdManagerImpl(SecureIdStore secureIdStore) {
this.secureIdStore = secureIdStore;
// init DH community parameter
try {
initDHParamSpec();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidParameterSpecException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
// init my privateIdentity, first from locally stored data.
// if fail, then generate a new identity.
try{
localIdentity = new PrivateIdentityImpl(secureIdStore.getLocalData());
} catch (Exception e){
localIdentity = createPrivateIdentity();
secureIdStore.setLocalData(localIdentity.toByteArray());
}
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#exist(org.limewire.io.GUID)
*/
public boolean isKnown(GUID remoteID){
return secureIdStore.get(remoteID) != null;
}
private RemoteIdKeys getRemoteIdKeys(GUID remoteId){
byte[] remoteIdKeysBytes = secureIdStore.get(remoteId);
if(remoteIdKeysBytes == null){
throw new IllegalArgumentException("unknown ID "+remoteId);
}
try {
return new RemoteIdKeys(remoteIdKeysBytes);
} catch (InvalidDataException e) {
throw new RuntimeException(e);
}
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#createHmac(org.limewire.io.GUID, byte[])
*/
public byte[] createHmac(GUID remoteId, byte[] data){
try {
Mac mac = Mac.getInstance(MAC_ALGO);
mac.init(getRemoteIdKeys(remoteId).getOutgoingMacHmacKey());
return mac.doFinal(data);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#verifyHmac(org.limewire.io.GUID, byte[], byte[])
*/
public boolean verifyHmac(GUID remoteId, byte[] data, byte[] hmacValue) {
try {
Mac mac = Mac.getInstance(MAC_ALGO);
mac.init(getRemoteIdKeys(remoteId).getIncomingVerificationHmacKey());
return Arrays.equals(mac.doFinal(data), hmacValue);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#encrypt(org.limewire.io.GUID, byte[])
*/
public byte[] encrypt(GUID remoteId, byte[] plaintext, byte[] iv) throws InvalidAlgorithmParameterException {
try {
Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, getRemoteIdKeys(remoteId).getOutgoingEncryptionKey(), new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(plaintext);
return encrypted;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (IllegalBlockSizeException e) {
throw new RuntimeException(e);
} catch (BadPaddingException e) {
throw new RuntimeException(e);
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidAlgorithmParameterException("Invalid IV");
}
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#decrypt(org.limewire.io.GUID, byte[])
*/
public byte[] decrypt(GUID remoteId, byte[] ciphertext, byte[] iv) throws InvalidDataException, InvalidAlgorithmParameterException{
try {
Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGO);
cipher.init(Cipher.DECRYPT_MODE, getRemoteIdKeys(remoteId).getIncomingDecryptionKey(), new IvParameterSpec(iv));
byte[] plaintext = cipher.doFinal(ciphertext);
return plaintext;
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch (IllegalBlockSizeException e) {
throw new RuntimeException(e);
} catch (BadPaddingException e) {
throw new InvalidDataException("bad ciphertext");
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidAlgorithmParameterException("Invalid IV");
}
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#verifySignature(org.limewire.io.GUID, byte[], byte[])
*/
public boolean verifySignature(GUID remoteId, byte [] data, byte [] signature){
return verifySignature(getRemoteIdKeys(remoteId).getSignaturePublicKey(), data, signature);
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#verifySignature(byte[], byte[], java.security.PublicKey)
*/
public boolean verifySignature(PublicKey publicKey, byte [] data, byte [] signature) {
try {
Signature verifier = Signature.getInstance(SIG_ALGO);
verifier.initVerify(publicKey);
verifier.update(data);
return verifier.verify(signature);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (SignatureException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#getMyIdentity()
*/
public Identity getLocalIdentity(){
return localIdentity;
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#processIdentity(org.limewire.security.id.Identity)
*/
public boolean addIdentity(Identity identity){
RemoteIdKeys rpe = verifyIdentityAndDoKeyAgreement(identity);
if(rpe == null){// bad identify
return false;
}else{
if(!isKnown(identity.getGuid())){
secureIdStore.put(identity.getGuid(), rpe.toByteArray());
}
return true;
}
}
private RemoteIdKeys createRemoteIdKeys(GUID remoteId, PublicKey pk, byte[] sharedSecret){
// generate all the symmetric keys
try{
// mac key for maccing outgoing messages
MessageDigest md = MessageDigest.getInstance(HASH_ALGO);
md.update(StringUtils.toUTF8Bytes("AUTH"));
md.update(localIdentity.getGuid().bytes());
md.update(sharedSecret);
byte[] tempSecretBytes = md.digest();
SecretKey outHmacKey = new SecretKeySpec(tempSecretBytes, MAC_ALGO);
// mac key for verifying incoming mac of messages
md.reset();
md.update(StringUtils.toUTF8Bytes("AUTH"));
md.update(remoteId.bytes());
md.update(sharedSecret);
tempSecretBytes = md.digest();
SecretKey inHmacKey = new SecretKeySpec(tempSecretBytes, MAC_ALGO);
// encryption key for encrypting outgoing messages
md.reset();
md.update(StringUtils.toUTF8Bytes("ENC"));
md.update(localIdentity.getGuid().bytes());
md.update(sharedSecret);
tempSecretBytes = md.digest();
SecretKey encryptionKey = new SecretKeySpec(tempSecretBytes, ENCRYPTION_KEY_ALGO);
// encryption key for decrypting incoming messages
md.reset();
md.update(StringUtils.toUTF8Bytes("ENC"));
md.update(remoteId.bytes());
md.update(sharedSecret);
tempSecretBytes = md.digest();
SecretKey decryptionKey = new SecretKeySpec(tempSecretBytes, ENCRYPTION_KEY_ALGO);
// create remoteIdKeys
RemoteIdKeys rpe = new RemoteIdKeys(remoteId, pk, outHmacKey, inHmacKey, encryptionKey, decryptionKey);
return rpe;
}catch(NoSuchAlgorithmException e){
throw new RuntimeException(e);
}
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#sign(byte[])
*/
public byte[] sign(byte[] data){
return sign(data, localIdentity.getPrivateSignatureKey());
}
private byte[] sign(byte[] data, PrivateKey sigKey){
try {
Signature signer = Signature.getInstance(SIG_ALGO);
signer.initSign(sigKey);
byte[] signature = null;
signer.update(data);
signature = signer.sign();
return signature;
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (SignatureException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/* (non-Javadoc)
* @see org.limewire.security.id.SecureIdManagerI#getMyId()
*/
public GUID getLocalGuid(){
return localIdentity.getGuid();
}
private void initDHParamSpec() throws NoSuchAlgorithmException, IOException, InvalidParameterSpecException{
AlgorithmParameters dhPara = AlgorithmParameters.getInstance(AGREEMENT_ALGO);
dhPara.init(Base32.decode(DH_PARAMETER));
dhParamSpec = dhPara.getParameterSpec(DHParameterSpec.class);
}
private KeyPair signatureKeyPairGen() throws NoSuchAlgorithmException {
KeyPairGenerator gen = KeyPairGenerator.getInstance(SIG_KEY_ALGO);
gen.initialize(SIGNATURE_KEY_SIZE, new SecureRandom());
return gen.generateKeyPair();
}
private KeyPair dhKeyPairGen() throws NoSuchAlgorithmException {
KeyPairGenerator gen = KeyPairGenerator.getInstance(AGREEMENT_ALGO);
try {
gen.initialize(dhParamSpec);
} catch (InvalidAlgorithmParameterException e) {
}
KeyPair keyPair = gen.generateKeyPair();
return keyPair;
}
private PrivateIdentityImpl createPrivateIdentity() {
// multiple installation mark
int multiInstallationMark = SecurityUtils.createSecureRandomNoBlock().nextInt();
try {
// signature key pair
KeyPair signatureKeyPair = signatureKeyPairGen();
// DH key
KeyPair dhKeyPair = dhKeyPairGen();
// my GUID
MessageDigest md = MessageDigest.getInstance(HASH_ALGO);
md.update(signatureKeyPair.getPublic().getEncoded());
byte[] hash = md.digest();
GUID myGuid = new GUID(GUID.makeSecureGuid(hash, multiInstallationMark));
// signature
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
out.write(myGuid.bytes());
out.write(signatureKeyPair.getPublic().getEncoded());
out.write(((DHPublicKey)dhKeyPair.getPublic()).getY().toByteArray());
} catch (IOException e) {
return null;
}
byte[] payload = out.toByteArray();
final byte[] signature = sign(payload, signatureKeyPair.getPrivate());
return new PrivateIdentityImpl(myGuid, signatureKeyPair.getPublic(), ((DHPublicKey)dhKeyPair.getPublic()).getY(), signature,
signatureKeyPair.getPrivate(), (DHPrivateKey)dhKeyPair.getPrivate(), multiInstallationMark);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private RemoteIdKeys verifyIdentityAndDoKeyAgreement(Identity identity) {
PublicKey remoteSignatureKey = identity.getPublicSignatureKey();
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
out.write(identity.getGuid().bytes());
out.write(identity.getPublicSignatureKey().getEncoded());
out.write(identity.getPublicDiffieHellmanComponent().toByteArray());
} catch (IOException e) {
return null;
}
byte[] payload = out.toByteArray();
// verify signature
if (! verifySignature(remoteSignatureKey, payload, identity.getSignature()))
return null;
// verify remoteID
GUID remoteID = identity.getGuid();//new GUID(remoteGuidBytes);
MessageDigest md;
try {
md = MessageDigest.getInstance(HASH_ALGO);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
md.update(remoteSignatureKey.getEncoded());
byte[] hash = md.digest();
if (! GUID.isSecureGuid(hash, remoteID) )
return null;
// diffie-hellman agreement
DHPublicKeySpec dhPubSpec = new DHPublicKeySpec(identity.getPublicDiffieHellmanComponent(), dhParamSpec.getP(), dhParamSpec.getG());
KeyAgreement keyAgree;
try {
keyAgree = KeyAgreement.getInstance(AGREEMENT_ALGO);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
KeyFactory factory;
try {
factory = KeyFactory.getInstance(AGREEMENT_ALGO);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
PublicKey remoteDhKey;
try {
remoteDhKey = factory.generatePublic(dhPubSpec);
keyAgree.init(localIdentity.getPrivateDiffieHellmanKey());
keyAgree.doPhase(remoteDhKey, true);
} catch (InvalidKeySpecException e) {
return null;
} catch (InvalidKeyException e) {
return null;
} catch (IllegalStateException e) {
return null;
}
byte[] sharedSecret = keyAgree.generateSecret();
RemoteIdKeys rpe = createRemoteIdKeys(remoteID, remoteSignatureKey, sharedSecret);
return rpe;
}
}