KeyImport.java
/*
** Module : KeyImport.java
** Abstract : complements the JDK's keytool and imports an arbitrary private
** key with its X.509 certificate from two separate files.
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- -T- --JPRM-- ----------------Description-----------------
** 001 NVS 20050223 NEW @19934 Created initial version. This reads and
** imports into a keystore a private key from an
** unencrypted DER encoded file along with its
** related certificate taken from a PEM file.
** 002 NVS 20050225 CHG @19957 Switched from JCEKS keystore type to JKS.
** The reason is NullPointerException
** at com.sun.crypto.provider.SunJCE_z.a
** (DashoA6275)
** at com.sun.crypto.provider.JceKeyStore.
** engineGetKey(DashoA6275)
** at java.security.KeyStore.getKey
** (KeyStore.java:289)
** which is specific to JCEKS only.
** 003 NVS 20060608 CHG @27027 Added some more information about the reason
** of this class existence.
** 004 GES 20061220 CHG @31799 Match interface changes.
** 005 IAS 20160331 Add input streams' close
** 006 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.security;
import java.io.*;
import java.util.*;
import java.security.*;
import java.security.spec.*;
import java.security.cert.*;
import javax.net.ssl.*;
import javax.crypto.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
/**
* Implements arbitrary private key / certificate pair import into a keystore.
* This functionality is supported by the KeyStore class, but not by the
* keytool supplied with J2SE.
* More specifically, the keytool provides a way of generating new private
* keys and their matching public certificates right into the keystore.
* However, there is no way of importing a private key generated by some other
* entity like a CA.
* <p>
* A private key file should be an unencrypted DER encoded binary file. Its
* associated X.509 certificate should be a regular PEM file.
* <p>
* The target keystore file is supposed to be created as a JKS type file.
*/
public class KeyImport
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(KeyImport.class);
/**
* Provides a command line driver for the private key import function.
* <p>
* The command line syntax is:
* <pre>
* KeyImport keystore-file alias private-key-file certificate-file
* </pre>
* The user will be prompted for the keystore password, then for the key
* entry password.
*
* @param args
* command line arguments. See syntax description above.
* @throws KeyStoreException
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
*/
public static void main(String[] args)
throws KeyStoreException, IOException, NoSuchAlgorithmException,
CertificateException
{
// parse arguments
if (args.length != 4)
{
LOG.info("The correct syntax is:" + System.lineSeparator() +
System.lineSeparator() +
"KeyImport keystore-file alias " + System.lineSeparator() +
"private-key-file certificate-file" + System.lineSeparator() +
System.lineSeparator());
return;
}
File keyf = new File(args[2]);
if (!keyf.exists())
{
LOG.severe("Key file " + args[2] + " not found");
return;
}
int keysize = (int)keyf.length();
File cerf = new File(args[3]);
if (!cerf.exists())
{
LOG.severe("Certificate file " + args[3] + " not found");
return;
}
// initialize the keystore
KeyStore ks = KeyStore.getInstance("JKS");
char[] password = Utils.prompt("Enter keystore password:");
File keyStore = new File(args[0]);
FileInputStream fks = null;
if (keyStore.exists())
fks = new FileInputStream(keyStore);
ks.load(fks, password);
if (fks != null)
fks.close();
fks = null;
// read the private key file entirely
byte[] encodedKey = new byte[keysize];
try (FileInputStream fk = new FileInputStream(keyf))
{
if (fk.read(encodedKey) != keysize)
throw new IOException("error reading private key file");
}
// create the private key
PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(encodedKey);
PrivateKey pk = null;
String algorithm = null;
try
{
KeyFactory kf = KeyFactory.getInstance("RSA");
pk = kf.generatePrivate(pkcs8);
algorithm = "RSA";
}
catch (InvalidKeySpecException ke)
{
KeyFactory kf = KeyFactory.getInstance("DSA");
try
{
pk = kf.generatePrivate(pkcs8);
algorithm = "DSA";
}
catch (InvalidKeySpecException kke)
{
throw new NoSuchAlgorithmException(
"private key is neither RSA nor DSA");
}
}
pkcs8 = null;
// create the certificate
FileInputStream fc = new FileInputStream(cerf);
BufferedInputStream bis = new BufferedInputStream(fc);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(bis);
bis.close();
bis = null;
fc.close();
fc = null;
cf = null;
// adding new key entry into the keystore
char[] keypass = Utils.prompt("Enter key entry password:");
X509Certificate[] chain = new X509Certificate[1];
chain[0] = cert;
ks.setKeyEntry(args[1], pk, keypass, chain);
// verifying new key entry
try
{
Key npk = ks.getKey(args[1], keypass);
}
catch (Exception exc)
{
LOG.severe("Key import operation failed" + System.lineSeparator() +
"Exception when verifying the key:", exc);
return;
}
// storing back to file
try (FileOutputStream fos = new FileOutputStream(keyStore))
{
ks.store(fos, password);
System.out.println("Keystore: " + args[0] + ", alias " + args[1] +
", " + algorithm + " private key imported.");
}
}
}