Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "pkcs11.h"
#include "pkcs11uri.h"
#include "plarena.h"
#include "prprf.h"
#include "secport.h"
/* Character sets used in the ABNF rules in RFC7512. */
#define PK11URI_DIGIT "0123456789"
#define PK11URI_ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
#define PK11URI_HEXDIG PK11URI_DIGIT "abcdefABCDEF"
#define PK11URI_UNRESERVED PK11URI_ALPHA PK11URI_DIGIT "-._~"
#define PK11URI_RES_AVAIL ":[]@!$'()*+,="
#define PK11URI_PATH_RES_AVAIL PK11URI_RES_AVAIL "&"
#define PK11URI_QUERY_RES_AVAIL PK11URI_RES_AVAIL "/?|"
#define PK11URI_ATTR_NM_CHAR PK11URI_ALPHA PK11URI_DIGIT "-_"
#define PK11URI_PCHAR PK11URI_UNRESERVED PK11URI_PATH_RES_AVAIL
#define PK11URI_QCHAR PK11URI_UNRESERVED PK11URI_QUERY_RES_AVAIL
/* Path attributes defined in RFC7512. */
static const char *pattr_names[] = {
PK11URI_PATTR_TOKEN,
PK11URI_PATTR_MANUFACTURER,
PK11URI_PATTR_SERIAL,
PK11URI_PATTR_MODEL,
PK11URI_PATTR_LIBRARY_MANUFACTURER,
PK11URI_PATTR_LIBRARY_DESCRIPTION,
PK11URI_PATTR_LIBRARY_VERSION,
PK11URI_PATTR_OBJECT,
PK11URI_PATTR_TYPE,
PK11URI_PATTR_ID,
PK11URI_PATTR_SLOT_MANUFACTURER,
PK11URI_PATTR_SLOT_DESCRIPTION,
PK11URI_PATTR_SLOT_ID
};
/* Query attributes defined in RFC7512. */
static const char *qattr_names[] = {
PK11URI_QATTR_PIN_SOURCE,
PK11URI_QATTR_PIN_VALUE,
PK11URI_QATTR_MODULE_NAME,
PK11URI_QATTR_MODULE_PATH
};
struct PK11URIBufferStr {
PLArenaPool *arena;
unsigned char *data;
size_t size;
size_t allocated;
};
typedef struct PK11URIBufferStr PK11URIBuffer;
struct PK11URIAttributeListEntryStr {
char *name;
SECItem value;
};
typedef struct PK11URIAttributeListEntryStr PK11URIAttributeListEntry;
struct PK11URIAttributeListStr {
PLArenaPool *arena;
PK11URIAttributeListEntry *attrs;
size_t num_attrs;
};
typedef struct PK11URIAttributeListStr PK11URIAttributeList;
struct PK11URIStr {
PLArenaPool *arena;
PK11URIAttributeList pattrs;
PK11URIAttributeList vpattrs;
PK11URIAttributeList qattrs;
PK11URIAttributeList vqattrs;
};
#define PK11URI_ARENA_SIZE 1024
typedef int (*PK11URIAttributeCompareNameFunc)(const char *a, const char *b);
/* This belongs in secport.h */
#define PORT_ArenaGrowArray(poolp, oldptr, type, oldnum, newnum) \
(type *)PORT_ArenaGrow((poolp), (oldptr), \
(oldnum) * sizeof(type), (newnum) * sizeof(type))
#define PORT_ReallocArray(oldptr, type, newnum) \
(type *)PORT_Realloc((oldptr), (newnum) * sizeof(type))
/* Functions for resizable buffer. */
static SECStatus
pk11uri_AppendBuffer(PK11URIBuffer *buffer, const unsigned char *data,
size_t size)
{
/* Check overflow. */
if (buffer->size + size < buffer->size)
return SECFailure;
if (buffer->size + size > buffer->allocated) {
size_t allocated = buffer->allocated * 2 + size;
if (allocated < buffer->allocated)
return SECFailure;
if (buffer->arena)
buffer->data = PORT_ArenaGrow(buffer->arena, buffer->data,
buffer->allocated, allocated);
else
buffer->data = PORT_Realloc(buffer->data, allocated);
if (buffer->data == NULL)
return SECFailure;
buffer->allocated = allocated;
}
memcpy(&buffer->data[buffer->size], data, size);
buffer->size += size;
return SECSuccess;
}
static void
pk11uri_InitBuffer(PK11URIBuffer *buffer, PLArenaPool *arena)
{
memset(buffer, 0, sizeof(PK11URIBuffer));
buffer->arena = arena;
}
static void
pk11uri_DestroyBuffer(PK11URIBuffer *buffer)
{
if (buffer->arena == NULL) {
PORT_Free(buffer->data);
}
}
/* URI encoding functions. */
static char *
pk11uri_Escape(PLArenaPool *arena, const unsigned char *value, size_t length,
const char *available)
{
PK11URIBuffer buffer;
const unsigned char *p;
unsigned char buf[4];
char *result = NULL;
SECStatus ret;
pk11uri_InitBuffer(&buffer, arena);
for (p = value; p < value + length; p++) {
if (strchr(available, *p) == NULL) {
if (PR_snprintf((char *)buf, sizeof(buf), "%%%02X", *p) == (PRUint32)-1) {
goto fail;
}
ret = pk11uri_AppendBuffer(&buffer, buf, 3);
if (ret != SECSuccess) {
goto fail;
}
} else {
ret = pk11uri_AppendBuffer(&buffer, p, 1);
if (ret != SECSuccess) {
goto fail;
}
}
}
buf[0] = '\0';
ret = pk11uri_AppendBuffer(&buffer, buf, 1);
if (ret != SECSuccess) {
goto fail;
}
/* Steal the memory allocated in buffer. */
result = (char *)buffer.data;
buffer.data = NULL;
fail:
pk11uri_DestroyBuffer(&buffer);
return result;
}
static unsigned char *
pk11uri_Unescape(PLArenaPool *arena, const char *value, size_t *length)
{
PK11URIBuffer buffer;
const char *p;
unsigned char buf[1];
unsigned char *result = NULL;
SECStatus ret;
pk11uri_InitBuffer(&buffer, arena);
for (p = value; p < value + *length; p++) {
if (*p == '%') {
int c;
size_t i;
p++;
for (c = 0, i = 0; i < 2; i++) {
int h = *(p + i);
if ('0' <= h && h <= '9') {
c = (c << 4) | (h - '0');
} else if ('a' <= h && h <= 'f') {
c = (c << 4) | (h - 'a' + 10);
} else if ('A' <= h && h <= 'F') {
c = (c << 4) | (h - 'A' + 10);
} else {
break;
}
}
if (i != 2) {
goto fail;
}
p++;
buf[0] = c;
} else {
buf[0] = *p;
}
ret = pk11uri_AppendBuffer(&buffer, buf, 1);
if (ret != SECSuccess) {
goto fail;
}
}
*length = buffer.size;
buf[0] = '\0';
ret = pk11uri_AppendBuffer(&buffer, buf, 1);
if (ret != SECSuccess) {
goto fail;
}
result = buffer.data;
buffer.data = NULL;
fail:
pk11uri_DestroyBuffer(&buffer);
return result;
}
/* Functions for manipulating attributes array. */
/* Compare two attribute names by the array index in attr_names. Both
* attribute names must be present in attr_names, otherwise it is a
* programming error. */
static int
pk11uri_CompareByPosition(const char *a, const char *b,
const char **attr_names, size_t num_attr_names)
{
size_t i, j;
for (i = 0; i < num_attr_names; i++) {
if (strcmp(a, attr_names[i]) == 0) {
break;
}
}
PR_ASSERT(i < num_attr_names);
for (j = 0; j < num_attr_names; j++) {
if (strcmp(b, attr_names[j]) == 0) {
break;
}
}
PR_ASSERT(j < num_attr_names);
return i - j;
}
/* Those pk11uri_Compare{Path,Query}AttributeName functions are used
* to reorder attributes when inserting. */
static int
pk11uri_ComparePathAttributeName(const char *a, const char *b)
{
return pk11uri_CompareByPosition(a, b, pattr_names, PR_ARRAY_SIZE(pattr_names));
}
static int
pk11uri_CompareQueryAttributeName(const char *a, const char *b)
{
return pk11uri_CompareByPosition(a, b, qattr_names, PR_ARRAY_SIZE(qattr_names));
}
static SECStatus
pk11uri_InsertToAttributeList(PK11URIAttributeList *attrs,
char *name, unsigned char *value, size_t size,
PK11URIAttributeCompareNameFunc compare_name,
PRBool allow_duplicate)
{
size_t i;
if (attrs->arena) {
attrs->attrs = PORT_ArenaGrowArray(attrs->arena, attrs->attrs,
PK11URIAttributeListEntry,
attrs->num_attrs,
attrs->num_attrs + 1);
} else {
attrs->attrs = PORT_ReallocArray(attrs->attrs,
PK11URIAttributeListEntry,
attrs->num_attrs + 1);
}
if (attrs->attrs == NULL) {
return SECFailure;
}
for (i = 0; i < attrs->num_attrs; i++) {
if (!allow_duplicate && strcmp(name, attrs->attrs[i].name) == 0) {
return SECFailure;
}
if (compare_name(name, attrs->attrs[i].name) < 0) {
memmove(&attrs->attrs[i + 1], &attrs->attrs[i],
sizeof(PK11URIAttributeListEntry) * (attrs->num_attrs - i));
break;
}
}
attrs->attrs[i].name = name;
attrs->attrs[i].value.type = siBuffer;
attrs->attrs[i].value.data = value;
attrs->attrs[i].value.len = size;
attrs->num_attrs++;
return SECSuccess;
}
static SECStatus
pk11uri_InsertToAttributeListEscaped(PK11URIAttributeList *attrs,
const char *name, size_t name_size,
const char *value, size_t value_size,
PK11URIAttributeCompareNameFunc compare_name,
PRBool allow_duplicate)
{
char *name_copy = NULL;
unsigned char *value_copy = NULL;
SECStatus ret;
if (attrs->arena) {
name_copy = PORT_ArenaNewArray(attrs->arena, char, name_size + 1);
} else {
name_copy = PORT_Alloc(name_size + 1);
}
if (name_copy == NULL) {
goto fail;
}
memcpy(name_copy, name, name_size);
name_copy[name_size] = '\0';
value_copy = pk11uri_Unescape(attrs->arena, value, &value_size);
if (value_copy == NULL) {
goto fail;
}
ret = pk11uri_InsertToAttributeList(attrs, name_copy, value_copy, value_size,
compare_name, allow_duplicate);
if (ret != SECSuccess) {
goto fail;
}
return ret;
fail:
if (attrs->arena == NULL) {
PORT_Free(name_copy);
PORT_Free(value_copy);
}
return SECFailure;
}
static void
pk11uri_InitAttributeList(PK11URIAttributeList *attrs, PLArenaPool *arena)
{
memset(attrs, 0, sizeof(PK11URIAttributeList));
attrs->arena = arena;
}
static void
pk11uri_DestroyAttributeList(PK11URIAttributeList *attrs)
{
if (attrs->arena == NULL) {
size_t i;
for (i = 0; i < attrs->num_attrs; i++) {
PORT_Free(attrs->attrs[i].name);
PORT_Free(attrs->attrs[i].value.data);
}
PORT_Free(attrs->attrs);
}
}
static SECStatus
pk11uri_AppendAttributeListToBuffer(PK11URIBuffer *buffer,
PK11URIAttributeList *attrs,
int separator,
const char *unescaped)
{
size_t i;
SECStatus ret;
for (i = 0; i < attrs->num_attrs; i++) {
unsigned char sep[1];
char *escaped;
PK11URIAttributeListEntry *attr = &attrs->attrs[i];
if (i > 0) {
sep[0] = separator;
ret = pk11uri_AppendBuffer(buffer, sep, 1);
if (ret != SECSuccess) {
return ret;
}
}
ret = pk11uri_AppendBuffer(buffer, (unsigned char *)attr->name,
strlen(attr->name));
if (ret != SECSuccess) {
return ret;
}
sep[0] = '=';
ret = pk11uri_AppendBuffer(buffer, sep, 1);
if (ret != SECSuccess) {
return ret;
}
escaped = pk11uri_Escape(buffer->arena, attr->value.data, attr->value.len,
unescaped);
if (escaped == NULL) {
return ret;
}
ret = pk11uri_AppendBuffer(buffer, (unsigned char *)escaped,
strlen(escaped));
if (buffer->arena == NULL) {
PORT_Free(escaped);
}
if (ret != SECSuccess) {
return ret;
}
}
return SECSuccess;
}
/* Creation of PK11URI object. */
static PK11URI *
pk11uri_AllocURI(void)
{
PLArenaPool *arena;
PK11URI *result;
arena = PORT_NewArena(PK11URI_ARENA_SIZE);
if (arena == NULL) {
return NULL;
}
result = PORT_ArenaZAlloc(arena, sizeof(PK11URI));
if (result == NULL) {
PORT_FreeArena(arena, PR_FALSE);
return NULL;
}
result->arena = arena;
pk11uri_InitAttributeList(&result->pattrs, arena);
pk11uri_InitAttributeList(&result->vpattrs, arena);
pk11uri_InitAttributeList(&result->qattrs, arena);
pk11uri_InitAttributeList(&result->vqattrs, arena);
return result;
}
static SECStatus
pk11uri_InsertAttributes(PK11URIAttributeList *dest_attrs,
PK11URIAttributeList *dest_vattrs,
const PK11URIAttribute *attrs,
size_t num_attrs,
const char **attr_names,
size_t num_attr_names,
PK11URIAttributeCompareNameFunc compare_name,
PRBool allow_duplicate,
PRBool vendor_allow_duplicate)
{
SECStatus ret;
size_t i;
for (i = 0; i < num_attrs; i++) {
char *name, *value;
const char *p;
size_t j;
p = attrs[i].name;
/* The attribute must not be empty. */
if (*p == '\0') {
return SECFailure;
}
/* Check that the name doesn't contain invalid character. */
for (; *p != '\0'; p++) {
if (strchr(PK11URI_ATTR_NM_CHAR, *p) == NULL) {
return SECFailure;
}
}
name = PORT_ArenaStrdup(dest_attrs->arena, attrs[i].name);
if (name == NULL) {
return SECFailure;
}
value = PORT_ArenaStrdup(dest_attrs->arena, attrs[i].value);
if (value == NULL) {
return SECFailure;
}
for (j = 0; j < num_attr_names; j++) {
if (strcmp(name, attr_names[j]) == 0) {
break;
}
}
if (j < num_attr_names) {
/* Named attribute. */
ret = pk11uri_InsertToAttributeList(dest_attrs,
name,
(unsigned char *)value,
strlen(value),
compare_name,
allow_duplicate);
if (ret != SECSuccess) {
return ret;
}
} else {
/* Vendor attribute. */
ret = pk11uri_InsertToAttributeList(dest_vattrs,
name,
(unsigned char *)value,
strlen(value),
strcmp,
vendor_allow_duplicate);
if (ret != SECSuccess) {
return ret;
}
}
}
return SECSuccess;
}
PK11URI *
PK11URI_CreateURI(const PK11URIAttribute *pattrs,
size_t num_pattrs,
const PK11URIAttribute *qattrs,
size_t num_qattrs)
{
PK11URI *result;
SECStatus ret;
result = pk11uri_AllocURI();
ret = pk11uri_InsertAttributes(&result->pattrs, &result->vpattrs,
pattrs, num_pattrs,
pattr_names, PR_ARRAY_SIZE(pattr_names),
pk11uri_ComparePathAttributeName,
PR_FALSE, PR_FALSE);
if (ret != SECSuccess) {
goto fail;
}
ret = pk11uri_InsertAttributes(&result->qattrs, &result->vqattrs,
qattrs, num_qattrs,
qattr_names, PR_ARRAY_SIZE(qattr_names),
pk11uri_CompareQueryAttributeName,
PR_FALSE, PR_TRUE);
if (ret != SECSuccess) {
goto fail;
}
return result;
fail:
PK11URI_DestroyURI(result);
return NULL;
}
/* Parsing. */
static SECStatus
pk11uri_ParseAttributes(const char **string,
const char *stop_chars,
int separator,
const char *accept_chars,
const char **attr_names, size_t num_attr_names,
PK11URIAttributeList *attrs,
PK11URIAttributeList *vattrs,
PK11URIAttributeCompareNameFunc compare_name,
PRBool allow_duplicate,
PRBool vendor_allow_duplicate)
{
const char *p = *string;
for (; *p != '\0'; p++) {
const char *name_start, *name_end, *value_start, *value_end;
size_t name_length, value_length, i;
SECStatus ret;
if (strchr(stop_chars, *p) != NULL) {
break;
}
for (name_start = p; *p != '=' && *p != '\0'; p++) {
if (strchr(PK11URI_ATTR_NM_CHAR, *p) != NULL)
continue;
return SECFailure;
}
if (*p == '\0') {
return SECFailure;
}
name_end = p++;
/* The attribute name must not be empty. */
if (name_end == name_start) {
return SECFailure;
}
for (value_start = p; *p != separator && *p != '\0'; p++) {
if (strchr(stop_chars, *p) != NULL) {
break;
}
if (strchr(accept_chars, *p) != NULL) {
continue;
}
if (*p == '%') {
const char ch2 = *++p;
if (strchr(PK11URI_HEXDIG, ch2) != NULL) {
const char ch3 = *++p;
if (strchr(PK11URI_HEXDIG, ch3) != NULL)
continue;
}
}
return SECFailure;
}
value_end = p;
name_length = name_end - name_start;
value_length = value_end - value_start;
for (i = 0; i < num_attr_names; i++) {
if (name_length == strlen(attr_names[i]) &&
memcmp(name_start, attr_names[i], name_length) == 0) {
break;
}
}
if (i < num_attr_names) {
/* Named attribute. */
ret = pk11uri_InsertToAttributeListEscaped(attrs,
name_start, name_length,
value_start, value_length,
compare_name,
allow_duplicate);
if (ret != SECSuccess) {
return ret;
}
} else {
/* Vendor attribute. */
ret = pk11uri_InsertToAttributeListEscaped(vattrs,
name_start, name_length,
value_start, value_length,
strcmp,
vendor_allow_duplicate);
if (ret != SECSuccess) {
return ret;
}
}
if (*p == '?' || *p == '\0') {
break;
}
}
*string = p;
return SECSuccess;
}
PK11URI *
PK11URI_ParseURI(const char *string)
{
PK11URI *result;
const char *p = string;
SECStatus ret;
if (PORT_Strncasecmp("pkcs11:", p, 7) != 0) {
return NULL;
}
p += 7;
result = pk11uri_AllocURI();
if (result == NULL) {
return NULL;
}
/* Parse the path component and its attributes. */
ret = pk11uri_ParseAttributes(&p, "?", ';', PK11URI_PCHAR,
pattr_names, PR_ARRAY_SIZE(pattr_names),
&result->pattrs, &result->vpattrs,
pk11uri_ComparePathAttributeName,
PR_FALSE, PR_FALSE);
if (ret != SECSuccess) {
goto fail;
}
/* Parse the query component and its attributes. */
if (*p == '?') {
p++;
ret = pk11uri_ParseAttributes(&p, "", '&', PK11URI_QCHAR,
qattr_names, PR_ARRAY_SIZE(qattr_names),
&result->qattrs, &result->vqattrs,
pk11uri_CompareQueryAttributeName,
PR_FALSE, PR_TRUE);
if (ret != SECSuccess) {
goto fail;
}
}
return result;
fail:
PK11URI_DestroyURI(result);
return NULL;
}
/* Formatting. */
char *
PK11URI_FormatURI(PLArenaPool *arena, PK11URI *uri)
{
PK11URIBuffer buffer;
SECStatus ret;
char *result = NULL;
pk11uri_InitBuffer(&buffer, arena);
ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)"pkcs11:", 7);
if (ret != SECSuccess)
goto fail;
ret = pk11uri_AppendAttributeListToBuffer(&buffer, &uri->pattrs, ';', PK11URI_PCHAR);
if (ret != SECSuccess) {
goto fail;
}
if (uri->pattrs.num_attrs > 0 && uri->vpattrs.num_attrs > 0) {
ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)";", 1);
if (ret != SECSuccess) {
goto fail;
}
}
ret = pk11uri_AppendAttributeListToBuffer(&buffer, &uri->vpattrs, ';',
PK11URI_PCHAR);
if (ret != SECSuccess) {
goto fail;
}
if (uri->qattrs.num_attrs > 0 || uri->vqattrs.num_attrs > 0) {
ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)"?", 1);
if (ret != SECSuccess) {
goto fail;
}
}
ret = pk11uri_AppendAttributeListToBuffer(&buffer, &uri->qattrs, '&', PK11URI_QCHAR);
if (ret != SECSuccess) {
goto fail;
}
if (uri->qattrs.num_attrs > 0 && uri->vqattrs.num_attrs > 0) {
ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)"&", 1);
if (ret != SECSuccess) {
goto fail;
}
}
ret = pk11uri_AppendAttributeListToBuffer(&buffer, &uri->vqattrs, '&',
PK11URI_QCHAR);
if (ret != SECSuccess) {
goto fail;
}
ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)"\0", 1);
if (ret != SECSuccess) {
goto fail;
}
result = (char *)buffer.data;
buffer.data = NULL;
fail:
pk11uri_DestroyBuffer(&buffer);
return result;
}
/* Deallocating. */
void
PK11URI_DestroyURI(PK11URI *uri)
{
pk11uri_DestroyAttributeList(&uri->pattrs);
pk11uri_DestroyAttributeList(&uri->vpattrs);
pk11uri_DestroyAttributeList(&uri->qattrs);
pk11uri_DestroyAttributeList(&uri->vqattrs);
PORT_FreeArena(uri->arena, PR_FALSE);
}
/* Accessors. */
static const SECItem *
pk11uri_GetAttribute(PK11URIAttributeList *attrs,
PK11URIAttributeList *vattrs,
const char *name)
{
size_t i;
for (i = 0; i < attrs->num_attrs; i++) {
if (strcmp(name, attrs->attrs[i].name) == 0) {
return &attrs->attrs[i].value;
}
}
for (i = 0; i < vattrs->num_attrs; i++) {
if (strcmp(name, vattrs->attrs[i].name) == 0) {
return &vattrs->attrs[i].value;
}
}
return NULL;
}
const SECItem *
PK11URI_GetPathAttributeItem(PK11URI *uri, const char *name)
{
return pk11uri_GetAttribute(&uri->pattrs, &uri->vpattrs, name);
}
const char *
PK11URI_GetPathAttribute(PK11URI *uri, const char *name)
{
const SECItem *value;
value = PK11URI_GetPathAttributeItem(uri, name);
if (!value) {
return NULL;
}
return (const char *)value->data;
}
const SECItem *
PK11URI_GetQueryAttributeItem(PK11URI *uri, const char *name)
{
return pk11uri_GetAttribute(&uri->qattrs, &uri->vqattrs, name);
}
const char *
PK11URI_GetQueryAttribute(PK11URI *uri, const char *name)
{
const SECItem *value;
value = PK11URI_GetQueryAttributeItem(uri, name);
if (!value) {
return NULL;
}
return (const char *)value->data;
}