/*
 * Copyright 2001-2017 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the OpenSSL license (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

// SPDX-License-Identifier: Apache-2.0 OR ISC
// Modifications Copyright Amazon.com, Inc. or its affiliates.

#include <ctype.h>
#include <string.h>
#include "openssl/mem.h"

#include "../internal.h"
#include "internal.h"

#define OCSP_MAX_LINE_LEN 4096;

// OCSP_REQ_CTX states

// If set no reading should be performed
#define OHS_NOREAD 0x1000
// Error condition
#define OHS_ERROR (0 | OHS_NOREAD)
// First line being read
#define OHS_FIRSTLINE 1
// MIME headers being read
#define OHS_HEADERS 2
// OCSP initial header (tag + length) being read
#define OHS_ASN1_HEADER 3
// OCSP content octets being read
#define OHS_ASN1_CONTENT 4
// First call: ready to start I/O
#define OHS_ASN1_WRITE_INIT (5 | OHS_NOREAD)
// Request being sent
#define OHS_ASN1_WRITE (6 | OHS_NOREAD)
// Request being flushed
#define OHS_ASN1_FLUSH (7 | OHS_NOREAD)
// Completed
#define OHS_DONE (8 | OHS_NOREAD)
// Headers set, no final \r\n included
#define OHS_HTTP_HEADER (9 | OHS_NOREAD)

static int check_protocol(char *line) {
  if (strlen(line) >= 4 && strncmp(line, "HTTP", 4) == 0) {
    return 1;
  }
  return 0;
}

// Parse the HTTP response. This will look like this: "HTTP/1.0 200 OK". We
// need to obtain the numeric code and (optional) informational message.
static int parse_http_line(char *line) {
  int http_code;
  char *code, *reason, *end;
  if (!check_protocol(line)) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_SERVER_RESPONSE_PARSE_ERROR);
    return 0;
  }
  // Skip to first white space (passed protocol info)
  for (code = line; *code != '\0' && !OPENSSL_isspace(*code); code++) {
    continue;
  }
  if (*code == '\0') {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_SERVER_RESPONSE_PARSE_ERROR);
    return 0;
  }

  // Skip past white space to start of response code.
  while (*code != '\0' && OPENSSL_isspace(*code)) {
    code++;
  }
  if (*code == '\0') {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_SERVER_RESPONSE_PARSE_ERROR);
    return 0;
  }

  // Find end of response code: first whitespace after start of code.
  for (reason = code; *reason != '\0' && !OPENSSL_isspace(*reason); reason++) {
    continue;
  }
  if (*reason == '\0') {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_SERVER_RESPONSE_PARSE_ERROR);
    return 0;
  }

  // Set end of response code and start of message.
  *reason++ = '\0';
  // Attempt to parse numeric code
  http_code = (int)strtoul(code, &end, 10);
  if (*end != '\0') {
    return 0;
  }

  // Skip over any leading white space in message.
  while (*reason != '\0' && OPENSSL_isspace(*reason)) {
    reason++;
  }
  if (*reason != '\0') {
    // Finally, zap any trailing white space in message (include CRLF).
    // We know reason has a non-white space character so this is OK.
    for (end = reason + strlen(reason) - 1; OPENSSL_isspace(*end); end--) {
      *end = '\0';
    }
  }
  if (http_code != 200) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_SERVER_RESPONSE_PARSE_ERROR);
    if (*reason == '\0') {
      ERR_add_error_data(2, "Code=", code);
    } else {
      ERR_add_error_data(4, "Code=", code, ",Reason=", reason);
    }
    return 0;
  }
  return 1;
}

// |OCSP_REQUEST| sending:
// OHS_HTTP_HEADER should be initial state when first calling
// |OCSP_REQ_CTX_nbio|. OHS_HTTP_HEADER represents the state where OCSP request
// headers have finished being written, and we want to add the final "\r\n" to
// the ASN.1 OCSP request. This falls through to OHS_ASN1_WRITE_INIT and
// OHS_ASN1_WRITE, where we start writing the OCSP request from |rctx->mem| to
// |rctx->io|. |OHS_ASN1_WRITE| will continue writing the ASN.1 contents until
// all |OCSP_REQUEST| ASN.1 contents have been written. When OHS_ASN1_WRITE
// finishes writing, we will reset the BIO contents of |rctx->mem| and set the
// state to OHS_ASN1_FLUSH. OHS_ASN1_FLUSH will flush any buffered output that
// had been in |rctx->io|.
//
// |OCSP_RESPONSE| awaiting:
// Once OHS_ASN1_FLUSH has finished, we'll turn into a state of OHS_FIRSTLINE,
// and start expecting to read the first line of the HTTP OCSP response written
// back by the OCSP responder in |rctx->mem|. OHS_FIRSTLINE expects to parse the
// first line of the HTTP response, which contains the numeric code and
// (optional) informational message. The numeric code is parsed and verified
// with |parse_http_line|. Once the numeric code is parsed, OHS_FIRSTLINE will
// transtion to OHS_HEADERS. OHS_HEADERS parses any additional subsequent HTTP
// content headers in the OCSP HTTP response. Once a blank line is detected, we
// fallthrough to the state OHS_ASN1_HEADER and start expecting the ASN.1
// contents of the OCSP response. OHS_ASN1_HEADER first checks the ASN1 header
// contents, which should contain the length field. This then falls through to
// |OHS_ASN1_CONTENT| where we start reading in the actual contents of the
// ASN.1 OCSP response. Once all ASN.1 contents up to the length field have been
// read, |OCSP_REQ_CTX_nbio| will finish in the state of OHS_DONE.
// |OCSP_REQ_CTX_nbio| will not return 1 until we reach OHS_DONE.
int OCSP_REQ_CTX_nbio(OCSP_REQ_CTX *rctx) {
  int ret, tmp_data_len;
  size_t data_len;
  const unsigned char *data;
next_io:
  if (!(rctx->state & OHS_NOREAD)) {
    tmp_data_len = BIO_read(rctx->io, rctx->iobuf, rctx->iobuflen);
    if (tmp_data_len <= 0) {
      if (BIO_should_retry(rctx->io)) {
        return -1;
      }
      return 0;
    }

    // Write data to memory BIO.
    if (BIO_write(rctx->mem, rctx->iobuf, tmp_data_len) != tmp_data_len) {
      return 0;
    }
    data_len = (size_t)tmp_data_len;
  }

  switch (rctx->state) {
    case OHS_HTTP_HEADER:
      // Last operation was adding headers: need a final "\r\n".
      if (BIO_write(rctx->mem, "\r\n", 2) != 2) {
        rctx->state = OHS_ERROR;
        return 0;
      }
      rctx->state = OHS_ASN1_WRITE_INIT;

      OPENSSL_FALLTHROUGH;
    case OHS_ASN1_WRITE_INIT:
      if(!BIO_mem_contents(rctx->mem, NULL, &data_len)) {
        rctx->state = OHS_ERROR;
        return 0;
      }
      rctx->asn1_len = data_len;
      rctx->state = OHS_ASN1_WRITE;

      OPENSSL_FALLTHROUGH;
    case OHS_ASN1_WRITE:
      if(!BIO_mem_contents(rctx->mem, &data, &data_len)) {
        rctx->state = OHS_ERROR;
        return 0;
      }

      int write_len = BIO_write(rctx->io, data + (data_len - rctx->asn1_len),
                                (int)rctx->asn1_len);
      if (write_len <= 0) {
        if (BIO_should_retry(rctx->io)) {
          return -1;
        }
        rctx->state = OHS_ERROR;
        return 0;
      }

      rctx->asn1_len -= write_len;
      if (rctx->asn1_len > 0) {
        goto next_io;
      }
      rctx->state = OHS_ASN1_FLUSH;
      if(!BIO_reset(rctx->mem)) {
        return 0;
      }

      OPENSSL_FALLTHROUGH;
    case OHS_ASN1_FLUSH:
      ret = BIO_flush(rctx->io);
      if (ret > 0) {
        rctx->state = OHS_FIRSTLINE;
        goto next_io;
      }

      if (BIO_should_retry(rctx->io)) {
        return -1;
      }
      rctx->state = OHS_ERROR;
      return 0;

    case OHS_ERROR:
      return 0;

    case OHS_FIRSTLINE:
    case OHS_HEADERS:

    // Attempt to read a line in.
    next_line:
      // Due to strange memory BIO behaviour with BIO_gets we have to
      // check there's a complete line in there before calling BIO_gets
      // or we'll just get a partial read.
      if(!BIO_mem_contents(rctx->mem, &data, &data_len)) {
        rctx->state = OHS_ERROR;
        return 0;
      }
      if ((data_len <= 0) || !memchr(data, '\n', data_len)) {
        if (data_len >= (size_t)rctx->iobuflen) {
          rctx->state = OHS_ERROR;
          return 0;
        }
        goto next_io;
      }
      tmp_data_len = BIO_gets(rctx->mem, (char *)rctx->iobuf, rctx->iobuflen);

      if (tmp_data_len <= 0) {
        if (BIO_should_retry(rctx->mem)) {
          goto next_io;
        }
        rctx->state = OHS_ERROR;
        return 0;
      }

      // Don't allow excessive lines.
      if (tmp_data_len >= rctx->iobuflen) {
        rctx->state = OHS_ERROR;
        return 0;
      }
      data_len = (size_t)tmp_data_len;

      // First line.
      if (rctx->state == OHS_FIRSTLINE) {
        if (parse_http_line((char *)rctx->iobuf)) {
          rctx->state = OHS_HEADERS;
          goto next_line;
        } else {
          rctx->state = OHS_ERROR;
          return 0;
        }
      } else {
        // Look for blank line: end of headers.
        for (data = rctx->iobuf; *data; data++) {
          if ((*data != '\r') && (*data != '\n')) {
            break;
          }
        }
        if (*data != '\0') {
          goto next_line;
        }

        rctx->state = OHS_ASN1_HEADER;
      }

      OPENSSL_FALLTHROUGH;
    case OHS_ASN1_HEADER:
      // Now reading ASN1 header: can read at least 2 bytes which is
      // enough for ASN1 SEQUENCE header and either length field or at
      // least the length of the length field.
      if(!BIO_mem_contents(rctx->mem, &data, &data_len)) {
        rctx->state = OHS_ERROR;
        return 0;
      }
      if (data_len < 2) {
        goto next_io;
      }

      // Check it is an ASN1 SEQUENCE.
      if (*data++ != (V_ASN1_SEQUENCE | V_ASN1_CONSTRUCTED)) {
        rctx->state = OHS_ERROR;
        return 0;
      }

      // Check out length field. This is checking to see if the length is
      // encoded as long-form (multiple bytes) versus being a length that
      // can be encoded into 7 bits. "0x80" implies "0x80 + N", where N is
      // the number of length bytes to follow.
      if ((*data & 0x80) != 0) {
        // If MSB set on initial length octet we can now always read 6
        // octets: make sure we have them.
        if (data_len < 6) {
          goto next_io;
        }
        data_len = *data & 0x7F;
        // Not NDEF or excessive length.
        if (!data_len || (data_len > 4)) {
          rctx->state = OHS_ERROR;
          return 0;
        }
        data++;
        rctx->asn1_len = 0;
        for (size_t i = 0; i < data_len; i++) {
          rctx->asn1_len <<= 8;
          rctx->asn1_len |= *data++;
        }
        if (rctx->asn1_len > rctx->max_resp_len) {
          rctx->state = OHS_ERROR;
          return 0;
        }
        rctx->asn1_len += data_len + 2;
      } else {
        rctx->asn1_len = *data + 2;
      }
      rctx->state = OHS_ASN1_CONTENT;

      OPENSSL_FALLTHROUGH;
    case OHS_ASN1_CONTENT:
      if(!BIO_mem_contents(rctx->mem, NULL, &data_len)) {
        rctx->state = OHS_ERROR;
        return 0;
      }
      if (data_len < rctx->asn1_len) {
        goto next_io;
      }
      rctx->state = OHS_DONE;
      return 1;

    case OHS_DONE:
      return 1;
  }

  return 0;
}


int OCSP_sendreq_nbio(OCSP_RESPONSE **presp, OCSP_REQ_CTX *rctx) {
  return OCSP_REQ_CTX_nbio_d2i(rctx, (ASN1_VALUE **)presp,
                               ASN1_ITEM_rptr(OCSP_RESPONSE));
}

OCSP_RESPONSE *OCSP_sendreq_bio(BIO *b, const char *path, OCSP_REQUEST *req) {
  OCSP_RESPONSE *resp = NULL;
  OCSP_REQ_CTX *ctx;
  int rv;

  ctx = OCSP_sendreq_new(b, path, req, -1);
  if (ctx == NULL) {
    return NULL;
  }

  // This waits indefinitely on a response, if |BIO_should_retry| is on and
  // the BIO persists.
  do {
    rv = OCSP_sendreq_nbio(&resp, ctx);
  } while ((rv == -1) && BIO_should_retry(b));

  OCSP_REQ_CTX_free(ctx);
  if (rv) {
    return resp;
  }
  return NULL;
}

int OCSP_REQ_CTX_nbio_d2i(OCSP_REQ_CTX *rctx, ASN1_VALUE **pval,
                          const ASN1_ITEM *it) {
  int rv;
  size_t len;
  const unsigned char *p;

  rv = OCSP_REQ_CTX_nbio(rctx);
  if (rv != 1) {
    return rv;
  }

  if (!BIO_mem_contents(rctx->mem, &p, &len)) {
    goto err;
  }
  *pval = ASN1_item_d2i(NULL, &p, (long)len, it);
  if (*pval == NULL) {
    goto err;
  }
  return 1;

err:
  rctx->state = OHS_ERROR;
  return 0;
}

OCSP_REQ_CTX *OCSP_REQ_CTX_new(BIO *io, int maxline) {
  OCSP_REQ_CTX *rctx = OPENSSL_malloc(sizeof(OCSP_REQ_CTX));

  if (rctx == NULL) {
    return NULL;
  }
  rctx->state = OHS_ERROR;
  rctx->max_resp_len = OCSP_MAX_RESP_LENGTH;
  rctx->mem = BIO_new(BIO_s_mem());
  rctx->io = io;
  if (maxline > 0) {
    rctx->iobuflen = maxline;
  } else {
    rctx->iobuflen = OCSP_MAX_LINE_LEN;
  }
  rctx->iobuf = OPENSSL_malloc(rctx->iobuflen);
  if (rctx->iobuf == NULL || rctx->mem == NULL) {
    OCSP_REQ_CTX_free(rctx);
    return NULL;
  }
  return rctx;
}

void OCSP_REQ_CTX_free(OCSP_REQ_CTX *rctx) {
  if (rctx == NULL) {
    return;
  }
  BIO_free(rctx->mem);
  OPENSSL_free(rctx->iobuf);
  OPENSSL_free(rctx);
}

OCSP_REQ_CTX *OCSP_sendreq_new(BIO *io, const char *path, OCSP_REQUEST *req,
                               int maxline) {
  OCSP_REQ_CTX *rctx = NULL;
  rctx = OCSP_REQ_CTX_new(io, maxline);
  if (rctx == NULL) {
    return NULL;
  }

  if (!OCSP_REQ_CTX_http(rctx, "POST", path)) {
    goto err;
  }
  if (req != NULL && !OCSP_REQ_CTX_set1_req(rctx, req)) {
    goto err;
  }
  return rctx;

err:
  OCSP_REQ_CTX_free(rctx);
  return NULL;
}

int OCSP_REQ_CTX_http(OCSP_REQ_CTX *rctx, const char *op, const char *path) {
  static const char http_hdr[] = "%s %s HTTP/1.0\r\n";

  // Set to a default path, if path is NULL.
  if (path == NULL) {
    path = "/";
  }

  if (BIO_printf(rctx->mem, http_hdr, op, path) <= 0) {
    return 0;
  }
  rctx->state = OHS_HTTP_HEADER;
  return 1;
}

int OCSP_REQ_CTX_set1_req(OCSP_REQ_CTX *rctx, OCSP_REQUEST *req) {
  return OCSP_REQ_CTX_i2d(rctx, ASN1_ITEM_rptr(OCSP_REQUEST),
                          (ASN1_VALUE *)req);
}

int OCSP_REQ_CTX_add1_header(OCSP_REQ_CTX *rctx, const char *name,
                             const char *value) {
  if (name == NULL) {
    return 0;
  }
  // The following being written conforms to the message-header field
  // specification in https://www.rfc-editor.org/rfc/rfc2616#section-4.2.
  // message-header = field-name ":" [ field-value ]
  if (BIO_puts(rctx->mem, name) <= 0) {
    return 0;
  }
  if (value != NULL) {
    if (BIO_write(rctx->mem, ": ", 2) != 2) {
      return 0;
    }
    if (BIO_puts(rctx->mem, value) <= 0) {
      return 0;
    }
  }
  if (BIO_write(rctx->mem, "\r\n", 2) != 2) {
    return 0;
  }
  rctx->state = OHS_HTTP_HEADER;
  return 1;
}

int OCSP_REQ_CTX_i2d(OCSP_REQ_CTX *rctx, const ASN1_ITEM *it, ASN1_VALUE *val) {
  static const char req_hdr[] =
      "Content-Type: application/ocsp-request\r\n"
      "Content-Length: %d\r\n\r\n";
  int reqlen = ASN1_item_i2d(val, NULL, it);
  if (BIO_printf(rctx->mem, req_hdr, reqlen) <= 0) {
    return 0;
  }
  if (ASN1_item_i2d_bio(it, rctx->mem, val) <= 0) {
    return 0;
  }
  rctx->state = OHS_ASN1_WRITE_INIT;
  return 1;
}

BIO *OCSP_REQ_CTX_get0_mem_bio(OCSP_REQ_CTX *rctx) { return rctx->mem; }

void OCSP_set_max_response_length(OCSP_REQ_CTX *rctx, unsigned long len) {
  if (len == 0) {
    rctx->max_resp_len = OCSP_MAX_RESP_LENGTH;
  } else {
    rctx->max_resp_len = len;
  }
}
