mirror of
https://git.openldap.org/openldap/openldap.git
synced 2025-12-22 23:59:34 -05:00
ITS#6742 from Rich Megginson @ Red Hat
This commit is contained in:
parent
1bb648841b
commit
f697a3ffed
1 changed files with 157 additions and 64 deletions
|
|
@ -59,6 +59,7 @@
|
||||||
#include <nss/secerr.h>
|
#include <nss/secerr.h>
|
||||||
#include <nss/keyhi.h>
|
#include <nss/keyhi.h>
|
||||||
#include <nss/secmod.h>
|
#include <nss/secmod.h>
|
||||||
|
#include <nss/cert.h>
|
||||||
|
|
||||||
/* NSS 3.12.5 and later have NSS_InitContext */
|
/* NSS 3.12.5 and later have NSS_InitContext */
|
||||||
#if NSS_VMAJOR <= 3 && NSS_VMINOR <= 12 && NSS_VPATCH < 5
|
#if NSS_VMAJOR <= 3 && NSS_VMINOR <= 12 && NSS_VPATCH < 5
|
||||||
|
|
@ -895,29 +896,137 @@ tlsm_pin_prompt(PK11SlotInfo *slot, PRBool retry, void *arg)
|
||||||
return tlsm_get_pin( slot, retry, ctx );
|
return tlsm_get_pin( slot, retry, ctx );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SECStatus
|
||||||
|
tlsm_get_basic_constraint_extension( CERTCertificate *cert,
|
||||||
|
CERTBasicConstraints *cbcval )
|
||||||
|
{
|
||||||
|
SECItem encodedVal = { 0, NULL };
|
||||||
|
SECStatus rc;
|
||||||
|
|
||||||
|
rc = CERT_FindCertExtension( cert, SEC_OID_X509_BASIC_CONSTRAINTS,
|
||||||
|
&encodedVal);
|
||||||
|
if ( rc != SECSuccess ) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = CERT_DecodeBasicConstraintValue( cbcval, &encodedVal );
|
||||||
|
|
||||||
|
/* free the raw extension data */
|
||||||
|
PORT_Free( encodedVal.data );
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PRBool
|
||||||
|
tlsm_cert_is_self_issued( CERTCertificate *cert )
|
||||||
|
{
|
||||||
|
/* A cert is self-issued if its subject and issuer are equal and
|
||||||
|
* both are of non-zero length.
|
||||||
|
*/
|
||||||
|
PRBool is_self_issued = cert &&
|
||||||
|
(PRBool)SECITEM_ItemsAreEqual( &cert->derIssuer,
|
||||||
|
&cert->derSubject ) &&
|
||||||
|
cert->derSubject.len > 0;
|
||||||
|
return is_self_issued;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SECStatus
|
||||||
|
tlsm_verify_cert(CERTCertDBHandle *handle, CERTCertificate *cert, void *pinarg,
|
||||||
|
PRBool checksig, SECCertUsage certUsage, int errorToIgnore )
|
||||||
|
{
|
||||||
|
CERTVerifyLog verifylog;
|
||||||
|
SECStatus ret = SECSuccess;
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
/* the log captures information about every cert in the chain, so we can tell
|
||||||
|
which cert caused the problem and what the problem was */
|
||||||
|
memset( &verifylog, 0, sizeof( verifylog ) );
|
||||||
|
verifylog.arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE );
|
||||||
|
if ( verifylog.arena == NULL ) {
|
||||||
|
Debug( LDAP_DEBUG_ANY,
|
||||||
|
"TLS certificate verification: Out of memory for certificate verification logger\n",
|
||||||
|
0, 0, 0 );
|
||||||
|
return SECFailure;
|
||||||
|
}
|
||||||
|
ret = CERT_VerifyCertificate( handle, cert, checksig, certUsage, PR_Now(), pinarg, &verifylog,
|
||||||
|
NULL );
|
||||||
|
if ( ( name = cert->subjectName ) == NULL ) {
|
||||||
|
name = cert->nickname;
|
||||||
|
}
|
||||||
|
if ( verifylog.head == NULL ) {
|
||||||
|
/* it is possible for CERT_VerifyCertificate return with an error with no logging */
|
||||||
|
if ( ret != SECSuccess ) {
|
||||||
|
PRErrorCode errcode = PR_GetError();
|
||||||
|
Debug( LDAP_DEBUG_ANY,
|
||||||
|
"TLS: certificate [%s] is not valid - error %d:%s.\n",
|
||||||
|
name ? name : "(unknown)",
|
||||||
|
errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const char *name;
|
||||||
|
CERTVerifyLogNode *node;
|
||||||
|
|
||||||
|
ret = SECSuccess; /* reset */
|
||||||
|
node = verifylog.head;
|
||||||
|
while ( node ) {
|
||||||
|
if ( ( name = node->cert->subjectName ) == NULL ) {
|
||||||
|
name = node->cert->nickname;
|
||||||
|
}
|
||||||
|
if ( node->error ) {
|
||||||
|
/* NSS does not like CA certs that have the basic constraints extension
|
||||||
|
with the CA flag set to FALSE - openssl doesn't check if the cert
|
||||||
|
is self issued */
|
||||||
|
if ( ( node->error == SEC_ERROR_CA_CERT_INVALID ) &&
|
||||||
|
tlsm_cert_is_self_issued( node->cert ) ) {
|
||||||
|
CERTBasicConstraints basicConstraint;
|
||||||
|
SECStatus rv = tlsm_get_basic_constraint_extension( node->cert, &basicConstraint );
|
||||||
|
if ( ( rv == SECSuccess ) && ( basicConstraint.isCA == PR_FALSE ) ) {
|
||||||
|
Debug( LDAP_DEBUG_TRACE,
|
||||||
|
"TLS: certificate [%s] is not correct because it is a CA cert and the "
|
||||||
|
"BasicConstraint CA flag is set to FALSE - allowing for now, but "
|
||||||
|
"please fix your certs if possible\n", name, 0, 0 );
|
||||||
|
} else { /* does not have basicconstraint, or some other error */
|
||||||
|
ret = SECFailure;
|
||||||
|
Debug( LDAP_DEBUG_ANY,
|
||||||
|
"TLS: certificate [%s] is not valid - CA cert is not valid\n",
|
||||||
|
name, 0, 0 );
|
||||||
|
}
|
||||||
|
} else if ( errorToIgnore && ( node->error == errorToIgnore ) ) {
|
||||||
|
Debug( LDAP_DEBUG_ANY,
|
||||||
|
"TLS: Warning: ignoring error for certificate [%s] - error %ld:%s.\n",
|
||||||
|
name, node->error, PR_ErrorToString( node->error, PR_LANGUAGE_I_DEFAULT ) );
|
||||||
|
} else {
|
||||||
|
ret = SECFailure;
|
||||||
|
Debug( LDAP_DEBUG_ANY,
|
||||||
|
"TLS: certificate [%s] is not valid - error %ld:%s.\n",
|
||||||
|
name, node->error, PR_ErrorToString( node->error, PR_LANGUAGE_I_DEFAULT ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CERT_DestroyCertificate( node->cert );
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PORT_FreeArena( verifylog.arena, PR_FALSE );
|
||||||
|
|
||||||
|
if ( ret == SECSuccess ) {
|
||||||
|
Debug( LDAP_DEBUG_TRACE,
|
||||||
|
"TLS: certificate [%s] is valid\n", name, 0, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static SECStatus
|
static SECStatus
|
||||||
tlsm_auth_cert_handler(void *arg, PRFileDesc *fd,
|
tlsm_auth_cert_handler(void *arg, PRFileDesc *fd,
|
||||||
PRBool checksig, PRBool isServer)
|
PRBool checksig, PRBool isServer)
|
||||||
{
|
{
|
||||||
SECStatus ret = SSL_AuthCertificate(arg, fd, checksig, isServer);
|
SECCertUsage certUsage = isServer ? certUsageSSLClient : certUsageSSLServer;
|
||||||
|
SECStatus ret = SECSuccess;
|
||||||
|
|
||||||
if ( ret != SECSuccess ) {
|
ret = tlsm_verify_cert( (CERTCertDBHandle *)arg, SSL_PeerCertificate( fd ),
|
||||||
PRErrorCode errcode = PORT_GetError();
|
SSL_RevealPinArg( fd ),
|
||||||
/* we bypass NSS's hostname checks and do our own - tlsm_session_chkhost will handle it */
|
checksig, certUsage, 0 );
|
||||||
if ( errcode == SSL_ERROR_BAD_CERT_DOMAIN ) {
|
|
||||||
Debug( LDAP_DEBUG_TRACE,
|
|
||||||
"TLS certificate verification: defer\n",
|
|
||||||
0, 0, 0 );
|
|
||||||
} else {
|
|
||||||
Debug( LDAP_DEBUG_ANY,
|
|
||||||
"TLS certificate verification: Error, %d: %s\n",
|
|
||||||
errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ) ;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Debug( LDAP_DEBUG_TRACE,
|
|
||||||
"TLS certificate verification: ok\n",
|
|
||||||
0, 0, 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -1013,7 +1122,7 @@ tlsm_free_pem_objs( tlsm_ctx *ctx )
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca )
|
tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca, PRBool istrusted )
|
||||||
{
|
{
|
||||||
CK_SLOT_ID slotID;
|
CK_SLOT_ID slotID;
|
||||||
PK11SlotInfo *slot = NULL;
|
PK11SlotInfo *slot = NULL;
|
||||||
|
|
@ -1055,9 +1164,14 @@ tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca )
|
||||||
slotID = 0; /* CA and trust objects use slot 0 */
|
slotID = 0; /* CA and trust objects use slot 0 */
|
||||||
PR_snprintf( tmpslotname, sizeof(tmpslotname), TLSM_PEM_TOKEN_FMT, slotID );
|
PR_snprintf( tmpslotname, sizeof(tmpslotname), TLSM_PEM_TOKEN_FMT, slotID );
|
||||||
slotname = tmpslotname;
|
slotname = tmpslotname;
|
||||||
|
istrusted = PR_TRUE;
|
||||||
} else {
|
} else {
|
||||||
if ( ctx->tc_slotname == NULL ) { /* need new slot */
|
if ( ctx->tc_slotname == NULL ) { /* need new slot */
|
||||||
|
if ( istrusted ) {
|
||||||
|
slotID = 0;
|
||||||
|
} else {
|
||||||
slotID = ++tlsm_slot_count;
|
slotID = ++tlsm_slot_count;
|
||||||
|
}
|
||||||
ctx->tc_slotname = PR_smprintf( TLSM_PEM_TOKEN_FMT, slotID );
|
ctx->tc_slotname = PR_smprintf( TLSM_PEM_TOKEN_FMT, slotID );
|
||||||
}
|
}
|
||||||
slotname = ctx->tc_slotname;
|
slotname = ctx->tc_slotname;
|
||||||
|
|
@ -1065,9 +1179,17 @@ tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca )
|
||||||
if ( ( ptr = PL_strrchr( filename, sep ) ) ) {
|
if ( ( ptr = PL_strrchr( filename, sep ) ) ) {
|
||||||
PL_strfree( ctx->tc_certname );
|
PL_strfree( ctx->tc_certname );
|
||||||
++ptr;
|
++ptr;
|
||||||
|
if ( istrusted ) {
|
||||||
|
/* pemnss conflates trusted certs with CA certs - since there can
|
||||||
|
be more than one CA cert in a file (e.g. ca-bundle.crt) pemnss
|
||||||
|
numbers each trusted cert - in the case of a server cert, there will be
|
||||||
|
only one, so it will be number 0 */
|
||||||
|
ctx->tc_certname = PR_smprintf( "%s:%s - 0", slotname, ptr );
|
||||||
|
} else {
|
||||||
ctx->tc_certname = PR_smprintf( "%s:%s", slotname, ptr );
|
ctx->tc_certname = PR_smprintf( "%s:%s", slotname, ptr );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
slot = PK11_FindSlotByName( slotname );
|
slot = PK11_FindSlotByName( slotname );
|
||||||
|
|
||||||
|
|
@ -1083,7 +1205,7 @@ tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca )
|
||||||
PK11_SETATTRS( attrs, CKA_CLASS, &objClass, sizeof(objClass) ); attrs++;
|
PK11_SETATTRS( attrs, CKA_CLASS, &objClass, sizeof(objClass) ); attrs++;
|
||||||
PK11_SETATTRS( attrs, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL) ); attrs++;
|
PK11_SETATTRS( attrs, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL) ); attrs++;
|
||||||
PK11_SETATTRS( attrs, CKA_LABEL, (unsigned char *)filename, strlen(filename)+1 ); attrs++;
|
PK11_SETATTRS( attrs, CKA_LABEL, (unsigned char *)filename, strlen(filename)+1 ); attrs++;
|
||||||
if ( isca ) {
|
if ( istrusted ) {
|
||||||
PK11_SETATTRS( attrs, CKA_TRUST, &cktrue, sizeof(CK_BBOOL) ); attrs++;
|
PK11_SETATTRS( attrs, CKA_TRUST, &cktrue, sizeof(CK_BBOOL) ); attrs++;
|
||||||
} else {
|
} else {
|
||||||
PK11_SETATTRS( attrs, CKA_TRUST, &ckfalse, sizeof(CK_BBOOL) ); attrs++;
|
PK11_SETATTRS( attrs, CKA_TRUST, &ckfalse, sizeof(CK_BBOOL) ); attrs++;
|
||||||
|
|
@ -1200,7 +1322,7 @@ tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( cacertfile ) {
|
if ( cacertfile ) {
|
||||||
int rc = tlsm_add_cert_from_file( ctx, cacertfile, isca );
|
int rc = tlsm_add_cert_from_file( ctx, cacertfile, isca, PR_TRUE );
|
||||||
if ( rc ) {
|
if ( rc ) {
|
||||||
errcode = PR_GetError();
|
errcode = PR_GetError();
|
||||||
Debug( LDAP_DEBUG_ANY,
|
Debug( LDAP_DEBUG_ANY,
|
||||||
|
|
@ -1264,7 +1386,7 @@ tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
fullpath = PR_smprintf( "%s/%s", cacertdir, entry->name );
|
fullpath = PR_smprintf( "%s/%s", cacertdir, entry->name );
|
||||||
if ( !tlsm_add_cert_from_file( ctx, fullpath, isca ) ) {
|
if ( !tlsm_add_cert_from_file( ctx, fullpath, isca, PR_TRUE ) ) {
|
||||||
Debug( LDAP_DEBUG_TRACE,
|
Debug( LDAP_DEBUG_TRACE,
|
||||||
"TLS: loaded CA certificate file %s from CA certificate directory %s.\n",
|
"TLS: loaded CA certificate file %s from CA certificate directory %s.\n",
|
||||||
fullpath, cacertdir, 0 );
|
fullpath, cacertdir, 0 );
|
||||||
|
|
@ -1623,43 +1745,11 @@ tlsm_find_and_verify_cert_key(tlsm_ctx *ctx, PRFileDesc *fd, const char *certnam
|
||||||
} else {
|
} else {
|
||||||
checkSig = PR_FALSE;
|
checkSig = PR_FALSE;
|
||||||
}
|
}
|
||||||
status = CERT_VerifyCertificateNow( ctx->tc_certdb, cert,
|
/* may not have a CA cert - ok - ignore SEC_ERROR_UNKNOWN_ISSUER */
|
||||||
checkSig, certUsage,
|
status = tlsm_verify_cert( ctx->tc_certdb, cert, pin_arg,
|
||||||
pin_arg, NULL );
|
checkSig, certUsage, SEC_ERROR_UNKNOWN_ISSUER );
|
||||||
if ( status != SECSuccess ) {
|
if ( status == SECSuccess ) {
|
||||||
/* NSS doesn't like self-signed CA certs that are also used for
|
|
||||||
TLS/SSL server certs (such as generated by openssl req -x509)
|
|
||||||
CERT_VerifyCertificateNow returns SEC_ERROR_UNTRUSTED_ISSUER in that case
|
|
||||||
so, see if the cert and issuer are the same cert
|
|
||||||
*/
|
|
||||||
PRErrorCode errcode = PR_GetError();
|
|
||||||
|
|
||||||
if ( errcode == SEC_ERROR_UNTRUSTED_ISSUER ) {
|
|
||||||
CERTCertificate *issuer = CERT_FindCertIssuer( cert, PR_Now(), certUsageSSLServer );
|
|
||||||
if ( NULL == issuer ) {
|
|
||||||
/* no issuer - fail */
|
|
||||||
Debug( LDAP_DEBUG_ANY,
|
|
||||||
"TLS: error: the server certificate %s has no issuer - "
|
|
||||||
"please check this certificate for validity\n",
|
|
||||||
certname, 0, 0 );
|
|
||||||
} else if ( CERT_CompareCerts( cert, issuer ) ) {
|
|
||||||
/* self signed - warn and allow */
|
|
||||||
status = SECSuccess;
|
|
||||||
rc = 0;
|
rc = 0;
|
||||||
Debug( LDAP_DEBUG_ANY,
|
|
||||||
"TLS: warning: using self-signed server certificate %s\n",
|
|
||||||
certname, 0, 0 );
|
|
||||||
}
|
|
||||||
CERT_DestroyCertificate( issuer );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( status != SECSuccess ) {
|
|
||||||
Debug( LDAP_DEBUG_ANY,
|
|
||||||
"TLS: error: the certificate %s is not valid - error %d:%s\n",
|
|
||||||
certname, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rc = 0; /* success */
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PRErrorCode errcode = PR_GetError();
|
PRErrorCode errcode = PR_GetError();
|
||||||
|
|
@ -1949,7 +2039,7 @@ tlsm_deferred_ctx_init( void *arg )
|
||||||
/* otherwise, assume this is the name of a cert already in the db */
|
/* otherwise, assume this is the name of a cert already in the db */
|
||||||
if ( ctx->tc_using_pem ) {
|
if ( ctx->tc_using_pem ) {
|
||||||
/* this sets ctx->tc_certname to the correct value */
|
/* this sets ctx->tc_certname to the correct value */
|
||||||
int rc = tlsm_add_cert_from_file( ctx, lt->lt_certfile, PR_FALSE /* not a ca */ );
|
int rc = tlsm_add_cert_from_file( ctx, lt->lt_certfile, PR_FALSE, PR_TRUE );
|
||||||
if ( rc ) {
|
if ( rc ) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
@ -2277,8 +2367,8 @@ static char *
|
||||||
tlsm_session_errmsg( tls_session *sess, int rc, char *buf, size_t len )
|
tlsm_session_errmsg( tls_session *sess, int rc, char *buf, size_t len )
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
int prerror = PR_GetError();
|
||||||
|
|
||||||
rc = PR_GetError();
|
|
||||||
i = PR_GetErrorTextLength();
|
i = PR_GetErrorTextLength();
|
||||||
if ( i > len ) {
|
if ( i > len ) {
|
||||||
char *msg = LDAP_MALLOC( i+1 );
|
char *msg = LDAP_MALLOC( i+1 );
|
||||||
|
|
@ -2287,9 +2377,12 @@ tlsm_session_errmsg( tls_session *sess, int rc, char *buf, size_t len )
|
||||||
LDAP_FREE( msg );
|
LDAP_FREE( msg );
|
||||||
} else if ( i ) {
|
} else if ( i ) {
|
||||||
PR_GetErrorText( buf );
|
PR_GetErrorText( buf );
|
||||||
|
} else if ( prerror ) {
|
||||||
|
i = PR_snprintf( buf, len, "TLS error %d:%s",
|
||||||
|
prerror, PR_ErrorToString( prerror, PR_LANGUAGE_I_DEFAULT ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return i ? buf : NULL;
|
return ( i > 0 ) ? buf : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue