2 * ============LICENSE_START====================================================
4 * ===========================================================================
5 * Copyright (c) 2023 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
11 * http://www.apache.org/licenses/LICENSE-2.0
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====================================================
22 package org.onap.ccsdk.apps.cadi.taf.cert;
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;
34 import javax.net.ssl.TrustManagerFactory;
35 import jakarta.servlet.http.HttpServletRequest;
36 import jakarta.servlet.http.HttpServletResponse;
38 import org.onap.ccsdk.apps.cadi.Access;
39 import org.onap.ccsdk.apps.cadi.Access.Level;
40 import org.onap.ccsdk.apps.cadi.CachedPrincipal;
41 import org.onap.ccsdk.apps.cadi.CachedPrincipal.Resp;
42 import org.onap.ccsdk.apps.cadi.CadiException;
43 import org.onap.ccsdk.apps.cadi.CredVal;
44 import org.onap.ccsdk.apps.cadi.Lur;
45 import org.onap.ccsdk.apps.cadi.Symm;
46 import org.onap.ccsdk.apps.cadi.Taf.LifeForm;
47 import org.onap.ccsdk.apps.cadi.config.Config;
48 import org.onap.ccsdk.apps.cadi.config.SecurityInfo;
49 import org.onap.ccsdk.apps.cadi.config.SecurityInfoC;
50 import org.onap.ccsdk.apps.cadi.principal.TaggedPrincipal;
51 import org.onap.ccsdk.apps.cadi.principal.X509Principal;
52 import org.onap.ccsdk.apps.cadi.taf.HttpTaf;
53 import org.onap.ccsdk.apps.cadi.taf.TafResp;
54 import org.onap.ccsdk.apps.cadi.taf.TafResp.RESP;
55 import org.onap.ccsdk.apps.cadi.taf.basic.BasicHttpTaf;
56 import org.onap.ccsdk.apps.cadi.util.Split;
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;
66 private ArrayList<String> cadiIssuers;
68 private SecurityInfo si;
69 private BasicHttpTaf bht;
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);
81 public X509Taf(Access access, Lur lur, CertIdentity ... cis) throws CertificateException, NoSuchAlgorithmException, CadiException {
83 env = access.getProperty(Config.AAF_ENV,null);
85 throw new CadiException("X509Taf requires Environment ("+Config.AAF_ENV+") to be set.");
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);
94 Class<?> dci = access.classLoader().loadClass("org.onap.ccsdk.apps.auth.direct.DirectCertIdentity");
98 CertIdentity temp[] = new CertIdentity[cis.length+1];
99 System.arraycopy(cis, 0, temp, 1, cis.length);
100 temp[0] = (CertIdentity) dci.newInstance();
103 } catch (Exception e) {
107 si = new SecurityInfo(access);
110 public static final X509Certificate getCert(byte[] certBytes) throws CertificateException {
111 ByteArrayInputStream bais = new ByteArrayInputStream(certBytes);
112 return (X509Certificate)certFactory.generateCertificate(bais);
115 public static final byte[] getFingerPrint(byte[] ba) {
118 md = (MessageDigest)messageDigest.clone();
119 } catch (CloneNotSupportedException e) {
120 // should never get here
128 public TafResp validate(LifeForm reading, HttpServletRequest req, HttpServletResponse resp) {
129 // Check for Mutual SSL
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
143 int length = subject.length();
146 while(start<length) {
147 while(Character.isWhitespace(subject.charAt(start))) {
152 comma = subject.indexOf(',',start);
154 end = subject.length();
156 end = comma<=0?0:comma-1;
158 while(Character.isWhitespace(subject.charAt(end))) {
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);
173 access.printf(Level.DEBUG,"Certificate is not for environment '%s'",env);
180 access.log(Level.DEBUG,"Certificate is not acceptable for Authentication");
182 access.log(Level.DEBUG,"Issuer is not trusted for Authentication");
185 access.log(Level.DEBUG,"There is no client certificate on the transaction");
190 byte[] certBytes = null;
191 X509Certificate cert=null;
192 String responseText=null;
193 String authHeader = req.getHeader("Authorization");
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);
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());
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);
218 * Identity from CERT if well know CA and specific encoded information
220 // If found Identity doesn't work, try SignedStuff Protocol
221 // cert.checkValidity();
222 // cert.--- GET FINGERPRINT?
223 String stuff = req.getHeader("Signature");
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");
228 // return new X509HttpTafResp(access, null, "No signed Data to validate with X509 Certificate", RESP.TRY_ANOTHER_TAF);
230 // Note: Data Pos shows is "<signatureType> <data>"
231 // int dataPos = (stuff.indexOf(' ')); // determine what is Algorithm
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
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);
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);
255 return new X509HttpTafResp(access, null, "No Certificate Info on Transaction", RESP.TRY_ANOTHER_TAF);
258 // A cert has been found, match Identify
259 TaggedPrincipal prin=null;
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;
267 // if Principal is found, check for "AS_USER" and whether this entity is trusted to declare
269 // Note: Tag for Certs is Fingerprint, but that takes computation... leaving off
270 return new X509HttpTafResp(
274 RESP.IS_AUTHENTICATED);
277 } catch (Exception e) {
278 return new X509HttpTafResp(access, null, e.getMessage(), RESP.TRY_ANOTHER_TAF);
281 return new X509HttpTafResp(access, null, "Certificate cannot be used for authentication", RESP.TRY_ANOTHER_TAF);
285 public Resp revalidate(CachedPrincipal prin, Object state) {
289 public void add(BasicHttpTaf bht) {
293 public CredVal getCredVal(final String key) {
297 return bht.getCredVal(key);