17cf2cb1d2
Most C standard library functions are undefined if passed NULL, even when the corresponding length is zero. This gives them (and, in turn, all functions which call them) surprising behavior on empty arrays. Some compilers will miscompile code due to this rule. See also https://www.imperialviolet.org/2016/06/26/nonnull.html Add OPENSSL_memcpy, etc., wrappers which avoid this problem. BUG=23 Change-Id: I95f42b23e92945af0e681264fffaf578e7f8465e Reviewed-on: https://boringssl-review.googlesource.com/12928 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com>
831 lines
24 KiB
C
831 lines
24 KiB
C
/*
|
|
* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL project
|
|
* 2004.
|
|
*/
|
|
/* ====================================================================
|
|
* Copyright (c) 2004 The OpenSSL Project. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. 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.
|
|
*
|
|
* 3. All advertising materials mentioning features or use of this
|
|
* software must display the following acknowledgment:
|
|
* "This product includes software developed by the OpenSSL Project
|
|
* for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)"
|
|
*
|
|
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
|
* endorse or promote products derived from this software without
|
|
* prior written permission. For written permission, please contact
|
|
* licensing@OpenSSL.org.
|
|
*
|
|
* 5. Products derived from this software may not be called "OpenSSL"
|
|
* nor may "OpenSSL" appear in their names without prior written
|
|
* permission of the OpenSSL Project.
|
|
*
|
|
* 6. Redistributions of any form whatsoever must retain the following
|
|
* acknowledgment:
|
|
* "This product includes software developed by the OpenSSL Project
|
|
* for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)"
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
|
* EXPRESSED 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 OpenSSL PROJECT OR
|
|
* ITS 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.
|
|
* ====================================================================
|
|
*
|
|
* This product includes cryptographic software written by Eric Young
|
|
* (eay@cryptsoft.com). This product includes software written by Tim
|
|
* Hudson (tjh@cryptsoft.com).
|
|
*
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include <openssl/mem.h>
|
|
#include <openssl/obj.h>
|
|
#include <openssl/stack.h>
|
|
#include <openssl/thread.h>
|
|
#include <openssl/x509.h>
|
|
#include <openssl/x509v3.h>
|
|
|
|
#include "pcy_int.h"
|
|
#include "../internal.h"
|
|
|
|
/*
|
|
* Enable this to print out the complete policy tree at various point during
|
|
* evaluation.
|
|
*/
|
|
|
|
/*
|
|
* #define OPENSSL_POLICY_DEBUG
|
|
*/
|
|
|
|
#ifdef OPENSSL_POLICY_DEBUG
|
|
|
|
static void expected_print(BIO *err, X509_POLICY_LEVEL *lev,
|
|
X509_POLICY_NODE *node, int indent)
|
|
{
|
|
if ((lev->flags & X509_V_FLAG_INHIBIT_MAP)
|
|
|| !(node->data->flags & POLICY_DATA_FLAG_MAP_MASK))
|
|
BIO_puts(err, " Not Mapped\n");
|
|
else {
|
|
int i;
|
|
STACK_OF(ASN1_OBJECT) *pset = node->data->expected_policy_set;
|
|
ASN1_OBJECT *oid;
|
|
BIO_puts(err, " Expected: ");
|
|
for (i = 0; i < sk_ASN1_OBJECT_num(pset); i++) {
|
|
oid = sk_ASN1_OBJECT_value(pset, i);
|
|
if (i)
|
|
BIO_puts(err, ", ");
|
|
i2a_ASN1_OBJECT(err, oid);
|
|
}
|
|
BIO_puts(err, "\n");
|
|
}
|
|
}
|
|
|
|
static void tree_print(char *str, X509_POLICY_TREE *tree,
|
|
X509_POLICY_LEVEL *curr)
|
|
{
|
|
X509_POLICY_LEVEL *plev;
|
|
X509_POLICY_NODE *node;
|
|
int i;
|
|
BIO *err;
|
|
err = BIO_new_fp(stderr, BIO_NOCLOSE);
|
|
if (!curr)
|
|
curr = tree->levels + tree->nlevel;
|
|
else
|
|
curr++;
|
|
BIO_printf(err, "Level print after %s\n", str);
|
|
BIO_printf(err, "Printing Up to Level %ld\n", curr - tree->levels);
|
|
for (plev = tree->levels; plev != curr; plev++) {
|
|
BIO_printf(err, "Level %ld, flags = %x\n",
|
|
plev - tree->levels, plev->flags);
|
|
for (i = 0; i < sk_X509_POLICY_NODE_num(plev->nodes); i++) {
|
|
node = sk_X509_POLICY_NODE_value(plev->nodes, i);
|
|
X509_POLICY_NODE_print(err, node, 2);
|
|
expected_print(err, plev, node, 2);
|
|
BIO_printf(err, " Flags: %x\n", node->data->flags);
|
|
}
|
|
if (plev->anyPolicy)
|
|
X509_POLICY_NODE_print(err, plev->anyPolicy, 2);
|
|
}
|
|
|
|
BIO_free(err);
|
|
|
|
}
|
|
#else
|
|
|
|
# define tree_print(a,b,c) /* */
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Initialize policy tree. Return values: 0 Some internal error occured. -1
|
|
* Inconsistent or invalid extensions in certificates. 1 Tree initialized
|
|
* OK. 2 Policy tree is empty. 5 Tree OK and requireExplicitPolicy true. 6
|
|
* Tree empty and requireExplicitPolicy true.
|
|
*/
|
|
|
|
static int tree_init(X509_POLICY_TREE **ptree, STACK_OF(X509) *certs,
|
|
unsigned int flags)
|
|
{
|
|
X509_POLICY_TREE *tree;
|
|
X509_POLICY_LEVEL *level;
|
|
const X509_POLICY_CACHE *cache;
|
|
X509_POLICY_DATA *data = NULL;
|
|
X509 *x;
|
|
int ret = 1;
|
|
int i, n;
|
|
int explicit_policy;
|
|
int any_skip;
|
|
int map_skip;
|
|
*ptree = NULL;
|
|
n = sk_X509_num(certs);
|
|
|
|
#if 0
|
|
/* Disable policy mapping for now... */
|
|
flags |= X509_V_FLAG_INHIBIT_MAP;
|
|
#endif
|
|
|
|
if (flags & X509_V_FLAG_EXPLICIT_POLICY)
|
|
explicit_policy = 0;
|
|
else
|
|
explicit_policy = n + 1;
|
|
|
|
if (flags & X509_V_FLAG_INHIBIT_ANY)
|
|
any_skip = 0;
|
|
else
|
|
any_skip = n + 1;
|
|
|
|
if (flags & X509_V_FLAG_INHIBIT_MAP)
|
|
map_skip = 0;
|
|
else
|
|
map_skip = n + 1;
|
|
|
|
/* Can't do anything with just a trust anchor */
|
|
if (n == 1)
|
|
return 1;
|
|
/*
|
|
* First setup policy cache in all certificates apart from the trust
|
|
* anchor. Note any bad cache results on the way. Also can calculate
|
|
* explicit_policy value at this point.
|
|
*/
|
|
for (i = n - 2; i >= 0; i--) {
|
|
x = sk_X509_value(certs, i);
|
|
X509_check_purpose(x, -1, -1);
|
|
cache = policy_cache_set(x);
|
|
/* If cache NULL something bad happened: return immediately */
|
|
if (cache == NULL)
|
|
return 0;
|
|
/*
|
|
* If inconsistent extensions keep a note of it but continue
|
|
*/
|
|
if (x->ex_flags & EXFLAG_INVALID_POLICY)
|
|
ret = -1;
|
|
/*
|
|
* Otherwise if we have no data (hence no CertificatePolicies) and
|
|
* haven't already set an inconsistent code note it.
|
|
*/
|
|
else if ((ret == 1) && !cache->data)
|
|
ret = 2;
|
|
if (explicit_policy > 0) {
|
|
if (!(x->ex_flags & EXFLAG_SI))
|
|
explicit_policy--;
|
|
if ((cache->explicit_skip != -1)
|
|
&& (cache->explicit_skip < explicit_policy))
|
|
explicit_policy = cache->explicit_skip;
|
|
}
|
|
}
|
|
|
|
if (ret != 1) {
|
|
if (ret == 2 && !explicit_policy)
|
|
return 6;
|
|
return ret;
|
|
}
|
|
|
|
/* If we get this far initialize the tree */
|
|
|
|
tree = OPENSSL_malloc(sizeof(X509_POLICY_TREE));
|
|
|
|
if (!tree)
|
|
return 0;
|
|
|
|
tree->flags = 0;
|
|
tree->levels = OPENSSL_malloc(sizeof(X509_POLICY_LEVEL) * n);
|
|
tree->nlevel = 0;
|
|
tree->extra_data = NULL;
|
|
tree->auth_policies = NULL;
|
|
tree->user_policies = NULL;
|
|
|
|
if (!tree->levels) {
|
|
OPENSSL_free(tree);
|
|
return 0;
|
|
}
|
|
|
|
OPENSSL_memset(tree->levels, 0, n * sizeof(X509_POLICY_LEVEL));
|
|
|
|
tree->nlevel = n;
|
|
|
|
level = tree->levels;
|
|
|
|
/* Root data: initialize to anyPolicy */
|
|
|
|
data = policy_data_new(NULL, OBJ_nid2obj(NID_any_policy), 0);
|
|
|
|
if (!data || !level_add_node(level, data, NULL, tree))
|
|
goto bad_tree;
|
|
|
|
for (i = n - 2; i >= 0; i--) {
|
|
level++;
|
|
x = sk_X509_value(certs, i);
|
|
cache = policy_cache_set(x);
|
|
X509_up_ref(x);
|
|
level->cert = x;
|
|
|
|
if (!cache->anyPolicy)
|
|
level->flags |= X509_V_FLAG_INHIBIT_ANY;
|
|
|
|
/* Determine inhibit any and inhibit map flags */
|
|
if (any_skip == 0) {
|
|
/*
|
|
* Any matching allowed if certificate is self issued and not the
|
|
* last in the chain.
|
|
*/
|
|
if (!(x->ex_flags & EXFLAG_SI) || (i == 0))
|
|
level->flags |= X509_V_FLAG_INHIBIT_ANY;
|
|
} else {
|
|
if (!(x->ex_flags & EXFLAG_SI))
|
|
any_skip--;
|
|
if ((cache->any_skip >= 0)
|
|
&& (cache->any_skip < any_skip))
|
|
any_skip = cache->any_skip;
|
|
}
|
|
|
|
if (map_skip == 0)
|
|
level->flags |= X509_V_FLAG_INHIBIT_MAP;
|
|
else {
|
|
if (!(x->ex_flags & EXFLAG_SI))
|
|
map_skip--;
|
|
if ((cache->map_skip >= 0)
|
|
&& (cache->map_skip < map_skip))
|
|
map_skip = cache->map_skip;
|
|
}
|
|
|
|
}
|
|
|
|
*ptree = tree;
|
|
|
|
if (explicit_policy)
|
|
return 1;
|
|
else
|
|
return 5;
|
|
|
|
bad_tree:
|
|
|
|
X509_policy_tree_free(tree);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int tree_link_matching_nodes(X509_POLICY_LEVEL *curr,
|
|
const X509_POLICY_DATA *data)
|
|
{
|
|
X509_POLICY_LEVEL *last = curr - 1;
|
|
X509_POLICY_NODE *node;
|
|
int matched = 0;
|
|
size_t i;
|
|
/* Iterate through all in nodes linking matches */
|
|
for (i = 0; i < sk_X509_POLICY_NODE_num(last->nodes); i++) {
|
|
node = sk_X509_POLICY_NODE_value(last->nodes, i);
|
|
if (policy_node_match(last, node, data->valid_policy)) {
|
|
if (!level_add_node(curr, data, node, NULL))
|
|
return 0;
|
|
matched = 1;
|
|
}
|
|
}
|
|
if (!matched && last->anyPolicy) {
|
|
if (!level_add_node(curr, data, last->anyPolicy, NULL))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* This corresponds to RFC3280 6.1.3(d)(1): link any data from
|
|
* CertificatePolicies onto matching parent or anyPolicy if no match.
|
|
*/
|
|
|
|
static int tree_link_nodes(X509_POLICY_LEVEL *curr,
|
|
const X509_POLICY_CACHE *cache)
|
|
{
|
|
size_t i;
|
|
X509_POLICY_DATA *data;
|
|
|
|
for (i = 0; i < sk_X509_POLICY_DATA_num(cache->data); i++) {
|
|
data = sk_X509_POLICY_DATA_value(cache->data, i);
|
|
/*
|
|
* If a node is mapped any it doesn't have a corresponding
|
|
* CertificatePolicies entry. However such an identical node would
|
|
* be created if anyPolicy matching is enabled because there would be
|
|
* no match with the parent valid_policy_set. So we create link
|
|
* because then it will have the mapping flags right and we can prune
|
|
* it later.
|
|
*/
|
|
#if 0
|
|
if ((data->flags & POLICY_DATA_FLAG_MAPPED_ANY)
|
|
&& !(curr->flags & X509_V_FLAG_INHIBIT_ANY))
|
|
continue;
|
|
#endif
|
|
/* Look for matching nodes in previous level */
|
|
if (!tree_link_matching_nodes(curr, data))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* This corresponds to RFC3280 6.1.3(d)(2): Create new data for any unmatched
|
|
* policies in the parent and link to anyPolicy.
|
|
*/
|
|
|
|
static int tree_add_unmatched(X509_POLICY_LEVEL *curr,
|
|
const X509_POLICY_CACHE *cache,
|
|
const ASN1_OBJECT *id,
|
|
X509_POLICY_NODE *node, X509_POLICY_TREE *tree)
|
|
{
|
|
X509_POLICY_DATA *data;
|
|
if (id == NULL)
|
|
id = node->data->valid_policy;
|
|
/*
|
|
* Create a new node with qualifiers from anyPolicy and id from unmatched
|
|
* node.
|
|
*/
|
|
data = policy_data_new(NULL, id, node_critical(node));
|
|
|
|
if (data == NULL)
|
|
return 0;
|
|
/* Curr may not have anyPolicy */
|
|
data->qualifier_set = cache->anyPolicy->qualifier_set;
|
|
data->flags |= POLICY_DATA_FLAG_SHARED_QUALIFIERS;
|
|
if (!level_add_node(curr, data, node, tree)) {
|
|
policy_data_free(data);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int tree_link_unmatched(X509_POLICY_LEVEL *curr,
|
|
const X509_POLICY_CACHE *cache,
|
|
X509_POLICY_NODE *node, X509_POLICY_TREE *tree)
|
|
{
|
|
const X509_POLICY_LEVEL *last = curr - 1;
|
|
size_t i;
|
|
|
|
if ((last->flags & X509_V_FLAG_INHIBIT_MAP)
|
|
|| !(node->data->flags & POLICY_DATA_FLAG_MAPPED)) {
|
|
/* If no policy mapping: matched if one child present */
|
|
if (node->nchild)
|
|
return 1;
|
|
if (!tree_add_unmatched(curr, cache, NULL, node, tree))
|
|
return 0;
|
|
/* Add it */
|
|
} else {
|
|
/* If mapping: matched if one child per expected policy set */
|
|
STACK_OF(ASN1_OBJECT) *expset = node->data->expected_policy_set;
|
|
if ((size_t)node->nchild == sk_ASN1_OBJECT_num(expset))
|
|
return 1;
|
|
/* Locate unmatched nodes */
|
|
for (i = 0; i < sk_ASN1_OBJECT_num(expset); i++) {
|
|
ASN1_OBJECT *oid = sk_ASN1_OBJECT_value(expset, i);
|
|
if (level_find_node(curr, node, oid))
|
|
continue;
|
|
if (!tree_add_unmatched(curr, cache, oid, node, tree))
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
static int tree_link_any(X509_POLICY_LEVEL *curr,
|
|
const X509_POLICY_CACHE *cache,
|
|
X509_POLICY_TREE *tree)
|
|
{
|
|
size_t i;
|
|
/*
|
|
* X509_POLICY_DATA *data;
|
|
*/
|
|
X509_POLICY_NODE *node;
|
|
X509_POLICY_LEVEL *last = curr - 1;
|
|
|
|
for (i = 0; i < sk_X509_POLICY_NODE_num(last->nodes); i++) {
|
|
node = sk_X509_POLICY_NODE_value(last->nodes, i);
|
|
|
|
if (!tree_link_unmatched(curr, cache, node, tree))
|
|
return 0;
|
|
|
|
#if 0
|
|
|
|
/*
|
|
* Skip any node with any children: we only want unmathced nodes.
|
|
* Note: need something better for policy mapping because each node
|
|
* may have multiple children
|
|
*/
|
|
if (node->nchild)
|
|
continue;
|
|
|
|
/*
|
|
* Create a new node with qualifiers from anyPolicy and id from
|
|
* unmatched node.
|
|
*/
|
|
data = policy_data_new(NULL, node->data->valid_policy,
|
|
node_critical(node));
|
|
|
|
if (data == NULL)
|
|
return 0;
|
|
/* Curr may not have anyPolicy */
|
|
data->qualifier_set = cache->anyPolicy->qualifier_set;
|
|
data->flags |= POLICY_DATA_FLAG_SHARED_QUALIFIERS;
|
|
if (!level_add_node(curr, data, node, tree)) {
|
|
policy_data_free(data);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
/* Finally add link to anyPolicy */
|
|
if (last->anyPolicy) {
|
|
if (!level_add_node(curr, cache->anyPolicy, last->anyPolicy, NULL))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Prune the tree: delete any child mapped child data on the current level
|
|
* then proceed up the tree deleting any data with no children. If we ever
|
|
* have no data on a level we can halt because the tree will be empty.
|
|
*/
|
|
|
|
static int tree_prune(X509_POLICY_TREE *tree, X509_POLICY_LEVEL *curr)
|
|
{
|
|
STACK_OF(X509_POLICY_NODE) *nodes;
|
|
X509_POLICY_NODE *node;
|
|
int i;
|
|
nodes = curr->nodes;
|
|
if (curr->flags & X509_V_FLAG_INHIBIT_MAP) {
|
|
for (i = sk_X509_POLICY_NODE_num(nodes) - 1; i >= 0; i--) {
|
|
node = sk_X509_POLICY_NODE_value(nodes, i);
|
|
/* Delete any mapped data: see RFC3280 XXXX */
|
|
if (node->data->flags & POLICY_DATA_FLAG_MAP_MASK) {
|
|
node->parent->nchild--;
|
|
OPENSSL_free(node);
|
|
(void)sk_X509_POLICY_NODE_delete(nodes, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (;;) {
|
|
--curr;
|
|
nodes = curr->nodes;
|
|
for (i = sk_X509_POLICY_NODE_num(nodes) - 1; i >= 0; i--) {
|
|
node = sk_X509_POLICY_NODE_value(nodes, i);
|
|
if (node->nchild == 0) {
|
|
node->parent->nchild--;
|
|
OPENSSL_free(node);
|
|
(void)sk_X509_POLICY_NODE_delete(nodes, i);
|
|
}
|
|
}
|
|
if (curr->anyPolicy && !curr->anyPolicy->nchild) {
|
|
if (curr->anyPolicy->parent)
|
|
curr->anyPolicy->parent->nchild--;
|
|
OPENSSL_free(curr->anyPolicy);
|
|
curr->anyPolicy = NULL;
|
|
}
|
|
if (curr == tree->levels) {
|
|
/* If we zapped anyPolicy at top then tree is empty */
|
|
if (!curr->anyPolicy)
|
|
return 2;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static int tree_add_auth_node(STACK_OF(X509_POLICY_NODE) **pnodes,
|
|
X509_POLICY_NODE *pcy)
|
|
{
|
|
if (!*pnodes) {
|
|
*pnodes = policy_node_cmp_new();
|
|
if (!*pnodes)
|
|
return 0;
|
|
} else if (sk_X509_POLICY_NODE_find(*pnodes, NULL, pcy))
|
|
return 1;
|
|
|
|
if (!sk_X509_POLICY_NODE_push(*pnodes, pcy))
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
/*
|
|
* Calculate the authority set based on policy tree. The 'pnodes' parameter
|
|
* is used as a store for the set of policy nodes used to calculate the user
|
|
* set. If the authority set is not anyPolicy then pnodes will just point to
|
|
* the authority set. If however the authority set is anyPolicy then the set
|
|
* of valid policies (other than anyPolicy) is store in pnodes. The return
|
|
* value of '2' is used in this case to indicate that pnodes should be freed.
|
|
*/
|
|
|
|
static int tree_calculate_authority_set(X509_POLICY_TREE *tree,
|
|
STACK_OF(X509_POLICY_NODE) **pnodes)
|
|
{
|
|
X509_POLICY_LEVEL *curr;
|
|
X509_POLICY_NODE *node, *anyptr;
|
|
STACK_OF(X509_POLICY_NODE) **addnodes;
|
|
int i;
|
|
size_t j;
|
|
curr = tree->levels + tree->nlevel - 1;
|
|
|
|
/* If last level contains anyPolicy set is anyPolicy */
|
|
if (curr->anyPolicy) {
|
|
if (!tree_add_auth_node(&tree->auth_policies, curr->anyPolicy))
|
|
return 0;
|
|
addnodes = pnodes;
|
|
} else
|
|
/* Add policies to authority set */
|
|
addnodes = &tree->auth_policies;
|
|
|
|
curr = tree->levels;
|
|
for (i = 1; i < tree->nlevel; i++) {
|
|
/*
|
|
* If no anyPolicy node on this this level it can't appear on lower
|
|
* levels so end search.
|
|
*/
|
|
if (!(anyptr = curr->anyPolicy))
|
|
break;
|
|
curr++;
|
|
for (j = 0; j < sk_X509_POLICY_NODE_num(curr->nodes); j++) {
|
|
node = sk_X509_POLICY_NODE_value(curr->nodes, j);
|
|
if ((node->parent == anyptr)
|
|
&& !tree_add_auth_node(addnodes, node))
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (addnodes == pnodes)
|
|
return 2;
|
|
|
|
*pnodes = tree->auth_policies;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int tree_calculate_user_set(X509_POLICY_TREE *tree,
|
|
STACK_OF(ASN1_OBJECT) *policy_oids,
|
|
STACK_OF(X509_POLICY_NODE) *auth_nodes)
|
|
{
|
|
size_t i;
|
|
X509_POLICY_NODE *node;
|
|
ASN1_OBJECT *oid;
|
|
|
|
X509_POLICY_NODE *anyPolicy;
|
|
X509_POLICY_DATA *extra;
|
|
|
|
/*
|
|
* Check if anyPolicy present in authority constrained policy set: this
|
|
* will happen if it is a leaf node.
|
|
*/
|
|
|
|
if (sk_ASN1_OBJECT_num(policy_oids) <= 0)
|
|
return 1;
|
|
|
|
anyPolicy = tree->levels[tree->nlevel - 1].anyPolicy;
|
|
|
|
for (i = 0; i < sk_ASN1_OBJECT_num(policy_oids); i++) {
|
|
oid = sk_ASN1_OBJECT_value(policy_oids, i);
|
|
if (OBJ_obj2nid(oid) == NID_any_policy) {
|
|
tree->flags |= POLICY_FLAG_ANY_POLICY;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < sk_ASN1_OBJECT_num(policy_oids); i++) {
|
|
oid = sk_ASN1_OBJECT_value(policy_oids, i);
|
|
node = tree_find_sk(auth_nodes, oid);
|
|
if (!node) {
|
|
if (!anyPolicy)
|
|
continue;
|
|
/*
|
|
* Create a new node with policy ID from user set and qualifiers
|
|
* from anyPolicy.
|
|
*/
|
|
extra = policy_data_new(NULL, oid, node_critical(anyPolicy));
|
|
if (!extra)
|
|
return 0;
|
|
extra->qualifier_set = anyPolicy->data->qualifier_set;
|
|
extra->flags = POLICY_DATA_FLAG_SHARED_QUALIFIERS
|
|
| POLICY_DATA_FLAG_EXTRA_NODE;
|
|
node = level_add_node(NULL, extra, anyPolicy->parent, tree);
|
|
}
|
|
if (!tree->user_policies) {
|
|
tree->user_policies = sk_X509_POLICY_NODE_new_null();
|
|
if (!tree->user_policies)
|
|
return 1;
|
|
}
|
|
if (!sk_X509_POLICY_NODE_push(tree->user_policies, node))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
|
|
}
|
|
|
|
static int tree_evaluate(X509_POLICY_TREE *tree)
|
|
{
|
|
int ret, i;
|
|
X509_POLICY_LEVEL *curr = tree->levels + 1;
|
|
const X509_POLICY_CACHE *cache;
|
|
|
|
for (i = 1; i < tree->nlevel; i++, curr++) {
|
|
cache = policy_cache_set(curr->cert);
|
|
if (!tree_link_nodes(curr, cache))
|
|
return 0;
|
|
|
|
if (!(curr->flags & X509_V_FLAG_INHIBIT_ANY)
|
|
&& !tree_link_any(curr, cache, tree))
|
|
return 0;
|
|
tree_print("before tree_prune()", tree, curr);
|
|
ret = tree_prune(tree, curr);
|
|
if (ret != 1)
|
|
return ret;
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
static void exnode_free(X509_POLICY_NODE *node)
|
|
{
|
|
if (node->data && (node->data->flags & POLICY_DATA_FLAG_EXTRA_NODE))
|
|
OPENSSL_free(node);
|
|
}
|
|
|
|
void X509_policy_tree_free(X509_POLICY_TREE *tree)
|
|
{
|
|
X509_POLICY_LEVEL *curr;
|
|
int i;
|
|
|
|
if (!tree)
|
|
return;
|
|
|
|
sk_X509_POLICY_NODE_free(tree->auth_policies);
|
|
sk_X509_POLICY_NODE_pop_free(tree->user_policies, exnode_free);
|
|
|
|
for (i = 0, curr = tree->levels; i < tree->nlevel; i++, curr++) {
|
|
if (curr->cert)
|
|
X509_free(curr->cert);
|
|
if (curr->nodes)
|
|
sk_X509_POLICY_NODE_pop_free(curr->nodes, policy_node_free);
|
|
if (curr->anyPolicy)
|
|
policy_node_free(curr->anyPolicy);
|
|
}
|
|
|
|
if (tree->extra_data)
|
|
sk_X509_POLICY_DATA_pop_free(tree->extra_data, policy_data_free);
|
|
|
|
OPENSSL_free(tree->levels);
|
|
OPENSSL_free(tree);
|
|
|
|
}
|
|
|
|
/*
|
|
* Application policy checking function. Return codes: 0 Internal Error. 1
|
|
* Successful. -1 One or more certificates contain invalid or inconsistent
|
|
* extensions -2 User constrained policy set empty and requireExplicit true.
|
|
*/
|
|
|
|
int X509_policy_check(X509_POLICY_TREE **ptree, int *pexplicit_policy,
|
|
STACK_OF(X509) *certs,
|
|
STACK_OF(ASN1_OBJECT) *policy_oids, unsigned int flags)
|
|
{
|
|
int ret;
|
|
X509_POLICY_TREE *tree = NULL;
|
|
STACK_OF(X509_POLICY_NODE) *nodes, *auth_nodes = NULL;
|
|
*ptree = NULL;
|
|
|
|
*pexplicit_policy = 0;
|
|
ret = tree_init(&tree, certs, flags);
|
|
|
|
switch (ret) {
|
|
|
|
/* Tree empty requireExplicit False: OK */
|
|
case 2:
|
|
return 1;
|
|
|
|
/* Some internal error */
|
|
case -1:
|
|
return -1;
|
|
|
|
/* Some internal error */
|
|
case 0:
|
|
return 0;
|
|
|
|
/* Tree empty requireExplicit True: Error */
|
|
|
|
case 6:
|
|
*pexplicit_policy = 1;
|
|
return -2;
|
|
|
|
/* Tree OK requireExplicit True: OK and continue */
|
|
case 5:
|
|
*pexplicit_policy = 1;
|
|
break;
|
|
|
|
/* Tree OK: continue */
|
|
|
|
case 1:
|
|
if (!tree)
|
|
/*
|
|
* tree_init() returns success and a null tree
|
|
* if it's just looking at a trust anchor.
|
|
* I'm not sure that returning success here is
|
|
* correct, but I'm sure that reporting this
|
|
* as an internal error which our caller
|
|
* interprets as a malloc failure is wrong.
|
|
*/
|
|
return 1;
|
|
break;
|
|
}
|
|
|
|
if (!tree)
|
|
goto error;
|
|
ret = tree_evaluate(tree);
|
|
|
|
tree_print("tree_evaluate()", tree, NULL);
|
|
|
|
if (ret <= 0)
|
|
goto error;
|
|
|
|
/* Return value 2 means tree empty */
|
|
if (ret == 2) {
|
|
X509_policy_tree_free(tree);
|
|
if (*pexplicit_policy)
|
|
return -2;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/* Tree is not empty: continue */
|
|
|
|
ret = tree_calculate_authority_set(tree, &auth_nodes);
|
|
|
|
if (!ret)
|
|
goto error;
|
|
|
|
if (!tree_calculate_user_set(tree, policy_oids, auth_nodes))
|
|
goto error;
|
|
|
|
if (ret == 2)
|
|
sk_X509_POLICY_NODE_free(auth_nodes);
|
|
|
|
if (tree)
|
|
*ptree = tree;
|
|
|
|
if (*pexplicit_policy) {
|
|
nodes = X509_policy_tree_get0_user_policies(tree);
|
|
if (sk_X509_POLICY_NODE_num(nodes) <= 0)
|
|
return -2;
|
|
}
|
|
|
|
return 1;
|
|
|
|
error:
|
|
|
|
X509_policy_tree_free(tree);
|
|
|
|
return 0;
|
|
|
|
}
|