/*******************************************************************************
 * Copyright (c) 2016, 2019 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/

package org.eclipse.hono.service.auth.device;

import java.security.GeneralSecurityException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.eclipse.hono.service.auth.X509CertificateChainValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.vertx.core.Future;
import io.vertx.core.Promise;


/**
 * Validates a device's certificate chain using a {@link CertPathValidator}.
 *
 */
public class DeviceCertificateValidator implements X509CertificateChainValidator {

    private static final Logger LOG = LoggerFactory.getLogger(DeviceCertificateValidator.class);

    /**
     * {@inheritDoc}
     */
    @Override
    public Future<Void> validate(final List<X509Certificate> chain, final TrustAnchor trustAnchor) {

        Objects.requireNonNull(chain);
        Objects.requireNonNull(trustAnchor);

        return validate(chain, Set.of(trustAnchor));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Future<Void> validate(final List<X509Certificate> chain, final Set<TrustAnchor> trustAnchors) {

        Objects.requireNonNull(chain);
        Objects.requireNonNull(trustAnchors);

        if (chain.isEmpty()) {
            throw new IllegalArgumentException("certificate chain must not be empty");
        } else if (trustAnchors.isEmpty()) {
            throw new IllegalArgumentException("trust anchor list must not be empty");
        }

        final Promise<Void> result = Promise.promise();

        try {
            final PKIXParameters params = new PKIXParameters(trustAnchors);
            // TODO do we need to check for revocation?
            params.setRevocationEnabled(false);
            final CertificateFactory factory = CertificateFactory.getInstance("X.509");
            final CertPath path = factory.generateCertPath(chain);
            final CertPathValidator validator = CertPathValidator.getInstance("PKIX");
            validator.validate(path, params);
            LOG.debug("validation of device certificate [subject DN: {}] succeeded",
                    chain.get(0).getSubjectX500Principal().getName());
            result.complete();
        } catch (GeneralSecurityException e) {
            LOG.debug("validation of device certificate [subject DN: {}] failed",
                    chain.get(0).getSubjectX500Principal().getName(), e);
            if (e instanceof CertificateException) {
                result.fail(e);
            } else {
                result.fail(new CertificateException("validation of device certificate failed", e));
            }
        }
        return result.future();
    }
}