/*
* Copyright 2014-2016 CyberVision, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kaaproject.kaa.common.endpoint.security;
import org.apache.commons.compress.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Random;
/**
* The Class KeyUtil is used to persist and fetch Public and Private Keys.
*
* @author Andrew Shvayka
*/
public abstract class KeyUtil {
private static final Logger LOG = LoggerFactory.getLogger(KeyUtil.class);
private static final String RSA = "RSA";
private KeyUtil() {
}
/**
* Saves public and private keys to specified files.
*
* @param keyPair the key pair
* @param privateKeyFile the private key file
* @param publicKeyFile the public key file
* @throws IOException Signals that an I/O exception has occurred.
*/
public static void saveKeyPair(KeyPair keyPair, String privateKeyFile, String publicKeyFile)
throws IOException {
File privateFile = makeDirs(privateKeyFile);
File publicFile = makeDirs(publicKeyFile);
OutputStream privateKeyOutput = null;
OutputStream publicKeyOutput = null;
try {
privateKeyOutput = new FileOutputStream(privateFile);
publicKeyOutput = new FileOutputStream(publicFile);
saveKeyPair(keyPair, privateKeyOutput, publicKeyOutput);
} finally {
IOUtils.closeQuietly(privateKeyOutput);
IOUtils.closeQuietly(publicKeyOutput);
}
}
/**
* Saves public and private keys to specified streams.
*
* @param keyPair the key pair
* @param privateKeyOutput the private key output stream
* @param publicKeyOutput the public key output stream
* @throws IOException Signals that an I/O exception has occurred.
*/
public static void saveKeyPair(
KeyPair keyPair, OutputStream privateKeyOutput, OutputStream publicKeyOutput)
throws IOException {
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// Store Public Key.
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
publicKey.getEncoded());
publicKeyOutput.write(x509EncodedKeySpec.getEncoded());
// Store Private Key.
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
privateKey.getEncoded());
privateKeyOutput.write(pkcs8EncodedKeySpec.getEncoded());
}
/**
* Create all required directories.
*
* @param privateKeyFile the private key file
* @return the file
*/
private static File makeDirs(String privateKeyFile) {
File privateFile = new File(privateKeyFile);
if (privateFile.getParentFile() != null && !privateFile.getParentFile().exists()
&& !privateFile.getParentFile().mkdirs()) {
LOG.warn("Failed to create required directories: {}",
privateFile.getParentFile().getAbsolutePath());
}
return privateFile;
}
/**
* Generate key pair and saves it to specified files.
*
* @param privateKeyLocation the private key location
* @param publicKeyLocation the public key location
* @return the key pair
*/
public static KeyPair generateKeyPair(String privateKeyLocation, String publicKeyLocation) {
try {
KeyPair clientKeyPair = generateKeyPair();
saveKeyPair(clientKeyPair, privateKeyLocation, publicKeyLocation);
return clientKeyPair;
} catch (Exception ex) {
LOG.error("Error generating client key pair", ex);
}
return null;
}
/**
* Generate key pair and saves it to specified streams.
*
* @param privateKeyOutput the private key output stream
* @param publicKeyOutput the public key output stream
* @return the key pair
*/
public static KeyPair generateKeyPair(
OutputStream privateKeyOutput, OutputStream publicKeyOutput) {
try {
KeyPair clientKeyPair = generateKeyPair();
saveKeyPair(clientKeyPair, privateKeyOutput, publicKeyOutput);
return clientKeyPair;
} catch (Exception ex) {
LOG.error("Error generating client key pair", ex);
}
return null;
}
/**
* Generates a key pair.
*
* @return key pair
* @throws NoSuchAlgorithmException no such algorithm
*/
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator clientKeyGen = KeyPairGenerator.getInstance(RSA);
clientKeyGen.initialize(2048);
return clientKeyGen.genKeyPair();
}
/**
* Gets the public key from file.
*
* @param file the file
* @return the public
* @throws IOException the i/o exception
* @throws InvalidKeyException invalid key exception
*/
public static PublicKey getPublic(File file) throws IOException, InvalidKeyException {
DataInputStream dis = null;
try {
FileInputStream fis = new FileInputStream(file);
dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) file.length()];
dis.readFully(keyBytes);
return getPublic(keyBytes);
} finally {
IOUtils.closeQuietly(dis);
}
}
/**
* Gets the public key from input stream.
*
* @param input the input stream
* @return the public
* @throws IOException the i/o exception
* @throws InvalidKeyException invalid key exception
*/
public static PublicKey getPublic(InputStream input) throws IOException, InvalidKeyException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len = 0;
while (-1 != (len = input.read(buffer))) {
output.write(buffer, 0, len);
}
byte[] keyBytes = output.toByteArray();
return getPublic(keyBytes);
}
/**
* Gets the public key from bytes.
*
* @param keyBytes the key bytes
* @return the public
* @throws InvalidKeyException invalid key exception
*/
public static PublicKey getPublic(byte[] keyBytes) throws InvalidKeyException {
try {
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance(RSA);
return kf.generatePublic(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
throw new InvalidKeyException(ex);
}
}
/**
* Gets the private key from file.
*
* @param file the file
* @return the private
* @throws IOException the i/o exception
* @throws InvalidKeyException invalid key exception
*/
public static PrivateKey getPrivate(File file) throws IOException, InvalidKeyException {
DataInputStream dis = null;
try {
FileInputStream fis = new FileInputStream(file);
dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) file.length()];
dis.readFully(keyBytes);
return getPrivate(keyBytes);
} finally {
IOUtils.closeQuietly(dis);
}
}
/**
* Gets the private key from input stream.
*
* @param input the input stream
* @return the private
* @throws IOException the i/o exception
* @throws InvalidKeyException invalid key exception
*/
public static PrivateKey getPrivate(InputStream input) throws IOException, InvalidKeyException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len = 0;
while (-1 != (len = input.read(buffer))) {
output.write(buffer, 0, len);
}
byte[] keyBytes = output.toByteArray();
return getPrivate(keyBytes);
}
/**
* Gets the private key from bytes.
*
* @param keyBytes the key bytes
* @return the private
* @throws InvalidKeyException invalid key exception
*/
public static PrivateKey getPrivate(byte[] keyBytes) throws InvalidKeyException {
try {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance(RSA);
return kf.generatePrivate(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
throw new InvalidKeyException(ex);
}
}
/**
* Validates RSA public and private key.
*
* @param keyPair the keypair
* @return true if keys matches
*/
public static boolean validateKeyPair(KeyPair keyPair) {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
if (publicKey.getModulus().bitLength() != privateKey.getModulus().bitLength()) {
LOG.error("Keypair length matching error");
return false;
}
byte[] rawPayload = new byte[64];
new Random().nextBytes(rawPayload);
MessageEncoderDecoder encDec = new MessageEncoderDecoder(privateKey, publicKey);
byte[] encodedPayload;
byte[] decodedPayload;
try {
encodedPayload = encDec.encodeData(rawPayload);
decodedPayload = encDec.decodeData(encodedPayload);
} catch (GeneralSecurityException ex) {
LOG.error("Validation keypair error ", ex);
return false;
}
return Arrays.equals(rawPayload, decodedPayload);
}
}