2 * Copyright (c) 2016, 2017 Inocybe Technologies and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.aaa.cert.impl;
10 import java.io.ByteArrayInputStream;
11 import java.io.ByteArrayOutputStream;
13 import java.io.FileInputStream;
14 import java.io.FileOutputStream;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.math.BigInteger;
18 import java.security.KeyPair;
19 import java.security.KeyPairGenerator;
20 import java.security.KeyStore;
21 import java.security.KeyStoreException;
22 import java.security.NoSuchAlgorithmException;
23 import java.security.PrivateKey;
24 import java.security.PublicKey;
25 import java.security.SecureRandom;
26 import java.security.UnrecoverableKeyException;
27 import java.security.cert.Certificate;
28 import java.security.cert.CertificateException;
29 import java.security.cert.CertificateFactory;
30 import java.security.cert.X509Certificate;
31 import java.util.Base64;
32 import java.util.Date;
33 import org.apache.commons.lang3.StringUtils;
34 import org.bouncycastle.asn1.x500.X500Name;
35 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
36 import org.bouncycastle.cert.X509CertificateHolder;
37 import org.bouncycastle.cert.X509v3CertificateBuilder;
38 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
39 import org.bouncycastle.operator.ContentSigner;
40 import org.bouncycastle.operator.OperatorCreationException;
41 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
42 import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * ODLKeyTool implements the basic operations that manage the Java keyStores
48 * such as create, generate, add and delete certificates.
52 public class ODLKeyTool {
53 private static final Logger LOG = LoggerFactory.getLogger(ODLKeyTool.class);
55 private final String workingDir;
57 protected ODLKeyTool() {
58 this(KeyStoreConstant.KEY_STORE_PATH);
61 public ODLKeyTool(final String workingDirectory) {
62 workingDir = workingDirectory;
63 KeyStoreConstant.createDir(workingDir);
67 * Add certificate to the given keystore.
70 * java keystore object
76 * true to delete the old certificate that has the same alias
77 * otherwise it will fail if there is a certificate has same
79 * @return the given Keystore containing the certificate otherwise return
82 public KeyStore addCertificate(final KeyStore keyStore, final String certificate, final String alias,
83 final boolean deleteOld) {
85 final X509Certificate newCert = getCertificate(certificate);
86 if (keyStore.isCertificateEntry(alias) && deleteOld) {
87 keyStore.deleteEntry(alias);
89 if (newCert != null) {
90 keyStore.setCertificateEntry(alias, newCert);
92 LOG.warn("{} Not a valid certificate {}", alias, certificate);
96 } catch (final KeyStoreException e) {
97 LOG.error("failed to add certificate", e);
103 * Convert the given java keystore object to byte array.
107 * @param keystorePassword
108 * the password of the given keystore
111 public byte[] convertKeystoreToBytes(final KeyStore keyStore, final String keystorePassword) {
112 final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
114 keyStore.store(byteArrayOutputStream, keystorePassword.toCharArray());
115 } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
116 LOG.error("Fatal error convert keystore to bytes", e);
118 return byteArrayOutputStream.toByteArray();
122 * Create a keystore that has self sign private/public keys using the
123 * default key algorithm (RSA), size (2048) and signing algorithm
124 * (SHA1WithRSAEncryption).
126 * @param keyStoreName
128 * @param keystorePassword
129 * the keystore password
130 * @param distinguishedName
131 * the generated key's Distinguished Name
133 * the private key alias
136 * @return keystore object
138 public KeyStore createKeyStoreWithSelfSignCert(final String keyStoreName, final String keystorePassword,
139 final String distinguishedName, final String keyAlias, final int validity) {
140 return createKeyStoreWithSelfSignCert(keyStoreName, keystorePassword, distinguishedName, keyAlias, validity,
141 KeyStoreConstant.DEFAULT_KEY_ALG, KeyStoreConstant.DEFAULT_KEY_SIZE, KeyStoreConstant.DEFAULT_SIGN_ALG);
145 * Create a keystore that has self sign private/public keys.
147 * @param keyStoreName
149 * @param keystorePassword
150 * the keystore password
151 * @param distinguishedName
152 * the generated key's Distinguished Name
154 * the private key alias
158 * the algorithm that will be used to generate the key
162 * the signing algorithm
163 * @return keystore object
165 public KeyStore createKeyStoreWithSelfSignCert(final String keyStoreName, final String keystorePassword,
166 final String distinguishedName, final String keyAlias, final int validity, final String keyAlg,
167 final int keySize, final String signAlg) {
169 final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyAlg);
170 keyPairGenerator.initialize(keySize);
171 final KeyPair keyPair = keyPairGenerator.generateKeyPair();
172 final long currTime = System.currentTimeMillis();
173 final SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
174 final X509v3CertificateBuilder x509V3CertBuilder =
175 new X509v3CertificateBuilder(new X500Name(distinguishedName), getSecureRandomeInt(),
177 new Date(currTime + KeyStoreConstant.DAY_TIME * validity),
178 new X500Name(distinguishedName), keyInfo);
179 final X509CertificateHolder x509Cert = x509V3CertBuilder
180 .build(new JcaContentSignerBuilder(signAlg).build(keyPair.getPrivate()));
181 final KeyStore ctlKeyStore = KeyStore.getInstance("JKS");
182 ctlKeyStore.load(null, keystorePassword.toCharArray());
183 final Certificate[] chain = new Certificate[] { new JcaX509CertificateConverter()
184 .getCertificate(x509Cert) };
185 ctlKeyStore.setKeyEntry(keyAlias, keyPair.getPrivate(), keystorePassword.toCharArray(), chain);
186 LOG.info("{} is created", keyStoreName);
188 } catch (final NoSuchAlgorithmException | SecurityException | KeyStoreException | CertificateException
189 | IOException | OperatorCreationException e) {
190 LOG.error("Fatal error creating keystore", e);
196 * Create empty keystore does not has private or public key.
198 * @param keystorePassword
199 * the keystore password
200 * @return keystore object
202 public KeyStore createEmptyKeyStore(final String keystorePassword) {
204 final KeyStore trustKeyStore = KeyStore.getInstance("JKS");
205 trustKeyStore.load(null, keystorePassword.toCharArray());
206 return trustKeyStore;
207 } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
208 LOG.error("Failed to create trust keystore", e);
214 * Export the given keystore as a file under the working directory.
218 * @param keystorePassword
219 * the keystore password
222 * @return true if successes to export the keystore
224 public boolean exportKeystore(final KeyStore keystore, final String keystorePassword, final String fileName) {
225 if (keystore == null) {
229 final File realPath = KeyStoreConstant.toAbsoluteFile(fileName, workingDir);
230 try (FileOutputStream fOutputStream = new FileOutputStream(realPath)) {
231 keystore.store(fOutputStream, keystorePassword.toCharArray());
233 } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
234 LOG.error("Fatal error export keystore", e);
240 * Generate a certificate signing request based on the given keystore
241 * private/public key.
245 * @param keystorePassword
246 * the keystore password
248 * Alias of the given keystore's private key.
250 * the signing algorithm
252 * true to add the certificate request tag to the certificate
254 * @return certificate request as string.
256 public String generateCertificateReq(final KeyStore keyStore, final String keystorePassword, final String keyAlias,
257 final String signAlg, final boolean withTag) {
259 if (keyStore.containsAlias(keyAlias)) {
260 final X509Certificate odlCert = (X509Certificate) keyStore.getCertificate(keyAlias);
261 final PublicKey pubKey = odlCert.getPublicKey();
262 final PrivateKey privKey = (PrivateKey) keyStore.getKey(keyAlias, keystorePassword.toCharArray());
263 final String subject = odlCert.getSubjectDN().getName();
264 final X500Name xName = new X500Name(subject);
265 final SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(pubKey.getEncoded());
266 final PKCS10CertificationRequestBuilder csrb = new PKCS10CertificationRequestBuilder(xName,
268 final ContentSigner contSigner = new JcaContentSignerBuilder(signAlg).build(privKey);
269 final String certReq = Base64.getEncoder().encodeToString(csrb.build(contSigner).getEncoded());
270 return !withTag ? certReq : new StringBuilder()
271 .append(KeyStoreConstant.BEGIN_CERTIFICATE_REQUEST).append('\n')
272 .append(certReq).append('\n')
273 .append(KeyStoreConstant.END_CERTIFICATE_REQUEST)
276 LOG.info("KeyStore does not contain alias {}", keyAlias);
277 return StringUtils.EMPTY;
278 } catch (final NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException
279 | OperatorCreationException | IOException e) {
280 LOG.error("Failed to generate certificate request", e);
281 return StringUtils.EMPTY;
286 * Get a certificate as String based on the given alias.
289 * keystore that has the certificate
293 * true to add the certificate tag to the certificate string.
294 * @return certificate as string.
296 public String getCertificate(final KeyStore keyStore, final String certAlias, final boolean withTag) {
298 if (keyStore.containsAlias(certAlias)) {
299 final X509Certificate odlCert = (X509Certificate) keyStore.getCertificate(certAlias);
300 final String cert = Base64.getEncoder().encodeToString(odlCert.getEncoded());
301 return !withTag ? cert : new StringBuilder()
302 .append(KeyStoreConstant.BEGIN_CERTIFICATE).append('\n')
303 .append(cert).append('\n')
304 .append(KeyStoreConstant.END_CERTIFICATE)
307 LOG.info("KeyStore does not contain alias {}", certAlias);
308 return StringUtils.EMPTY;
309 } catch (final CertificateException | KeyStoreException e) {
310 LOG.error("Failed to get Certificate", e);
311 return StringUtils.EMPTY;
316 * Get a X509Certificate object based on given certificate string.
320 * @return X509Certificate if the certificate string is not well formated
323 private X509Certificate getCertificate(String certificate) {
324 if (certificate.isEmpty()) {
328 if (certificate.contains(KeyStoreConstant.BEGIN_CERTIFICATE)) {
329 final int fIdx = certificate.indexOf(KeyStoreConstant.BEGIN_CERTIFICATE)
330 + KeyStoreConstant.BEGIN_CERTIFICATE.length();
331 final int sIdx = certificate.indexOf(KeyStoreConstant.END_CERTIFICATE);
332 certificate = certificate.substring(fIdx, sIdx);
334 final byte[] byteCert = Base64.getDecoder().decode(certificate);
335 final InputStream inputStreamCert = new ByteArrayInputStream(byteCert);
336 CertificateFactory certFactory;
338 certFactory = CertificateFactory.getInstance("X.509");
339 final X509Certificate newCert = (X509Certificate) certFactory.generateCertificate(inputStreamCert);
340 newCert.checkValidity();
342 } catch (final CertificateException e) {
343 LOG.error("Failed to get certificate", e);
349 * generate secure random number.
351 * @return secure random number as BigInteger.
353 private BigInteger getSecureRandomeInt() {
354 final SecureRandom secureRandom = new SecureRandom();
355 final BigInteger bigInt = BigInteger.valueOf(secureRandom.nextInt());
356 return new BigInteger(1, bigInt.toByteArray());
360 * Load the keystore object from the given byte array.
362 * @param keyStoreBytes
363 * array of byte contain keystore object
364 * @param keystorePassword
365 * the keystore password
366 * @return keystore object otherwise return null if it fails to load.
368 public KeyStore loadKeyStore(final byte[] keyStoreBytes, final String keystorePassword) {
370 final KeyStore keyStore = KeyStore.getInstance("JKS");
371 keyStore.load(new ByteArrayInputStream(keyStoreBytes), keystorePassword.toCharArray());
373 } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
374 LOG.error("Fatal error load keystore", e);
380 * Load the keystore from the working directory.
382 * @param keyStoreName
384 * @param keystorePassword
386 * @return keystore object otherwise return null if it fails to load.
388 public KeyStore loadKeyStore(final String keyStoreName, final String keystorePassword) {
389 final File realPath = KeyStoreConstant.toAbsoluteFile(keyStoreName, workingDir);
390 try (FileInputStream fInputStream = new FileInputStream(realPath)) {
391 final KeyStore keyStore = KeyStore.getInstance("JKS");
392 keyStore.load(fInputStream, keystorePassword.toCharArray());
394 } catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException e) {
395 LOG.error("failed to get keystore {}", e.getMessage());