]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
GnuTLS challenge signing
authorTom Carroll <incentivedesign@gmail.com>
Fri, 24 Apr 2020 06:15:59 +0000 (23:15 -0700)
committerTom Carroll <incentivedesign@gmail.com>
Wed, 6 May 2020 08:54:26 +0000 (01:54 -0700)
This is the GnuTLS implementation for signing the challenge. Variables
added to store the paths for the second set of credentials.

Challenge response comprises two elements: an identity and signature.
The identity is the user certificate, represented as a PEM-encoded PKCS7
certificate chain. The challenge is signed with the user certificate
and submitted back to the server as a PEM-encoded signature.

Signed-off-by: Tom Carroll <incentivedesign@gmail.com>
gnutls.c
library.c
openconnect-internal.h

index 681ad12a6e076a45d36ab03e345a94f8952fcffb..b40fa64a773334cf98ebda1ef8c6b96684481e1d 100644 (file)
--- a/gnutls.c
+++ b/gnutls.c
@@ -2024,6 +2024,223 @@ static int load_certificate(struct openconnect_info *vpninfo)
        return ret;
 }
 
+static int pem_encode(const gnutls_datum_t *data,
+               gnutls_datum_t *result)
+{
+       char *r, *s, *p = NULL;
+       size_t r_n, s_n, p_n = 0;
+       int ret;
+
+       if (result == NULL)
+               return GNUTLS_E_INVALID_REQUEST;
+
+       r = (char *)data->data;
+       r_n = data->size;
+
+       /* handle when data->size == 0 */
+       if (r_n == 0) {
+               ret = 0;
+               goto done;
+       }
+
+       /* size of encoding */
+       p_n = ((r_n + 2) / 3) * 4;
+       /* plus newlines (subtract off nul) */
+       p_n += ((p_n + 63) / 64) - 1;
+       /* plus nul */
+       p = s = malloc(p_n + 1); s_n = p_n;
+       if (p == NULL)
+               return GNUTLS_E_MEMORY_ERROR;
+
+       /**
+        * r := ptr to data to encode
+        * r_n := the length of data to encode
+        * s := output pointer
+        * s_n := length of output, not including terminating nul
+        */
+       while (r_n > 0) {
+               /* encode one line at at time */
+               const gnutls_datum_t datum = {(void *)r, r_n > 48 ? 48 : r_n};
+               char tmp[66];
+               size_t n = sizeof tmp;
+
+               r += datum.size; r_n -= datum.size;
+               ret = gnutls_pem_base64_encode(NULL, &datum, tmp, &n);
+               /* check assumptions */
+               if ((ret < 0) || (n > 64) || (n > s_n)) {
+                       ret = GNUTLS_E_BASE64_ENCODING_ERROR;
+                       goto cleanup;
+               }
+               /* append nul */
+               tmp[n++] = '\n';
+               /* copy */
+               memcpy(s, tmp, n);
+               /* s_n will wrap on the last block */
+               s += n; s_n -= n;
+       }
+       /* nul terminate */
+       p[p_n] = 0;
+
+ done:
+       *result = (gnutls_datum_t) {(void *)p, p_n};
+       p = NULL;
+
+ cleanup:
+       free(p);
+       return ret;
+}
+
+/**
+ * Sign the challenge provide by the server.
+ *
+ * challenge is the XML response provided by the server. An example is
+ * <?xml version="1.0" encoding="UTF-8"?>
+ * <config-auth client="vpn" type="auth-request" aggregate-auth-version="       2">
+ * <opaque is-for="sg">
+ * <tunnel-group>ANYCONNECT-MCA</tunnel-group>
+ * <aggauth-handle>136775778</aggauth-handle>
+ * <auth-method>multiple-cert</auth-method>
+ * <auth-method>single-sign-on</auth-method>
+ * <config-hash>1506879881148</config-hash>
+ * </opaque>
+ * <multiple-client-cert-request>
+ * <hash-algorithm>sha256</hash-algorithm>
+ * <hash-algorithm>sha384</hash-algorithm>
+ * <hash-algorithm>sha512</hash-algorithm>
+ * </multiple-client-cert-request>
+ * <random>FA4003BD87436B227####snip####C138A08FF724F0100015B863F75091483       9EE79C86DFE8F0B9A0199E2</random>
+ * <cert-authenticated></cert-authenticated>
+ * </config-auth>
+ *
+ * identity is a PEM-encoded (base64, line length of 64) PKCS7
+ * certificate chain. Note that I have only gotten anyconnect to
+ * use a single certificate for signing a challenge.
+ *
+ * response is the PEM-encoded signature of the callenge.
+ */
+int cert_auth_challenge_response(struct openconnect_info *vpninfo,
+               int cert_rq, const char *challenge, char **identity,
+               struct challenge_response *response)
+{
+       const struct table_entry {
+               int id;
+               gnutls_digest_algorithm_t digest;
+       } *entry, table[] = {
+               { CERT_AUTH_DIGEST_SHA512, GNUTLS_DIG_SHA512 },
+               { CERT_AUTH_DIGEST_SHA384, GNUTLS_DIG_SHA384 },
+               { CERT_AUTH_DIGEST_SHA256, GNUTLS_DIG_SHA256 },
+               { CERT_AUTH_DIGEST_UNKNOWN, GNUTLS_DIG_UNKNOWN },
+       };
+       gnutls_privkey_t key = NULL;
+       gnutls_pubkey_t pubkey = NULL;
+       gnutls_x509_crt_t *chain = NULL;
+       unsigned int chain_len = 0;
+       gnutls_pkcs7_t p7 = NULL;
+       gnutls_datum_t p7bin, p7pem = {NULL, 0};
+       const gnutls_datum_t dat = {(void *)challenge, strlen(challenge)};
+       gnutls_datum_t sig, sigpem = {NULL, 0};
+       unsigned int i;
+       int pk;
+       int ret = -ENOMEM, err = 0;
+
+       if (response == NULL)
+               return -EINVAL;
+
+       ret = load_keycert(vpninfo, vpninfo->key2, vpninfo->cert2,
+                       vpninfo->key2_password, &key, &chain, &chain_len, NULL);
+       if (ret < 0)
+               goto cleanup;
+       /**
+        * Get PK algorithm of the certificate
+        */
+       err = gnutls_pubkey_init(&pubkey);
+       if (err < 0)
+               goto cleanup;
+
+       err = gnutls_pubkey_import_x509(pubkey, chain[0], 0);
+       if (err < 0)
+               goto cleanup;
+
+       pk = gnutls_pubkey_get_pk_algorithm(pubkey, NULL);
+
+       /* Export identity as PKCS7 */
+       err = gnutls_pkcs7_init(&p7);
+       if (err < 0)
+               goto cleanup;
+
+       for (i = 0; i < chain_len; i++) {
+               err = gnutls_pkcs7_set_crt(p7, chain[i]);
+               if (err < 0)
+                       goto cleanup;
+       }
+
+       err = gnutls_pkcs7_export2(p7, GNUTLS_X509_FMT_DER, &p7bin);
+       if (err < 0)
+               goto cleanup;
+
+       /* export as PEM */
+       err = pem_encode(&p7bin, &p7pem);
+       free_datum(&p7bin);
+       if (err < 0)
+               goto cleanup;
+
+       /**
+        * Sign the challenge
+        * Anyconnect prefers SHA512
+        */
+       for (entry = table; entry->digest != GNUTLS_DIG_UNKNOWN; entry++) {
+               gnutls_digest_algorithm_t algo = entry->digest;
+
+               if ((cert_rq & entry->id) != entry->id)
+                       continue;
+#if GNUTLS_VERSION_NUMBER >= 0x030600
+               err = gnutls_privkey_sign_data2(key, gnutls_pk_to_sign(pk, algo), 0, &dat, &sig);
+#else /* GNUTLS_VERSION_NUMBER < 0x030600 */
+               err = gnutls_privkey_sign_data(key, algo, 0, &dat, &sig);
+#endif
+               if (err != GNUTLS_E_INVALID_REQUEST
+                       && err != GNUTLS_E_CONSTRAINT_ERROR)
+                       break;
+       }
+       if (entry->digest == GNUTLS_DIG_UNKNOWN) {
+               err = GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM;
+               ret = -EINVAL;
+       }
+       if (err < 0)
+               goto cleanup;
+
+       err = pem_encode(&sig, &sigpem);
+       free_datum(&sig);
+       if (err < 0)
+               goto cleanup;
+
+       /* Success! */
+       *identity = (char *)p7pem.data;
+       p7pem = (gnutls_datum_t) {0};
+       *response =
+         (struct challenge_response) {entry->id, (char *)sigpem.data};
+       sigpem = (gnutls_datum_t) {0};
+       ret = 0;
+
+ cleanup:
+       if (err < 0) {
+               vpn_progress(vpninfo, PRG_ERR,
+                   _("Failed responding to the challenge: (%d) %s\n"),
+                   err, gnutls_strerror(err));
+               vpn_progress(vpninfo, PRG_DEBUG,
+                   _("cert_rq = 0x%.2x"), cert_rq);
+       }
+       free_datum(&sigpem);
+       free_datum(&p7pem);
+       gnutls_pkcs7_deinit(p7);
+       gnutls_privkey_deinit(key);
+       gnutls_pubkey_deinit(pubkey);
+       for (i = 0; i < chain_len; i++)
+               gnutls_x509_crt_deinit(chain[i]);
+       gnutls_free(chain);
+       return ret;
+}
+
 static int get_cert_fingerprint(struct openconnect_info *vpninfo,
                                gnutls_x509_crt_t cert,
                                gnutls_digest_algorithm_t algo,
index 586c5798a7f03f9a9932a38a138438bbcc44a686..5d89f4d1d234c895ae3b3ccef83e59b4e6b4e511 100644 (file)
--- a/library.c
+++ b/library.c
@@ -395,6 +395,9 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo)
        free(vpninfo->ifname);
        free(vpninfo->dtls_cipher);
        free(vpninfo->peer_cert_hash);
