/* * CMS Decoding Operations * (C) 1999-2007 Jack Lloyd * * Distributed under the terms of the Botan license */ #include #include #include #include #include #include #include namespace Botan { namespace { /* * Compute the hash of some content */ secure_vector hash_of(const secure_vector& content, const AlgorithmIdentifier& hash_algo, std::string& hash_name) { hash_name = OIDS::lookup(hash_algo.oid); Algorithm_Factory& af = global_state().algorithm_factory(); std::unique_ptr hash_fn(af.make_hash_function(hash_name)); return hash_fn->process(content); } /* * Find a cert based on SignerIdentifier */ std::vector get_cert(BER_Decoder& signer_info, X509_Store&) { BER_Object id = signer_info.get_next_object(); std::vector found; #if 0 if(id.type_tag == SEQUENCE && id.class_tag == CONSTRUCTED) { X509_DN issuer; BigInt serial; BER_Decoder iands(id.value); iands.decode(issuer); iands.decode(serial); found = store.get_certs( X509_Store_Search::by_issuer_and_serial(issuer, serial)); } else if(id.type_tag == 0 && id.class_tag == CONSTRUCTED) found = store.get_certs(X509_Store_Search::by_skid(id.value)); else throw Decoding_Error("CMS: Unknown tag for cert identifier"); #endif throw Internal_Error("Not implemented"); // verify cert if found if(found.size() > 1) throw Internal_Error("CMS: Found more than one match in get_cert"); return found; } /* * Read OriginatorInfo */ void read_orig_info(BER_Decoder& info, X509_Store& store) { BER_Object next = info.get_next_object(); if(next.type_tag == 0 && next.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) { DataSource_Memory certs(next.value); while(!certs.end_of_data()) { // FIXME: can be attribute certs too // FIXME: DoS? X509_Certificate cert(certs); store.add_cert(cert); } next = info.get_next_object(); } if(next.type_tag == 1 && next.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) { DataSource_Memory crls(next.value); while(!crls.end_of_data()) { // FIXME: DoS? X509_CRL crl(crls); store.add_crl(crl); } next = info.get_next_object(); } info.push_back(next); } /* * Decode any Attributes, and check type */ secure_vector decode_attributes(BER_Decoder& ber, const OID& type, bool& bad_attributes) { BER_Object obj = ber.get_next_object(); secure_vector digest; bool got_digest = false; bool got_content_type = false; if(obj.type_tag == 0 && obj.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) ber.push_back(obj); else { BER_Decoder attributes(obj.value); while(attributes.more_items()) { Attribute attr; attributes.decode(attr); BER_Decoder attr_value(attr.parameters); if(attr.oid == OIDS::lookup("PKCS9.MessageDigest")) { got_digest = true; attr_value.decode(digest, OCTET_STRING); } else if(attr.oid == OIDS::lookup("PKCS9.ContentType")) { got_content_type = true; OID inner_type; attr_value.decode(inner_type); if(inner_type != type) bad_attributes = true; } else throw Decoding_Error("Unknown/unhandled CMS attribute found: " + OIDS::lookup(attr.oid)); } if(!got_digest || !got_content_type) bad_attributes = true; } return digest; } } /* * Decode this layer of CMS encoding */ void CMS_Decoder::decode_layer() { try { if(status == FAILURE) throw Invalid_State("CMS: Decoder is in FAILURE state"); status = GOOD; info = ""; type = next_type; if(type == OIDS::lookup("CMS.DataContent")) return; BER_Decoder decoder(data); if(type == OIDS::lookup("CMS.CompressedData")) decompress(decoder); else if(type == OIDS::lookup("CMS.DigestedData")) { size_t version; AlgorithmIdentifier hash_algo; secure_vector digest; BER_Decoder hash_info = decoder.start_cons(SEQUENCE); hash_info.decode(version); if(version != 0 && version != 2) throw Decoding_Error("CMS: Unknown version for DigestedData"); hash_info.decode(hash_algo); read_econtent(hash_info); hash_info.decode(digest, OCTET_STRING); hash_info.end_cons(); if(digest != hash_of(data, hash_algo, info)) status = BAD; } else if(type == OIDS::lookup("CMS.SignedData")) { #if 1 throw Internal_Error("FIXME: not implemented"); #else size_t version; BER_Decoder sig_info = BER::get_subsequence(decoder); BER::decode(sig_info, version); if(version != 1 && version != 3) throw Decoding_Error("CMS: Unknown version for SignedData"); BER::get_subset(sig_info); // hash algos (do something with these?) read_econtent(sig_info); read_orig_info(sig_info, store); BER_Decoder signer_infos = BER::get_subset(sig_info); while(signer_infos.more_items()) { AlgorithmIdentifier sig_algo, hash_algo; secure_vector signature, digest; size_t version; BER_Decoder signer_info = BER::get_subsequence(signer_infos); BER::decode(signer_info, version); if(version != 1 && version != 3) throw Decoding_Error("CMS: Unknown version for SignerInfo"); std::vector certs = get_cert(signer_info, store); if(certs.size() == 0) { status = NO_KEY; continue; } BER::decode(signer_info, hash_algo); bool bad_attr = false; digest = decode_attributes(signer_info, next_type, bad_attr); if(bad_attr) { status = BAD; continue; } BER::decode(signer_info, sig_algo); BER::decode(signer_info, signature, OCTET_STRING); // unsigned attributes signer_info.verify_end(); if(digest.has_items()) { std::string hash; if(digest != hash_of(data, hash_algo, hash)) { status = BAD; continue; } status = check_sig(signed_attr, sig_algo, signature, certs[0]); } else status = check_sig(data, sig_algo, signature, certs[0]); if(status == BAD) continue; // fix this (gets only last signer, for one thing) // maybe some way for the user to get all certs that signed the // message? that would be useful info = "CN=" + cert.subject_info("CommonName") + ",O=" + cert.subject_info("Organization") + ",OU=" + cert.subject_info("Organizational Unit"); } #endif } else if(type == OIDS::lookup("CMS.EnvelopedData")) { throw Internal_Error("FIXME: not implemented"); } else if(type == OIDS::lookup("CMS.AuthenticatedData")) { throw Internal_Error("FIXME: not implemented"); } else throw Decoding_Error("CMS: Unknown content ID " + type.as_string()); } catch(std::exception) { status = FAILURE; } } }