Skip to content

Commit

Permalink
Pull request #512: Implement fingerprints verification for encrypted …
Browse files Browse the repository at this point in the history
…DNS protocols

Merge in ADGUARD-CORE-LIBS/dns-libs from feature/AG-18701 to dev-2.2

Squashed commit of the following:

commit 9bfc1cc0dea9b9e27305f1435a27db625d340e07
Merge: 5fb83736 a6e431bf
Author: Boris <[email protected]>
Date:   Wed Mar 15 12:06:07 2023 +0200

    Merge branch 'feature/AG-18701' of ssh://bit.adguard.com:7999/adguard-core-libs/dns-libs into feature/AG-18701

commit 5fb83736c141fd9a80804d723edbb1c04363a5d3
Author: Boris <[email protected]>
Date:   Wed Mar 15 12:05:39 2023 +0200

    issue #AG-18701, fix win test

commit a6e431bf47a001e613a0d428209e6fffa14d74b8
Author: Max Grupper <[email protected]>
Date:   Tue Mar 14 20:08:18 2023 +0200

    try to fix win

commit 51ee7ded272ea6367491696c89fbdc9ecf03ec3c
Merge: 10b3fa4d 92ed0b4
Author: Max Grupper <[email protected]>
Date:   Tue Mar 14 18:27:08 2023 +0200

    Merge branch 'dev-2.2' into feature/AG-18701

    # Conflicts:
    #	net/tls_codec.cpp
    #	net/tls_codec.h
    #	upstream/upstream_doq.cpp

commit 10b3fa4d933a0e1d3ee132aa0c9bebd3f26871ee
Author: Max Grupper <[email protected]>
Date:   Tue Mar 14 18:09:43 2023 +0200

    redundant

commit 1bdcd2ab4ef5bd433c40295712501d399230b55a
Merge: c313bc8f c9a7c34
Author: Max Grupper <[email protected]>
Date:   Tue Mar 14 18:02:30 2023 +0200

    Merge branch 'dev-2.2' into feature/AG-18701

    # Conflicts:
    #	CHANGELOG.md
    #	upstream/upstream_doh.cpp

commit c313bc8f428a316d2a959d021727cb40beafa7ac
Author: Boris <[email protected]>
Date:   Tue Mar 14 17:57:26 2023 +0200

    issue #AG-18701, win part 2

commit 5adfbc6d0fe17b99ed78a6ce51a6cbe859115724
Author: Boris <[email protected]>
Date:   Tue Mar 14 17:56:23 2023 +0200

    issue #AG-18701, win part

commit 82755c877fa5bb493f71cfbfbc898b1ea12d98d1
Author: Max Grupper <[email protected]>
Date:   Fri Mar 10 12:56:41 2023 +0200

    use Result

commit 9d4b7de79b9f07e9537c5646ed680654617fb414
Author: Max Grupper <[email protected]>
Date:   Fri Mar 10 10:02:21 2023 +0200

    move fingerprints parsing to factory

commit 41e71ff11188e4329b5892ef1a7650a3e5d12bfa
Author: Max Grupper <[email protected]>
Date:   Mon Mar 6 11:27:00 2023 +0200

    comments

commit 7989b31bddc1970a0e7b21157f50ab41ca84bf08
Author: Max Grupper <[email protected]>
Date:   Sat Mar 4 14:00:57 2023 +0200

    remove rvalue ref, improve errors

commit f102f7ea054d43b96b6f83b0227cbaf1bc9de4d0
Author: Max Grupper <[email protected]>
Date:   Fri Mar 3 17:09:34 2023 +0200

    replace decoded fingerprints from options

commit 86901c0968a7f91f7a566d39d3f2fae5e50db228
Author: Max Grupper <[email protected]>
Date:   Fri Mar 3 11:40:42 2023 +0200

    use get_if

