/*
* Copyright (C) 2003-2014 Free Software Foundation, Inc.
* Copyright (C) 2014 Red Hat
*
* Author: Nikos Mavrogiannopoulos
*
* This file is part of GnuTLS.
*
* The GnuTLS is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
*
*/
/* Functions that relate on PKCS12 Bag packet parsing.
*/
#include "gnutls_int.h"
#include
#include
#include "errors.h"
#include
#include "x509_int.h"
#include "pkcs7_int.h"
/**
* gnutls_pkcs12_bag_init:
* @bag: A pointer to the type to be initialized
*
* This function will initialize a PKCS12 bag structure. PKCS12 Bags
* usually contain private keys, lists of X.509 Certificates and X.509
* Certificate revocation lists.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int gnutls_pkcs12_bag_init(gnutls_pkcs12_bag_t * bag)
{
*bag = gnutls_calloc(1, sizeof(gnutls_pkcs12_bag_int));
if (*bag) {
return 0; /* success */
}
return GNUTLS_E_MEMORY_ERROR;
}
static inline void _pkcs12_bag_free_data(gnutls_pkcs12_bag_t bag)
{
unsigned i;
for (i = 0; i < bag->bag_elements; i++) {
_gnutls_free_datum(&bag->element[i].data);
_gnutls_free_datum(&bag->element[i].local_key_id);
gnutls_free(bag->element[i].friendly_name);
bag->element[i].friendly_name = NULL;
bag->element[i].type = 0;
}
}
/**
* gnutls_pkcs12_bag_deinit:
* @bag: A pointer to the type to be initialized
*
* This function will deinitialize a PKCS12 Bag structure.
**/
void gnutls_pkcs12_bag_deinit(gnutls_pkcs12_bag_t bag)
{
if (!bag)
return;
_pkcs12_bag_free_data(bag);
gnutls_free(bag);
}
/**
* gnutls_pkcs12_bag_get_type:
* @bag: The bag
* @indx: The element of the bag to get the type
*
* This function will return the bag's type.
*
* Returns: On error a negative error value or one of the #gnutls_pkcs12_bag_type_t enumerations.
**/
int
gnutls_pkcs12_bag_get_type(gnutls_pkcs12_bag_t bag, unsigned indx)
{
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (indx >= bag->bag_elements)
return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
return bag->element[indx].type;
}
/**
* gnutls_pkcs12_bag_get_count:
* @bag: The bag
*
* This function will return the number of the elements within the bag.
*
* Returns: Number of elements in bag, or an negative error code on
* error.
**/
int gnutls_pkcs12_bag_get_count(gnutls_pkcs12_bag_t bag)
{
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
return bag->bag_elements;
}
/**
* gnutls_pkcs12_bag_get_data:
* @bag: The bag
* @indx: The element of the bag to get the data from
* @data: where the bag's data will be. Should be treated as constant.
*
* This function will return the bag's data. The data is a constant
* that is stored into the bag. Should not be accessed after the bag
* is deleted.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int
gnutls_pkcs12_bag_get_data(gnutls_pkcs12_bag_t bag, unsigned indx,
gnutls_datum_t * data)
{
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (indx >= bag->bag_elements)
return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
data->data = bag->element[indx].data.data;
data->size = bag->element[indx].data.size;
return 0;
}
#define X509_CERT_OID "1.2.840.113549.1.9.22.1"
#define X509_CRL_OID "1.2.840.113549.1.9.23.1"
#define RANDOM_NONCE_OID "1.2.840.113549.1.9.25.3"
int
_pkcs12_decode_crt_bag(gnutls_pkcs12_bag_type_t type,
const gnutls_datum_t * in, gnutls_datum_t * out)
{
int ret;
ASN1_TYPE c2 = ASN1_TYPE_EMPTY;
switch (type) {
case GNUTLS_BAG_CERTIFICATE:
if ((ret = asn1_create_element(_gnutls_get_pkix(),
"PKIX1.pkcs-12-CertBag",
&c2)) != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret = asn1_der_decoding(&c2, in->data, in->size, NULL);
if (ret != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret =
_gnutls_x509_read_string(c2, "certValue", out,
ASN1_ETYPE_OCTET_STRING, 1);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
break;
case GNUTLS_BAG_CRL:
if ((ret = asn1_create_element(_gnutls_get_pkix(),
"PKIX1.pkcs-12-CRLBag",
&c2)) != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret = asn1_der_decoding(&c2, in->data, in->size, NULL);
if (ret != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret =
_gnutls_x509_read_string(c2, "crlValue", out,
ASN1_ETYPE_OCTET_STRING, 1);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
break;
case GNUTLS_BAG_SECRET:
if ((ret = asn1_create_element(_gnutls_get_pkix(),
"PKIX1.pkcs-12-SecretBag",
&c2)) != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret = asn1_der_decoding(&c2, in->data, in->size, NULL);
if (ret != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret =
_gnutls_x509_read_string(c2, "secretValue", out,
ASN1_ETYPE_OCTET_STRING, 1);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
break;
default:
gnutls_assert();
asn1_delete_structure(&c2);
return GNUTLS_E_UNIMPLEMENTED_FEATURE;
}
asn1_delete_structure(&c2);
return 0;
cleanup:
asn1_delete_structure(&c2);
return ret;
}
int
_pkcs12_encode_crt_bag(gnutls_pkcs12_bag_type_t type,
const gnutls_datum_t * raw, gnutls_datum_t * out)
{
int ret;
ASN1_TYPE c2 = ASN1_TYPE_EMPTY;
switch (type) {
case GNUTLS_BAG_CERTIFICATE:
if ((ret = asn1_create_element(_gnutls_get_pkix(),
"PKIX1.pkcs-12-CertBag",
&c2)) != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret = asn1_write_value(c2, "certId", X509_CERT_OID, 1);
if (ret != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret =
_gnutls_x509_write_string(c2, "certValue", raw,
ASN1_ETYPE_OCTET_STRING);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
break;
case GNUTLS_BAG_CRL:
if ((ret = asn1_create_element(_gnutls_get_pkix(),
"PKIX1.pkcs-12-CRLBag",
&c2)) != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret = asn1_write_value(c2, "crlId", X509_CRL_OID, 1);
if (ret != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret =
_gnutls_x509_write_string(c2, "crlValue", raw,
ASN1_ETYPE_OCTET_STRING);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
break;
case GNUTLS_BAG_SECRET:
if ((ret = asn1_create_element(_gnutls_get_pkix(),
"PKIX1.pkcs-12-SecretBag",
&c2)) != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret =
asn1_write_value(c2, "secretTypeId", RANDOM_NONCE_OID,
1);
if (ret != ASN1_SUCCESS) {
gnutls_assert();
ret = _gnutls_asn2err(ret);
goto cleanup;
}
ret =
_gnutls_x509_write_string(c2, "secretValue", raw,
ASN1_ETYPE_OCTET_STRING);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
break;
default:
gnutls_assert();
asn1_delete_structure(&c2);
return GNUTLS_E_UNIMPLEMENTED_FEATURE;
}
ret = _gnutls_x509_der_encode(c2, "", out, 0);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
asn1_delete_structure(&c2);
return 0;
cleanup:
asn1_delete_structure(&c2);
return ret;
}
/**
* gnutls_pkcs12_bag_set_data:
* @bag: The bag
* @type: The data's type
* @data: the data to be copied.
*
* This function will insert the given data of the given type into
* the bag.
*
* Returns: the index of the added bag on success, or a negative
* value on error.
**/
int
gnutls_pkcs12_bag_set_data(gnutls_pkcs12_bag_t bag,
gnutls_pkcs12_bag_type_t type,
const gnutls_datum_t * data)
{
int ret;
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (bag->bag_elements == MAX_BAG_ELEMENTS - 1) {
gnutls_assert();
/* bag is full */
return GNUTLS_E_MEMORY_ERROR;
}
if (bag->bag_elements == 1) {
/* A bag with a key or an encrypted bag, must have
* only one element.
*/
if (bag->element[0].type == GNUTLS_BAG_PKCS8_KEY ||
bag->element[0].type == GNUTLS_BAG_PKCS8_ENCRYPTED_KEY
|| bag->element[0].type == GNUTLS_BAG_ENCRYPTED) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
}
ret =
_gnutls_set_datum(&bag->element[bag->bag_elements].data,
data->data, data->size);
if (ret < 0) {
gnutls_assert();
return ret;
}
bag->element[bag->bag_elements].type = type;
bag->bag_elements++;
return bag->bag_elements - 1;
}
/**
* gnutls_pkcs12_bag_set_crt:
* @bag: The bag
* @crt: the certificate to be copied.
*
* This function will insert the given certificate into the
* bag. This is just a wrapper over gnutls_pkcs12_bag_set_data().
*
* Returns: the index of the added bag on success, or a negative
* value on failure.
**/
int
gnutls_pkcs12_bag_set_crt(gnutls_pkcs12_bag_t bag, gnutls_x509_crt_t crt)
{
int ret;
gnutls_datum_t data;
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
ret = _gnutls_x509_der_encode(crt->cert, "", &data, 0);
if (ret < 0) {
gnutls_assert();
return ret;
}
ret =
gnutls_pkcs12_bag_set_data(bag, GNUTLS_BAG_CERTIFICATE, &data);
_gnutls_free_datum(&data);
return ret;
}
/**
* gnutls_pkcs12_bag_set_crl:
* @bag: The bag
* @crl: the CRL to be copied.
*
* This function will insert the given CRL into the
* bag. This is just a wrapper over gnutls_pkcs12_bag_set_data().
*
* Returns: the index of the added bag on success, or a negative error code
* on failure.
**/
int
gnutls_pkcs12_bag_set_crl(gnutls_pkcs12_bag_t bag, gnutls_x509_crl_t crl)
{
int ret;
gnutls_datum_t data;
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
ret = _gnutls_x509_der_encode(crl->crl, "", &data, 0);
if (ret < 0) {
gnutls_assert();
return ret;
}
ret = gnutls_pkcs12_bag_set_data(bag, GNUTLS_BAG_CRL, &data);
_gnutls_free_datum(&data);
return ret;
}
/**
* gnutls_pkcs12_bag_set_key_id:
* @bag: The bag
* @indx: The bag's element to add the id
* @id: the ID
*
* This function will add the given key ID, to the specified, by the
* index, bag element. The key ID will be encoded as a 'Local key
* identifier' bag attribute, which is usually used to distinguish
* the local private key and the certificate pair.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value. or a negative error code on error.
**/
int
gnutls_pkcs12_bag_set_key_id(gnutls_pkcs12_bag_t bag, unsigned indx,
const gnutls_datum_t * id)
{
int ret;
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (indx > bag->bag_elements - 1) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
ret = _gnutls_set_datum(&bag->element[indx].local_key_id,
id->data, id->size);
if (ret < 0) {
gnutls_assert();
return ret;
}
return 0;
}
/**
* gnutls_pkcs12_bag_get_key_id:
* @bag: The bag
* @indx: The bag's element to add the id
* @id: where the ID will be copied (to be treated as const)
*
* This function will return the key ID, of the specified bag element.
* The key ID is usually used to distinguish the local private key and
* the certificate pair.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value. or a negative error code on error.
**/
int
gnutls_pkcs12_bag_get_key_id(gnutls_pkcs12_bag_t bag, unsigned indx,
gnutls_datum_t * id)
{
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (indx > bag->bag_elements - 1) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
id->data = bag->element[indx].local_key_id.data;
id->size = bag->element[indx].local_key_id.size;
return 0;
}
/**
* gnutls_pkcs12_bag_get_friendly_name:
* @bag: The bag
* @indx: The bag's element to add the id
* @name: will hold a pointer to the name (to be treated as const)
*
* This function will return the friendly name, of the specified bag
* element. The key ID is usually used to distinguish the local
* private key and the certificate pair.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value. or a negative error code on error.
**/
int
gnutls_pkcs12_bag_get_friendly_name(gnutls_pkcs12_bag_t bag, unsigned indx,
char **name)
{
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (indx > bag->bag_elements - 1) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
*name = bag->element[indx].friendly_name;
return 0;
}
/**
* gnutls_pkcs12_bag_set_friendly_name:
* @bag: The bag
* @indx: The bag's element to add the id
* @name: the name
*
* This function will add the given key friendly name, to the
* specified, by the index, bag element. The name will be encoded as
* a 'Friendly name' bag attribute, which is usually used to set a
* user name to the local private key and the certificate pair.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value. or a negative error code on error.
**/
int
gnutls_pkcs12_bag_set_friendly_name(gnutls_pkcs12_bag_t bag, unsigned indx,
const char *name)
{
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (indx > bag->bag_elements - 1) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
bag->element[indx].friendly_name = gnutls_strdup(name);
if (name == NULL) {
gnutls_assert();
return GNUTLS_E_MEMORY_ERROR;
}
return 0;
}
/**
* gnutls_pkcs12_bag_decrypt:
* @bag: The bag
* @pass: The password used for encryption, must be ASCII.
*
* This function will decrypt the given encrypted bag and return 0 on
* success.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned,
* otherwise a negative error code is returned.
**/
int gnutls_pkcs12_bag_decrypt(gnutls_pkcs12_bag_t bag, const char *pass)
{
int ret;
gnutls_datum_t dec;
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (bag->element[0].type != GNUTLS_BAG_ENCRYPTED) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
ret =
_gnutls_pkcs7_decrypt_data(&bag->element[0].data, pass, &dec);
if (ret < 0) {
gnutls_assert();
return ret;
}
/* decryption succeeded. Now decode the SafeContents
* stuff, and parse it.
*/
_gnutls_free_datum(&bag->element[0].data);
ret = _pkcs12_decode_safe_contents(&dec, bag);
_gnutls_free_datum(&dec);
if (ret < 0) {
gnutls_assert();
return ret;
}
return 0;
}
/**
* gnutls_pkcs12_bag_encrypt:
* @bag: The bag
* @pass: The password used for encryption, must be ASCII
* @flags: should be one of #gnutls_pkcs_encrypt_flags_t elements bitwise or'd
*
* This function will encrypt the given bag.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned,
* otherwise a negative error code is returned.
**/
int
gnutls_pkcs12_bag_encrypt(gnutls_pkcs12_bag_t bag, const char *pass,
unsigned int flags)
{
int ret;
ASN1_TYPE safe_cont = ASN1_TYPE_EMPTY;
gnutls_datum_t der = { NULL, 0 };
gnutls_datum_t enc = { NULL, 0 };
schema_id id;
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (bag->element[0].type == GNUTLS_BAG_ENCRYPTED) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
/* Encode the whole bag to a safe contents
* structure.
*/
ret = _pkcs12_encode_safe_contents(bag, &safe_cont, NULL);
if (ret < 0) {
gnutls_assert();
return ret;
}
/* DER encode the SafeContents.
*/
ret = _gnutls_x509_der_encode(safe_cont, "", &der, 0);
asn1_delete_structure(&safe_cont);
if (ret < 0) {
gnutls_assert();
return ret;
}
if (flags & GNUTLS_PKCS_PLAIN) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
id = _gnutls_pkcs_flags_to_schema(flags);
/* Now encrypt them.
*/
ret = _gnutls_pkcs7_encrypt_data(id, &der, pass, &enc);
_gnutls_free_datum(&der);
if (ret < 0) {
gnutls_assert();
return ret;
}
/* encryption succeeded.
*/
_pkcs12_bag_free_data(bag);
bag->element[0].type = GNUTLS_BAG_ENCRYPTED;
bag->element[0].data = enc;
bag->bag_elements = 1;
return 0;
}
/**
* gnutls_pkcs12_bag_enc_info:
* @bag: The bag
* @schema: indicate the schema as one of %gnutls_pkcs_encrypt_flags_t
* @cipher: the cipher used as %gnutls_cipher_algorithm_t
* @salt: PBKDF2 salt (if non-NULL then @salt_size initially holds its size)
* @salt_size: PBKDF2 salt size
* @iter_count: PBKDF2 iteration count
* @oid: if non-NULL it will contain an allocated null-terminated variable with the OID
*
* This function will provide information on the encryption algorithms used
* in an encrypted bag. If the structure algorithms
* are unknown the code %GNUTLS_E_UNKNOWN_CIPHER_TYPE will be returned,
* and only @oid, will be set. That is, @oid will be set on encrypted bags
* whether supported or not. It must be deinitialized using gnutls_free().
* The other variables are only set on supported structures.
*
* Returns: %GNUTLS_E_INVALID_REQUEST if the provided bag isn't encrypted,
* %GNUTLS_E_UNKNOWN_CIPHER_TYPE if the structure's encryption isn't supported, or
* another negative error code in case of a failure. Zero on success.
**/
int
gnutls_pkcs12_bag_enc_info(gnutls_pkcs12_bag_t bag, unsigned int *schema, unsigned int *cipher,
void *salt, unsigned int *salt_size, unsigned int *iter_count, char **oid)
{
int ret;
struct pbkdf2_params kdf;
const struct pkcs_cipher_schema_st *p;
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (bag->element[0].type != GNUTLS_BAG_ENCRYPTED) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
ret =
_gnutls_pkcs7_data_enc_info(&bag->element[0].data, &p, &kdf, oid);
if (ret < 0) {
gnutls_assert();
return ret;
}
if (schema)
*schema = p->flag;
if (cipher)
*cipher = p->cipher;
if (iter_count)
*iter_count = kdf.iter_count;
if (salt) {
if (*salt_size >= (unsigned)kdf.salt_size) {
memcpy(salt, kdf.salt, kdf.salt_size);
} else {
*salt_size = kdf.salt_size;
return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
}
}
if (salt_size)
*salt_size = kdf.salt_size;
return 0;
}
/**
* gnutls_pkcs12_bag_set_privkey:
* @bag: The bag
* @privkey: the private key to be copied.
* @password: the password to protect the key with (may be %NULL)
* @flags: should be one of #gnutls_pkcs_encrypt_flags_t elements bitwise or'd
*
* This function will insert the given private key into the
* bag. This is just a wrapper over gnutls_pkcs12_bag_set_data().
*
* Returns: the index of the added bag on success, or a negative
* value on failure.
**/
int
gnutls_pkcs12_bag_set_privkey(gnutls_pkcs12_bag_t bag, gnutls_x509_privkey_t privkey,
const char *password, unsigned flags)
{
int ret;
gnutls_datum_t data = {NULL, 0};
if (bag == NULL) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
ret = gnutls_x509_privkey_export2_pkcs8(privkey, GNUTLS_X509_FMT_DER,
password, flags, &data);
if (ret < 0)
return gnutls_assert_val(ret);
if (password == NULL) {
ret = gnutls_pkcs12_bag_set_data(bag, GNUTLS_BAG_PKCS8_KEY, &data);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
} else {
ret = gnutls_pkcs12_bag_set_data(bag, GNUTLS_BAG_PKCS8_ENCRYPTED_KEY, &data);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
}
cleanup:
_gnutls_free_datum(&data);
return ret;
}