Fix a threading bug in passphrase callback support for context objects.

Also add a bunch of unit tests for loading and dumping private keys with passphrases.
diff --git a/test/test_crypto.py b/test/test_crypto.py
index b44f345..7d46f91 100644
--- a/test/test_crypto.py
+++ b/test/test_crypto.py
@@ -9,7 +9,50 @@
 from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType
 from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType
 from OpenSSL.crypto import X509Req, X509ReqType
-from OpenSSL.crypto import FILETYPE_PEM, load_certificate
+from OpenSSL.crypto import FILETYPE_PEM, load_certificate, load_privatekey
+from OpenSSL.crypto import dump_privatekey
+
+
+cleartextPrivateKeyPEM = (
+    "-----BEGIN RSA PRIVATE KEY-----\n"
+    "MIICXAIBAAKBgQDaemNe1syksAbFFpF3aoOrZ18vB/IQNZrAjFqXPv9iieJm7+Tc\n"
+    "g+lA/v0qmoEKrpT2xfwxXmvZwBNM4ZhyRC3DPIFEyJV7/3IA1p5iuMY/GJI1VIgn\n"
+    "aikQCnrsyxtaRpsMBeZRniaVzcUJ+XnEdFGEjlo+k0xlwfVclDEMwgpXAQIDAQAB\n"
+    "AoGBALi0a7pMQqqgnriVAdpBVJveQtxSDVWi2/gZMKVZfzNheuSnv4amhtaKPKJ+\n"
+    "CMZtHkcazsE2IFvxRN/kgato9H3gJqq8nq2CkdpdLNVKBoxiCtkLfutdY4SQLtoY\n"
+    "USN7exk131pchsAJXYlR6mCW+ZP+E523cNwpPgsyKxVbmXSBAkEA9470fy2W0jFM\n"
+    "taZFslpntKSzbvn6JmdtjtvWrM1bBaeeqFiGBuQFYg46VaCUaeRWYw02jmYAsDYh\n"
+    "ZQavmXThaQJBAOHtlAQ0IJJEiMZr6vtVPH32fmbthSv1AUSYPzKqdlQrUnOXPQXu\n"
+    "z70cFoLG1TvPF5rBxbOkbQ/s8/ka5ZjPfdkCQCeC7YsO36+UpsWnUCBzRXITh4AC\n"
+    "7eYLQ/U1KUJTVF/GrQ/5cQrQgftwgecAxi9Qfmk4xqhbp2h4e0QAmS5I9WECQH02\n"
+    "0QwrX8nxFeTytr8pFGezj4a4KVCdb2B3CL+p3f70K7RIo9d/7b6frJI6ZL/LHQf2\n"
+    "UP4pKRDkgKsVDx7MELECQGm072/Z7vmb03h/uE95IYJOgY4nfmYs0QKA9Is18wUz\n"
+    "DpjfE33p0Ha6GO1VZRIQoqE24F8o5oimy3BEjryFuw4=\n"
+    "-----END RSA PRIVATE KEY-----\n")
+
+
+encryptedPrivateKeyPEM = (
+    "-----BEGIN RSA PRIVATE KEY-----\n"
+    "Proc-Type: 4,ENCRYPTED\n"
+    "DEK-Info: BF-CBC,8306665233D056B1\n"
+    "\n"
+    "BwxghOcX1F+M108qRGBfpUBrfaeKOszDEV18OjEE55p0yGsiDxvdol3c4bwI5ITy\n"
+    "ltP8w9O33CDUCjr+Ymj8xLpPP60TTfr/aHq+2fEuG4TfkeHb5fVYm0mgVnaOhJs3\n"
+    "a2n5IL/KNCdP3zMZa0IaMJ0M+VK90SLpq5nzXOWkufLyZL1+n8srkk06gepmHS7L\n"
+    "rH3rALNboG8yTH1qjE8PwcMrJAQfRMd4/4RTQv+4pUuKj7I2en+YwSQ/gomy7qN1\n"
+    "3s/gMgV/2GUbEcTVch4thZ9l3WsX18V76rBQkiZ7yrJkxwNMv+Qc2GfHtBnsXAyA\n"
+    "0nIE4Mm/OQqX8h7EJ4c2s1DMGVS0YZGU+75HN0A3iD01h8C5utqSScWzBA45j/Vy\n"
+    "3aypQVqQeW7kBMQlpc6pHvJ1EsjiAJRCto7tZNLxRdjMKBV4w75JNLaAFSraqA+R\n"
+    "/WPcdcXAQuhmCeh31fzmVOHJGRF7/5pAR/b7AnFTD4YbYVcglNis/jpdiI9k2AYP\n"
+    "wZNwXOIh6Ibq5hMvyV4/pySyLbgDOrfrOGpi8N6lBbzewByYQKiXwUEZf+Y5499/\n"
+    "CckajBhgYynPpe6mgsSeklWGc845iIwAtzavBNZIkn1hKP1P+TFjbl2O75u/9JLJ\n"
+    "6i4IFYCyQmwiHX8nTR717SpCN2gyZ2HrX7z2mKP/KokkAX2yidwoKh9FMUV5lOGO\n"
+    "JPc4MfPo4lPB7SP30AtOh7y7zlS3x8Uo0+0wCg5Z5Fn/73x3W+p5nyI0G9n7RGzL\n"
+    "ZeCWLdG/Cm6ZyIpYZGbZ5m+U3Fr6/El9V6LSxrB1TB+8G1NTdLlbeA==\n"
+    "-----END RSA PRIVATE KEY-----\n")
+encryptedPrivateKeyPEMPassphrase = "foobar"
+
+
 
 class _Python23TestCaseHelper:
     # Python 2.3 compatibility.
