diff --git a/nimsapi.py b/nimsapi.py
index 724fa77c07f30e5a0622dbb3a9d080108dcf40e5..e205035404d21b03dc58313cd7e3ad2c26d4be39 100755
--- a/nimsapi.py
+++ b/nimsapi.py
@@ -16,6 +16,7 @@ import argparse
 import markdown
 import bson.json_util
 import webapp2_extras.routes
+import Crypto.PublicKey.RSA
 
 import nimsutil
 
@@ -37,7 +38,8 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
 
     def get(self):
         """Return API documentation"""
-        resource = """Resource                                          | Description
+        resources = """
+                      Resource                                          | Description
                       :-------------------------------------------------|:-----------------------
                       /nimsapi/download                                 | download
                       /nimsapi/dump                                     | dump
@@ -69,7 +71,7 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
                       [(/nimsapi/epochs/listschema)]                    | schema for epoch list
                       [(/nimsapi/epochs/schema)]                        | schema for single epoch
                       /nimsapi/epochs/*<eid>*                           | details for one epoch, *<eid>*"""
-        resource = re.sub(r'\[\((.*)\)\]', r'[\1](\1)', resource).replace('<', '&lt;').replace('>', '&gt;')
+        resources = re.sub(r'\[\((.*)\)\]', r'[\1](\1)', resources).replace('<', '&lt;').replace('>', '&gt;').strip()
         self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
         self.response.write('<html>\n')
         self.response.write('<head>\n')
@@ -88,7 +90,7 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
         self.response.write('</style>\n')
         self.response.write('</head>\n')
         self.response.write('<body style="min-width:900px">\n')
-        self.response.write(markdown.markdown(resource, ['extra']))
+        self.response.write(markdown.markdown(resources, ['extra']))
         self.response.write('</body>\n')
         self.response.write('</html>\n')
 
@@ -390,7 +392,7 @@ class ArgumentParser(argparse.ArgumentParser):
         super(ArgumentParser, self).__init__()
         self.add_argument('uri', help='NIMS DB URI')
         self.add_argument('stage_path', help='path to staging area')
-        self.add_argument('-k', '--pubkey', help='path to public SSL key file')
+        self.add_argument('--privkey', help='path to private SSL key file')
         self.add_argument('-u', '--uid', help='site UID')
         self.add_argument('-f', '--logfile', help='path to log file')
         self.add_argument('-l', '--loglevel', default='info', help='path to log file')
@@ -438,14 +440,16 @@ app = webapp2.WSGIApplication(routes, debug=True)
 if __name__ == '__main__':
     args = ArgumentParser().parse_args()
     nimsutil.configure_log(args.logfile, not args.quiet, args.loglevel)
-    if args.pubkey:
-        pubkey = open(args.pubkey).read()  # failure raises a sensible IOError
-        log.debug('SSL pubkey loaded')
+
+    if args.privkey:
+        privkey = Crypto.PublicKey.RSA.importKey(open(args.privkey).read())
+        log.debug('SSL private key loaded')
     else:
-        pubkey = None
-        log.warning('PUBKEY NOT SPECIFIED')
+        privkey = None
+        log.warning('PRIVKEY NOT SPECIFIED')
+
     from paste import httpserver
-    app.config = dict(stage_path=args.stage_path, site_id=args.uid, pubkey=pubkey)
+    app.config = dict(stage_path=args.stage_path, site_id=args.uid, privkey=privkey)
     app.db = (pymongo.MongoReplicaSetClient(args.uri) if 'replicaSet' in args.uri else pymongo.MongoClient(args.uri)).get_default_database()
     httpserver.serve(app, host=httpserver.socket.gethostname(), port='8080')
 
diff --git a/nimsapi.wsgi b/nimsapi.wsgi
index 8f7b59ae06b0e0c7d4c2be599ae552a72471ade1..5fcc9b609251aa6d05fd2f12bac326561e77ad68 100644
--- a/nimsapi.wsgi
+++ b/nimsapi.wsgi
@@ -15,9 +15,10 @@ import pymongo
 import webapp2
 import nimsapi
 import nimsutil
+import Crypto.PublicKey.RSA
 
 log_file = '/var/local/log/nimsapi.log'
-pubkey_file = '/var/local/nims/internims/internims.pub'
+privkey_file = '/var/local/nims/internims/internims.pem'
 db_uri = 'mongodb://nims:cnimr750@cnifs.stanford.edu,cnibk.stanford.edu/nims?replicaSet=cni'
 stage_path = '/scratch/upload'
 
@@ -25,11 +26,10 @@ nimsutil.configure_log(log_file, False)
 db_client = pymongo.MongoReplicaSetClient(db_uri) if 'replicaSet' in db_uri else pymongo.MongoClient(db_uri)
 
 try:
-    pubkey = open(pubkey_file).read() # FIXME: don't read too much
-    # FIXME: verify that this is a valid public key
-except IOError:
-    pubkey = None
+    privkey = Crypto.PublicKey.RSA.importKey(open(privkey_file).read())
+except ValueError as e:
+    privkey = None
 
 application = nimsapi.app
-application.config = dict(stage_path=stage_path, site_id='stanford-cni', pubkey=pubkey)
+application.config = dict(stage_path=stage_path, site_id='stanford_cni', privkey=privkey)
 application.db = db_client.get_default_database()
diff --git a/nimsapiutil.py b/nimsapiutil.py
index c44e6e26bf12535047c7d56e111c3b9c07d6d532..a07d5a549dce91dd3bb95021e0b515b7af63e141 100644
--- a/nimsapiutil.py
+++ b/nimsapiutil.py
@@ -8,13 +8,15 @@ import webapp2
 import datetime
 import requests
 import bson.json_util
-import Crypto.Hash.HMAC
-import Crypto.Random.random
+import Crypto.Hash.SHA
+import Crypto.PublicKey.RSA
+import Crypto.Signature.PKCS1_v1_5
 
 log = logging.getLogger('nimsapi')
 requests_log = logging.getLogger('requests')            # configure Requests logging
 requests_log.setLevel(logging.WARNING)                  # set level to WARNING (default is INFO)
 
+
 class NIMSRequestHandler(webapp2.RequestHandler):
 
     """fetches pubkey from own self.db.remotes. needs to be aware of OWN site uid"""
@@ -62,86 +64,72 @@ class NIMSRequestHandler(webapp2.RequestHandler):
         self.userid = self.request.remote_user or '@public'
         self.user = self.app.db.users.find_one({'_id': self.userid})
         self.user_is_superuser = self.user.get('superuser')
-        self.response.headers['Content-Type'] = 'application/json'
         self.target_id = self.request.get('iid', None)
         self.site_id = self.app.config.get('site_id')           # is ALREADY 'None' if not specified in args, never empty
-        self.pubkey = self.app.config.get('pubkey')             # is ALREADY 'None' if not specified in args, never empty
-
-        # requests coming from another NIMS instance are dealt with differently
-        if self.request.user_agent.startswith('NIMS Instance'):
-            log.debug('request from "{0}", interNIMS p2p initiated'.format(self.request.user_agent))
-            try:
-                authinfo = self.request.headers['authorization']
-                challenge_id, digest = base64.b64decode(authinfo).split()
-                user, remote_site = challenge_id.split(':')
-                # look up pubkey from db.remotes
-                projection = {'_id': False, 'pubkey': True}
-                remote_pubkey = self.app.db.remotes.find_one({'_id': remote_site}, projection)['pubkey']
-                # look up challenge from db.challenges
-                projection = {'_id': False, 'challenge': True}
-                challenge = self.app.db.challenges.find_one({'_id': challenge_id}, projection)['challenge']
-                # delete challenge from db.challenges
-                self.app.db.challenges.remove({'_id': challenge_id})
-                # calculate expected response
-                h = Crypto.Hash.HMAC.new(remote_pubkey, challenge)
-                self.expected = base64.b64encode('%s %s' % (challenge_id, h.hexdigest()))
-                log.debug('recieved: %s' % authinfo)
-                log.debug('expected: %s' % self.expected)
-                # verify
-                if self.expected == authinfo:
-                    log.debug('CRAM response accepted - %s authenticated' % challenge_id)
-                else:
-                    self.abort(403, 'Not Authorized: cram failed')
-            except KeyError as e:
-                # send a 401 with a fresh challenge
-                # challenge associated with a challenge-id, cid, to ease lookup
-                cid = self.request.get('cid')
-                if not cid: self.abort(403, 'cid, challenge_id, required')
-                challenge = {'_id': cid,
-                             'challenge': str(Crypto.Random.random.getrandbits(128)),
-                             'timestamp': datetime.datetime.now()}
-                # upsert challenge with time of creation
-                self.app.db.challenges.find_and_modify(query={'_id': cid}, update=challenge, upsert=True, new=True)
-                # send 401 + challenge in 'www-authenticate' header
-                self.response.headers['www-authenticate'] = base64.b64encode(challenge['challenge'])
-                self.response.set_status(401)
-                log.debug('issued challenge to %s; %s' % (cid, challenge['challenge']))
+        self.privkey = self.app.config.get('privkey')           # is ALREADY 'None' if not specified in args, never empty
 
     def dispatch(self):
         """dispatching and request forwarding"""
+        # dispatch to local instance
         if self.target_id in [None, self.site_id]:
-            log.debug('{0} dispatching to local {1}'.format(socket.gethostname(), self.request.url))
-            super(NIMSRequestHandler, self).dispatch()
-        elif self.pubkey is None and self.site_id is None:
-            log.warning('target is %s, but no site ID, and no pubkey. cannot dispatch')
-        elif self.pubkey is not None and self.site_id is not None:
-            log.debug('{0} dispatching to remote {1}'.format(socket.gethostname(), self.target_id))
+            log.debug(socket.gethostname() + ' dispatching to local ' + self.request.url)
+            # request originates from remote instance
+            if self.request.user_agent.startswith('NIMS Instance'):
+                # is the requester an authorized remote site
+                requester = self.request.user_agent.replace('NIMS Instance', '').strip()
+                target = self.app.db.remotes.find_one({'_id':requester})
+                if not target:
+                    log.debug('remote host ' + requester + ' not in auth list. DENIED')
+                    self.abort(403, requester + ' is not authorized')
+                log.debug('request from ' + self.request.user_agent + ', interNIMS p2p initiated')
+                # verify signature
+                self.signature = base64.b64decode(self.request.headers.get('Authorization'))
+                payload = self.request.body
+                key = Crypto.PublicKey.RSA.importKey(target['pubkey'])
+                h = Crypto.Hash.SHA.new(payload)
+                verifier = Crypto.Signature.PKCS1_v1_5.new(key)
+                if verifier.verify(h, self.signature):
+                    log.debug('message/signature is authentic')
+                    super(NIMSRequestHandler, self).dispatch()
+                else:
+                    log.debug('message/signature is not authentic')
+                    self.abort(403, 'authentication failed')
+            # request originates from self
+            else:
+                    super(NIMSRequestHandler, self).dispatch()
+
+        # dispatch to remote instance
+        elif self.privkey is not None and self.site_id is not None:
+            log.debug(socket.gethostname() + ' dispatching to remote ' + self.target_id)
             # is target registered?
             target = self.app.db.remotes.find_one({'_id': self.target_id}, {'_id':False, 'hostname':True})
             if not target:
-                log.debug('remote host {0} not in auth list. DENIED'.format(self.target_id))
-                self.abort(403, 'forbidden: site is not registered with interNIMS')
-            self.cid = self.userid + ':' + self.site_id
+                log.debug('remote host ' + self.target_id + ' not in auth list. DENIED')
+                self.abort(403, self.target_id + 'is not authorized')
+
+            # disassemble the incoming request
+            reqparams = dict(self.request.params)
+            reqpayload = self.request.body                                      # request payload, almost always empty
             reqheaders = dict(self.request.headers)
-            # adjust the request, pass as much of orig request as possible
-            reqheaders['User-Agent'] = 'NIMS Instance {0}'.format(self.site_id)
-            del reqheaders['Host']
-            target_api = 'http://{0}{1}?{2}'.format(target['hostname'], self.request.path, self.request.query_string)
-            reqparams = {'cid': self.cid}
-            # first attempt, expect 401, send as little as possible...
-            # TODO: error handling for host-down/host-unreachable
-            # TODO: timeout?
-            r = requests.request(method=self.request.method, url=target_api, params=reqparams, headers=reqheaders)
-            if r.status_code == 401:
-                challenge = base64.b64decode(r.headers['www-authenticate'])
-                log.debug('Authorization requested - challenge: %s' % challenge)
-                h = Crypto.Hash.HMAC.new(self.pubkey, challenge)
-                response = base64.b64encode('%s %s' % (self.cid, h.hexdigest()))
-                log.debug('response:  %s %s' % (self.cid, h.hexdigest()))
-                log.debug('b4encoded: %s' % response)
-                reqheaders['authorization'] = response
-                r = requests.request(method=self.request.method, url=target_api, params=reqparams, data=self.request.body, headers=reqheaders, cookies=self.request.cookies)
+            reqheaders['User-Agent'] = 'NIMS Instance ' + self.site_id
+            del reqheaders['Host']                                              # delete old host destination
+
+            # create a signature of the incoming request payload
+            h = Crypto.Hash.SHA.new(reqpayload)
+            signature = Crypto.Signature.PKCS1_v1_5.new(self.privkey).sign(h)
+            reqheaders['Authorization'] = base64.b64encode(signature)
+
+            # construct outgoing request
+            target_api = 'http://' + target['hostname'] + self.request.path      # TODO: switch to https
+            # target_api = 'https://' + target['hostname'] + self.request.path)
+            r = requests.request(method=self.request.method, data=reqpayload, url=target_api, params=reqparams, headers=reqheaders, verify=False)
+
+            # return response content
+            # TODO: headers
             self.response.write(r.content)
 
+        elif self.privkey is None or self.site_id is None:
+            log.debug('no private key (privkey), or local instance id (iid). cannot dispatch to remote')
+
     def schema(self, *args, **kwargs):
         self.response.write(json.dumps(self.json_schema, default=bson.json_util.default))
diff --git a/uwsgi.ini b/uwsgi.ini
index 80353d18f0835a2c7a74507789825fe0bb42a23c..96570d75f04ad95cd6c824199128794a18262f1f 100644
--- a/uwsgi.ini
+++ b/uwsgi.ini
@@ -7,4 +7,5 @@ wsgi-file       = /var/local/nims/nimsapi/nimsapi.wsgi
 processes       = 2
 threads         = 2
 master          = 1
+lazy            = 1
 # vacuum          = 1