Beispiel #1
0
// ResponseData ::= SEQUENCE {
//    version             [0] EXPLICIT Version DEFAULT v1,
//    responderID             ResponderID,
//    producedAt              GeneralizedTime,
//    responses               SEQUENCE OF SingleResponse,
//    responseExtensions  [1] EXPLICIT Extensions OPTIONAL }
static inline der::Result
ResponseData(der::Input& input, Context& context,
             const CERTSignedData& signedResponseData,
             /*const*/ SECItem* certs, size_t numCerts)
{
  uint8_t version;
  if (der::OptionalVersion(input, version) != der::Success) {
    return der::Failure;
  }
  if (version != der::v1) {
    // TODO: more specific error code for bad version?
    return der::Fail(SEC_ERROR_BAD_DER);
  }

  // ResponderID ::= CHOICE {
  //    byName              [1] Name,
  //    byKey               [2] KeyHash }
  SECItem responderID;
  uint16_t responderIDLength;
  ResponderIDType responderIDType
    = input.Peek(static_cast<uint8_t>(ResponderIDType::byName))
    ? ResponderIDType::byName
    : ResponderIDType::byKey;
  if (ExpectTagAndGetLength(input, static_cast<uint8_t>(responderIDType),
                            responderIDLength) != der::Success) {
    return der::Failure;
  }
  // TODO: responderID probably needs to have another level of ASN1 tag/length
  // checked and stripped.
  if (input.Skip(responderIDLength, responderID) != der::Success) {
    return der::Failure;
  }

  // This is the soonest we can verify the signature. We verify the signature
  // right away to follow the principal of minimizing the processing of data
  // before verifying its signature.
  if (VerifySignature(context, responderIDType, responderID, certs, numCerts,
                      signedResponseData) != SECSuccess) {
    return der::Failure;
  }

  // TODO: Do we even need to parse this? Should we just skip it?
  PRTime producedAt;
  if (der::GeneralizedTime(input, producedAt) != der::Success) {
    return der::Failure;
  }

  // We don't accept an empty sequence of responses. In practice, a legit OCSP
  // responder will never return an empty response, and handling the case of an
  // empty response makes things unnecessarily complicated.
  if (der::NestedOf(input, der::SEQUENCE, der::SEQUENCE,
                    der::MustNotBeEmpty,
                    bind(SingleResponse, _1, ref(context))) != der::Success) {
    return der::Failure;
  }

  if (!input.AtEnd()) {
    if (CheckExtensionsForCriticality(input) != der::Success) {
      return der::Failure;
    }
  }

  return der::Success;
}
Beispiel #2
0
// SingleResponse ::= SEQUENCE {
//    certID                       CertID,
//    certStatus                   CertStatus,
//    thisUpdate                   GeneralizedTime,
//    nextUpdate           [0]     EXPLICIT GeneralizedTime OPTIONAL,
//    singleExtensions     [1]     EXPLICIT Extensions{{re-ocsp-crl |
//                                              re-ocsp-archive-cutoff |
//                                              CrlEntryExtensions, ...}
//                                              } OPTIONAL }
static inline der::Result
SingleResponse(der::Input& input, Context& context)
{
  bool match = false;
  if (der::Nested(input, der::SEQUENCE,
                  bind(CertID, _1, cref(context), ref(match)))
        != der::Success) {
    return der::Failure;
  }

  if (!match) {
    // This response does not reference the certificate we're interested in.
    // By consuming the rest of our input and returning successfully, we can
    // continue processing and examine another response that might have what
    // we want.
    input.SkipToEnd();
    return der::Success;
  }

  // CertStatus ::= CHOICE {
  //     good        [0]     IMPLICIT NULL,
  //     revoked     [1]     IMPLICIT RevokedInfo,
  //     unknown     [2]     IMPLICIT UnknownInfo }
  //
  // In the event of multiple SingleResponses for a cert that have conflicting
  // statuses, we use the following precedence rules:
  //
  // * revoked overrides good and unknown
  // * good overrides unknown
  if (input.Peek(static_cast<uint8_t>(CertStatus::Good))) {
    if (ExpectTagAndLength(input, static_cast<uint8_t>(CertStatus::Good), 0)
          != der::Success) {
      return der::Failure;
    }
    if (context.certStatus != CertStatus::Revoked) {
      context.certStatus = CertStatus::Good;
    }
  } else if (input.Peek(static_cast<uint8_t>(CertStatus::Revoked))) {
    // We don't need any info from the RevokedInfo structure, so we don't even
    // parse it. TODO: We should mention issues like this in the explanation of
    // why we treat invalid OCSP responses equivalently to revoked for OCSP
    // stapling.
    if (der::Skip(input, static_cast<uint8_t>(CertStatus::Revoked))
          != der::Success) {
      return der::Failure;
    }
    context.certStatus = CertStatus::Revoked;
  } else if (ExpectTagAndLength(input,
                                static_cast<uint8_t>(CertStatus::Unknown),
                                0) != der::Success) {
    return der::Failure;
  }

  // http://tools.ietf.org/html/rfc6960#section-3.2
  // 5. The time at which the status being indicated is known to be
  //    correct (thisUpdate) is sufficiently recent;
  // 6. When available, the time at or before which newer information will
  //    be available about the status of the certificate (nextUpdate) is
  //    greater than the current time.

  // We won't accept any OCSP responses that are more than 10 days old, even if
  // the nextUpdate time is further in the future.
  static const PRTime OLDEST_ACCEPTABLE = INT64_C(10) * ONE_DAY;

  PRTime thisUpdate;
  if (der::GeneralizedTime(input, thisUpdate) != der::Success) {
    return der::Failure;
  }

  if (thisUpdate > context.time + SLOP) {
    return der::Fail(SEC_ERROR_OCSP_FUTURE_RESPONSE);
  }

  PRTime notAfter;
  static const uint8_t NEXT_UPDATE_TAG =
    der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0;
  if (input.Peek(NEXT_UPDATE_TAG)) {
    PRTime nextUpdate;
    if (der::Nested(input, NEXT_UPDATE_TAG,
                    bind(der::GeneralizedTime, _1, ref(nextUpdate)))
          != der::Success) {
      return der::Failure;
    }

    if (nextUpdate < thisUpdate) {
      return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
    }
    if (nextUpdate - thisUpdate <= OLDEST_ACCEPTABLE) {
      notAfter = nextUpdate;
    } else {
      notAfter = thisUpdate + OLDEST_ACCEPTABLE;
    }
  } else {
    // NSS requires all OCSP responses without a nextUpdate to be recent.
    // Match that stricter behavior.
    notAfter = thisUpdate + ONE_DAY;
  }

  if (context.time < SLOP) { // prevent underflow
    return der::Fail(SEC_ERROR_INVALID_ARGS);
  }
  if (context.time - SLOP > notAfter) {
    return der::Fail(SEC_ERROR_OCSP_OLD_RESPONSE);
  }


  if (!input.AtEnd()) {
    if (CheckExtensionsForCriticality(input) != der::Success) {
      return der::Failure;
    }
  }

  if (context.thisUpdate) {
    *context.thisUpdate = thisUpdate;
  }
  if (context.validThrough) {
    *context.validThrough = notAfter;
  }

  return der::Success;
}
Beispiel #3
0
// BasicOCSPResponse       ::= SEQUENCE {
//    tbsResponseData      ResponseData,
//    signatureAlgorithm   AlgorithmIdentifier,
//    signature            BIT STRING,
//    certs            [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
der::Result
BasicResponse(der::Input& input, Context& context)
{
  der::Input::Mark mark(input.GetMark());

  uint16_t length;
  if (der::ExpectTagAndGetLength(input, der::SEQUENCE, length)
        != der::Success) {
    return der::Failure;
  }

  // The signature covers the entire DER encoding of tbsResponseData, including
  // the beginning tag and length. However, when we're parsing tbsResponseData,
  // we want to strip off the tag and length because we don't need it after
  // we've confirmed it's there and figured out what length it is.

  der::Input tbsResponseData;

  if (input.Skip(length, tbsResponseData) != der::Success) {
    return der::Failure;
  }

  CERTSignedData signedData;

  input.GetSECItem(siBuffer, mark, signedData.data);

  if (der::Nested(input, der::SEQUENCE,
                  bind(der::AlgorithmIdentifier, _1,
                       ref(signedData.signatureAlgorithm))) != der::Success) {
    return der::Failure;
  }

  if (der::Skip(input, der::BIT_STRING, signedData.signature) != der::Success) {
    return der::Failure;
  }
  if (signedData.signature.len == 0) {
    return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE);
  }
  unsigned int unusedBitsAtEnd = signedData.signature.data[0];
  // XXX: Really the constraint should be that unusedBitsAtEnd must be less
  // than 7. But, we suspect there are no valid OCSP response signatures with
  // non-zero unused bits. It seems like NSS assumes this in various places, so
  // we enforce it. If we find compatibility issues, we'll know we're wrong.
  if (unusedBitsAtEnd != 0) {
    return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE);
  }
  ++signedData.signature.data;
  --signedData.signature.len;
  signedData.signature.len = (signedData.signature.len << 3); // Bytes to bits

  // Parse certificates, if any

  SECItem certs[8];
  size_t numCerts = 0;

  if (!input.AtEnd()) {
    // We ignore the lengths of the wrappers because we'll detect bad lengths
    // during parsing--too short and we'll run out of input for parsing a cert,
    // and too long and we'll have leftover data that won't parse as a cert.

    // [0] wrapper
    if (der::ExpectTagAndIgnoreLength(
          input, der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0)
        != der::Success) {
      return der::Failure;
    }

    // SEQUENCE wrapper
    if (der::ExpectTagAndIgnoreLength(input, der::SEQUENCE) != der::Success) {
      return der::Failure;
    }

    // sequence of certificates
    while (!input.AtEnd()) {
      if (numCerts == PR_ARRAY_SIZE(certs)) {
        return der::Fail(SEC_ERROR_BAD_DER);
      }

      // Unwrap the SEQUENCE that contains the certificate, which is itself a
      // SEQUENCE.
      der::Input::Mark mark(input.GetMark());
      if (der::Skip(input, der::SEQUENCE) != der::Success) {
        return der::Failure;
      }

      input.GetSECItem(siBuffer, mark, certs[numCerts]);
      ++numCerts;
    }
  }

  return ResponseData(tbsResponseData, context, signedData, certs, numCerts);
}