diff options
Diffstat (limited to 'src/libstrongswan/plugins/pem/pem_builder.c')
-rw-r--r-- | src/libstrongswan/plugins/pem/pem_builder.c | 566 |
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); +} + |