package com.limegroup.gnutella.security;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.KeyFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.io.UnsupportedEncodingException;
import java.io.File;
import com.limegroup.gnutella.util.FileUtils;
import com.bitzi.util.Base32;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
public class SignatureVerifier {
private static final Log LOG = LogFactory.getLog(SignatureVerifier.class);
private final byte[] plainText;
private final byte[] signature;
private final PublicKey publicKey;
private final String algorithm;
private final String digAlg;
public SignatureVerifier(byte[] pText, byte[] sigBytes, PublicKey key,
String algorithm) {
this(pText, sigBytes, key, algorithm, null);
}
public SignatureVerifier(byte[] pText, byte[] sigBytes, PublicKey key,
String algorithm, String digAlg) {
this.plainText = pText;
this.signature = sigBytes;
this.publicKey = key;
this.algorithm = algorithm;
this.digAlg = digAlg;
}
public String toString() {
//String alg = digAlg == null ? algorithm : digAlg + "with" + algorithm;
return "text: " + new String(plainText) + ", sig: " + new String(signature) +
", key: " + publicKey + ", alg: " + algorithm + ", digAlg: " + digAlg;
}
public boolean verifySignature() {
String alg = digAlg == null ? algorithm : digAlg + "with" + algorithm;
try {
Signature verifier = Signature.getInstance(alg);
verifier.initVerify(publicKey);
verifier.update(plainText,0, plainText.length);
return verifier.verify(signature);
} catch (NoSuchAlgorithmException nsax) {
LOG.error("No alg." + this, nsax);
return false;
} catch (InvalidKeyException ikx) {
LOG.error("Invalid key. " + this, ikx);
return false;
} catch (SignatureException sx) {
LOG.error("Bad sig." + this, sx);
return false;
} catch (ClassCastException ccx) {
LOG.error("bad cast." + this, ccx);
return false;
}
}
/**
* Retrieves the data from a byte[] containing both the signature & content,
* returning the data only if it is verified.
*/
public static String getVerifiedData(byte[] data, File keyFile, String alg, String dig) {
PublicKey key = readKey(keyFile, alg);
byte[][] info = parseData(data);
return verify(key, info, alg, dig);
}
/**
* Retrieves the data from a file, returning the data only if it is verified.
*/
public static String getVerifiedData(File source, File keyFile, String alg, String dig) {
PublicKey key = readKey(keyFile, alg);
byte[][] info = parseData(FileUtils.readFileFully(source));
return verify(key, info, alg, dig);
}
/**
* Verified the key, info, using the algorithm & digest algorithm.
*/
private static String verify(PublicKey key, byte[][] info, String alg, String dig) {
if(key == null || info == null) {
LOG.warn("No key or data to verify.");
return null;
}
SignatureVerifier sv = new SignatureVerifier(info[1], info[0], key, alg, dig);
if(sv.verifySignature()) {
try {
return new String(info[1], "UTF-8");
} catch(UnsupportedEncodingException uee) {
return new String(info[1]);
}
} else {
return null;
}
}
/**
* Reads a public key from disk.
*/
public static PublicKey readKey(File keyFile, String alg) {
byte[] fileData = FileUtils.readFileFully(keyFile);
if(fileData == null)
return null;
try {
EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base32.decode(new String(fileData)));
KeyFactory kf = KeyFactory.getInstance(alg);
PublicKey key = kf.generatePublic(pubKeySpec);
return key;
} catch(NoSuchAlgorithmException nsae) {
LOG.error("Invalid algorithm: " + alg, nsae);
return null;
} catch(InvalidKeySpecException ikse) {
LOG.error("Invalid keyspec: " + keyFile, ikse);
return null;
}
}
/**
* Parses data, returning the signature & content.
*/
private static byte[][] parseData(byte[] data) {
if(data == null) {
LOG.warn("No data to parse.");
return null;
}
// look for the separator between sig & data.
int i = findPipes(data);
if(i == -1 || i >= data.length - 3) {
LOG.warn("Couldn't find pipes.");
return null;
}
byte[] sig = new byte[i];
byte[] content = new byte[data.length - i - 2];
System.arraycopy(data, 0, sig, 0, sig.length);
System.arraycopy(data, i+2, content, 0, content.length);
return new byte[][] { Base32.decode(new String(sig)), content };
}
/**
* @return the index of "|" starting from startIndex, -1 if none found in
* this.data
*/
private static int findPipes(byte[] data) {
for(int i = 0 ; i < data.length-1; i++) {
if(data[i] == (byte)124 && data[i+1] == (byte)124)
return i;
}
return -1;
}
}