blob: 0c6ee9f5845eea7c66f884d399637b41100d821f [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.
//
#include "crypto/default/cms/cms_parser.h"
#include <stdint.h>
#include <string>
#include <vector>
#include "crypto/default/cms/ber_parser.h"
#include "crypto/default/cms/cms_error_code.h"
#include "crypto/default/cms/oids.h"
#include "openssl/base.h"
#include "openssl/bytestring.h"
namespace credentio_cms {
namespace {
constexpr unsigned ContextSpecificConstructed(int n) {
return CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | n;
}
constexpr unsigned ContextSpecific(int n) {
return CBS_ASN1_CONTEXT_SPECIFIC | n;
}
// Scoped class making it easier to keep track of the constructed tag by
// calling EndConstructed on destruction.
class ScopedConstructedElement {
public:
// Only calls EndConstructed when destroyed, use it when the constructed tag
// was already read (usually with GetOptionalTag).
explicit ScopedConstructedElement(BerParser* parser, const char* hint)
: parser_(parser) {
parser->SetElementName(hint);
}
// Reads expected_tag, provides a scope name for the error messages and calls
// EndConstructed when destroyed.
ScopedConstructedElement(unsigned expected_tag, const char* hint,
BerParser* parser)
: parser_(parser) {
parser->GetTag(expected_tag);
parser->SetElementName(hint);
}
~ScopedConstructedElement() { parser_->EndConstructed(); }
private:
BerParser* parser_; // Not owned.
};
// Some applications encode OCTET STRING as a constructed element composed
// of octet strings.
void ParseBerOctetString(int tag, BerParser* parser,
std::vector<ByteString>* data) {
data->clear();
if (parser->Peek(tag)) {
data->push_back(ByteString());
parser->GetElementData(tag, data->back().cbs_ptr());
return;
}
ScopedConstructedElement constructed_string(tag | CBS_ASN1_CONSTRUCTED,
"BerOctetString", parser);
for (; !parser->IsNullOrEmpty();) {
data->push_back(ByteString());
parser->GetElementData(CBS_ASN1_OCTETSTRING, data->back().cbs_ptr());
}
}
// Expect a sequence with a single OID in it.
void ParseAlgorithmIdentifier(const char* hint, BerParser* parser,
AlgorithmIdentifier* id) {
parser->GetAnyElementAndSkipChildren(id->raw_value.cbs_ptr());
// Extract the algorithm identifier parts.
// https://tools.ietf.org/html/rfc5280#section-4.1.1.2
BerParser id_parser(CBS_data(id->raw_value.cbs_ptr()),
CBS_len(id->raw_value.cbs_ptr()));
id_parser.GetTag(CBS_ASN1_SEQUENCE);
id_parser.GetElementData(CBS_ASN1_OBJECT, id->algorithm_oid.cbs_ptr());
if (!id_parser.IsNullOrEmpty()) {
id_parser.GetAnyElementAndSkipChildren(id->parameter.cbs_ptr());
}
id_parser.EndConstructed();
if (!id_parser.ok()) {
parser->SetError(id_parser.status(), hint);
}
}
template <typename T>
void GetIssuerAndSerialNumber(BerParser* parser, T* ids) {
ScopedConstructedElement issuer_and_serial_number(
CBS_ASN1_SEQUENCE, "Issuer and serial number", parser);
parser->GetAnyElementAndSkipChildren(ids->issuer_name.cbs_ptr());
parser->GetElementData(CBS_ASN1_INTEGER, ids->serial_number.cbs_ptr());
}
// Sn = Serial Number
// Ski = Subject Key Identifier
template <typename T>
void GetIssuerAndSnOrSki(BerParser* parser, T* ids) {
if (parser->Peek(CBS_ASN1_SEQUENCE)) {
GetIssuerAndSerialNumber(parser, ids);
} else if (parser->Peek(ContextSpecific(0))) {
parser->GetElementData(ContextSpecific(0),
ids->subject_key_identifier.cbs_ptr());
}
}
// http://tools.ietf.org/html/rfc5652#section-6.2.1
void ParseKeyTransRecipientInfo(BerParser* parser, RecipientInfo* recipient) {
ScopedConstructedElement key_agree(parser, "KeyTransRecipientInfo");
parser->SetElementName("RecipientInfo");
recipient->version = parser->GetInt();
if (recipient->version != 0 && recipient->version != 2) {
parser->SetError(static_cast<ErrorCode>(ErrorCode::UNSUPPORTED),
"Unsupported recipient version");
return;
}
recipient->encrypted_keys.push_back(RecipientEncryptedKey());
GetIssuerAndSnOrSki(parser, &recipient->encrypted_keys.back());
ParseAlgorithmIdentifier("Key Encryption Algorithm", parser,
&recipient->key_encryption_algorithm);
parser->GetElementData(
CBS_ASN1_OCTETSTRING,
recipient->encrypted_keys.back().encrypted_key.cbs_ptr());
}
void ParseRecipientEncryptedKey(BerParser* parser, RecipientEncryptedKey* key) {
ScopedConstructedElement recipient_key(CBS_ASN1_SEQUENCE,
"RecicientEncryptedKey", parser);
if (parser->Peek(CBS_ASN1_SEQUENCE)) {
GetIssuerAndSerialNumber(parser, key);
} else {
ScopedConstructedElement rid(ContextSpecificConstructed(0),
"RecipientKeyIdentifier", parser);
GetIssuerAndSerialNumber(parser, key);
parser->GetOptionalElementData(CBS_ASN1_GENERALIZEDTIME,
key->date.cbs_ptr());
if (!parser->IsNullOrEmpty()) {
parser->GetAnyElementAndSkipChildren(key->other.cbs_ptr());
}
}
parser->GetElementData(CBS_ASN1_OCTETSTRING, key->encrypted_key.cbs_ptr());
}
// http://tools.ietf.org/html/rfc5652#section-6.2.2
void ParseOriginatorPublicKey(BerParser* parser,
OriginatorIdentifierOrKey* oik) {
ScopedConstructedElement opk(parser, "OriginatorPublicKey");
ParseAlgorithmIdentifier("Public Key Algorithm", parser,
&oik->public_key_algorithm);
parser->GetElementData(CBS_ASN1_BITSTRING, oik->public_key_value.cbs_ptr());
}
// http://tools.ietf.org/html/rfc5652#section-6.2.2
void ParseOriginatorIdentifierOrKey(BerParser* parser,
OriginatorIdentifierOrKey* oik) {
ScopedConstructedElement originator(ContextSpecificConstructed(0),
"OriginatorIdentifierOrKey", parser);
if (parser->Peek(CBS_ASN1_SEQUENCE)) {
GetIssuerAndSerialNumber(parser, oik);
} else if (parser->GetOptionalTag(ContextSpecificConstructed(0))) {
parser->GetElementData(CBS_ASN1_OCTETSTRING,
oik->subject_key_identifier.cbs_ptr());
parser->EndConstructed();
} else if (parser->GetOptionalTag(ContextSpecificConstructed(1))) {
ParseOriginatorPublicKey(parser, oik);
} else {
parser->SetError(static_cast<ErrorCode>(ErrorCode::UNSUPPORTED),
"Unsupported originator tag");
}
}
// http://tools.ietf.org/html/rfc5652#section-6.2.2
void ParseKeyAgreeRecipientInfo(BerParser* parser, RecipientInfo* recipient) {
ScopedConstructedElement key_agree(parser, "KeyAgreeRecipientInfo");
recipient->version = parser->GetInt();
if (recipient->version != 3) {
parser->SetError(static_cast<ErrorCode>(ErrorCode::UNSUPPORTED),
"Unsupported key agree recipient version");
return;
}
ParseOriginatorIdentifierOrKey(parser,
&recipient->originator_identifier_or_key);
// User Key Material (UKM)
if (parser->GetOptionalTag(ContextSpecificConstructed(1))) {
parser->GetElementData(CBS_ASN1_OCTETSTRING,
recipient->user_keying_material.cbs_ptr());
parser->EndConstructed();
}
ParseAlgorithmIdentifier("Key Encryption Algorithm", parser,
&recipient->key_encryption_algorithm);
ScopedConstructedElement recipient_keys(CBS_ASN1_SEQUENCE,
"RecipientEncryptedKeys", parser);
for (; !parser->IsNullOrEmpty();) {
RecipientEncryptedKey key;
ParseRecipientEncryptedKey(parser, &key);
recipient->encrypted_keys.push_back(key);
}
}
// https://tools.ietf.org/html/rfc5751#section-2.5.3
bool ExtractSmimeEncryptionKeyPreference(SignerInfo* signer,
ByteString key_preference) {
SmimeEncryptionKeyPreference& preference = signer->encryption_key_preference;
BerParser parser(CBS_data(key_preference.cbs_ptr()),
CBS_len(key_preference.cbs_ptr()));
if (parser.Peek(ContextSpecificConstructed(0))) {
ScopedConstructedElement issuer_sn(ContextSpecificConstructed(0),
"IssuerAndSerialNumber", &parser);
parser.GetAnyElementAndSkipChildren(preference.issuer_name.cbs_ptr());
parser.GetElementData(CBS_ASN1_INTEGER, preference.serial_number.cbs_ptr());
} else if (parser.Peek(ContextSpecificConstructed(1))) {
ScopedConstructedElement issuer_sn(ContextSpecificConstructed(1),
"RecipientKeyIdentifier", &parser);
parser.GetElementData(CBS_ASN1_OCTETSTRING,
preference.subject_key_identifier.cbs_ptr());
parser.GetOptionalElementData(CBS_ASN1_GENERALIZEDTIME,
preference.date.cbs_ptr());
parser.GetAnyElementAndSkipChildren(preference.other.cbs_ptr());
} else if (parser.Peek(ContextSpecificConstructed(2))) {
parser.GetElementData(ContextSpecific(2),
preference.subject_key_identifier.cbs_ptr());
} else {
return false;
}
return parser.ok();
}
bool ExtractSingleValue(const Attribute& attribute, unsigned expected_tag,
CBS* out) {
if (attribute.values.size() != 1) {
return false;
}
CBS copy;
CBS_init(&copy, CBS_data(attribute.values[0].cbs_ptr()),
CBS_len(attribute.values[0].cbs_ptr()));
return CBS_get_asn1(&copy, out, expected_tag) == 1;
}
bool ExtractCommonAttributes(SignerInfo* signer) {
bool status = true;
for (const auto& attribute : signer->signed_attributes) {
if (CompareOid(attribute.type.cbs(), kContentAttributeType,
sizeof(kContentAttributeType))) {
status &= ExtractSingleValue(attribute, CBS_ASN1_OBJECT,
signer->content_type_signed.cbs_ptr());
} else if (CompareOid(attribute.type.cbs(), kContentAttributeMessageDigest,
sizeof(kContentAttributeMessageDigest))) {
status &= ExtractSingleValue(attribute, CBS_ASN1_OCTETSTRING,
signer->message_digest.cbs_ptr());
} else if (CompareOid(attribute.type.cbs(),
kSmimeEncryptionKeyPreferenceOid,
sizeof(kSmimeEncryptionKeyPreferenceOid))) {
status &= (attribute.values.size() == 1);
if (status) {
status &=
ExtractSmimeEncryptionKeyPreference(signer, attribute.values[0]);
}
}
}
return status;
}
// Extracts the message digest and content type from the signed attributes.
// http://tools.ietf.org/html/rfc5652#section-5.3
void ParseSignedAttributes(BerParser* parser, SignerInfo* signer) {
// Keep the raw signed attributes to make the signature check easier.
parser->GetAnyElementAndSkipChildren(signer->raw_signed_attributes.cbs_ptr());
// Extract the content type and content digest.
BerParser att_parser(CBS_data(signer->raw_signed_attributes.cbs_ptr()),
CBS_len(signer->raw_signed_attributes.cbs_ptr()));
att_parser.GetTag(ContextSpecificConstructed(0));
for (; !att_parser.IsNullOrEmpty();) {
ScopedConstructedElement seq(CBS_ASN1_SEQUENCE, "Authenticated Attributes",
&att_parser);
signer->signed_attributes.push_back(Attribute());
Attribute& attribute = signer->signed_attributes.back();
att_parser.GetElementData(CBS_ASN1_OBJECT, attribute.type.cbs_ptr());
ScopedConstructedElement s(CBS_ASN1_SET, "Signed attribute values",
&att_parser);
for (; !att_parser.IsNullOrEmpty();) {
attribute.values.push_back(ByteString());
att_parser.GetAnyElementAndSkipChildren(
attribute.values.back().cbs_ptr());
}
}
att_parser.EndConstructed();
if (!att_parser.ok()) {
parser->SetError(att_parser.status(),
"Failed to parse the signed attributes");
return;
}
if (!ExtractCommonAttributes(signer)) {
parser->SetError(att_parser.status(),
"Invalid content type or digest attributes");
}
}
// Extract the unsigned attributes for anybody who needs to look at them.
// http://tools.ietf.org/html/rfc5652#section-5.3
void ParseUnsignedAttributes(BerParser* parser, SignerInfo* signer) {
parser->GetAnyElementAndSkipChildren(
signer->raw_unsigned_attributes.cbs_ptr());
BerParser att_parser(CBS_data(signer->raw_unsigned_attributes.cbs_ptr()),
CBS_len(signer->raw_unsigned_attributes.cbs_ptr()));
att_parser.GetTag(ContextSpecificConstructed(1));
for (; !att_parser.IsNullOrEmpty();) {
ScopedConstructedElement seq(CBS_ASN1_SEQUENCE, "Unsigned Attributes",
&att_parser);
signer->unsigned_attributes.push_back(Attribute());
Attribute& attribute = signer->unsigned_attributes.back();
att_parser.GetElementData(CBS_ASN1_OBJECT, attribute.type.cbs_ptr());
ScopedConstructedElement s(CBS_ASN1_SET, "Unsigned attribute values",
&att_parser);
for (; !att_parser.IsNullOrEmpty();) {
attribute.values.push_back(ByteString());
att_parser.GetAnyElementAndSkipChildren(
attribute.values.back().cbs_ptr());
}
}
att_parser.EndConstructed();
if (!att_parser.ok()) {
parser->SetError(att_parser.status(),
"Failed to parse the unsigned attributes");
}
}
// http://tools.ietf.org/html/rfc5652#section-5.3
static void ParseSignerInfoInternal(BerParser* parser, SignerInfo* signer) {
ScopedConstructedElement signer_info(CBS_ASN1_SEQUENCE, "SignerInfo", parser);
signer->version = parser->GetInt();
if (signer->version != 1 && signer->version != 3) {
parser->SetError(ErrorCode::UNSUPPORTED, "Unsupported signer version");
return;
}
GetIssuerAndSnOrSki(parser, signer);
ParseAlgorithmIdentifier("Digest Algorithm", parser,
&signer->digest_algorithm);
// Authenticated attributes
if (parser->Peek(ContextSpecificConstructed(0))) {
ParseSignedAttributes(parser, signer);
}
ParseAlgorithmIdentifier("Signature Algorithm", parser,
&signer->signature_algorithm);
parser->GetElementData(CBS_ASN1_OCTETSTRING,
signer->signature_value.cbs_ptr());
// Implicit tag 1: Unauthenticated attributes
if (parser->Peek(ContextSpecificConstructed(1))) {
ParseUnsignedAttributes(parser, signer);
}
}
void ParseCertificates(BerParser* parser,
std::vector<ByteString>* certificates) {
ScopedConstructedElement s(ContextSpecificConstructed(0), "Certificates",
parser);
for (; !parser->IsNullOrEmpty();) {
ByteString out;
parser->GetAnyElementAndSkipChildren(out.cbs_ptr());
certificates->push_back(out);
}
}
// Certificates Revocation Lists are ignored.
void ParseCrls(BerParser* parser) {
ScopedConstructedElement s(ContextSpecificConstructed(1), "CRL", parser);
for (; !parser->IsNullOrEmpty();) {
ByteString out;
parser->GetAnyElementAndSkipChildren(out.cbs_ptr());
}
}
// https://tools.ietf.org/html/rfc5652#section-5.1
void ParseSignedData(BerParser* parser, Content* content) {
content->type = ContentType::SIGNED_DATA;
ScopedConstructedElement signed_data(CBS_ASN1_SEQUENCE, "SignedData", parser);
content->version = parser->GetInt();
{
ScopedConstructedElement algorigthm_set(CBS_ASN1_SET, "AlgorithmSet",
parser);
for (; !parser->IsNullOrEmpty();) {
content->digest_algorithms.push_back(AlgorithmIdentifier());
ParseAlgorithmIdentifier("DigestAlgorithm", parser,
&content->digest_algorithms.back());
}
}
{
ScopedConstructedElement contentInfo(CBS_ASN1_SEQUENCE, "Content", parser);
parser->GetElementData(CBS_ASN1_OBJECT, content->content_type.cbs_ptr());
content->content_tag = 0;
if (parser->GetOptionalTag(ContextSpecificConstructed(0))) {
if (parser->Peek(CBS_ASN1_OCTETSTRING) ||
parser->Peek(CBS_ASN1_OCTETSTRING | CBS_ASN1_CONSTRUCTED)) {
content->content_offsets.first = parser->GetCurrentOffset();
ParseBerOctetString(CBS_ASN1_OCTETSTRING, parser, &content->content);
content->content_offsets.second = parser->GetCurrentOffset();
content->content_tag = CBS_ASN1_OCTETSTRING;
} else if (parser->Peek(CBS_ASN1_SEQUENCE)) {
// PKCS#7 compatibility.
// See https://tools.ietf.org/html/rfc5652#section-5.2.1
content->content_offsets.first = parser->GetCurrentOffset();
ByteString bytes;
parser->GetAnyElementAndSkipChildren(bytes.cbs_ptr());
content->content_offsets.second = parser->GetCurrentOffset();
content->content.push_back(bytes);
content->content_tag = CBS_ASN1_SEQUENCE;
} else {
parser->SetError(ErrorCode::UNSUPPORTED, "Unexpected type");
}
parser->EndConstructed(); // Content
}
}
// Certificates
if (parser->Peek(ContextSpecificConstructed(0))) {
ParseCertificates(parser, &content->certificates);
}
// Certificate Revocation List are ignored.
if (parser->Peek(ContextSpecificConstructed(1))) {
ParseCrls(parser);
}
// Signers
{
ScopedConstructedElement signers(CBS_ASN1_SET, "Signers", parser);
for (; !parser->IsNullOrEmpty();) {
content->signers.push_back(SignerInfo());
ParseSignerInfoInternal(parser, &content->signers.back());
}
}
}
// http://tools.ietf.org/html/rfc5652#section-6.1
void ParseEncryptedContentInfo(BerParser* parser, Content* content) {
ScopedConstructedElement s(CBS_ASN1_SEQUENCE, "EncryptedContentInfo", parser);
CBS oid;
parser->GetElementData(CBS_ASN1_OBJECT, &oid);
if (!CompareOid(oid, kDataOid, sizeof(kDataOid))) {
parser->SetError(ErrorCode::UNSUPPORTED, "Unexpected content type.");
return;
}
ParseAlgorithmIdentifier("content encryption algorithm", parser,
&content->encryption_algorithm);
if (parser->Peek(CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED) ||
parser->Peek(CBS_ASN1_CONTEXT_SPECIFIC)) {
content->content_offsets.first = parser->GetCurrentOffset();
ParseBerOctetString(CBS_ASN1_CONTEXT_SPECIFIC, parser, &content->content);
content->content_offsets.second = parser->GetCurrentOffset();
} else {
content->content.clear();
}
}
// http://tools.ietf.org/html/rfc5652#section-6.2
void ParseRecipientsInfo(BerParser* parser, Content* content) {
ScopedConstructedElement recipients(CBS_ASN1_SET, "Recipients", parser);
for (; !parser->IsNullOrEmpty();) {
content->recipients.push_back(RecipientInfo());
if (parser->GetOptionalTag(CBS_ASN1_SEQUENCE)) {
// http://tools.ietf.org/html/rfc2630#section-6.2.1
ParseKeyTransRecipientInfo(parser, &content->recipients.back());
} else if (parser->GetOptionalTag(ContextSpecificConstructed(1))) {
// http://tools.ietf.org/html/rfc2630#section-6.2.2
ParseKeyAgreeRecipientInfo(parser, &content->recipients.back());
} else {
// Unsupported:
// KEKRecipientInfo, PasswordRecipientInfo, OtherRecipientInfo
parser->SetError(ErrorCode::UNSUPPORTED, "Unsupported recipient type.");
return;
}
}
}
// https://tools.ietf.org/html/rfc5652#section-6.1
void ParseEnvelopedData(BerParser* parser, Content* content) {
content->type = ContentType::ENVELOPED_DATA;
ScopedConstructedElement enveloped_data(CBS_ASN1_SEQUENCE, "EnvelopedData",
parser);
content->version = parser->GetInt();
if (content->version != 0 && content->version != 2) {
parser->SetError(ErrorCode::UNSUPPORTED, "Unsupported envelope version.");
return;
}
if (parser->Peek(ContextSpecificConstructed(0))) {
ScopedConstructedElement s(ContextSpecificConstructed(0), "OriginatorInfo",
parser);
if (parser->Peek(ContextSpecificConstructed(0))) {
ParseCertificates(parser, &content->certificates);
}
if (parser->Peek(ContextSpecificConstructed(1))) {
ParseCrls(parser);
}
}
ParseRecipientsInfo(parser, content);
ParseEncryptedContentInfo(parser, content);
// UnprotectedAttributes are not used in this implementation.
if (parser->Peek(ContextSpecificConstructed(1))) {
CBS dummy;
parser->GetAnyElementAndSkipChildren(&dummy);
}
}
} // namespace
// RFC 5652 (partial support), RFC 2633 SMIME and RFC 2315 PKCS #7
ErrorCode ParseCms(const char* data, size_t size, Content* contents,
std::string* error_message) {
BerParser parser(reinterpret_cast<const uint8_t*>(data), size);
parser.SetElementName("ContentInfo");
parser.GetTag(CBS_ASN1_SEQUENCE);
CBS oid;
parser.GetElementData(CBS_ASN1_OBJECT, &oid);
parser.GetTag(ContextSpecificConstructed(0));
if (CompareOid(oid, kSignedDataOid, sizeof(kSignedDataOid))) {
ParseSignedData(&parser, contents);
} else if (CompareOid(oid, kEnvelopedDataOid, sizeof(kEnvelopedDataOid))) {
ParseEnvelopedData(&parser, contents);
} else {
parser.SetError(ErrorCode::UNSUPPORTED, "Unsupported data type.");
}
parser.EndConstructed();
*error_message = parser.error_message();
return parser.status();
}
} // namespace credentio_cms