Merge crypto.sign and crypto.verify addition
diff --git a/ChangeLog b/ChangeLog
index aef5db6..b2debc9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2010-02-27 James Yonan <james@openvpn.net>
+
+ * src/crypto/crypto.c: Added crypto.sign and crypto.verify methods
+ that wrap EVP_Sign and EVP_Verify function families, using code
+ derived from Dave Cridland's PyOpenSSL branch.
+
+ * test/test_crypto.py: Added unit tests for crypto.sign and
+ crypto.verify.
+
2010-01-27 Jean-Paul Calderone <exarkun@twistedmatrix.com>
* src/ssl/connection.c, src/util.h: Apply patch from Sandro Tosi to
diff --git a/doc/pyOpenSSL.tex b/doc/pyOpenSSL.tex
index a41b575..564e6bb 100644
--- a/doc/pyOpenSSL.tex
+++ b/doc/pyOpenSSL.tex
@@ -285,6 +285,24 @@
See also the man page for the C function \function{PKCS12_parse}.
\end{funcdesc}
+\begin{funcdesc}{sign}{key, data, digest}
+Sign a data string using the given key and message digest.
+
+\var{key} is a \code{PKey} instance. \var{data} is a \code{str} instance.
+\var{digest} is a \code{str} naming a supported message digest type, for example
+\code{``sha1''}.
+\end{funcdesc}
+
+\begin{funcdesc}{verify}{certificate, signature, data, digest}
+Verify the signature for a data string.
+
+\var{certificate} is a \code{X509} instance corresponding to the private key
+which generated the signature. \var{signature} is a \var{str} instance giving
+the signature itself. \var{data} is a \var{str} instance giving the data to
+which the signature applies. \var{digest} is a \var{str} instance naming the
+message digest type of the signature, for example \code{``sha1''}.
+\end{funcdesc}
+
\subsubsection{X509 objects \label{openssl-x509}}
X509 objects have the following methods:
diff --git a/src/crypto/crypto.c b/src/crypto/crypto.c
index cc97887..d9f1a4e 100644
--- a/src/crypto/crypto.c
+++ b/src/crypto/crypto.c
@@ -2,6 +2,7 @@
* crypto.c
*
* Copyright (C) AB Strakt 2001, All rights reserved
+ * Copyright (C) Keyphrene 2004, All rights reserved
* Copyright (C) Jean-Paul Calderone 2008-2009, All rights reserved
*
* Main file of crypto sub module.
@@ -590,6 +591,101 @@
return NULL;
}
+static char crypto_sign_doc[] = "\n\
+Sign data with a digest\n\
+\n\
+@param pkey: Pkey to sign with\n\
+@param data: data to be signed\n\
+@param digest: message digest to use\n\
+@return: signature\n\
+";
+
+static PyObject *
+crypto_sign(PyObject *spam, PyObject *args) {
+ PyObject *buffer;
+ crypto_PKeyObj *pkey;
+ char *data = NULL;
+ char *digest_name;
+ int err;
+ unsigned int sig_len;
+ const EVP_MD *digest;
+ EVP_MD_CTX md_ctx;
+ unsigned char sig_buf[512];
+
+ if (!PyArg_ParseTuple(args, "O!ss:sign", &crypto_PKey_Type,
+ &pkey, &data, &digest_name)) {
+ return NULL;
+ }
+
+ if ((digest = EVP_get_digestbyname(digest_name)) == NULL) {
+ PyErr_SetString(PyExc_ValueError, "No such digest method");
+ return NULL;
+ }
+
+ EVP_SignInit(&md_ctx, digest);
+ EVP_SignUpdate(&md_ctx, data, strlen(data));
+ sig_len = sizeof(sig_buf);
+ err = EVP_SignFinal(&md_ctx, sig_buf, &sig_len, pkey->pkey);
+
+ if (err != 1) {
+ exception_from_error_queue(crypto_Error);
+ return NULL;
+ }
+
+ buffer = PyString_FromStringAndSize((char*)sig_buf, sig_len);
+ return buffer;
+}
+
+static char crypto_verify_doc[] = "\n\
+Verify a signature\n\
+\n\
+@param cert: signing certificate (X509 object)\n\
+@param signature: signature returned by sign function\n\
+@param data: data to be verified\n\
+@param digest: message digest to use\n\
+@return: None if the signature is correct, raise exception otherwise\n\
+";
+
+static PyObject *
+crypto_verify(PyObject *spam, PyObject *args) {
+ crypto_X509Obj *cert;
+ unsigned char *signature;
+ int sig_len;
+ char *data, *digest_name;
+ int err;
+ const EVP_MD *digest;
+ EVP_MD_CTX md_ctx;
+ EVP_PKEY *pkey;
+
+ if (!PyArg_ParseTuple(args, "O!t#ss:verify", &crypto_X509_Type, &cert, &signature, &sig_len,
+ &data, &digest_name)) {
+ return NULL;
+ }
+
+ if ((digest = EVP_get_digestbyname(digest_name)) == NULL){
+ PyErr_SetString(PyExc_ValueError, "No such digest method");
+ return NULL;
+ }
+
+ pkey = X509_get_pubkey(cert->x509);
+ if (pkey == NULL) {
+ PyErr_SetString(PyExc_ValueError, "No public key");
+ return NULL;
+ }
+
+ EVP_VerifyInit(&md_ctx, digest);
+ EVP_VerifyUpdate(&md_ctx, data, strlen((char*)data));
+ err = EVP_VerifyFinal(&md_ctx, signature, sig_len, pkey);
+ EVP_PKEY_free(pkey);
+
+ if (err != 1) {
+ exception_from_error_queue(crypto_Error);
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
/* Methods in the OpenSSL.crypto module (i.e. none) */
static PyMethodDef crypto_methods[] = {
@@ -603,6 +699,8 @@
{ "load_crl", (PyCFunction)crypto_load_crl, METH_VARARGS, crypto_load_crl_doc },
{ "load_pkcs7_data", (PyCFunction)crypto_load_pkcs7_data, METH_VARARGS, crypto_load_pkcs7_data_doc },
{ "load_pkcs12", (PyCFunction)crypto_load_pkcs12, METH_VARARGS, crypto_load_pkcs12_doc },
+ { "sign", (PyCFunction)crypto_sign, METH_VARARGS, crypto_sign_doc },
+ { "verify", (PyCFunction)crypto_verify, METH_VARARGS, crypto_verify_doc },
{ "X509_verify_cert_error_string", (PyCFunction)crypto_X509_verify_cert_error_string, METH_VARARGS, crypto_X509_verify_cert_error_string_doc },
{ "_exception_from_error_queue", (PyCFunction)crypto_exception_from_error_queue, METH_NOARGS, crypto_exception_from_error_queue_doc },
{ NULL, NULL }
diff --git a/test/test_crypto.py b/test/test_crypto.py
index 8235ad4..d9b215f 100644
--- a/test/test_crypto.py
+++ b/test/test_crypto.py
@@ -22,6 +22,7 @@
from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12
from OpenSSL.crypto import CRL, Revoked, load_crl
from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType
+from OpenSSL.crypto import sign, verify
from OpenSSL.test.util import TestCase
@@ -1859,5 +1860,46 @@
self.assertRaises(Error, load_crl, FILETYPE_PEM, "hello, world")
+class SignVerifyTests(TestCase):
+ """
+ Tests for L{OpenSSL.crypto.sign} and L{OpenSSL.crypto.verify}.
+ """
+ def test_sign_verify(self):
+ """
+ L{sign} generates a cryptographic signature which L{verify} can check.
+ """
+ content = (
+ "It was a bright cold day in April, and the clocks were striking "
+ "thirteen. Winston Smith, his chin nuzzled into his breast in an "
+ "effort to escape the vile wind, slipped quickly through the "
+ "glass doors of Victory Mansions, though not quickly enough to "
+ "prevent a swirl of gritty dust from entering along with him.")
+
+ # sign the content with this private key
+ priv_key = load_privatekey(FILETYPE_PEM, root_key_pem)
+ # verify the content with this cert
+ good_cert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ # certificate unrelated to priv_key, used to trigger an error
+ bad_cert = load_certificate(FILETYPE_PEM, server_cert_pem)
+
+ for digest in ('md5', 'sha1'):
+ sig = sign(priv_key, content, digest)
+
+ # Verify the signature of content, will throw an exception if error.
+ verify(good_cert, sig, content, digest)
+
+ # This should fail because the certificate doesn't match the
+ # private key that was used to sign the content.
+ self.assertRaises(Error, verify, bad_cert, sig, content, digest)
+
+ # This should fail because we've "tainted" the content after
+ # signing it.
+ self.assertRaises(Error, verify, good_cert, sig, content+"tainted", digest)
+
+ # test that unknown digest types fail
+ self.assertRaises(ValueError, sign, priv_key, content, "strange-digest")
+ self.assertRaises(ValueError, verify, good_cert, sig, content, "strange-digest")
+
+
if __name__ == '__main__':
main()