code: /*
   * Copyright (c) 2019 the Eclipse Milo Authors
   *
   * This program and the accompanying materials are made
   * available under the terms of the Eclipse Public License 2.0
   * which is available at https://www.eclipse.org/legal/epl-2.0/
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  
  package org.eclipse.milo.opcua.stack.core.util.validation;
  
  import java.security.cert.CertPath;
  import java.security.cert.CertPathValidatorException;
  import java.security.cert.Certificate;
  import java.security.cert.PKIXCertPathChecker;
  import java.security.cert.PKIXReason;
  import java.security.cert.X509Certificate;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Set;
  
  import org.eclipse.milo.opcua.stack.core.UaException;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import static com.google.common.collect.Sets.newHashSet;
  
  class OpcUaCertificateUsageChecker extends PKIXCertPathChecker {
  
      private static final Logger LOGGER =
          LoggerFactory.getLogger(OpcUaCertificateUsageChecker.class);
  
      private static final String KEY_USAGE_OID = "2.5.29.15";
      private static final String EXTENDED_KEY_USAGE_OID = "2.5.29.37";
  
      private final X509Certificate endEntityCert;
  
      private final CertPath certPath;
      private final Set<ValidationCheck> validationChecks;
      private final boolean endEntityIsClient;
  
      OpcUaCertificateUsageChecker(CertPath certPath, Set<ValidationCheck> validationChecks, boolean endEntityIsClient) {
          this.certPath = certPath;
          this.validationChecks = validationChecks;
          this.endEntityIsClient = endEntityIsClient;
  
          endEntityCert = (X509Certificate) certPath.getCertificates().get(0);
      }
  
      @Override
      public void init(boolean forward) throws CertPathValidatorException {
          if (forward) {
              throw new CertPathValidatorException("forward checking not supported");
          }
      }
  
      @Override
      public boolean isForwardCheckingSupported() {
          return false;
      }
  
      @Override
      public Set<String> getSupportedExtensions() {
          return Collections.unmodifiableSet(newHashSet(KEY_USAGE_OID, EXTENDED_KEY_USAGE_OID));
      }
  
      @Override
      public void check(Certificate cert, Collection<String> unresolvedCritExts) throws CertPathValidatorException {
          X509Certificate certificate = (X509Certificate) cert;
  
          Set<String> criticalExtensions = certificate.getCriticalExtensionOIDs();
          if (criticalExtensions == null) criticalExtensions = Collections.emptySet();
  
          if (endEntityCert.equals(cert)) {
              try {
                  CertificateValidationUtil.checkEndEntityKeyUsage((X509Certificate) cert);
  
                  LOGGER.debug(
                      "validated KeyUsage for end entity: {}",
                      ((X509Certificate) cert).getSubjectX500Principal().getName()
                  );
              } catch (UaException e) {
                  if (validationChecks.contains(ValidationCheck.KEY_USAGE_END_ENTITY) ||
                      criticalExtensions.contains(KEY_USAGE_OID)
                  ) {
  
                      throw new CertPathValidatorException(
                          e.getMessage(),
                          e,
                          certPath,
                          certPath.getCertificates().indexOf(cert),
                          PKIXReason.INVALID_KEY_USAGE
                      );
                  } else {
                      LOGGER.warn(
                          "check suppressed: certificate failed end-entity usage check: {}",
                          ((X509Certificate) cert).getSubjectX500Principal().getName()
                      );
                  }
              }
              try {
                  CertificateValidationUtil.checkEndEntityExtendedKeyUsage(certificate, endEntityIsClient);
  
                  LOGGER.debug(
                      "validated ExtendedKeyUsage for end entity: {}",
                      ((X509Certificate) cert).getSubjectX500Principal().getName()
                  );
              } catch (UaException e) {
                  if (validationChecks.contains(ValidationCheck.EXTENDED_KEY_USAGE_END_ENTITY) ||
                      criticalExtensions.contains(EXTENDED_KEY_USAGE_OID)
                  ) {
  
                      throw new CertPathValidatorException(
                          e.getMessage(),
                          e,
                          certPath,
                          certPath.getCertificates().indexOf(cert),
                          PKIXReason.INVALID_KEY_USAGE
                      );
                  } else {
                      LOGGER.warn(
                          "check suppressed: certificate failed end-entity usage check: {}",
                          ((X509Certificate) cert).getSubjectX500Principal().getName()
                      );
                  }
              }
          } else {
              try {
                  CertificateValidationUtil.checkIssuerKeyUsage((X509Certificate) cert);
  
                  LOGGER.debug(
                      "validated KeyUsage for issuer: {}",
                      ((X509Certificate) cert).getSubjectX500Principal().getName()
                  );
              } catch (UaException e) {
                  if (validationChecks.contains(ValidationCheck.KEY_USAGE_ISSUER) ||
                      criticalExtensions.contains(KEY_USAGE_OID)
                  ) {
  
                      throw new CertPathValidatorException(
                          e.getMessage(),
                          e,
                          certPath,
                          certPath.getCertificates().indexOf(cert),
                          PKIXReason.INVALID_KEY_USAGE
                      );
                  } else {
                      LOGGER.warn(
                          "check suppressed: certificate failed issuer usage check: {}",
                          ((X509Certificate) cert).getSubjectX500Principal().getName()
                      );
                  }
              }
          }
  
          if (unresolvedCritExts != null && !unresolvedCritExts.isEmpty()) {
              unresolvedCritExts.remove(KEY_USAGE_OID);
              unresolvedCritExts.remove(EXTENDED_KEY_USAGE_OID);
          }
      }
  
  }
  