diff --git a/CHANGES b/CHANGES index 2f3a02469b..4a8ff320e0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,16 @@ +6.0.0-dev.206 | 2023-03-15 10:02:52 +0100 + + * SSL/TLS: Parse CertificateRequest message (Johanna Amann, Corelight) + + This commit introduces parsing of the CertificateRequest message in the + TLS handshake. It introduces a new event ssl_certificate_request, as + well as a new function parse_distinguished_name, which can be used to + parse part of the ssl_certificate_request event parameters. + + This commit also introduces a new policy script, which appends + information about the CAs a TLS server requests in the + CertificateRequest message, if it sends it. + 6.0.0-dev.202 | 2023-03-14 10:33:32 +0100 * GH-2672: Parse DNSSEC AD and CD bits (Michael R. Torres) diff --git a/NEWS b/NEWS index 9ba7f93b27..cb3815067b 100644 --- a/NEWS +++ b/NEWS @@ -58,6 +58,11 @@ New Functionality - The X.509 certificate parser now exposes the signature type that is given inside the signed portion of the certificate. +- The SSL parser now parses the CertificateRequest handshake message. There is a new + ``ssl_certificate_request`` event and a new ``parse_distinguished_name`` function. + We also added the ``protocols/ssl/certificate-request-info`` policy script, that + adds some additional information to ``ssl.log``. + - Add logging metrics for streams (``zeek-log-stream-writes``) and writers (``zeek-log-writer-writes-total``). diff --git a/VERSION b/VERSION index b774e6fb20..a3e1d675e9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.0.0-dev.202 +6.0.0-dev.206 diff --git a/scripts/policy/protocols/ssl/certificate-request-info.zeek b/scripts/policy/protocols/ssl/certificate-request-info.zeek new file mode 100644 index 0000000000..0b22e48563 --- /dev/null +++ b/scripts/policy/protocols/ssl/certificate-request-info.zeek @@ -0,0 +1,23 @@ +##! When the server requests a client certificate, it optionally may specify a list of CAs that +##! it accepts. If the server does this, this script adds this list to ssl.log. + +@load base/protocols/ssl + +module SSL; + +redef record SSL::Info += { + ## List of cient certificate CAs accepted by the server + requested_client_certificate_authorities: vector of string &optional &log; +}; + +event ssl_certificate_request(c: connection, is_client: bool, certificate_types: index_vec, supported_signature_algorithms: SSL::SignatureAndHashAlgorithm, certificate_authorities: string_vec) + { + if ( is_client ) + return; + + local out: vector of string = vector(); + for ( _, ca in certificate_authorities ) + out += parse_distinguished_name(ca); + + c$ssl$requested_client_certificate_authorities = out; + } diff --git a/scripts/test-all-policy.zeek b/scripts/test-all-policy.zeek index 37a9860cb5..87dd2e661c 100644 --- a/scripts/test-all-policy.zeek +++ b/scripts/test-all-policy.zeek @@ -122,6 +122,7 @@ @load protocols/ssh/geo-data.zeek @load protocols/ssh/interesting-hostnames.zeek @load protocols/ssh/software.zeek +@load protocols/ssl/certificate-request-info.zeek @load protocols/ssl/decryption.zeek @load protocols/ssl/expiring-certs.zeek @load protocols/ssl/heartbleed.zeek diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index 91248b2648..c7f8beadfe 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -657,7 +657,7 @@ event ssl_stapled_ocsp%(c: connection, is_client: bool, response: string%); ## ## .. zeek:see:: ssl_alert ssl_established ssl_extension ssl_server_hello ## ssl_session_ticket_handshake x509_certificate ssl_client_hello -## ssl_change_cipher_spec ssl_connection_flipped +## ssl_change_cipher_spec ssl_connection_flipped ssl_certificate_request event ssl_handshake_message%(c: connection, is_client: bool, msg_type: count, length: count%); ## This event is raised when a SSL/TLS ChangeCipherSpec message is encountered @@ -687,3 +687,26 @@ event ssl_change_cipher_spec%(c: connection, is_client: bool%); ## ssl_session_ticket_handshake x509_certificate ssl_client_hello ## ssl_handshake_message event ssl_connection_flipped%(c: connection%); + +## This event is raised, when a Certificate Request handshake message is encountered. This +## Message can be used by a TLS server to request a client certificate. +## +## c: The connection. +## +## is_client: True if event is raised for the client side of the connection +## (the side that sends the client hello). This is typically equivalent +## with the originator, but does not have to be in all circumstances. +## +## certificate_types: List of the types of certificates that the client may offer. +## +## supported_signature_algorithms: List of hash/sighature algorithm pairs that the server +## supports, listed in descending order of preferences. +## +## certificate_authorities: List of distinguished names of certificate authorities that are +## acceptable to the server. The individual entries are DER encoded. +## :zeek:id:`parse_distinguished_name` can be used to decode the strings. +## +## .. zeek:see:: ssl_handshake_message x509_certificate ssl_server_hello ssl_client_hello +## parse_distinguished_name +event ssl_certificate_request%(c: connection, is_client: bool, certificate_types: index_vec, supported_signature_algorithms: SSL::SignatureAndHashAlgorithm, certificate_authorities: string_vec%); + diff --git a/src/analyzer/protocol/ssl/functions.bif b/src/analyzer/protocol/ssl/functions.bif index 185d57a941..2e91190b73 100644 --- a/src/analyzer/protocol/ssl/functions.bif +++ b/src/analyzer/protocol/ssl/functions.bif @@ -65,3 +65,34 @@ function set_keys%(c: connection, keys: string%): bool return zeek::val_mgr->False(); %} + +## Decodes a DER-encoded distinguished name into an ASCII string, +## using the RFC2253 representation +## +## dn: DER encoded distinguished name +## +## Returns: Ascii representation on success, empty string on failure +## +## .. zeek:see:: ssl_certificate_request +function parse_distinguished_name%(dn: string%): string + %{ + const unsigned char* in = dn->Bytes(); + + X509_NAME* dn_x509 = d2i_X509_NAME(nullptr, &in, dn->Len()); + if ( ! dn_x509 ) + { + // we were not able to parse. Let's return an empty string. + return zeek::make_intrusive(""); + } + + char buf[256]; + memset(buf, 0, sizeof(buf)); + BIO* bio = BIO_new(BIO_s_mem()); + + X509_NAME_print_ex(bio, dn_x509, 0, XN_FLAG_RFC2253); + int len = BIO_gets(bio, buf, sizeof(buf)); + auto out = zeek::make_intrusive(len, buf); + BIO_free(bio); + X509_NAME_free(dn_x509); + return out; + %} diff --git a/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac b/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac index 9bfca7ac45..ce19a4b2a9 100644 --- a/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac +++ b/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac @@ -626,6 +626,50 @@ refine connection Handshake_Conn += { return true; %} + function proc_certificate_request(rec: HandshakeRecord, req: CertificateRequest) : bool + %{ + if ( ! ssl_certificate_request ) + return true; + + auto ctlist = zeek::make_intrusive(zeek::id::index_vec); + auto ctypes = ${req.certificate_types}; + + if ( ctypes ) + for ( unsigned int i = 0; i < ctypes->size(); ++i) + ctlist->Assign(i, zeek::val_mgr->Count((*ctypes)[i])); + + auto slist = zeek::make_intrusive(zeek::id::find_type("signature_and_hashalgorithm_vec")); + if ( ${req.uses_signature_and_hashalgorithm} ) + { + auto sigalgs = ${req.supported_signature_algorithms.supported_signature_algorithms}; + + if ( sigalgs ) + { + for ( unsigned int i = 0; i < sigalgs->size(); ++i ) + { + auto el = zeek::make_intrusive(zeek::BifType::Record::SSL::SignatureAndHashAlgorithm); + el->Assign(0, (*sigalgs)[i]->HashAlgorithm()); + el->Assign(1, (*sigalgs)[i]->SignatureAlgorithm()); + slist->Assign(i, std::move(el)); + } + } + } + + + auto calist = zeek::make_intrusive(zeek::id::string_vec); + auto certificate_authorities = ${req.certificate_authorities.certificate_authorities}; + if ( certificate_authorities ) + for ( unsigned int i = 0; i < certificate_authorities->size(); ++i ) + { + auto ca = (*certificate_authorities)[i]->certificate_authority(); + calist->Assign(i, zeek::make_intrusive(ca.length(), (const char*) ca.data())); + } + + zeek::BifEvent::enqueue_ssl_certificate_request(zeek_analyzer(), zeek_analyzer()->Conn(), ${rec.is_orig} ^ flipped_, ctlist, slist, calist); + + return true; + %} + }; refine typeattr ClientHello += &let { @@ -754,3 +798,7 @@ refine typeattr Handshake += &let { refine typeattr SignedCertificateTimestamp += &let { proc : bool = $context.connection.proc_signedcertificatetimestamp(rec, version, logid, timestamp, digitally_signed_algorithms, digitally_signed_signature); }; + +refine typeattr CertificateRequest += &let { + proc: bool = $context.connection.proc_certificate_request(rec, this); +}; diff --git a/src/analyzer/protocol/ssl/tls-handshake-protocol.pac b/src/analyzer/protocol/ssl/tls-handshake-protocol.pac index 9fd408e070..2aa6b6a45e 100644 --- a/src/analyzer/protocol/ssl/tls-handshake-protocol.pac +++ b/src/analyzer/protocol/ssl/tls-handshake-protocol.pac @@ -439,9 +439,30 @@ type DhAnonServerKeyExchange(rec: HandshakeRecord) = record { # V3 Certificate Request (7.4.4.) ###################################################################### +type CertificateAuthorities = record { + certificate_authority_len: uint16; + certificate_authority: bytestring &length=certificate_authority_len; +}; + +type CertificateAuthoritiesContainer = record { + certificate_authorities: CertificateAuthorities[] &until($input.length() == 0); +}; + # For now, ignore Certificate Request Details; just eat up message. type CertificateRequest(rec: HandshakeRecord) = record { + certificate_types_len: uint8; + certificate_types: uint8[certificate_types_len]; + alg: case uses_signature_and_hashalgorithm of { + true -> supported_signature_algorithms: SignatureAlgorithm(rec); + false -> nothing: bytestring &length=0; + } &requires(uses_signature_and_hashalgorithm); + certificate_authorities_len: uint16; + certificate_authorities: CertificateAuthoritiesContainer &length=certificate_authorities_len; cont : bytestring &restofdata &transient; +} &let { + uses_signature_and_hashalgorithm : bool = + ($context.connection.chosen_version() > TLSv11) && + ($context.connection.chosen_version() != DTLSv10); }; @@ -931,7 +952,7 @@ type PreSharedKey(rec: HandshakeRecord) = case rec.msg_type of { type SignatureAlgorithm(rec: HandshakeRecord) = record { length: uint16; supported_signature_algorithms: SignatureAndHashAlgorithm[] &until($input.length() == 0); -} +} &length=length+2; type EllipticCurves(rec: HandshakeRecord) = record { length: uint16; diff --git a/testing/btest/Baseline/bifs.x509_parse_dn/.stdout b/testing/btest/Baseline/bifs.x509_parse_dn/.stdout new file mode 100644 index 0000000000..b969b22d39 --- /dev/null +++ b/testing/btest/Baseline/bifs.x509_parse_dn/.stdout @@ -0,0 +1,5 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +CN=certauth.idrix.fr + +CN=Senate PIV-I CA G4,OU=Office of the Sergeant at Arms,OU=U.S. Senate,O=U.S. Government,C=US +OU=\E3\82\A2\E3\83\97\E3\83\AA\E3\82\B1\E3\83\BC\E3\82\B7\E3\83\A7\E3\83\B3CA,O=\E6\97\A5\E6\9C\AC\E5\9B\BD\E6\94\BF\E5\BA\9C,C=JP diff --git a/testing/btest/Baseline/scripts.base.protocols.ssl.certificate_request/out b/testing/btest/Baseline/scripts.base.protocols.ssl.certificate_request/out new file mode 100644 index 0000000000..af3be60949 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ssl.certificate_request/out @@ -0,0 +1,15 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +[1, 2, 64] +[[HashAlgorithm=4, SignatureAlgorithm=3], [HashAlgorithm=5, SignatureAlgorithm=3], [HashAlgorithm=6, SignatureAlgorithm=3], [HashAlgorithm=8, SignatureAlgorithm=7], [HashAlgorithm=8, SignatureAlgorithm=8], [HashAlgorithm=8, SignatureAlgorithm=9], [HashAlgorithm=8, SignatureAlgorithm=10], [HashAlgorithm=8, SignatureAlgorithm=11], [HashAlgorithm=8, SignatureAlgorithm=4], [HashAlgorithm=8, SignatureAlgorithm=5], [HashAlgorithm=8, SignatureAlgorithm=6], [HashAlgorithm=4, SignatureAlgorithm=1], [HashAlgorithm=5, SignatureAlgorithm=1], [HashAlgorithm=6, SignatureAlgorithm=1], [HashAlgorithm=3, SignatureAlgorithm=3], [HashAlgorithm=3, SignatureAlgorithm=1], [HashAlgorithm=3, SignatureAlgorithm=2], [HashAlgorithm=4, SignatureAlgorithm=2], [HashAlgorithm=5, SignatureAlgorithm=2], [HashAlgorithm=6, SignatureAlgorithm=2]] +======== +[1] +[[HashAlgorithm=4, SignatureAlgorithm=1]] +0H1\x0b0\x09\x06\x03U\x04\x06\x13\x02US1\x130\x11\x06\x03U\x04\x08\x0c\x0aSome-State1\x120\x10\x06\x03U\x04\x07\x0c\x09Somewhere1\x100\x0e\x06\x03U\x04\x0a\x0c\x07SomeOrg +O=SomeOrg,L=Somewhere,ST=Some-State,C=US +======== +[1, 64, 2] +[] +======== +[1, 2] +[] +======== diff --git a/testing/btest/Baseline/scripts.policy.protocols.ssl.certificate-request-info/ssl.log b/testing/btest/Baseline/scripts.policy.protocols.ssl.certificate-request-info/ssl.log new file mode 100644 index 0000000000..9898a0b74b --- /dev/null +++ b/testing/btest/Baseline/scripts.policy.protocols.ssl.certificate-request-info/ssl.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ssl +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert requested_client_certificate_authorities +#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 fd42:496a:d659:bb85::1 52464 fd42:496a:d659:bb85:216:3eff:fe6a:a257 3000 TLSv12 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 x25519 - F handshake_failure - F CsxkrnXGIl 0a171ee771a26530c650fe8b8a6bf205177bfb64fbb3e5303ba348c13ffc7dfa,c628dd5aae1f216da6ce4f8f914fb7141c2b0afd3522cce5900bcc4840657bfd (empty) - O=SomeOrg\x2cL=Somewhere\x2cST=Some-State\x2cC=US +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Traces/tls/certificate-request-failed.pcap b/testing/btest/Traces/tls/certificate-request-failed.pcap new file mode 100644 index 0000000000..0191710fd7 Binary files /dev/null and b/testing/btest/Traces/tls/certificate-request-failed.pcap differ diff --git a/testing/btest/Traces/tls/client-certificate.pcap b/testing/btest/Traces/tls/client-certificate.pcap new file mode 100644 index 0000000000..cef1e8a505 Binary files /dev/null and b/testing/btest/Traces/tls/client-certificate.pcap differ diff --git a/testing/btest/bifs/x509_parse_dn.zeek b/testing/btest/bifs/x509_parse_dn.zeek new file mode 100644 index 0000000000..2a3c169b71 --- /dev/null +++ b/testing/btest/bifs/x509_parse_dn.zeek @@ -0,0 +1,10 @@ +# @TEST-EXEC: zeek -b %INPUT +# @TEST-EXEC: btest-diff .stdout + +event zeek_init() + { + print parse_distinguished_name("0\x1c1\x1a0\x18\x06\x03U\x04\x03\x13\x11certauth.idrix.fr"); + print parse_distinguished_name("00000\x1c1\x1a0\x18\x06\x03U\x04\x03\x13\x11certauth.idrix.fr"); # invalid + print parse_distinguished_name("\x30\x81\x83\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x18\x30\x16\x06\x03\x55\x04\x0A\x13\x0F\x55\x2E\x53\x2E\x20\x47\x6F\x76\x65\x72\x6E\x6D\x65\x6E\x74\x31\x14\x30\x12\x06\x03\x55\x04\x0B\x13\x0B\x55\x2E\x53\x2E\x20\x53\x65\x6E\x61\x74\x65\x31\x27\x30\x25\x06\x03\x55\x04\x0B\x13\x1E\x4F\x66\x66\x69\x63\x65\x20\x6F\x66\x20\x74\x68\x65\x20\x53\x65\x72\x67\x65\x61\x6E\x74\x20\x61\x74\x20\x41\x72\x6D\x73\x31\x1B\x30\x19\x06\x03\x55\x04\x03\x13\x12\x53\x65\x6E\x61\x74\x65\x20\x50\x49\x56\x2D\x49\x20\x43\x41\x20\x47\x34"); + print parse_distinguished_name("\x30\x4C\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x4A\x50\x31\x18\x30\x16\x06\x03\x55\x04\x0A\x0C\x0F\xE6\x97\xA5\xE6\x9C\xAC\xE5\x9B\xBD\xE6\x94\xBF\xE5\xBA\x9C\x31\x23\x30\x21\x06\x03\x55\x04\x0B\x0C\x1A\xE3\x82\xA2\xE3\x83\x97\xE3\x83\xAA\xE3\x82\xB1\xE3\x83\xBC\xE3\x82\xB7\xE3\x83\xA7\xE3\x83\xB3\x43\x41\x30"); + } diff --git a/testing/btest/scripts/base/protocols/ssl/certificate_request.zeek b/testing/btest/scripts/base/protocols/ssl/certificate_request.zeek new file mode 100644 index 0000000000..259eca3ed8 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ssl/certificate_request.zeek @@ -0,0 +1,22 @@ +# This tests the certificate_request message parsing + +# @TEST-EXEC: zeek -b -r $TRACES/tls/client-certificate.pcap %INPUT > out +# @TEST-EXEC: zeek -C -b -r $TRACES/tls/certificate-request-failed.pcap %INPUT >> out +# @TEST-EXEC: zeek -C -b -r $TRACES/tls/webrtc-stun.pcap %INPUT >> out +# @TEST-EXEC: zeek -C -b -r $TRACES/mysql/encrypted.trace %INPUT >> out +# @TEST-EXEC: btest-diff out + +@load base/protocols/ssl +@load base/protocols/mysql + +event ssl_certificate_request(c: connection, is_client: bool, certificate_types: index_vec, supported_signature_algorithms: SSL::SignatureAndHashAlgorithm, certificate_authorities: string_vec) + { + print certificate_types; + print supported_signature_algorithms; + for ( _, ca in certificate_authorities ) + { + print ca; + print parse_distinguished_name(ca); + } + print "========"; + } diff --git a/testing/btest/scripts/policy/protocols/ssl/certificate-request-info.zeek b/testing/btest/scripts/policy/protocols/ssl/certificate-request-info.zeek new file mode 100644 index 0000000000..6b6afec279 --- /dev/null +++ b/testing/btest/scripts/policy/protocols/ssl/certificate-request-info.zeek @@ -0,0 +1,4 @@ +# @TEST-EXEC: zeek -C -r $TRACES/tls/certificate-request-failed.pcap %INPUT +# @TEST-EXEC: btest-diff ssl.log + +@load protocols/ssl/certificate-request-info diff --git a/testing/external/commit-hash.zeek-testing b/testing/external/commit-hash.zeek-testing index 8dbdf3f2e5..f45d77b541 100644 --- a/testing/external/commit-hash.zeek-testing +++ b/testing/external/commit-hash.zeek-testing @@ -1 +1 @@ -6e7bde1dce189ce4822cab48362c45f7392dba01 +3a1a8c6734ee975a532af20ffb440d092cef8a9b diff --git a/testing/external/commit-hash.zeek-testing-private b/testing/external/commit-hash.zeek-testing-private index 82ff38b3fa..ae5473c5e5 100644 --- a/testing/external/commit-hash.zeek-testing-private +++ b/testing/external/commit-hash.zeek-testing-private @@ -1 +1 @@ -256d8e2d73ae84d7d9307d1b77059f0fd48c1ffe +45f7b81b68344514b6c155d32e688eb21501e079