/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "plarena.h"

#include "seccomon.h"
#include "secitem.h"
#include "secasn1.h"
#include "secder.h"
#include "cert.h"
#include "secerr.h"
#include "secoid.h"
#include "sechash.h"
#include "keyhi.h"
#include "cryptohi.h"
#include "ocsp.h"
#include "ocspti.h"
#include "ocspi.h"
#include "pk11pub.h"

extern const SEC_ASN1Template ocsp_ResponderIDByNameTemplate[];
extern const SEC_ASN1Template ocsp_ResponderIDByKeyTemplate[];
extern const SEC_ASN1Template ocsp_OCSPResponseTemplate[];

ocspCertStatus *
ocsp_CreateCertStatus(PLArenaPool *arena,
                      ocspCertStatusType status,
                      PRTime revocationTime)
{
    ocspCertStatus *cs;

    if (!arena) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    switch (status) {
        case ocspCertStatus_good:
        case ocspCertStatus_unknown:
        case ocspCertStatus_revoked:
            break;
        default:
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            return NULL;
    }

    cs = PORT_ArenaZNew(arena, ocspCertStatus);
    if (!cs)
        return NULL;
    cs->certStatusType = status;
    switch (status) {
        case ocspCertStatus_good:
            cs->certStatusInfo.goodInfo = SECITEM_AllocItem(arena, NULL, 0);
            if (!cs->certStatusInfo.goodInfo)
                return NULL;
            break;
        case ocspCertStatus_unknown:
            cs->certStatusInfo.unknownInfo = SECITEM_AllocItem(arena, NULL, 0);
            if (!cs->certStatusInfo.unknownInfo)
                return NULL;
            break;
        case ocspCertStatus_revoked:
            cs->certStatusInfo.revokedInfo =
                PORT_ArenaZNew(arena, ocspRevokedInfo);
            if (!cs->certStatusInfo.revokedInfo)
                return NULL;
            cs->certStatusInfo.revokedInfo->revocationReason =
                SECITEM_AllocItem(arena, NULL, 0);
            if (!cs->certStatusInfo.revokedInfo->revocationReason)
                return NULL;
            if (DER_TimeToGeneralizedTimeArena(arena,
                                               &cs->certStatusInfo.revokedInfo->revocationTime,
                                               revocationTime) !=
                SECSuccess)
                return NULL;
            break;
        default:
            PORT_Assert(PR_FALSE);
    }
    return cs;
}

static const SEC_ASN1Template mySEC_EnumeratedTemplate[] = {
    { SEC_ASN1_ENUMERATED, 0, NULL, sizeof(SECItem) }
};

static const SEC_ASN1Template mySEC_PointerToEnumeratedTemplate[] = {
    { SEC_ASN1_POINTER, 0, mySEC_EnumeratedTemplate }
};

static const SEC_ASN1Template ocsp_EncodeRevokedInfoTemplate[] = {
    { SEC_ASN1_GENERALIZED_TIME,
      offsetof(ocspRevokedInfo, revocationTime) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
          SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
      offsetof(ocspRevokedInfo, revocationReason),
      mySEC_PointerToEnumeratedTemplate },
    { 0 }
};

static const SEC_ASN1Template ocsp_PointerToEncodeRevokedInfoTemplate[] = {
    { SEC_ASN1_POINTER, 0,
      ocsp_EncodeRevokedInfoTemplate }
};

static const SEC_ASN1Template mySEC_NullTemplate[] = {
    { SEC_ASN1_NULL, 0, NULL, sizeof(SECItem) }
};

static const SEC_ASN1Template ocsp_CertStatusTemplate[] = {
    { SEC_ASN1_CHOICE, offsetof(ocspCertStatus, certStatusType),
      0, sizeof(ocspCertStatus) },
    { SEC_ASN1_CONTEXT_SPECIFIC | 0,
      0, mySEC_NullTemplate, ocspCertStatus_good },
    { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED |
          SEC_ASN1_CONTEXT_SPECIFIC | 1,
      offsetof(ocspCertStatus, certStatusInfo.revokedInfo),
      ocsp_PointerToEncodeRevokedInfoTemplate, ocspCertStatus_revoked },
    { SEC_ASN1_CONTEXT_SPECIFIC | 2,
      0, mySEC_NullTemplate, ocspCertStatus_unknown },
    { 0 }
};

