[AAF-21] Initial code import
[aaf/authz.git] / authz-certman / src / main / java / com / att / authz / cm / ca / AppCA.java
diff --git a/authz-certman/src/main/java/com/att/authz/cm/ca/AppCA.java b/authz-certman/src/main/java/com/att/authz/cm/ca/AppCA.java
new file mode 100644 (file)
index 0000000..761867d
--- /dev/null
@@ -0,0 +1,357 @@
+/*******************************************************************************\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