blob: 91d000afbcd7c32bbc4bb34a3e4bc046fc9ea4e0 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** SSL Context wrapper
*/
#include "tcn.h"
#include "ssl_private.h"
#ifdef HAVE_SSL_CONF_CMD
#define SSL_THROW_RETURN -9
#include "apr_file_io.h"
/**
* Define the Path Checking modes
*/
#define PCM_EXISTS 0x1
#define PCM_ISREG 0x2
#define PCM_ISDIR 0x4
#define PCM_ISNONZERO 0x8
#define FLAGS_CHECK_FILE (PCM_EXISTS|PCM_ISREG|PCM_ISNONZERO)
#define FLAGS_CHECK_DIR (PCM_EXISTS|PCM_ISDIR)
static int path_check(apr_pool_t *p, const char *path, int pcm)
{
apr_finfo_t finfo;
if (path == NULL)
return 1;
if (pcm & PCM_EXISTS &&
apr_stat(&finfo, path, APR_FINFO_TYPE|APR_FINFO_SIZE, p) != 0)
return 1;
if (pcm & PCM_ISREG && finfo.filetype != APR_REG)
return 1;
if (pcm & PCM_ISDIR && finfo.filetype != APR_DIR)
return 1;
if (pcm & PCM_ISNONZERO && finfo.size <= 0)
return 1;
return 0;
}
static int check_dir(apr_pool_t *p, const char *dir)
{
return path_check(p, dir, FLAGS_CHECK_DIR);
}
static int check_file(apr_pool_t *p, const char *file)
{
return path_check(p, file, FLAGS_CHECK_FILE);
}
static apr_status_t ssl_ctx_config_cleanup(void *data)
{
tcn_ssl_conf_ctxt_t *c = (tcn_ssl_conf_ctxt_t *)data;
if (c != NULL && c->cctx != NULL) {
SSL_CONF_CTX_free(c->cctx);
c->cctx = NULL;
c->pool = NULL;
}
return APR_SUCCESS;
}
/* Initialize an SSL_CONF context */
TCN_IMPLEMENT_CALL(jlong, SSLConf, make)(TCN_STDARGS, jlong pool,
jint flags)
{
apr_pool_t *p = J2P(pool, apr_pool_t *);
tcn_ssl_conf_ctxt_t *c = NULL;
SSL_CONF_CTX *cctx;
unsigned long ec;
UNREFERENCED(o);
SSL_ERR_clear();
cctx = SSL_CONF_CTX_new();
ec = SSL_ERR_get();
if (!cctx || ec != 0) {
if (ec != 0) {
char err[TCN_OPENSSL_ERROR_STRING_LENGTH];
ERR_error_string_n(ec, err, TCN_OPENSSL_ERROR_STRING_LENGTH);
tcn_Throw(e, "Could not create SSL_CONF context (%s)", err);
} else {
tcn_Throw(e, "Could not create SSL_CONF context");
}
return 0;
}
SSL_CONF_CTX_set_flags(cctx, flags);
if ((c = apr_pcalloc(p, sizeof(tcn_ssl_conf_ctxt_t))) == NULL) {
tcn_ThrowAPRException(e, apr_get_os_error());
return 0;
}
c->cctx = cctx;
c->pool = p;
/*
* Some Tomcat Native specific settings are also set via this representation
* of the SSL_CONF_CTX. This process is a little bit hacky. The expected
* call sequence is:
* - SSLConf.make() - create SSL_CONF_CTX and the associated Tomcat Native
* object
* - SSLConf.check() - MUST be called for each Tomcat specific setting that
* needs to be configured. May be called for OpenSSL settings in which
* case the setting will be validated.
* - SSLConf.assign() - this actually *applies* the Tomcat Native specific
* configuration to Tomcat Native as well as linking the SSL_CONF_CTX
* object with the SSL_CTX object.
* - SSLConf.apply() - called for each OpenSSL setting. Any Tomcat specific
* settings used here will be ignored.
* - SSLConf.finish() - MUST be called to complete the OpenSSL setting
* process.
*/
/* Initialise Tomcat Native specific OCSP defaults */
c->no_ocsp_check = OCSP_NO_CHECK_DEFAULT;
c->ocsp_soft_fail = OCSP_SOFT_FAIL_DEFAULT;
c->ocsp_timeout = OCSP_TIMEOUT_DEFAULT;
c->ocsp_verify_flags = OCSP_VERIFY_FLAGS_DEFAULT;
/*
* Let us cleanup the SSL_CONF context when the pool is destroyed
*/
apr_pool_cleanup_register(p, (const void *)c,
ssl_ctx_config_cleanup,
apr_pool_cleanup_null);
return P2J(c);
}
/* Destroy an SSL_CONF context */
TCN_IMPLEMENT_CALL(void, SSLConf, free)(TCN_STDARGS, jlong cctx)
{
tcn_ssl_conf_ctxt_t *c = J2P(cctx, tcn_ssl_conf_ctxt_t *);
UNREFERENCED_STDARGS;
TCN_ASSERT(c != 0);
apr_pool_cleanup_run(c->pool, c, ssl_ctx_config_cleanup);
}
/* Check a command for an SSL_CONF context */
TCN_IMPLEMENT_CALL(jint, SSLConf, check)(TCN_STDARGS, jlong cctx,
jstring cmd, jstring value)
{
tcn_ssl_conf_ctxt_t *c = J2P(cctx, tcn_ssl_conf_ctxt_t *);
int rc = 1;
int value_type;
unsigned long ec;
TCN_ALLOC_CSTRING(cmd);
TCN_ALLOC_CSTRING(value);
UNREFERENCED(o);
TCN_ASSERT(c != 0);
TCN_ASSERT(c->cctx != 0);
if (!J2S(cmd)) {
tcn_Throw(e, "Can not check null SSL_CONF command");
rc = SSL_THROW_RETURN;
goto cleanup;
}
/*
* Although this is the check method, this sets the Tomcat specific
* settings.
*/
if (!strcmp(J2S(cmd), "NO_OCSP_CHECK")) {
if (!strcasecmp(J2S(value), "false"))
c->no_ocsp_check = 0;
else
c->no_ocsp_check = 1;
rc = 1;
goto cleanup;
}
if (!strcmp(J2S(cmd), "OCSP_SOFT_FAIL")) {
if (!strcasecmp(J2S(value), "false"))
c->ocsp_soft_fail = 0;
else
c->ocsp_soft_fail = 1;
rc = 1;
goto cleanup;
}
if (!strcmp(J2S(cmd), "OCSP_TIMEOUT")) {
int i;
errno = 0;
i = (int) strtol(J2S(value), NULL, 10);
if (!errno) {
// Tomcat configures timeout is millisecond. APR uses microseconds.
c->ocsp_timeout = i * 1000;
}
rc = 1;
goto cleanup;
}
if (!strcmp(J2S(cmd), "OCSP_VERIFY_FLAGS")) {
int i;
errno = 0;
i = (int) strtol(J2S(value), NULL, 10);
if (!errno) {
c->ocsp_verify_flags = i;
}
rc = 1;
goto cleanup;
}
SSL_ERR_clear();
value_type = SSL_CONF_cmd_value_type(c->cctx, J2S(cmd));
ec = SSL_ERR_get();
if (ec != 0) {
char err[TCN_OPENSSL_ERROR_STRING_LENGTH];
ERR_error_string_n(ec, err, TCN_OPENSSL_ERROR_STRING_LENGTH);
tcn_Throw(e, "Could not determine SSL_CONF command type for '%s' (%s)", J2S(cmd), err);
rc = SSL_THROW_RETURN;
goto cleanup;
}
if (value_type == SSL_CONF_TYPE_UNKNOWN) {
tcn_Throw(e, "Invalid SSL_CONF command '%s', type unknown", J2S(cmd));
rc = SSL_THROW_RETURN;
goto cleanup;
}
if (value_type == SSL_CONF_TYPE_FILE) {
if (!J2S(value)) {
tcn_Throw(e, "SSL_CONF command '%s' needs a non-empty file argument", J2S(cmd));
rc = SSL_THROW_RETURN;
goto cleanup;
}
if (check_file(c->pool, J2S(value))) {
tcn_Throw(e, "SSL_CONF command '%s' file '%s' does not exist or is empty", J2S(cmd), J2S(value));
rc = SSL_THROW_RETURN;
goto cleanup;
}
}
else if (value_type == SSL_CONF_TYPE_DIR) {
if (!J2S(value)) {
tcn_Throw(e, "SSL_CONF command '%s' needs a non-empty directory argument", J2S(cmd));
rc = SSL_THROW_RETURN;
goto cleanup;
}
if (check_dir(c->pool, J2S(value))) {
tcn_Throw(e, "SSL_CONF command '%s' directory '%s' does not exist", J2S(cmd), J2S(value));
rc = SSL_THROW_RETURN;
goto cleanup;
}
}
cleanup:
TCN_FREE_CSTRING(cmd);
TCN_FREE_CSTRING(value);
return rc;
}
/* Assign an SSL_CTX to an SSL_CONF context */
TCN_IMPLEMENT_CALL(void, SSLConf, assign)(TCN_STDARGS, jlong cctx,
jlong ctx)
{
tcn_ssl_conf_ctxt_t *c = J2P(cctx, tcn_ssl_conf_ctxt_t *);
tcn_ssl_ctxt_t *sc = J2P(ctx, tcn_ssl_ctxt_t *);
UNREFERENCED_STDARGS;
TCN_ASSERT(c != 0);
TCN_ASSERT(c->cctx != 0);
TCN_ASSERT(sc != 0);
// sc->ctx == 0 is allowed!
SSL_CONF_CTX_set_ssl_ctx(c->cctx, sc->ctx);
sc->no_ocsp_check = c->no_ocsp_check;
sc->ocsp_soft_fail = c->ocsp_soft_fail;
sc->ocsp_timeout = c->ocsp_timeout;
sc->ocsp_verify_flags = c->ocsp_verify_flags;
}
/* Apply a command to an SSL_CONF context */
TCN_IMPLEMENT_CALL(jint, SSLConf, apply)(TCN_STDARGS, jlong cctx,
jstring cmd, jstring value)
{
tcn_ssl_conf_ctxt_t *c = J2P(cctx, tcn_ssl_conf_ctxt_t *);
int rc;
unsigned long ec;
#ifndef HAVE_EXPORT_CIPHERS
size_t len;
#endif
char *buf = NULL;
TCN_ALLOC_CSTRING(cmd);
TCN_ALLOC_CSTRING(value);
UNREFERENCED(o);
TCN_ASSERT(c != 0);
TCN_ASSERT(c->cctx != 0);
if (!J2S(cmd)) {
tcn_Throw(e, "Can not apply null SSL_CONF command");
rc = SSL_THROW_RETURN;
goto cleanup;
}
#ifndef HAVE_EXPORT_CIPHERS
if (!strcmp(J2S(cmd), "CipherString")) {
/*
* Always disable NULL and export ciphers,
* no matter what was given in the config.
*/
len = strlen(J2S(value)) + strlen(SSL_CIPHERS_ALWAYS_DISABLED) + 1;
buf = malloc(len * sizeof(char));
if (buf == NULL) {
tcn_Throw(e, "Could not allocate memory to adjust cipher string");
rc = SSL_THROW_RETURN;
goto cleanup;
}
memcpy(buf, SSL_CIPHERS_ALWAYS_DISABLED, strlen(SSL_CIPHERS_ALWAYS_DISABLED));
memcpy(buf + strlen(SSL_CIPHERS_ALWAYS_DISABLED), J2S(value), strlen(J2S(value)));
buf[len - 1] = '\0';
}
#endif
if (!strcmp(J2S(cmd), "NO_OCSP_CHECK")) {
/*
* Skip as this is a Tomcat specific setting that will have been set
* when check() was called.
*/
rc = 1;
goto cleanup;
}
if (!strcmp(J2S(cmd), "OCSP_SOFT_FAIL")) {
/*
* Skip as this is a Tomcat specific setting that will have been set
* when check() was called.
*/
rc = 1;
goto cleanup;
}
if (!strcmp(J2S(cmd), "OCSP_TIMEOUT")) {
/*
* Skip as this is a Tomcat specific setting that will have been set
* when check() was called.
*/
rc = 1;
goto cleanup;
}
if (!strcmp(J2S(cmd), "OCSP_VERIFY_FLAGS")) {
/*
* Skip as this is a Tomcat specific setting that will have been set
* when check() was called.
*/
rc = 1;
goto cleanup;
}
SSL_ERR_clear();
rc = SSL_CONF_cmd(c->cctx, J2S(cmd), buf != NULL ? buf : J2S(value));
ec = SSL_ERR_get();
if (rc <= 0 || ec != 0) {
if (ec != 0) {
char err[TCN_OPENSSL_ERROR_STRING_LENGTH];
ERR_error_string_n(ec, err, TCN_OPENSSL_ERROR_STRING_LENGTH);
tcn_Throw(e, "Could not apply SSL_CONF command '%s' with value '%s' (%s)", J2S(cmd), buf != NULL ? buf : J2S(value), err);
} else {
tcn_Throw(e, "Could not apply SSL_CONF command '%s' with value '%s'", J2S(cmd), buf != NULL ? buf : J2S(value));
}
rc = SSL_THROW_RETURN;
goto cleanup;
}
cleanup:
#ifndef HAVE_EXPORT_CIPHERS
if (buf != NULL) {
free(buf);
}
#endif
TCN_FREE_CSTRING(cmd);
TCN_FREE_CSTRING(value);
return rc;
}
/* Finish an SSL_CONF context */
TCN_IMPLEMENT_CALL(jint, SSLConf, finish)(TCN_STDARGS, jlong cctx)
{
tcn_ssl_conf_ctxt_t *c = J2P(cctx, tcn_ssl_conf_ctxt_t *);
int rc;
unsigned long ec;
UNREFERENCED_STDARGS;
TCN_ASSERT(c != 0);
TCN_ASSERT(c->cctx != 0);
SSL_ERR_clear();
rc = SSL_CONF_CTX_finish(c->cctx);
ec = SSL_ERR_get();
if (rc <= 0 || ec != 0) {
if (ec != 0) {
char err[TCN_OPENSSL_ERROR_STRING_LENGTH];
ERR_error_string_n(ec, err, TCN_OPENSSL_ERROR_STRING_LENGTH);
tcn_Throw(e, "Could not finish SSL_CONF commands (%s)", err);
} else {
tcn_Throw(e, "Could not finish SSL_CONF commands");
}
return SSL_THROW_RETURN;
}
return rc;
}
#else /* HAVE_SSL_CONF_CMD */
/* SSL_CONF is not supported.
* Create empty stubs.
*/
TCN_IMPLEMENT_CALL(jlong, SSLConf, make)(TCN_STDARGS, jlong pool,
jint flags)
{
UNREFERENCED_STDARGS;
UNREFERENCED(pool);
UNREFERENCED(flags);
return 0;
}
TCN_IMPLEMENT_CALL(void, SSLConf, free)(TCN_STDARGS, jlong cctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(cctx);
}
TCN_IMPLEMENT_CALL(jint, SSLConf, check)(TCN_STDARGS, jlong cctx,
jstring cmd, jstring value)
{
UNREFERENCED_STDARGS;
UNREFERENCED(cctx);
UNREFERENCED(cmd);
UNREFERENCED(value);
return APR_ENOTIMPL;
}
TCN_IMPLEMENT_CALL(void, SSLConf, assign)(TCN_STDARGS, jlong cctx,
jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(cctx);
UNREFERENCED(ctx);
}
TCN_IMPLEMENT_CALL(jint, SSLConf, apply)(TCN_STDARGS, jlong cctx,
jstring cmd, jstring value)
{
UNREFERENCED_STDARGS;
UNREFERENCED(cctx);
UNREFERENCED(cmd);
UNREFERENCED(value);
return APR_ENOTIMPL;
}
TCN_IMPLEMENT_CALL(jint, SSLConf, finish)(TCN_STDARGS, jlong cctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(cctx);
return APR_ENOTIMPL;
}
#endif /* HAVE_SSL_CONF_CMD */