summaryrefslogtreecommitdiff
path: root/src/libstrongswan/plugins/pem/pem_builder.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstrongswan/plugins/pem/pem_builder.c')
-rw-r--r--src/libstrongswan/plugins/pem/pem_builder.c566
1 files changed, 566 insertions, 0 deletions
diff --git a/src/libstrongswan/plugins/pem/pem_builder.c b/src/libstrongswan/plugins/pem/pem_builder.c
new file mode 100644
index 000000000..2f285e9bc
--- /dev/null
+++ b/src/libstrongswan/plugins/pem/pem_builder.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2009 Martin Willi
+ * Copyright (C) 2001-2008 Andreas Steffen
+ * Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+#include "pem_builder.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <debug.h>
+#include <library.h>
+#include <utils/lexparser.h>
+#include <asn1/asn1.h>
+#include <crypto/hashers/hasher.h>
+#include <crypto/crypters/crypter.h>
+#include <credentials/certificates/x509.h>
+
+#define PKCS5_SALT_LEN 8 /* bytes */
+
+/**
+ * check the presence of a pattern in a character string, skip if found
+ */
+static bool present(char* pattern, chunk_t* ch)
+{
+ u_int len = strlen(pattern);
+
+ if (ch->len >= len && strneq(ch->ptr, pattern, len))
+ {
+ *ch = chunk_skip(*ch, len);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * find a boundary of the form -----tag name-----
+ */
+static bool find_boundary(char* tag, chunk_t *line)
+{
+ chunk_t name = chunk_empty;
+
+ if (!present("-----", line) ||
+ !present(tag, line) ||
+ *line->ptr != ' ')
+ {
+ return FALSE;
+ }
+ *line = chunk_skip(*line, 1);
+
+ /* extract name */
+ name.ptr = line->ptr;
+ while (line->len > 0)
+ {
+ if (present("-----", line))
+ {
+ DBG2(" -----%s %.*s-----", tag, (int)name.len, name.ptr);
+ return TRUE;
+ }
+ line->ptr++; line->len--; name.len++;
+ }
+ return FALSE;
+}
+
+/*
+ * decrypts a passphrase protected encrypted data block
+ */
+static status_t pem_decrypt(chunk_t *blob, encryption_algorithm_t alg,
+ size_t key_size, chunk_t iv, chunk_t passphrase)
+{
+ hasher_t *hasher;
+ crypter_t *crypter;
+ chunk_t salt = { iv.ptr, PKCS5_SALT_LEN };
+ chunk_t hash;
+ chunk_t decrypted;
+ chunk_t key = {alloca(key_size), key_size};
+ u_int8_t padding, *last_padding_pos, *first_padding_pos;
+
+ /* build key from passphrase and IV */
+ hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
+ if (hasher == NULL)
+ {
+ DBG1(" MD5 hash algorithm not available");
+ return NOT_SUPPORTED;
+ }
+ hash.len = hasher->get_hash_size(hasher);
+ hash.ptr = alloca(hash.len);
+ hasher->get_hash(hasher, passphrase, NULL);
+ hasher->get_hash(hasher, salt, hash.ptr);
+ memcpy(key.ptr, hash.ptr, hash.len);
+
+ if (key.len > hash.len)
+ {
+ hasher->get_hash(hasher, hash, NULL);
+ hasher->get_hash(hasher, passphrase, NULL);
+ hasher->get_hash(hasher, salt, hash.ptr);
+ memcpy(key.ptr + hash.len, hash.ptr, key.len - hash.len);
+ }
+ hasher->destroy(hasher);
+
+ /* decrypt blob */
+ crypter = lib->crypto->create_crypter(lib->crypto, alg, key_size);
+ if (crypter == NULL)
+ {
+ DBG1(" %N encryption algorithm not available",
+ encryption_algorithm_names, alg);
+ return NOT_SUPPORTED;
+ }
+ crypter->set_key(crypter, key);
+
+ if (iv.len != crypter->get_block_size(crypter) ||
+ blob->len % iv.len)
+ {
+ crypter->destroy(crypter);
+ DBG1(" data size is not multiple of block size");
+ return PARSE_ERROR;
+ }
+ crypter->decrypt(crypter, *blob, iv, &decrypted);
+ crypter->destroy(crypter);
+ memcpy(blob->ptr, decrypted.ptr, blob->len);
+ chunk_free(&decrypted);
+
+ /* determine amount of padding */
+ last_padding_pos = blob->ptr + blob->len - 1;
+ padding = *last_padding_pos;
+ if (padding > blob->len)
+ {
+ first_padding_pos = blob->ptr;
+ }
+ else
+ {
+ first_padding_pos = last_padding_pos - padding;
+ }
+ /* check the padding pattern */
+ while (--last_padding_pos > first_padding_pos)
+ {
+ if (*last_padding_pos != padding)
+ {
+ DBG1(" invalid passphrase");
+ return INVALID_ARG;
+ }
+ }
+ /* remove padding */
+ blob->len -= padding;
+ return SUCCESS;
+}
+
+/**
+ * Converts a PEM encoded file into its binary form (RFC 1421, RFC 934)
+ */
+static status_t pem_to_bin(chunk_t *blob, chunk_t(*cb)(void*,int), void *cb_data,
+ bool *pgp)
+{
+ typedef enum {
+ PEM_PRE = 0,
+ PEM_MSG = 1,
+ PEM_HEADER = 2,
+ PEM_BODY = 3,
+ PEM_POST = 4,
+ PEM_ABORT = 5
+ } state_t;
+
+ encryption_algorithm_t alg = ENCR_UNDEFINED;
+ size_t key_size = 0;
+ bool encrypted = FALSE;
+ state_t state = PEM_PRE;
+ chunk_t src = *blob;
+ chunk_t dst = *blob;
+ chunk_t line = chunk_empty;
+ chunk_t iv = chunk_empty;
+ chunk_t passphrase;
+ int try = 0;
+ u_char iv_buf[HASH_SIZE_MD5];
+
+ dst.len = 0;
+ iv.ptr = iv_buf;
+ iv.len = 0;
+
+ while (fetchline(&src, &line))
+ {
+ if (state == PEM_PRE)
+ {
+ if (find_boundary("BEGIN", &line))
+ {
+ state = PEM_MSG;
+ }
+ continue;
+ }
+ else
+ {
+ if (find_boundary("END", &line))
+ {
+ state = PEM_POST;
+ break;
+ }
+ if (state == PEM_MSG)
+ {
+ state = PEM_HEADER;
+ if (memchr(line.ptr, ':', line.len) == NULL)
+ {
+ state = PEM_BODY;
+ }
+ }
+ if (state == PEM_HEADER)
+ {
+ err_t ugh = NULL;
+ chunk_t name = chunk_empty;
+ chunk_t value = chunk_empty;
+
+ /* an empty line separates HEADER and BODY */
+ if (line.len == 0)
+ {
+ state = PEM_BODY;
+ continue;
+ }
+
+ /* we are looking for a parameter: value pair */
+ DBG2(" %.*s", (int)line.len, line.ptr);
+ ugh = extract_parameter_value(&name, &value, &line);
+ if (ugh != NULL)
+ {
+ continue;
+ }
+ if (match("Proc-Type", &name) && *value.ptr == '4')
+ {
+ encrypted = TRUE;
+ }
+ else if (match("DEK-Info", &name))
+ {
+ chunk_t dek;
+
+ if (!extract_token(&dek, ',', &value))
+ {
+ dek = value;
+ }
+ if (match("DES-EDE3-CBC", &dek))
+ {
+ alg = ENCR_3DES;
+ key_size = 24;
+ }
+ else if (match("AES-128-CBC", &dek))
+ {
+ alg = ENCR_AES_CBC;
+ key_size = 16;
+ }
+ else if (match("AES-192-CBC", &dek))
+ {
+ alg = ENCR_AES_CBC;
+ key_size = 24;
+ }
+ else if (match("AES-256-CBC", &dek))
+ {
+ alg = ENCR_AES_CBC;
+ key_size = 32;
+ }
+ else
+ {
+ DBG1(" encryption algorithm '%.*s' not supported",
+ dek.len, dek.ptr);
+ return NOT_SUPPORTED;
+ }
+ eat_whitespace(&value);
+ iv = chunk_from_hex(value, iv.ptr);
+ }
+ }
+ else /* state is PEM_BODY */
+ {
+ chunk_t data;
+
+ /* remove any trailing whitespace */
+ if (!extract_token(&data ,' ', &line))
+ {
+ data = line;
+ }
+
+ /* check for PGP armor checksum */
+ if (*data.ptr == '=')
+ {
+ *pgp = TRUE;
+ data.ptr++;
+ data.len--;
+ DBG2(" armor checksum: %.*s", (int)data.len, data.ptr);
+ continue;
+ }
+
+ if (blob->len - dst.len < data.len / 4 * 3)
+ {
+ state = PEM_ABORT;
+ }
+ data = chunk_from_base64(data, dst.ptr);
+
+ dst.ptr += data.len;
+ dst.len += data.len;
+ }
+ }
+ }
+ /* set length to size of binary blob */
+ blob->len = dst.len;
+
+ if (state != PEM_POST)
+ {
+ DBG1(" file coded in unknown format, discarded");
+ return PARSE_ERROR;
+ }
+ if (!encrypted)
+ {
+ return SUCCESS;
+ }
+ if (!cb)
+ {
+ DBG1(" missing passphrase");
+ return INVALID_ARG;
+ }
+ while (TRUE)
+ {
+ passphrase = cb(cb_data, ++try);
+ if (!passphrase.len || !passphrase.ptr)
+ {
+ return INVALID_ARG;
+ }
+ switch (pem_decrypt(blob, alg, key_size, iv, passphrase))
+ {
+ case INVALID_ARG:
+ /* bad passphrase, retry */
+ continue;
+ case SUCCESS:
+ return SUCCESS;
+ default:
+ return FAILED;
+ }
+ }
+}
+
+/**
+ * load the credential from a blob
+ */
+static void *load_from_blob(chunk_t blob, credential_type_t type, int subtype,
+ chunk_t(*cb)(void*,int), void *cb_data,
+ x509_flag_t flags)
+{
+ void *cred = NULL;
+ bool pgp = FALSE;
+
+ blob = chunk_clone(blob);
+ if (!is_asn1(blob))
+ {
+ if (pem_to_bin(&blob, cb, cb_data, &pgp) != SUCCESS)
+ {
+ chunk_clear(&blob);
+ return NULL;
+ }
+ if (pgp && type == CRED_PRIVATE_KEY)
+ {
+ /* PGP encoded keys are parsed with a KEY_ANY key type, as it
+ * can contain any type of key. However, ipsec.secrets uses
+ * RSA for PGP keys, which is actually wrong. */
+ subtype = KEY_ANY;
+ }
+ }
+ /* if CERT_ANY is given, ASN1 encoded blob is handled as X509 */
+ if (type == CRED_CERTIFICATE && subtype == CERT_ANY)
+ {
+ subtype = pgp ? CERT_GPG : CERT_X509;
+ }
+ cred = lib->creds->create(lib->creds, type, subtype,
+ pgp ? BUILD_BLOB_PGP : BUILD_BLOB_ASN1_DER, blob,
+ flags ? BUILD_X509_FLAG : BUILD_END,
+ flags, BUILD_END);
+ chunk_clear(&blob);
+ return cred;
+}
+
+/**
+ * load the credential from a file
+ */
+static void *load_from_file(char *file, credential_type_t type, int subtype,
+ chunk_t(*cb)(void*,int), void *cb_data,
+ x509_flag_t flags)
+{
+ void *cred = NULL;
+ struct stat sb;
+ void *addr;
+ int fd;
+
+ fd = open(file, O_RDONLY);
+ if (fd == -1)
+ {
+ DBG1(" opening '%s' failed: %s", file, strerror(errno));
+ return NULL;
+ }
+
+ if (fstat(fd, &sb) == -1)
+ {
+ DBG1(" getting file size of '%s' failed: %s", file, strerror(errno));
+ close(fd);
+ return NULL;
+ }
+
+ addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (addr == MAP_FAILED)
+ {
+ DBG1(" mapping '%s' failed: %s", file, strerror(errno));
+ close(fd);
+ return NULL;
+ }
+
+ cred = load_from_blob(chunk_create(addr, sb.st_size), type, subtype,
+ cb, cb_data, flags);
+
+ munmap(addr, sb.st_size);
+ close(fd);
+ return cred;
+}
+
+/**
+ * load the credential from a file descriptor
+ */
+static void *load_from_fd(int fd, credential_type_t type, int subtype,
+ chunk_t(*cb)(void*,int), void *cb_data,
+ x509_flag_t flags)
+{
+ char buf[8096];
+ char *pos = buf;
+ ssize_t len, total = 0;
+
+ while (TRUE)
+ {
+ len = read(fd, pos, buf + sizeof(buf) - pos);
+ if (len < 0)
+ {
+ DBG1("reading from file descriptor failed: %s", strerror(errno));
+ return NULL;
+ }
+ if (len == 0)
+ {
+ break;
+ }
+ total += len;
+ if (total == sizeof(buf))
+ {
+ DBG1("buffer too small to read from file descriptor");
+ return NULL;
+ }
+ }
+ return load_from_blob(chunk_create(buf, total), type, subtype,
+ cb, cb_data, flags);
+}
+
+/**
+ * passphrase callback to use if passphrase given
+ */
+static chunk_t given_passphrase_cb(chunk_t *passphrase, int try)
+{
+ if (try > 1)
+ { /* try only once for given passphrases */
+ return chunk_empty;
+ }
+ return *passphrase;
+}
+
+/**
+ * Load all kind of PEM encoded credentials.
+ */
+static void *pem_load(credential_type_t type, int subtype, va_list args)
+{
+ char *file = NULL;
+ int fd = -1;
+ chunk_t pem = chunk_empty, passphrase = chunk_empty;
+ chunk_t (*cb)(void *data, int try) = NULL;
+ void *cb_data = NULL;
+ int flags = 0;
+
+ while (TRUE)
+ {
+ switch (va_arg(args, builder_part_t))
+ {
+ case BUILD_FROM_FILE:
+ file = va_arg(args, char*);
+ continue;
+ case BUILD_FROM_FD:
+ fd = va_arg(args, int);
+ continue;
+ case BUILD_BLOB_PEM:
+ pem = va_arg(args, chunk_t);
+ continue;
+ case BUILD_PASSPHRASE:
+ passphrase = va_arg(args, chunk_t);
+ if (passphrase.len && passphrase.ptr)
+ {
+ cb = (void*)given_passphrase_cb;
+ cb_data = &passphrase;
+ }
+ continue;
+ case BUILD_PASSPHRASE_CALLBACK:
+ cb = va_arg(args, chunk_t(*)(void*,int));
+ cb_data = va_arg(args, void*);
+ continue;
+ case BUILD_X509_FLAG:
+ flags = va_arg(args, int);
+ continue;
+ case BUILD_END:
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ }
+
+ if (pem.ptr)
+ {
+ return load_from_blob(pem, type, subtype, cb, cb_data, flags);
+ }
+ if (file)
+ {
+ return load_from_file(file, type, subtype, cb, cb_data, flags);
+ }
+ if (fd != -1)
+ {
+ return load_from_fd(fd, type, subtype, cb, cb_data, flags);
+ }
+ return NULL;
+}
+
+/**
+ * Private key PEM loader.
+ */
+private_key_t *pem_private_key_load(key_type_t type, va_list args)
+{
+ return pem_load(CRED_PRIVATE_KEY, type, args);
+}
+
+/**
+ * Public key PEM loader.
+ */
+public_key_t *pem_public_key_load(key_type_t type, va_list args)
+{
+ return pem_load(CRED_PUBLIC_KEY, type, args);
+}
+
+/**
+ * Certificate PEM loader.
+ */
+certificate_t *pem_certificate_load(certificate_type_t type, va_list args)
+{
+ return pem_load(CRED_CERTIFICATE, type, args);
+}
+