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()