blob: d21669b2634eb3987c99ea1a6f2af12266795e95 [file]
// 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.
//
#ifndef THIRD_PARTY_CREDENTIO_CRYPTO_DEFAULT_CMS_BER_PARSER_H_
#define THIRD_PARTY_CREDENTIO_CRYPTO_DEFAULT_CMS_BER_PARSER_H_
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "crypto/default/cms/cms_error_code.h"
#include "openssl/bytestring.h"
namespace credentio_cms {
// Zero copy ASN.1 parser supporting the minimum amount of BER encoding needed
// to work with the S/MIME clients targeted.
//
// This parser reads the elements in the order they appear, when a constructed
// element is read, the next element will be read from its value.
//
// Example if the data contains this:
// SEQUENCE
// INTEGER 1
// OCTECT_STRING 'foo'
// SET
// ...
// The elements read will be in order: SEQUENCE, INTEGER, OCTET_STRING,
// SET.
//
// Error handling: the parser keeps track of the first error. Once an error
// is encountered all the functions will effectively be no-op.
// This allows the caller to postpone error checking until the end of the
// parsing and makes the code easier to read.
class BerParser {
public:
// The data must be valid for the life of the BerParser object.
BerParser(const uint8_t* data, size_t length);
// Returns true if the parsing is successful, or false if there was an error.
// Errors are sticky and once the parser encounters an error (or SetError is
// called).
bool ok() const;
// Returns the current status.
ErrorCode status() const;
// Returns the text for the error, if any.
std::string error_message() const;
// Checks that the next element has the expected tag and read it.
// If the element is constructed the parser will read from its value until
// EndConstructedElement is called.
// If the next element does not match an error will be set and ok() will
// always return false.
void GetTag(unsigned expected_tag);
// If the next element has a tag equals to expected_tag this function behaves
// like GetTag, otherwise it is a no-op.
// Returns true if the expected_tag was found and read.
bool GetOptionalTag(unsigned expected_tag);
// If the next element has a tag equal to expected_tag, read it and make
// 'out' point to the element value, otherwise set an error condition and
// clear the 'out' parameter.
// If the element is constructed the parser will read from its value until
// EndConstructedElement is called.
//
// Restriction: the element must have a definite length.
void GetElementData(unsigned expected_tag, CBS* out);
// If the next element has a tag equal to 'expected_tag' this function behaves
// like GetElementData, otherwise it is a no-op
bool GetOptionalElementData(unsigned expected_tag, CBS* out);
// Indicate that parsing is done for the current constructed element, check
// that there is no data left in it and keep reading from its siblings.
// If the current constructed element had an indefinite length, also check for
// the end of content marker.
void EndConstructed();
// Read the next element regardless of its tag and put the header and data in
// the 'out' parameter. Its contents will be skipped even if it is a
// constructed element (so no need to call EndConstructedElement).
// Restriction: the element must have a definite length.
void GetAnyElementAndSkipChildren(CBS* out);
// Returns true if there is no error and the next element matches the
// expected tag.
bool Peek(unsigned expected_tag) const;
// Returns the offset of the current tag byte relative to the start of the
// 'data' passed in the constructor.
size_t GetCurrentOffset();
// Returns true if:
// - The next element has the NULL type (the null will be consumed).
// - The current parsing scope is empty (reached the end of the data).
// - The next element is the End Of Content marker and the scope was an
// indefinite length element.
// - There was an earlier parsing error (to indicate that nothing more
// will be read).
bool IsNullOrEmpty();
// Checks that the next element has an INTEGER tag and its value fits in an
// int. The element is consumed.
// If there is an error return zero.
int GetInt();
// The text passed in will be added to the error message if an error happens
// in the current parsing context.
// This has no effect on parsing but allows error messages to be more
// informative.
void SetElementName(absl::string_view text);
// If the parser is not already in an error state, this will set the internal
// status and build an error message using 'text' and appending the byte
// offset and scope chain.
// If 'status' is OK this function is no-op (i.e. it doesn't clear existing
// errors).
void SetError(ErrorCode status, absl::string_view text);
private:
struct Scope {
Scope(const CBS& data, bool indefinite)
: data(data), indefinite(indefinite) {}
// If this is the first stack level it points to the complete data.
// If we are currently parsing a constructed element value, if indefinite_
// is false it contains the element value, otherwise it contains all the
// data that is left to parse.
CBS data;
// Indicates if 'data' belongs to a definite or indefinite length
// constructed element.
bool indefinite = false;
// Used to provide more useful error messages.
std::string element_name;
};
// Sets out data to nullptr and its length to 0.
// Returns false if an error occurred.
bool IsValid(CBS* out);
// Returns true if the next two bytes are 0,0 (ASN.1 End Of Content), does not
// consume the EOC marker.
bool IsEndOfContent();
// Reads the next element.
// - out_tag will contain the tag value read.
// - raw_tag: if it is false and the element is constructed, a new scope will
// be pushed and the next tags will be read from its value. If it is true
// the element value will be skipped by the parser.
// - out_value will be set to the element data if it is not null.
// - out_value will be set to the element header and data if it is not null.
void GetTagInternal(unsigned* out_tag, CBS* out_value,
CBS* out_header_and_value, bool raw_tag);
// Calls SetError if tag != expected_tag and clears out if it is not null.
void CompareTag(unsigned tag, unsigned expected_tag, CBS* out);
std::string error_message_;
ErrorCode status_ = ErrorCode::OK;
// Contains the start of the data being parsed, only used to show offsets in
// error messages.
const uint8_t* start_;
// Each time a constructed tag is entered a new scope is pushed. If the length
// is indefinite the data contains all the remaining data.
// Each time CloseTag is called, a scope is popped, if the length was
// indefinite the next read will start right after the scope just popped.
std::vector<Scope> scopes_;
};
} // namespace credentio_cms
#endif // THIRD_PARTY_CREDENTIO_CRYPTO_DEFAULT_CMS_BER_PARSER_H_