From patchwork Sun Jul 22 00:06:45 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 420 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.28.255.1]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id 41rxI3ZXVFsHCAAAIUCqbw for ; Sun, 22 Jul 2018 06:07:50 -0400 Received: from director3.mail.ord1c.rsapps.net ([172.28.255.1]) by director7.mail.ord1d.rsapps.net (Dovecot) with LMTP id w9m6I3ZXVFsBTQAAovjBpQ ; Sun, 22 Jul 2018 06:07:50 -0400 Received: from smtp35.gate.ord1c ([172.28.255.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by director3.mail.ord1c.rsapps.net with LMTP id GKtzI3ZXVFvxegAAdSFV8w ; Sun, 22 Jul 2018 06:07:50 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp35.gate.ord1c.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 1682a4ea-8d97-11e8-ba54-5452002f485d-1-1 Received: from [216.105.38.7] ([216.105.38.7:45800] helo=lists.sourceforge.net) by smtp35.gate.ord1c.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id B9/8B-06038-577545B5; Sun, 22 Jul 2018 06:07:50 -0400 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1fhBGd-0007Rr-Fd; Sun, 22 Jul 2018 10:07:07 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1fhBGb-0007RP-Rb for openvpn-devel@lists.sourceforge.net; Sun, 22 Jul 2018 10:07:05 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=dgw6cvAkkTanS0CJGL/OHz1CL3WfsgFc1snKISkaiYE=; b=QOLZ1Fs0ICmZl712VtqafGTkGC Hn2+LVGkONoZrTntY3T/l0TeAExlcwn3KDAtVLZ4OPnXHmjkc4FiIqkYCkA3HCBuk7QzznkCBRhy/ VzCCAu+hBHVs6uCstsdbqXE96iBeCUi0H0mtDFZZp7ULkzCQwoxp8YywNFsGyB9sQrng=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=dgw6cvAkkTanS0CJGL/OHz1CL3WfsgFc1snKISkaiYE=; b=Dt2Uxd/jbffygSN3mthFCml4JX I3iy0VK8a84FCmdwrR13KL+n5bRQ61y3pA8AcnIJZPB7Wik4bmZMH14+uv7jp1CMZZjsgg9MeyPQK ObK9BwWVyR+0atJ1AjR7tsEIfYTFRBgaFSuepFts0HZlOIYuGXwjTwMFS2Mq5b9dHnQ8=; Received: from mail-ed1-f48.google.com ([209.85.208.48]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1fhBGW-005lq6-1Y for openvpn-devel@lists.sourceforge.net; Sun, 22 Jul 2018 10:07:05 +0000 Received: by mail-ed1-f48.google.com with SMTP id t2-v6so13272622edr.5 for ; Sun, 22 Jul 2018 03:06:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=dgw6cvAkkTanS0CJGL/OHz1CL3WfsgFc1snKISkaiYE=; b=rJxdBoduQIeSIAVrAIfirn5OUeZhZJGsaQUAgCZH2IREveASp5LeTMc33R3B8FurZG rbDewBKwzMngKJZ8ZK7Tm3iHJwK+StUGNjcv6LhSuqAofyb8e0V6LwxTAVa0d8wUHMDb 8zq9DX0RXgomcDf08xXbu9DG46M+DAfkqTnsPS9QQgrbuQW7TZdi8g5L+YNFijAbwo1N ZY6jtKhVw2qCRh+GLWScBbHViQMgsYNhMMRRjplwvB/yC98aA2G77Ay215R7zk2/NnD9 wOr6XD095R+QNv2JIeW6VceKDi1LeUDgPyQ9iCr6q31sTdck3Pe/tT02cGiy5OLwFwHH EvAA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=dgw6cvAkkTanS0CJGL/OHz1CL3WfsgFc1snKISkaiYE=; b=DAi1RPk89ngJq+Iap9N4x4pWHhDQXyqBPe1BRg/iePi4t8MvO305SGliet5p2zyzsH /5ixw/kgUMhmmZVO00F2LvJR9Oa0o98JCSxp2nZutCse/ueI0cRI9IdPSoyoCKwrLa7n fsfHpH/m0vZIMU83aMhB9eiiU7s9HtJKb1uoM+4mrpWweCGAVM9Zt1EHnv/AqUY+CFOE Bt0jtDFJpll5kcldXxYJQfu0dfH+XeL1gj9cJONun4aOudgzYCOpKqcpfups4BMBcouh WNHbh2KKHPtmydG39piSSZc4PQf+WbpAfcUBM0TJfYxWwMRDi7ZJ/EZrB0gy5taJPBti 5/JA== X-Gm-Message-State: AOUpUlHl8bXcxkNIYNDwLSUMRYfl7i4R0iMrwz1KRklMOP1FqJdvEvJG x/yqbTwOW1kL4KXEaeRFtJwDLBmtgPs= X-Google-Smtp-Source: AAOMgpfU0/sJRtGLPDm1jMrvZdD/6vudC7cP5rm/lhLIVvg+tK376ivW+RMClYLARcWEpE+n2Dy39Q== X-Received: by 2002:a50:c8c3:: with SMTP id k3-v6mr9252883edh.193.1532254013183; Sun, 22 Jul 2018 03:06:53 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:7010:ac59:baa:1fcf]) by smtp.gmail.com with ESMTPSA id c25-v6sm3036219edt.54.2018.07.22.03.06.52 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 22 Jul 2018 03:06:52 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Sun, 22 Jul 2018 12:06:45 +0200 Message-Id: <20180722100645.5813-1-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <8eebd206-6742-712d-2097-ee196bbb025c@unstable.cc> References: <8eebd206-6742-712d-2097-ee196bbb025c@unstable.cc> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.48 listed in wl.mailspike.net] -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.48 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender X-Headers-End: 1fhBGW-005lq6-1Y Subject: [Openvpn-devel] [PATCH v3 2/9] Add crypto_pem_{encode,decode}() X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger Needed for tls-crypt-v2, but isolated enough to be reviewed as a separate patch. The encode API allocates memory, because it fits our typical gc-oriented code pattern and the caller does not have to do multiple calls or calculations to determine the required destination buffer size. The decode API does not allocate memory, because the required destination buffer is always smaller than the input buffer (so is easy to manage by the caller) and does not force the caller to use the heap. Signed-off-by: Steffan Karger Acked-by: Antonio Quartulli --- v2: - Add doxygen lines with "." - mbedtls: clear dst if encoding failed - openssl: check BIO_new() return value - update copyright date - rebase op cleanup patches, removes script_security hack - extend unit test to check that pem_encode is not a no-op v3: - move bio assigned in openssl pem decode to own line src/openvpn/crypto_backend.h | 29 +++++++++ src/openvpn/crypto_mbedtls.c | 75 ++++++++++++++++++++++ src/openvpn/crypto_openssl.c | 82 ++++++++++++++++++++++++ tests/unit_tests/openvpn/Makefile.am | 16 ++++- tests/unit_tests/openvpn/test_crypto.c | 88 ++++++++++++++++++++++++++ 5 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/openvpn/test_crypto.c diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h index bb53b8c9..f917c8d7 100644 --- a/src/openvpn/crypto_backend.h +++ b/src/openvpn/crypto_backend.h @@ -36,6 +36,7 @@ #include "crypto_mbedtls.h" #endif #include "basic.h" +#include "buffer.h" /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */ #define OPENVPN_AEAD_TAG_LENGTH 16 @@ -105,6 +106,34 @@ void show_available_digests(void); void show_available_engines(void); +/** + * Encode binary data as PEM. + * + * @param name The name to use in the PEM header/footer. + * @param dst Destination buffer for PEM-encoded data. Must be a valid + * pointer to an uninitialized buffer structure. Iff this + * function returns true, the buffer will contain memory + * allocated through the supplied gc. + * @param src Source buffer. + * @param gc The garbage collector to use when allocating memory for dst. + * + * @return true iff PEM encode succeeded. + */ +bool crypto_pem_encode(const char *name, struct buffer *dst, + const struct buffer *src, struct gc_arena *gc); + +/** + * Decode a PEM buffer to binary data. + * + * @param name The name expected in the PEM header/footer. + * @param dst Destination buffer for decoded data. + * @param src Source buffer (PEM data). + * + * @return true iff PEM decode succeeded. + */ +bool crypto_pem_decode(const char *name, struct buffer *dst, + const struct buffer *src); + /* * * Random number functions, used in cases where we want diff --git a/src/openvpn/crypto_mbedtls.c b/src/openvpn/crypto_mbedtls.c index 8ff6704d..82f4e574 100644 --- a/src/openvpn/crypto_mbedtls.c +++ b/src/openvpn/crypto_mbedtls.c @@ -44,11 +44,13 @@ #include "otime.h" #include "misc.h" +#include #include #include #include #include #include +#include #include @@ -229,6 +231,79 @@ show_available_engines(void) "available\n"); } +bool +crypto_pem_encode(const char *name, struct buffer *dst, + const struct buffer *src, struct gc_arena *gc) +{ + /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */ + char header[1000+1] = { 0 }; + char footer[1000+1] = { 0 }; + + if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----\n", name)) + { + return false; + } + if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----\n", name)) + { + return false; + } + + size_t out_len = 0; + if (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL != + mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), + NULL, 0, &out_len)) + { + return false; + } + + *dst = alloc_buf_gc(out_len, gc); + if (!mbed_ok(mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), + BPTR(dst), BCAP(dst), &out_len)) + || !buf_inc_len(dst, out_len)) + { + CLEAR(*dst); + return false; + } + + return true; +} + +bool +crypto_pem_decode(const char *name, struct buffer *dst, + const struct buffer *src) +{ + /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */ + char header[1000+1] = { 0 }; + char footer[1000+1] = { 0 }; + + if (*(BLAST(src)) != '\0') + { + msg(M_WARN, "PEM decode error: source buffer not null-terminated"); + return false; + } + if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----", name)) + { + return false; + } + if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----", name)) + { + return false; + } + + size_t use_len = 0; + mbedtls_pem_context ctx = { 0 }; + bool ret = mbed_ok(mbedtls_pem_read_buffer(&ctx, header, footer, BPTR(src), + NULL, 0, &use_len)); + if (ret && !buf_write(dst, ctx.buf, ctx.buflen)) + { + ret = false; + msg(M_WARN, "PEM decode error: destination buffer too small"); + } + + mbedtls_pem_free(&ctx); + return ret; +} + /* * * Random number functions, used in cases where we want diff --git a/src/openvpn/crypto_openssl.c b/src/openvpn/crypto_openssl.c index 4fb2f6d6..9ec2048d 100644 --- a/src/openvpn/crypto_openssl.c +++ b/src/openvpn/crypto_openssl.c @@ -387,6 +387,88 @@ show_available_engines(void) #endif } + +bool +crypto_pem_encode(const char *name, struct buffer *dst, + const struct buffer *src, struct gc_arena *gc) +{ + bool ret = false; + BIO *bio = BIO_new(BIO_s_mem()); + if (!bio || !PEM_write_bio(bio, name, "", BPTR(src), BLEN(src))) + { + ret = false; + goto cleanup; + } + + BUF_MEM *bptr; + BIO_get_mem_ptr(bio, &bptr); + + *dst = alloc_buf_gc(bptr->length, gc); + ASSERT(buf_write(dst, bptr->data, bptr->length)); + + ret = true; +cleanup: + if (!BIO_free(bio)) + { + ret = false;; + } + + return ret; +} + +bool +crypto_pem_decode(const char *name, struct buffer *dst, + const struct buffer *src) +{ + bool ret = false; + + BIO *bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src)); + if (!bio) + { + crypto_msg(M_FATAL, "Cannot open memory BIO for PEM decode"); + } + + char *name_read = NULL; + char *header_read = NULL; + uint8_t *data_read = NULL; + long data_read_len = 0; + if (!PEM_read_bio(bio, &name_read, &header_read, &data_read, + &data_read_len)) + { + dmsg(D_CRYPT_ERRORS, "%s: PEM decode failed", __func__); + goto cleanup; + } + + if (strcmp(name, name_read)) + { + dmsg(D_CRYPT_ERRORS, + "%s: unexpected PEM name (got '%s', expected '%s')", + __func__, name_read, name); + goto cleanup; + } + + uint8_t *dst_data = buf_write_alloc(dst, data_read_len); + if (!dst_data) + { + dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__, + BCAP(dst), data_read_len); + goto cleanup; + } + memcpy(dst_data, data_read, data_read_len); + + ret = true; +cleanup: + OPENSSL_free(name_read); + OPENSSL_free(header_read); + OPENSSL_free(data_read); + if (!BIO_free(bio)) + { + ret = false;; + } + + return ret; +} + /* * * Random number functions, used in cases where we want diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index db4d46e1..1ff62615 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT check_PROGRAMS += argv_testdriver buffer_testdriver endif -check_PROGRAMS += packet_id_testdriver tls_crypt_testdriver +check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver TESTS = $(check_PROGRAMS) @@ -31,6 +31,20 @@ buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/platform.c +crypto_testdriver_CFLAGS = @TEST_CFLAGS@ \ + -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ + $(OPTIONAL_CRYPTO_CFLAGS) +crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ + $(OPTIONAL_CRYPTO_LIBS) +crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \ + $(openvpn_srcdir)/buffer.c \ + $(openvpn_srcdir)/crypto.c \ + $(openvpn_srcdir)/crypto_mbedtls.c \ + $(openvpn_srcdir)/crypto_openssl.c \ + $(openvpn_srcdir)/otime.c \ + $(openvpn_srcdir)/packet_id.c \ + $(openvpn_srcdir)/platform.c + packet_id_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ $(OPTIONAL_CRYPTO_CFLAGS) diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c new file mode 100644 index 00000000..7027d3da --- /dev/null +++ b/tests/unit_tests/openvpn/test_crypto.c @@ -0,0 +1,88 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2016-2018 Fox Crypto B.V. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "crypto.h" + +#include "mock_msg.h" + +static const char testtext[] = "Dummy text to test PEM encoding"; + +static void +crypto_pem_encode_decode_loopback(void **state) { + struct gc_arena gc = gc_new(); + struct buffer src_buf; + buf_set_read(&src_buf, (void *)testtext, sizeof(testtext)); + + uint8_t dec[sizeof(testtext)]; + struct buffer dec_buf; + buf_set_write(&dec_buf, dec, sizeof(dec)); + + struct buffer pem_buf; + + assert_true(crypto_pem_encode("TESTKEYNAME", &pem_buf, &src_buf, &gc)); + assert_true(BLEN(&src_buf) < BLEN(&pem_buf)); + + /* Wrong key name */ + assert_false(crypto_pem_decode("WRONGNAME", &dec_buf, &pem_buf)); + + assert_true(crypto_pem_decode("TESTKEYNAME", &dec_buf, &pem_buf)); + assert_int_equal(BLEN(&src_buf), BLEN(&dec_buf)); + assert_memory_equal(BPTR(&src_buf), BPTR(&dec_buf), BLEN(&src_buf)); + + gc_free(&gc); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(crypto_pem_encode_decode_loopback), + }; + +#if defined(ENABLE_CRYPTO_OPENSSL) + OpenSSL_add_all_algorithms(); +#endif + + int ret = cmocka_run_group_tests_name("crypto tests", tests, NULL, NULL); + +#if defined(ENABLE_CRYPTO_OPENSSL) + EVP_cleanup(); +#endif + + return ret; +}