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