static const SEC_ASN1Template mySECOID_AlgorithmIDTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(SECAlgorithmID) },
    { SEC_ASN1_OBJECT_ID,
      offsetof(SECAlgorithmID, algorithm) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY,
      offsetof(SECAlgorithmID, parameters) },
    { 0 }
};

static const SEC_ASN1Template mySEC_AnyTemplate[] = {
    { SEC_ASN1_ANY | SEC_ASN1_MAY_STREAM, 0, NULL, sizeof(SECItem) }
};

static const SEC_ASN1Template mySEC_SequenceOfAnyTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF, 0, mySEC_AnyTemplate }
};

static const SEC_ASN1Template mySEC_PointerToSequenceOfAnyTemplate[] = {
    { SEC_ASN1_POINTER, 0, mySEC_SequenceOfAnyTemplate }
};

static const SEC_ASN1Template mySEC_IntegerTemplate[] = {
    { SEC_ASN1_INTEGER, 0, NULL, sizeof(SECItem) }
};

static const SEC_ASN1Template mySEC_PointerToIntegerTemplate[] = {
    { SEC_ASN1_POINTER, 0, mySEC_IntegerTemplate }
};

static const SEC_ASN1Template mySEC_GeneralizedTimeTemplate[] = {
    { SEC_ASN1_GENERALIZED_TIME | SEC_ASN1_MAY_STREAM, 0, NULL, sizeof(SECItem) }
};

static const SEC_ASN1Template mySEC_PointerToGeneralizedTimeTemplate[] = {
    { SEC_ASN1_POINTER, 0, mySEC_GeneralizedTimeTemplate }
};

static const SEC_ASN1Template ocsp_myCertIDTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(CERTOCSPCertID) },
    { SEC_ASN1_INLINE,
      offsetof(CERTOCSPCertID, hashAlgorithm),
      mySECOID_AlgorithmIDTemplate },
    { SEC_ASN1_OCTET_STRING,
      offsetof(CERTOCSPCertID, issuerNameHash) },
    { SEC_ASN1_OCTET_STRING,
      offsetof(CERTOCSPCertID, issuerKeyHash) },
    { SEC_ASN1_INTEGER,
      offsetof(CERTOCSPCertID, serialNumber) },
    { 0 }
};

static const SEC_ASN1Template myCERT_CertExtensionTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(CERTCertExtension) },
    { SEC_ASN1_OBJECT_ID,
      offsetof(CERTCertExtension, id) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */
      offsetof(CERTCertExtension, critical) },
    { SEC_ASN1_OCTET_STRING,
      offsetof(CERTCertExtension, value) },
    { 0 }
};

static const SEC_ASN1Template myCERT_SequenceOfCertExtensionTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF, 0, myCERT_CertExtensionTemplate }
};

static const SEC_ASN1Template myCERT_PointerToSequenceOfCertExtensionTemplate[] = {
    { SEC_ASN1_POINTER, 0, myCERT_SequenceOfCertExtensionTemplate }
};

static const SEC_ASN1Template ocsp_mySingleResponseTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(CERTOCSPSingleResponse) },
    { SEC_ASN1_POINTER,
      offsetof(CERTOCSPSingleResponse, certID),
      ocsp_myCertIDTemplate },
    { SEC_ASN1_ANY,
      offsetof(CERTOCSPSingleResponse, derCertStatus) },
    { SEC_ASN1_GENERALIZED_TIME,
      offsetof(CERTOCSPSingleResponse, thisUpdate) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
          SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
      offsetof(CERTOCSPSingleResponse, nextUpdate),
      mySEC_PointerToGeneralizedTimeTemplate },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
          SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1,
      offsetof(CERTOCSPSingleResponse, singleExtensions),
      myCERT_PointerToSequenceOfCertExtensionTemplate },
    { 0 }
};

static const SEC_ASN1Template ocsp_myResponseDataTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(ocspResponseData) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | /* XXX DER_DEFAULT */
          SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
      offsetof(ocspResponseData, version),
      mySEC_PointerToIntegerTemplate },
    { SEC_ASN1_ANY,
      offsetof(ocspResponseData, derResponderID) },
    { SEC_ASN1_GENERALIZED_TIME,
      offsetof(ocspResponseData, producedAt) },
    { SEC_ASN1_SEQUENCE_OF,
      offsetof(ocspResponseData, responses),
      ocsp_mySingleResponseTemplate },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
          SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1,
      offsetof(ocspResponseData, responseExtensions),
      myCERT_PointerToSequenceOfCertExtensionTemplate },
    { 0 }
};

static const SEC_ASN1Template ocsp_EncodeBasicOCSPResponseTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(ocspBasicOCSPResponse) },
    { SEC_ASN1_POINTER,
      offsetof(ocspBasicOCSPResponse, tbsResponseData),
      ocsp_myResponseDataTemplate },
    { SEC_ASN1_INLINE,
      offsetof(ocspBasicOCSPResponse, responseSignature.signatureAlgorithm),
      mySECOID_AlgorithmIDTemplate },
    { SEC_ASN1_BIT_STRING,
      offsetof(ocspBasicOCSPResponse, responseSignature.signature) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
          SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
      offsetof(ocspBasicOCSPResponse, responseSignature.derCerts),
      mySEC_PointerToSequenceOfAnyTemplate },
    { 0 }
};

static CERTOCSPSingleResponse *
ocsp_CreateSingleResponse(PLArenaPool *arena,
                          CERTOCSPCertID *id, ocspCertStatus *status,
                          PRTime thisUpdate, const PRTime *nextUpdate)
{
    CERTOCSPSingleResponse *sr;

    if (!arena || !id || !status) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    sr = PORT_ArenaZNew(arena, CERTOCSPSingleResponse);
    if (!sr)
        return NULL;
    sr->arena = arena;
    sr->certID = id;
    sr->certStatus = status;
    if (DER_TimeToGeneralizedTimeArena(arena, &sr->thisUpdate, thisUpdate) !=
        SECSuccess)
        return NULL;
    sr->nextUpdate = NULL;
    if (nextUpdate) {
        sr->nextUpdate = SECITEM_AllocItem(arena, NULL, 0);
        if (!sr->nextUpdate)
            return NULL;
        if (DER_TimeToGeneralizedTimeArena(arena, sr->nextUpdate, *nextUpdate) !=
            SECSuccess)
            return NULL;
    }

    sr->singleExtensions = PORT_ArenaNewArray(arena, CERTCertExtension *, 1);
    if (!sr->singleExtensions)
        return NULL;

    sr->singleExtensions[0] = NULL;

    if (!SEC_ASN1EncodeItem(arena, &sr->derCertStatus,
                            status, ocsp_CertStatusTemplate))
        return NULL;

    return sr;
}

CERTOCSPSingleResponse *
CERT_CreateOCSPSingleResponseGood(PLArenaPool *arena,
                                  CERTOCSPCertID *id,
                                  PRTime thisUpdate,
                                  const PRTime *nextUpdate)
{
    ocspCertStatus *cs;
    if (!arena) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }
    cs = ocsp_CreateCertStatus(arena, ocspCertStatus_good, 0);
    if (!cs)
        return NULL;
    return ocsp_CreateSingleResponse(arena, id, cs, thisUpdate, nextUpdate);
}

CERTOCSPSingleResponse *
CERT_CreateOCSPSingleResponseUnknown(PLArenaPool *arena,
                                     CERTOCSPCertID *id,
                                     PRTime thisUpdate,
                                     const PRTime *nextUpdate)
{
    ocspCertStatus *cs;
    if (!arena) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }
    cs = ocsp_CreateCertStatus(arena, ocspCertStatus_unknown, 0);
    if (!cs)
        return NULL;
    return ocsp_CreateSingleResponse(arena, id, cs, thisUpdate, nextUpdate);
}

CERTOCSPSingleResponse *
CERT_CreateOCSPSingleResponseRevoked(
    PLArenaPool *arena,
    CERTOCSPCertID *id,
    PRTime thisUpdate,
    const PRTime *nextUpdate,
    PRTime revocationTime,
    const CERTCRLEntryReasonCode *revocationReason)
{
    ocspCertStatus *cs;
    /* revocationReason is not yet supported, so it must be NULL. */
    if (!arena || revocationReason) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }
    cs = ocsp_CreateCertStatus(arena, ocspCertStatus_revoked, revocationTime);
    if (!cs)
        return NULL;
    return ocsp_CreateSingleResponse(arena, id, cs, thisUpdate, nextUpdate);
}

/* responderCert == 0 means:
 * create a response with an invalid signature (for testing purposes) */
SECItem *
CERT_CreateEncodedOCSPSuccessResponse(
    PLArenaPool *arena,
    CERTCertificate *responderCert,
    CERTOCSPResponderIDType responderIDType,
    PRTime producedAt,
    CERTOCSPSingleResponse **responses,
    void *wincx)
{
    PLArenaPool *tmpArena;
    ocspResponseData *rd = NULL;
    ocspResponderID *rid = NULL;
    const SEC_ASN1Template *responderIDTemplate = NULL;
    ocspBasicOCSPResponse *br = NULL;
    ocspResponseBytes *rb = NULL;
    CERTOCSPResponse *response = NULL;

    SECOidTag algID;
    SECOidData *od = NULL;
    SECKEYPrivateKey *privKey = NULL;
    SECItem *result = NULL;

    if (!arena || !responses) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }
    if (responderIDType != ocspResponderID_byName &&
        responderIDType != ocspResponderID_byKey) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (!tmpArena)
        return NULL;

    rd = PORT_ArenaZNew(tmpArena, ocspResponseData);
    if (!rd)
        goto done;
    rid = PORT_ArenaZNew(tmpArena, ocspResponderID);
    if (!rid)
        goto done;
    br = PORT_ArenaZNew(tmpArena, ocspBasicOCSPResponse);
    if (!br)
        goto done;
    rb = PORT_ArenaZNew(tmpArena, ocspResponseBytes);
    if (!rb)
        goto done;
    response = PORT_ArenaZNew(tmpArena, CERTOCSPResponse);
    if (!response)
        goto done;

    rd->version.data = NULL;
    rd->version.len = 0;
    rd->responseExtensions = NULL;
    rd->responses = responses;
    if (DER_TimeToGeneralizedTimeArena(tmpArena, &rd->producedAt, producedAt) !=
        SECSuccess)
        goto done;

    if (!responderCert) {
        /* use invalid signature for testing purposes */
        unsigned char dummyChar = 'd';
        SECItem dummy;

        dummy.len = 1;
        dummy.data = &dummyChar;

        /* it's easier to produdce a keyHash out of nowhere,
         * than to produce an encoded subject,
         * so for our dummy response we always use byKey
         */

        rid->responderIDType = ocspResponderID_byKey;
        if (!ocsp_DigestValue(tmpArena, SEC_OID_SHA1, &rid->responderIDValue.keyHash,
                              &dummy))
            goto done;

        if (!SEC_ASN1EncodeItem(tmpArena, &rd->derResponderID, rid,
                                ocsp_ResponderIDByKeyTemplate))
            goto done;

        br->tbsResponseData = rd;

        if (!SEC_ASN1EncodeItem(tmpArena, &br->tbsResponseDataDER, br->tbsResponseData,
                                ocsp_myResponseDataTemplate))
            goto done;

        br->responseSignature.derCerts = PORT_ArenaNewArray(tmpArena, SECItem *, 1);
        if (!br->responseSignature.derCerts)
            goto done;
        br->responseSignature.derCerts[0] = NULL;

        algID = SEC_GetSignatureAlgorithmOidTag(rsaKey, SEC_OID_SHA1);
        if (algID == SEC_OID_UNKNOWN)
            goto done;

        /* match the regular signature code, which doesn't use the arena */
        if (!SECITEM_AllocItem(NULL, &br->responseSignature.signature, 1))
            goto done;
        PORT_Memcpy(br->responseSignature.signature.data, &dummyChar, 1);

        /* convert len-in-bytes to len-in-bits */
        br->responseSignature.signature.len = br->responseSignature.signature.len << 3;
    } else {
        rid->responderIDType = responderIDType;
        if (responderIDType == ocspResponderID_byName) {
            responderIDTemplate = ocsp_ResponderIDByNameTemplate;
            if (CERT_CopyName(tmpArena, &rid->responderIDValue.name,
                              &responderCert->subject) != SECSuccess)
                goto done;
        } else {
            responderIDTemplate = ocsp_ResponderIDByKeyTemplate;
            if (!CERT_GetSubjectPublicKeyDigest(tmpArena, responderCert,
                                                SEC_OID_SHA1, &rid->responderIDValue.keyHash))
                goto done;
        }

        if (!SEC_ASN1EncodeItem(tmpArena, &rd->derResponderID, rid,
                                responderIDTemplate))
            goto done;

        br->tbsResponseData = rd;

        if (!SEC_ASN1EncodeItem(tmpArena, &br->tbsResponseDataDER, br->tbsResponseData,
                                ocsp_myResponseDataTemplate))
            goto done;

        br->responseSignature.derCerts = PORT_ArenaNewArray(tmpArena, SECItem *, 1);
        if (!br->responseSignature.derCerts)
            goto done;
        br->responseSignature.derCerts[0] = NULL;

        privKey = PK11_FindKeyByAnyCert(responderCert, wincx);
        if (!privKey)
            goto done;

        algID = SEC_GetSignatureAlgorithmOidTag(privKey->keyType, SEC_OID_SHA1);
        if (algID == SEC_OID_UNKNOWN)
            goto done;

        if (SEC_SignData(&br->responseSignature.signature,
                         br->tbsResponseDataDER.data, br->tbsResponseDataDER.len,
                         privKey, algID) !=
            SECSuccess)
            goto done;

        /* convert len-in-bytes to len-in-bits */
        br->responseSignature.signature.len = br->responseSignature.signature.len << 3;

        /* br->responseSignature.signature wasn't allocated from arena,
         * we must free it when done. */
    }

    if (SECOID_SetAlgorithmID(tmpArena, &br->responseSignature.signatureAlgorithm, algID, 0) !=
        SECSuccess)
        goto done;

    if (!SEC_ASN1EncodeItem(tmpArena, &rb->response, br,
                            ocsp_EncodeBasicOCSPResponseTemplate))
        goto done;

    rb->responseTypeTag = SEC_OID_PKIX_OCSP_BASIC_RESPONSE;

    od = SECOID_FindOIDByTag(rb->responseTypeTag);
    if (!od)
        goto done;

    rb->responseType = od->oid;
    rb->decodedResponse.basic = br;

    response->arena = tmpArena;
    response->responseBytes = rb;
    response->statusValue = ocspResponse_successful;

    if (!SEC_ASN1EncodeInteger(tmpArena, &response->responseStatus,
                               response->statusValue))
        goto done;

    result = SEC_ASN1EncodeItem(arena, NULL, response, ocsp_OCSPResponseTemplate);

