Blame SOURCES/0010-Add-a-PEM-validity-checker-and-validate-SCEP-CA-file.patch

cebf48
From f636d0f64fbcb978b06afe9f9576678afcee01c0 Mon Sep 17 00:00:00 2001
cebf48
From: Rob Crittenden <rcritten@redhat.com>
cebf48
Date: Thu, 4 Nov 2021 13:51:31 -0400
cebf48
Subject: [PATCH] Add a PEM validity checker and validate SCEP CA files
cebf48
cebf48
If a non-PEM file was passed into add-scep-ca it would
cebf48
accept it without question but later fail with:
cebf48
cebf48
status: CA_UNREACHABLE
cebf48
ca-error: Error: failed to verify signature on server response.
cebf48
cebf48
Try to do basic validation of user-provided PEM files by:
cebf48
cebf48
- stripping BEGIN/END headers
cebf48
- removing newlines and carriage returns
cebf48
- using OpenSSL EVP library to base64 decode the block
cebf48
cebf48
This isn't fool-proof but it at least does some basic
cebf48
sanity checking to ensure the file(s) exist and appear
cebf48
to be PEM files.
cebf48
cebf48
The unit tests use some Let's Encrypt CA certificates.
cebf48
cebf48
https://bugzilla.redhat.com/show_bug.cgi?id=1492112
cebf48
cebf48
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
cebf48
---
cebf48
 src/Makefile.am                               |   2 +-
cebf48
 src/getcert-add-scep-ca.1.in                  |   2 +
cebf48
 src/getcert.c                                 |  39 ++++-
cebf48
 src/util-o.c                                  | 144 ++++++++++++++++++
cebf48
 src/util-o.h                                  |   7 +
cebf48
 tests/040-pem/bad.empty                       |   0
cebf48
 .../bad.isrg-root-x1-cross-signed.der.b64     |  25 +++
cebf48
 tests/040-pem/expected.out                    |   7 +
cebf48
 .../good.isrg-root-x1-cross-signed.pem        |  31 ++++
cebf48
 .../good.isrg-root-x1-cross-signed_cr.pem     |  31 ++++
cebf48
 tests/040-pem/good.lets_encrypt_chain.pem     |  93 +++++++++++
cebf48
 tests/040-pem/run.sh                          |  21 +++
cebf48
 tests/Makefile.am                             |   8 +-
cebf48
 tests/tools/Makefile.am                       |   3 +-
cebf48
 tests/tools/pem.c                             |  69 +++++++++
cebf48
 15 files changed, 474 insertions(+), 8 deletions(-)
cebf48
 create mode 100644 tests/040-pem/bad.empty
cebf48
 create mode 100644 tests/040-pem/bad.isrg-root-x1-cross-signed.der.b64
cebf48
 create mode 100644 tests/040-pem/expected.out
cebf48
 create mode 100644 tests/040-pem/good.isrg-root-x1-cross-signed.pem
cebf48
 create mode 100644 tests/040-pem/good.isrg-root-x1-cross-signed_cr.pem
cebf48
 create mode 100644 tests/040-pem/good.lets_encrypt_chain.pem
cebf48
 create mode 100755 tests/040-pem/run.sh
cebf48
 create mode 100644 tests/tools/pem.c
cebf48
cebf48
diff --git a/src/Makefile.am b/src/Makefile.am
cebf48
index 53571c5..d8e0a2e 100644
cebf48
--- a/src/Makefile.am
cebf48
+++ b/src/Makefile.am
cebf48
@@ -155,7 +155,7 @@ pkglibexecdir = $(libexecdir)/$(PACKAGE)
cebf48
 getcert_CFLAGS = $(AM_CFLAGS) $(NSS_CFLAGS) $(UUID_CFLAGS)
cebf48
 getcert_SOURCES = getcert.c tm.c tm.h
cebf48
 getcert_LDADD = libcm.a $(GETCERT_LIBS) $(KRB5_LIBS) $(NSS_LIBS) $(UUID_LIBS) \
cebf48
-			$(POPT_LIBS) $(LTLIBICONV) $(LDAP_LIBS)
cebf48
+			$(POPT_LIBS) $(LTLIBICONV) $(LDAP_LIBS) $(OPENSSL_LIBS)
cebf48
 if WITH_IPA
cebf48
 bin_PROGRAMS += ipa-getcert
cebf48
 ipa_getcert_CFLAGS = $(getcert_CFLAGS)
cebf48
diff --git a/src/getcert-add-scep-ca.1.in b/src/getcert-add-scep-ca.1.in
cebf48
index c2751ed..901791e 100644
cebf48
--- a/src/getcert-add-scep-ca.1.in
cebf48
+++ b/src/getcert-add-scep-ca.1.in
cebf48
@@ -14,6 +14,8 @@ helper.  The \fIadd\-scep\-ca\fR command is more or less a wrapper for the
cebf48
 
cebf48
 .SH OPTIONS
cebf48
 .TP
cebf48
+All user\-provided certificate files must be in PEM format.
cebf48
+.TP
cebf48
 \fB\-c\fR \fINAME\fR, \fB\-\-ca\fR=\fINAME\fR
cebf48
 The nickname to give to this CA configuration.  This same value can later be
cebf48
 passed in to \fIgetcert\fR's \fIrequest\fR, \fIresubmit\fR, and
cebf48
diff --git a/src/getcert.c b/src/getcert.c
cebf48
index 4afafcb..ddcb739 100644
cebf48
--- a/src/getcert.c
cebf48
+++ b/src/getcert.c
cebf48
@@ -49,6 +49,7 @@
cebf48
 #include "submit-u.h"
cebf48
 #include "tdbus.h"
cebf48
 #include "tdbusm.h"
cebf48
+#include "util-o.h"
cebf48
 
cebf48
 #ifdef ENABLE_NLS
cebf48
 #include <libintl.h>
cebf48
@@ -4544,15 +4545,16 @@ add_scep_ca(const char *argv0, int argc, const char **argv)
cebf48
 	int c, prefer_non_renewal = 0, verbose = 0;
cebf48
 	dbus_bool_t b;
cebf48
 	static DBusMessage *req, *rep;
cebf48
+	const char *poptarg;
cebf48
 	poptContext pctx;
cebf48
 	struct poptOption popts[] = {
cebf48
 		{"ca", 'c', POPT_ARG_STRING, &caname, 0, _("nickname to give to the new CA configuration"), HELP_TYPE_NAME},
cebf48
 		{"url", 'u', POPT_ARG_STRING, &url, 0, _("location of SCEP server"), HELP_TYPE_URL},
cebf48
 		{"id", 'i', POPT_ARG_STRING, &id, 0, _("CA identifier"), HELP_TYPE_ID},
cebf48
-		{"ca-cert", 'R', POPT_ARG_STRING, &root, 0, _("file containing CA's certificate"), HELP_TYPE_FILENAME},
cebf48
-		{"ra-cert", 'r', POPT_ARG_STRING, &racert, 0, _("file containing RA's certificate"), HELP_TYPE_FILENAME},
cebf48
-		{"other-certs", 'I', POPT_ARG_STRING, &certs, 0, _("file containing certificates in RA's certifying chain"), HELP_TYPE_FILENAME},
cebf48
-		{"signingca", 'N', POPT_ARG_STRING, &signingca, 0, _("the CA certificate which signed the RA certificate"), HELP_TYPE_FILENAME},
cebf48
+		{"ca-cert", 'R', POPT_ARG_STRING, NULL, 'R', _("file containing CA's certificate"), HELP_TYPE_FILENAME},
cebf48
+		{"ra-cert", 'r', POPT_ARG_STRING, NULL, 'r', _("file containing RA's certificate"), HELP_TYPE_FILENAME},
cebf48
+		{"other-certs", 'I', POPT_ARG_STRING, NULL, 'I', _("file containing certificates in RA's certifying chain"), HELP_TYPE_FILENAME},
cebf48
+		{"signingca", 'N', POPT_ARG_STRING, NULL, 'N', _("the CA certificate which signed the RA certificate"), HELP_TYPE_FILENAME},
cebf48
 		{"non-renewal", 'n', POPT_ARG_NONE, &prefer_non_renewal, 0, _("prefer to not use the SCEP Renewal feature"), NULL},
cebf48
 		{"session", 's', POPT_ARG_NONE, NULL, 's', _("connect to the certmonger service on the session bus"), NULL},
cebf48
 		{"system", 'S', POPT_ARG_NONE, NULL, 'S', _("connect to the certmonger service on the system bus"), NULL},
cebf48
@@ -4572,6 +4574,7 @@ add_scep_ca(const char *argv0, int argc, const char **argv)
cebf48
 		return 1;
cebf48
 	}
