1 /*******************************************************************************
\r
2 * ============LICENSE_START====================================================
\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
12 * * http://www.apache.org/licenses/LICENSE-2.0
\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
21 * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
\r
23 ******************************************************************************/
\r
24 package com.att.authz.cm.ca;
\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
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
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
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
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
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
79 throw new CertException(Config.AAF_ENV + " must be set to create Certificates");
\r
83 public void set(CSRMeta csr) {
\r
85 csr.environment(env);
\r
87 csr.o("ATT Services,Inc.");
\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
97 clients = new Clients(trans,urlstr);
\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
105 trans.decryptor().decrypt(pw).toCharArray());
\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
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
128 if(match==null && xc.getSubjectDN().getName().startsWith("CN=ATT ")) {
\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
137 sb.append(bi.charAt(i));
\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
152 trustCerts.add(xc);
\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
164 if(trust_cas!=null) {
\r
165 for(String fname : Split.split(';', trust_cas)) {
\r
166 File crt = new File(data,fname);
\r
168 bytes = Factory.decode(crt);
\r
170 Collection<? extends Certificate> cc = Factory.toX509Certificate(bytes);
\r
171 for(Certificate c : cc) {
\r
172 trustCerts.add((X509Certificate)c);
\r
174 } catch (CertificateException e) {
\r
175 throw new CertException(e);
\r
183 String[] trustChain = new String[trustCerts.size()];
\r
185 for( Certificate cert : trustCerts) {
\r
186 trustChain[++i]=BCFactory.toString(trans,cert);
\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
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
203 csr = csrmeta.generateCSR(trans);
\r
204 if(trans.info().isLoggable()) {
\r
205 trans.info().log(BCFactory.toString(trans, csr));
\r
207 if(trans.info().isLoggable()) {
\r
208 trans.info().log(csr);
\r
214 tt = trans.start("Enroll CSR", Env.SUB);
\r
215 Client client = null;
\r
217 client = clients.best();
\r
218 EnrollmentResponse er = client.enrol(
\r
219 csrmeta.initialConversationCert(trans),
\r
220 csrmeta.keypair(trans).getPrivate(),
\r
222 MS_PROFILE /* profile... MS can't deal with blanks*/);
\r
224 if(er.isSuccess()) {
\r
225 for( Certificate cert : er.getCertStore().getCertificates(null)) {
\r
226 return (X509Certificate)cert;
\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
236 } catch (ClientException e) {
\r
237 trans.error().log(e,"SCEP Client Error, Temporarily Invalidating Client");
\r
239 clients.invalidate(client);
\r
241 } catch (InterruptedException|TransactionException|CertificateException|OperatorCreationException | CertStoreException e) {
\r
242 trans.error().log(e);
\r
251 private class Clients implements Iterable<Client>{
\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
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
264 private final Client[] client;
\r
265 private final Date[] failure;
\r
266 private int preferred;
\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
280 client[i] = new Client(new URL(CA_PREFIX + info[0] + CA_POSTFIX),
\r
281 new CertificateVerifier() {
\r
283 public boolean verify(X509Certificate cert) {
\r
288 double d = GreatCircle.calc(info[1],info[2],localLat,localLong);
\r
294 trans.init().printf("Preferred Certificate Authority is %s",urlstrs[preferred]);
\r
295 for(int i=0;i<urlstrs.length;++i) {
\r
297 trans.init().printf("Alternate Certificate Authority is %s",urlstrs[i]);
\r
301 private Client best() throws ClientException {
\r
302 if(failure[preferred]==null) {
\r
303 return client[preferred];
\r
306 // See if Alternate available
\r
307 for(int i=0;i<failure.length;++i) {
\r
308 if(failure[i]==null) {
\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
324 // if still nothing found, then throw.
\r
326 throw new ClientException("No available machines to call");
\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
341 public Iterator<Client> iterator() {
\r
342 return new Iterator<Client>() {
\r
343 private int iter = 0;
\r
345 public boolean hasNext() {
\r
346 return iter < Clients.this.client.length;
\r
350 public Client next() {
\r
351 return Clients.this.client[iter++];
\r