/* * 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); } }