commit 2dc5237cddb5ef913e906d8f71c60d36b3059281
Author: Max Grupper <[email protected]>
Date:   Fri Mar 3 10:58:50 2023 +0200

    redundant content

commit c6bf2e04dca3bb69689c80e5478c8c0d10089df0
Merge: ad155bcb 590162ac
Author: Max Grupper <[email protected]>
Date:   Thu Mar 2 21:11:42 2023 +0200

    Merge remote-tracking branch 'origin/feature/AG-18701' into feature/AG-18701

commit ad155bcb7286661a46fc6768b7e8d5a534487d64
Author: Max Grupper <[email protected]>
Date:   Thu Mar 2 21:10:26 2023 +0200

    decode fingerprint inside lib

commit 590162ac036dd197b9bedb9b2fe67f055d4e6da2
Author: Max Grupper <[email protected]>
Date:   Thu Mar 2 16:52:31 2023 +0200

    capi

commit c971029475dc7159d903d02c373ce16371b4a26c
Author: Max Grupper <[email protected]>
Date:   Thu Mar 2 16:07:58 2023 +0200

    refactor

commit d5898ab0c0079778adddd26ee6c0a3ac222129dd
Author: Max Grupper <[email protected]>
Date:   Thu Mar 2 11:44:58 2023 +0200

    try to fix win test

... and 37 more commits
  • Loading branch information
grumaxxx committed Mar 15, 2023
1 parent 92ed0b4 commit 52bbe7c
Show file tree
Hide file tree
Showing 40 changed files with 634 additions and 94 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

* [Feature] Implement fingerprints verification for two types of fingerprints for encrypted DNS protocols.
1) SPKI fingerprint, set separately in the upstream options, it compared with the sha256 hash of the `SubjectPublicKeyInfo` certificate part. It is possible to transfer several such fingerprints, they will try to get matched with one of the certificates in the chain.
2) The fingerprint of the certificate in full, which is passed as one of the DNS Stamp fields. Compared with sha256 hashes of the entire certificate.
* [C API] See `ag_upstream_options.fingerprints`
* [Apple] See `AGDnsUpstream.fingerprints`
* [Android] See `UpstreamSettings.fingerprints`

How it is used:
Computes the Fingerprints (for the public keys/ for full certificate) found in the server’s certificate chain
If a computed fingerprint exactly matches one of the configured pins the chain is successfully verified.

* [Feature] Changed the signature of `com.adguard.dnslibs.proxy.DnsProxy` constructor: now throws a
`com.adguard.dnslibs.proxy.DnsProxyInitException` on failure, containing the same info as the native error.

Expand Down
6 changes: 5 additions & 1 deletion net/application_verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ std::optional<Uint8Vector> ApplicationVerifier::serialize_certificate(X509 *cert
return out;
}

std::optional<std::string> ApplicationVerifier::verify(X509_STORE_CTX *ctx, std::string_view host) const {
std::optional<std::string> ApplicationVerifier::verify(
X509_STORE_CTX *ctx, std::string_view host, std::span<CertFingerprint> fingerprints) const {
if (auto err = verify_host_name(X509_STORE_CTX_get0_cert(ctx), host)) {
return err;
}
Expand All @@ -44,6 +45,9 @@ std::optional<std::string> ApplicationVerifier::verify(X509_STORE_CTX *ctx, std:
}
}

if (auto err = verify_fingerprints(chain, fingerprints)) {
return err;
}
return m_on_certificate_verification(std::move(event));
}

Expand Down
73 changes: 72 additions & 1 deletion net/certificate_verifier.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "dns/net/certificate_verifier.h"
#include <algorithm>
#include <openssl/x509v3.h>

#include "dns/net/certificate_verifier.h"

