/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008-2013 Palo Alto Research Center, Inc.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. You should have received
* a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.ccnx.ccn.impl.security.crypto.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import java.util.logging.Level;
import javax.crypto.Mac;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DEREncodable;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DERTags;
import org.bouncycastle.asn1.DERUnknownTag;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.ccnx.ccn.KeyManager;
import org.ccnx.ccn.config.PlatformConfiguration;
import org.ccnx.ccn.impl.security.crypto.SignatureLocks;
import org.ccnx.ccn.impl.security.crypto.gingerbreadfix.JDKDigestSignature;
import org.ccnx.ccn.impl.support.Log;
/**
* Helper class for generating signatures.
*/
public class SignatureHelper {
/**
* Signs an array of bytes with a private signing key and specified digest algorithm.
* @param digestAlgorithm the digest algorithm. if null uses DEFAULT_DIGEST_ALGORITHM
* @param toBeSigned the array of bytes to be signed.
* @param signingKey the signing key.
* @return the signature.
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public static byte [] sign(String digestAlgorithm,
byte [] toBeSigned,
Key signingKey) throws SignatureException,
NoSuchAlgorithmException, InvalidKeyException {
if (null == toBeSigned) {
Log.info("sign: null content to be signed!");
throw new SignatureException("Cannot sign null content!");
}
if (null == signingKey) {
Log.info("sign: Signing key cannot be null.");
Log.info("Temporarily generating fake signature.");
return DigestHelper.digest(digestAlgorithm, toBeSigned);
}
String sigAlgName =
getSignatureAlgorithmName(((null == digestAlgorithm) || (digestAlgorithm.length() == 0)) ?
DigestHelper.DEFAULT_DIGEST_ALGORITHM : digestAlgorithm,
signingKey);
if (null != sigAlgName && sigAlgName.toUpperCase().startsWith(CryptoConstants.HMAC)) {
Mac mac = Mac.getInstance(sigAlgName, KeyManager.PROVIDER);
mac.init(signingKey);
return mac.doFinal(toBeSigned);
}
if (null == sigAlgName)
throw new InvalidKeyException("Key algorithm: " + signingKey.getAlgorithm() + "not supported");
// DKS TODO if we switch to SHA256, this fails.
Signature sig = Signature.getInstance(sigAlgName);
// Protect against GC on platforms that don't do JNI for crypto properly
SignatureLocks.signingLock();
try {
sig.initSign((PrivateKey)signingKey);
sig.update(toBeSigned);
return sig.sign();
} finally {
SignatureLocks.signingUnock();
}
}
/**
* Sign concatenation of the toBeSigneds.
* @param digestAlgorithm the digest algorithm. if null uses DEFAULT_DIGEST_ALGORITHM
* @param toBeSigneds the content to be signed.
* @param signingKey the signing key.
* @return the signature.
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public static byte [] sign(String digestAlgorithm,
byte[][] toBeSigneds,
Key signingKey) throws SignatureException,
NoSuchAlgorithmException, InvalidKeyException {
if (null == toBeSigneds) {
Log.info("sign: null content to be signed!");
throw new SignatureException("Cannot sign null content!");
}
if (null == signingKey) {
Log.info("sign: Signing key cannot be null.");
Log.info("Temporarily generating fake signature.");
return DigestHelper.digest(digestAlgorithm, toBeSigneds);
}
String sigAlgName =
getSignatureAlgorithmName(((null == digestAlgorithm) || (digestAlgorithm.length() == 0)) ?
DigestHelper.DEFAULT_DIGEST_ALGORITHM : digestAlgorithm,
signingKey);
if (null != sigAlgName && sigAlgName.toUpperCase().startsWith(CryptoConstants.HMAC)) {
Mac mac = Mac.getInstance(sigAlgName, KeyManager.PROVIDER);
mac.init(signingKey);
for (byte[] toBeSigned : toBeSigneds)
mac.update(toBeSigned);
return mac.doFinal();
}
if (null == sigAlgName)
throw new InvalidKeyException("Key algorithm: " + signingKey.getAlgorithm() + "not supported");
Signature sig = Signature.getInstance(sigAlgName);
// Protect against GC on platforms that don't do JNI for crypto properly
SignatureLocks.signingLock();
try {
sig.initSign((PrivateKey)signingKey);
for (int i=0; i < toBeSigneds.length; ++i) {
sig.update(toBeSigneds[i]);
}
return sig.sign();
} finally {
SignatureLocks.signingUnock();
}
}
/**
* Verifies the signature on the concatenation of a set of individual
* data items, given the verification key and digest algorithm.
* @param data the data; which are expected to have been concatenated before
* signing. Any null arrays are skipped.
* @param signature the signature.
* @param digestAlgorithm the digest algorithm. if null uses DEFAULT_DIGEST_ALGORITHM
* @param verificationKey the public or symmetric (secret) verification key.
* @return the correctness of the signature as a boolean.
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public static boolean verify(
final byte[][] data,
final byte [] signature,
String digestAlgorithm,
final Key verificationKey) throws SignatureException,
NoSuchAlgorithmException, InvalidKeyException {
if (null == verificationKey) {
Log.info("verify: Verifying key cannot be null.");
throw new IllegalArgumentException("verify: Verifying key cannot be null.");
}
String sigAlgName =
getSignatureAlgorithmName(((null == digestAlgorithm) || (digestAlgorithm.length() == 0)) ?
DigestHelper.DEFAULT_DIGEST_ALGORITHM : digestAlgorithm,
verificationKey);
if (null != sigAlgName && sigAlgName.toUpperCase().startsWith(CryptoConstants.HMAC)) {
Mac mac = Mac.getInstance(sigAlgName, KeyManager.PROVIDER);
mac.init(verificationKey);
for (byte[] b : data) {
mac.update(b);
}
byte[] check = mac.doFinal();
return Arrays.equals(check, signature);
}
if (PlatformConfiguration.workaroundGingerbreadBug) {
// this clause is only used when running on Android Gingerbread. It is
// necessary to work around a bug in the Gingerbread version of
// Bouncycastle
return new JDKDigestSignature.SHA256WithRSAEncryption() {
boolean verify() throws InvalidKeyException, SignatureException {
SignatureLocks.signingLock();
try {
engineInitVerify((PublicKey)verificationKey);
if (null != data) {
for (int i=0; i < data.length; ++i) {
if (data[i] != null)
engineUpdate(data[i], 0, data[i].length);
}
}
return engineVerify(signature);
} finally {
SignatureLocks.signingUnock();
}
}
}.verify();
} else {
if (null == sigAlgName)
throw new InvalidKeyException("Key algorithm: " + verificationKey.getAlgorithm() + "not supported");
Signature sig = Signature.getInstance(sigAlgName);
// Protect against GC on platforms that don't do JNI for crypto properly
SignatureLocks.signingLock();
try {
sig.initVerify((PublicKey)verificationKey);
if (null != data) {
for (int i=0; i < data.length; ++i) {
if (data[i] != null)
sig.update(data[i]);
}
}
return sig.verify(signature);
} finally {
SignatureLocks.signingUnock();
}
}
}
/**
* Verify a standalone signature.
* @param data the data whose signature we want to verify
* @param signature the signature itself
* @param digestAlgorithm the digest algorithm used to generate the signature,
* if null uses DEFAULT_DIGEST_ALGORITHM
* @param verificationKey the public key to verify the signature with
* @return true if signature valid, false otherwise
* @throws InvalidKeyException
* @throws SignatureException
* @throws NoSuchAlgorithmException
*/
public static boolean verify(byte [] data, byte [] signature, String digestAlgorithm,
Key verificationKey)
throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
return verify(new byte[][]{data}, signature, digestAlgorithm, verificationKey);
}
/**
* Gets an AlgorithmIdentifier incorporating a given digest and
* encryption algorithm, and containing any necessary parameters for
* the signing key.
*
* @param hashAlgorithm the JCA standard name of the digest algorithm
* (e.g. "SHA1")
* @param signingKey the private key that will be used to compute the
* signature
* @return the algorithm identifier.
* @throws NoSuchAlgorithmException if the algorithm identifier can't
* be formed
* @throws InvalidParameterSpecException
* @throws InvalidAlgorithmParameterException
*/
public static AlgorithmIdentifier getSignatureAlgorithm(
String hashAlgorithm, Key signingKey)
throws NoSuchAlgorithmException, InvalidParameterSpecException,
InvalidAlgorithmParameterException
{
String signatureAlgorithmOID = getSignatureAlgorithmOID(
hashAlgorithm, signingKey.getAlgorithm());
if (signatureAlgorithmOID == null) {
if (Log.isLoggable(Level.WARNING)) {
Log.warning("Error: got no signature algorithm!");
}
throw new NoSuchAlgorithmException(
"Cannot determine OID for hash algorithm "+ hashAlgorithm + " and encryption alg " + signingKey.getAlgorithm());
}
AlgorithmIdentifier thisSignatureAlgorithm = null;
try {
DEREncodable paramData = null;
AlgorithmParameters params = OIDLookup.getParametersFromKey(signingKey);
if (params == null) {
paramData = new DERUnknownTag(DERTags.NULL, new byte [0]);
} else {
ByteArrayInputStream bais = new ByteArrayInputStream(params.getEncoded());
ASN1InputStream dis = new ASN1InputStream(bais);
paramData = dis.readObject();
}
// Now we need the OID and the parameters. This is not the most
// efficient way in the world to do this, but it should work.
thisSignatureAlgorithm =
new AlgorithmIdentifier(new DERObjectIdentifier(signatureAlgorithmOID),
paramData);
} catch (IOException ex) {
System.out.println("This should not happen: getSignatureAlgorithm -- " );
System.out.println(" IOException thrown when decoding a key");
ex.getMessage();
ex.printStackTrace();
throw new InvalidParameterSpecException(ex.getMessage());
}
return thisSignatureAlgorithm;
}
/**
* Gets the JCA string name of a signature algorithm, to be used with
* a Signature object.
*
* @param hashAlgorithm the JCA standard name of the digest algorithm
* (e.g. "SHA1").
* @param signingKey the key that will be used to compute the
* signature.
*
* @returns the JCA string alias for the signature algorithm.
*/
public static String getSignatureAlgorithmName(
String hashAlgorithm, Key signingKey)
{
return getSignatureAlgorithmName(hashAlgorithm, signingKey.getAlgorithm());
}
/**
* Gets the JCA string name of a signature algorithm, to be used with
* a Signature object.
* @param hashAlgorithm the JCA standard name of the digest algorithm
* (e.g. "SHA1").
* @param keyAlgorithm the key algorithm.
* @return the JCA string alias for the signature algorithm.
*/
public static String getSignatureAlgorithmName(
String hashAlgorithm, String keyAlgorithm)
{
String signatureAlgorithm = OIDLookup.getSignatureAlgorithm(hashAlgorithm,
keyAlgorithm);
//Log.info("getSignatureAlgorithmName: combining " +
// hashAlgorithm + " and " + keyAlgorithm +
// " results in: " + signatureAlgorithm);
return signatureAlgorithm;
}
/**
* Gets the OID of a signature algorithm, to be used with
* a Signature object.
* @param hashAlgorithm the JCA standard name of the digest algorithm
* (e.g. "SHA1").
* @param keyAlgorithm the key algorithm.
* @return the JCA string alias for the signature algorithm.
*/
public static String getSignatureAlgorithmOID(
String hashAlgorithm, String keyAlgorithm)
{
String signatureAlgorithm = OIDLookup.getSignatureAlgorithmOID(hashAlgorithm,
keyAlgorithm);
// Log.info("getSignatureAlgorithmOID: combining " +
// hashAlgorithm + " and " + keyAlgorithm +
// " results in: " + signatureAlgorithm);
return signatureAlgorithm;
}
}