+       free(vpninfo->cert2);
+       free(vpninfo->key2);
+       free_pass(&vpninfo->key2_password);
 #if defined(OPENCONNECT_OPENSSL)
        free(vpninfo->cstp_cipher);
 #if defined(HAVE_BIO_METH_FREE)
index 0b01dbb6ebd2c134b26ceeb1649afb9c0daf8109..b86b6cb24d7027bb78ab34389acbad082921a0b1 100644 (file)
@@ -451,6 +451,10 @@ struct openconnect_info {
        int no_http_keepalive;
        int dump_http_traffic;
 
+       char *cert2;
+       char *key2;
+       char *key2_password;
+
        int token_mode;
        int token_bypassed;
        int token_tries;
@@ -744,6 +748,32 @@ struct openconnect_info {
        } while(0)
 #define vpn_perror(vpninfo, msg) vpn_progress((vpninfo), PRG_ERR, "%s: %s\n", (msg), strerror(errno))
 
+/* certificate authentication */
+typedef enum {
+       CERT_AUTH_REQ_CERT1 = (1U<<0),
+#define CERT_AUTH_REQ_CERT1 CERT_AUTH_REQ_CERT1
+       CERT_AUTH_REQ_CERT2 = (1U<<1),
+#define CERT_AUTH_CERT2 CERT_AUTH_REQ_CERT2,
+       CERT_AUTH_DIGEST_SHA256 = (1U<<8),
+#define CERT_AUTH_DIGEST_SHA256 CERT_AUTH_DIGEST_SHA256
+       CERT_AUTH_DIGEST_SHA384 = (1U<<9),
+#define CERT_AUTH_DIGEST_SHA384 CERT_AUTH_DIGEST_SHA384
+       CERT_AUTH_DIGEST_SHA512 = (1U<<10),
+#define CERT_AUTH_DIGEST_SHA512 CERT_AUTH_DIGEST_SHA512
+       CERT_AUTH_DIGEST_UNKNOWN = (1U<<30),
+#define CERT_AUTH_DIGEST_UNKNOWN CERT_AUTH_DIGEST_UNKNOWN
+} cert_auth_t;
+#define CERT_AUTH_DIGEST_MASK (CERT_AUTH_DIGEST_SHA256|CERT_AUTH_DIGEST_SHA384|CERT_AUTH_DIGEST_SHA512)
+
+struct challenge_response {
+       int digest;
+       char *data;
+};
+
+int cert_auth_challenge_response(struct openconnect_info *vpninfo,
+               int cert_rq, const char *challenge, char **identity,
+               struct challenge_response *response);
+
 /****************************************************************************/
 /* Oh Solaris how we hate thee! */
 #ifdef HAVE_SUNOS_BROKEN_TIME