/*
* Copyright (C) 2013 Red Hat Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and
* the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* * The names of contributors to this software may not be
* used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* Author: Stef Walter <stefw@redhat.com>
*/
#include "config.h"
#include "asn1.h"
#include "attrs.h"
#include "constants.h"
#include "debug.h"
#include "lexer.h"
#include "message.h"
#include "pem.h"
#include "persist.h"
#include "pkcs11.h"
#include "pkcs11i.h"
#include "pkcs11x.h"
#include "types.h"
#include "url.h"
#include "basic.asn.h"
#include <libtasn1.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define PERSIST_HEADER "p11-kit-object-v1"
struct _p11_persist {
p11_dict *constants;
node_asn *asn1_defs;
};
bool
p11_persist_magic (const unsigned char *data,
size_t length)
{
return (strnstr ((char *)data, "[" PERSIST_HEADER "]", length) != NULL);
}
bool
p11_persist_is_generated (const unsigned char *data,
size_t length)
{
static const char comment[] =
"# This file has been auto-generated and written by p11-kit.";
return length >= sizeof (comment) - 1 &&
memcmp ((const char *)data, comment, sizeof (comment) - 1) == 0;
}
p11_persist *
p11_persist_new (void)
{
p11_persist *persist;
persist = calloc (1, sizeof (p11_persist));
return_val_if_fail (persist != NULL, NULL);
persist->constants = p11_constant_reverse (true);
return_val_if_fail (persist->constants != NULL, NULL);
return persist;
}
void
p11_persist_free (p11_persist *persist)
{
if (!persist)
return;
p11_dict_free (persist->constants);
asn1_delete_structure (&persist->asn1_defs);
free (persist);
}
struct constant {
CK_ULONG value;
const char *string;
};
static bool
parse_string (p11_lexer *lexer,
CK_ATTRIBUTE *attr)
{
const char *value;
const char *end;
size_t length;
unsigned char *data;
value = lexer->tok.field.value;
end = value + strlen (value);
/* Not a string/binary value */
if (value == end || value[0] != '\"' || *(end - 1) != '\"')
return false;
/* Note that we don't skip whitespace when decoding, as you might in other URLs */
data = p11_url_decode (value + 1, end - 1, "", &length);
if (data == NULL) {
p11_lexer_msg(lexer, "bad encoding of attribute value");
return false;
}
attr->pValue = data;
attr->ulValueLen = length;
return true;
}
static void
format_string (CK_ATTRIBUTE *attr,
p11_buffer *buf)
{
const unsigned char *value;
assert (attr->ulValueLen != CK_UNAVAILABLE_INFORMATION);
p11_buffer_add (buf, "\"", 1);
value = attr->pValue;
p11_url_encode (value, value + attr->ulValueLen, P11_URL_VERBATIM " ", buf);
p11_buffer_add (buf, "\"", 1);
}
static bool
parse_bool (p11_lexer *lexer,
CK_ATTRIBUTE *attr)
{
const char *value = lexer->tok.field.value;
CK_BBOOL boolean;
if (strcmp (value, "true") == 0) {
boolean = CK_TRUE;
} else if (strcmp (value, "false") == 0) {
boolean = CK_FALSE;
} else {
/* Not a valid boolean value */
return false;
}
attr->pValue = memdup (&boolean, sizeof (boolean));
return_val_if_fail (attr != NULL, FALSE);
attr->ulValueLen = sizeof (boolean);
return true;
}
static bool
format_bool (CK_ATTRIBUTE *attr,
p11_buffer *buf)
{
const CK_BBOOL *value;
if (attr->ulValueLen != sizeof (CK_BBOOL))
return false;
switch (attr->type) {
case CKA_TOKEN:
case CKA_PRIVATE:
case CKA_TRUSTED:
case CKA_SENSITIVE:
case CKA_ENCRYPT:
case CKA_DECRYPT:
case CKA_WRAP:
case CKA_UNWRAP:
case CKA_SIGN:
case CKA_SIGN_RECOVER:
case CKA_VERIFY:
case CKA_VERIFY_RECOVER:
case CKA_DERIVE:
case CKA_EXTRACTABLE:
case CKA_LOCAL:
case CKA_NEVER_EXTRACTABLE:
case CKA_ALWAYS_SENSITIVE:
case CKA_MODIFIABLE:
case CKA_SECONDARY_AUTH:
case CKA_ALWAYS_AUTHENTICATE:
case CKA_WRAP_WITH_TRUSTED:
case CKA_RESET_ON_INIT:
case CKA_HAS_RESET:
case CKA_COLOR:
case CKA_X_DISTRUSTED:
case CKA_NSS_MOZILLA_CA_POLICY:
break;
default:
return false;
}
value = attr->pValue;
if (*value == CK_TRUE)
p11_buffer_add (buf, "true", -1);
else if (*value == CK_FALSE)
p11_buffer_add (buf, "false", -1);
else
return false;
return true;
}
static bool
parse_ulong (p11_lexer *lexer,
CK_ATTRIBUTE *attr)
{
unsigned long value;
char *end;
end = NULL;
value = strtoul (lexer->tok.field.value, &end, 10);
/* Not a valid number value */
if (!end || *end != '\0')
return false;
attr->pValue = memdup (&value, sizeof (CK_ULONG));
return_val_if_fail (attr->pValue != NULL, false);
attr->ulValueLen = sizeof (CK_ULONG);
return true;
}
static bool
format_ulong (CK_ATTRIBUTE *attr,
p11_buffer *buf)
{
char string[sizeof (CK_ULONG) * 4];
const CK_ULONG *value;
if (attr->ulValueLen != sizeof (CK_ULONG))
return false;
switch (attr->type) {
case CKA_CERTIFICATE_CATEGORY:
case CKA_CERTIFICATE_TYPE:
case CKA_CLASS:
case CKA_JAVA_MIDP_SECURITY_DOMAIN:
case CKA_KEY_GEN_MECHANISM:
case CKA_KEY_TYPE:
case CKA_MECHANISM_TYPE:
case CKA_MODULUS_BITS:
case CKA_PRIME_BITS:
case CKA_SUB_PRIME_BITS:
case CKA_VALUE_BITS:
case CKA_VALUE_LEN:
case CKA_TRUST_DIGITAL_SIGNATURE:
case CKA_TRUST_NON_REPUDIATION:
case CKA_TRUST_KEY_ENCIPHERMENT:
case CKA_TRUST_DATA_ENCIPHERMENT:
case CKA_TRUST_KEY_AGREEMENT:
case CKA_TRUST_KEY_CERT_SIGN:
case CKA_TRUST_CRL_SIGN:
case CKA_TRUST_SERVER_AUTH:
case CKA_TRUST_CLIENT_AUTH:
case CKA_TRUST_CODE_SIGNING:
case CKA_TRUST_EMAIL_PROTECTION:
case CKA_TRUST_IPSEC_END_SYSTEM:
case CKA_TRUST_IPSEC_TUNNEL:
case CKA_TRUST_IPSEC_USER:
case CKA_TRUST_TIME_STAMPING:
case CKA_TRUST_STEP_UP_APPROVED:
case CKA_X_ASSERTION_TYPE:
case CKA_AUTH_PIN_FLAGS:
case CKA_HW_FEATURE_TYPE:
case CKA_PIXEL_X:
case CKA_PIXEL_Y:
case CKA_RESOLUTION:
case CKA_CHAR_ROWS:
case CKA_CHAR_COLUMNS:
case CKA_BITS_PER_PIXEL:
break;
default:
return false;
}
value = attr->pValue;
snprintf (string, sizeof (string), "%lu", *value);
p11_buffer_add (buf, string, -1);
return true;
}
static bool
parse_constant (p11_persist *persist,
p11_lexer *lexer,
CK_ATTRIBUTE *attr)
{
CK_ULONG value;
value = p11_constant_resolve (persist->constants, lexer->tok.field.value);
/* Not a valid constant */
if (value == CKA_INVALID)
return false;
attr->pValue = memdup (&value, sizeof (CK_ULONG));
return_val_if_fail (attr->pValue != NULL, false);
attr->ulValueLen = sizeof (CK_ULONG);
return true;
}
static bool
format_constant (CK_ATTRIBUTE *attr,
p11_buffer *buf)
{
const p11_constant *table;
const CK_ULONG *value;
const char *nick;
if (attr->ulValueLen != sizeof (CK_ULONG))
return false;
switch (attr->type) {
case CKA_TRUST_DIGITAL_SIGNATURE:
case CKA_TRUST_NON_REPUDIATION:
case CKA_TRUST_KEY_ENCIPHERMENT:
case CKA_TRUST_DATA_ENCIPHERMENT:
case CKA_TRUST_KEY_AGREEMENT:
case CKA_TRUST_KEY_CERT_SIGN:
case CKA_TRUST_CRL_SIGN:
case CKA_TRUST_SERVER_AUTH:
case CKA_TRUST_CLIENT_AUTH:
case CKA_TRUST_CODE_SIGNING:
case CKA_TRUST_EMAIL_PROTECTION:
case CKA_TRUST_IPSEC_END_SYSTEM:
case CKA_TRUST_IPSEC_TUNNEL:
case CKA_TRUST_IPSEC_USER:
case CKA_TRUST_TIME_STAMPING:
table = p11_constant_trusts;
break;
case CKA_CLASS:
table = p11_constant_classes;
break;
case CKA_CERTIFICATE_TYPE:
table = p11_constant_certs;
break;
case CKA_KEY_TYPE:
table = p11_constant_keys;
break;
case CKA_X_ASSERTION_TYPE:
table = p11_constant_asserts;
break;
case CKA_CERTIFICATE_CATEGORY:
table = p11_constant_categories;
break;
case CKA_KEY_GEN_MECHANISM:
case CKA_MECHANISM_TYPE:
table = p11_constant_mechanisms;
break;
default:
table = NULL;
};
if (!table)
return false;
value = attr->pValue;
nick = p11_constant_nick (table, *value);
if (!nick)
return false;
p11_buffer_add (buf, nick, -1);
return true;
}
static bool
parse_oid (p11_persist *persist,
p11_lexer *lexer,
CK_ATTRIBUTE *attr)
{
char message[ASN1_MAX_ERROR_DESCRIPTION_SIZE] = { 0, };
node_asn *asn;
size_t length;
char *value;
int ret;
value = lexer->tok.field.value;
length = strlen (value);
/* Not an OID value? */
if (length < 4 ||
strchr (value, '.') == NULL ||
strspn (value, "0123456790.") != length ||
strstr (value, "..") != NULL ||
value[0] == '.' || value[0] == '0' ||
value[length - 1] == '.' ||
strchr (value, '.') == strrchr (value, '.')) {
return false;
}
if (!persist->asn1_defs) {
ret = asn1_array2tree (basic_asn1_tab, &persist->asn1_defs, message);
if (ret != ASN1_SUCCESS) {
p11_debug_precond ("failed to load BASIC definitions: %s: %s\n",
asn1_strerror (ret), message);
return false;
}
}
ret = asn1_create_element (persist->asn1_defs, "BASIC.ObjectIdentifier", &asn);
if (ret != ASN1_SUCCESS) {
p11_debug_precond ("failed to create ObjectIdentifier element: %s\n",
asn1_strerror (ret));
return false;
}
ret = asn1_write_value (asn, "", value, 1);
if (ret == ASN1_VALUE_NOT_VALID) {
p11_lexer_msg (lexer, "invalid oid value");
asn1_delete_structure (&asn);
return false;
}
return_val_if_fail (ret == ASN1_SUCCESS, false);
attr->pValue = p11_asn1_encode (asn, &length);
return_val_if_fail (attr->pValue != NULL, false);
attr->ulValueLen = length;
asn1_delete_structure (&asn);
return true;
}
static bool
format_oid (p11_persist *persist,
CK_ATTRIBUTE *attr,
p11_buffer *buf)
{
char message[ASN1_MAX_ERROR_DESCRIPTION_SIZE] = { 0, };
node_asn *asn;
char *data;
size_t len;
int ret;
if (attr->type != CKA_OBJECT_ID || attr->ulValueLen == 0)
return false;
if (!persist->asn1_defs) {
ret = asn1_array2tree (basic_asn1_tab, &persist->asn1_defs, message);
if (ret != ASN1_SUCCESS) {
p11_debug_precond ("failed to load BASIC definitions: %s: %s\n",
asn1_strerror (ret), message);
return false;
}
}
ret = asn1_create_element (persist->asn1_defs, "BASIC.ObjectIdentifier", &asn);
if (ret != ASN1_SUCCESS) {
p11_debug_precond ("failed to create ObjectIdentifier element: %s\n",
asn1_strerror (ret));
return false;
}
ret = asn1_der_decoding (&asn, attr->pValue, attr->ulValueLen, message);
if (ret != ASN1_SUCCESS) {
p11_message ("invalid oid value: %s", message);
return false;
}
data = p11_asn1_read (asn, "", &len);
return_val_if_fail (data != NULL, false);
asn1_delete_structure (&asn);
p11_buffer_add (buf, data, len - 1);
free (data);
return true;
}
static bool
parse_value (p11_persist *persist,
p11_lexer *lexer,
CK_ATTRIBUTE *attr)
{
return parse_constant (persist, lexer, attr) ||
parse_string (lexer, attr) ||
parse_bool (lexer, attr) ||
parse_ulong (lexer, attr) ||
parse_oid (persist, lexer, attr);
}
static void
format_value (p11_persist *persist,
CK_ATTRIBUTE *attr,
p11_buffer *buf)
{
assert (attr->ulValueLen != CK_UNAVAILABLE_INFORMATION);
if (format_bool (attr, buf) ||
format_constant (attr, buf) ||
format_ulong (attr, buf) ||
format_oid (persist, attr, buf))
return;
/* Everything else as string */
format_string (attr, buf);
}
static bool
field_to_attribute (p11_persist *persist,
p11_lexer *lexer,
CK_ATTRIBUTE **attrs)
{
CK_ATTRIBUTE attr = { 0, };
char *end;
end = NULL;
attr.type = strtoul (lexer->tok.field.name, &end, 10);
/* Not a valid number value, probably a constant */
if (!end || *end != '\0') {
attr.type = p11_constant_resolve (persist->constants, lexer->tok.field.name);
if (attr.type == CKA_INVALID || !p11_constant_name (p11_constant_types, attr.type)) {
p11_lexer_msg (lexer, "invalid or unsupported attribute");
return false;
}
}
if (!parse_value (persist, lexer, &attr)) {
p11_lexer_msg (lexer, "invalid value");
return false;
}
*attrs = p11_attrs_take (*attrs, attr.type,
attr.pValue, attr.ulValueLen);
return true;
}
static CK_ATTRIBUTE *
certificate_to_attributes (const unsigned char *der,
size_t length)
{
CK_OBJECT_CLASS klassv = CKO_CERTIFICATE;
CK_CERTIFICATE_TYPE x509 = CKC_X_509;
CK_ATTRIBUTE klass = { CKA_CLASS, &klassv, sizeof (klassv) };
CK_ATTRIBUTE certificate_type = { CKA_CERTIFICATE_TYPE, &x509, sizeof (x509) };
CK_ATTRIBUTE value = { CKA_VALUE, (void *)der, length };
return p11_attrs_build (NULL, &klass, &certificate_type, &value, NULL);
}
static CK_ATTRIBUTE *
public_key_to_attributes (const unsigned char *der,
size_t length)
{
/* Eventually we might choose to contribute a class here ... */
CK_ATTRIBUTE public_key = { CKA_PUBLIC_KEY_INFO, (void *)der, length };
return p11_attrs_build (NULL, &public_key, NULL);
}
typedef struct {
p11_lexer *lexer;
CK_ATTRIBUTE *attrs;
bool result;
} parse_block;
static void
on_pem_block (const char *type,
const unsigned char *contents,
size_t length,
void *user_data)
{
parse_block *pb = user_data;
CK_ATTRIBUTE *attrs;
if (strcmp (type, "CERTIFICATE") == 0) {
attrs = certificate_to_attributes (contents, length);
pb->attrs = p11_attrs_merge (pb->attrs, attrs, false);
pb->result = true;
} else if (strcmp (type, "PUBLIC KEY") == 0) {
attrs = public_key_to_attributes (contents, length);
pb->attrs = p11_attrs_merge (pb->attrs, attrs, false);
pb->result = true;
} else {
p11_lexer_msg (pb->lexer, "unsupported pem block in store");
pb->result = false;
}
}
static bool
pem_to_attributes (p11_lexer *lexer,
CK_ATTRIBUTE **attrs)
{
parse_block pb = { lexer, *attrs, false };
unsigned int count;
count = p11_pem_parse (lexer->tok.pem.begin,
lexer->tok.pem.length,
on_pem_block, &pb);
if (count == 0) {
p11_lexer_msg (lexer, "invalid pem block");
return false;
}
/* The lexer should have only matched one block */
return_val_if_fail (count == 1, false);
*attrs = pb.attrs;
return pb.result;
}
bool
p11_persist_read (p11_persist *persist,
const char *filename,
const unsigned char *data,
size_t length,
p11_array *objects)
{
p11_lexer lexer;
CK_ATTRIBUTE *attrs;
bool failed;
bool skip;
return_val_if_fail (persist != NULL, false);
return_val_if_fail (objects != NULL, false);
skip = false;
attrs = NULL;
failed = false;
p11_lexer_init (&lexer, filename, (const char *)data, length);
while (p11_lexer_next (&lexer, &failed)) {
switch (lexer.tok_type) {
case TOK_SECTION:
if (attrs && !p11_array_push (objects, attrs))
return_val_if_reached (false);
attrs = NULL;
if (strcmp (lexer.tok.section.name, PERSIST_HEADER) != 0) {
p11_lexer_msg (&lexer, "unrecognized or invalid section header");
skip = true;
} else {
attrs = p11_attrs_build (NULL, NULL);
return_val_if_fail (attrs != NULL, false);
skip = false;
}
failed = false;
break;
case TOK_FIELD:
if (skip) {
failed = false;
} else if (!attrs) {
p11_lexer_msg (&lexer, "attribute before p11-kit section header");
failed = true;
} else {
failed = !field_to_attribute (persist, &lexer, &attrs);
}
break;
case TOK_PEM:
if (skip) {
failed = false;
} else if (!attrs) {
p11_lexer_msg (&lexer, "pem block before p11-kit section header");
failed = true;
} else {
failed = !pem_to_attributes (&lexer, &attrs);
}
break;
}
if (failed)
break;
}
if (attrs && !p11_array_push (objects, attrs))
return_val_if_reached (false);
attrs = NULL;
p11_lexer_done (&lexer);
return !failed;
}
static CK_ATTRIBUTE *
find_certificate_value (CK_ATTRIBUTE *attrs)
{
CK_OBJECT_CLASS klass;
CK_CERTIFICATE_TYPE type;
if (!p11_attrs_find_ulong (attrs, CKA_CLASS, &klass) ||
klass != CKO_CERTIFICATE)
return NULL;
if (!p11_attrs_find_ulong (attrs, CKA_CERTIFICATE_TYPE, &type) ||
type != CKC_X_509)
return NULL;
return p11_attrs_find_valid (attrs, CKA_VALUE);
}
bool
p11_persist_write (p11_persist *persist,
CK_ATTRIBUTE *attrs,
p11_buffer *buf)
{
char string[sizeof (CK_ULONG) * 4];
CK_ATTRIBUTE *cert_value;
CK_ATTRIBUTE *spki_value;
const char *nick;
int i;
cert_value = find_certificate_value (attrs);
spki_value = p11_attrs_find_valid (attrs, CKA_PUBLIC_KEY_INFO);
p11_buffer_add (buf, "[" PERSIST_HEADER "]\n", -1);
for (i = 0; !p11_attrs_terminator (attrs + i); i++) {
/* These are written later? */
if (cert_value != NULL &&
(attrs[i].type == CKA_CLASS ||
attrs[i].type == CKA_CERTIFICATE_TYPE ||
attrs[i].type == CKA_VALUE))
continue;
/* These are written later? */
if (spki_value != NULL &&
attrs[i].type == CKA_PUBLIC_KEY_INFO)
continue;
/* These are never written */
if (attrs[i].type == CKA_TOKEN ||
attrs[i].type == CKA_X_ORIGIN ||
attrs[i].type == CKA_X_GENERATED)
continue;
if (attrs[i].ulValueLen == CK_UNAVAILABLE_INFORMATION)
continue;
nick = p11_constant_nick (p11_constant_types, attrs[i].type);
if (nick == NULL) {
snprintf (string, sizeof (string), "%lu", attrs[i].type);
nick = string;
}
p11_buffer_add (buf, nick, -1);
p11_buffer_add (buf, ": ", 2);
format_value (persist, attrs + i, buf);
p11_buffer_add (buf, "\n", 1);
}
if (cert_value != NULL) {
if (!p11_pem_write (cert_value->pValue, cert_value->ulValueLen, "CERTIFICATE", buf))
return_val_if_reached (false);
} else if (spki_value != NULL) {
if (!p11_pem_write (spki_value->pValue, spki_value->ulValueLen, "PUBLIC KEY", buf))
return_val_if_reached (false);
}
p11_buffer_add (buf, "\n", 1);
return p11_buffer_ok (buf);
}