@@ -398,22 +441,7 @@
 RipBiEEMEV4eAY317bHPwPP+4Bj9t0l8AsDLseC5vLRHgxrLEu3bn08DYx6imB5Q
 UBj849/xpszEM7BhwKE0GiQ=
 -----END CERTIFICATE-----
------BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQDaemNe1syksAbFFpF3aoOrZ18vB/IQNZrAjFqXPv9iieJm7+Tc
-g+lA/v0qmoEKrpT2xfwxXmvZwBNM4ZhyRC3DPIFEyJV7/3IA1p5iuMY/GJI1VIgn
-aikQCnrsyxtaRpsMBeZRniaVzcUJ+XnEdFGEjlo+k0xlwfVclDEMwgpXAQIDAQAB
-AoGBALi0a7pMQqqgnriVAdpBVJveQtxSDVWi2/gZMKVZfzNheuSnv4amhtaKPKJ+
-CMZtHkcazsE2IFvxRN/kgato9H3gJqq8nq2CkdpdLNVKBoxiCtkLfutdY4SQLtoY
-USN7exk131pchsAJXYlR6mCW+ZP+E523cNwpPgsyKxVbmXSBAkEA9470fy2W0jFM
-taZFslpntKSzbvn6JmdtjtvWrM1bBaeeqFiGBuQFYg46VaCUaeRWYw02jmYAsDYh
-ZQavmXThaQJBAOHtlAQ0IJJEiMZr6vtVPH32fmbthSv1AUSYPzKqdlQrUnOXPQXu
-z70cFoLG1TvPF5rBxbOkbQ/s8/ka5ZjPfdkCQCeC7YsO36+UpsWnUCBzRXITh4AC
-7eYLQ/U1KUJTVF/GrQ/5cQrQgftwgecAxi9Qfmk4xqhbp2h4e0QAmS5I9WECQH02
-0QwrX8nxFeTytr8pFGezj4a4KVCdb2B3CL+p3f70K7RIo9d/7b6frJI6ZL/LHQf2
-UP4pKRDkgKsVDx7MELECQGm072/Z7vmb03h/uE95IYJOgY4nfmYs0QKA9Is18wUz
-DpjfE33p0Ha6GO1VZRIQoqE24F8o5oimy3BEjryFuw4=
------END RSA PRIVATE KEY-----
-"""
+""" + cleartextPrivateKeyPEM
 
     def signable(self):
         """
@@ -534,3 +562,93 @@
         self.assertEqual(
             cert.digest("md5"),
             "A8:EB:07:F8:53:25:0A:F2:56:05:C5:A5:C4:C4:C7:15")
+
+
+
+class FunctionTests(TestCase):
+    """
+    Tests for free-functions in the L{OpenSSL.crypto} module.
+    """
+    def test_load_privatekey_wrongPassphrase(self):
+        """
+        L{load_privatekey} raises L{OpenSSL.crypto.Error} when it is passed an
+        encrypted PEM and an incorrect passphrase.
+        """
+        self.assertRaises(
+            Error,
+            load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, "quack")
+
+
+    def test_load_privatekey_passphrase(self):
+        """
+        L{load_privatekey} can create a L{PKey} object from an encrypted PEM
+        string if given the passphrase.
+        """
+        key = load_privatekey(
+            FILETYPE_PEM, encryptedPrivateKeyPEM,
+            encryptedPrivateKeyPEMPassphrase)
+        self.assertTrue(isinstance(key, PKeyType))
+
+
+    def test_load_privatekey_wrongPassphraseCallback(self):
+        """
+        L{load_privatekey} raises L{OpenSSL.crypto.Error} when it is passed an
+        encrypted PEM and a passphrase callback which returns an incorrect
+        passphrase.
+        """
+        called = []
+        def cb(*a):
+            called.append(None)
+            return "quack"
+        self.assertRaises(
+            Error,
+            load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb)
+        self.assertTrue(called)
+
+    def test_load_privatekey_passphraseCallback(self):
+        """
+        L{load_privatekey} can create a L{PKey} object from an encrypted PEM
+        string if given a passphrase callback which returns the correct
+        password.
+        """
+        called = []
+        def cb(writing):
+            called.append(writing)
+            return encryptedPrivateKeyPEMPassphrase
+        key = load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb)
+        self.assertTrue(isinstance(key, PKeyType))
+        self.assertEqual(called, [False])
+
+
+    def test_dump_privatekey_passphrase(self):
+        """
+        L{dump_privatekey} writes an encrypted PEM when given a passphrase.
+        """
+        passphrase = "foo"
+        key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+        pem = dump_privatekey(FILETYPE_PEM, key, "blowfish", passphrase)
+        self.assertTrue(isinstance(pem, str))
+        loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase)
+        self.assertTrue(isinstance(loadedKey, PKeyType))
+        self.assertEqual(loadedKey.type(), key.type())
+        self.assertEqual(loadedKey.bits(), key.bits())
+
+
+    def test_dump_privatekey_passphraseCallback(self):
+        """
+        L{dump_privatekey} writes an encrypted PEM when given a callback which
+        returns the correct passphrase.
+        """
+        passphrase = "foo"
+        called = []
+        def cb(writing):
+            called.append(writing)
+            return passphrase
+        key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+        pem = dump_privatekey(FILETYPE_PEM, key, "blowfish", cb)
+        self.assertTrue(isinstance(pem, str))
+        self.assertEqual(called, [True])
+        loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase)
+        self.assertTrue(isinstance(loadedKey, PKeyType))
+        self.assertEqual(loadedKey.type(), key.type())
+        self.assertEqual(loadedKey.bits(), key.bits())