cebf48
 	while ((c = poptGetNextOpt(pctx)) > 0) {
cebf48
+		poptarg = poptGetOptArg(pctx);
cebf48
 		switch (c) {
cebf48
 		case 's':
cebf48
 			bus = cm_tdbus_session;
cebf48
@@ -4586,6 +4589,34 @@ add_scep_ca(const char *argv0, int argc, const char **argv)
cebf48
 			poptPrintHelp(pctx, stdout, 0);
cebf48
 			return 1;
cebf48
 			break;
cebf48
+		case 'R':
cebf48
+            if (validate_pem(globals.tctx, poptarg) != 0) {
cebf48
+				printf("The root certificate(s) in %s is not valid PEM\n", poptarg);
cebf48
+				return 1;
cebf48
+			}
cebf48
+			root = talloc_strdup(globals.tctx, poptarg);
cebf48
+			break;
cebf48
+		case 'r':
cebf48
+            if (validate_pem(globals.tctx, poptarg) != 0) {
cebf48
+				printf("The RA certificate(s) in %s is not valid PEM\n", poptarg);
cebf48
+				return 1;
cebf48
+			}
cebf48
+			racert = talloc_strdup(globals.tctx, poptarg);
cebf48
+			break;
cebf48
+		case 'I':
cebf48
+            if (validate_pem(globals.tctx, poptarg) != 0) {
cebf48
+				printf("The certificate(s) in %s is not valid PEM\n", poptarg);
cebf48
+				return 1;
cebf48
+			}
cebf48
+			certs = talloc_strdup(globals.tctx, poptarg);
cebf48
+			break;
cebf48
+		case 'N':
cebf48
+            if (validate_pem(globals.tctx, poptarg) != 0) {
cebf48
+				printf("The certificate(s) in %s is not valid PEM\n", poptarg);
cebf48
+				return 1;
cebf48
+			}
cebf48
+			signingca = talloc_strdup(globals.tctx, poptarg);
cebf48
+			break;
cebf48
 		}
cebf48
 	}
