#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // asn1c quirk struct Vanetza_Security_Certificate : public Vanetza_Security_CertificateBase {}; namespace vanetza { namespace security { namespace { bool assign_permissions(const v2::Certificate& certificate, VerifyConfirm& confirm) { for (auto& subject_attribute : certificate.subject_attributes) { if (get_type(subject_attribute) != v2::SubjectAttributeType::ITS_AID_SSP_List) { continue; } auto& permissions = boost::get >(subject_attribute); for (auto& permission : permissions) { if (permission.its_aid == confirm.its_aid) { confirm.permissions = permission.service_specific_permissions; return true; } } break; } return false; } } // namespace StraightVerifyService::StraightVerifyService(const Runtime& runtime, Backend& backend) : m_runtime(runtime), m_backend(backend) { } StraightVerifyService::StraightVerifyService(const Runtime& runtime, Backend& backend, PositionProvider& position) : m_runtime(runtime), m_backend(backend), m_position_provider(&position) { } void StraightVerifyService::use_certificate_cache(v2::CertificateCache* cache) { m_context_v2.m_cert_cache = cache; } void StraightVerifyService::use_certificate_provider(v2::CertificateProvider* provider) { m_context_v2.m_cert_provider = provider; } void StraightVerifyService::use_certificate_validator(v2::CertificateValidator* validator) { m_context_v2.m_cert_validator = validator; } void StraightVerifyService::use_sign_header_policy(v2::SignHeaderPolicy* policy) { m_context_v2.m_sign_policy = policy; } void StraightVerifyService::use_certificate_provider(v3::CertificateProvider* provider) { if (provider) { use_certificate_cache(&provider->cache()); } } void StraightVerifyService::use_certificate_cache(v3::CertificateCache* cache) { m_context_v3.m_cert_cache = cache; } void StraightVerifyService::use_certificate_validator(v3::CertificateValidator* validator) { m_context_v3.m_cert_validator = validator; } void StraightVerifyService::use_sign_header_policy(v3::SignHeaderPolicy* policy) { m_context_v3.m_sign_policy = policy; } VerifyConfirm StraightVerifyService::verify(const VerifyRequest& request) { struct visitor : public boost::static_visitor { visitor(StraightVerifyService* service) : m_service(service) { } VerifyConfirm operator()(const v2::SecuredMessage& msg) { return m_service->verify(msg); } VerifyConfirm operator()(const v3::SecuredMessage& msg) { return m_service->verify(msg); } StraightVerifyService* m_service = nullptr; } visitor(this); return boost::apply_visitor(visitor, request.secured_message); } VerifyConfirm StraightVerifyService::verify(const v2::SecuredMessage& secured_message) { // TODO check if certificates in chain have been revoked for all CA certificates, ATs are never revoked VerifyConfirm confirm; using namespace v2; if (PayloadType::Signed != secured_message.payload.type) { confirm.report = VerificationReport::Unsigned_Message; return confirm; } if (2 != secured_message.protocol_version()) { confirm.report = VerificationReport::Incompatible_Protocol; return confirm; } if (!m_context_v2.complete() || !m_position_provider) { confirm.report = VerificationReport::Configuration_Problem; return confirm; } v2::CertificateProvider& cert_provider = *m_context_v2.m_cert_provider; v2::CertificateCache& cert_cache = *m_context_v2.m_cert_cache; v2::CertificateValidator& cert_validator = *m_context_v2.m_cert_validator; v2::SignHeaderPolicy& sign_policy = *m_context_v2.m_sign_policy; const std::list* requested_certs = secured_message.header_field(); if (requested_certs) { for (auto& requested_cert : *requested_certs) { if (truncate(calculate_hash(cert_provider.own_certificate())) == requested_cert) { sign_policy.request_certificate(); } for (auto& cert : cert_provider.own_chain()) { if (truncate(calculate_hash(cert)) == requested_cert) { sign_policy.request_certificate_chain(); } } } } const IntX* its_aid = secured_message.header_field(); if (!its_aid) { // ITS-AID is required to be present, report as incompatible protocol, as that's the closest match confirm.report = VerificationReport::Incompatible_Protocol; return confirm; } confirm.its_aid = its_aid->get(); const SignerInfo* signer_info = secured_message.header_field(); std::list possible_certificates; bool possible_certificates_from_cache = false; // use a dummy hash for initialization HashedId8 signer_hash; signer_hash.fill(0x00); if (signer_info) { switch (get_type(*signer_info)) { case SignerInfoType::Certificate: possible_certificates.push_back(boost::get(*signer_info)); signer_hash = calculate_hash(boost::get(*signer_info)); if (confirm.its_aid == aid::CA && cert_cache.lookup(signer_hash, SubjectType::Authorization_Ticket).size() == 0) { // Previously unknown certificate, send own certificate in next CAM // See TS 103 097 v1.2.1, section 7.1, 1st bullet, 3rd dash sign_policy.request_certificate(); } break; case SignerInfoType::Certificate_Digest_With_SHA256: signer_hash = boost::get(*signer_info); possible_certificates.splice(possible_certificates.end(), cert_cache.lookup(signer_hash, SubjectType::Authorization_Ticket)); possible_certificates_from_cache = true; break; case SignerInfoType::Certificate_Chain: { std::list chain = boost::get>(*signer_info); if (chain.size() == 0) { confirm.report = VerificationReport::Signer_Certificate_Not_Found; return confirm; } else if (chain.size() > 3) { // prevent DoS by sending very long chains, maximum length is three certificates, because: // AT → AA → Root and no other signatures are allowed, sending the Root is optional confirm.report = VerificationReport::Invalid_Certificate; return confirm; } // pre-check chain certificates, otherwise they're not available for the ticket check for (auto& cert : chain) { // root certificates must already be known, otherwise the validation will fail anyway if (cert.subject_info.subject_type == SubjectType::Authorization_Authority) { // there's no need to report unknown signers at this point, see comment above CertificateValidity validity = cert_validator.check_certificate(cert); // we can abort early if there are invalid AA certificates in the chain if (!validity) { confirm.report = VerificationReport::Invalid_Certificate; confirm.certificate_validity = validity; return confirm; } // We won't cache outdated or premature certificates in the cache and abort early. // This check isn't required as it would just fail below or in the consistency checks, // but it's an optimization and saves us from polluting the cache with such certificates. if (!check_certificate_time(cert, m_runtime.now()) || !check_certificate_region(cert, m_position_provider->position_fix())) { confirm.report = VerificationReport::Invalid_Certificate; return confirm; } cert_cache.insert(cert); } } // last certificate must be the authorization ticket signer_hash = calculate_hash(chain.back()); possible_certificates.push_back(chain.back()); } break; default: confirm.report = VerificationReport::Unsupported_Signer_Identifier_Type; return confirm; break; } } if (possible_certificates.size() == 0) { confirm.report = VerificationReport::Signer_Certificate_Not_Found; confirm.certificate_id = signer_hash; sign_policy.request_unrecognized_certificate(signer_hash); return confirm; } if (!check_generation_time(secured_message, m_runtime.now())) { confirm.report = VerificationReport::Invalid_Timestamp; return confirm; } // TODO check Duplicate_Message, Invalid_Mobility_Data, Unencrypted_Message, Decryption_Error // check signature const TrailerField* signature_field = secured_message.trailer_field(TrailerFieldType::Signature); const v2::Signature* signature = boost::get(signature_field); if (!signature) { confirm.report = VerificationReport::Unsigned_Message; return confirm; } if (PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256 != get_type(*signature)) { confirm.report = VerificationReport::False_Signature; return confirm; } // check the size of signature.R and siganture.s auto ecdsa = extract_ecdsa_signature(*signature); const auto field_len = field_size(PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256); if (!ecdsa || ecdsa->s.size() != field_len) { confirm.report = VerificationReport::False_Signature; return confirm; } // verify payload signature with given signature ByteBuffer payload = convert_for_signing(secured_message, secured_message.trailer_fields); boost::optional signer; for (const auto& cert : possible_certificates) { SubjectType subject_type = cert.subject_info.subject_type; if (subject_type != SubjectType::Authorization_Ticket) { confirm.report = VerificationReport::Invalid_Certificate; confirm.certificate_validity = CertificateInvalidReason::Invalid_Signer; return confirm; } boost::optional public_key = get_public_key(cert, m_backend); // public key could not be extracted if (!public_key) { confirm.report = VerificationReport::Invalid_Certificate; confirm.certificate_validity = CertificateInvalidReason::Missing_Public_Key; return confirm; } if (m_backend.verify_data(public_key.get(), payload, *ecdsa)) { signer = cert; break; } } if (!signer) { // HashedId8 of authorization tickets is not guaranteed to be globally unique. // The collision probability is rather low, but it might happen. if (signer_info && get_type(*signer_info) == SignerInfoType::Certificate_Digest_With_SHA256) { // assume a hash collision since we got only a digest with message confirm.report = VerificationReport::Signer_Certificate_Not_Found; } else { // signature does not match the certificate received with this message confirm.report = VerificationReport::False_Signature; } confirm.certificate_id = signer_hash; sign_policy.request_unrecognized_certificate(signer_hash); return confirm; } // we can only check the generation location after we have identified the correct certificate if (!check_generation_location(secured_message, *signer)) { confirm.report = VerificationReport::Invalid_Certificate; confirm.certificate_validity = CertificateInvalidReason::Off_Region; return confirm; } CertificateValidity cert_validity = CertificateValidity::valid(); if (!possible_certificates_from_cache) { // certificates from cache are already verified as trusted cert_validity = cert_validator.check_certificate(*signer); } confirm.certificate_validity = cert_validity; // if certificate could not be verified return correct DecapReport if (!cert_validity) { confirm.report = VerificationReport::Invalid_Certificate; if (cert_validity.reason() == CertificateInvalidReason::Unknown_Signer) { if (get_type(signer->signer_info) == SignerInfoType::Certificate_Digest_With_SHA256) { auto signer_hash = boost::get(signer->signer_info); confirm.certificate_id = signer_hash; sign_policy.request_unrecognized_certificate(signer_hash); } } return confirm; } if (!check_certificate_time(*signer, m_runtime.now())) { confirm.report = VerificationReport::Invalid_Certificate; confirm.certificate_validity = CertificateInvalidReason::Off_Time_Period; return confirm; } if (!check_certificate_region(*signer, m_position_provider->position_fix())) { confirm.report = VerificationReport::Invalid_Certificate; confirm.certificate_validity = CertificateInvalidReason::Off_Region; return confirm; } // Assign permissions from the certificate based on the message AID already present in the confirm // and reject the certificate if no permissions are present for the claimed AID. if (!assign_permissions(*signer, confirm)) { // This might seem weird, because the certificate itself is valid, but not for the received message. confirm.report = VerificationReport::Invalid_Certificate; confirm.certificate_validity = CertificateInvalidReason::Insufficient_ITS_AID; return confirm; } // cache only certificates that are useful, one that mismatches its restrictions isn't cert_cache.insert(*signer); confirm.report = VerificationReport::Success; return confirm; } VerifyConfirm StraightVerifyService::verify(const v3::SecuredMessage& msg) { /* * TS 103 097 v1.3.1 demands to assess the validity of signed data * according to IEEE 1609.2 clause 5.2. */ VerifyConfirm confirm; confirm.report = VerificationReport::Incompatible_Protocol; /*< fallback error code */ if (!msg.is_signed()) { confirm.report = VerificationReport::Unsigned_Message; return confirm; } if (msg.protocol_version() != 3) { confirm.report = VerificationReport::Incompatible_Protocol; return confirm; } auto gen_time = msg.generation_time(); if (!gen_time) { // TS 103 097 v1.3.1 demands generation time to be always present confirm.report = VerificationReport::Invalid_Timestamp; return confirm; } // TODO further generation time checks depending on application profile auto signature = msg.signature(); if (!signature) { confirm.report = VerificationReport::Unsigned_Message; return confirm; } struct certificate_lookup_visitor : public boost::static_visitor { certificate_lookup_visitor(v3::CertificateCache* cache) : m_cache(cache) { } const v3::asn1::Certificate* operator()(const v3::asn1::HashedId8* digest) { // look up certificate matching digest in local storage if (m_cache && digest) { const v3::Certificate* found = m_cache->lookup(v3::convert(*digest)); return found ? found->content() : nullptr; } else { return nullptr; } } const v3::asn1::Certificate* operator()(const v3::asn1::Certificate* cert) { return cert; } v3::CertificateCache* m_cache; } certificate_lookup_visitor(m_context_v3.m_cert_cache); auto signer_identifier = msg.signer_identifier(); // track "known" stations auto maybe_digest = v3::get_certificate_id(signer_identifier); if (m_context_v3.m_cert_cache && maybe_digest) { bool was_unknown_station = m_context_v3.m_cert_cache->announce(*maybe_digest); if (was_unknown_station && msg.its_aid() == aid::CA && m_context_v3.m_sign_policy) { // CAM from unknown station received, add our certificate to next outgoing message m_context_v3.m_sign_policy->request_certificate(); } } // check if a ITS-AID 36 (CAM) with inline cert request shall be handled by us if (msg.its_aid() == aid::CA && m_context_v3.m_sign_policy) { Vanetza_Security_SequenceOfHashedId3* requests = msg->content->choice.signedData->tbsData->headerInfo.inlineP2pcdRequest; if (requests) { for (int i = 0; i < requests->list.count; ++i) { const Vanetza_Security_HashedId3_t* req_digest = requests->list.array[i]; const HashedId3 hid3 = create_hashed_id3(*req_digest); m_context_v3.m_sign_policy->enqueue_p2p_request(hid3); } } Vanetza_Security_Certificate* included_cert = msg->content->choice.signedData->tbsData->headerInfo.requestedCertificate; if (included_cert) { auto maybe_digest = v3::calculate_digest(*included_cert); if (maybe_digest) { m_context_v3.m_sign_policy->discard_p2p_request(truncate(*maybe_digest)); } } } const v3::asn1::Certificate* certificate = boost::apply_visitor(certificate_lookup_visitor, signer_identifier); if (!certificate) { if (msg.its_aid() == aid::CA && m_context_v3.m_sign_policy && maybe_digest) { // for received CAMs (having digest as signer identifier) with unknown AT we request the full AT certificate m_context_v3.m_sign_policy->request_unrecognized_certificate(*maybe_digest); } confirm.report = VerificationReport::Signer_Certificate_Not_Found; return confirm; } // code below can safely dereference certificate assert(certificate != nullptr); // check AT certificate's validity if (m_context_v3.m_cert_validator) { auto verdict = m_context_v3.m_cert_validator->valid_for_signing(v3::CertificateView { certificate }, msg.its_aid()); if (verdict != v3::CertificateValidator::Verdict::Valid) { confirm.report = VerificationReport::Invalid_Certificate; return confirm; } } boost::optional aa_digest; switch (certificate->issuer.present) { case Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest: aa_digest = v3::convert(certificate->issuer.choice.sha256AndDigest); break; case Vanetza_Security_IssuerIdentifier_PR_sha384AndDigest: aa_digest = v3::convert(certificate->issuer.choice.sha384AndDigest); break; default: break; } if (aa_digest && m_context_v3.m_cert_cache) { if (!m_context_v3.m_cert_cache->is_known(*aa_digest)) { if (m_context_v3.m_sign_policy) { // request unknown AA certificate for any received message signed with AT issued by this AA m_context_v3.m_sign_policy->request_unrecognized_certificate(*aa_digest); } } // TODO: if AA is validated and fails, announce it in cache } auto public_key = v3::get_public_key(*certificate); if (!public_key) { confirm.report = VerificationReport::Invalid_Certificate; confirm.certificate_validity = CertificateInvalidReason::Missing_Public_Key; return confirm; } try { ByteBuffer msg_hash = v3::calculate_message_hash(m_backend, msg.hash_id(), msg.signing_payload(), v3::CertificateView { certificate }); if (!m_backend.verify_digest(*public_key, msg_hash, *signature)) { confirm.report = VerificationReport::False_Signature; return confirm; } } catch (const std::exception&) { // malformed input data, e.g. failing certificate encoding confirm.report = VerificationReport::Incompatible_Protocol; return confirm; } confirm.its_aid = msg.its_aid(); confirm.permissions = v3::get_app_permissions(*certificate, confirm.its_aid); confirm.certificate_id = maybe_digest; confirm.report = VerificationReport::Success; if (m_context_v3.m_cert_cache && confirm.certificate_id) { v3::CertificateCache& cache = *m_context_v3.m_cert_cache; bool already_cached = cache.lookup(*confirm.certificate_id) != nullptr; if (!already_cached && confirm.its_aid == aid::CA && m_context_v3.m_sign_policy) { // CAM from unknown station received, add our certificate to next outgoing message m_context_v3.m_sign_policy->request_certificate(); } // update certificate cache with received certificate if (v3::contains_certificate(signer_identifier)) { cache.store(v3::Certificate { *certificate }); } } return confirm; } } // namespace security } // namespace vanetza