/*
 * Copyright 2020, The Android Open Source Project
 *
 * 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.
 */

#pragma once

#include <openssl/err.h>
#include <openssl/x509.h>
#include <stdint.h>

#include <functional>
#include <memory>
#include <optional>
#include <variant>

namespace keystore {
// We use boringssl error codes. Error codes that we add are folded into LIB_USER.
// The CertificateUtilsInternallErrorCodes enum should not be used by callers, instead use the
// BoringSslError constant definitions below for error codes.
using BoringSslError = unsigned long;

#define DEFINE_OPENSSL_OBJECT_POINTER(name) using name##_Ptr = bssl::UniquePtr<name>

DEFINE_OPENSSL_OBJECT_POINTER(ASN1_BIT_STRING);
DEFINE_OPENSSL_OBJECT_POINTER(ASN1_STRING);
DEFINE_OPENSSL_OBJECT_POINTER(ASN1_INTEGER);
DEFINE_OPENSSL_OBJECT_POINTER(ASN1_OCTET_STRING);
DEFINE_OPENSSL_OBJECT_POINTER(ASN1_TIME);
DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY);
DEFINE_OPENSSL_OBJECT_POINTER(X509);
DEFINE_OPENSSL_OBJECT_POINTER(X509_ALGOR);
DEFINE_OPENSSL_OBJECT_POINTER(X509_EXTENSION);
DEFINE_OPENSSL_OBJECT_POINTER(X509_NAME);
DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY_CTX);

class CertUtilsError {
  public:
    enum Error {
        Ok = 0,
        BoringSsl,
        Encoding,
        MemoryAllocation,
        InvalidArgument,
        UnexpectedNullPointer,
        SignatureFailed,
        TimeError,
    };

  private:
    Error e_;

  public:
    constexpr CertUtilsError(Error e) : e_(e) {}
    explicit constexpr operator bool() const { return e_ != Ok; }
};

struct KeyUsageExtension {
    bool isSigningKey;
    bool isEncryptionKey;
    bool isCertificationKey;
};

struct BasicConstraintsExtension {
    bool isCa;
    std::optional<int> pathLength;
};

/**
 * This function allocates and prepares an X509 certificate structure with all of the information
 * given. Next steps would be to set an Issuer with `setIssuer` and sign it with either
 * `signCert` or `signCertWith`.
 * @param evp_pkey The public key that the certificate is issued for.
 * @param serial The certificate serial number.
 * @param subject The X509 name encoded subject common name.
 * @param activeDateTimeMilliSeconds The not before date in epoch milliseconds.
 * @param usageExpireDateTimeMilliSeconds The not after date in epoch milliseconds.
 * @param addSubjectKeyIdEx If true, adds the subject key id extension.
 * @param keyUsageEx If given adds, the key usage extension with the given flags.
 * @param basicConstraints If given, adds the basic constraints extension with the given data.
 * @return CertUtilsError::Ok on success.
 */
std::variant<CertUtilsError, X509_Ptr>
makeCert(const EVP_PKEY* evp_pkey,                                                   //
         std::optional<std::reference_wrapper<const std::vector<uint8_t>>> serial,   //
         std::optional<std::reference_wrapper<const std::vector<uint8_t>>> subject,  //
         const int64_t activeDateTimeMilliSeconds,                                   //
         const int64_t usageExpireDateTimeMilliSeconds,                              //
         bool addSubjectKeyIdEx,                                                     //
         std::optional<KeyUsageExtension> keyUsageEx,                                //
         std::optional<BasicConstraintsExtension> basicConstraints);                 //

/**
 * Takes the subject name from `signingCert` and sets it as issuer name in `cert`.
 * if `addAuthKeyExt` is true it also generates the digest of the signing certificates's public key
 * and sets it as authority key id extension in `cert`.
 * For self signed certificates pass the same pointer to both `cert` and `signingCert`.
 *
 * @param cert
 * @param signingCert
 * @param addAuthKeyExt
 * @return CertUtilsError::Ok on success.
 */
CertUtilsError setIssuer(X509* cert, const X509* signingCert, bool addAuthKeyExt);

/**
 * Takes a certificate, and private signing_key.
 * Signs the certificate with the latter.
 */
CertUtilsError signCert(X509* certificate, EVP_PKEY* signing_key);

enum class Digest {
    SHA1,
    SHA224,
    SHA256,
    SHA384,
    SHA512,
};

enum class Algo {
    ECDSA,
    RSA,
};

enum class Padding {
    Ignored,
    PKCS1_5,
    PSS,
};

/**
 * Takes an int64_t representing UNIX epoch time in milliseconds and turns it into a UTCTime
 * or GeneralizedTime string depending on whether the year is in the interval [1950 .. 2050).
 * Note: The string returned in the array buffer is NUL terminated and of length 13 (UTCTime)
 * or 15 (GeneralizedTime).
 * @param timeMillis
 * @return UTCTime or GeneralizedTime string.
 */
std::optional<std::array<char, 16>> toTimeString(int64_t timeMillis);

/**
 * Sets the signature specifier of the certificate and the signature according to the parameters
 * c. Then it signs the certificate with the `sign` callback.
 * IMPORTANT: The parameters `algo`, `padding`, and `digest` do not control the actual signing
 * algorithm. The caller is responsible to provide a callback that actually performs the signature
 * as described by this triplet.
 * The `padding` argument is ignored if `algo` is Algo::EC.
 * The `digest` field controls the message digest used, and, in case of RSA with PSS padding,
 *              also the MGF1 digest.
 *
 * @param certificate X509 certificate structure to be signed.
 * @param sign Callback function used to digest and sign the DER encoded to-be-signed certificate.
 * @param algo Algorithm specifier used to encode the signing algorithm id of the X509 certificate.
 * @param padding Padding specifier used to encode the signing algorithm id of the X509 certificate.
 * @param digest Digest specifier used to encode the signing algorithm id of the X509 certificate.
 * @return CertUtilsError::Ok on success.
 */
CertUtilsError signCertWith(X509* certificate,
                            std::function<std::vector<uint8_t>(const uint8_t*, size_t)> sign,
                            Algo algo, Padding padding, Digest digest);

/**
 * Generates the DER representation of the given signed X509 certificate structure.
 * @param certificate
 * @return std::vector<uint8_t> with the DER encoded certificate on success. An error code
 *         otherwise.
 */
std::variant<CertUtilsError, std::vector<uint8_t>> encodeCert(X509* certificate);

}  // namespace keystore
