SignatureUtil.java
/*
* Copyright 2019-Present Entando Inc. (http://www.entando.com) All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* 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.
*/
package org.entando.kubernetes.service.digitalexchange.signature;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import org.entando.kubernetes.exception.EntandoGeneralSignatureException;
public class SignatureUtil {
private static final String ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
private static final int KEY_SIZE = 2048;
private static final String BEGIN_RSA_PUBLIC_KEY = "-----BEGIN RSA PUBLIC KEY-----\n";
private static final String END_RSA_PUBLIC_KEY = "-----END RSA PUBLIC KEY-----";
private static final String BEGIN_RSA_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n";
private static final String END_RSA_PRIVATE_KEY = "-----END RSA PRIVATE KEY-----";
private SignatureUtil() {
// Utility class. Not to be instantiated.
}
/**
* Generate a public/private {@link KeyPair} object.
*
* @return the generated {@link KeyPair}
*/
public static KeyPair createKeyPair() {
try {
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
keyGen.initialize(KEY_SIZE);
return keyGen.generateKeyPair();
} catch (GeneralSecurityException ex) {
throw new EntandoGeneralSignatureException(ex);
}
}
/**
* Convert a {@link PublicKey} object to its PEM representation.
*
* @param publicKey the {@link PublicKey} to convert
* @return the public key in PEM format
*/
public static String publicKeyToPEM(PublicKey publicKey) {
return BEGIN_RSA_PUBLIC_KEY
+ getBase64Key(publicKey)
+ END_RSA_PUBLIC_KEY;
}
/**
* Convert a PEM formatted public key to a {@link PublicKey} object.
*
* @param pemPublicKey the PEM formatted public key
* @return {@link PublicKey} object
*/
public static PublicKey publicKeyFromPEM(String pemPublicKey) {
final String base64Key = pemPublicKey
.replace(BEGIN_RSA_PUBLIC_KEY, "")
.replace(END_RSA_PUBLIC_KEY, "")
.replace("\n", "")
.trim();
final byte[] bytes = Base64.getDecoder().decode(base64Key);
try {
final X509EncodedKeySpec ks = new X509EncodedKeySpec(bytes);
final KeyFactory kf = KeyFactory.getInstance(ALGORITHM);
return kf.generatePublic(ks);
} catch (GeneralSecurityException ex) {
throw new EntandoGeneralSignatureException(ex);
}
}
public static PrivateKey getPrivateKeyFromBytes(byte[] privateKeyBytes) {
try {
final KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
final PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
return keyFactory.generatePrivate(privKeySpec);
} catch (GeneralSecurityException ex) {
throw new EntandoGeneralSignatureException(ex);
}
}
public static String privateKeyToPEM(PrivateKey privateKey) {
return BEGIN_RSA_PRIVATE_KEY
+ getBase64Key(privateKey)
+ END_RSA_PRIVATE_KEY;
}
public static PrivateKey privateKeyFromPEM(String pemPrivateKey) {
final String base64Key = pemPrivateKey
.replace(BEGIN_RSA_PRIVATE_KEY, "")
.replace(END_RSA_PRIVATE_KEY, "")
.replace("\n", "")
.trim();
final byte[] bytes = Base64.getDecoder().decode(base64Key);
try {
final PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(bytes);
final KeyFactory kf = KeyFactory.getInstance(ALGORITHM);
return kf.generatePrivate(privateKeySpec);
} catch (GeneralSecurityException ex) {
throw new EntandoGeneralSignatureException(ex);
}
}
private static String getBase64Key(Key key) {
final StringBuilder sb = new StringBuilder();
final String base64 = Base64.getEncoder().encodeToString(key.getEncoded());
final int lineLength = 64;
for (int i = 0; i < base64.length(); i += lineLength) {
final int chars = Math.min(base64.length(), i + lineLength);
sb.append(base64, i, chars).append("\n");
}
return sb.toString();
}
/**
* Generate the encoded signature of the data using the private key.
*
* @param in the data to use to generate the signature
* @param privateKey the key to use to encode the signature
* @return the encoded signature
*/
public static String signPackage(InputStream in, PrivateKey privateKey) {
try {
final Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
updateSignature(signature, in);
return Base64.getEncoder().encodeToString(signature.sign());
} catch (GeneralSecurityException | IOException ex) {
throw new EntandoGeneralSignatureException(ex);
}
}
/**
* Verifies if the alleged signature is the actual signature of the specified input stream generated by the private
* key corresponding to the public key.
*
* @param in the data input stream
* @param publicKey the public key
* @param base64Signature the private key encoded signature
* @return true if the signature of the input stream correspond to the decoded signature
*/
public static boolean verifySignature(InputStream in, PublicKey publicKey, String base64Signature) {
try {
final byte[] bytes = Base64.getDecoder().decode(base64Signature);
final Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicKey);
updateSignature(signature, in);
return signature.verify(bytes);
} catch (GeneralSecurityException | IOException ex) {
throw new EntandoGeneralSignatureException(ex);
}
}
private static void updateSignature(Signature signature, InputStream in)
throws GeneralSecurityException, IOException {
try (BufferedInputStream bin = new BufferedInputStream(in)) {
final byte[] buffer = new byte[1024];
while (bin.available() != 0) {
final int len = bin.read(buffer);
signature.update(buffer, 0, len);
}
}
}
}