Increase junit coverage
[aai/sparky-be.git] / src / main / java / org / onap / aai / sparky / util / test / KeystoreBuilder.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 Amdocs
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  *
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 package org.onap.aai.sparky.util.test;
24
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.net.UnknownHostException;
32 import java.security.KeyManagementException;
33 import java.security.KeyStore;
34 import java.security.KeyStoreException;
35 import java.security.MessageDigest;
36 import java.security.NoSuchAlgorithmException;
37 import java.security.cert.CertificateEncodingException;
38 import java.security.cert.CertificateException;
39 import java.security.cert.CertificateParsingException;
40 import java.security.cert.X509Certificate;
41 import java.util.ArrayList;
42 import java.util.Collection;
43 import java.util.List;
44
45 import javax.net.ssl.SSLContext;
46 import javax.net.ssl.SSLException;
47 import javax.net.ssl.SSLSocket;
48 import javax.net.ssl.SSLSocketFactory;
49 import javax.net.ssl.TrustManager;
50 import javax.net.ssl.TrustManagerFactory;
51 import javax.net.ssl.X509TrustManager;
52
53 /**
54  * The Class KeystoreBuilder.
55  */
56 public class KeystoreBuilder {
57
58   /**
59    * The Class EndPoint.
60    */
61   private class EndPoint {
62     private String hostname;
63     private int port;
64
65     /**
66      * Instantiates a new end point.
67      */
68     @SuppressWarnings("unused")
69     public EndPoint() {}
70
71     /**
72      * Instantiates a new end point.
73      *
74      * @param host the host
75      * @param port the port
76      */
77     public EndPoint(String host, int port) {
78       this.hostname = host;
79       this.port = port;
80     }
81
82     public String getHostname() {
83       return hostname;
84     }
85
86     @SuppressWarnings("unused")
87     public void setHostname(String hostname) {
88       this.hostname = hostname;
89     }
90
91     public int getPort() {
92       return port;
93     }
94
95     public void setPort(int port) {
96       this.port = port;
97     }
98
99     /* (non-Javadoc)
100      * @see java.lang.Object#toString()
101      */
102     @Override
103     public String toString() {
104       return "EndPoint [hostname=" + hostname + ", port=" + port + "]";
105     }
106
107   }
108
109   private List<EndPoint> endpoints = new ArrayList<EndPoint>();
110
111   /**
112    * @return the endpoints
113    */
114   public List<EndPoint> getEndpoints() {
115     return endpoints;
116   }
117
118   /**
119    * @param endpoints the endpoints to set
120    */
121   public void setEndpoints(List<EndPoint> endpoints) {
122     this.endpoints = endpoints;
123   }
124
125   /**
126    * Initialize end points list.
127    *
128    * @param endpointList the endpoint list
129    */
130   private void initializeEndPointsList(String endpointList) {
131     String[] endpointUris = endpointList.split(";");
132
133     for (String endpointUri : endpointUris) {
134
135       String ipAndPort = endpointUri.replaceAll("http://", "");
136       ipAndPort = endpointUri.replaceAll("https://", "");
137
138       // System.out.println("ipAndPortUrl = " + ipAndPort);
139
140       String[] hostAndPort = ipAndPort.split(":");
141
142       String hostname = hostAndPort[0];
143       int port = Integer.parseInt(hostAndPort[1]);
144
145       EndPoint ep = new EndPoint(hostname, port);
146       endpoints.add(ep);
147     }
148
149   }
150
151   /**
152    * Instantiates a new keystore builder.
153    *
154    * @param endpointList the endpoint list
155    * @throws NoSuchAlgorithmException the no such algorithm exception
156    */
157   public KeystoreBuilder(String endpointList) throws NoSuchAlgorithmException {
158     initializeEndPointsList(endpointList);
159     sha1 = MessageDigest.getInstance("SHA1");
160     md5 = MessageDigest.getInstance("MD5");
161   }
162
163   private static final String SEP = File.separator;
164   private SavingTrustManager savingTrustManager;
165   private SSLSocketFactory sslSocketFactory;
166   private MessageDigest sha1;
167   private MessageDigest md5;
168   private KeyStore ks;
169   private String keystoreFileName;
170   private String keystorePassword;
171   private boolean dumpCertDetails = false;
172
173   public void setDumpCertDetails(boolean shouldSet) {
174     dumpCertDetails = shouldSet;
175   }
176
177   /**
178    * Update keystore.
179    *
180    * @param keystoreFileName the keystore file name
181    * @param keystorePassword the keystore password
182    * @throws KeyStoreException the key store exception
183    * @throws NoSuchAlgorithmException the no such algorithm exception
184    * @throws CertificateException the certificate exception
185    * @throws IOException Signals that an I/O exception has occurred.
186    * @throws KeyManagementException the key management exception
187    */
188   public void updateKeystore(String keystoreFileName, String keystorePassword)
189       throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException,
190       KeyManagementException {
191
192     this.keystoreFileName = keystoreFileName;
193     this.keystorePassword = keystorePassword;
194
195     File file = new File(keystoreFileName);
196     String password = keystorePassword;
197
198     if (file.isFile() == false) {
199
200       File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security");
201       file = new File(dir, "jssecacerts");
202       if (file.isFile() == false) {
203
204         file = new File(dir, "cacerts");
205         System.out.println("keystore file doesn't exist, preloading new file with cacerts");
206
207       } else {
208         System.out.println("keystore file doesn't exist, preloading new file with jssecacerts");
209       }
210       password = "changeit";
211
212     }
213
214     InputStream in = new FileInputStream(file);
215     ks = KeyStore.getInstance(KeyStore.getDefaultType());
216     ks.load(in, password.toCharArray());
217     in.close();
218
219     SSLContext context = SSLContext.getInstance("TLS");
220     TrustManagerFactory tmf =
221         TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
222     tmf.init(ks);
223     X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
224     savingTrustManager = new SavingTrustManager(defaultTrustManager);
225     context.init(null, new TrustManager[] {savingTrustManager}, null);
226     sslSocketFactory = context.getSocketFactory();
227
228     System.out.println("About to add the following endpoint server certificates to the keystore:");
229     for (EndPoint ep : endpoints) {
230       System.out.println("\t--------------------------");
231       System.out.println("\t" + ep.toString());
232
233       X509Certificate[] certChain =
234           getCertificateChainForRemoteEndpoint(ep.getHostname(), ep.getPort());
235
236       if (certChain == null) {
237         System.out.println("Could not obtain server certificate chain");
238         return;
239       }
240
241       dumpCertChainInfo(certChain);
242
243       updateKeyStoreWithCertChain(certChain);
244
245     }
246
247   }
248
249   /**
250    * Gets the certificate chain for remote endpoint.
251    *
252    * @param hostname the hostname
253    * @param port the port
254    * @return the certificate chain for remote endpoint
255    * @throws UnknownHostException the unknown host exception
256    * @throws IOException Signals that an I/O exception has occurred.
257    */
258   private X509Certificate[] getCertificateChainForRemoteEndpoint(String hostname, int port)
259       throws UnknownHostException, IOException {
260
261     System.out.println("Opening connection to "+hostname+":"+port+"..");
262     SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(hostname, port);
263     socket.setSoTimeout(10000);
264
265     try {
266       System.out.println("Starting SSL handshake...");
267       socket.startHandshake();
268       socket.close();
269       System.out.println("\nNo errors, certificate is already trusted");
270       System.exit(0);
271     } catch (SSLException exc) {
272       System.out.println("\nCaught SSL exception, we are not authorized to access this server yet");
273       // e.printStackTrace(System.out);
274     }
275
276     return savingTrustManager.chain;
277
278   }
279
280   /**
281    * Dump cert chain info.
282    *
283    * @param chain the chain
284    * @throws NoSuchAlgorithmException the no such algorithm exception
285    * @throws CertificateEncodingException the certificate encoding exception
286    * @throws CertificateParsingException the certificate parsing exception
287    */
288   private void dumpCertChainInfo(X509Certificate[] chain)
289       throws NoSuchAlgorithmException, CertificateEncodingException, CertificateParsingException {
290
291     System.out.println();
292     System.out.println("Server sent " + chain.length + " certificate(s):");
293     System.out.println();
294
295     for (int i = 0; i < chain.length; i++) {
296       X509Certificate cert = chain[i];
297
298       if (dumpCertDetails) {
299         System.out.println("Full cert details @ index = " + i + " \n" + cert.toString());
300       }
301
302       System.out.println("Subject: " + cert.getSubjectDN());
303       System.out.println("Issuer: " + cert.getIssuerDN());
304       System.out.println("SubjectAlternativeNames: ");
305
306       /*
307        * RFC-5280, pg. 38, section 4.2.1.6 ( Subject Alternative Names )
308        * 
309        * Finally, the semantics of subject alternative names that include wildcard characters (e.g.,
310        * as a placeholder for a set of names) are not addressed by this specification. Applications
311        * with specific requirements MAY use such names, but they must define the semantics.
312        * 
313        * id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 }
314        * 
315        * SubjectAltName ::= GeneralNames
316        * 
317        * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
318        * 
319        * GeneralName ::= CHOICE { otherName [0] OtherName, rfc822Name [1] IA5String, dNSName [2]
320        * IA5String, <-- the 2 in the output is a type operand x400Address [3] ORAddress,
321        * directoryName [4] Name, ediPartyName [5] EDIPartyName, uniformResourceIdentifier [6]
322        * IA5String, iPAddress [7] OCTET STRING, registeredID [8] OBJECT IDENTIFIER }
323        * 
324        * OtherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY DEFINED BY
325        * type-id }
326        * 
327        * EDIPartyName ::= SEQUENCE { nameAssigner [0] DirectoryString OPTIONAL, partyName [1]
328        * DirectoryString }
329        * 
330        */
331
332       Collection<List<?>> sans = cert.getSubjectAlternativeNames();
333
334       for (List<?> san : sans) {
335
336         /*
337          * It seems the structure of the array elements contained within the SAN is: [<sanType>,
338          * <sanValue>]*
339          * 
340          */
341
342         int type = ((Integer) san.get(0)).intValue();
343         String typeStr = getSanType(type);
344         String value = (String) san.get(1);
345
346         System.out.println(String.format("\tType:'%s',  Value: '%s'.", typeStr, value));
347
348       }
349
350     }
351
352   }
353
354   /**
355    * Gets the subject alternative names.
356    *
357    * @param cert the cert
358    * @return the subject alternative names
359    * @throws CertificateParsingException the certificate parsing exception
360    */
361   private List<String> getSubjectAlternativeNames(X509Certificate cert)
362       throws CertificateParsingException {
363
364     Collection<List<?>> sans = cert.getSubjectAlternativeNames();
365     List<String> subjectAlternativeNames = new ArrayList<String>();
366
367     for (List<?> san : sans) {
368
369       /*
370        * It seems the structure of the array elements contained within the SAN is: [<sanType>,
371        * <sanValue>]*
372        * 
373        */
374
375       String value = (String) san.get(1);
376       subjectAlternativeNames.add(value);
377     }
378
379     return subjectAlternativeNames;
380   }
381
382   /**
383    * Update key store with cert chain.
384    *
385    * @param chain the chain
386    * @throws NoSuchAlgorithmException the no such algorithm exception
387    * @throws KeyStoreException the key store exception
388    * @throws CertificateException the certificate exception
389    * @throws IOException Signals that an I/O exception has occurred.
390    */
391   private void updateKeyStoreWithCertChain(X509Certificate[] chain)
392       throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
393
394     for (X509Certificate cert : chain) {
395
396       List<String> sans = getSubjectAlternativeNames(cert);
397
398       for (String san : sans) {
399         ks.setCertificateEntry(san, cert);
400         System.out.println(
401             "Added certificate to keystore '" + keystoreFileName + "' using alias '" + san + "'");
402       }
403     }
404
405     OutputStream out = new FileOutputStream(keystoreFileName);
406     ks.store(out, keystorePassword.toCharArray());
407     out.close();
408
409   }
410
411
412   /**
413    * The Class SavingTrustManager.
414    */
415   private static class SavingTrustManager implements X509TrustManager {
416
417     private final X509TrustManager tm;
418     private X509Certificate[] chain;
419
420     /**
421      * Instantiates a new saving trust manager.
422      *
423      * @param tm the tm
424      */
425     SavingTrustManager(X509TrustManager tm) {
426       this.tm = tm;
427     }
428
429     @Override
430     public X509Certificate[] getAcceptedIssuers() {
431       throw new UnsupportedOperationException();
432     }
433
434     /* (non-Javadoc)
435      * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], java.lang.String)
436      */
437     @Override
438     public void checkClientTrusted(X509Certificate[] chain, String authType)
439         throws CertificateException {
440       throw new UnsupportedOperationException();
441     }
442
443     /* (non-Javadoc)
444      * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String)
445      */
446     @Override
447     public void checkServerTrusted(X509Certificate[] chain, String authType)
448         throws CertificateException {
449       this.chain = chain;
450       tm.checkServerTrusted(chain, authType);
451     }
452   }
453
454   private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
455
456   /**
457    * Gets the san type.
458    *
459    * @param type the type
460    * @return the san type
461    */
462   // TODO: convert to enum(int,string)
463   private String getSanType(int type) {
464     switch (type) {
465       case 0:
466         return "otherName";
467       case 1:
468         return "rfc822Name";
469       case 2:
470         return "dNSName";
471       case 3:
472         return "x400Address";
473       case 4:
474         return "directoryName";
475       case 5:
476         return "ediPartyName";
477       case 6:
478         return "uniformResourceIdentifier";
479       case 7:
480         return "iPAddress";
481       case 8:
482         return "registeredID";
483       default:
484         return "unknownSanType";
485     }
486   }
487
488
489   /**
490    * To hex string.
491    *
492    * @param bytes the bytes
493    * @return the string
494    */
495   private static String toHexString(byte[] bytes) {
496     StringBuilder sb = new StringBuilder(bytes.length * 3);
497     for (int b : bytes) {
498       b &= 0xff;
499       sb.append(HEXDIGITS[b >> 4]);
500       sb.append(HEXDIGITS[b & 15]);
501       sb.append(' ');
502     }
503     return sb.toString();
504   }
505
506
507
508   /**
509    * The main method.
510    *
511    * @param args the arguments
512    * @throws Exception the exception
513    */
514   public static void main(String[] args) throws Exception {
515
516     /*
517      * Examples: localhost:8440;localhost:8442 d:\1\adhoc_keystore.jks aaiDomain2 false
518      * localhost:8440;localhost:8442 d:\1\adhoc_keystore.jks aaiDomain2 true
519      */
520
521     if (args.length != 4) {
522       System.out.println(
523           "Usage:   KeyBuilder <[ip:port];*> <keystoreFileName>"
524           + " <keystorePassword> <dumpCertDetails> ");
525       System.exit(1);
526     }
527     KeystoreBuilder kb = new KeystoreBuilder(args[0]);
528     kb.setDumpCertDetails(Boolean.parseBoolean(args[3]));
529     kb.updateKeystore(args[1], args[2]);
530
531   }
532 }
533
534