* 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.
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/src/crypto/crypto.c b/src/crypto/crypto.c
index decf722..dd279cf 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.
@@ -545,6 +546,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(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[] = {
@@ -557,6 +653,8 @@
     { "dump_certificate_request", (PyCFunction)crypto_dump_certificate_request, METH_VARARGS, crypto_dump_certificate_request_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 fbe5635..77a0c85 100644
--- a/test/test_crypto.py
+++ b/test/test_crypto.py
@@ -1533,5 +1533,35 @@
 
 
 
+class SignVerifyTests(TestCase):
+    """
+    Tests for L{OpenSSL.crypto.sign} and L{OpenSSL.crypto.verify}.
+    """
+    def test_sign_verify(self):
+        from OpenSSL.crypto import sign, verify
+
+        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."
+        priv_key = load_privatekey (FILETYPE_PEM, root_key_pem)    # sign the content with this private key
+        good_cert = load_certificate(FILETYPE_PEM, root_cert_pem)  # verify the content with this cert
+        bad_cert = load_certificate(FILETYPE_PEM, server_cert_pem) # certificate unrelated to priv_key, used to trigger an error
+
+        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()