blob: 221b5431ddf08b371101c0998dbbd8356a9a6fe8 [file] [edit]
// Copyright 2026 Google LLC
//
// 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
//
// https://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.
//
#include "crypto/default/cms/ber_parser.h"
#include <cstring>
#include <limits>
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "crypto/default/cms/cms_error_code.h"
#include "openssl/base.h"
#include "openssl/bytestring.h"
namespace credentio_cms {
BerParser::BerParser(const uint8_t* data, size_t length) {
CBS cbs;
CBS_init(&cbs, data, length);
scopes_.push_back(Scope(cbs, false));
start_ = data;
}
void BerParser::SetElementName(absl::string_view text) {
scopes_.back().element_name = text;
}
bool BerParser::ok() const { return status_ == ErrorCode::OK; }
ErrorCode BerParser::status() const { return status_; }
std::string BerParser::error_message() const { return error_message_; }
// Checks that the next element has the expected tag. If the element is
// constructed the parser will read from it until CloseTag is called.
void BerParser::GetTag(unsigned expected_tag) {
unsigned tag = 0;
GetTagInternal(&tag, nullptr, nullptr, false /* raw */);
CompareTag(tag, expected_tag, nullptr /* out */);
}
bool BerParser::GetOptionalTag(unsigned expected_tag) {
if (!Peek(expected_tag)) {
return false;
}
GetTag(expected_tag);
return ok();
}
void BerParser::GetElementData(unsigned expected_tag, CBS* out) {
if (!IsValid(out)) {
return;
}
unsigned tag = 0;
GetTagInternal(&tag, out, nullptr /* headers */, false /* raw tag */);
CompareTag(tag, expected_tag, out);
}
bool BerParser::GetOptionalElementData(unsigned expected_tag, CBS* out) {
if (!IsValid(out) || !Peek(expected_tag)) {
return false;
}
GetElementData(expected_tag, out);
return ok();
}
void BerParser::GetAnyElementAndSkipChildren(CBS* out) {
if (!IsValid(out)) {
return;
}
unsigned tag;
GetTagInternal(&tag, nullptr, out, true /* raw */);
}
bool BerParser::Peek(unsigned expected_tag) const {
if (!ok()) {
return false;
}
const Scope& scope = scopes_.back();
return CBS_peek_asn1_tag(&scope.data, expected_tag) == 1;
}
bool BerParser::IsNullOrEmpty() {
if (!ok()) {
return true;
}
Scope& scope = scopes_.back();
if (CBS_len(&scope.data) == 0) {
return true;
}
if (CBS_peek_asn1_tag(&scope.data, CBS_ASN1_NULL) == 1) {
if (CBS_get_asn1(&scope.data, nullptr, CBS_ASN1_NULL) != 1) {
SetError(ErrorCode::ASN1_PARSING_ERROR, "Failed to read NULL element ");
}
return true;
}
return scope.indefinite && IsEndOfContent();
}
int BerParser::GetInt() {
if (!ok()) {
// No-op if there was already an error.
return 0;
}
Scope& scope = scopes_.back();
uint64_t v;
if (CBS_get_asn1_uint64(&scope.data, &v) != 1 ||
v > std::numeric_limits<int>::max()) {
SetError(ErrorCode::ASN1_PARSING_ERROR, "Failed to get an int ");
return 0;
}
return static_cast<int>(v);
}
void BerParser::EndConstructed() {
if (!ok()) {
return;
}
if (scopes_.size() < 2) {
// There should always be at least one element in the scope stack, this
// would pop the last one.
SetError(ErrorCode::ASN1_PARSING_ERROR, "Unexpected EndConstructed call ");
return;
}
Scope& scope = scopes_.back();
if (!scope.indefinite) {
if (CBS_len(&scope.data) > 0) {
SetError(ErrorCode::ASN1_PARSING_ERROR,
"Found data remaining while expecting the end of the current "
"element ");
}
scopes_.pop_back();
return;
}
if (!IsEndOfContent() || CBS_skip(&scope.data, 2) != 1) {
SetError(ErrorCode::ASN1_PARSING_ERROR,
"Error while expecting the end of an indefinite length element ");
return;
}
const auto end = CBS_data(&scope.data);
scopes_.pop_back();
const auto previous = CBS_data(&scopes_.back().data);
if (end < previous || CBS_skip(&scopes_.back().data, end - previous) != 1) {
SetError(
ErrorCode::ASN1_PARSING_ERROR,
"Failed to seek past the end of the current indefinite length tag ");
}
}
bool BerParser::IsValid(CBS* out) {
if (out == nullptr) {
SetError(ErrorCode::INVALID_PARAMETER,
"Caller error, null pointer passed to IsValid ");
return false;
}
CBS_init(out, nullptr, 0);
return status_ == ErrorCode::OK;
}
bool BerParser::IsEndOfContent() {
Scope& scope = scopes_.back();
return CBS_len(&scope.data) >= 2 &&
memcmp(CBS_data(&scope.data), "\0\0", 2) == 0;
}
void BerParser::GetTagInternal(unsigned* tag, CBS* value, CBS* header_and_value,
bool raw_tag) {
if (!ok()) {
return;
}
Scope& scope = scopes_.back();
size_t header_length;
CBS out;
int indefinite_length;
if (CBS_get_any_ber_asn1_element(&scope.data, &out, tag, &header_length,
/*out_ber_found=*/nullptr,
&indefinite_length) != 1) {
SetError(ErrorCode::ASN1_PARSING_ERROR, "Parsing error ");
return;
}
if (indefinite_length && (value != nullptr || header_and_value != nullptr)) {
SetError(
ErrorCode::ASN1_PARSING_ERROR,
"Indefinite length not supported when the element data is needed ");
return;
}
if (header_and_value != nullptr) {
*header_and_value = out;
}
if (value != nullptr) {
*value = out;
if (CBS_skip(value, header_length) != 1) {
SetError(ErrorCode::ASN1_PARSING_ERROR, "Failed to skip the header ");
return;
}
}
if (raw_tag || (*tag & CBS_ASN1_CONSTRUCTED) == 0) {
return;
}
// If the tag is constructed, start a new scope containing either the
// element we just read (definite length) or all the data remaining if the
// length is unknown.
if (indefinite_length) {
scopes_.push_back(Scope(scope.data, indefinite_length));
} else {
if (CBS_skip(&out, header_length) != 1) {
SetError(ErrorCode::ASN1_PARSING_ERROR, "Failed to read the header ");
return;
}
scopes_.push_back(Scope(out, indefinite_length));
}
}
void BerParser::CompareTag(unsigned tag, unsigned expected_tag, CBS* out) {
if (ok() && tag != expected_tag) {
std::string message =
absl::StrCat("Got tag ", tag, " instead of tag ", expected_tag);
SetError(ErrorCode::ASN1_PARSING_ERROR, message);
if (out != nullptr) {
CBS_init(out, nullptr, 0);
}
}
}
void BerParser::SetError(ErrorCode status, absl::string_view text) {
if (!ok()) {
// Don't clear existing errors.
return;
}
status_ = status;
error_message_ =
absl::StrCat(text, " at offset: ", GetCurrentOffset(), " in scope: ");
for (const auto& scope : scopes_) {
if (!scope.element_name.empty()) {
absl::StrAppend(&error_message_, scope.element_name, " > ");
}
}
}
size_t BerParser::GetCurrentOffset() {
if (scopes_.empty()) {
return 0;
}
return CBS_data(&scopes_.back().data) - start_;
}
} // namespace credentio_cms