aboutsummaryrefslogtreecommitdiffstats
path: root/src/libstrongswan/crypto/pkcs5.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstrongswan/crypto/pkcs5.c')
-rw-r--r--src/libstrongswan/crypto/pkcs5.c630
1 files changed, 630 insertions, 0 deletions
diff --git a/src/libstrongswan/crypto/pkcs5.c b/src/libstrongswan/crypto/pkcs5.c
new file mode 100644
index 000000000..76a67fc4a
--- /dev/null
+++ b/src/libstrongswan/crypto/pkcs5.c
@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2012-2013 Tobias Brunner
+ * 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 "pkcs5.h"
+
+#include <utils/debug.h>
+#include <asn1/oid.h>
+#include <asn1/asn1.h>
+#include <asn1/asn1_parser.h>
+
+typedef struct private_pkcs5_t private_pkcs5_t;
+
+/**
+ * Private data of a pkcs5_t object
+ */
+struct private_pkcs5_t {
+
+ /**
+ * Implements pkcs5_t.
+ */
+ pkcs5_t public;
+
+ /**
+ * Salt used during encryption
+ */
+ chunk_t salt;
+
+ /**
+ * Iterations for key derivation
+ */
+ u_int64_t iterations;
+
+ /**
+ * Encryption algorithm
+ */
+ encryption_algorithm_t encr;
+
+ /**
+ * Encryption key length
+ */
+ size_t keylen;
+
+ /**
+ * Crypter
+ */
+ crypter_t *crypter;
+
+
+ /**
+ * The encryption scheme
+ */
+ enum {
+ PKCS5_SCHEME_PBES1,
+ PKCS5_SCHEME_PBES2,
+ } scheme;
+
+ /**
+ * Data used for individual schemes
+ */
+ union {
+ struct {
+ /**
+ * Hash algorithm
+ */
+ hash_algorithm_t hash;
+
+ /**
+ * Hasher
+ */
+ hasher_t *hasher;
+
+ } pbes1;
+ struct {
+ /**
+ * PRF algorithm
+ */
+ pseudo_random_function_t prf_alg;
+
+ /**
+ * PRF
+ */
+ prf_t * prf;
+
+ /**
+ * IV
+ */
+ chunk_t iv;
+
+ } pbes2;
+ } data;
+};
+
+/**
+ * Verify padding of decrypted blob.
+ * Length of blob is adjusted accordingly.
+ */
+static bool verify_padding(chunk_t *blob)
+{
+ u_int8_t padding, count;
+
+ padding = count = blob->ptr[blob->len - 1];
+
+ if (padding > 8)
+ {
+ return FALSE;
+ }
+ for (; blob->len && count; --blob->len, --count)
+ {
+ if (blob->ptr[blob->len - 1] != padding)
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Prototype for key derivation functions.
+ */
+typedef bool (*kdf_t)(private_pkcs5_t *this, chunk_t password, chunk_t key);
+
+/**
+ * Try to decrypt the given data with the given password using the given
+ * key derivation function. keymat is where the kdf function writes the key
+ * to, key and iv point to the actual keys and initialization vectors resp.
+ */
+static bool decrypt_generic(private_pkcs5_t *this, chunk_t password,
+ chunk_t data, chunk_t *decrypted, kdf_t kdf,
+ chunk_t keymat, chunk_t key, chunk_t iv)
+{
+ if (!kdf(this, password, keymat))
+ {
+ return FALSE;
+ }
+ if (!this->crypter->set_key(this->crypter, key) ||
+ !this->crypter->decrypt(this->crypter, data, iv, decrypted))
+ {
+ memwipe(keymat.ptr, keymat.len);
+ return FALSE;
+ }
+ memwipe(keymat.ptr, keymat.len);
+ if (verify_padding(decrypted))
+ {
+ return TRUE;
+ }
+ chunk_free(decrypted);
+ return FALSE;
+}
+
+/**
+ * Function F of PBKDF2
+ */
+static bool pbkdf2_f(chunk_t block, prf_t *prf, chunk_t seed,
+ u_int64_t iterations)
+{
+ chunk_t u;
+ u_int64_t i;
+
+ u = chunk_alloca(prf->get_block_size(prf));
+ if (!prf->get_bytes(prf, seed, u.ptr))
+ {
+ return FALSE;
+ }
+ memcpy(block.ptr, u.ptr, block.len);
+
+ for (i = 1; i < iterations; i++)
+ {
+ if (!prf->get_bytes(prf, u, u.ptr))
+ {
+ return FALSE;
+ }
+ memxor(block.ptr, u.ptr, block.len);
+ }
+ return TRUE;
+}
+
+/**
+ * PBKDF2 key derivation function for PBES2, key must be allocated
+ */
+static bool pbkdf2(private_pkcs5_t *this, chunk_t password, chunk_t key)
+{
+ prf_t *prf;
+ chunk_t keymat, block, seed;
+ size_t blocks;
+ u_int32_t i = 0;
+
+ prf = this->data.pbes2.prf;
+
+ if (!prf->set_key(prf, password))
+ {
+ return FALSE;
+ }
+
+ block.len = prf->get_block_size(prf);
+ blocks = (key.len - 1) / block.len + 1;
+ keymat = chunk_alloca(blocks * block.len);
+
+ seed = chunk_cata("cc", this->salt, chunk_from_thing(i));
+
+ for (; i < blocks; i++)
+ {
+ htoun32(seed.ptr + this->salt.len, i + 1);
+ block.ptr = keymat.ptr + (i * block.len);
+ if (!pbkdf2_f(block, prf, seed, this->iterations))
+ {
+ return FALSE;
+ }
+ }
+ memcpy(key.ptr, keymat.ptr, key.len);
+ return TRUE;
+}
+
+/**
+ * PBKDF1 key derivation function for PBES1, key must be allocated
+ */
+static bool pbkdf1(private_pkcs5_t *this, chunk_t password, chunk_t key)
+{
+ hasher_t *hasher;
+ chunk_t hash;
+ u_int64_t i;
+
+ hasher = this->data.pbes1.hasher;
+
+ hash = chunk_alloca(hasher->get_hash_size(hasher));
+ if (!hasher->get_hash(hasher, password, NULL) ||
+ !hasher->get_hash(hasher, this->salt, hash.ptr))
+ {
+ return FALSE;
+ }
+
+ for (i = 1; i < this->iterations; i++)
+ {
+ if (!hasher->get_hash(hasher, hash, hash.ptr))
+ {
+ return FALSE;
+ }
+ }
+ memcpy(key.ptr, hash.ptr, key.len);
+ return TRUE;
+}
+
+static bool ensure_crypto_primitives(private_pkcs5_t *this, chunk_t data)
+{
+ if (!this->crypter)
+ {
+ this->crypter = lib->crypto->create_crypter(lib->crypto, this->encr,
+ this->keylen);
+ if (!this->crypter)
+ {
+ DBG1(DBG_ASN, " %N encryption algorithm not available",
+ encryption_algorithm_names, this->encr);
+ return FALSE;
+ }
+ }
+ if (data.len % this->crypter->get_block_size(this->crypter))
+ {
+ DBG1(DBG_ASN, " data size is not a multiple of block size");
+ return FALSE;
+ }
+ switch (this->scheme)
+ {
+ case PKCS5_SCHEME_PBES1:
+ {
+ if (!this->data.pbes1.hasher)
+ {
+ hasher_t *hasher;
+
+ hasher = lib->crypto->create_hasher(lib->crypto,
+ this->data.pbes1.hash);
+ if (!hasher)
+ {
+ DBG1(DBG_ASN, " %N hash algorithm not available",
+ hash_algorithm_names, this->data.pbes1.hash);
+ return FALSE;
+ }
+ if (hasher->get_hash_size(hasher) < this->keylen)
+ {
+ hasher->destroy(hasher);
+ return FALSE;
+ }
+ this->data.pbes1.hasher = hasher;
+ }
+ }
+ case PKCS5_SCHEME_PBES2:
+ {
+ if (!this->data.pbes2.prf)
+ {
+ prf_t *prf;
+
+ prf = lib->crypto->create_prf(lib->crypto,
+ this->data.pbes2.prf_alg);
+ if (!prf)
+ {
+ DBG1(DBG_ASN, " %N prf algorithm not available",
+ pseudo_random_function_names,
+ this->data.pbes2.prf_alg);
+ return FALSE;
+ }
+ this->data.pbes2.prf = prf;
+ }
+ }
+ }
+ return TRUE;
+}
+
+METHOD(pkcs5_t, decrypt, bool,
+ private_pkcs5_t *this, chunk_t password, chunk_t data, chunk_t *decrypted)
+{
+ chunk_t keymat, key, iv;
+ kdf_t kdf;
+
+ if (!ensure_crypto_primitives(this, data) || !decrypted)
+ {
+ return FALSE;
+ }
+ switch (this->scheme)
+ {
+ case PKCS5_SCHEME_PBES1:
+ kdf = pbkdf1;
+ keymat = chunk_alloca(this->keylen * 2);
+ key = chunk_create(keymat.ptr, this->keylen);
+ iv = chunk_create(keymat.ptr + this->keylen, this->keylen);
+ break;
+ case PKCS5_SCHEME_PBES2:
+ kdf = pbkdf2;
+ keymat = chunk_alloca(this->keylen);
+ key = keymat;
+ iv = this->data.pbes2.iv;
+ break;
+ default:
+ return FALSE;
+ }
+ return decrypt_generic(this, password, data, decrypted, kdf,
+ keymat, key, iv);
+}
+
+/**
+ * Converts an ASN.1 INTEGER object to an u_int64_t. If the INTEGER is longer
+ * than 8 bytes only the 8 LSBs are returned.
+ *
+ * @param blob body of an ASN.1 coded integer object
+ * @return converted integer
+ */
+static u_int64_t parse_asn1_integer_uint64(chunk_t blob)
+{
+ u_int64_t val = 0;
+ int i;
+
+ for (i = 0; i < blob.len; i++)
+ { /* if it is longer than 8 bytes, we just use the 8 LSBs */
+ val <<= 8;
+ val |= (u_int64_t)blob.ptr[i];
+ }
+ return val;
+}
+
+/**
+ * ASN.1 definition of a PBEParameter structure
+ */
+static const asn1Object_t pbeParameterObjects[] = {
+ { 0, "PBEParameter", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */
+ { 1, "salt", ASN1_OCTET_STRING, ASN1_BODY }, /* 1 */
+ { 1, "iterationCount", ASN1_INTEGER, ASN1_BODY }, /* 2 */
+ { 0, "exit", ASN1_EOC, ASN1_EXIT }
+};
+#define PBEPARAM_SALT 1
+#define PBEPARAM_ITERATION_COUNT 2
+
+/**
+ * Parse a PBEParameter structure
+ */
+static bool parse_pbes1_params(private_pkcs5_t *this, chunk_t blob, int level0)
+{
+ asn1_parser_t *parser;
+ chunk_t object;
+ int objectID;
+ bool success;
+
+ parser = asn1_parser_create(pbeParameterObjects, blob);
+ parser->set_top_level(parser, level0);
+
+ while (parser->iterate(parser, &objectID, &object))
+ {
+ switch (objectID)
+ {
+ case PBEPARAM_SALT:
+ {
+ this->salt = chunk_clone(object);
+ break;
+ }
+ case PBEPARAM_ITERATION_COUNT:
+ {
+ this->iterations = parse_asn1_integer_uint64(object);
+ break;
+ }
+ }
+ }
+ success = parser->success(parser);
+ parser->destroy(parser);
+ return success;
+}
+
+/**
+ * ASN.1 definition of a PBKDF2-params structure
+ * The salt is actually a CHOICE and could be an AlgorithmIdentifier from
+ * PBKDF2-SaltSources (but as per RFC 2898 that's for future versions).
+ */
+static const asn1Object_t pbkdf2ParamsObjects[] = {
+ { 0, "PBKDF2-params", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */
+ { 1, "salt", ASN1_OCTET_STRING, ASN1_BODY }, /* 1 */
+ { 1, "iterationCount",ASN1_INTEGER, ASN1_BODY }, /* 2 */
+ { 1, "keyLength", ASN1_INTEGER, ASN1_OPT|ASN1_BODY }, /* 3 */
+ { 1, "end opt", ASN1_EOC, ASN1_END }, /* 4 */
+ { 1, "prf", ASN1_EOC, ASN1_DEF|ASN1_RAW }, /* 5 */
+ { 0, "exit", ASN1_EOC, ASN1_EXIT }
+};
+#define PBKDF2_SALT 1
+#define PBKDF2_ITERATION_COUNT 2
+#define PBKDF2_KEYLENGTH 3
+#define PBKDF2_PRF 5
+
+/**
+ * Parse a PBKDF2-params structure
+ */
+static bool parse_pbkdf2_params(private_pkcs5_t *this, chunk_t blob, int level0)
+{
+ asn1_parser_t *parser;
+ chunk_t object;
+ int objectID;
+ bool success;
+
+ parser = asn1_parser_create(pbkdf2ParamsObjects, blob);
+ parser->set_top_level(parser, level0);
+
+ /* keylen is optional */
+ this->keylen = 0;
+
+ while (parser->iterate(parser, &objectID, &object))
+ {
+ switch (objectID)
+ {
+ case PBKDF2_SALT:
+ {
+ this->salt = chunk_clone(object);
+ break;
+ }
+ case PBKDF2_ITERATION_COUNT:
+ {
+ this->iterations = parse_asn1_integer_uint64(object);
+ break;
+ }
+ case PBKDF2_KEYLENGTH:
+ {
+ this->keylen = (size_t)parse_asn1_integer_uint64(object);
+ break;
+ }
+ case PBKDF2_PRF:
+ { /* defaults to id-hmacWithSHA1, no other is currently defined */
+ this->data.pbes2.prf_alg = PRF_HMAC_SHA1;
+ break;
+ }
+ }
+ }
+ success = parser->success(parser);
+ parser->destroy(parser);
+ return success;
+}
+
+/**
+ * ASN.1 definition of a PBES2-params structure
+ */
+static const asn1Object_t pbes2ParamsObjects[] = {
+ { 0, "PBES2-params", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */
+ { 1, "keyDerivationFunc", ASN1_EOC, ASN1_RAW }, /* 1 */
+ { 1, "encryptionScheme", ASN1_EOC, ASN1_RAW }, /* 2 */
+ { 0, "exit", ASN1_EOC, ASN1_EXIT }
+};
+#define PBES2PARAMS_KEY_DERIVATION_FUNC 1
+#define PBES2PARAMS_ENCRYPTION_SCHEME 2
+
+/**
+ * Parse a PBES2-params structure
+ */
+static bool parse_pbes2_params(private_pkcs5_t *this, chunk_t blob, int level0)
+{
+ asn1_parser_t *parser;
+ chunk_t object, params;
+ int objectID;
+ bool success = FALSE;
+
+ parser = asn1_parser_create(pbes2ParamsObjects, blob);
+ parser->set_top_level(parser, level0);
+
+ while (parser->iterate(parser, &objectID, &object))
+ {
+ switch (objectID)
+ {
+ case PBES2PARAMS_KEY_DERIVATION_FUNC:
+ {
+ int oid = asn1_parse_algorithmIdentifier(object,
+ parser->get_level(parser) + 1, &params);
+ if (oid != OID_PBKDF2)
+ { /* unsupported key derivation function */
+ goto end;
+ }
+ if (!parse_pbkdf2_params(this, params,
+ parser->get_level(parser) + 1))
+ {
+ goto end;
+ }
+ break;
+ }
+ case PBES2PARAMS_ENCRYPTION_SCHEME:
+ {
+ int oid = asn1_parse_algorithmIdentifier(object,
+ parser->get_level(parser) + 1, &params);
+ if (oid != OID_3DES_EDE_CBC)
+ { /* unsupported encryption scheme */
+ goto end;
+ }
+ if (this->keylen <= 0)
+ { /* default key length for DES-EDE3-CBC-Pad */
+ this->keylen = 24;
+ }
+ if (!asn1_parse_simple_object(&params, ASN1_OCTET_STRING,
+ parser->get_level(parser) + 1, "IV"))
+ {
+ goto end;
+ }
+ this->encr = ENCR_3DES;
+ this->data.pbes2.iv = chunk_clone(params);
+ break;
+ }
+ }
+ }
+ success = parser->success(parser);
+end:
+ parser->destroy(parser);
+ return success;
+}
+
+METHOD(pkcs5_t, destroy, void,
+ private_pkcs5_t *this)
+{
+ DESTROY_IF(this->crypter);
+ chunk_free(&this->salt);
+ switch (this->scheme)
+ {
+ case PKCS5_SCHEME_PBES1:
+ DESTROY_IF(this->data.pbes1.hasher);
+ break;
+ case PKCS5_SCHEME_PBES2:
+ DESTROY_IF(this->data.pbes2.prf);
+ chunk_free(&this->data.pbes2.iv);
+ break;
+ }
+ free(this);
+}
+
+/*
+ * Described in header
+ */
+pkcs5_t *pkcs5_from_algorithmIdentifier(chunk_t blob, int level0)
+{
+ private_pkcs5_t *this;
+ chunk_t params;
+ int oid;
+
+ INIT(this,
+ .public = {
+ .decrypt = _decrypt,
+ .destroy = _destroy,
+ },
+ .scheme = PKCS5_SCHEME_PBES1,
+ .keylen = 8,
+ );
+
+ oid = asn1_parse_algorithmIdentifier(blob, level0, &params);
+
+ switch (oid)
+ {
+ case OID_PBE_MD5_DES_CBC:
+ this->encr = ENCR_DES;
+ this->data.pbes1.hash = HASH_MD5;
+ break;
+ case OID_PBE_SHA1_DES_CBC:
+ this->encr = ENCR_DES;
+ this->data.pbes1.hash = HASH_SHA1;
+ break;
+ case OID_PBES2:
+ this->scheme = PKCS5_SCHEME_PBES2;
+ break;
+ default:
+ /* encryption scheme not supported */
+ goto failure;
+ }
+
+ switch (this->scheme)
+ {
+ case PKCS5_SCHEME_PBES1:
+ if (!parse_pbes1_params(this, params, level0))
+ {
+ goto failure;
+ }
+ break;
+ case PKCS5_SCHEME_PBES2:
+ if (!parse_pbes2_params(this, params, level0))
+ {
+ goto failure;
+ }
+ break;
+ }
+ return &this->public;
+
+failure:
+ destroy(this);
+ return NULL;
+}