strongswan/src/libstrongswan/plugins/pkcs12/pkcs12_decode.c

582 lines
13 KiB
C

/*
* Copyright (C) 2013 Tobias Brunner
* HSR 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 "pkcs12_decode.h"
#include <utils/debug.h>
#include <asn1/oid.h>
#include <asn1/asn1.h>
#include <asn1/asn1_parser.h>
#include <credentials/sets/mem_cred.h>
typedef struct private_pkcs12_t private_pkcs12_t;
/**
* Private data of a pkcs12_t object
*/
struct private_pkcs12_t {
/**
* Public interface
*/
pkcs12_t public;
/**
* Contained credentials
*/
mem_cred_t *creds;
};
METHOD(container_t, get_type, container_type_t,
private_pkcs12_t *this)
{
return CONTAINER_PKCS12;
}
METHOD(container_t, get_data, bool,
private_pkcs12_t *this, chunk_t *data)
{
/* we could return the content of the outer-most PKCS#7 container (authSafe)
* don't really see the point though */
return FALSE;
}
METHOD(container_t, get_encoding, bool,
private_pkcs12_t *this, chunk_t *encoding)
{
/* similar to get_data() we don't have any use for it at the moment */
return FALSE;
}
METHOD(pkcs12_t, create_cert_enumerator, enumerator_t*,
private_pkcs12_t *this)
{
return this->creds->set.create_cert_enumerator(&this->creds->set, CERT_ANY,
KEY_ANY, NULL, FALSE);
}
METHOD(pkcs12_t, create_key_enumerator, enumerator_t*,
private_pkcs12_t *this)
{
return this->creds->set.create_private_enumerator(&this->creds->set,
KEY_ANY, NULL);
}
METHOD(container_t, destroy, void,
private_pkcs12_t *this)
{
this->creds->destroy(this->creds);
free(this);
}
static private_pkcs12_t *pkcs12_create()
{
private_pkcs12_t *this;
INIT(this,
.public = {
.container = {
.get_type = _get_type,
.create_signature_enumerator = (void*)enumerator_create_empty,
.get_data = _get_data,
.get_encoding = _get_encoding,
.destroy = _destroy,
},
.create_cert_enumerator = _create_cert_enumerator,
.create_key_enumerator = _create_key_enumerator,
},
.creds = mem_cred_create(),
);
return this;
}
/**
* ASN.1 definition of an CertBag structure
*/
static const asn1Object_t certBagObjects[] = {
{ 0, "CertBag", ASN1_SEQUENCE, ASN1_BODY }, /* 0 */
{ 1, "certId", ASN1_OID, ASN1_BODY }, /* 1 */
{ 1, "certValue", ASN1_CONTEXT_C_0, ASN1_BODY }, /* 2 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#define CERT_BAG_ID 1
#define CERT_BAG_VALUE 2
/**
* Parse a CertBag structure and extract certificate
*/
static bool add_certificate(private_pkcs12_t *this, int level0, chunk_t blob)
{
asn1_parser_t *parser;
chunk_t object;
int objectID;
int oid = OID_UNKNOWN;
bool success = FALSE;
parser = asn1_parser_create(certBagObjects, blob);
parser->set_top_level(parser, level0);
while (parser->iterate(parser, &objectID, &object))
{
switch (objectID)
{
case CERT_BAG_ID:
oid = asn1_known_oid(object);
break;
case CERT_BAG_VALUE:
{
if (oid == OID_X509_CERTIFICATE &&
asn1_parse_simple_object(&object, ASN1_OCTET_STRING,
parser->get_level(parser)+1, "x509Certificate"))
{
certificate_t *cert;
DBG2(DBG_ASN, "-- > parsing certificate from PKCS#12");
cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_BLOB_ASN1_DER, object,
BUILD_END);
if (cert)
{
this->creds->add_cert(this->creds, FALSE, cert);
DBG2(DBG_ASN, "-- < --");
}
else
{
DBG2(DBG_ASN, "-- < failed parsing certificate from "
"PKCS#12");
}
}
break;
}
}
}
success = parser->success(parser);
parser->destroy(parser);
return success;
}
/**
* ASN.1 definition of an AuthenticatedSafe structure
*/
static const asn1Object_t safeContentsObjects[] = {
{ 0, "SafeContents", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */
{ 1, "SafeBag", ASN1_SEQUENCE, ASN1_BODY }, /* 1 */
{ 2, "bagId", ASN1_OID, ASN1_BODY }, /* 2 */
{ 2, "bagValue", ASN1_CONTEXT_C_0, ASN1_BODY }, /* 3 */
{ 2, "bagAttr", ASN1_SET, ASN1_OPT|ASN1_RAW }, /* 4 */
{ 2, "end opt", ASN1_EOC, ASN1_END }, /* 5 */
{ 0, "end loop", ASN1_EOC, ASN1_END }, /* 6 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#define SAFE_BAG_ID 2
#define SAFE_BAG_VALUE 3
/**
* Parse a SafeContents structure and extract credentials
*/
static bool parse_safe_contents(private_pkcs12_t *this, int level0,
chunk_t blob)
{
asn1_parser_t *parser;
chunk_t object;
int objectID;
int oid = OID_UNKNOWN;
bool success = FALSE;
parser = asn1_parser_create(safeContentsObjects, blob);
parser->set_top_level(parser, level0);
while (parser->iterate(parser, &objectID, &object))
{
switch (objectID)
{
case SAFE_BAG_ID:
oid = asn1_known_oid(object);
break;
case SAFE_BAG_VALUE:
{
switch (oid)
{
case OID_P12_CERT_BAG:
{
add_certificate(this, parser->get_level(parser)+1,
object);
break;
}
case OID_P12_KEY_BAG:
case OID_P12_PKCS8_KEY_BAG:
{
private_key_t *key;
DBG2(DBG_ASN, "-- > parsing private key from PKCS#12");
key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY,
KEY_ANY, BUILD_BLOB_ASN1_DER, object,
BUILD_END);
if (key)
{
this->creds->add_key(this->creds, key);
DBG2(DBG_ASN, "-- < --");
}
else
{
DBG2(DBG_ASN, "-- < failed parsing private key "
"from PKCS#12");
}
}
default:
break;
}
break;
}
}
}
success = parser->success(parser);
parser->destroy(parser);
return success;
}
/**
* ASN.1 definition of an AuthenticatedSafe structure
*/
static const asn1Object_t authenticatedSafeObjects[] = {
{ 0, "AuthenticatedSafe", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */
{ 1, "ContentInfo", ASN1_SEQUENCE, ASN1_OBJ }, /* 1 */
{ 0, "end loop", ASN1_EOC, ASN1_END }, /* 2 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#define AUTHENTICATED_SAFE_DATA 1
/**
* Parse an AuthenticatedSafe structure
*/
static bool parse_authenticated_safe(private_pkcs12_t *this, chunk_t blob)
{
asn1_parser_t *parser;
chunk_t object;
int objectID;
bool success = FALSE;
parser = asn1_parser_create(authenticatedSafeObjects, blob);
while (parser->iterate(parser, &objectID, &object))
{
switch (objectID)
{
case AUTHENTICATED_SAFE_DATA:
{
container_t *container;
chunk_t data;
container = lib->creds->create(lib->creds, CRED_CONTAINER,
CONTAINER_PKCS7, BUILD_BLOB_ASN1_DER,
object, BUILD_END);
if (!container)
{
goto end;
}
switch (container->get_type(container))
{
case CONTAINER_PKCS7_DATA:
case CONTAINER_PKCS7_ENCRYPTED_DATA:
case CONTAINER_PKCS7_ENVELOPED_DATA:
if (container->get_data(container, &data))
{
break;
}
/* fall-through */
default:
container->destroy(container);
goto end;
}
container->destroy(container);
if (!parse_safe_contents(this, parser->get_level(parser)+1,
data))
{
chunk_free(&data);
goto end;
}
chunk_free(&data);
break;
}
}
}
success = parser->success(parser);
end:
parser->destroy(parser);
return success;
}
/**
* Verify the given MAC with available passwords.
*/
static bool verify_mac(hash_algorithm_t hash, chunk_t salt,
uint64_t iterations, chunk_t data, chunk_t mac)
{
integrity_algorithm_t integ;
enumerator_t *enumerator;
shared_key_t *shared;
signer_t *signer;
chunk_t key, calculated;
bool success = FALSE;
integ = hasher_algorithm_to_integrity(hash, mac.len);
signer = lib->crypto->create_signer(lib->crypto, integ);
if (!signer)
{
return FALSE;
}
key = chunk_alloca(signer->get_key_size(signer));
calculated = chunk_alloca(signer->get_block_size(signer));
enumerator = lib->credmgr->create_shared_enumerator(lib->credmgr,
SHARED_PRIVATE_KEY_PASS, NULL, NULL);
while (enumerator->enumerate(enumerator, &shared, NULL, NULL))
{
if (!pkcs12_derive_key(hash, shared->get_key(shared), salt, iterations,
PKCS12_KEY_MAC, key))
{
break;
}
if (!signer->set_key(signer, key) ||
!signer->get_signature(signer, data, calculated.ptr))
{
break;
}
if (chunk_equals_const(mac, calculated))
{
success = TRUE;
break;
}
}
enumerator->destroy(enumerator);
signer->destroy(signer);
return success;
}
/**
* ASN.1 definition of digestInfo
*/
static const asn1Object_t digestInfoObjects[] = {
{ 0, "digestInfo", ASN1_SEQUENCE, ASN1_OBJ }, /* 0 */
{ 1, "digestAlgorithm", ASN1_EOC, ASN1_RAW }, /* 1 */
{ 1, "digest", ASN1_OCTET_STRING, ASN1_BODY }, /* 2 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#define DIGEST_INFO_ALGORITHM 1
#define DIGEST_INFO_DIGEST 2
/**
* Parse a digestInfo structure
*/
static bool parse_digest_info(chunk_t blob, int level0, hash_algorithm_t *hash,
chunk_t *digest)
{
asn1_parser_t *parser;
chunk_t object;
int objectID;
bool success;
parser = asn1_parser_create(digestInfoObjects, blob);
parser->set_top_level(parser, level0);
while (parser->iterate(parser, &objectID, &object))
{
switch (objectID)
{
case DIGEST_INFO_ALGORITHM:
{
int oid = asn1_parse_algorithmIdentifier(object,
parser->get_level(parser)+1, NULL);
*hash = hasher_algorithm_from_oid(oid);
break;
}
case DIGEST_INFO_DIGEST:
{
*digest = object;
break;
}
default:
break;
}
}
success = parser->success(parser);
parser->destroy(parser);
return success;
}
/**
* ASN.1 definition of a PFX structure
*/
static const asn1Object_t PFXObjects[] = {
{ 0, "PFX", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */
{ 1, "version", ASN1_INTEGER, ASN1_BODY }, /* 1 */
{ 1, "authSafe", ASN1_SEQUENCE, ASN1_OBJ }, /* 2 */
{ 1, "macData", ASN1_SEQUENCE, ASN1_OPT|ASN1_BODY }, /* 3 */
{ 2, "mac", ASN1_SEQUENCE, ASN1_RAW }, /* 4 */
{ 2, "macSalt", ASN1_OCTET_STRING, ASN1_BODY }, /* 5 */
{ 2, "iterations", ASN1_INTEGER, ASN1_DEF|ASN1_BODY }, /* 6 */
{ 1, "end opt", ASN1_EOC, ASN1_END }, /* 7 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#define PFX_AUTH_SAFE 2
#define PFX_MAC 4
#define PFX_SALT 5
#define PFX_ITERATIONS 6
/**
* Parse an ASN.1 encoded PFX structure
*/
static bool parse_PFX(private_pkcs12_t *this, chunk_t blob)
{
asn1_parser_t *parser;
int objectID;
chunk_t object, auth_safe, digest = chunk_empty, salt = chunk_empty,
data = chunk_empty;
hash_algorithm_t hash = HASH_UNKNOWN;
container_t *container = NULL;
uint64_t iterations = 0;
bool success = FALSE;
parser = asn1_parser_create(PFXObjects, blob);
while (parser->iterate(parser, &objectID, &object))
{
switch (objectID)
{
case PFX_AUTH_SAFE:
{
auth_safe = object;
break;
}
case PFX_MAC:
{
if (!parse_digest_info(object, parser->get_level(parser)+1,
&hash, &digest))
{
goto end_parse;
}
break;
}
case PFX_SALT:
{
salt = object;
break;
}
case PFX_ITERATIONS:
{
iterations = object.len ? asn1_parse_integer_uint64(object) : 1;
break;
}
}
}
success = parser->success(parser);
end_parse:
parser->destroy(parser);
if (!success)
{
return FALSE;
}
success = FALSE;
DBG2(DBG_ASN, "-- > --");
container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7,
BUILD_BLOB_ASN1_DER, auth_safe, BUILD_END);
if (container && container->get_data(container, &data))
{
if (hash != HASH_UNKNOWN)
{
if (container->get_type(container) != CONTAINER_PKCS7_DATA)
{
goto end;
}
if (!verify_mac(hash, salt, iterations, data, digest))
{
DBG1(DBG_ASN, " MAC verification of PKCS#12 container failed");
goto end;
}
}
else
{
enumerator_t *enumerator;
auth_cfg_t *auth;
if (container->get_type(container) != CONTAINER_PKCS7_SIGNED_DATA)
{
goto end;
}
enumerator = container->create_signature_enumerator(container);
if (!enumerator->enumerate(enumerator, &auth))
{
DBG1(DBG_ASN, " signature verification of PKCS#12 container "
"failed");
enumerator->destroy(enumerator);
goto end;
}
enumerator->destroy(enumerator);
}
success = parse_authenticated_safe(this, data);
}
end:
DBG2(DBG_ASN, "-- < --");
DESTROY_IF(container);
chunk_free(&data);
return success;
}
/**
* See header.
*/
pkcs12_t *pkcs12_decode(container_type_t type, va_list args)
{
private_pkcs12_t *this;
chunk_t blob = chunk_empty;
while (TRUE)
{
switch (va_arg(args, builder_part_t))
{
case BUILD_BLOB_ASN1_DER:
blob = va_arg(args, chunk_t);
continue;
case BUILD_END:
break;
default:
return NULL;
}
break;
}
if (blob.len)
{
if (blob.len >= 2 &&
blob.ptr[0] == ASN1_SEQUENCE && blob.ptr[1] == 0x80)
{ /* looks like infinite length BER encoding, but we can't handle it.
*/
return NULL;
}
this = pkcs12_create();
if (parse_PFX(this, blob))
{
return &this->public;
}
destroy(this);
}
return NULL;
}