Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
allow specification of multiple certificate fingerprints on command-l…
…ine via --servercert

Server certificates will be accepted if they match *any* of the provided fingerprints.

Behavior with `--servercert` is otherwise unchanged; it still disables system trust
stores, meaning that _only_ certificates matching the provided fingerprints will be
accepted if it is specified one or more times.

This will allow the use of `--servercert` to non-interactively connect to a server which
has a non-trusted certificate and redirects to one or more other servers with non-trusted
certificates. (See #25 for a real case.)

Signed-off-by: Daniel Lenski <dlenski@gmail.com>
  • Loading branch information
dlenski committed Feb 5, 2021
1 parent f07d798 commit 856d39d
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 44 deletions.
90 changes: 47 additions & 43 deletions main.c
Expand Up @@ -92,7 +92,14 @@ static int cookieonly;
static int allow_stdin_read;

static char *token_filename;
static char *server_cert = NULL;
static int allowed_fingerprints;

struct accepted_cert {
struct accepted_cert *next;
char *fingerprint;
char *host;
int port;
} *accepted_certs;

static char *username;
static char *password;
Expand Down Expand Up @@ -849,7 +856,7 @@ static void usage(void)
#endif

printf("\n%s:\n", _("Server validation"));
printf(" --servercert=FINGERPRINT %s\n", _("Server's certificate SHA1 fingerprint"));
printf(" --servercert=FINGERPRINT %s\n", _("Accept only server certificate with this fingerprint"));
printf(" --no-system-trust %s\n", _("Disable default system certificate authorities"));
printf(" --cafile=FILE %s\n", _("Cert file for server verification"));

Expand Down Expand Up @@ -1526,6 +1533,7 @@ int main(int argc, char **argv)
struct openconnect_info *vpninfo;
char *urlpath = NULL;
struct oc_vpn_option *gai;
struct accepted_cert *newcert;
char *ip;
char *proxy = getenv("https_proxy");
char *vpnc_script = NULL;
Expand Down Expand Up @@ -1696,8 +1704,19 @@ int main(int argc, char **argv)
}
break;
case OPT_SERVERCERT:
server_cert = keep_config_arg();
newcert = malloc(sizeof(*newcert));
if (!newcert) {
fprintf(stderr, _("Failed to allocate memory\n"));
exit(1);
}
newcert->next = accepted_certs;
accepted_certs = newcert;
newcert->fingerprint = keep_config_arg();
newcert->host = NULL;
newcert->port = 0;

openconnect_set_system_trust(vpninfo, 0);
allowed_fingerprints++;
break;
case OPT_RESOLVE:
ip = strchr(config_arg, ':');
Expand Down Expand Up @@ -2207,47 +2226,41 @@ static void __attribute__ ((format(printf, 3, 4)))
}
}

struct accepted_cert {
struct accepted_cert *next;
char *fingerprint;
char *host;
int port;
} *accepted_certs;

static int validate_peer_cert(void *_vpninfo, const char *reason)
{
struct openconnect_info *vpninfo = _vpninfo;
const char *fingerprint;
struct accepted_cert *this;

#ifdef INSECURE_DEBUGGING
if (server_cert && strcasecmp(server_cert, "ACCEPT")) {
#else
if (server_cert) {
#endif
int err = openconnect_check_peer_cert_hash(vpninfo, server_cert);

if (!err)
return 0;

if (err < 0)
vpn_progress(vpninfo, PRG_ERR,
_("Could not calculate hash of server's certificate\n"));
else
vpn_progress(vpninfo, PRG_ERR,
_("Server SSL certificate didn't match: %s\n"),
openconnect_get_peer_cert_hash(vpninfo));

return -EINVAL;
}

fingerprint = openconnect_get_peer_cert_hash(vpninfo);

for (this = accepted_certs; this; this = this->next) {
if (!strcasecmp(this->host, vpninfo->hostname) &&
this->port == vpninfo->port &&
!openconnect_check_peer_cert_hash(vpninfo, this->fingerprint))
#ifdef INSECURE_DEBUGGING
if (this->port == 0 && this->host == NULL && !strcasecmp(this->fingerprint, "ACCEPT")) {
fprintf(stderr, _("Insecurely accepting certificate from VPN server \"%s\" because you ran with --servercert=ACCEPT.\n"),
vpninfo->hostname);
return 0;
} else
#endif
/* XX: if set by --servercert argument (port 0 and host NULL), accept for any host/port */
if ((this->host == NULL || !strcasecmp(this->host, vpninfo->hostname)) &&
(this->port == 0 || this->port == vpninfo->port)) {
int err = openconnect_check_peer_cert_hash(vpninfo, this->fingerprint);
if (!err)
return 0;
else if (err < 0) {
vpn_progress(vpninfo, PRG_ERR,
_("Could not check server's certificate against %s\n"),
this->fingerprint);
}
}
}

if (allowed_fingerprints) {
vpn_progress(vpninfo, PRG_ERR,
_("None of the %d fingerprint(s) specified via --servercert match server's certificate: %s\n"),
allowed_fingerprints, fingerprint);
return -EINVAL;
}

while (1) {
Expand All @@ -2263,12 +2276,6 @@ static int validate_peer_cert(void *_vpninfo, const char *reason)
if (non_inter)
return -EINVAL;

#ifdef INSECURE_DEBUGGING
if (!strcasecmp(server_cert, "ACCEPT")) {
fprintf(stderr, _("Insecurely accepting because you ran with --servertcert=ACCEPT.\n"));
goto accepted;
}
#endif
fprintf(stderr, _("Enter '%s' to accept, '%s' to abort; anything else to view: "),
_("yes"), _("no"));

Expand All @@ -2278,9 +2285,6 @@ static int validate_peer_cert(void *_vpninfo, const char *reason)

if (!strcasecmp(response, _("yes"))) {
struct accepted_cert *newcert;
#ifdef INSECURE_DEBUGGING
accepted:
#endif
newcert = malloc(sizeof(*newcert));
if (newcert) {
newcert->next = accepted_certs;
Expand Down
5 changes: 4 additions & 1 deletion openconnect.8.in
Expand Up @@ -554,7 +554,10 @@ to
instead of using the normal resolver to look it up.
.TP
.B \-\-servercert=HASH
Accept server's SSL certificate only if the provided fingerprint matches.
Accept server's SSL certificate only if it matches the provided fingerprint.
This option implies \-\-no\-system\-trust, and may be specified multiple
times in order to accept multiple possible fingerprints.

The allowed fingerprint types are
.IR SHA1 ,
.IR SHA256 ,
Expand Down

0 comments on commit 856d39d

Please sign in to comment.