cebf48
 	if (c != -1) {
cebf48
diff --git a/src/util-o.c b/src/util-o.c
cebf48
index db45964..c05872c 100644
cebf48
--- a/src/util-o.c
cebf48
+++ b/src/util-o.c
cebf48
@@ -598,3 +598,147 @@ util_private_EVP_PKEY_dup(EVP_PKEY *pkey)
cebf48
 {
cebf48
 	return util_EVP_PKEY_dup(pkey, i2d_PrivateKey, d2i_PrivateKey);
cebf48
 }
cebf48
+
cebf48
+static unsigned char *
cebf48
+decode_base64(const unsigned char *input, int length, int *outlength) {
cebf48
+	int expected_len;
cebf48
+	unsigned char *output;
cebf48
+	int output_len;
cebf48
+
cebf48
+	expected_len = 3 * length / 4;
cebf48
+	output = calloc(expected_len + 1, 1);
cebf48
+	output_len = EVP_DecodeBlock(output, input, length);
cebf48
+
cebf48
+	if (output_len < 0) {
cebf48
+		*outlength = -1;
cebf48
+		free(output);
cebf48
+		return NULL;
cebf48
+	}
cebf48
+	if (output_len % 3 != 0) {
cebf48
+		*outlength = -1;
cebf48
+		free(output);
cebf48
+		return NULL;
cebf48
+	}
cebf48
+	if (expected_len != output_len) {
cebf48
+		*outlength = -1;
cebf48
+		free(output);
cebf48
+		return NULL;
cebf48
+	}
cebf48
+	*outlength = output_len;
cebf48
+	return output;
cebf48
+}
cebf48
+
cebf48
+int
cebf48
+validate_pem(void *parent, const char *path)
cebf48
+{
cebf48
+	char *p;
cebf48
+	char *s = NULL, *sp, *sq;
cebf48
+	int ret = 0;
cebf48
+	FILE *fp;
cebf48
+	struct stat st;
cebf48
+	char *tmp1 = NULL;
cebf48
+	unsigned char *tmp2 = NULL;
cebf48
+	char *buffer;
cebf48
+	int n, i, length;
cebf48
+	int found = 0;
cebf48
+
cebf48
+	fp = fopen(path, "r");
cebf48
+	if (fp == NULL) {
cebf48
+		printf("Unable to open %s for reading: %s\n",
cebf48
+				path, strerror(errno));
cebf48
+		return -1;
cebf48
+	}
cebf48
+	if (fstat(fileno(fp), &st) == -1) {
cebf48
+		printf("Error opening %s for reading: %s\n",
cebf48
+				path, strerror(errno));
cebf48
+		fclose(fp);
cebf48
+		return -1;
cebf48
+	}
cebf48
+	if (st.st_size == 0) {
cebf48
+		printf("%s is an empty file.\n", path);
cebf48
+		fclose(fp);
cebf48
+		return -1;
cebf48
+	}
cebf48
+	
cebf48
+	buffer = malloc(st.st_size + 1);
cebf48
+	if (buffer == NULL) {
cebf48
+		printf("Error allocating memory.\n");
cebf48
+		fclose(fp);
cebf48
+		return -1;
cebf48
+	}
cebf48
+
cebf48
+	n = 0;
cebf48
+	while (n < st.st_size) {
cebf48
+		i = fread(buffer + n, 1, st.st_size - n, fp);
cebf48
+		if (i <= 0) {
cebf48
+			printf("Error reading %s: %s.\n",
cebf48
+				   path, strerror(errno));
cebf48
+			fclose(fp);
cebf48
+			ret = -1;
cebf48
+			goto done;
cebf48
+		}
cebf48
+		n += i;
cebf48
+	}
cebf48
+	fclose(fp);
cebf48
+	buffer[st.st_size] = '\0';
cebf48
+	length = st.st_size;
cebf48
+	s = malloc(length + 1);
cebf48
+	if (s == NULL) {
cebf48
+		printf("Error allocating memory.\n");
cebf48
+		ret = -1;
cebf48
+		goto done;
cebf48
+	}
cebf48
+	memcpy(s, buffer, length);
cebf48
+	s[length] = '\0';
cebf48
+	sp = s;
cebf48
+	tmp1 = NULL;
cebf48
+	tmp2 = NULL;
cebf48
+	while ((sp = strstr(sp, "-----BEGIN")) != NULL) {
cebf48
+		sq = strstr(sp, "-----END");
cebf48
+		if (sq != NULL) {
cebf48
+			found++;
cebf48
+			sq += strcspn(sq, "\r\n");
cebf48
+			sq += strspn(sq, "\r\n");
cebf48
+
cebf48
+			/* Strip down to pure base64 so no headers, new lines or cr */
cebf48
+			tmp1 = strndup(sp, sq - sp);
cebf48
+			p = strstr(tmp1, "-----BEGIN");
cebf48
+			if (p != NULL) {
cebf48
+				p += strcspn(p, "\n");
cebf48
+				if (*p == '\n') {
cebf48
+   				 p++;
cebf48
+				}
cebf48
+				memmove(tmp1, p, strlen(p) + 1);
cebf48
+			}
cebf48
+			p = strstr(tmp1, "\n-----END");
cebf48
+			if (p != NULL) {
cebf48
+				*p = '\0';
cebf48
+			}
cebf48
+			while ((p = strchr(tmp1, '\r')) != NULL) {
cebf48
+				memmove(p, p + 1, strlen(p));
cebf48
+			}
cebf48
+			while ((p = strchr(tmp1, '\n')) != NULL) {
cebf48
+				memmove(p, p + 1, strlen(p));
cebf48
+			}
cebf48
+			length = 0;
cebf48
+			tmp2 = decode_base64((unsigned char *)tmp1, strlen(tmp1), &length);
cebf48
+			if (length < 0) {
cebf48
+				ret = -1;
cebf48
+				goto done;
cebf48
+			}
cebf48
+			sp = sq;
cebf48
+		}
cebf48
+	}
cebf48
+
cebf48
+	if (found == 0) {
cebf48
+		ret = -1;
cebf48
+	}
cebf48
+
cebf48
+done:
cebf48
+	free(buffer);
cebf48
+	free(s);
cebf48
+	free(tmp1);
cebf48
+	free(tmp2);
cebf48
+
cebf48
+	return ret;
cebf48
+}
cebf48
diff --git a/src/util-o.h b/src/util-o.h
cebf48
index 916777b..8550e07 100644
cebf48
--- a/src/util-o.h
cebf48
+++ b/src/util-o.h
cebf48
@@ -16,6 +16,12 @@
cebf48
  */
cebf48
 
cebf48
 #ifndef utilo_h
cebf48
+#include <openssl/err.h>
cebf48
+#include <openssl/evp.h>
cebf48
+#include <openssl/objects.h>
cebf48
+#include <openssl/x509.h>
cebf48
+#include <openssl/x509v3.h>
cebf48
+
cebf48
 #define utilo_h
cebf48
 
cebf48
 struct cm_store_entry;
cebf48
@@ -71,5 +77,6 @@ int util_X509_set1_version(X509 *x, ASN1_INTEGER *version);
cebf48
 void util_NETSCAPE_SPKI_set_sig_alg(NETSCAPE_SPKI *spki, const X509_ALGOR *sig_alg);
cebf48
 EVP_PKEY *util_public_EVP_PKEY_dup(EVP_PKEY *pkey);
cebf48
 EVP_PKEY *util_private_EVP_PKEY_dup(EVP_PKEY *pkey);
cebf48
+int validate_pem(void *parent, const char *path);
cebf48
 
cebf48
 #endif
cebf48
diff --git a/tests/040-pem/bad.empty b/tests/040-pem/bad.empty
cebf48
new file mode 100644
cebf48
index 0000000..e69de29
cebf48
diff --git a/tests/040-pem/bad.isrg-root-x1-cross-signed.der.b64 b/tests/040-pem/bad.isrg-root-x1-cross-signed.der.b64
cebf48
new file mode 100644
cebf48
index 0000000..f9d7e5b
cebf48
--- /dev/null
cebf48
+++ b/tests/040-pem/bad.isrg-root-x1-cross-signed.der.b64
cebf48
@@ -0,0 +1,25 @@
cebf48
+MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/MSQwIgYDVQQK
cebf48
+ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X
cebf48
+DTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1owTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIElu
cebf48
+dGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIi
cebf48
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B4
cebf48
+93XCov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpLwYqGcWlK
cebf48
+ZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+DLtFJV4yAdLbaL9A4jXsD
cebf48
+cCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/
cebf48
+iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeY
cebf48
+jzYIlefiN5YNNnWe+w5ysR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHdu
cebf48
+Rze6zqxZXmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4FQsD
cebf48
+j43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBcSLeCO5imfWCKoqMp
cebf48
+gsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2qlPRmP6zjzZN7IKw0KKP/32+IVQtQi
cebf48
+0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TNDTwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB
cebf48
+/zAOBgNVHQ8BAf8EBAMCAQYwSwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBw
cebf48
+cy5pZGVudHJ1c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
cebf48
++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsG
cebf48
+AQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMDwGA1UdHwQ1MDMwMaAv
cebf48
+oC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9EU1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYE
cebf48
+FHm0WeZ7tuXkAXOACIjIGlj26ZtuMA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oG
cebf48
+rS+o44+/yQoDFVDC5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMr
cebf48
+AdSW9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuGWCLKTVXk
cebf48
+cGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9Ohe8Y4IWS6wY7bCkjCWDc
cebf48
+RQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFCDfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr
cebf48
+6GtPAQw4dy753ec5
cebf48
diff --git a/tests/040-pem/expected.out b/tests/040-pem/expected.out
cebf48
new file mode 100644
cebf48
index 0000000..0459fd2
cebf48
--- /dev/null
cebf48
+++ b/tests/040-pem/expected.out
cebf48
@@ -0,0 +1,7 @@
cebf48
+OK
cebf48
+OK
cebf48
+OK
cebf48
+got expected error with bad.empty
cebf48
+got expected error with bad.isrg-root-x1-cross-signed.der
cebf48
+got expected error with bad.notfound
cebf48
+OK
cebf48
diff --git a/tests/040-pem/good.isrg-root-x1-cross-signed.pem b/tests/040-pem/good.isrg-root-x1-cross-signed.pem
cebf48
new file mode 100644
cebf48
index 0000000..239794a
cebf48
--- /dev/null
cebf48
+++ b/tests/040-pem/good.isrg-root-x1-cross-signed.pem
cebf48
@@ -0,0 +1,31 @@
cebf48
+-----BEGIN CERTIFICATE----- 
cebf48
+MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
cebf48
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
cebf48
+DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
cebf48
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cebf48
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
cebf48
+AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
cebf48
+ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
cebf48
+wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
cebf48
+LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
cebf48
+4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
cebf48
+bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
cebf48
+sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
cebf48
+Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
cebf48
+FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
cebf48
+SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
cebf48
+PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
cebf48
+TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
cebf48
+SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
cebf48
+c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
cebf48
++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
cebf48
+ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
cebf48
+b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
cebf48
+U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
cebf48
+MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
cebf48
+5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
cebf48
+9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
cebf48
+WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
cebf48
+he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
cebf48
+Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
cebf48
+-----END CERTIFICATE-----
cebf48
diff --git a/tests/040-pem/good.isrg-root-x1-cross-signed_cr.pem b/tests/040-pem/good.isrg-root-x1-cross-signed_cr.pem
cebf48
new file mode 100644
cebf48
index 0000000..239794a
cebf48
--- /dev/null
cebf48
+++ b/tests/040-pem/good.isrg-root-x1-cross-signed_cr.pem
cebf48
@@ -0,0 +1,31 @@
cebf48
+-----BEGIN CERTIFICATE----- 
cebf48
+MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
cebf48
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
cebf48
+DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
cebf48
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cebf48
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
cebf48
+AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
cebf48
+ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
cebf48
+wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
cebf48
+LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
cebf48
+4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
cebf48
+bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
cebf48
+sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
cebf48
+Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
cebf48
+FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
cebf48
+SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
cebf48
+PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
cebf48
+TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
cebf48
+SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
cebf48
+c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
cebf48
++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
cebf48
+ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
cebf48
+b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
cebf48
+U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
cebf48
+MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
cebf48
+5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
cebf48
+9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
cebf48
+WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
cebf48
+he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
cebf48
+Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
cebf48
+-----END CERTIFICATE-----
cebf48
diff --git a/tests/040-pem/good.lets_encrypt_chain.pem b/tests/040-pem/good.lets_encrypt_chain.pem
cebf48
new file mode 100644
cebf48
index 0000000..29a16ff
cebf48
--- /dev/null
cebf48
+++ b/tests/040-pem/good.lets_encrypt_chain.pem
cebf48
@@ -0,0 +1,93 @@
cebf48
+-----BEGIN CERTIFICATE-----
cebf48
+MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw
cebf48
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cebf48
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1
cebf48
+WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
cebf48
+RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi
cebf48
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX
cebf48
+NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf
cebf48
+89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl
cebf48
+Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc
cebf48
+Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz
cebf48
+uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB
cebf48
+AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU
cebf48
+BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB
cebf48
+FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo
cebf48
+SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js
cebf48
+LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF
cebf48
+BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG
cebf48
+AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD
cebf48
+VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB
cebf48
+ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx
cebf48
+A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM
cebf48
+UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2
cebf48
+DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1
cebf48
+eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu
cebf48
+OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw
cebf48
+p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY
cebf48
+2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0
cebf48
+ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR
cebf48
+PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b
cebf48
+rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt
cebf48
+-----END CERTIFICATE-----
cebf48
+-----BEGIN CERTIFICATE-----
cebf48
+MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
cebf48
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cebf48
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
cebf48
+WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
cebf48
+RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
cebf48
+AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
cebf48
+R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
cebf48
+sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
cebf48
+NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
cebf48
+Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
cebf48
+/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
cebf48
+AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
cebf48
+Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
cebf48
+FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
cebf48
+AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
cebf48
+Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
cebf48
+gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
cebf48
+PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
cebf48
+ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
cebf48
+CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
cebf48
+lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
cebf48
+avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
cebf48
+yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
cebf48
+yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
cebf48
+hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
cebf48
+HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
cebf48
+MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
cebf48
+nLRbwHOoq7hHwg==
cebf48
+-----END CERTIFICATE-----
cebf48
+-----BEGIN CERTIFICATE-----
cebf48
+MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
cebf48
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
cebf48
+DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
cebf48
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cebf48
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
cebf48
+AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
cebf48
+ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
cebf48
+wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
cebf48
+LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
cebf48
+4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
cebf48
+bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
cebf48
+sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
cebf48
+Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
cebf48
+FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
cebf48
+SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
cebf48
+PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
cebf48
+TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
cebf48
+SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
cebf48
+c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
cebf48
++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
cebf48
+ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
cebf48
+b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
cebf48
+U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
cebf48
+MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
cebf48
+5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
cebf48
+9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
cebf48
+WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
cebf48
+he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
cebf48
+Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
cebf48
+-----END CERTIFICATE-----
cebf48
diff --git a/tests/040-pem/run.sh b/tests/040-pem/run.sh
cebf48
new file mode 100755
cebf48
index 0000000..1d4d1f4
cebf48
--- /dev/null
cebf48
+++ b/tests/040-pem/run.sh
cebf48
@@ -0,0 +1,21 @@
cebf48
+#!/bin/bash -e
cebf48
+
cebf48
+cd "$tmpdir"
cebf48
+cp -p "$srcdir"/040-pem/bad.* $tmpdir
cebf48
+base64 -d < "$tmpdir"/bad.isrg-root-x1-cross-signed.der.b64 > "$tmpdir"/bad.isrg-root-x1-cross-signed.der
cebf48
+rm -f "$tmpdir"/bad.isrg-root-x1-cross-signed.der.b64
cebf48
+
cebf48
+for good in "$srcdir"/040-pem/good.* ; do
cebf48
+	if ! "$toolsdir"/pem "$good" ; then
cebf48
+		exit 1
cebf48
+	fi
cebf48
+done
cebf48
+for bad in "$tmpdir"/bad.* bad.notfound; do
cebf48
+	if "$toolsdir"/pem "$bad" > /dev/null; then
cebf48
+		echo unexpected success with `basename "$bad"`
cebf48
+		exit 1
cebf48
+	else
cebf48
+		echo got expected error with `basename "$bad"`
cebf48
+	fi
cebf48
+done
cebf48
+echo OK
cebf48
diff --git a/tests/Makefile.am b/tests/Makefile.am
cebf48
index 013d34b..e20b6d8 100644
cebf48
--- a/tests/Makefile.am
cebf48
+++ b/tests/Makefile.am
cebf48
@@ -132,6 +132,8 @@ CLEANFILES = \
cebf48
 	038-ms-v2-template/actual.err \
cebf48
 	039-fromfile/actual.out \
cebf48
 	039-fromfile/actual.err
cebf48
+	040-pem/actual.out \
cebf48
+	040-pem/actual.err
cebf48
 EXTRA_DIST = \
cebf48
 	run-tests.sh functions certmonger.conf tools/cachain.sh \
cebf48
 	001-keyiread/run.sh \
cebf48
@@ -353,7 +355,8 @@ EXTRA_DIST = \
cebf48
 	038-ms-v2-template/expected.out \
cebf48
 	038-ms-v2-template/extract-extdata.py \
cebf48
 	038-ms-v2-template/run.sh \
cebf48
-	039-fromfile/run.sh
cebf48
+	039-fromfile/run.sh \
cebf48
+	040-pem/run.sh
cebf48
 
cebf48
 subdirs = \
cebf48
 	001-keyiread \
cebf48
@@ -388,7 +391,8 @@ subdirs = \
cebf48
 	036-getcert \
cebf48
 	037-rekey2 \
cebf48
 	038-ms-v2-template \
cebf48
-	039-fromfile
cebf48
+	039-fromfile \
cebf48
+	040-pem
cebf48
 
cebf48
 if HAVE_DBM_NSSDB
cebf48
 subdirs += \
cebf48
diff --git a/tests/tools/Makefile.am b/tests/tools/Makefile.am
cebf48
index 53f658e..1a01ee6 100644
cebf48
--- a/tests/tools/Makefile.am
cebf48
+++ b/tests/tools/Makefile.am
cebf48
@@ -16,7 +16,8 @@ endif
cebf48
 noinst_PROGRAMS = keyiread keygen csrgen submit certread certsave oid2name \
cebf48
 		  name2oid iterate prefs dates listnicks pem2base base2pem \
cebf48
 		  dparse payload checksig base64 cadata citerate casave hooks \
cebf48
-		  libexecdir canon srv addcinfo ls json json-utf8 printenv fromfile
cebf48
+		  libexecdir canon srv addcinfo ls json json-utf8 printenv fromfile \
cebf48
+		  pem
cebf48
 noinst_LIBRARIES = libtools.a
cebf48
 if HAVE_OPENSSL
cebf48
 noinst_PROGRAMS += pk7parse pk7env scepgen pk7verify pk7decrypt
cebf48
diff --git a/tests/tools/pem.c b/tests/tools/pem.c
cebf48
new file mode 100644
cebf48
index 0000000..4fdd4f4
cebf48
--- /dev/null
cebf48
+++ b/tests/tools/pem.c
cebf48
@@ -0,0 +1,69 @@
cebf48
+/*
cebf48
+ * Copyright (C) 2021 Red Hat, Inc.
cebf48
+ * 
cebf48
+ * This program is free software: you can redistribute it and/or modify
cebf48
+ * it under the terms of the GNU General Public License as published by
cebf48
+ * the Free Software Foundation, either version 3 of the License, or
cebf48
+ * (at your option) any later version.
cebf48
+ *
cebf48
+ * This program is distributed in the hope that it will be useful,
cebf48
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
cebf48
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
cebf48
+ * GNU General Public License for more details.
cebf48
+ *
cebf48
+ * You should have received a copy of the GNU General Public License
cebf48
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
cebf48
+ */
cebf48
+
cebf48
+#include "../../src/config.h"
cebf48
+
cebf48
+#include <sys/types.h>
cebf48
+#include <errno.h>
cebf48
+#include <fcntl.h>
cebf48
+#ifdef HAVE_INTTYPES_H
cebf48
+#include <inttypes.h>
cebf48
+#endif
cebf48
+#include <stdio.h>
cebf48
+#include <stdint.h>
cebf48
+#include <stdlib.h>
cebf48
+#include <string.h>
cebf48
+#include <unistd.h>
cebf48
+
cebf48
+#include <popt.h>
cebf48
+
cebf48
+#include <talloc.h>
cebf48
+
cebf48
+#include "../../src/util-o.h"
cebf48
+
cebf48
+int
cebf48
+main(int argc, const char **argv)
cebf48
+{
cebf48
+	const char *filename;
cebf48
+	void *parent;
cebf48
+	int i, ret = 0;
cebf48
+	poptContext pctx;
cebf48
+	struct poptOption popts[] = {
cebf48
+		POPT_AUTOHELP
cebf48
+		POPT_TABLEEND
cebf48
+	};
cebf48
+
cebf48
+	parent = talloc_new(NULL);
cebf48
+	pctx = poptGetContext("pem", argc, argv, popts, 0);
cebf48
+	while ((i = poptGetNextOpt(pctx)) > 0) {
cebf48
+		continue;
cebf48
+	}
cebf48
+	if (i != -1) {
cebf48
+		poptPrintUsage(pctx, stdout, 0);
cebf48
+		return 1;
cebf48
+	}
cebf48
+	while ((filename = poptGetArg(pctx)) != NULL) {
cebf48
+        if (validate_pem(parent, (char *)filename) == 0) {
cebf48
+			printf("OK\n");
cebf48
+		} else {
cebf48
+			ret = 1;
cebf48
+		}
cebf48
+	}
cebf48
+	talloc_free(parent);
cebf48
+	poptFreeContext(pctx);
cebf48
+	return ret;
cebf48
+}
cebf48
-- 
cebf48
2.31.1
cebf48