761867d711381022e90148f43c7a7e92ed6e5df8
[aaf/authz.git] / authz-certman / src / main / java / com / att / authz / cm / ca / AppCA.java
1 /*******************************************************************************\r
2  * ============LICENSE_START====================================================\r
3  * * org.onap.aai\r
4  * * ===========================================================================\r
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
6  * * Copyright © 2017 Amdocs\r
7  * * ===========================================================================\r
8  * * Licensed under the Apache License, Version 2.0 (the "License");\r
9  * * you may not use this file except in compliance with the License.\r
10  * * You may obtain a copy of the License at\r
11  * * \r
12  *  *      http://www.apache.org/licenses/LICENSE-2.0\r
13  * * \r
14  *  * Unless required by applicable law or agreed to in writing, software\r
15  * * distributed under the License is distributed on an "AS IS" BASIS,\r
16  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
17  * * See the License for the specific language governing permissions and\r
18  * * limitations under the License.\r
19  * * ============LICENSE_END====================================================\r
20  * *\r
21  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
22  * *\r
23  ******************************************************************************/\r
24 package com.att.authz.cm.ca;\r
25 \r
26 import java.io.File;\r
27 import java.io.IOException;\r
28 import java.net.Authenticator;\r
29 import java.net.MalformedURLException;\r
30 import java.net.PasswordAuthentication;\r
31 import java.net.URL;\r
32 import java.security.cert.CertStore;\r
33 import java.security.cert.CertStoreException;\r
34 import java.security.cert.Certificate;\r
35 import java.security.cert.CertificateException;\r
36 import java.security.cert.X509Certificate;\r
37 import java.util.ArrayList;\r
38 import java.util.Collection;\r
39 import java.util.Date;\r
40 import java.util.Iterator;\r
41 import java.util.List;\r
42 \r
43 import org.bouncycastle.operator.OperatorCreationException;\r
44 import org.bouncycastle.pkcs.PKCS10CertificationRequest;\r
45 import org.jscep.client.Client;\r
46 import org.jscep.client.ClientException;\r
47 import org.jscep.client.EnrollmentResponse;\r
48 import org.jscep.client.verification.CertificateVerifier;\r
49 import org.jscep.transaction.TransactionException;\r
50 \r
51 import com.att.authz.cm.cert.BCFactory;\r
52 import com.att.authz.cm.cert.CSRMeta;\r
53 import com.att.authz.cm.cert.StandardFields;\r
54 import com.att.authz.common.Define;\r
55 import com.att.cadi.cm.CertException;\r
56 import com.att.cadi.cm.Factory;\r
57 import com.att.cadi.config.Config;\r
58 import com.att.cadi.routing.GreatCircle;\r
59 import com.att.inno.env.Env;\r
60 import com.att.inno.env.TimeTaken;\r
61 import com.att.inno.env.Trans;\r
62 import com.att.inno.env.util.Split;\r
63 \r
64 public class AppCA extends CA {\r
65         public static final String CA_PERM_TYPE = Define.ROOT_NS+".ca"; // Permission Type for validation\r
66         private static final String AAF_DATA_DIR = "aaf_data_dir";\r
67         private static final String CA_PREFIX = "http://";\r
68         private static final String CA_POSTFIX="/certsrv/mscep_admin/mscep.dll";\r
69 \r
70         private final static String MS_PROFILE="1";\r
71         private static final String CM_TRUST_CAS = "cm_trust_cas";\r
72         private Clients clients;\r
73 \r
74         private static class AAFStdFields implements StandardFields {\r
75                 private final String env;\r
76                 public AAFStdFields(Trans trans) throws CertException {\r
77                         env = trans.getProperty(Config.AAF_ENV);\r
78                         if(env==null) {\r
79                                 throw new CertException(Config.AAF_ENV + " must be set to create Certificates");\r
80                         }\r
81                 }\r
82                 @Override\r
83                 public void set(CSRMeta csr) {\r
84                         // Environment\r
85                         csr.environment(env);\r
86                         // Standard Fields\r
87                         csr.o("ATT Services,Inc.");\r
88                         csr.l("St Louis");\r
89                         csr.st("Missouri");\r
90                         csr.c("US");\r
91                 }\r
92         }\r
93 \r
94         public AppCA(final Trans trans, final String name, final String urlstr, final String id, final String pw) throws IOException, CertificateException, CertException {\r
95                 super(name,new AAFStdFields(trans), CA_PERM_TYPE);\r
96                 \r
97                 clients = new Clients(trans,urlstr);\r
98                 \r
99                 \r
100                 // Set this for NTLM password Microsoft\r
101                 Authenticator.setDefault(new Authenticator() {\r
102                           public PasswordAuthentication getPasswordAuthentication () {\r
103                             return new PasswordAuthentication (\r
104                                         id,\r
105                                         trans.decryptor().decrypt(pw).toCharArray());\r
106                         }\r
107                 });\r
108 \r
109 \r
110 \r
111                 try {\r
112                         StringBuilder sb = new StringBuilder("CA Reported Trusted Certificates");\r
113                         List<X509Certificate> trustCerts = new ArrayList<X509Certificate>();\r
114                         for(Client client : clients) {\r
115                                 CertStore cs = client.getCaCertificate(MS_PROFILE);\r
116                                 \r
117                                 Collection<? extends Certificate> cc = cs.getCertificates(null);\r
118                                 for(Certificate c : cc) {\r
119                                         X509Certificate xc = (X509Certificate)c;\r
120                                         // Avoid duplicate Certificates from multiple servers\r
121                                         X509Certificate match = null;\r
122                                         for(X509Certificate t : trustCerts) {\r
123                                                 if(t.getSerialNumber().equals(xc.getSerialNumber())) {\r
124                                                         match = xc;\r
125                                                         break;\r
126                                                 }\r
127                                         }\r
128                                         if(match==null && xc.getSubjectDN().getName().startsWith("CN=ATT ")) {\r
129                                                 sb.append("\n\t");\r
130                                                 sb.append(xc.getSubjectDN());\r
131                                                 sb.append("\n\t\tSerial Number: ");\r
132                                                 String bi = xc.getSerialNumber().toString(16);\r
133                                                 for(int i=0;i<bi.length();++i) {\r
134                                                         if(i>1 && i%2==0) {\r
135                                                                 sb.append(':');\r
136                                                         }\r
137                                                         sb.append(bi.charAt(i));\r
138                                                 }\r
139                                                 sb.append("\n\t\tIssuer:        ");\r
140                                                 sb.append(xc.getIssuerDN());\r
141                                                 sb.append("\n\t\tNot Before:    ");\r
142                                                 sb.append(xc.getNotBefore());\r
143                                                 sb.append("\n\t\tNot After:     ");\r
144                                                 sb.append(xc.getNotAfter());\r
145                                                 sb.append("\n\t\tSigAlgorithm:  ");\r
146                                                 sb.append(xc.getSigAlgName());\r
147                                                 sb.append("\n\t\tType:          ");\r
148                                                 sb.append(xc.getType());\r
149                                                 sb.append("\n\t\tVersion:       ");\r
150                                                 sb.append(xc.getVersion());\r
151 \r
152                                                 trustCerts.add(xc);\r
153                                         }\r
154                                 }\r
155                         }\r
156                         trans.init().log(sb);\r
157                         // Add Additional ones from Property\r
158                         String data_dir = trans.getProperty(AAF_DATA_DIR);\r
159                         if(data_dir!=null) {\r
160                                 File data = new File(data_dir);\r
161                                 if(data.exists()) {\r
162                                         String trust_cas = trans.getProperty(CM_TRUST_CAS);\r
163                                         byte[] bytes;\r
164                                         if(trust_cas!=null) {\r
165                                                 for(String fname : Split.split(';', trust_cas)) {\r
166                                                         File crt = new File(data,fname);\r
167                                                         if(crt.exists()) {\r
168                                                                 bytes = Factory.decode(crt);\r
169                                                                 try {\r
170                                                                         Collection<? extends Certificate> cc = Factory.toX509Certificate(bytes);\r
171                                                                         for(Certificate c : cc) {\r
172                                                                                 trustCerts.add((X509Certificate)c);\r
173                                                                         }\r
174                                                                 } catch (CertificateException e) {\r
175                                                                         throw new CertException(e);\r
176                                                                 }\r
177                                                         }\r
178                                                 }\r
179                                         }\r
180                                 }\r
181                         }\r
182                         \r
183                         String[] trustChain = new String[trustCerts.size()];\r
184                         int i=-1;\r
185                         for( Certificate cert : trustCerts) {\r
186                                 trustChain[++i]=BCFactory.toString(trans,cert);\r
187                         }\r
188                         \r
189                         setTrustChain(trustChain);\r
190                 } catch (ClientException | CertStoreException e) {\r
191                         // Note:  Cannot validly start without all Clients, because we need to read all Issuing Certificates\r
192                         // This is acceptable risk for most things, as we're not real time in general\r
193                         throw new CertException(e);\r
194                 }\r
195         }\r
196 \r
197 \r
198         @Override\r
199         public X509Certificate sign(Trans trans, CSRMeta csrmeta) throws IOException, CertException {\r
200                 TimeTaken tt = trans.start("Generating CSR and Keys for New Certificate", Env.SUB);\r
201                 PKCS10CertificationRequest csr;\r
202                 try {\r
203                         csr = csrmeta.generateCSR(trans);\r
204                         if(trans.info().isLoggable()) {\r
205                                 trans.info().log(BCFactory.toString(trans, csr));\r
206                         } \r
207                         if(trans.info().isLoggable()) {\r
208                                 trans.info().log(csr);\r
209                         }\r
210                 } finally {\r
211                         tt.done();\r
212                 }\r
213                 \r
214                 tt = trans.start("Enroll CSR", Env.SUB);\r
215                 Client client = null;\r
216                 try {\r
217                         client = clients.best();\r
218                         EnrollmentResponse er = client.enrol(\r
219                                         csrmeta.initialConversationCert(trans),\r
220                                         csrmeta.keypair(trans).getPrivate(),\r
221                                         csr,\r
222                                         MS_PROFILE /* profile... MS can't deal with blanks*/);\r
223                         while(true) {\r
224                                 if(er.isSuccess()) {\r
225                                         for( Certificate cert : er.getCertStore().getCertificates(null)) {\r
226                                                 return (X509Certificate)cert;\r
227                                         }\r
228                                         break;\r
229                                 } else if (er.isPending()) {\r
230                                         trans.checkpoint("Polling, waiting on CA to complete");\r
231                                         Thread.sleep(3000);\r
232                                 } else if (er.isFailure()) {\r
233                                         throw new CertException(er.getFailInfo().toString());\r
234                                 }\r
235                         }\r
236                 } catch (ClientException e) {\r
237                         trans.error().log(e,"SCEP Client Error, Temporarily Invalidating Client");\r
238                         if(client!=null) {\r
239                                 clients.invalidate(client);\r
240                         }\r
241                 } catch (InterruptedException|TransactionException|CertificateException|OperatorCreationException | CertStoreException e) {\r
242                         trans.error().log(e);\r
243                 } finally {\r
244                         tt.done();\r
245                 }\r
246                 \r
247                 return null;\r
248         }\r
249 \r
250 \r
251         private class Clients implements Iterable<Client>{\r
252                 /**\r
253                  * CSO Servers are in Dallas and St Louis\r
254                  * GEO_LOCATION   LATITUDE    LONGITUDE    ZIPCODE   TIMEZONE\r
255                  * ------------   --------    ---------    -------   --------\r
256                  * DLLSTXCF       32.779295   -96.800014   75202     America/Chicago\r
257                  * STLSMORC       38.627345   -90.193774   63101     America/Chicago\r
258                  * \r
259                  * The online production issuing CA servers are:\r
260                  *      AAF - CADI Issuing CA 01        135.41.45.152   MOSTLS1AAFXXA02\r
261                  *      AAF - CADI Issuing CA 02        135.31.72.154   TXDLLS2AAFXXA02\r
262                  */\r
263                 \r
264                 private final Client[] client;\r
265                 private final Date[] failure;\r
266                 private int preferred;\r
267 \r
268                 public Clients(Trans trans, String urlstr) throws MalformedURLException { \r
269                         String[] urlstrs = Split.split(',', urlstr);\r
270                         client = new Client[urlstrs.length];\r
271                         failure = new Date[urlstrs.length];\r
272                         double distance = Double.MAX_VALUE;\r
273                         String localLat = trans.getProperty("AFT_LATITUDE","39.833333"); //Note: Defaulting to GEO center of US\r
274                         String localLong = trans.getProperty("AFT_LONGITUDE","-98.583333");\r
275                         for(int i=0;i<urlstrs.length;++i) {\r
276                                 String[] info = Split.split('/', urlstrs[i]);\r
277                                 if(info.length<3) {\r
278                                         throw new MalformedURLException("Configuration needs LAT and LONG, i.e. ip:port/lat/long");\r
279                                 }\r
280                                 client[i] = new Client(new URL(CA_PREFIX + info[0] + CA_POSTFIX), \r
281                                         new CertificateVerifier() {\r
282                                                 @Override\r
283                                                 public boolean verify(X509Certificate cert) {\r
284                                                         return true;\r
285                                                 }\r
286                                         }\r
287                                 );\r
288                                 double d = GreatCircle.calc(info[1],info[2],localLat,localLong);\r
289                                 if(d<distance) {\r
290                                         preferred = i;\r
291                                         distance=d;\r
292                                 }\r
293                         }\r
294                         trans.init().printf("Preferred Certificate Authority is %s",urlstrs[preferred]);\r
295                         for(int i=0;i<urlstrs.length;++i) {\r
296                                 if(i!=preferred) {\r
297                                         trans.init().printf("Alternate Certificate Authority is %s",urlstrs[i]);\r
298                                 }\r
299                         }\r
300                 }\r
301                 private Client best() throws ClientException {\r
302                         if(failure[preferred]==null) {\r
303                                 return client[preferred];\r
304                         } else {\r
305                                 Client c=null;\r
306                                 // See if Alternate available\r
307                                 for(int i=0;i<failure.length;++i) {\r
308                                         if(failure[i]==null) {\r
309                                                 c=client[i];\r
310                                         }\r
311                                 }\r
312                                 \r
313                                 // If not, see if any expirations can be cleared\r
314                                 Date now = new Date();\r
315                                 for(int i=0;i<failure.length;++i) {\r
316                                         if(now.after(failure[i])) {\r
317                                                 failure[i]=null;\r
318                                                 if(c==null) {\r
319                                                         c=client[i];\r
320                                                 }\r
321                                         }\r
322                                 }\r
323                                 \r
324                                 // if still nothing found, then throw.\r
325                                 if(c==null) {\r
326                                         throw new ClientException("No available machines to call");\r
327                                 } \r
328                                 return c;\r
329                         }\r
330                 }\r
331                 \r
332                 public void invalidate(Client clt) {\r
333                    for(int i=0;i<client.length;++i) {\r
334                            if(client[i].equals(clt)) {\r
335                                    failure[i]=new Date(System.currentTimeMillis()+180000 /* 3 mins */);\r
336                            }\r
337                    }\r
338                 }\r
339                 \r
340                 @Override\r
341                 public Iterator<Client> iterator() {\r
342                         return new Iterator<Client>() {\r
343                                 private int iter = 0;\r
344                                 @Override\r
345                                 public boolean hasNext() {\r
346                                         return iter < Clients.this.client.length;\r
347                                 }\r
348 \r
349                                 @Override\r
350                                 public Client next() {\r
351                                         return Clients.this.client[iter++];\r
352                                 }\r
353                                 \r
354                         };\r
355                 }\r
356         }\r
357 }\r