diff options
Diffstat (limited to 'cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/taf/cert/X509Taf.java')
-rw-r--r-- | cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/taf/cert/X509Taf.java | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/taf/cert/X509Taf.java b/cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/taf/cert/X509Taf.java new file mode 100644 index 00000000..c854523e --- /dev/null +++ b/cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/taf/cert/X509Taf.java @@ -0,0 +1,300 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.ccsdk + * =========================================================================== + * Copyright (c) 2023 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.ccsdk.apps.cadi.taf.cert; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; + +import javax.net.ssl.TrustManagerFactory; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.onap.ccsdk.apps.cadi.Access; +import org.onap.ccsdk.apps.cadi.Access.Level; +import org.onap.ccsdk.apps.cadi.CachedPrincipal; +import org.onap.ccsdk.apps.cadi.CachedPrincipal.Resp; +import org.onap.ccsdk.apps.cadi.CadiException; +import org.onap.ccsdk.apps.cadi.CredVal; +import org.onap.ccsdk.apps.cadi.Lur; +import org.onap.ccsdk.apps.cadi.Symm; +import org.onap.ccsdk.apps.cadi.Taf.LifeForm; +import org.onap.ccsdk.apps.cadi.config.Config; +import org.onap.ccsdk.apps.cadi.config.SecurityInfo; +import org.onap.ccsdk.apps.cadi.config.SecurityInfoC; +import org.onap.ccsdk.apps.cadi.principal.TaggedPrincipal; +import org.onap.ccsdk.apps.cadi.principal.X509Principal; +import org.onap.ccsdk.apps.cadi.taf.HttpTaf; +import org.onap.ccsdk.apps.cadi.taf.TafResp; +import org.onap.ccsdk.apps.cadi.taf.TafResp.RESP; +import org.onap.ccsdk.apps.cadi.taf.basic.BasicHttpTaf; +import org.onap.ccsdk.apps.cadi.util.Split; + +public class X509Taf implements HttpTaf { + private static final String CERTIFICATE_NOT_VALID_FOR_AUTHENTICATION = "Certificate NOT valid for Authentication"; + public static final CertificateFactory certFactory; + public static final MessageDigest messageDigest; + public static final TrustManagerFactory tmf; + private Access access; + private CertIdentity[] certIdents; +// private Lur lur; + private ArrayList<String> cadiIssuers; + private String env; + private SecurityInfo si; + private BasicHttpTaf bht; + + static { + try { + certFactory = CertificateFactory.getInstance("X.509"); + messageDigest = MessageDigest.getInstance("SHA-256"); // use this to clone + tmf = TrustManagerFactory.getInstance(SecurityInfoC.SSL_KEY_MANAGER_FACTORY_ALGORITHM); + } catch (Exception e) { + throw new RuntimeException("X.509 and SHA-256 are required for X509Taf",e); + } + } + + public X509Taf(Access access, Lur lur, CertIdentity ... cis) throws CertificateException, NoSuchAlgorithmException, CadiException { + this.access = access; + env = access.getProperty(Config.AAF_ENV,null); + if (env==null) { + throw new CadiException("X509Taf requires Environment ("+Config.AAF_ENV+") to be set."); + } +// this.lur = lur; + this.cadiIssuers = new ArrayList<>(); + for (String ci : access.getProperty(Config.CADI_X509_ISSUERS, "").split(":")) { + access.printf(Level.INIT, "Trusting Identity for Certificates signed by \"%s\"",ci); + cadiIssuers.add(ci); + } + try { + Class<?> dci = access.classLoader().loadClass("org.onap.ccsdk.apps.auth.direct.DirectCertIdentity"); + if (dci==null) { + certIdents = cis; + } else { + CertIdentity temp[] = new CertIdentity[cis.length+1]; + System.arraycopy(cis, 0, temp, 1, cis.length); + temp[0] = (CertIdentity) dci.newInstance(); + certIdents=temp; + } + } catch (Exception e) { + certIdents = cis; + } + + si = new SecurityInfo(access); + } + + public static final X509Certificate getCert(byte[] certBytes) throws CertificateException { + ByteArrayInputStream bais = new ByteArrayInputStream(certBytes); + return (X509Certificate)certFactory.generateCertificate(bais); + } + + public static final byte[] getFingerPrint(byte[] ba) { + MessageDigest md; + try { + md = (MessageDigest)messageDigest.clone(); + } catch (CloneNotSupportedException e) { + // should never get here + return new byte[0]; + } + md.update(ba); + return md.digest(); + } + + @Override + public TafResp validate(LifeForm reading, HttpServletRequest req, HttpServletResponse resp) { + // Check for Mutual SSL + try { + X509Certificate[] certarr = (X509Certificate[])req.getAttribute("javax.servlet.request.X509Certificate"); + if (certarr!=null && certarr.length>0) { + si.checkClientTrusted(certarr); + // Note: If the Issuer is not in the TrustStore, it's not added to the Cert list + String issuer = certarr[0].getIssuerDN().toString(); + String subject = certarr[0].getSubjectDN().getName(); + access.printf(Level.DEBUG,"Client Certificate found\n Subject '%s'\n Issuer '%s'",subject,issuer); + if (cadiIssuers.contains(issuer)) { + // avoiding extra object creation, since this is validated EVERY transaction with a Cert + int start = 0; + int end = 1; + int comma; + int length = subject.length(); + + compare: + while(start<length) { + while(Character.isWhitespace(subject.charAt(start))) { + if(++start>length) { + break compare; + } + } + comma = subject.indexOf(',',start); + if(comma<0) { + end = subject.length(); + } else { + end = comma<=0?0:comma-1; + } + while(Character.isWhitespace(subject.charAt(end))) { + if(--end < 0) { + break compare; + } + } + if(subject.regionMatches(start, "OU=", 0, 3) || + subject.regionMatches(start, "CN=", 0, 3)) { + int at = subject.indexOf('@', start); + if(at<end && at>=0) { + String[] sa = Split.splitTrim(':', subject, start+3,end+1); + if (sa.length==1 || (sa.length>1 && env!=null && env.equals(sa[1]))) { // Check Environment + return new X509HttpTafResp(access, + new X509Principal(sa[0], certarr[0],(byte[])null,bht), + "X509Taf validated " + sa[0] + (sa.length<2?"":" for aaf_env " + env ), RESP.IS_AUTHENTICATED); + } else { + access.printf(Level.DEBUG,"Certificate is not for environment '%s'",env); + break; + } + } + } + start = comma+1; + } + access.log(Level.DEBUG,"Certificate is not acceptable for Authentication"); + } else { + access.log(Level.DEBUG,"Issuer is not trusted for Authentication"); + } + } else { + access.log(Level.DEBUG,"There is no client certificate on the transaction"); + } + + + byte[] array = null; + byte[] certBytes = null; + X509Certificate cert=null; + String responseText=null; + String authHeader = req.getHeader("Authorization"); + + if (certarr!=null) { // If cert !=null, Cert is Tested by Mutual Protocol. + if (authHeader!=null) { // This is only intended to be a Secure Connection, not an Identity + for (String auth : Split.split(',',authHeader)) { + if (auth.startsWith("Bearer ")) { // Bearer = OAuth... Don't use as Authenication + return new X509HttpTafResp(access, null, "Certificate verified, but Bearer Token is presented", RESP.TRY_ANOTHER_TAF); + } + } + } + cert = certarr[0]; + responseText = ", validated by Mutual SSL Protocol"; + } else { // If cert == null, Get Declared Cert (in header), but validate by having them sign something + if (authHeader != null) { + for (String auth : Split.splitTrim(',',authHeader)) { + if (auth.startsWith("x509 ")) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(auth.length()); + try { + array = auth.getBytes(); + ByteArrayInputStream bais = new ByteArrayInputStream(array); + Symm.base64noSplit.decode(bais, baos, 5); + certBytes = baos.toByteArray(); + cert = getCert(certBytes); + + /** + * Identity from CERT if well know CA and specific encoded information + */ + // If found Identity doesn't work, try SignedStuff Protocol + // cert.checkValidity(); + // cert.--- GET FINGERPRINT? + String stuff = req.getHeader("Signature"); + if (stuff==null) + return new X509HttpTafResp(access, null, "Header entry 'Signature' required to validate One way X509 Certificate", RESP.TRY_ANOTHER_TAF); + String data = req.getHeader("Data"); + // if (data==null) + // return new X509HttpTafResp(access, null, "No signed Data to validate with X509 Certificate", RESP.TRY_ANOTHER_TAF); + + // Note: Data Pos shows is "<signatureType> <data>" + // int dataPos = (stuff.indexOf(' ')); // determine what is Algorithm + // Get Signature + bais = new ByteArrayInputStream(stuff.getBytes()); + baos = new ByteArrayOutputStream(stuff.length()); + Symm.base64noSplit.decode(bais, baos); + array = baos.toByteArray(); + // Signature sig = Signature.getInstance(stuff.substring(0, dataPos)); // get Algorithm from first part of Signature + + Signature sig = Signature.getInstance(cert.getSigAlgName()); + sig.initVerify(cert.getPublicKey()); + sig.update(data.getBytes()); + if (!sig.verify(array)) { + access.log(Level.ERROR, "Signature doesn't Match"); + return new X509HttpTafResp(access, null, CERTIFICATE_NOT_VALID_FOR_AUTHENTICATION, RESP.TRY_ANOTHER_TAF); + } + responseText = ", validated by Signed Data"; + } catch (Exception e) { + access.log(e, "Exception while validating Cert"); + return new X509HttpTafResp(access, null, CERTIFICATE_NOT_VALID_FOR_AUTHENTICATION, RESP.TRY_ANOTHER_TAF); + } + } + } + } + if (cert==null) { + return new X509HttpTafResp(access, null, "No Certificate Info on Transaction", RESP.TRY_ANOTHER_TAF); + } + + // A cert has been found, match Identify + TaggedPrincipal prin=null; + + for (int i=0;prin==null && i<certIdents.length;++i) { + if ((prin=certIdents[i].identity(req, cert, certBytes))!=null) { + responseText = prin.getName() + " matches Certificate " + cert.getSubjectX500Principal().getName() + responseText; + } + } + + // if Principal is found, check for "AS_USER" and whether this entity is trusted to declare + if (prin!=null) { + // Note: Tag for Certs is Fingerprint, but that takes computation... leaving off + return new X509HttpTafResp( + access, + prin, + responseText, + RESP.IS_AUTHENTICATED); + } + } + } catch (Exception e) { + return new X509HttpTafResp(access, null, e.getMessage(), RESP.TRY_ANOTHER_TAF); + } + + return new X509HttpTafResp(access, null, "Certificate cannot be used for authentication", RESP.TRY_ANOTHER_TAF); + } + + @Override + public Resp revalidate(CachedPrincipal prin, Object state) { + return null; + } + + public void add(BasicHttpTaf bht) { + this.bht = bht; + } + + public CredVal getCredVal(final String key) { + if (bht==null) { + return null; + } else { + return bht.getCredVal(key); + } + } +} |