677f2139e044fb1feab3910932be815846cd674d
[aaf/authz.git] / cadi / core / src / main / java / org / onap / aaf / cadi / taf / cert / X509Taf.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.cadi.taf.cert;
23
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.security.MessageDigest;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.Signature;
29 import java.security.cert.CertificateException;
30 import java.security.cert.CertificateFactory;
31 import java.security.cert.X509Certificate;
32 import java.util.ArrayList;
33
34 import javax.net.ssl.TrustManagerFactory;
35 import javax.servlet.http.HttpServletRequest;
36 import javax.servlet.http.HttpServletResponse;
37
38 import org.onap.aaf.cadi.Access;
39 import org.onap.aaf.cadi.Access.Level;
40 import org.onap.aaf.cadi.CachedPrincipal;
41 import org.onap.aaf.cadi.CachedPrincipal.Resp;
42 import org.onap.aaf.cadi.CadiException;
43 import org.onap.aaf.cadi.CredVal;
44 import org.onap.aaf.cadi.Lur;
45 import org.onap.aaf.cadi.Symm;
46 import org.onap.aaf.cadi.Taf.LifeForm;
47 import org.onap.aaf.cadi.config.Config;
48 import org.onap.aaf.cadi.config.SecurityInfo;
49 import org.onap.aaf.cadi.config.SecurityInfoC;
50 import org.onap.aaf.cadi.principal.TaggedPrincipal;
51 import org.onap.aaf.cadi.principal.X509Principal;
52 import org.onap.aaf.cadi.taf.HttpTaf;
53 import org.onap.aaf.cadi.taf.TafResp;
54 import org.onap.aaf.cadi.taf.TafResp.RESP;
55 import org.onap.aaf.cadi.taf.basic.BasicHttpTaf;
56 import org.onap.aaf.cadi.util.Split;
57
58 public class X509Taf implements HttpTaf {
59     private static final String CERTIFICATE_NOT_VALID_FOR_AUTHENTICATION = "Certificate NOT valid for Authentication";
60     public static final CertificateFactory certFactory;
61     public static final MessageDigest messageDigest;
62     public static final TrustManagerFactory tmf;
63     private Access access;
64     private CertIdentity[] certIdents;
65 //    private Lur lur;
66     private ArrayList<String> cadiIssuers;
67     private String env;
68     private SecurityInfo si;
69     private BasicHttpTaf bht;
70
71     static {
72         try {
73             certFactory = CertificateFactory.getInstance("X.509");
74             messageDigest = MessageDigest.getInstance("SHA-256"); // use this to clone
75             tmf = TrustManagerFactory.getInstance(SecurityInfoC.SSL_KEY_MANAGER_FACTORY_ALGORITHM);
76         } catch (Exception e) {
77             throw new RuntimeException("X.509 and SHA-256 are required for X509Taf",e);
78         }
79     }
80     
81     public X509Taf(Access access, Lur lur, CertIdentity ... cis) throws CertificateException, NoSuchAlgorithmException, CadiException {
82         this.access = access;
83         env = access.getProperty(Config.AAF_ENV,null);
84         if (env==null) {
85             throw new CadiException("X509Taf requires Environment ("+Config.AAF_ENV+") to be set.");
86         }
87 //        this.lur = lur;
88         this.cadiIssuers = new ArrayList<>();
89         for (String ci : access.getProperty(Config.CADI_X509_ISSUERS, "").split(":")) {
90             access.printf(Level.INIT, "Trusting Identity for Certificates signed by \"%s\"",ci);
91             cadiIssuers.add(ci);
92         }
93         try {
94             Class<?> dci = access.classLoader().loadClass("org.onap.aaf.auth.direct.DirectCertIdentity");
95             if (dci==null) {
96                 certIdents = cis;
97             } else {
98                 CertIdentity temp[] = new CertIdentity[cis.length+1];
99                 System.arraycopy(cis, 0, temp, 1, cis.length);
100                 temp[0] = (CertIdentity) dci.newInstance();
101                 certIdents=temp;
102             }
103         } catch (Exception e) {
104             certIdents = cis;
105         }
106         
107         si = new SecurityInfo(access);
108     }
109
110     public static final X509Certificate getCert(byte[] certBytes) throws CertificateException {
111         ByteArrayInputStream bais = new ByteArrayInputStream(certBytes);
112         return (X509Certificate)certFactory.generateCertificate(bais);
113     }
114
115     public static final byte[] getFingerPrint(byte[] ba) {
116         MessageDigest md;
117         try {
118             md = (MessageDigest)messageDigest.clone();
119         } catch (CloneNotSupportedException e) {
120             // should never get here
121             return new byte[0];
122         }
123         md.update(ba);
124         return md.digest();
125     }
126
127     @Override
128     public TafResp validate(LifeForm reading, HttpServletRequest req, HttpServletResponse resp) {
129         // Check for Mutual SSL
130         try {
131             X509Certificate[] certarr = (X509Certificate[])req.getAttribute("javax.servlet.request.X509Certificate");
132             if (certarr!=null && certarr.length>0) {
133                 si.checkClientTrusted(certarr);
134                 // Note: If the Issuer is not in the TrustStore, it's not added to the Cert list
135                 String issuer = certarr[0].getIssuerDN().toString();
136                 String subject = certarr[0].getSubjectDN().getName();
137                 access.printf(Level.DEBUG,"Client Certificate found\n  Subject %s\n  Issuer  %s",subject,issuer);
138                 if (cadiIssuers.contains(issuer)) {
139                     // avoiding extra object creation, since this is validated EVERY transaction with a Cert
140                         int start = 0;
141                         int end = 1;
142                         int comma;
143                         int length = subject.length();
144                         
145                         compare:
146                         while(start<length) {
147                                 while(Character.isWhitespace(subject.charAt(start))) {
148                                         if(++start>length) {
149                                                 break compare;
150                                         }
151                                 }
152                         comma = subject.indexOf(',',start);
153                         if(comma<0) {
154                                 end = subject.length();
155                         } else {
156                                 end = comma<=0?0:comma-1;
157                         }
158                                 while(Character.isWhitespace(subject.charAt(end))) {
159                                         if(--end < 0) {
160                                                 break compare;
161                                         }
162                                 }
163                         if(subject.regionMatches(start, "OU=", 0, 3) ||
164                            subject.regionMatches(start, "CN=", 0, 3)) {
165                            int at = subject.indexOf('@', start);
166                            if(at<end && at>=0) {
167                                String[] sa = Split.splitTrim(':', subject, start+3,end+1);
168                                if (sa.length==1 || (sa.length>1 && env!=null && env.equals(sa[1]))) { // Check Environment 
169                                    return new X509HttpTafResp(access, 
170                                            new X509Principal(sa[0], certarr[0],(byte[])null,bht), 
171                                            "X509Taf validated " + sa[0] + (sa.length<2?"":" for aaf_env " + env ), RESP.IS_AUTHENTICATED);
172                                } else {
173                                   access.printf(Level.DEBUG,"Certificate is not for environment '%s'",env);
174                                   break;
175                                }
176                            }
177                         }
178                         start = comma+1;
179                         }
180                         access.log(Level.DEBUG,"Certificate is not acceptable for Authentication");
181                 } else {
182                         access.log(Level.DEBUG,"Issuer is not trusted for Authentication");
183                 }
184             } else {
185                 access.log(Level.DEBUG,"There is no client certificate on the transaction");
186             }
187         
188
189             byte[] array = null;
190             byte[] certBytes = null;
191             X509Certificate cert=null;
192             String responseText=null;
193             String authHeader = req.getHeader("Authorization");
194
195             if (certarr!=null) {  // If cert !=null, Cert is Tested by Mutual Protocol.
196                 if (authHeader!=null) { // This is only intended to be a Secure Connection, not an Identity
197                     for (String auth : Split.split(',',authHeader)) {
198                         if (auth.startsWith("Bearer ")) { // Bearer = OAuth... Don't use as Authenication
199                             return new X509HttpTafResp(access, null, "Certificate verified, but Bearer Token is presented", RESP.TRY_ANOTHER_TAF);
200                         }
201                     }
202                 }
203                 cert = certarr[0];
204                 responseText = ", validated by Mutual SSL Protocol";
205             } else {         // If cert == null, Get Declared Cert (in header), but validate by having them sign something
206                 if (authHeader != null) {
207                     for (String auth : Split.splitTrim(',',authHeader)) {
208                         if (auth.startsWith("x509 ")) {
209                             ByteArrayOutputStream baos = new ByteArrayOutputStream(auth.length());
210                             try {
211                                 array = auth.getBytes();
212                                 ByteArrayInputStream bais = new ByteArrayInputStream(array);
213                                 Symm.base64noSplit.decode(bais, baos, 5);
214                                 certBytes = baos.toByteArray();
215                                 cert = getCert(certBytes);
216                                 
217                                 /** 
218                                  * Identity from CERT if well know CA and specific encoded information
219                                  */
220                                 // If found Identity doesn't work, try SignedStuff Protocol
221         //                                    cert.checkValidity();
222         //                                    cert.--- GET FINGERPRINT?
223                                 String stuff = req.getHeader("Signature");
224                                 if (stuff==null) 
225                                     return new X509HttpTafResp(access, null, "Header entry 'Signature' required to validate One way X509 Certificate", RESP.TRY_ANOTHER_TAF);
226                                 String data = req.getHeader("Data"); 
227         //                                    if (data==null) 
228         //                                        return new X509HttpTafResp(access, null, "No signed Data to validate with X509 Certificate", RESP.TRY_ANOTHER_TAF);
229         
230                                 // Note: Data Pos shows is "<signatureType> <data>"
231         //                                    int dataPos = (stuff.indexOf(' ')); // determine what is Algorithm
232                                 // Get Signature 
233                                 bais = new ByteArrayInputStream(stuff.getBytes());
234                                 baos = new ByteArrayOutputStream(stuff.length());
235                                 Symm.base64noSplit.decode(bais, baos);
236                                 array = baos.toByteArray();
237         //                                    Signature sig = Signature.getInstance(stuff.substring(0, dataPos)); // get Algorithm from first part of Signature
238                                 
239                                 Signature sig = Signature.getInstance(cert.getSigAlgName()); 
240                                 sig.initVerify(cert.getPublicKey());
241                                 sig.update(data.getBytes());
242                                 if (!sig.verify(array)) {
243                                     access.log(Level.ERROR, "Signature doesn't Match");
244                                     return new X509HttpTafResp(access, null, CERTIFICATE_NOT_VALID_FOR_AUTHENTICATION, RESP.TRY_ANOTHER_TAF);
245                                 }
246                                 responseText = ", validated by Signed Data";
247                             } catch (Exception e) {
248                                 access.log(e, "Exception while validating Cert");
249                                 return new X509HttpTafResp(access, null, CERTIFICATE_NOT_VALID_FOR_AUTHENTICATION, RESP.TRY_ANOTHER_TAF);
250                             }
251                         }
252                     }
253                 }
254                 if (cert==null) {
255                     return new X509HttpTafResp(access, null, "No Certificate Info on Transaction", RESP.TRY_ANOTHER_TAF);
256                 }
257                 
258                 // A cert has been found, match Identify
259                 TaggedPrincipal prin=null;
260                 
261                 for (int i=0;prin==null && i<certIdents.length;++i) {
262                     if ((prin=certIdents[i].identity(req, cert, certBytes))!=null) {
263                         responseText = prin.getName() + " matches Certificate " + cert.getSubjectX500Principal().getName() + responseText;
264                     }
265                 }
266     
267                 // if Principal is found, check for "AS_USER" and whether this entity is trusted to declare
268                 if (prin!=null) {
269                     return new X509HttpTafResp(
270                         access,
271                         prin,
272                         responseText,
273                         RESP.IS_AUTHENTICATED);
274                 }
275             }
276         } catch (Exception e) {
277             return new X509HttpTafResp(access, null, e.getMessage(), RESP.TRY_ANOTHER_TAF);    
278         }
279     
280         return new X509HttpTafResp(access, null, "Certificate cannot be used for authentication", RESP.TRY_ANOTHER_TAF);
281     }
282
283     @Override
284     public Resp revalidate(CachedPrincipal prin, Object state) {
285         return null;
286     }
287
288     public void add(BasicHttpTaf bht) {
289         this.bht = bht;
290     }
291     
292     public CredVal getCredVal(final String key) {
293         if (bht==null) {
294             return null;
295         } else {
296             return bht.getCredVal(key);
297         }
298     }
299 }