namespace ag::dns {

std::optional<std::string> CertificateVerifier::verify_host_name(X509 *certificate, std::string_view host) const {
Expand All @@ -12,4 +14,73 @@ std::optional<std::string> CertificateVerifier::verify_host_name(X509 *certifica
return "Host name does not match certificate subject names";
}

static std::optional<Uint8Array<SHA256_DIGEST_LENGTH>> get_cert_hash(X509 *certificate, bool is_public_key) {
Uint8Vector out;
int buf_len;
EVP_PKEY *pkey;

if (is_public_key) {
pkey = X509_get_pubkey(certificate);
buf_len = i2d_PUBKEY(pkey, nullptr);
} else {
buf_len = i2d_X509(certificate, nullptr);
}
if (buf_len <= 0) {
return std::nullopt;
}

out.resize(buf_len);
auto *buffer = (unsigned char *) out.data();
if (is_public_key) {
i2d_PUBKEY(pkey, (unsigned char **) &buffer);
EVP_PKEY_free(pkey);
} else {
i2d_X509(certificate, (unsigned char **) &buffer);
}

std::array<uint8_t, SHA256_DIGEST_LENGTH> hash;
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, out.data(), out.size());
SHA256_Final((uint8_t *) hash.data(), &ctx);

return hash;
}

static bool is_cert_find_in_fingerprints(X509 *certificate, std::span<CertFingerprint> fingerprints) {
std::optional<Uint8Array<SHA256_DIGEST_LENGTH>> spki, full;
return std::any_of(fingerprints.begin(), fingerprints.end(), [&](CertFingerprint f) {
if (auto *spki_digest = std::get_if<SpkiSha256Digest>(&f)) {
if (!spki.has_value()) {
spki = get_cert_hash(certificate, true);
}
return spki.has_value() ? std::equal(spki_digest->data.begin(), spki_digest->data.end(), spki->data())
: false;
} else if (auto *cert_digest = std::get_if<CertSha256Digest>(&f)) {
if (!full.has_value()) {
full = get_cert_hash(certificate, false);
}
return full.has_value() ? std::equal(cert_digest->data.begin(), cert_digest->data.end(), full->data())
: false;
}
return false;
});
}

std::optional<std::string> CertificateVerifier::verify_fingerprints(
STACK_OF(X509) *chain, std::span<CertFingerprint> fingerprints) const {
if (fingerprints.empty()) {
return std::nullopt;
}

for (size_t i = 0; i < sk_X509_num(chain); ++i) {
X509 *certificate = sk_X509_value(chain, i);
if (is_cert_find_in_fingerprints(certificate, fingerprints)) {
return std::nullopt;
}
}

return "Fingerprints doesn't match with any certificate in chain";
}

} // namespace ag::dns
5 changes: 4 additions & 1 deletion net/default_verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ DefaultVerifier &DefaultVerifier::operator=(DefaultVerifier &&other) noexcept {
}

std::optional<std::string> DefaultVerifier::verify(
X509_STORE_CTX *ctx_template, std::string_view host_name) const {
X509_STORE_CTX *ctx_template, std::string_view host_name, std::span<CertFingerprint> fingerprints) const {
if (m_ca_store == nullptr) {
return "CA store is not set";
}
Expand All @@ -126,6 +126,9 @@ std::optional<std::string> DefaultVerifier::verify(
return X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx));
}

if (auto err = verify_fingerprints(X509_STORE_CTX_get0_untrusted(ctx), fingerprints)) {
return err;
}
return std::nullopt;
}

Expand Down
6 changes: 5 additions & 1 deletion net/include/dns/net/application_verifier.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#pragma once

#include <optional>
#include <span>

#include "dns/net/certificate_verifier.h"

