| // 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_ |