/**
* This software is GPLv2.
* Take a look at the LICENSE file for more info.
*/
package de.tu.dresden.dud.dc;
import java.math.BigInteger;
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.NoSuchProviderException;
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.X509EncodedKeySpec;
import java.util.LinkedList;
import org.apache.log4j.Logger;
import de.tu.dresden.dud.dc.InfoService.InfoServiceInfoActiveParticipantList;
import de.tu.dresden.dud.dc.InfoService.InfoServiceInfoPassiveParticipantList;
/**
* The Key Manager handles almost all the crypto stuff.
*
* It generates the Diffie-Hellman key parts, and verifies DSA signatures.
*
*
* Diffie-Hellman needs a generator g and a prime number p. Both are considered as public,
* and fixed for this DC protocol. They are taken out of RFC 5114.
*
* Furthermore each exchanging party needs a secret (a for participant p1 and b for participant p2).
*
* p1 calculates A = g^a mod p; and sends A,g,p to p2.
* p2 calculates B = g^b mod p; and sends B to p2.
*
* The key K = B^a mod p = A^b mod p;
*
* In this implementation "a" is called "dhPrivatePart"
*
* @author klobs
*
*/
public class KeyManager {
// Logging
private static Logger log = Logger.getLogger(KeyManager.class);
// p and g taken from http://www.rfc-archive.org/getrfc.php?rfc=5114
public static final String P = new String
("87A8E61DB4B6663CFFBBD19C651959998CEEF608660DD0F2"
+ "5D2CEED4435E3B00E00DF8F1D61957D4FAF7DF4561B2AA30"
+ "16C3D91134096FAA3BF4296D830E9A7C209E0C6497517ABD"
+ "5A8A9D306BCF67ED91F9E6725B4758C022E0B1EF4275BF7B"
+ "6C5BFC11D45F9088B941F54EB1E59BB8BC39A0BF12307F5C"
+ "4FDB70C581B23F76B63ACAE1CAA6B7902D52526735488A0E"
+ "F13C6D9A51BFA4AB3AD8347796524D8EF6A167B5A41825D9"
+ "67E144E5140564251CCACB83E6B486F6B3CA3F7971506026"
+ "C0B857F689962856DED4010ABD0BE621C3A3960A54E710C3"
+ "75F26375D7014103A4B54330C198AF126116D2276E11715F"
+ "693877FAD7EF09CADB094AE91E1A1597");
public static final String G = new String
("3FB32C9B73134D0B2E77506660EDBD484CA7B18F21EF2054"
+ "07F4793A1A0BA12510DBC15077BE463FFF4FED4AAC0BB555"
+ "BE3A6C1B0C6B47B1BC3773BF7E8C6F62901228F8C28CBB18"
+ "A55AE31341000A650196F931C77A57F2DDF463E5E9EC144B"
+ "777DE62AAAB8A8628AC376D282D6ED3864E67982428EBC83"
+ "1D14348F6F2F9193B5045AF2767164E1DFC967C1FB3F2E55"
+ "A4BD1BFFE83B9C80D052B985D182EA0ADB2A3B7313D3FE14"
+ "C8484B1E052588B9B7D2BBD2DF016199ECD06E1557CD0915"
+ "B3353BBB64E0EC377FD028370DF92B52C7891428CDC67EB6"
+ "184B523D1DB246C32F63078490F00EF8D647D148D4795451"
+ "5E2327CFEF98C582664B4C0F6CC41659");
private BigInteger dhPublicPart = null;
private BigInteger dhPrivatePart = null;
private Signature dsa = null;
private KeyPair keyPair = null;
private LinkedList<String> unfinishedKeyExReqs = new LinkedList<String>();
public KeyManager(){
try {
// Generate DSA public/private key-pair
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
SecureRandom r1 = SecureRandom.getInstance("SHA1PRNG", "SUN");
keyGen.initialize(1024, r1);
dsa = Signature.getInstance("SHA1withDSA", "SUN");
keyPair = keyGen.generateKeyPair();
dsa.initSign(keyPair.getPrivate());
// Call DH-Private part generation:
generateDHPrivatePart();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
public KeyManager(byte[] dsaPublicKey, byte[] dsaPrivateKey){
try {
// setup the DSA Part
// look at http://download.oracle.com/javase/tutorial/security/apisign/vstep2.html for an explanation
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(dsaPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance("DSA", "SUN");
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
X509EncodedKeySpec privKeySpec = new X509EncodedKeySpec(dsaPublicKey);
PrivateKey privKey = keyFactory.generatePrivate(privKeySpec);
keyPair = new KeyPair(pubKey, privKey);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
// generate DH-Public part
generateDHPrivatePart();
}
/**
* This method keeps helping track of unfinished key exchanges. It adds a
* participant's id to a list in case this participant is unknown during the
* key exchange request.
*
* As soon as a new {@link InfoServiceInfoActiveParticipantList} or
* {@link InfoServiceInfoPassiveParticipantList} arrives,
* finishKeyExchangeRequests should be called.
*
* @param id
* The id of the unknown participant.
*/
public synchronized void addUnfinishedKeyExchangeRequest(String id){
if (! unfinishedKeyExReqs.contains(id)) unfinishedKeyExReqs.add(id);
}
/**
* This function does the DH-Key / El-Gamal Key-Calculation.
*
* @param The other participants to exchange keys with.
* @param pm The participant manager that contains all the Participants
*/
public void activateKeyExchangeBetween(String p1, boolean useInverse, ParticipantManager pm){
BigInteger calculatedSecret = null;
ParticipantMgmntInfo pmi = null;
BigInteger remoteDHPublicPart = null;
if (p1 == null || pm == null){
log.warn("Key commit arguments can not be null. Aborting Key Commitment.");
return;
}
pmi = pm.getParticipantMgmntInfoByParticipantID(p1);
if (p1.equals(pm.getMe().getId())) {
log.warn("No need to exchange keys with myself. Aborting Key Commitment.");
return;
}
if(pmi == null) {
log.warn("Unable to find ParticipantManagementInfo for " + p1 + ". Aborting Key Commitment.");
return;
}
if (pmi.getKey().getState() != DCKey.KEY_REQUESTED) {
log.warn("Key of " + p1 + " is not in exchanged state. Aborting Key Commitment.");
return;
}
remoteDHPublicPart = new BigInteger(pmi.getParticipant().getDHPublicPart());
calculatedSecret = remoteDHPublicPart.modPow(dhPrivatePart, new BigInteger(P, 16));
pmi.getKey().setCalculatedSecret(calculatedSecret, useInverse);
}
/**
* If the participant <i>A</i> doesn't know the key exchange requesting
* other participant <i>B</i>, <i>A</i> stores <i>B</i>'s id in a list
* (managed by the KeyManager). In case <i>A</i> receives a new
* {@link InfoServiceInfoPassiveParticipantList} or an
* {@link InfoServiceInfoActiveParticipantList}, it shall call this method
* to try finishing the unfinished key requests.
*
* TODO: The current procedure hopes that the requested <i>id</i>s in the
* list will show up after a while. If they do not, there will be an
* infinite loop, requesting for the participant id.
*
* @param c
* the connection on which the other participant is expected.
*/
public synchronized void finishUnfinishedKeyExchReqs(Connection c){
while(unfinishedKeyExReqs.size() > 0){
String id = unfinishedKeyExReqs.removeFirst();
unfinishedKeyExReqs.remove(id);
c.requestKeyExchange(id);
}
}
private void generateDHPrivatePart(){
// Generate random for session keys:
SecureRandom r = new SecureRandom();
byte[] a = new byte[4];
r.nextBytes(a);
BigInteger generator = new BigInteger(G, 16);
dhPrivatePart = new BigInteger(a);
dhPublicPart = generator.modPow(dhPrivatePart, new BigInteger(P, 16));
}
public byte[] getDHPublicPart(){
if (dhPublicPart == null) return null;
return dhPublicPart.toByteArray();
}
public byte[] getDHPublicPartSignature(){
if (dhPublicPart == null) return null;
return sign(getDHPublicPart());
}
public PublicKey getDSAPublicKey(){
return keyPair.getPublic();
}
public KeyPair getDSAKeypair(){
return keyPair;
}
public String getDSAPublicKeyID(){
if (keyPair == null) return null;
MessageDigest md;
try {
md = MessageDigest.getInstance( "SHA1", "SUN" );
md.update(keyPair.getPublic().getEncoded());
byte[] digest = md.digest();
return Util.convertToHex(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.toString();
e.printStackTrace();
}
return null;
}
public byte[] sign(byte[] m){
if(dsa == null) return null;
try {
dsa.update(m);
return dsa.sign();
} catch (SignatureException e) {
e.printStackTrace();
}
return null;
}
public boolean verifyKey(Participant p){
try {
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(
p.getDSAPublicSignature());
KeyFactory keyFactory = KeyFactory.getInstance("DSA", "SUN");
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
Signature sig = Signature.getInstance("SHA1withDSA", "SUN");
sig.initVerify(pubKey);
sig.update(p.getDHPublicPart());
return sig.verify(p.getDHPublicPartSignature());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
return false;
}
}