Make tls1_check_chain return a set of flags indicating checks passed

by a certificate chain. Add additional tests to handle client
certificates: checks for matching certificate type and issuer name
comparison.

Print out results of checks for each candidate chain tested in
s_server/s_client.
This commit is contained in:
Dr. Stephen Henson 2012-07-27 13:39:23 +00:00
parent ec4a50b3c3
commit 6dbb6219e7
8 changed files with 280 additions and 39 deletions

View File

@ -4,6 +4,12 @@
Changes between 1.0.1 and 1.1.0 [xx XXX xxxx] Changes between 1.0.1 and 1.1.0 [xx XXX xxxx]
*) Make tls1_check_chain return a set of flags indicating checks passed
by a certificate chain. Add additional tests to handle client
certificates: checks for matching certificate type and issuer name
comparison.
[Steve Henson]
*) If an attempt is made to use a signature algorithm not in the peer *) If an attempt is made to use a signature algorithm not in the peer
preference list abort the handshake. If client has no suitable preference list abort the handshake. If client has no suitable
signature algorithms in response to a certificate request do not signature algorithms in response to a certificate request do not

View File

@ -1134,12 +1134,45 @@ struct ssl_excert_st
struct ssl_excert_st *next, *prev; struct ssl_excert_st *next, *prev;
}; };
struct chain_flags
{
int flag;
const char *name;
};
struct chain_flags chain_flags_list[] =
{
{CERT_PKEY_VALID, "Overall Validity"},
{CERT_PKEY_SIGN, "Sign with EE key"},
{CERT_PKEY_EE_SIGNATURE, "EE signature"},
{CERT_PKEY_CA_SIGNATURE, "CA signature"},
{CERT_PKEY_EE_PARAM, "EE key parameters"},
{CERT_PKEY_CA_PARAM, "CA key parameters"},
{CERT_PKEY_EXPLICIT_SIGN, "Explicity sign with EE key"},
{CERT_PKEY_ISSUER_NAME, "Issuer Name"},
{CERT_PKEY_CERT_TYPE, "Certificate Type"},
{0, NULL}
};
static void print_chain_flags(BIO *out, int flags)
{
struct chain_flags *ctmp = chain_flags_list;
while(ctmp->name)
{
BIO_printf(out, "\t%s: %s\n", ctmp->name,
flags & ctmp->flag ? "OK" : "NOT OK");
ctmp++;
}
}
/* Very basic selection callback: just use any certificate chain /* Very basic selection callback: just use any certificate chain
* reported as valid. More sophisticated could prioritise according * reported as valid. More sophisticated could prioritise according
* to local policy. * to local policy.
*/ */
static int set_cert_cb(SSL *ssl, void *arg) static int set_cert_cb(SSL *ssl, void *arg)
{ {
int i, rv;
SSL_EXCERT *exc = arg; SSL_EXCERT *exc = arg;
SSL_certs_clear(ssl); SSL_certs_clear(ssl);
@ -1151,10 +1184,20 @@ static int set_cert_cb(SSL *ssl, void *arg)
*/ */
while (exc->next) while (exc->next)
exc = exc->next; exc = exc->next;
i = 0;
while(exc) while(exc)
{ {
if (SSL_check_chain(ssl, exc->cert, exc->key, exc->chain)) i++;
rv = SSL_check_chain(ssl, exc->cert, exc->key, exc->chain);
BIO_printf(bio_err, "Checking cert chain %d:\nSubject: ", i);
X509_NAME_print_ex(bio_err, X509_get_subject_name(exc->cert), 0,
XN_FLAG_ONELINE);
BIO_puts(bio_err, "\n");
print_chain_flags(bio_err, rv);
if (rv & CERT_PKEY_VALID)
{ {
SSL_use_certificate(ssl, exc->cert); SSL_use_certificate(ssl, exc->cert);
SSL_use_PrivateKey(ssl, exc->key); SSL_use_PrivateKey(ssl, exc->key);

View File

@ -1963,6 +1963,12 @@ int ssl3_get_certificate_request(SSL *s)
SSLerr(SSL_F_SSL3_GET_CERTIFICATE_REQUEST,SSL_R_DATA_LENGTH_TOO_LONG); SSLerr(SSL_F_SSL3_GET_CERTIFICATE_REQUEST,SSL_R_DATA_LENGTH_TOO_LONG);
goto err; goto err;
} }
/* Clear certificate digests and validity flags */
for (i = 0; i < SSL_PKEY_NUM; i++)
{
s->cert->pkeys[i].digest = NULL;
s->cert->pkeys[i].valid_flags = 0;
}
if ((llen & 1) || !tls1_process_sigalgs(s, p, llen)) if ((llen & 1) || !tls1_process_sigalgs(s, p, llen))
{ {
ssl3_send_alert(s,SSL3_AL_FATAL,SSL_AD_DECODE_ERROR); ssl3_send_alert(s,SSL3_AL_FATAL,SSL_AD_DECODE_ERROR);

View File

@ -662,6 +662,26 @@ struct ssl_session_st
/* Con't include root CA in chain */ /* Con't include root CA in chain */
#define SSL_BUILD_CHAIN_FLAG_NO_ROOT 0x2 #define SSL_BUILD_CHAIN_FLAG_NO_ROOT 0x2
/* Flags returned by SSL_check_chain */
/* Certificate can be used with this session */
#define CERT_PKEY_VALID 0x1
/* Certificate can also be used for signing */
#define CERT_PKEY_SIGN 0x2
/* EE certificate signing algorithm OK */
#define CERT_PKEY_EE_SIGNATURE 0x10
/* CA signature algorithms OK */
#define CERT_PKEY_CA_SIGNATURE 0x20
/* EE certificate parameters OK */
#define CERT_PKEY_EE_PARAM 0x40
/* CA certificate parameters OK */
#define CERT_PKEY_CA_PARAM 0x80
/* Signing explicitly allowed as opposed to SHA1 fallback */
#define CERT_PKEY_EXPLICIT_SIGN 0x100
/* Client CA issuer names match (always set for server cert) */
#define CERT_PKEY_ISSUER_NAME 0x200
/* Cert type matches client types (always set for server cert) */
#define CERT_PKEY_CERT_TYPE 0x400
/* Note: SSL[_CTX]_set_{options,mode} use |= op on the previous value, /* Note: SSL[_CTX]_set_{options,mode} use |= op on the previous value,
* they cannot be used to clear bits. */ * they cannot be used to clear bits. */

View File

@ -467,7 +467,8 @@ void ssl_cert_clear_certs(CERT *c)
if (cpk->authz != NULL) if (cpk->authz != NULL)
OPENSSL_free(cpk->authz); OPENSSL_free(cpk->authz);
#endif #endif
cpk->valid_flags = 0; /* Clear all flags apart from explicit sign */
cpk->valid_flags &= CERT_PKEY_EXPLICIT_SIGN;
} }
} }

View File

@ -2086,21 +2086,21 @@ void ssl_set_cert_masks(CERT *c, const SSL_CIPHER *cipher)
have_ecdh_tmp=(c->ecdh_tmp || c->ecdh_tmp_cb || c->ecdh_tmp_auto); have_ecdh_tmp=(c->ecdh_tmp || c->ecdh_tmp_cb || c->ecdh_tmp_auto);
#endif #endif
cpk= &(c->pkeys[SSL_PKEY_RSA_ENC]); cpk= &(c->pkeys[SSL_PKEY_RSA_ENC]);
rsa_enc= cpk->valid_flags; rsa_enc= cpk->valid_flags & CERT_PKEY_VALID;
rsa_enc_export=(rsa_enc && EVP_PKEY_size(cpk->privatekey)*8 <= kl); rsa_enc_export=(rsa_enc && EVP_PKEY_size(cpk->privatekey)*8 <= kl);
cpk= &(c->pkeys[SSL_PKEY_RSA_SIGN]); cpk= &(c->pkeys[SSL_PKEY_RSA_SIGN]);
rsa_sign= (cpk->valid_flags & CERT_PKEY_SIGN); rsa_sign= cpk->valid_flags & CERT_PKEY_SIGN;
cpk= &(c->pkeys[SSL_PKEY_DSA_SIGN]); cpk= &(c->pkeys[SSL_PKEY_DSA_SIGN]);
dsa_sign= (cpk->valid_flags & CERT_PKEY_SIGN); dsa_sign= cpk->valid_flags & CERT_PKEY_SIGN;
cpk= &(c->pkeys[SSL_PKEY_DH_RSA]); cpk= &(c->pkeys[SSL_PKEY_DH_RSA]);
dh_rsa= cpk->valid_flags; dh_rsa= cpk->valid_flags & CERT_PKEY_VALID;
dh_rsa_export=(dh_rsa && EVP_PKEY_size(cpk->privatekey)*8 <= kl); dh_rsa_export=(dh_rsa && EVP_PKEY_size(cpk->privatekey)*8 <= kl);
cpk= &(c->pkeys[SSL_PKEY_DH_DSA]); cpk= &(c->pkeys[SSL_PKEY_DH_DSA]);
/* FIX THIS EAY EAY EAY */ /* FIX THIS EAY EAY EAY */
dh_dsa= cpk->valid_flags; dh_dsa= cpk->valid_flags & CERT_PKEY_VALID;
dh_dsa_export=(dh_dsa && EVP_PKEY_size(cpk->privatekey)*8 <= kl); dh_dsa_export=(dh_dsa && EVP_PKEY_size(cpk->privatekey)*8 <= kl);
cpk= &(c->pkeys[SSL_PKEY_ECC]); cpk= &(c->pkeys[SSL_PKEY_ECC]);
have_ecc_cert= cpk->valid_flags; have_ecc_cert= cpk->valid_flags & CERT_PKEY_VALID;
mask_k=0; mask_k=0;
mask_a=0; mask_a=0;
emask_k=0; emask_k=0;

View File

@ -466,14 +466,6 @@
#define NAMED_CURVE_TYPE 3 #define NAMED_CURVE_TYPE 3
#endif /* OPENSSL_NO_EC */ #endif /* OPENSSL_NO_EC */
/* Values for valid_flags in CERT_PKEY structure */
/* Certificate inconsistent with session, key missing etc */
#define CERT_PKEY_INVALID 0x0
/* Certificate can be used with this sesstion */
#define CERT_PKEY_VALID 0x1
/* Certificate can also be used for signing */
#define CERT_PKEY_SIGN 0x2
typedef struct cert_pkey_st typedef struct cert_pkey_st
{ {
X509 *x509; X509 *x509;

View File

@ -1451,6 +1451,7 @@ static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char
unsigned short len; unsigned short len;
unsigned char *data = *p; unsigned char *data = *p;
int renegotiate_seen = 0; int renegotiate_seen = 0;
size_t i;
s->servername_done = 0; s->servername_done = 0;
s->tlsext_status_type = -1; s->tlsext_status_type = -1;
@ -1474,6 +1475,12 @@ static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char
OPENSSL_free(s->cert->shared_sigalgs); OPENSSL_free(s->cert->shared_sigalgs);
s->cert->shared_sigalgs = NULL; s->cert->shared_sigalgs = NULL;
} }
/* Clear certificate digests and validity flags */
for (i = 0; i < SSL_PKEY_NUM; i++)
{
s->cert->pkeys[i].digest = NULL;
s->cert->pkeys[i].valid_flags = 0;
}
if (data >= (d+n-2)) if (data >= (d+n-2))
goto ri_check; goto ri_check;
@ -1961,7 +1968,6 @@ static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char
* in the case of a session resumption. */ * in the case of a session resumption. */
if (!s->hit) if (!s->hit)
{ {
size_t i;
if (s->s3->tlsext_authz_client_types != NULL) if (s->s3->tlsext_authz_client_types != NULL)
OPENSSL_free(s->s3->tlsext_authz_client_types); OPENSSL_free(s->s3->tlsext_authz_client_types);
s->s3->tlsext_authz_client_types = s->s3->tlsext_authz_client_types =
@ -3180,11 +3186,6 @@ int tls1_process_sigalgs(SSL *s, const unsigned char *data, int dsize)
if (!c) if (!c)
return 0; return 0;
c->pkeys[SSL_PKEY_DSA_SIGN].digest = NULL;
c->pkeys[SSL_PKEY_RSA_SIGN].digest = NULL;
c->pkeys[SSL_PKEY_RSA_ENC].digest = NULL;
c->pkeys[SSL_PKEY_ECC].digest = NULL;
c->peer_sigalgs = OPENSSL_malloc(dsize); c->peer_sigalgs = OPENSSL_malloc(dsize);
if (!c->peer_sigalgs) if (!c->peer_sigalgs)
return 0; return 0;
@ -3201,8 +3202,12 @@ int tls1_process_sigalgs(SSL *s, const unsigned char *data, int dsize)
{ {
md = tls12_get_hash(sigptr->rhash); md = tls12_get_hash(sigptr->rhash);
c->pkeys[idx].digest = md; c->pkeys[idx].digest = md;
c->pkeys[idx].valid_flags = CERT_PKEY_EXPLICIT_SIGN;
if (idx == SSL_PKEY_RSA_SIGN) if (idx == SSL_PKEY_RSA_SIGN)
{
c->pkeys[SSL_PKEY_RSA_ENC].valid_flags = CERT_PKEY_EXPLICIT_SIGN;
c->pkeys[SSL_PKEY_RSA_ENC].digest = md; c->pkeys[SSL_PKEY_RSA_ENC].digest = md;
}
} }
} }
@ -3546,40 +3551,76 @@ static int tls1_check_sig_alg(CERT *c, X509 *x, int default_nid)
return 1; return 1;
return 0; return 0;
} }
/* Check to see if a certificate issuer name matches list of CA names */
static int ssl_check_ca_name(STACK_OF(X509_NAME) *names, X509 *x)
{
X509_NAME *nm;
int i;
nm = X509_get_issuer_name(x);
for (i = 0; i < sk_X509_NAME_num(names); i++)
{
if(!X509_NAME_cmp(nm, sk_X509_NAME_value(names, i)))
return 1;
}
return 0;
}
/* Check certificate chain is consistent with TLS extensions and is /* Check certificate chain is consistent with TLS extensions and is
* usable by server. * usable by server. This servers two purposes: it allows users to
* check chains before passing them to the server and it allows the
* server to check chains before attempting to use them.
*/ */
/* Flags which need to be set for a certificate when stict mode not set */
#define CERT_PKEY_VALID_FLAGS \
(CERT_PKEY_EE_SIGNATURE|CERT_PKEY_EE_PARAM)
/* Strict mode flags */
#define CERT_PKEY_STRICT_FLAGS \
(CERT_PKEY_VALID_FLAGS|CERT_PKEY_CA_SIGNATURE|CERT_PKEY_CA_PARAM \
| CERT_PKEY_ISSUER_NAME|CERT_PKEY_CERT_TYPE)
int tls1_check_chain(SSL *s, X509 *x, EVP_PKEY *pk, STACK_OF(X509) *chain, int tls1_check_chain(SSL *s, X509 *x, EVP_PKEY *pk, STACK_OF(X509) *chain,
int idx) int idx)
{ {
int i; int i;
int rv = CERT_PKEY_INVALID; int rv = 0;
int check_flags = 0, strict_mode;
CERT_PKEY *cpk = NULL; CERT_PKEY *cpk = NULL;
CERT *c = s->cert; CERT *c = s->cert;
/* idx != -1 means checking server chains */
if (idx != -1) if (idx != -1)
{ {
cpk = c->pkeys + idx; cpk = c->pkeys + idx;
x = cpk->x509; x = cpk->x509;
pk = cpk->privatekey; pk = cpk->privatekey;
chain = cpk->chain; chain = cpk->chain;
strict_mode = c->cert_flags & SSL_CERT_FLAG_TLS_STRICT;
/* If no cert or key, forget it */ /* If no cert or key, forget it */
if (!x || !pk) if (!x || !pk)
goto end; goto end;
} }
else else
{ {
if (!x || !pk)
goto end;
idx = ssl_cert_type(x, pk); idx = ssl_cert_type(x, pk);
if (idx == -1) if (idx == -1)
goto end; goto end;
cpk = c->pkeys + idx;
if (c->cert_flags & SSL_CERT_FLAG_TLS_STRICT)
check_flags = CERT_PKEY_STRICT_FLAGS;
else
check_flags = CERT_PKEY_VALID_FLAGS;
strict_mode = 1;
} }
/* Check all signature algorithms are consistent with /* Check all signature algorithms are consistent with
* signature algorithms extension if TLS 1.2 or later * signature algorithms extension if TLS 1.2 or later
* and strict mode. * and strict mode.
*/ */
if (TLS1_get_version(s) >= TLS1_2_VERSION if (TLS1_get_version(s) >= TLS1_2_VERSION && strict_mode)
&& c->cert_flags & SSL_CERT_FLAG_TLS_STRICT)
{ {
int default_nid; int default_nid;
unsigned char rsign = 0; unsigned char rsign = 0;
@ -3627,39 +3668,171 @@ int tls1_check_chain(SSL *s, X509 *x, EVP_PKEY *pk, STACK_OF(X509) *chain,
break; break;
} }
if (j == c->conf_sigalgslen) if (j == c->conf_sigalgslen)
goto end; {
if (check_flags)
goto skip_sigs;
else
goto end;
}
} }
/* Check signature algorithm of each cert in chain */ /* Check signature algorithm of each cert in chain */
if (!tls1_check_sig_alg(c, x, default_nid)) if (!tls1_check_sig_alg(c, x, default_nid))
goto end; {
if (!check_flags) goto end;
}
else
rv |= CERT_PKEY_EE_SIGNATURE;
rv |= CERT_PKEY_CA_SIGNATURE;
for (i = 0; i < sk_X509_num(chain); i++) for (i = 0; i < sk_X509_num(chain); i++)
{ {
if (!tls1_check_sig_alg(c, sk_X509_value(chain, i), if (!tls1_check_sig_alg(c, sk_X509_value(chain, i),
default_nid)) default_nid))
goto end; {
if (check_flags)
{
rv &= ~CERT_PKEY_CA_SIGNATURE;
break;
}
else
goto end;
}
} }
} }
/* Else not TLS 1.2, so mark EE and CA signing algorithms OK */
/* Check cert parameters are consistent */ else if(check_flags)
if (!tls1_check_cert_param(s, x)) rv |= CERT_PKEY_EE_SIGNATURE|CERT_PKEY_CA_SIGNATURE;
skip_sigs:
/* Check cert parameters are consistent: server certs only */
if (!s->server || tls1_check_cert_param(s, x))
rv |= CERT_PKEY_EE_PARAM;
else if (!check_flags)
goto end; goto end;
if (!s->server)
rv |= CERT_PKEY_CA_PARAM;
/* In strict mode check rest of chain too */ /* In strict mode check rest of chain too */
if (c->cert_flags & SSL_CERT_FLAG_TLS_STRICT) else if (strict_mode)
{ {
rv |= CERT_PKEY_CA_PARAM;
for (i = 0; i < sk_X509_num(chain); i++) for (i = 0; i < sk_X509_num(chain); i++)
{ {
if (!tls1_check_cert_param(s, sk_X509_value(chain, i))) if (!tls1_check_cert_param(s, sk_X509_value(chain, i)))
goto end; {
if (check_flags)
{
rv &= ~CERT_PKEY_CA_PARAM;
break;
}
else
goto end;
}
} }
} }
rv = CERT_PKEY_VALID; if (!s->server && strict_mode)
{
STACK_OF(X509_NAME) *ca_dn;
int check_type = 0;
switch (pk->type)
{
case EVP_PKEY_RSA:
check_type = TLS_CT_RSA_SIGN;
break;
case EVP_PKEY_DSA:
check_type = TLS_CT_DSS_SIGN;
break;
case EVP_PKEY_EC:
check_type = TLS_CT_ECDSA_SIGN;
break;
case EVP_PKEY_DH:
case EVP_PKEY_DHX:
{
int cert_type = X509_certificate_type(x, pk);
if (cert_type & EVP_PKS_RSA)
check_type = TLS_CT_RSA_FIXED_DH;
if (cert_type & EVP_PKS_DSA)
check_type = TLS_CT_DSS_FIXED_DH;
}
}
if (check_type)
{
const unsigned char *ctypes;
int ctypelen;
if (c->ctypes)
{
ctypes = c->ctypes;
ctypelen = (int)c->ctype_num;
}
else
{
ctypes = (unsigned char *)s->s3->tmp.ctype;
ctypelen = s->s3->tmp.ctype_num;
}
for (i = 0; i < ctypelen; i++)
{
if (ctypes[i] == check_type)
{
rv |= CERT_PKEY_CERT_TYPE;
break;
}
}
if (!(rv & CERT_PKEY_CERT_TYPE) && !check_flags)
goto end;
}
else
rv |= CERT_PKEY_CERT_TYPE;
ca_dn = s->s3->tmp.ca_names;
if (!sk_X509_NAME_num(ca_dn))
rv |= CERT_PKEY_ISSUER_NAME;
if (!(rv & CERT_PKEY_ISSUER_NAME))
{
if (ssl_check_ca_name(ca_dn, x))
rv |= CERT_PKEY_ISSUER_NAME;
}
if (!(rv & CERT_PKEY_ISSUER_NAME))
{
for (i = 0; i < sk_X509_num(chain); i++)
{
X509 *xtmp = sk_X509_value(chain, i);
if (ssl_check_ca_name(ca_dn, xtmp))
{
rv |= CERT_PKEY_ISSUER_NAME;
break;
}
}
}
if (!check_flags && !(rv & CERT_PKEY_ISSUER_NAME))
goto end;
}
else
rv |= CERT_PKEY_ISSUER_NAME|CERT_PKEY_CERT_TYPE;
if (!check_flags || (rv & check_flags) == check_flags)
rv |= CERT_PKEY_VALID;
end: end:
if (cpk)
if (TLS1_get_version(s) >= TLS1_2_VERSION)
{ {
if (rv && cpk->digest) if (cpk->valid_flags & CERT_PKEY_EXPLICIT_SIGN)
rv |= CERT_PKEY_EXPLICIT_SIGN|CERT_PKEY_SIGN;
else if (cpk->digest)
rv |= CERT_PKEY_SIGN; rv |= CERT_PKEY_SIGN;
cpk->valid_flags = rv; }
else
rv |= CERT_PKEY_SIGN|CERT_PKEY_EXPLICIT_SIGN;
/* When checking a CERT_PKEY structure all flags are irrelevant
* if the chain is invalid.
*/
if (!check_flags)
{
if (rv & CERT_PKEY_VALID)
cpk->valid_flags = rv;
else
cpk->valid_flags = 0;
} }
return rv; return rv;
} }