done:
    if (privKey)
        SECKEY_DestroyPrivateKey(privKey);
    if (br && br->responseSignature.signature.data)
        SECITEM_FreeItem(&br->responseSignature.signature, PR_FALSE);
    PORT_FreeArena(tmpArena, PR_FALSE);

    return result;
}

static const SEC_ASN1Template ocsp_OCSPErrorResponseTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(CERTOCSPResponse) },
    { SEC_ASN1_ENUMERATED,
      offsetof(CERTOCSPResponse, responseStatus) },
    { 0, 0,
      mySEC_NullTemplate },
    { 0 }
};

SECItem *
CERT_CreateEncodedOCSPErrorResponse(PLArenaPool *arena, int error)
{
    CERTOCSPResponse response;
    SECItem *result = NULL;

    switch (error) {
        case SEC_ERROR_OCSP_MALFORMED_REQUEST:
            response.statusValue = ocspResponse_malformedRequest;
            break;
        case SEC_ERROR_OCSP_SERVER_ERROR:
            response.statusValue = ocspResponse_internalError;
            break;
        case SEC_ERROR_OCSP_TRY_SERVER_LATER:
            response.statusValue = ocspResponse_tryLater;
            break;
        case SEC_ERROR_OCSP_REQUEST_NEEDS_SIG:
            response.statusValue = ocspResponse_sigRequired;
            break;
        case SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST:
            response.statusValue = ocspResponse_unauthorized;
            break;
        default:
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            return NULL;
    }

    if (!SEC_ASN1EncodeInteger(NULL, &response.responseStatus,
                               response.statusValue))
        return NULL;

    result = SEC_ASN1EncodeItem(arena, NULL, &response,
                                ocsp_OCSPErrorResponseTemplate);

    SECITEM_FreeItem(&response.responseStatus, PR_FALSE);

    return result;
}