namespace ag::dns {
Expand All @@ -20,7 +23,8 @@ class ApplicationVerifier : public CertificateVerifier {

static std::optional<Uint8Vector> serialize_certificate(X509 *cert);

std::optional<std::string> verify(X509_STORE_CTX *ctx, std::string_view host) const override;
std::optional<std::string> verify(
X509_STORE_CTX *ctx, std::string_view host, std::span<CertFingerprint> fingerprints) const override;

private:
OnCertificateVerificationFn m_on_certificate_verification;
Expand Down
36 changes: 30 additions & 6 deletions net/include/dns/net/certificate_verifier.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
#pragma once


#include <optional>
#include <span>
#include <string_view>
#include <variant>
#include <openssl/ssl.h>

#include "common/utils.h"


namespace ag::dns {

struct SpkiSha256Digest {
Uint8Array<SHA256_DIGEST_LENGTH> data;
};

struct CertSha256Digest {
Uint8Array<SHA256_DIGEST_LENGTH> data;
};

using CertFingerprint = std::variant<SpkiSha256Digest, CertSha256Digest>;

/**
* An abstract verifier which encapsulates the SSL/TLS certificate verification procedure.
* It's used in the DNS-over-HTTPS and DNS-over-TLS upstreams, for example.
Expand All @@ -19,22 +31,34 @@ class CertificateVerifier {
virtual ~CertificateVerifier() = default;

/**
* Verify given certificate chain with corresponding server name
* @param chain certificate chain
* Verify given certificate chain with corresponding server name and fingerprints
* @param ctx certificate chain
* @param host_name host name
* @param fingerprints list of fingerprints
* @return nullopt if verified successfully, non-nullopt otherwise
*/
virtual std::optional<std::string> verify(X509_STORE_CTX *ctx, std::string_view host_name) const = 0;
virtual std::optional<std::string> verify(
X509_STORE_CTX *ctx, std::string_view host_name, std::span<CertFingerprint> fingerprints) const = 0;

protected:
/**
* Verify that given certificate matches given server name
*
* @param certificate certificate object
* @param host server name
* @return nullopt if verified successfully, non-nullopt otherwise
*/
virtual std::optional<std::string> verify_host_name(X509 *certificate, std::string_view host) const;

/**
* Verify that given certificate chain matches at least one of corresponding fingerprints:
* Computes the Fingerprints (for the public keys/ for full certificate) found in the server’s certificate chain
* If a computed fingerprint exactly matches one of the configured pins the chain is successfully verified.
* @param chain certificate chain
* @param fingerprints list of fingerprints
* @return nullopt if verified successfully, non-nullopt otherwise
*/
virtual std::optional<std::string> verify_fingerprints(
STACK_OF(X509) *chain, std::span<CertFingerprint> fingerprints) const;
};

} // namespace ag::dns
} // namespace ag::dns
6 changes: 4 additions & 2 deletions net/include/dns/net/default_verifier.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#pragma once

#include <optional>
#include <span>

#include <openssl/err.h>
#include <openssl/ssl.h>

#include "dns/net/certificate_verifier.h"


