1 /*******************************************************************************
\r
2 * ============LICENSE_START====================================================
\r
4 * * ===========================================================================
\r
5 * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
\r
6 * * ===========================================================================
\r
7 * * Licensed under the Apache License, Version 2.0 (the "License");
\r
8 * * you may not use this file except in compliance with the License.
\r
9 * * You may obtain a copy of the License at
\r
11 * * http://www.apache.org/licenses/LICENSE-2.0
\r
13 * * Unless required by applicable law or agreed to in writing, software
\r
14 * * distributed under the License is distributed on an "AS IS" BASIS,
\r
15 * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
16 * * See the License for the specific language governing permissions and
\r
17 * * limitations under the License.
\r
18 * * ============LICENSE_END====================================================
\r
20 * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
\r
22 ******************************************************************************/
\r
23 package com.att.authz.cm.ca;
\r
25 import java.io.File;
\r
26 import java.io.IOException;
\r
27 import java.net.Authenticator;
\r
28 import java.net.MalformedURLException;
\r
29 import java.net.PasswordAuthentication;
\r
30 import java.net.URL;
\r
31 import java.security.cert.CertStore;
\r
32 import java.security.cert.CertStoreException;
\r
33 import java.security.cert.Certificate;
\r
34 import java.security.cert.CertificateException;
\r
35 import java.security.cert.X509Certificate;
\r
36 import java.util.ArrayList;
\r
37 import java.util.Collection;
\r
38 import java.util.Date;
\r
39 import java.util.Iterator;
\r
40 import java.util.List;
\r
42 import org.bouncycastle.operator.OperatorCreationException;
\r
43 import org.bouncycastle.pkcs.PKCS10CertificationRequest;
\r
44 import org.jscep.client.Client;
\r
45 import org.jscep.client.ClientException;
\r
46 import org.jscep.client.EnrollmentResponse;
\r
47 import org.jscep.client.verification.CertificateVerifier;
\r
48 import org.jscep.transaction.TransactionException;
\r
50 import com.att.authz.cm.cert.BCFactory;
\r
51 import com.att.authz.cm.cert.CSRMeta;
\r
52 import com.att.authz.cm.cert.StandardFields;
\r
53 import com.att.authz.common.Define;
\r
54 import com.att.cadi.cm.CertException;
\r
55 import com.att.cadi.cm.Factory;
\r
56 import com.att.cadi.config.Config;
\r
57 import com.att.cadi.routing.GreatCircle;
\r
58 import com.att.inno.env.Env;
\r
59 import com.att.inno.env.TimeTaken;
\r
60 import com.att.inno.env.Trans;
\r
61 import com.att.inno.env.util.Split;
\r
63 public class AppCA extends CA {
\r
64 public static final String CA_PERM_TYPE = Define.ROOT_NS+".ca"; // Permission Type for validation
\r
65 private static final String AAF_DATA_DIR = "aaf_data_dir";
\r
66 private static final String CA_PREFIX = "http://";
\r
67 private static final String CA_POSTFIX="/certsrv/mscep_admin/mscep.dll";
\r
69 private final static String MS_PROFILE="1";
\r
70 private static final String CM_TRUST_CAS = "cm_trust_cas";
\r
71 private Clients clients;
\r
73 private static class AAFStdFields implements StandardFields {
\r
74 private final String env;
\r
75 public AAFStdFields(Trans trans) throws CertException {
\r
76 env = trans.getProperty(Config.AAF_ENV);
\r
78 throw new CertException(Config.AAF_ENV + " must be set to create Certificates");
\r
82 public void set(CSRMeta csr) {
\r
84 csr.environment(env);
\r
86 csr.o("ATT Services,Inc.");
\r
93 public AppCA(final Trans trans, final String name, final String urlstr, final String id, final String pw) throws IOException, CertificateException, CertException {
\r
94 super(name,new AAFStdFields(trans), CA_PERM_TYPE);
\r
96 clients = new Clients(trans,urlstr);
\r
99 // Set this for NTLM password Microsoft
\r
100 Authenticator.setDefault(new Authenticator() {
\r
101 public PasswordAuthentication getPasswordAuthentication () {
\r
102 return new PasswordAuthentication (
\r
104 trans.decryptor().decrypt(pw).toCharArray());
\r
111 StringBuilder sb = new StringBuilder("CA Reported Trusted Certificates");
\r
112 List<X509Certificate> trustCerts = new ArrayList<X509Certificate>();
\r
113 for(Client client : clients) {
\r
114 CertStore cs = client.getCaCertificate(MS_PROFILE);
\r
116 Collection<? extends Certificate> cc = cs.getCertificates(null);
\r
117 for(Certificate c : cc) {
\r
118 X509Certificate xc = (X509Certificate)c;
\r
119 // Avoid duplicate Certificates from multiple servers
\r
120 X509Certificate match = null;
\r
121 for(X509Certificate t : trustCerts) {
\r
122 if(t.getSerialNumber().equals(xc.getSerialNumber())) {
\r
127 if(match==null && xc.getSubjectDN().getName().startsWith("CN=ATT ")) {
\r
129 sb.append(xc.getSubjectDN());
\r
130 sb.append("\n\t\tSerial Number: ");
\r
131 String bi = xc.getSerialNumber().toString(16);
\r
132 for(int i=0;i<bi.length();++i) {
\r
133 if(i>1 && i%2==0) {
\r
136 sb.append(bi.charAt(i));
\r
138 sb.append("\n\t\tIssuer: ");
\r
139 sb.append(xc.getIssuerDN());
\r
140 sb.append("\n\t\tNot Before: ");
\r
141 sb.append(xc.getNotBefore());
\r
142 sb.append("\n\t\tNot After: ");
\r
143 sb.append(xc.getNotAfter());
\r
144 sb.append("\n\t\tSigAlgorithm: ");
\r
145 sb.append(xc.getSigAlgName());
\r
146 sb.append("\n\t\tType: ");
\r
147 sb.append(xc.getType());
\r
148 sb.append("\n\t\tVersion: ");
\r
149 sb.append(xc.getVersion());
\r
151 trustCerts.add(xc);
\r
155 trans.init().log(sb);
\r
156 // Add Additional ones from Property
\r
157 String data_dir = trans.getProperty(AAF_DATA_DIR);
\r
158 if(data_dir!=null) {
\r
159 File data = new File(data_dir);
\r
160 if(data.exists()) {
\r
161 String trust_cas = trans.getProperty(CM_TRUST_CAS);
\r
163 if(trust_cas!=null) {
\r
164 for(String fname : Split.split(';', trust_cas)) {
\r
165 File crt = new File(data,fname);
\r
167 bytes = Factory.decode(crt);
\r
169 Collection<? extends Certificate> cc = Factory.toX509Certificate(bytes);
\r
170 for(Certificate c : cc) {
\r
171 trustCerts.add((X509Certificate)c);
\r
173 } catch (CertificateException e) {
\r
174 throw new CertException(e);
\r
182 String[] trustChain = new String[trustCerts.size()];
\r
184 for( Certificate cert : trustCerts) {
\r
185 trustChain[++i]=BCFactory.toString(trans,cert);
\r
188 setTrustChain(trustChain);
\r
189 } catch (ClientException | CertStoreException e) {
\r
190 // Note: Cannot validly start without all Clients, because we need to read all Issuing Certificates
\r
191 // This is acceptable risk for most things, as we're not real time in general
\r
192 throw new CertException(e);
\r
198 public X509Certificate sign(Trans trans, CSRMeta csrmeta) throws IOException, CertException {
\r
199 TimeTaken tt = trans.start("Generating CSR and Keys for New Certificate", Env.SUB);
\r
200 PKCS10CertificationRequest csr;
\r
202 csr = csrmeta.generateCSR(trans);
\r
203 if(trans.info().isLoggable()) {
\r
204 trans.info().log(BCFactory.toString(trans, csr));
\r
206 if(trans.info().isLoggable()) {
\r
207 trans.info().log(csr);
\r
213 tt = trans.start("Enroll CSR", Env.SUB);
\r
214 Client client = null;
\r
216 client = clients.best();
\r
217 EnrollmentResponse er = client.enrol(
\r
218 csrmeta.initialConversationCert(trans),
\r
219 csrmeta.keypair(trans).getPrivate(),
\r
221 MS_PROFILE /* profile... MS can't deal with blanks*/);
\r
223 if(er.isSuccess()) {
\r
224 for( Certificate cert : er.getCertStore().getCertificates(null)) {
\r
225 return (X509Certificate)cert;
\r
228 } else if (er.isPending()) {
\r
229 trans.checkpoint("Polling, waiting on CA to complete");
\r
230 Thread.sleep(3000);
\r
231 } else if (er.isFailure()) {
\r
232 throw new CertException(er.getFailInfo().toString());
\r
235 } catch (ClientException e) {
\r
236 trans.error().log(e,"SCEP Client Error, Temporarily Invalidating Client");
\r
238 clients.invalidate(client);
\r
240 } catch (InterruptedException|TransactionException|CertificateException|OperatorCreationException | CertStoreException e) {
\r
241 trans.error().log(e);
\r
250 private class Clients implements Iterable<Client>{
\r
252 * CSO Servers are in Dallas and St Louis
\r
253 * GEO_LOCATION LATITUDE LONGITUDE ZIPCODE TIMEZONE
\r
254 * ------------ -------- --------- ------- --------
\r
255 * DLLSTXCF 32.779295 -96.800014 75202 America/Chicago
\r
256 * STLSMORC 38.627345 -90.193774 63101 America/Chicago
\r
258 * The online production issuing CA servers are:
\r
259 * AAF - CADI Issuing CA 01 135.41.45.152 MOSTLS1AAFXXA02
\r
260 * AAF - CADI Issuing CA 02 135.31.72.154 TXDLLS2AAFXXA02
\r
263 private final Client[] client;
\r
264 private final Date[] failure;
\r
265 private int preferred;
\r
267 public Clients(Trans trans, String urlstr) throws MalformedURLException {
\r
268 String[] urlstrs = Split.split(',', urlstr);
\r
269 client = new Client[urlstrs.length];
\r
270 failure = new Date[urlstrs.length];
\r
271 double distance = Double.MAX_VALUE;
\r
272 String localLat = trans.getProperty("AFT_LATITUDE","39.833333"); //Note: Defaulting to GEO center of US
\r
273 String localLong = trans.getProperty("AFT_LONGITUDE","-98.583333");
\r
274 for(int i=0;i<urlstrs.length;++i) {
\r
275 String[] info = Split.split('/', urlstrs[i]);
\r
276 if(info.length<3) {
\r
277 throw new MalformedURLException("Configuration needs LAT and LONG, i.e. ip:port/lat/long");
\r
279 client[i] = new Client(new URL(CA_PREFIX + info[0] + CA_POSTFIX),
\r
280 new CertificateVerifier() {
\r
282 public boolean verify(X509Certificate cert) {
\r
287 double d = GreatCircle.calc(info[1],info[2],localLat,localLong);
\r
293 trans.init().printf("Preferred Certificate Authority is %s",urlstrs[preferred]);
\r
294 for(int i=0;i<urlstrs.length;++i) {
\r
296 trans.init().printf("Alternate Certificate Authority is %s",urlstrs[i]);
\r
300 private Client best() throws ClientException {
\r
301 if(failure[preferred]==null) {
\r
302 return client[preferred];
\r
305 // See if Alternate available
\r
306 for(int i=0;i<failure.length;++i) {
\r
307 if(failure[i]==null) {
\r
312 // If not, see if any expirations can be cleared
\r
313 Date now = new Date();
\r
314 for(int i=0;i<failure.length;++i) {
\r
315 if(now.after(failure[i])) {
\r
323 // if still nothing found, then throw.
\r
325 throw new ClientException("No available machines to call");
\r
331 public void invalidate(Client clt) {
\r
332 for(int i=0;i<client.length;++i) {
\r
333 if(client[i].equals(clt)) {
\r
334 failure[i]=new Date(System.currentTimeMillis()+180000 /* 3 mins */);
\r
340 public Iterator<Client> iterator() {
\r
341 return new Iterator<Client>() {
\r
342 private int iter = 0;
\r
344 public boolean hasNext() {
\r
345 return iter < Clients.this.client.length;
\r
349 public Client next() {
\r
350 return Clients.this.client[iter++];
\r