From 6bc6fa1cf11c9e20c3b962503d93570a05a341a1 Mon Sep 17 00:00:00 2001
From: Cyrus <24922493+cyrus-dev@users.noreply.github.com>
Date: Mon, 11 Sep 2023 16:03:01 -0400
Subject: [PATCH] Added code to check for multi pem single files

---
 .../entity/userdefined/Certificate.java       | 157 ++----------------
 .../CertificatePageController.java            |  25 +++
 2 files changed, 35 insertions(+), 147 deletions(-)

diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java
index e5d00fe9..fda9d4c8 100644
--- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java
+++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.google.common.base.Preconditions;
 import hirs.attestationca.persist.entity.ArchivableEntity;
 import hirs.attestationca.persist.entity.userdefined.certificate.CertificateVariables;
+import hirs.attestationca.persist.util.CredentialHelper;
 import hirs.utils.HexUtils;
 import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
@@ -72,7 +73,6 @@ import java.util.List;
 import java.util.ListIterator;
 import java.util.Objects;
 
-
 /**
  * This class enables the persistence of a single X509 certificates or X509 attribute certificate.
  * It stores certain attributes separately from the serialized certificate to enable querying on
@@ -312,16 +312,12 @@ public abstract class Certificate extends ArchivableEntity {
 
         // check for and handle possible PEM base 64 encoding
         String possiblePem = new String(certificateBytes, StandardCharsets.UTF_8);
-        if (isPEM(possiblePem)) {
-            possiblePem = possiblePem.replace(CertificateVariables.PEM_HEADER, "");
-            possiblePem = possiblePem.replace(CertificateVariables.PEM_FOOTER, "");
-            possiblePem = possiblePem.replace(CertificateVariables.PEM_ATTRIBUTE_HEADER, "");
-            possiblePem = possiblePem.replace(CertificateVariables.PEM_ATTRIBUTE_FOOTER, "");
-            this.certificateBytes = Base64.decode(possiblePem);
+        if (CredentialHelper.isPEM(possiblePem)) {
+            this.certificateBytes = CredentialHelper.stripPemHeaderFooter(possiblePem);
         }
 
         AuthorityKeyIdentifier authKeyIdentifier;
-        this.certificateBytes = trimCertificate(this.certificateBytes);
+        this.certificateBytes = CredentialHelper.trimCertificate(this.certificateBytes);
 
         // Extract certificate data
         switch (getCertificateType()) {
@@ -345,8 +341,8 @@ public abstract class Certificate extends ArchivableEntity {
                 this.beginValidity = x509Certificate.getNotBefore();
                 this.endValidity = x509Certificate.getNotAfter();
                 this.holderSerialNumber = BigInteger.ZERO;
-                this.issuerSorted = parseSortDNs(this.issuer);
-                this.subjectSorted = parseSortDNs(this.subject);
+                this.issuerSorted = CredentialHelper.parseSortDNs(this.issuer);
+                this.subjectSorted = CredentialHelper.parseSortDNs(this.subject);
                 this.policyConstraints = x509Certificate
                         .getExtensionValue(POLICY_CONSTRAINTS);
                 authKeyIdentifier = AuthorityKeyIdentifier
@@ -415,22 +411,12 @@ public abstract class Certificate extends ArchivableEntity {
                     case CertificateVariables.RSA512_256_OID:
                         this.signatureAlgorithm = CertificateVariables.RSA512_256_STRING;
                         break;
-                    case CertificateVariables.ECDSA_SHA1_OID:
-                        this.signatureAlgorithm = CertificateVariables.ECDSA_SHA1_STRING;
+                    case CertificateVariables.ECDSA_OID:
+                        this.signatureAlgorithm = CertificateVariables.ECDSA_STRING;
                         break;
                     case CertificateVariables.ECDSA_SHA224_OID:
                         this.signatureAlgorithm = CertificateVariables.ECDSA_SHA224_STRING;
                         break;
-                    case CertificateVariables.ECDSA_SHA256_OID:
-                        this.signatureAlgorithm = CertificateVariables.ECDSA_SHA256_STRING;
-                        break;
-                    case CertificateVariables.ECDSA_SHA384_OID:
-                        this.signatureAlgorithm = CertificateVariables.ECDSA_SHA384_STRING;
-                        break;
-                    case CertificateVariables.ECDSA_SHA512_OID:
-                        this.signatureAlgorithm = CertificateVariables.ECDSA_SHA512_STRING;
-                        break;
-
                     default:
                         break;
                 }
@@ -448,7 +434,7 @@ public abstract class Certificate extends ArchivableEntity {
                 this.signature = attCert.getSignatureValue().getBytes();
                 this.issuer = getAttributeCertificateIssuerNames(
                         attCertInfo.getIssuer())[0].toString();
-                this.issuerSorted = parseSortDNs(this.issuer);
+                this.issuerSorted = CredentialHelper.parseSortDNs(this.issuer);
 
                 // Parse notBefore and notAfter dates
                 this.beginValidity = recoverDate(attCertInfo
@@ -478,53 +464,6 @@ public abstract class Certificate extends ArchivableEntity {
         this.certAndTypeHash = Objects.hash(certificateHash, getClass().getSimpleName());
     }
 
-    @SuppressWarnings("magicnumber")
-    private byte[] trimCertificate(final byte[] certificateBytes) {
-        int certificateStart = 0;
-        int certificateLength = 0;
-        ByteBuffer certificateByteBuffer = ByteBuffer.wrap(certificateBytes);
-
-        StringBuilder malformedCertStringBuilder = new StringBuilder(CertificateVariables.MALFORMED_CERT_MESSAGE);
-        while (certificateByteBuffer.hasRemaining()) {
-            // Check if there isn't an ASN.1 structure in the provided bytes
-            if (certificateByteBuffer.remaining() <= 2) {
-                throw new IllegalArgumentException(malformedCertStringBuilder
-                        .append(" No certificate length field could be found.").toString());
-            }
-
-            // Look for first ASN.1 Sequence marked by the two bytes (0x30) and (0x82)
-            // The check advances our position in the ByteBuffer by one byte
-            int currentPosition = certificateByteBuffer.position();
-            if (certificateByteBuffer.get() == (byte) 0x30
-                    && certificateByteBuffer.get(currentPosition + 1) == (byte) 0x82) {
-                // Check if we have anything more in the buffer than an ASN.1 Sequence header
-                if (certificateByteBuffer.remaining() <= 3) {
-                    throw new IllegalArgumentException(malformedCertStringBuilder
-                            .append(" Certificate is nothing more than ASN.1 Sequence.")
-                            .toString());
-                }
-                // Mark the start of the first ASN.1 Sequence / Certificate Body
-                certificateStart = currentPosition;
-
-                // Parse the length as the 2-bytes following the start of the ASN.1 Sequence
-                certificateLength = Short.toUnsignedInt(
-                        certificateByteBuffer.getShort(currentPosition + 2));
-                // Add the 4 bytes that comprise the start of the ASN.1 Sequence and the length
-                certificateLength += 4;
-                break;
-            }
-        }
-
-        if (certificateStart + certificateLength > certificateBytes.length) {
-            throw new IllegalArgumentException(malformedCertStringBuilder
-                    .append(" Value of certificate length field extends beyond length")
-                    .append(" of provided certificate.").toString());
-        }
-        // Return bytes representing the main certificate body
-        return Arrays.copyOfRange(certificateBytes, certificateStart,
-                certificateStart + certificateLength);
-    }
-
     /**
      * Getter for the CRL Distribution that is reference by the Revocation Locator
      * on the portal.
@@ -684,10 +623,6 @@ public abstract class Certificate extends ArchivableEntity {
         return CertificateType.INVALID_CERTIFICATE;
     }
 
-    private boolean isPEM(final String possiblePEM) {
-        return possiblePEM.contains(CertificateVariables.PEM_HEADER)
-                || possiblePEM.contains(CertificateVariables.PEM_ATTRIBUTE_HEADER);
-    }
 
     private String parseKeyUsage(final boolean[] bools) {
         StringBuilder sb = new StringBuilder();
@@ -695,7 +630,7 @@ public abstract class Certificate extends ArchivableEntity {
         if (bools != null) {
             for (int i = 0; i < bools.length; i++) {
                 if (bools[i]) {
-                    sb.append(getKeyUsageString(i));
+                    sb.append(CredentialHelper.getKeyUsageString(i));
                 }
             }
         }
@@ -703,49 +638,6 @@ public abstract class Certificate extends ArchivableEntity {
         return sb.toString();
     }
 
-    /**
-     * Return the string associated with the boolean slot.
-     * @param bit associated with the location in the array.
-     * @return string value of the bit set.
-     */
-    private String getKeyUsageString(final int bit) {
-        String tempStr = "";
-
-        switch (bit) {
-            case CertificateVariables.KEY_USAGE_BIT0:
-                tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_DS);
-                break;
-            case CertificateVariables.KEY_USAGE_BIT1:
-                tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_NR);
-                break;
-            case CertificateVariables.KEY_USAGE_BIT2:
-                tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_KE);
-                break;
-            case CertificateVariables.KEY_USAGE_BIT3:
-                tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_DE);
-                break;
-            case CertificateVariables.KEY_USAGE_BIT4:
-                tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_KA);
-                break;
-            case CertificateVariables.KEY_USAGE_BIT5:
-                tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_KC);
-                break;
-            case CertificateVariables.KEY_USAGE_BIT6:
-                tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_CS);
-                break;
-            case CertificateVariables.KEY_USAGE_BIT7:
-                tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_EO);
-                break;
-            case CertificateVariables.KEY_USAGE_BIT8:
-                tempStr = String.format("%s%n", CertificateVariables.KEY_USAGE_DO);
-                break;
-            default:
-                break;
-        }
-
-        return tempStr;
-    }
-
     /**
      * Getter for the authorityKeyIdentifier.
      * @return the ID's byte representation
@@ -836,35 +728,6 @@ public abstract class Certificate extends ArchivableEntity {
         return sb.toString();
     }
 
-    /**
-     * This method is to take the DNs from certificates and sort them in an order
-     * that will be used to lookup issuer certificates.  This will not be stored in
-     * the certificate, just the DB for lookup.
-     * @param distinguishedName the original DN string.
-     * @return a modified string of sorted DNs
-     */
-    public static String parseSortDNs(final String distinguishedName) {
-        StringBuilder sb = new StringBuilder();
-        String dnsString;
-
-        if (distinguishedName == null || distinguishedName.isEmpty()) {
-            sb.append("BLANK");
-        } else {
-            dnsString = distinguishedName.trim();
-            dnsString = dnsString.toLowerCase();
-            List<String> dnValArray = Arrays.asList(dnsString.split(","));
-            Collections.sort(dnValArray);
-            ListIterator<String> dnListIter = dnValArray.listIterator();
-            while (dnListIter.hasNext()) {
-                sb.append(dnListIter.next());
-                if (dnListIter.hasNext()) {
-                    sb.append(",");
-                }
-            }
-        }
-
-        return sb.toString();
-    }
 
     /**
      * Retrieve the original X509 attribute certificate.
diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java
index 674d6c7f..82af7b67 100644
--- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java
+++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java
@@ -14,6 +14,7 @@ import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuth
 import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential;
 import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate;
 import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential;
+import hirs.attestationca.persist.util.CredentialHelper;
 import hirs.attestationca.portal.datatables.DataTableInput;
 import hirs.attestationca.portal.datatables.DataTableResponse;
 import hirs.attestationca.portal.datatables.OrderedListQueryDataTableAdapter;
@@ -48,12 +49,18 @@ import org.springframework.web.servlet.ModelAndView;
 import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 import org.springframework.web.servlet.view.RedirectView;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.lang.ref.Reference;
 import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
 import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -787,6 +794,24 @@ public class CertificatePageController extends PageController<NoPageParams> {
                 case ENDORSEMENTCREDENTIAL:
                     return new EndorsementCredential(fileBytes);
                 case TRUSTCHAIN:
+                    if (CredentialHelper.isMultiPEM(new String(fileBytes, StandardCharsets.UTF_8))) {
+                        try (ByteArrayInputStream certInputStream = new ByteArrayInputStream(fileBytes)) {
+                            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                            Collection c = cf.generateCertificates(certInputStream);
+                            Iterator i = c.iterator();
+                            while (i.hasNext()) {
+                                storeCertificate(
+                                        certificateType,
+                                        file.getOriginalFilename(),
+                                        messages, (Certificate)i.next());
+                            }
+
+                            // stop the main thread from saving/storing
+                            return null;
+                        } catch (CertificateException e) {
+                            throw new IOException("Cannot construct X509Certificate from the input stream", e);
+                        }
+                    }
                     return new CertificateAuthorityCredential(fileBytes);
                 default:
                     final String failMessage = String.format("Failed to parse uploaded file "