aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2015-11-05 06:23:41 +0000
committerRob Austein <sra@hactrn.net>2015-11-05 06:23:41 +0000
commitdf194d06e65bffa6e5ee5d094fe9ed90adaf46c8 (patch)
treef0636122675388ed2437202a8d7d3b8eabb05c09
parent83db6916f7bc00ca8783f705a71c0a3493bcc6bb (diff)
Add StatusCode.__repr__(). Add glorp to pass status set object
through to X509 verification callback handler so it can record status properly. svn path=/branches/tk705/; revision=6159
-rw-r--r--ext/POW.c159
1 files changed, 123 insertions, 36 deletions
diff --git a/ext/POW.c b/ext/POW.c
index 91d7d8fb..d6d9a80e 100644
--- a/ext/POW.c
+++ b/ext/POW.c
@@ -309,6 +309,7 @@ typedef struct {
PyObject_HEAD
X509_STORE_CTX *ctx;
x509_store_object *store;
+ PyObject *status;
} x509_store_ctx_object;
typedef struct {
@@ -1122,44 +1123,13 @@ whack_ec_key_to_namedCurve(EVP_PKEY *pkey)
* How to handle CMS cases: keep common command parser etc, but add an argument
* to that helper function which takes a function pointer to do the extended
* checking for this particular flavor of CMS object.
- *
- * Hmm. enum is the right type for C code, but is it right for
- * Python? Perhaps the Python view of this should be a set of
- * strings? Except there are two strings for each code: the name of
- * the code and the string translation of the code. Could stuff all
- * that into a dict(), of course. There's some kind of proxy dict we
- * can use to present a read-only dict to the user, that might be the
- * right approach here. Python implementation is getting a bit
- * complicated.
- *
- * Or the codes could be read-only first class objects in their own
- * right, with properties for numeric, name, and human readable
- * string. __int__() and __str__() methods. Python side doesn't
- * really need the numeric value if we go this way but including it is
- * harmless. For that matter, we could define all of this in a Python
- * module which we just import here, which would be a lot easier to
- * read...except then where do we get the enums for the C code? Feh.
- *
- * OK, so maybe the plan is to define a new type, which has .name,
- * .code, and .text attributes, all implemented via getsetters so
- * they're read-only. Internal object would contain the enum, a const
- * C char pointer for .name, and a PyObject for the .text attribute:
- * we want to allocate the .text once, because some of those values
- * come from OpenSSL; the others are trivial to convert to Python in
- * the getter function. We allocate an object for each code, and
- * stuff all of these into a sequence, which we don't need to expose,
- * it's just there to protect these objects from garbage collection
- * and to let us do a quick lookup from code -> object. Open question
- * whether we want to allocate a separate module to hold these,
- * probably not difficult. If we expose the sequence rather than
- * hiding it, we can create the separate module in Python code from
- * rpki/POW/__init__.py rather than having to do it in C.
*/
/*
* There's some ugly C preprocessor junk here. Sorry, but it's the
- * simplest way to keep all the definitions in a single place and expand
- * them into all the forms we need in both C and Python.
+ * simplest way to keep all the definitions in a single place and
+ * expand them into all the forms we need in both C and Python. We
+ * undef the macros once we're done with them to localize the mess.
*/
/*
@@ -1378,6 +1348,16 @@ status_code_object_str(status_code_object *self)
return self->name;
}
+static PyObject *
+status_code_object_repr(status_code_object *self)
+{
+ const char *text = PyString_AsString(self->text);
+ if (text == NULL)
+ return NULL;
+ return PyString_FromFormat("<%s object \"%s\" at %p>", self->ob_type->tp_name, text, self);
+}
+
+
static long
status_code_object_hash(status_code_object *self)
{
@@ -1444,7 +1424,7 @@ static PyTypeObject POW_StatusCode_Type = {
0, /* tp_getattr */
0, /* tp_setattr */
(cmpfunc) status_code_object_compare, /* tp_compare */
- 0, /* tp_repr */
+ (reprfunc) status_code_object_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
@@ -1554,6 +1534,99 @@ record_validation_status(PyObject *status, const validation_status_t code)
return result == 0;
}
+/*
+ * Validation callback code for use with x509_verify_cert().
+ *
+ * In theory record_validation_status() can throw an exception, but
+ * there's no direct way to handle that here, so we're depending on
+ * x509_store_object_verify() calling PyErr_Occurred() to check (which
+ * it has to do anyway to handle exceptions generated by Python
+ * callback handlers.
+ */
+static int
+validation_status_x509_verify_cert_cb(int ok, X509_STORE_CTX *ctx, PyObject *status)
+{
+ switch (ctx->error) {
+ case X509_V_OK:
+ return ok;
+
+ case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
+ /*
+ * Informational events, not really errors. ctx->check_issued()
+ * is called in many places where failure to find an issuer is not
+ * a failure for the calling function. Just leave these alone.
+ */
+ return ok;
+
+ case X509_V_ERR_CRL_HAS_EXPIRED:
+ /*
+ * This isn't really an error, exactly. CRLs don't really
+ * "expire". What OpenSSL really means by this error is just
+ * "it's now later than the issuer said it intended to publish a
+ * new CRL". Whether we treat this as an error or not is
+ * configurable, see the allow_stale_crl parameter.
+ *
+ * Deciding whether to allow stale CRLs is check_crl_1()'s job,
+ * not ours. By the time this callback occurs, we've already
+ * accepted the CRL; this callback is just notifying us that the
+ * object being checked is tainted by a stale CRL. So we mark the
+ * object as tainted and carry on.
+ */
+ record_validation_status(status, TAINTED_BY_STALE_CRL);
+ return 1;
+
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ /*
+ * This is another error that's only an error in the strange world
+ * of OpenSSL, but a more serious one. By default, OpenSSL
+ * expects all trust anchors to be self-signed. This is not a
+ * PKIX requirement, it's just an OpenSSL thing, but one violates
+ * it at one's peril, because the only way to convince OpenSSL to
+ * allow a non-self-signed trust anchor is to intercept this
+ * "error" in the verify callback handler.
+ *
+ * So this program supports non-self-signed trust anchors, but be
+ * warned that enabling this feature may cause this program's
+ * output not to work with other OpenSSL-based applications.
+ */
+#if 0
+ if (allow_non_self_signed_trust_anchor)
+ ok = 1;
+#else
+#warning Need some kind of global function to control configurable validation stuff like this.
+#endif
+ record_validation_status(status, TRUST_ANCHOR_NOT_SELF_SIGNED);
+ return ok;
+
+ /*
+ * Handle known OpenSSL verify errors except the ones we handle
+ * explicitly above.
+ */
+#define QV(x) \
+ case x: \
+ record_validation_status(status, STATUS_CODE_##x); \
+ return ok;
+ VALIDATION_STATUS_CODES_FROM_OPENSSL;
+#undef QV
+
+ default:
+ /*
+ * If you see this, it means that OpenSSL returned a status code
+ * we don't know about, so it's time to check the list of known
+ * OpenSSL verify errors to find out what's missing.
+ */
+ record_validation_status(status, UNKNOWN_OPENSSL_VERIFY_ERROR);
+ return ok;
+ }
+}
+
+/*
+ * Done with the sick macros.
+ */
+
+#undef VALIDATION_STATUS_CODES
+#undef VALIDATION_STATUS_CODES_FROM_OPENSSL
+
/*
@@ -4558,15 +4631,19 @@ x509_store_object_verify(x509_store_object *self, PyObject *args)
Py_XINCREF(x509);
Py_XINCREF(chain);
+ Py_XINCREF(status);
X509_STORE_CTX_set_cert(ctx->ctx, x509->x509);
X509_STORE_CTX_set_chain(ctx->ctx, stack);
+ ctx->status = status;
X509_STORE_CTX_set_verify_cb(ctx->ctx, x509_store_ctx_object_verify_cb);
ok = X509_verify_cert(ctx->ctx);
+ ctx->status = NULL;
X509_STORE_CTX_set_chain(ctx->ctx, NULL);
X509_STORE_CTX_set_cert(ctx->ctx, NULL);
+ Py_XDECREF(status);
Py_XDECREF(chain);
Py_XDECREF(x509);
@@ -4654,8 +4731,17 @@ x509_store_ctx_object_verify_cb(int ok, X509_STORE_CTX *ctx)
static char method_name[] = "verify_callback";
PyObject *self = X509_STORE_CTX_get_ex_data(ctx, x509_store_ctx_ex_data_idx);
PyObject *result = NULL;
+ PyObject *status = NULL;
+
+ if (self == NULL)
+ return ok;
+
+ status = ((x509_store_ctx_object *) self)->status;
+
+ if (status != NULL && status != Py_None)
+ ok = validation_status_x509_verify_cert_cb(ok, ctx, status);
- if (self == NULL || !PyObject_HasAttrString(self, method_name))
+ if (!PyObject_HasAttrString(self, method_name))
return ok;
if ((result = PyObject_CallMethod(self, method_name, "i", ok)) == NULL)
@@ -4678,6 +4764,7 @@ x509_store_ctx_object_new(PyTypeObject *type, GCC_UNUSED PyObject *args, GCC_UNU
self->ctx = NULL;
self->store = NULL;
+ self->status = NULL;
return (PyObject *) self;
error: