package com.sap.jam.api.security;
import java.io.ByteArrayInputStream;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import javax.crypto.Cipher;
import org.opensaml.xml.util.Base64;
/**
* This utility class has various helpers for working with signatures, certificates,
* and private keys.
*
* For example, generation of a signature, ie, signing data with a private key 2) Verification of such
* signatures, ie, verifying signature with a public key/certificate
*
* Public facing functions fall into one of these two buckets, variations are
* overloaded based on different input parameters and renamed based on different
* output values (ie, we like to handle base64 encoding so the caller doesn't
* need to)
*
* the notion of a "raw signature" is one that is stored as a java byte array,
* and has not been base64 encoded
*/
public final class SignatureUtil {
private final static String SIGNATURE_ALGORITHM = "SHA1withRSA";
private final static String PRIVATE_KEY_ALGORITHM = "RSA";
private final static String PUBLIC_CERT_ALGORITHM = "X.509";
/**
* Signature Generation functions all functions take the form signature =
* f(private key, signature base)
*
*/
public static String generateBase64Signature( final String privateKeyBase64, final byte[] signatureBase) {
return Base64.encodeBytes(generateRawSignature(makePrivateKey(privateKeyBase64), signatureBase));
}
public static String generateBase64Signature(final PrivateKey privateKey, final byte[] signatureBase) {
return Base64.encodeBytes(generateRawSignature(privateKey, signatureBase));
}
public static byte[] generateRawSignature(final String privateKeyBase64, final byte[] signatureBase) {
return generateRawSignature(makePrivateKey(privateKeyBase64), signatureBase);
}
public static byte[] generateRawSignature(final PrivateKey privateKey, final byte[] signatureBase) {
try {
final Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initSign(privateKey);
sig.update(signatureBase);
return sig.sign();
} catch (final InvalidKeyException e) {
throw new RuntimeException("Invalid private key: " + e.getMessage(), e);
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("Unknown signature generation algorithm (" + SIGNATURE_ALGORITHM + ") " + e.getMessage(), e);
} catch (final java.security.SignatureException e) {
throw new RuntimeException("Error encountered while signing: " + e.getMessage(), e);
}
}
/**
* Signature Verification functions all functions take the form success =
* f(public key, signature, signature base)
*
*/
public static boolean verifyBase64Signature(final String base64Certificate, final String base64Signature, final byte[] signatureBase) {
return verifyRawSignature(makePublicKey(base64Certificate), getRawSignatureFromBase64(base64Signature), signatureBase);
}
public static boolean verifyBase64Signature(final PublicKey publicKey, final String base64Signature, final byte[] signatureBase) {
return verifyRawSignature(publicKey, getRawSignatureFromBase64(base64Signature), signatureBase);
}
public static boolean verifyRawSignature(final String base64Certificate, final byte[] rawSignature, final byte[] signatureBase) {
return verifyRawSignature(makePublicKey(base64Certificate), rawSignature, signatureBase);
}
public static boolean verifyRawSignature(final PublicKey publicKey, final byte[] rawSignature, final byte[] signatureBase) {
try {
final Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signatureBase);
return sig.verify(rawSignature);
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("Unknown signature verification algorithm (" + SIGNATURE_ALGORITHM + ") " + e.getMessage(), e);
} catch (final InvalidKeyException e) {
throw new RuntimeException("Invalid public key: " + e.getMessage(), e);
} catch (final java.security.SignatureException e) {
throw new RuntimeException("Error encountered while verifying signature: " + e.getMessage(), e);
}
}
/**
* Utility helper functions prevent duplication of work over the various
* overloads
*/
/**
* convert basee64 signature to a raw signature
*/
private static byte[] getRawSignatureFromBase64(final String signature) {
return Base64.decode(signature);
}
/**
* convert a base64 encoded certificate into a java object public key
*/
public static PublicKey makePublicKey(final String certificateBase64) {
if (certificateBase64 == null || certificateBase64.isEmpty()) {
throw new IllegalArgumentException("Supplied 'certificateBase64' argument is null or empty.");
}
try {
final CertificateFactory cf = CertificateFactory.getInstance(PUBLIC_CERT_ALGORITHM);
final Certificate certificate = cf.generateCertificate(new ByteArrayInputStream(Base64.decode(certificateBase64)));
return certificate.getPublicKey();
} catch (final CertificateException e) {
throw new RuntimeException("Unable to generate certificates (" + PUBLIC_CERT_ALGORITHM + ") " + e.getMessage(), e);
}
}
/**
* convert a base64 encoded private key into a java object private key
*/
public static PrivateKey makePrivateKey(final String privateKeyBase64) {
if (privateKeyBase64 == null || privateKeyBase64.isEmpty()) {
throw new IllegalArgumentException("Supplied 'privateKeyBase64' argument is null or empty.");
}
try {
final KeyFactory key_factory = KeyFactory.getInstance(PRIVATE_KEY_ALGORITHM);
final PKCS8EncodedKeySpec private_key_spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyBase64));
return key_factory.generatePrivate(private_key_spec);
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("Invalid algorithm (" + PRIVATE_KEY_ALGORITHM + "): " + e.getMessage(), e);
} catch (final InvalidKeySpecException e) {
throw new RuntimeException("Invalid KeySpec: " + e.getMessage(), e);
}
}
public static X509Certificate makeCertificate(String certificateBase64) {
if (certificateBase64 == null || certificateBase64.isEmpty()) {
throw new IllegalArgumentException("Supplied 'certificateBase64' argument is null or empty.");
}
try {
byte[] certRaw = Base64.decode(certificateBase64);
CertificateFactory certFactory = CertificateFactory.getInstance(PUBLIC_CERT_ALGORITHM);
return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certRaw));
} catch (Exception e) {
throw new RuntimeException("Unable to deserialize supplied X509 certificate.", e);
}
}
/**
* @return true if encrypting then decrypting a small test string roundtrips.
*/
public static boolean validatePrivateKeyCertificateCompatibility(PrivateKey privateKey, X509Certificate certificate) {
return validatePrivateKeyPublicKeyCompatibility(privateKey, certificate.getPublicKey());
}
/**
* @return true if encrypting then decrypting a small test string roundtrips.
*/
public static boolean validatePrivateKeyPublicKeyCompatibility(PrivateKey privateKey, PublicKey publicKey) {
String plainData = "Some text for encryption";
try {
byte[] encryptedData = encrypt(plainData.getBytes("UTF-8"), publicKey);
byte[] decryptedData = decrypt(encryptedData, privateKey);
String plainDataRoundTrip = new String(decryptedData, "UTF-8");
if (plainData.equals(plainDataRoundTrip)) {
return true;
}
} catch (Exception e) {
return false;
}
return false;
}
static byte[] encrypt(byte[] plainData, PublicKey pubKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encryptedData = cipher.doFinal(plainData);
return encryptedData;
}
static byte[] decrypt(byte[] encryptedData, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedData = cipher.doFinal(encryptedData);
return decryptedData;
}
}