Use Base64.encoder() in ODLKeyTool
[aaa.git] / aaa-cert / src / main / java / org / opendaylight / aaa / cert / impl / ODLKeyTool.java
1 /*
2  * Copyright (c) 2016, 2017 Inocybe Technologies and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.aaa.cert.impl;
9
10 import java.io.ByteArrayInputStream;
11 import java.io.ByteArrayOutputStream;
12 import java.io.File;
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;
45
46 /**
47  * ODLKeyTool implements the basic operations that manage the Java keyStores
48  * such as create, generate, add and delete certificates.
49  *
50  * @author mserngawy
51  */
52 public class ODLKeyTool {
53     private static final Logger LOG = LoggerFactory.getLogger(ODLKeyTool.class);
54
55     private final String workingDir;
56
57     protected ODLKeyTool() {
58         this(KeyStoreConstant.KEY_STORE_PATH);
59     }
60
61     public ODLKeyTool(final String workingDirectory) {
62         workingDir = workingDirectory;
63         KeyStoreConstant.createDir(workingDir);
64     }
65
66     /**
67      * Add certificate to the given keystore.
68      *
69      * @param keyStore
70      *            java keystore object
71      * @param certificate
72      *            to add as string
73      * @param alias
74      *            of the certificate
75      * @param deleteOld
76      *            true to delete the old certificate that has the same alias
77      *            otherwise it will fail if there is a certificate has same
78      *            given alias.
79      * @return the given Keystore containing the certificate otherwise return
80      *         null.
81      */
82     public KeyStore addCertificate(final KeyStore keyStore, final String certificate, final String alias,
83             final boolean deleteOld) {
84         try {
85             final X509Certificate newCert = getCertificate(certificate);
86             if (keyStore.isCertificateEntry(alias) && deleteOld) {
87                 keyStore.deleteEntry(alias);
88             }
89             if (newCert != null) {
90                 keyStore.setCertificateEntry(alias, newCert);
91             } else {
92                 LOG.warn("{} Not a valid certificate {}", alias, certificate);
93                 return null;
94             }
95             return keyStore;
96         } catch (final KeyStoreException e) {
97             LOG.error("failed to add certificate", e);
98             return null;
99         }
100     }
101
102     /**
103      * Convert the given java keystore object to byte array.
104      *
105      * @param keyStore
106      *            object
107      * @param keystorePassword
108      *            the password of the given keystore
109      * @return byte array
110      */
111     public byte[] convertKeystoreToBytes(final KeyStore keyStore, final String keystorePassword) {
112         final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
113         try {
114             keyStore.store(byteArrayOutputStream, keystorePassword.toCharArray());
115         } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
116             LOG.error("Fatal error convert keystore to bytes", e);
117         }
118         return byteArrayOutputStream.toByteArray();
119     }
120
121     /**
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).
125      *
126      * @param keyStoreName
127      *            the keystore name
128      * @param keystorePassword
129      *            the keystore password
130      * @param distinguishedName
131      *            the generated key's Distinguished Name
132      * @param keyAlias
133      *            the private key alias
134      * @param validity
135      *            the key validity
136      * @return keystore object
137      */
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);
142     }
143
144     /**
145      * Create a keystore that has self sign private/public keys.
146      *
147      * @param keyStoreName
148      *            the keystore name
149      * @param keystorePassword
150      *            the keystore password
151      * @param distinguishedName
152      *            the generated key's Distinguished Name
153      * @param keyAlias
154      *            the private key alias
155      * @param validity
156      *            the key validity
157      * @param keyAlg
158      *            the algorithm that will be used to generate the key
159      * @param keySize
160      *            the key size
161      * @param signAlg
162      *            the signing algorithm
163      * @return keystore object
164      */
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) {
168         try {
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(),
176                             new Date(currTime),
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);
187             return ctlKeyStore;
188         } catch (final NoSuchAlgorithmException | SecurityException | KeyStoreException | CertificateException
189                 | IOException | OperatorCreationException e) {
190             LOG.error("Fatal error creating keystore", e);
191             return null;
192         }
193     }
194
195     /**
196      * Create empty keystore does not has private or public key.
197      *
198      * @param keystorePassword
199      *            the keystore password
200      * @return keystore object
201      */
202     public KeyStore createEmptyKeyStore(final String keystorePassword) {
203         try {
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);
209             return null;
210         }
211     }
212
213     /**
214      * Export the given keystore as a file under the working directory.
215      *
216      * @param keystore
217      *            object
218      * @param keystorePassword
219      *            the keystore password
220      * @param fileName
221      *            of the keystore
222      * @return true if successes to export the keystore
223      */
224     public boolean exportKeystore(final KeyStore keystore, final String keystorePassword, final String fileName) {
225         if (keystore == null) {
226             return false;
227         }
228
229         final File realPath = KeyStoreConstant.toAbsoluteFile(fileName, workingDir);
230         try (FileOutputStream fOutputStream = new FileOutputStream(realPath)) {
231             keystore.store(fOutputStream, keystorePassword.toCharArray());
232             return true;
233         } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
234             LOG.error("Fatal error export keystore", e);
235             return false;
236         }
237     }
238
239     /**
240      * Generate a certificate signing request based on the given keystore
241      * private/public key.
242      *
243      * @param keyStore
244      *            object
245      * @param keystorePassword
246      *            the keystore password
247      * @param keyAlias
248      *            Alias of the given keystore's private key.
249      * @param signAlg
250      *            the signing algorithm
251      * @param withTag
252      *            true to add the certificate request tag to the certificate
253      *            request string.
254      * @return certificate request as string.
255      */
256     public String generateCertificateReq(final KeyStore keyStore, final String keystorePassword, final String keyAlias,
257             final String signAlg, final boolean withTag) {
258         try {
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,
267                         subPubKeyInfo);
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)
274                     .toString();
275             }
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;
282         }
283     }
284
285     /**
286      * Get a certificate as String based on the given alias.
287      *
288      * @param keyStore
289      *            keystore that has the certificate
290      * @param certAlias
291      *            certificate alias
292      * @param withTag
293      *            true to add the certificate tag to the certificate string.
294      * @return certificate as string.
295      */
296     public String getCertificate(final KeyStore keyStore, final String certAlias, final boolean withTag) {
297         try {
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)
305                     .toString();
306             }
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;
312         }
313     }
314
315     /**
316      * Get a X509Certificate object based on given certificate string.
317      *
318      * @param certificate
319      *            as string
320      * @return X509Certificate if the certificate string is not well formated
321      *         will return null
322      */
323     private X509Certificate getCertificate(String certificate) {
324         if (certificate.isEmpty()) {
325             return null;
326         }
327
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);
333         }
334         final byte[] byteCert = Base64.getDecoder().decode(certificate);
335         final InputStream inputStreamCert = new ByteArrayInputStream(byteCert);
336         CertificateFactory certFactory;
337         try {
338             certFactory = CertificateFactory.getInstance("X.509");
339             final X509Certificate newCert = (X509Certificate) certFactory.generateCertificate(inputStreamCert);
340             newCert.checkValidity();
341             return newCert;
342         } catch (final CertificateException e) {
343             LOG.error("Failed to get certificate", e);
344             return null;
345         }
346     }
347
348     /**
349      * generate secure random number.
350      *
351      * @return secure random number as BigInteger.
352      */
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());
357     }
358
359     /**
360      * Load the keystore object from the given byte array.
361      *
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.
367      */
368     public KeyStore loadKeyStore(final byte[] keyStoreBytes, final String keystorePassword) {
369         try {
370             final KeyStore keyStore = KeyStore.getInstance("JKS");
371             keyStore.load(new ByteArrayInputStream(keyStoreBytes), keystorePassword.toCharArray());
372             return keyStore;
373         } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
374             LOG.error("Fatal error load keystore", e);
375             return null;
376         }
377     }
378
379     /**
380      * Load the keystore from the working directory.
381      *
382      * @param keyStoreName
383      *            keystore file name
384      * @param keystorePassword
385      *            keystore password
386      * @return keystore object otherwise return null if it fails to load.
387      */
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());
393             return keyStore;
394         } catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException e) {
395             LOG.error("failed to get keystore {}", e.getMessage());
396             return null;
397         }
398     }
399 }