First cut at adding support for extension handling in pyOpenSSL.
diff --git a/OpenSSL/crypto/x509.c b/OpenSSL/crypto/x509.c
index 08dc567..273f557 100644
--- a/OpenSSL/crypto/x509.c
+++ b/OpenSSL/crypto/x509.c
@@ -13,6 +13,7 @@
#include <Python.h>
#define crypto_MODULE
#include "crypto.h"
+#include "x509ext.h"
/*
* X.509 is a standard for digital certificates. See e.g. the OpenSSL homepage
@@ -300,7 +301,7 @@
py_pkey = crypto_PKey_New(pkey, 1);
if (py_pkey != NULL) {
- py_pkey->only_public = 1;
+ py_pkey->only_public = 1;
}
return (PyObject *)py_pkey;
}
@@ -685,6 +686,148 @@
return Py_None;
}
+static char crypto_X509_get_extension_count_doc[] = "\n\
+Get the number of extensions on the certificate.\n\
+\n\
+Arguments: self - X509 object\n\
+Returns: Number of extensions as a Python integer\n\
+";
+
+static PyObject *
+crypto_X509_get_extension_count(crypto_X509Obj *self, PyObject *args)
+{
+ if (!PyArg_ParseTuple(args, ":get_extension_count"))
+ return NULL;
+
+ return PyInt_FromLong((long)X509_get_ext_count(self->x509));
+}
+
+static char crypto_X509_get_extension_doc[] = "\n\
+Get a specific extension of the certificate.\n\
+\n\
+Arguments: self - X509 object\n\
+Returns: An X509 extension object\n\
+";
+
+static PyObject *
+crypto_X509_get_extension(crypto_X509Obj *self, PyObject *args)
+{
+ crypto_X509ExtensionObj *extobj;
+ int loc;
+ X509_EXTENSION *ext;
+
+ if (!PyArg_ParseTuple(args, "i:get_extension", &loc))
+ return NULL;
+
+ /* will return NULL if loc is outside the range of extensions,
+ not registered as an error*/
+ ext = X509_get_ext(self->x509, loc);
+ if (!ext) {
+ return NULL; /* Should be reported as an IndexError ? */
+ /*exception_from_error_queue();*/
+ }
+
+ extobj = PyObject_New(crypto_X509ExtensionObj, &crypto_X509Extension_Type);
+ extobj->x509_extension = X509_EXTENSION_dup(ext);
+
+ return extobj;
+}
+
+/* Copied from openssl/crypto/x509v3/v3_utl.c */
+
+static void str_free(void *str)
+{
+ OPENSSL_free(str);
+}
+static int sk_strcmp(const char * const *a, const char * const *b)
+{
+ return strcmp(*a, *b);
+}
+static int append_ia5(STACK **sk, ASN1_IA5STRING *value)
+{
+ char *tmp;
+ /* First some sanity checks */
+ if(value->type != V_ASN1_IA5STRING) return 1;
+ if(!value->data || !value->length) return 1;
+ if(!*sk) *sk = sk_new(sk_strcmp);
+ if(!*sk) return 0;
+ /* Don't add duplicates */
+ if(sk_find(*sk, (char *)value->data) != -1) return 1;
+ tmp = BUF_strdup((char *)value->data);
+ if(!tmp || !sk_push(*sk, tmp)) {
+ sk_pop_free(*sk, str_free);
+ *sk = NULL;
+ return 0;
+ }
+ return 1;
+}
+
+/* -------------------------------------------------*/
+/* !!! This only works for ASN1_IA5STRING values !!!*/
+/* -------------------------------------------------*/
+static STACK *get_ia5_san_value(GENERAL_NAMES *gens, int type)
+{
+ STACK *ret = NULL;
+ GENERAL_NAME *gen;
+ int i;
+
+ for(i = 0; i < sk_GENERAL_NAME_num(gens); i++)
+ {
+ gen = sk_GENERAL_NAME_value(gens, i);
+ if(gen->type != type) continue;
+ if(!append_ia5(&ret, gen->d.ia5)) return NULL;
+ }
+ return ret;
+}
+
+static char crypto_X509_get_subjectaltname_of_type_doc[] = "\n\
+Get a list of the values of some subjectaltname extensions\n\
+Presently the DNS,EMAIL and URI types are supported.\n\
+\n\
+Arguments: self - X509 object\n\
+ type - one of DNS,EMAIL or URI\n\
+Returns: A list of values\n\
+";
+
+static PyObject *
+crypto_X509_get_subjectaltname_of_type(crypto_X509Obj *self, PyObject *args)
+{
+ GENERAL_NAMES *gens;
+ STACK *ret;
+ char *s;
+ char *type;
+ PyObject *list;
+ int san_type;
+
+ if (!PyArg_ParseTuple(args, "s:get_subjectaltname_of_type", &type))
+ return NULL;
+
+ list = PyList_New(0);
+ gens = X509_get_ext_d2i(self->x509, NID_subject_alt_name, NULL, NULL);
+ if (gens == NULL) {
+ return list;
+ }
+
+ /* These are the ones that are labeled/stored as ASN1_IA5STRINGs */
+ if (strcmp(type,"DNS") == 0) san_type = GEN_DNS;
+ else if(strcmp(type,"EMAIL") == 0) san_type = GEN_EMAIL;
+ else if(strcmp(type,"URI") == 0) san_type = GEN_URI;
+ else {
+ PyErr_SetString(PyExc_AttributeError, type);
+ return NULL;
+ }
+
+ ret = get_ia5_san_value(gens, san_type);
+ if (ret != NULL) {
+ for ( ; s = sk_pop(ret) ; ){
+ PyList_Append(list, PyString_FromString(s));
+ }
+ return list;
+ }
+ else {
+ return list;
+ }
+}
/*
* ADD_METHOD(name) expands to a correct PyMethodDef declaration
* { 'name', (PyCFunction)crypto_X509_name, METH_VARARGS }
@@ -715,6 +858,9 @@
ADD_METHOD(subject_name_hash),
ADD_METHOD(digest),
ADD_METHOD(add_extensions),
+ ADD_METHOD(get_extension),
+ ADD_METHOD(get_extension_count),
+ ADD_METHOD(get_subjectaltname_of_type),
{ NULL, NULL }
};
#undef ADD_METHOD
diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py
index f2831c7..8e96b92 100644
--- a/OpenSSL/test/test_crypto.py
+++ b/OpenSSL/test/test_crypto.py
@@ -947,6 +947,26 @@
"""
pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM
+ extpem = """
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAkegAwIBAgIJAJHFjlcCgnQzMA0GCSqGSIb3DQEBBQUAMEcxCzAJBgNV
+BAYTAlNFMRUwEwYDVQQIEwxXZXN0ZXJib3R0b20xEjAQBgNVBAoTCUNhdGFsb2dp
+eDENMAsGA1UEAxMEUm9vdDAeFw0wODA0MjIxNDQ1MzhaFw0wOTA0MjIxNDQ1Mzha
+MFQxCzAJBgNVBAYTAlNFMQswCQYDVQQIEwJXQjEUMBIGA1UEChMLT3Blbk1ldGFk
+aXIxIjAgBgNVBAMTGW5vZGUxLm9tMi5vcGVubWV0YWRpci5vcmcwgZ8wDQYJKoZI
+hvcNAQEBBQADgY0AMIGJAoGBAPIcQMrwbk2nESF/0JKibj9i1x95XYAOwP+LarwT
+Op4EQbdlI9SY+uqYqlERhF19w7CS+S6oyqx0DRZSk4Y9dZ9j9/xgm2u/f136YS1u
+zgYFPvfUs6PqYLPSM8Bw+SjJ+7+2+TN+Tkiof9WP1cMjodQwOmdsiRbR0/J7+b1B
+hec1AgMBAAGjgcQwgcEwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT
+TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFIdHsBcMVVMbAO7j6NCj
+03HgLnHaMB8GA1UdIwQYMBaAFL2h9Bf9Mre4vTdOiHTGAt7BRY/8MEYGA1UdEQQ/
+MD2CDSouZXhhbXBsZS5vcmeCESoub20yLmV4bWFwbGUuY29thwSC7wgKgRNvbTJA
+b3Blbm1ldGFkaXIub3JnMA0GCSqGSIb3DQEBBQUAA4GBALd7WdXkp2KvZ7/PuWZA
+MPlIxyjS+Ly11+BNE0xGQRp9Wz+2lABtpgNqssvU156+HkKd02rGheb2tj7MX9hG
+uZzbwDAZzJPjzDQDD7d3cWsrVcfIdqVU7epHqIadnOF+X0ghJ39pAm6VVadnSXCt
+WpOdIpB8KksUTCzV591Nr1wd
+-----END CERTIFICATE-----
+ """
def signable(self):
"""
Create and return a new L{X509}.
@@ -1199,6 +1219,29 @@
b("A8:EB:07:F8:53:25:0A:F2:56:05:C5:A5:C4:C4:C7:15"))
+ def test_extension_count(self):
+ """
+ L{X509.get_extension_count} returns the number of extensions that are
+ present in the certificate
+ """
+ cert = load_certificate(FILETYPE_PEM, self.extpem)
+ self.assertEqual(cert.get_extension_count(),5)
+
+
+ def test_subjectaltname_of_type(self):
+ """
+ L{X509.get_subjectaltname_of_type} returns a list of strings
+ representing the values of the subjectAltName extensions of the
+ specified type
+ """
+ cert = load_certificate(FILETYPE_PEM, self.extpem)
+ domains = cert.get_subjectaltname_of_type("DNS")
+ domains.sort()
+ self.assertEqual(domains,['*.example.org', '*.om2.exmaple.com'])
+ self.assertEqual(cert.get_subjectaltname_of_type("EMAIL"),['om2@openmetadir.org'])
+ self.assertEqual(cert.get_subjectaltname_of_type("URI"),[])
+
+
def test_invalid_digest_algorithm(self):
"""
L{X509.digest} raises L{ValueError} if called with an unrecognized hash