diff --git a/src/fts3rest/fts3rest/controllers/delegation.py b/src/fts3rest/fts3rest/controllers/delegation.py
index acd65d3c2591e45b5806dde161c295f5ff98edad..16ef9f2456552daa5f660f3d59b8ef643a56f148 100644
--- a/src/fts3rest/fts3rest/controllers/delegation.py
+++ b/src/fts3rest/fts3rest/controllers/delegation.py
@@ -112,7 +112,7 @@ def _validate_proxy(proxy_pem, private_key_pem):
     x509_proxy_issuer = x509_list[1]
 
     expiration_time = x509_proxy.get_not_after().get_datetime().replace(tzinfo=None)
-    private_key = EVP.load_key_string(str(private_key_pem), callback=_mute_callback)
+    private_key = EVP.load_key_string(private_key_pem, callback=_mute_callback)
 
     # The modulus of the stored private key and the modulus of the proxy must match
     if x509_proxy.get_pubkey().get_modulus() != private_key.get_modulus():
@@ -153,7 +153,7 @@ def _build_full_proxy(x509_pem, privkey_pem):
         A full proxy
     """
     x509_list = _read_x509_list(x509_pem)
-    x509_chain = "".join(map(lambda x: x.as_pem(), x509_list[1:]))
+    x509_chain = b"".join(map(lambda x: x.as_pem(), x509_list[1:]))
     return x509_list[0].as_pem() + privkey_pem + x509_chain
 
 
@@ -331,11 +331,13 @@ class credential(Delegation):
         log.debug("Received delegated credentials for %s" % dlg_id)
         log.debug(x509_proxy_pem)
 
+        if credential_cache.priv_key and isinstance(credential_cache.priv_key, str):
+            priv_key = bytes(credential_cache.priv_key, "utf-8")
+        else:
+            priv_key = credential_cache.priv_key
         try:
-            expiration_time = _validate_proxy(x509_proxy_pem, credential_cache.priv_key)
-            x509_full_proxy_pem = _build_full_proxy(
-                x509_proxy_pem, credential_cache.priv_key
-            )
+            expiration_time = _validate_proxy(x509_proxy_pem, priv_key)
+            x509_full_proxy_pem = _build_full_proxy(x509_proxy_pem, priv_key)
         except ProxyException as ex:
             raise BadRequest("Could not process the proxy: " + str(ex))
 
diff --git a/src/fts3rest/fts3rest/tests/__init__.py b/src/fts3rest/fts3rest/tests/__init__.py
index c031d6afc219a0d1ea20ea85c41ffa5ec245e22f..ee046677309bce3f771a192723285329aeec28d8 100644
--- a/src/fts3rest/fts3rest/tests/__init__.py
+++ b/src/fts3rest/fts3rest/tests/__init__.py
@@ -129,7 +129,7 @@ class TestController(TestCase):
         if subject is None:
             subject = issuer + [("CN", "proxy")]
 
-        x509_request = X509.load_request_string(str(request_pem))
+        x509_request = X509.load_request_string(request_pem)
 
         not_before = ASN1.ASN1_UTCTIME()
         not_before.set_datetime(datetime.now(UTC))
diff --git a/src/fts3rest/fts3rest/tests/functional/test_delegation.py b/src/fts3rest/fts3rest/tests/functional/test_delegation.py
new file mode 100644
index 0000000000000000000000000000000000000000..8dae6d96002e7072b5d4b33bac11954f1093fff2
--- /dev/null
+++ b/src/fts3rest/fts3rest/tests/functional/test_delegation.py
@@ -0,0 +1,277 @@
+from datetime import datetime, timedelta
+from M2Crypto import EVP
+import time
+
+from fts3rest.controllers.delegation import _generate_proxy_request
+from fts3rest.tests import TestController
+from fts3rest.model.meta import Session
+from fts3.model import Credential, CredentialCache
+
+
+class TestDelegation(TestController):
+    """
+    Tests for the delegation controller
+    """
+
+    def test_get_termination_time_not_existing(self):
+        """
+        Get the termination time for a dlg_id that hasn't delegated yet
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        delegation_id = self.app.get(
+            url="/delegation/%s" % creds.delegation_id, status=200
+        ).json
+        self.assertEqual(
+            delegation_id, []
+        )  # for Flask, I changed None to [], as it makes more sense
+
+    def test_get_termination_time(self):
+        """
+        Get credentials termination time
+        """
+        self.test_valid_proxy()
+        creds = self.get_user_credentials()
+
+        termination_str = self.app.get(
+            url="/delegation/%s" % creds.delegation_id, status=200
+        ).json["termination_time"]
+
+        termination = datetime.strptime(termination_str, "%Y-%m-%dT%H:%M:%S")
+        self.assertGreater(
+            termination, datetime.utcnow() + timedelta(hours=2, minutes=58)
+        )
+
+    def test_put_cred_without_cache(self):
+        """
+        This is a regression test. It tries to PUT directly
+        credentials without the previous negotiation, so there is no
+        CredentialCache in the database. This attempt must fail.
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        request = self.app.get(
+            url="/delegation/%s/request" % creds.delegation_id, status=200
+        )
+        proxy = self.get_x509_proxy(request.get_data(as_text=True))
+
+        Session.delete(
+            Session.query(CredentialCache).get((creds.delegation_id, creds.user_dn))
+        )
+
+        self.app.put(
+            url="/delegation/%s/credential" % creds.delegation_id,
+            params=proxy,
+            status=400,
+        )
+
+    def test_put_malformed_pem(self):
+        """
+        Putting a malformed proxy must fail
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        self.app.get(url="/delegation/%s/request" % creds.delegation_id, status=200)
+
+        self.app.put(
+            url="/delegation/%s/credential" % creds.delegation_id,
+            params="MALFORMED!!!1",
+            status=400,
+        )
+
+    def test_valid_proxy(self):
+        """
+        Putting a well-formed proxy with all the right steps must succeed
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        request = self.app.get(
+            url="/delegation/%s/request" % creds.delegation_id, status=200
+        )
+        proxy = self.get_x509_proxy(request.get_data(as_text=True))
+
+        self.app.put(
+            url="/delegation/%s/credential" % creds.delegation_id,
+            params=proxy,
+            status=201,
+        )
+
+        proxy = Session.query(Credential).get((creds.delegation_id, creds.user_dn))
+        self.assertNotEqual(None, proxy)
+        return proxy
+
+    def test_dn_mismatch(self):
+        """
+        A well-formed proxy with mismatching issuer and subject must fail
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        request = self.app.get(
+            url="/delegation/%s/request" % creds.delegation_id, status=200
+        )
+
+        proxy = self.get_x509_proxy(
+            request.get_data(as_text=True), subject=[("DC", "dummy")]
+        )
+
+        self.app.put(
+            url="/delegation/%s/credential" % creds.delegation_id,
+            params=proxy,
+            status=400,
+        )
+
+    def test_signed_wrong_priv_key(self):
+        """
+        Regression for FTS-30
+        If a proxy is signed with an invalid private key, reject it
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        request = self.app.get(
+            url="/delegation/%s/request" % creds.delegation_id, status=200
+        )
+
+        proxy = self.get_x509_proxy(
+            request.get_data(as_text=True), private_key=EVP.PKey()
+        )
+
+        self.app.put(
+            url="/delegation/%s/credential" % creds.delegation_id,
+            params=proxy,
+            status=400,
+        )
+
+    def test_wrong_request(self):
+        """
+        Get a request, sign a different request and send it
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        self.app.get(url="/delegation/%s/request" % creds.delegation_id, status=200)
+
+        (different_request, _) = _generate_proxy_request()
+        proxy = self.get_x509_proxy(different_request.as_pem(), private_key=EVP.PKey())
+
+        self.app.put(
+            url="/delegation/%s/credential" % creds.delegation_id,
+            params=proxy,
+            status=400,
+        )
+
+    def test_get_request_different_dlg_id(self):
+        """
+        A user should be able only to get his/her own proxy request,
+        and be denied any other.
+        """
+        self.setup_gridsite_environment()
+
+        self.app.get(url="/delegation/12345xx/request", status=403)
+
+    def test_view_different_dlg_id(self):
+        """
+        A user should be able only to get his/her own delegation information.
+        """
+        self.setup_gridsite_environment()
+
+        self.app.get(url="/delegation/12345x", status=403)
+
+    def test_remove_delegation(self):
+        """
+        A user should be able to remove his/her proxy
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        self.test_valid_proxy()
+
+        self.app.delete(url="/delegation/%s" % creds.delegation_id, status=204)
+
+        self.app.delete(url="/delegation/%s" % creds.delegation_id, status=404)
+
+        proxy = Session.query(Credential).get((creds.delegation_id, creds.user_dn))
+
+        self.assertEqual(None, proxy)
+
+    def test_set_voms(self):
+        """
+        The server must regenerate a proxy with VOMS extensions
+        Need a real proxy for this one
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        # Need to push a real proxy :/
+        proxy_pem = self.get_real_x509_proxy()
+        if proxy_pem is None:
+            self.skipTest("Could not get a valid real proxy for test_set_voms")
+
+        proxy = Credential()
+        proxy.dn = creds.user_dn
+        proxy.dlg_id = creds.delegation_id
+        proxy.termination_time = datetime.utcnow() + timedelta(hours=1)
+        proxy.proxy = proxy_pem
+        Session.merge(proxy)
+        Session.commit()
+
+        # Now, request the voms extensions
+        self.app.post_json(
+            url="/delegation/%s/voms" % creds.delegation_id,
+            params=["dteam:/dteam/Role=lcgadmin"],
+            status=203,
+        )
+
+        # And validate
+        proxy2 = Session.query(Credential).get((creds.delegation_id, creds.user_dn))
+        self.assertNotEqual(proxy.proxy, proxy2.proxy)
+        self.assertEqual("dteam:/dteam/Role=lcgadmin", proxy2.voms_attrs)
+
+    def test_delegate_rfc(self):
+        """
+        Delegate an RFC-like proxy
+        """
+        self.setup_gridsite_environment()
+        creds = self.get_user_credentials()
+
+        request = self.app.get(
+            url="/delegation/%s/request" % creds.delegation_id, status=200
+        )
+
+        proxy = self.get_x509_proxy(
+            request.get_data(as_text=True),
+            subject=[
+                ("DC", "ch"),
+                ("DC", "cern"),
+                ("CN", "Test User"),
+                ("CN", str(int(time.time()))),
+            ],
+        )
+
+        self.app.put(
+            url="/delegation/%s/credential" % creds.delegation_id,
+            params=proxy,
+            status=201,
+        )
+
+        proxy = Session.query(Credential).get((creds.delegation_id, creds.user_dn))
+        self.assertNotEqual(None, proxy)
+
+    def test_cert(self, cert=None):
+        """
+        Test for returning the user certificate
+        """
+        self.setup_gridsite_environment()
+        if cert is None:
+            cert = "SSL_CLIENT_CERT"
+        self.app.environ_base["SSL_CLIENT_CERT"] = "certificate:" + cert
+
+        returns = self.app.get(url="/whoami/certificate", status=200).get_data(
+            as_text=True
+        )
+        self.assertEqual("certificate:" + cert, returns)