namespace ag::dns {


Expand All @@ -20,7 +21,8 @@ class DefaultVerifier : public CertificateVerifier {
DefaultVerifier &operator=(const DefaultVerifier &);
DefaultVerifier &operator=(DefaultVerifier &&) noexcept;

std::optional<std::string> verify(X509_STORE_CTX *ctx, std::string_view host_name) const override;
std::optional<std::string> verify(X509_STORE_CTX *ctx, std::string_view host_name,
std::span<CertFingerprint> fingerprints) const override;

private:
X509_STORE *m_ca_store = nullptr;
Expand Down
2 changes: 2 additions & 0 deletions net/include/dns/net/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class SocketFactory {
std::string server_name;
/** Application layer protocols */
std::vector<std::string> alpn;
/** Fingerprints */
std::vector<CertFingerprint> fingerprints;
};

explicit SocketFactory(Parameters parameters);
Expand Down
19 changes: 0 additions & 19 deletions net/outbound_http_proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,25 +261,6 @@ void HttpOProxy::on_close(void *arg, Error<SocketError> error) {
}
}

int HttpOProxy::ssl_verify_callback(X509_STORE_CTX *ctx, void *arg) {
SSL *ssl = (SSL *) X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
auto *self = (HttpOProxy *) arg;

if (self->m_settings->trust_any_certificate) {
log_proxy(self, trace, "Trusting any proxy certificate as specified in settings");
return 1;
}

if (auto err = self->m_parameters.verifier->verify(ctx, SSL_get_servername(ssl, SSL_get_servername_type(ssl)))) {
log_proxy(self, dbg, "Failed to verify certificate: {}", *err);
return 0;
}

log_proxy(self, trace, "Verified successfully");

return 1;
}

void HttpOProxy::handle_http_response_chunk(Connection *conn, std::string_view chunk) {
std::string_view seek;

Expand Down
1 change: 0 additions & 1 deletion net/outbound_http_proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class HttpOProxy : public OutboundProxy {
static void on_connected(void *arg);
static void on_read(void *arg, Uint8View data);
static void on_close(void *arg, Error<SocketError> error);
static int ssl_verify_callback(X509_STORE_CTX *ctx, void *arg);

void handle_http_response_chunk(Connection *conn, std::string_view chunk);

Expand Down
2 changes: 1 addition & 1 deletion net/secured_socket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ SecuredSocket::SecuredSocket(SocketFactory::SocketPtr underlying_socket, const C
{})
, m_state(SS_IDLE)
, m_underlying_socket(std::move(underlying_socket))
, m_codec(cert_verifier, secure_parameters.session_cache)
, m_codec(cert_verifier, secure_parameters.session_cache, std::move(secure_parameters.fingerprints))
, m_sni(std::move(secure_parameters.server_name))
, m_alpn(std::move(secure_parameters.alpn))
, m_log(__func__) {
Expand Down
9 changes: 6 additions & 3 deletions net/tls_codec.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "tls_codec.h"
#include <cstring>
#include <numeric>

Expand Down Expand Up @@ -25,10 +26,12 @@ static Uint8Vector make_alpn(const std::vector<std::string> &protos) {
return alpn;
}

TlsCodec::TlsCodec(const CertificateVerifier *cert_verifier, TlsSessionCache *session_cache)
TlsCodec::TlsCodec(const CertificateVerifier *cert_verifier, TlsSessionCache *session_cache,
std::vector<CertFingerprint> fingerprint)
: m_cert_verifier(cert_verifier)
, m_session_cache(session_cache)
, m_log(__func__) {
, m_log(__func__)
, m_fingerprints(std::move(fingerprint)) {
}

Error<TlsCodec::TlsError> TlsCodec::connect(const std::string &sni, std::vector<std::string> alpn) {
Expand Down Expand Up @@ -167,7 +170,7 @@ int TlsCodec::ssl_verify_callback(X509_STORE_CTX *ctx, void *arg) {
return 0;
}

if (auto err = self->m_cert_verifier->verify(ctx, self->m_server_name)) {
if (auto err = self->m_cert_verifier->verify(ctx, self->m_server_name, self->m_fingerprints)) {
dbglog(self->m_log, "Failed to verify certificate: {}", *err);
return 0;
}
Expand Down
4 changes: 3 additions & 1 deletion net/tls_codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class TlsCodec {
};
using WriteDecryptedResult = Result<DecryptedBytesWritten, TlsError>;

TlsCodec(const CertificateVerifier *cert_verifier, TlsSessionCache *session_cache);
TlsCodec(const CertificateVerifier *cert_verifier, TlsSessionCache *session_cache,
std::vector<CertFingerprint> fingerprint);

~TlsCodec() = default;

Expand Down Expand Up @@ -96,6 +97,7 @@ class TlsCodec {
TlsSessionCache *m_session_cache = nullptr;
bssl::UniquePtr<SSL> m_ssl;
Logger m_log;
std::vector<CertFingerprint> m_fingerprints;
std::string m_server_name;

static int ssl_verify_callback(X509_STORE_CTX *ctx, void *arg);
Expand Down
Loading

0 comments on commit 52bbe7c

Please sign in to comment.