--- /dev/null
+/*******************************************************************************\r
+ * ============LICENSE_START====================================================\r
+ * * org.onap.aai\r
+ * * ===========================================================================\r
+ * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
+ * * Copyright © 2017 Amdocs\r
+ * * ===========================================================================\r
+ * * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * * you may not use this file except in compliance with the License.\r
+ * * You may obtain a copy of the License at\r
+ * * \r
+ * * http://www.apache.org/licenses/LICENSE-2.0\r
+ * * \r
+ * * Unless required by applicable law or agreed to in writing, software\r
+ * * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * * See the License for the specific language governing permissions and\r
+ * * limitations under the License.\r
+ * * ============LICENSE_END====================================================\r
+ * *\r
+ * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
+ * *\r
+ ******************************************************************************/\r
+package com.att.authz.cm.ca;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.net.Authenticator;\r
+import java.net.MalformedURLException;\r
+import java.net.PasswordAuthentication;\r
+import java.net.URL;\r
+import java.security.cert.CertStore;\r
+import java.security.cert.CertStoreException;\r
+import java.security.cert.Certificate;\r
+import java.security.cert.CertificateException;\r
+import java.security.cert.X509Certificate;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Date;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import org.bouncycastle.operator.OperatorCreationException;\r
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;\r
+import org.jscep.client.Client;\r
+import org.jscep.client.ClientException;\r
+import org.jscep.client.EnrollmentResponse;\r
+import org.jscep.client.verification.CertificateVerifier;\r
+import org.jscep.transaction.TransactionException;\r
+\r
+import com.att.authz.cm.cert.BCFactory;\r
+import com.att.authz.cm.cert.CSRMeta;\r
+import com.att.authz.cm.cert.StandardFields;\r
+import com.att.authz.common.Define;\r
+import com.att.cadi.cm.CertException;\r
+import com.att.cadi.cm.Factory;\r
+import com.att.cadi.config.Config;\r
+import com.att.cadi.routing.GreatCircle;\r
+import com.att.inno.env.Env;\r
+import com.att.inno.env.TimeTaken;\r
+import com.att.inno.env.Trans;\r
+import com.att.inno.env.util.Split;\r
+\r
+public class AppCA extends CA {\r
+ public static final String CA_PERM_TYPE = Define.ROOT_NS+".ca"; // Permission Type for validation\r
+ private static final String AAF_DATA_DIR = "aaf_data_dir";\r
+ private static final String CA_PREFIX = "http://";\r
+ private static final String CA_POSTFIX="/certsrv/mscep_admin/mscep.dll";\r
+\r
+ private final static String MS_PROFILE="1";\r
+ private static final String CM_TRUST_CAS = "cm_trust_cas";\r
+ private Clients clients;\r
+\r
+ private static class AAFStdFields implements StandardFields {\r
+ private final String env;\r
+ public AAFStdFields(Trans trans) throws CertException {\r
+ env = trans.getProperty(Config.AAF_ENV);\r
+ if(env==null) {\r
+ throw new CertException(Config.AAF_ENV + " must be set to create Certificates");\r
+ }\r
+ }\r
+ @Override\r
+ public void set(CSRMeta csr) {\r
+ // Environment\r
+ csr.environment(env);\r
+ // Standard Fields\r
+ csr.o("ATT Services,Inc.");\r
+ csr.l("St Louis");\r
+ csr.st("Missouri");\r
+ csr.c("US");\r
+ }\r
+ }\r
+\r
+ public AppCA(final Trans trans, final String name, final String urlstr, final String id, final String pw) throws IOException, CertificateException, CertException {\r
+ super(name,new AAFStdFields(trans), CA_PERM_TYPE);\r
+ \r
+ clients = new Clients(trans,urlstr);\r
+ \r
+ \r
+ // Set this for NTLM password Microsoft\r
+ Authenticator.setDefault(new Authenticator() {\r
+ public PasswordAuthentication getPasswordAuthentication () {\r
+ return new PasswordAuthentication (\r
+ id,\r
+ trans.decryptor().decrypt(pw).toCharArray());\r
+ }\r
+ });\r
+\r
+\r
+\r
+ try {\r
+ StringBuilder sb = new StringBuilder("CA Reported Trusted Certificates");\r
+ List<X509Certificate> trustCerts = new ArrayList<X509Certificate>();\r
+ for(Client client : clients) {\r
+ CertStore cs = client.getCaCertificate(MS_PROFILE);\r
+ \r
+ Collection<? extends Certificate> cc = cs.getCertificates(null);\r
+ for(Certificate c : cc) {\r
+ X509Certificate xc = (X509Certificate)c;\r
+ // Avoid duplicate Certificates from multiple servers\r
+ X509Certificate match = null;\r
+ for(X509Certificate t : trustCerts) {\r
+ if(t.getSerialNumber().equals(xc.getSerialNumber())) {\r
+ match = xc;\r
+ break;\r
+ }\r
+ }\r
+ if(match==null && xc.getSubjectDN().getName().startsWith("CN=ATT ")) {\r
+ sb.append("\n\t");\r
+ sb.append(xc.getSubjectDN());\r
+ sb.append("\n\t\tSerial Number: ");\r
+ String bi = xc.getSerialNumber().toString(16);\r
+ for(int i=0;i<bi.length();++i) {\r
+ if(i>1 && i%2==0) {\r
+ sb.append(':');\r
+ }\r
+ sb.append(bi.charAt(i));\r
+ }\r
+ sb.append("\n\t\tIssuer: ");\r
+ sb.append(xc.getIssuerDN());\r
+ sb.append("\n\t\tNot Before: ");\r
+ sb.append(xc.getNotBefore());\r
+ sb.append("\n\t\tNot After: ");\r
+ sb.append(xc.getNotAfter());\r
+ sb.append("\n\t\tSigAlgorithm: ");\r
+ sb.append(xc.getSigAlgName());\r
+ sb.append("\n\t\tType: ");\r
+ sb.append(xc.getType());\r
+ sb.append("\n\t\tVersion: ");\r
+ sb.append(xc.getVersion());\r
+\r
+ trustCerts.add(xc);\r
+ }\r
+ }\r
+ }\r
+ trans.init().log(sb);\r
+ // Add Additional ones from Property\r
+ String data_dir = trans.getProperty(AAF_DATA_DIR);\r
+ if(data_dir!=null) {\r
+ File data = new File(data_dir);\r
+ if(data.exists()) {\r
+ String trust_cas = trans.getProperty(CM_TRUST_CAS);\r
+ byte[] bytes;\r
+ if(trust_cas!=null) {\r
+ for(String fname : Split.split(';', trust_cas)) {\r
+ File crt = new File(data,fname);\r
+ if(crt.exists()) {\r
+ bytes = Factory.decode(crt);\r
+ try {\r
+ Collection<? extends Certificate> cc = Factory.toX509Certificate(bytes);\r
+ for(Certificate c : cc) {\r
+ trustCerts.add((X509Certificate)c);\r
+ }\r
+ } catch (CertificateException e) {\r
+ throw new CertException(e);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ String[] trustChain = new String[trustCerts.size()];\r
+ int i=-1;\r
+ for( Certificate cert : trustCerts) {\r
+ trustChain[++i]=BCFactory.toString(trans,cert);\r
+ }\r
+ \r
+ setTrustChain(trustChain);\r
+ } catch (ClientException | CertStoreException e) {\r
+ // Note: Cannot validly start without all Clients, because we need to read all Issuing Certificates\r
+ // This is acceptable risk for most things, as we're not real time in general\r
+ throw new CertException(e);\r
+ }\r
+ }\r
+\r
+\r
+ @Override\r
+ public X509Certificate sign(Trans trans, CSRMeta csrmeta) throws IOException, CertException {\r
+ TimeTaken tt = trans.start("Generating CSR and Keys for New Certificate", Env.SUB);\r
+ PKCS10CertificationRequest csr;\r
+ try {\r
+ csr = csrmeta.generateCSR(trans);\r
+ if(trans.info().isLoggable()) {\r
+ trans.info().log(BCFactory.toString(trans, csr));\r
+ } \r
+ if(trans.info().isLoggable()) {\r
+ trans.info().log(csr);\r
+ }\r
+ } finally {\r
+ tt.done();\r
+ }\r
+ \r
+ tt = trans.start("Enroll CSR", Env.SUB);\r
+ Client client = null;\r
+ try {\r
+ client = clients.best();\r
+ EnrollmentResponse er = client.enrol(\r
+ csrmeta.initialConversationCert(trans),\r
+ csrmeta.keypair(trans).getPrivate(),\r
+ csr,\r
+ MS_PROFILE /* profile... MS can't deal with blanks*/);\r
+ while(true) {\r
+ if(er.isSuccess()) {\r
+ for( Certificate cert : er.getCertStore().getCertificates(null)) {\r
+ return (X509Certificate)cert;\r
+ }\r
+ break;\r
+ } else if (er.isPending()) {\r
+ trans.checkpoint("Polling, waiting on CA to complete");\r
+ Thread.sleep(3000);\r
+ } else if (er.isFailure()) {\r
+ throw new CertException(er.getFailInfo().toString());\r
+ }\r
+ }\r
+ } catch (ClientException e) {\r
+ trans.error().log(e,"SCEP Client Error, Temporarily Invalidating Client");\r
+ if(client!=null) {\r
+ clients.invalidate(client);\r
+ }\r
+ } catch (InterruptedException|TransactionException|CertificateException|OperatorCreationException | CertStoreException e) {\r
+ trans.error().log(e);\r
+ } finally {\r
+ tt.done();\r
+ }\r
+ \r
+ return null;\r
+ }\r
+\r
+\r
+ private class Clients implements Iterable<Client>{\r
+ /**\r
+ * CSO Servers are in Dallas and St Louis\r
+ * GEO_LOCATION LATITUDE LONGITUDE ZIPCODE TIMEZONE\r
+ * ------------ -------- --------- ------- --------\r
+ * DLLSTXCF 32.779295 -96.800014 75202 America/Chicago\r
+ * STLSMORC 38.627345 -90.193774 63101 America/Chicago\r
+ * \r
+ * The online production issuing CA servers are:\r
+ * AAF - CADI Issuing CA 01 135.41.45.152 MOSTLS1AAFXXA02\r
+ * AAF - CADI Issuing CA 02 135.31.72.154 TXDLLS2AAFXXA02\r
+ */\r
+ \r
+ private final Client[] client;\r
+ private final Date[] failure;\r
+ private int preferred;\r
+\r
+ public Clients(Trans trans, String urlstr) throws MalformedURLException { \r
+ String[] urlstrs = Split.split(',', urlstr);\r
+ client = new Client[urlstrs.length];\r
+ failure = new Date[urlstrs.length];\r
+ double distance = Double.MAX_VALUE;\r
+ String localLat = trans.getProperty("AFT_LATITUDE","39.833333"); //Note: Defaulting to GEO center of US\r
+ String localLong = trans.getProperty("AFT_LONGITUDE","-98.583333");\r
+ for(int i=0;i<urlstrs.length;++i) {\r
+ String[] info = Split.split('/', urlstrs[i]);\r
+ if(info.length<3) {\r
+ throw new MalformedURLException("Configuration needs LAT and LONG, i.e. ip:port/lat/long");\r
+ }\r
+ client[i] = new Client(new URL(CA_PREFIX + info[0] + CA_POSTFIX), \r
+ new CertificateVerifier() {\r
+ @Override\r
+ public boolean verify(X509Certificate cert) {\r
+ return true;\r
+ }\r
+ }\r
+ );\r
+ double d = GreatCircle.calc(info[1],info[2],localLat,localLong);\r
+ if(d<distance) {\r
+ preferred = i;\r
+ distance=d;\r
+ }\r
+ }\r
+ trans.init().printf("Preferred Certificate Authority is %s",urlstrs[preferred]);\r
+ for(int i=0;i<urlstrs.length;++i) {\r
+ if(i!=preferred) {\r
+ trans.init().printf("Alternate Certificate Authority is %s",urlstrs[i]);\r
+ }\r
+ }\r
+ }\r
+ private Client best() throws ClientException {\r
+ if(failure[preferred]==null) {\r
+ return client[preferred];\r
+ } else {\r
+ Client c=null;\r
+ // See if Alternate available\r
+ for(int i=0;i<failure.length;++i) {\r
+ if(failure[i]==null) {\r
+ c=client[i];\r
+ }\r
+ }\r
+ \r
+ // If not, see if any expirations can be cleared\r
+ Date now = new Date();\r
+ for(int i=0;i<failure.length;++i) {\r
+ if(now.after(failure[i])) {\r
+ failure[i]=null;\r
+ if(c==null) {\r
+ c=client[i];\r
+ }\r
+ }\r
+ }\r
+ \r
+ // if still nothing found, then throw.\r
+ if(c==null) {\r
+ throw new ClientException("No available machines to call");\r
+ } \r
+ return c;\r
+ }\r
+ }\r
+ \r
+ public void invalidate(Client clt) {\r
+ for(int i=0;i<client.length;++i) {\r
+ if(client[i].equals(clt)) {\r
+ failure[i]=new Date(System.currentTimeMillis()+180000 /* 3 mins */);\r
+ }\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public Iterator<Client> iterator() {\r
+ return new Iterator<Client>() {\r
+ private int iter = 0;\r
+ @Override\r
+ public boolean hasNext() {\r
+ return iter < Clients.this.client.length;\r
+ }\r
+\r
+ @Override\r
+ public Client next() {\r
+ return Clients.this.client[iter++];\r
+ }\r
+ \r
+ };\r
+ }\r
+ }\r
+}\r