Index: fedd/federation/access.py
===================================================================
--- fedd/federation/access.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/access.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -301,14 +301,19 @@
 	# Check every attribute that we know how to map and take the first
 	# success.
+	fail_proofs = [ ]
 	for attr in check:
-	    if self.auth.check_attribute(fid, attr.attr):
+	    access_ok, proof = self.auth.check_attribute(fid, attr.attr,
+		    with_proof=True)
+	    if access_ok:
 		self.log.debug("Access succeeded for %s %s" % (attr.attr, fid))
 		# XXX: needs to deal with dynamics
 		return copy.copy(attr.value), (False, False, False), \
-			[ fid ]
+			[ fid ], proof
 	    else:
+		fail_proofs.append(proof)
 		self.log.debug("Access failed for %s %s" % (attr.attr, fid))
 	else:
-            raise service_error(service_error.access, "Access denied")
+            raise service_error(service_error.access, "Access denied",
+		    proof=fail_proofs)
 
 
@@ -448,5 +453,5 @@
 	return (exp, state)
 
-    def build_access_response(self, alloc_id, ap, services):
+    def build_access_response(self, alloc_id, ap, services, proof):
         """
         Create the SOAP response.
@@ -461,4 +466,5 @@
         msg = { 
                 'allocID': alloc_id,
+		'proof': proof.to_dict(),
 		'fedAttr': [
 		    { 'attribute': 'domain', 'value': self.domain } , 
@@ -789,5 +795,5 @@
 	# exception denying access that triggers a fault response back to the
 	# caller.
-	found, match, owners = self.lookup_access(req, fid)
+	found, match, owners, proof = self.lookup_access(req, fid)
 	self.log.info(
 		"[RequestAccess] Access granted to %s with local creds %s" % \
@@ -819,5 +825,5 @@
 		    "Can't open %s/%s : %s" % (self.certdir, aid, e))
 	self.log.debug('[RequestAccess] Returning allocation ID: %s' % allocID)
-	return { 'allocID': { 'fedid': allocID } }
+	return { 'allocID': { 'fedid': allocID }, 'proof': proof.to_dict() }
 
     def ReleaseAccess(self, req, fid):
@@ -849,7 +855,10 @@
 	self.log.debug("[ReleaseAccess] deallocation requested for %s", aid)
 	#  Confirm access
-	if not self.auth.check_attribute(fid, auth_attr):
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    self.log.debug("[ReleaseAccess] deallocation denied for %s", aid)
-	    raise service_error(service_error.access, "Access Denied")
+	    raise service_error(service_error.access, "Access Denied",
+		    proof=proof)
 
 	# If there is an allocation in the state, delete it.  Note the locking.
@@ -865,5 +874,5 @@
 	    self.log.debug("[ReleaseAccess] Removing %s" % cf)
 	    os.remove(cf)
-	    return { 'allocID': req['allocID'] } 
+	    return { 'allocID': req['allocID'], 'proof': proof.to_dict() } 
 	else:
 	    self.state_lock.release()
Index: fedd/federation/authorizer.py
===================================================================
--- fedd/federation/authorizer.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/authorizer.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -1,5 +1,4 @@
 #/usr/local/bin/python
 
-from string import join
 from tempfile import mkstemp
 from subprocess import call
@@ -12,4 +11,5 @@
 from service_error import service_error
 from util import abac_pem_type, abac_split_cert
+from proof import proof
 
 
@@ -116,5 +116,5 @@
 	if attrs: attrs.discard(attr)
 
-    def check_attribute(self, name, attr):
+    def check_attribute(self, name, attr, with_proof=False):
 	"""
 	Return True if name has attr (or if attr is global).  Tuple names match
@@ -130,5 +130,6 @@
 	self.valid_name(name)
 	if attr in self.globals:
-	    return True
+	    if with_proof: return True, proof("me", name, attr)
+	    else: return True
 
 	if isinstance(name, tuple):
@@ -137,7 +138,15 @@
 		if self.attrs.has_key(lookup):
 		    if attr in self.attrs[lookup]:
-			return True
-	else:
-	    return  attr in self.attrs.get(self.auth_name(name), set())
+			if with_proof: return True, proof("me", name, attr)
+			else: return True
+		# Drop through
+		if with_proof: return False, proof("me", name, attr)
+		else: return False
+	else:
+	    if with_proof:
+		return attr in self.attrs.get(self.auth_name(name), set()), \
+			proof("me", name, attr)
+	    else:
+		return attr in self.attrs.get(self.auth_name(name), set())
 
     def set_global_attribute(self, attr):
@@ -375,6 +384,5 @@
 
 
-    def check_attribute(self, name, attr):
-	# XXX proof soon
+    def check_attribute(self, name, attr, with_proof=False):
 	if isinstance(name, tuple):
 	    raise abac_authorizer.bad_name(
@@ -399,10 +407,12 @@
 	    # Sigh. Unicode vs swig and swig seems to lose.  Make sure
 	    # everything we pass into ABAC is a str not a unicode.
-	    rv, proof = self.context.query(a, n)
+	    rv, p = self.context.query(a, n)
 	    # XXX delete soon
-	    if not rv and attr in self.globals: rv = True
-	    self.lock.release()
-
-	    return rv
+	    if not rv and attr in self.globals: 
+		rv = True
+		p = None
+	    self.lock.release()
+	    if with_proof: return rv, proof(self.fedid, name, a, p)
+	    else: return rv
 
     def set_global_attribute(self, attr):
Index: fedd/federation/client_lib.py
===================================================================
--- fedd/federation/client_lib.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/client_lib.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -7,4 +7,5 @@
 
 from string import join
+from datetime import datetime
 
 
@@ -31,4 +32,7 @@
 		callback=self.expand_file,
                 type="string", help="my certificate file")
+	self.add_option("--auth_log", action="callback", dest="auth_log",
+		callback=self.expand_file, default=None,
+		type="string", help="Log authentication decisions to this file")
 	self.add_option("--abac", action="callback", dest="abac_dir",
 		callback=self.expand_file,
@@ -53,5 +57,16 @@
                 const=sys.stderr, help="Print SOAP exchange to stderr")
 
-def exit_with_fault(exc, out=sys.stderr):
+def log_authentication(fn, action, outcome, proof):
+    f = open(fn, 'a')
+    print >>f, '%s %s at %s' % (action, outcome, datetime.now())
+    if isinstance(proof, list):
+	for p in proof:
+	    print >>f, p.to_xml()
+    else:
+	print >>f, proof.to_xml()
+    f.close()
+
+
+def exit_with_fault(exc, action, opts, out=sys.stderr):
     """
     Print an error message and exit.  exc is the RPCException that caused the
@@ -72,4 +87,11 @@
 	code = -1
 
+    if exc.code == service_error.access and opts.auth_log:
+	try:
+	    log_authentication(opts.auth_log, action, 'failed', exc.proof)
+	except EnvironmentError, e:
+	    print >>sys.stderr, "Failed to log to %s: %s" % \
+		    (e.filename, e.strerror)
+
     print>>out, codestr
     print>>out, "Description: %s" % exc.desc
@@ -81,5 +103,5 @@
     XMLPRC calls.
     """
-    def __init__(self, desc=None, code=None, errstr=None):
+    def __init__(self, desc=None, code=None, errstr=None, proof=None):
 	RuntimeError.__init__(self)
 	self.desc = desc
@@ -87,4 +109,5 @@
 	else: self.code = -1
 	self.errstr = errstr
+	self.proof = proof
 
 class CertificateMismatchError(RuntimeError): pass
@@ -208,5 +231,5 @@
 	    except service_error, e:
 		raise RPCException(desc=e.desc, code=e.code, 
-			errstr=e.code_string())
+			errstr=e.code_string(), proof=e.proof)
     elif transport == "xmlrpc":
 	if serialize_only:
Index: fedd/federation/deter_internal_access.py
===================================================================
--- fedd/federation/deter_internal_access.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/deter_internal_access.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -215,5 +215,7 @@
 	aid = "%s" % auth_attr
 	attrs = req.get('fedAttr', [])
-	if not self.auth.check_attribute(fid, auth_attr):
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    raise service_error(service_error.access, "Access denied")
 	else:
@@ -271,5 +273,6 @@
 		'allocID': req['allocID'],
 		'allocationLog': logv,
-		'segmentdescription': { 'topdldescription': rtopo.to_dict() }
+		'segmentdescription': { 'topdldescription': rtopo.to_dict() },
+		'proof': proof.to_dict(),
 		}
 	retval = copy.deepcopy(self.state[aid]['started'])
@@ -289,5 +292,7 @@
 
 	self.log.debug("Terminate request for %s" %aid)
-	if not self.auth.check_attribute(fid, auth_attr):
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    raise service_error(service_error.access, "Access denied")
 
@@ -308,3 +313,3 @@
 	self.state[aid]['vlan'] = None
 	self.state_lock.release()
-	return { 'allocID': req['allocID'] }
+	return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
Index: fedd/federation/dragon_access.py
===================================================================
--- fedd/federation/dragon_access.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/dragon_access.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -418,5 +418,5 @@
 	return (repo, alloc_log)
 
-    def finalize_experiment(self, topo, vlan_no, gri, aid, alloc_id):
+    def finalize_experiment(self, topo, vlan_no, gri, aid, alloc_id, proof):
 	"""
 	Place the relevant information in the global state block, and prepare
@@ -442,4 +442,5 @@
 		    'topdldescription': rtopo.to_dict()
 		    },
+		'proof': proof.to_dict(),
 		}
 	retval = copy.deepcopy(self.state[aid]['started'])
@@ -462,5 +463,7 @@
 	aid = "%s" % auth_attr
 	attrs = req.get('fedAttr', [])
-	if not self.auth.check_attribute(fid, auth_attr):
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    raise service_error(service_error.access, "Access denied")
 	else:
@@ -508,5 +511,5 @@
 	if gri:
 	    return self.finalize_experiment(topo, vlan_no, gri, aid, 
-		    req['allocID'])
+		    req['allocID'], proof)
 	elif err:
 	    raise service_error(service_error.federant,
@@ -526,5 +529,7 @@
 	self.log.debug("Terminate request for %s" %aid)
 	attrs = req.get('fedAttr', [])
-	if not self.auth.check_attribute(fid, auth_attr):
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    raise service_error(service_error.access, "Access denied")
 
@@ -549,3 +554,3 @@
 	self.log.debug("Stop segment for GRI: %s" %gri)
 	self.stop_segment(user, gri)
-	return { 'allocID': req['allocID'] }
+	return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
Index: fedd/federation/emulab_access.py
===================================================================
--- fedd/federation/emulab_access.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/emulab_access.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -24,4 +24,5 @@
 from service_error import service_error
 from remote_service import xmlrpc_handler, soap_handler, service_caller
+from proof import proof as access_proof
 
 import httplib
@@ -480,7 +481,8 @@
 
 	if self.auth_type == "legacy":
-	    found, dyn, owners = self.legacy_lookup_access(req, fid)
+	    found, dyn, owners= self.legacy_lookup_access(req, fid)
+	    proof = access_proof("me", fid, "create")
 	elif self.auth_type == 'abac':
-	    found, dyn, owners = self.lookup_access(req, fid, filter=pf)
+	    found, dyn, owners, proof = self.lookup_access(req, fid, filter=pf)
 	else:
 	    raise service_error(service_error.internal, 
@@ -523,5 +525,5 @@
 		    "Can't open %s/%s : %s" % (self.certdir, aid, e))
 	resp = self.build_access_response({ 'fedid': allocID } ,
-		ap, services)
+		ap, services, proof)
 	return resp
 
@@ -570,5 +572,7 @@
 	self.log.debug("[access] deallocation requested for %s by %s" % \
 		(aid, fid))
-	if not self.auth.check_attribute(fid, auth_attr):
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    self.log.debug("[access] deallocation denied for %s", aid)
 	    raise service_error(service_error.access, "Access Denied")
@@ -624,5 +628,5 @@
 	    self.log.debug("Removing %s" % cf)
 	    os.remove(cf)
-	    return { 'allocID': req['allocID'] } 
+	    return { 'allocID': req['allocID'], 'proof': proof.to_dict() } 
 	else:
 	    self.state_lock.release()
@@ -997,5 +1001,5 @@
 	return (ename, proj, user, pubkey_base, secretkey_base, alloc_log)
 
-    def finalize_experiment(self, starter, topo, aid, alloc_id):
+    def finalize_experiment(self, starter, topo, aid, alloc_id, proof):
 	"""
 	Store key bits of experiment state in the global repository, including
@@ -1022,5 +1026,6 @@
 		    'topdldescription': topo.clone().to_dict()
 		    },
-		'embedding': embedding
+		'embedding': embedding,
+		'proof': proof.to_dict(),
 		}
 	retval = copy.copy(self.allocation[aid]['started'])
@@ -1046,5 +1051,8 @@
 	aid = "%s" % auth_attr
 	attrs = req.get('fedAttr', [])
-	if not self.auth.check_attribute(fid, auth_attr):
+
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    raise service_error(service_error.access, "Access denied")
 	else:
@@ -1112,5 +1120,6 @@
 
 	if rv:
-	    return self.finalize_experiment(starter, topo, aid, req['allocID'])
+	    return self.finalize_experiment(starter, topo, aid, req['allocID'],
+		    proof)
 	elif err:
 	    raise service_error(service_error.federant,
@@ -1128,5 +1137,8 @@
 	aid = "%s" % auth_attr
 	attrs = req.get('fedAttr', [])
-	if not self.auth.check_attribute(fid, auth_attr):
+
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    raise service_error(service_error.access, "Access denied")
 
@@ -1157,3 +1169,3 @@
 		debug=self.create_debug, boss=self.boss, cert=self.xmlrpc_cert)
 	stopper(self, user, proj, ename)
-	return { 'allocID': req['allocID'] }
+	return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
Index: fedd/federation/experiment_control.py
===================================================================
--- fedd/federation/experiment_control.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/experiment_control.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -801,4 +801,5 @@
 	    self.response = None
 	    self.node = { }
+	    self.proof = None
 
 	def make_map(self, resp):
@@ -850,4 +851,5 @@
 			    self.log_collector.write(line)
 		    self.make_map(r['StartSegmentResponseBody'])
+		    if 'proof' in r: self.proof = r['proof']
 		    self.response = r
 		else:
@@ -1021,4 +1023,5 @@
 	    # mapping.
 	    embedding = [ ]
+	    proofs = { }
 	    for s in starters:
 		for k, v in s.node.items():
@@ -1028,4 +1031,6 @@
 			'testbed': s.testbed
 			})
+		if s.proof:
+		    proofs[s.testbed] = s.proof
             log.info("[start_segment]: Experiment %s active" % eid)
 
@@ -1044,4 +1049,10 @@
 		top.to_dict()
 	self.state[eid]['embedding'] = embedding
+	# Append startup proofs
+	for f in self.state[eid]['federant']:
+	    if 'name' in f and 'localname' in f['name']:
+		if f['name']['localname'] in proofs:
+		    f['proof'].append(proofs[f['name']['localname']])
+
         if self.state_filename: self.write_state()
         self.state_lock.release()
@@ -1326,8 +1337,12 @@
 	    raise service_error(service_error.protocol,
                         "Bad proxy response")
+	if 'proof' not in r:
+	    raise service_error(service_error.protocol,
+                        "Bad access response (no access proof)")
         
 	tbparam[tb] = { 
 		"allocID" : r['allocID'],
 		"uri": uri,
+		"proof": [ r['proof'] ],
 		}
 
@@ -1498,6 +1513,10 @@
 	    self.auth.save()
 
-        if not self.auth.check_attribute(fid, 'new'):
-            raise service_error(service_error.access, "New access denied")
+	access_ok, proof = self.auth.check_attribute(fid, 'new', 
+		with_proof=True)
+
+        if not access_ok:
+            raise service_error(service_error.access, "New access denied",
+		    proof=[proof])
 
         try:
@@ -1543,5 +1562,6 @@
                 ],
                 'experimentStatus': 'empty',
-                'experimentAccess': { 'X509' : expcert }
+                'experimentAccess': { 'X509' : expcert },
+		'proof': proof.to_dict(),
             }
 
@@ -1814,11 +1834,12 @@
             raise service_error(service_error.req, "No request?")
 
-        self.check_experiment_access(fid, key)
+        proof = self.check_experiment_access(fid, key)
 
         self.state_lock.acquire()
         if self.state.has_key(key):
             if self.state[key].has_key('vtopo'):
-                rv = { 'experiment' : {keytype: key },\
-                        'vtopo': self.state[key]['vtopo'],\
+                rv = { 'experiment' : {keytype: key },
+                        'vtopo': self.state[key]['vtopo'],
+			'proof': proof.to_dict(), 
                     }
             else:
@@ -1858,11 +1879,12 @@
             raise service_error(service_error.req, "No request?")
 
-        self.check_experiment_access(fid, key)
+        proof = self.check_experiment_access(fid, key)
 
         self.state_lock.acquire()
         if self.state.has_key(key):
             if self.state[key].has_key('vis'):
-                rv =  { 'experiment' : {keytype: key },\
-                        'vis': self.state[key]['vis'],\
+                rv =  { 'experiment' : {keytype: key },
+                        'vis': self.state[key]['vis'],
+			'proof': proof.to_dict(), 
                         }
             else:
@@ -1885,5 +1907,7 @@
 	between when it was started and the beginning of resource allocation.
 	This is basically the information about each local allocation.  This
-	fills in the values of the placeholder allocation in the state.
+	fills in the values of the placeholder allocation in the state.  It
+	also collects the access proofs and returns them as dicts for a
+	response message.
 	"""
 	# save federant information
@@ -1893,4 +1917,5 @@
 		    'allocID' : tbparams[k]['allocID'],
 		    'uri': tbparams[k]['uri'],
+		    'proof': tbparams[k]['proof'],
 		}
 
@@ -1903,7 +1928,11 @@
 		[ tbparams[tb]['federant'] for tb in tbparams.keys() \
 		    if tbparams[tb].has_key('federant') ]
+	# Access proofs for the response message
+	proofs = [copy.deepcopy(p) for k in tbparams.keys()\
+		    for p in tbparams[k]['federant']['proof']]
 	if self.state_filename: 
 	    self.write_state()
 	self.state_lock.release()
+	return proofs
 
     def clear_placeholder(self, eid, expid, tmpdir):
@@ -1945,5 +1974,5 @@
 
 	# Make sure that the caller can talk to us
-        self.check_experiment_access(fid, key)
+        proof = self.check_experiment_access(fid, key)
 
 	# Install the testbed map entries supplied with the request into a copy
@@ -2030,6 +2059,6 @@
 	    vtopo = topdl.topology_to_vtopo(top)
 	    vis = self.genviz(vtopo)
-	    self.save_federant_information(allocated, tbparams, eid, vtopo, 
-		    vis, top)
+	    proofs = self.save_federant_information(allocated, tbparams, 
+		    eid, vtopo, vis, top)
         except service_error, e:
             # If something goes wrong in the parse (usually an access error)
@@ -2042,13 +2071,4 @@
         # Start the background swapper and return the starting state.  From
         # here on out, the state will stick around a while.
-
-	# XXX: I think this is redundant
-	# Let users touch the state
-	# self.auth.set_attribute(fid, expid)
-	# self.auth.set_attribute(expid, expid)
-	# Override fedids can manipulate state as well
-	# for o in self.overrides:
-	    # self.auth.set_attribute(o, expid)
-	# self.auth.save()
 
         # Create a logger that logs to the experiment's state object as well as
@@ -2076,4 +2096,5 @@
                 ],
                 'experimentStatus': 'starting',
+		'proof': [ proof.to_dict() ] + proofs,
             }
 
@@ -2121,8 +2142,11 @@
             key = self.get_experiment_fedid(key)
 
-        if self.auth.check_attribute(fid, key):
-            return True
+	access_ok, proof = self.auth.check_attribute(fid, key, with_proof=True)
+
+        if access_ok:
+            return proof
         else:
-            raise service_error(service_error.access, "Access Denied")
+            raise service_error(service_error.access, "Access Denied",
+		proof)
 
 
@@ -2133,4 +2157,5 @@
 	"""
 	self.log.info("Get handler %s %s" % (path, fid))
+	# XXX: log proofs?
 	if self.auth.check_attribute(fid, path):
 	    return ("%s/%s" % (self.repodir, path), "application/binary")
@@ -2138,5 +2163,5 @@
 	    return (None, None)
 
-    def clean_info_response(self, rv):
+    def clean_info_response(self, rv, proof):
         """
         Remove the information in the experiment's state object that is not in
@@ -2162,4 +2187,5 @@
                 if f.has_key('allocID'): del f['allocID']
 		if f.has_key('uri'): del f['uri']
+	rv['proof'] = proof.to_dict()
 
         return rv
@@ -2188,5 +2214,5 @@
             raise service_error(service_error.req, "No request?")
 
-        self.check_experiment_access(fid, key)
+        proof = self.check_experiment_access(fid, key)
 
         # The state may be massaged by the service function that called
@@ -2199,5 +2225,5 @@
 
         if rv:
-            return self.clean_info_response(rv)
+            return self.clean_info_response(rv, proof)
         else:
             raise service_error(service_error.req, "No such experiment")
@@ -2207,10 +2233,10 @@
         Return all the stored info that this fedid can access
         """
-        rv = { 'info': [ ] }
+	rv = { 'info': [ ], 'proof': [ ] }
 
         self.state_lock.acquire()
         for key in [ k for k in self.state.keys() if isinstance(k, fedid)]:
 	    try:
-		self.check_experiment_access(fid, key)
+		proof = self.check_experiment_access(fid, key)
 	    except service_error, e:
 		if e.code == service_error.access:
@@ -2222,6 +2248,7 @@
             if self.state.has_key(key):
                 e = copy.deepcopy(self.state[key])
-                e = self.clean_info_response(e)
+                e = self.clean_info_response(e, proof)
                 rv['info'].append(e)
+		rv['proof'].append(proof.to_dict())
         self.state_lock.release()
         return rv
@@ -2351,5 +2378,4 @@
 	    if tmpdir: self.remove_dirs(tmpdir)
 
-
     def terminate_experiment(self, req, fid):
         """
@@ -2364,5 +2390,5 @@
 
 	key = self.get_experiment_key(req, 'experiment')
-        self.check_experiment_access(fid, key)
+        proof = self.check_experiment_access(fid, key)
         exp = req.get('experiment', False)
         force = req.get('force', False)
@@ -2416,8 +2442,9 @@
 	    if repo:
 		self.remove_dirs("%s/%s" % (self.repodir, repo))
-		
+	
             return { 
                     'experiment': exp , 
                     'deallocationLog': string.join(dealloc_list, ''),
+		    'proof': [proof.to_dict()],
                     }
         else:
@@ -2438,5 +2465,10 @@
 	rv = { 'name': name }
 
-        if name and self.auth.check_attribute(fid, name):
+	if not name:
+	    raise service_error(service_error.req, "No name?")
+
+	access_ok, proof = self.auth.check_attribute(fid, name, with_proof=True)
+
+        if access_ok:
 	    self.log.debug("[GetValue] asking for %s " % name)
 	    try:
@@ -2448,8 +2480,10 @@
 	    if v is not None:
 		rv['value'] = v
+	    rv['proof'] = proof.to_dict()
 	    self.log.debug("[GetValue] got %s from %s" % (v, name))
 	    return rv
         else:
-            raise service_error(service_error.access, "Access Denied")
+            raise service_error(service_error.access, "Access Denied",
+		    proof=proof)
 	
 
@@ -2466,5 +2500,10 @@
 	v = req.get('value', '')
 
-        if name and self.auth.check_attribute(fid, name):
+	if not name:
+	    raise service_error(service_error.req, "No name?")
+
+	access_ok, proof = self.auth.check_attribute(fid, name, with_proof=True)
+
+        if access_ok:
 	    try:
 		self.synch_store.set_value(name, v)
@@ -2479,5 +2518,6 @@
 		raise service_error(service_error.federant, 
 			"Synch key %s revoked" % name)
-	    return { 'name': name, 'value': v }
+		return { 'name': name, 'value': v, 'proof': proof.to_dict() }
         else:
-            raise service_error(service_error.access, "Access Denied")
+            raise service_error(service_error.access, "Access Denied",
+		    proof=proof)
Index: fedd/federation/proof.py
===================================================================
--- fedd/federation/proof.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
+++ fedd/federation/proof.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -0,0 +1,156 @@
+#/usr/local/bin/python
+
+
+import ABAC
+import sys
+import os, os.path
+
+from string import join
+from base64 import b64encode, b64decode
+from xml.parsers.expat import ParserCreate
+
+class proof:
+    """
+    Class to make manipulating proofs a little simpler.  Keeps track of the
+    target and does some of the credential formatting.
+    """
+    def __init__(self, me, p, a, c):
+	"""
+	P is the principal (string), a is the attribute to prove (string)
+	and c is the list of abac certificates that prove (or partially
+	prove) the connection.
+	"""
+	self.prover = "%s" % me
+	self.principal = "%s" % p
+	self.attribute = a
+	self.creds = c or []
+
+    def creds_to_certs(self):
+	ids = set([c.issuer_cert() for c in self.creds])
+	attrs = set([c.attribute_cert() for c in self.creds])
+	return list(ids) + list(attrs)
+
+    def __str__(self):
+	"""
+	User friendly output format
+	"""
+	rv = "prover: %s\nprincipal: %s\nattribute: %s" % \
+		(self.prover, self.principal, self.attribute)
+	if self.creds:
+	    rv += '\n'
+	    rv += join(["%s <- %s"%(c.head().string(), c.tail().string()) \
+		for c in self.creds], '\n')
+	return rv
+
+    def to_dict(self):
+	"""
+	Dict with the credentials encoded as X.509 certificates
+	"""
+
+	return { 
+		'prover': self.prover,
+		'principal': self.principal,
+		'attribute': self.attribute,
+		'credential': self.creds_to_certs(),
+		}
+    def to_context(self):
+	"""
+	Return an ABAC context containing just the credentials in the proof
+	"""
+	ctxt = ABAC.Context()
+	for c in self.creds_to_certs():
+	    if ctxt.load_id_chunk(c) != ABAC.ABAC_CERT_SUCCESS:
+		ctxt.load_attribute_chunk(c)
+	return ctxt
+
+    def to_xml(self):
+	"""
+	Convert the proof to a simple XML format
+	"""
+	rv = "<proof>\n"
+	rv += "<prover>%s</prover>\n" % self.prover
+	rv += "<principal>%s</principal>\n" % self.principal
+	rv += "<attribute>%s</attribute>\n" % self.attribute
+	for c in self.creds_to_certs(): 
+	    rv += "<credential>%s</credential>\n" % b64encode(c)
+	rv += "</proof>"
+	return rv
+
+    def save_to_xml(self, fn, mode='w'):
+	"""
+	Write an XML reprsentation to fn.  Mode is 'w' or 'a' to overwrite or
+	append to the file.
+	"""
+	f = open(fn, mode)
+	print >>f, self.to_xml()
+	f.close()
+
+    @staticmethod
+    def from_xml(filename=None, file=None, string=None):
+	"""
+	Create a proof from the XML representation
+	"""
+	class parse_proof:
+	    def element_end(self, name):
+		if name =='proof':
+		    self.proof = proof(self.me, self.pr, self.attr, 
+			    self.context.credentials())
+		elif name =='prover':
+		    self.me = self.chars.strip()
+		elif name =='principal':
+		    self.pr = self.chars.strip()
+		elif name == 'attribute':
+		    self.attr = self.chars.strip()
+		elif name == 'credential':
+		    c = b64decode(self.chars)
+		    if self.context.load_id_chunk(c) != ABAC.ABAC_CERT_SUCCESS:
+			self.context.load_attribute_chunk(c)
+		else:
+		    print "Doh!"
+
+	    def element_start(self, name, attrs):
+		self.chars = ''
+
+	    def cdata(self, data):
+		self.chars += data
+
+
+	    def __init__(self):
+		self.p = ParserCreate()
+		self.me = None
+		self.pr = None
+		self.attr = None
+		self.proof = None
+		self.context = ABAC.Context()
+		self.chars = ''
+
+		self.p.CharacterDataHandler = self.cdata
+		self.p.EndElementHandler = self.element_end
+		self.p.StartElementHandler = self.element_start
+
+	pp = parse_proof()
+	if filename: pp.p.ParseFile(open(filename, 'r'))
+	elif file: pp.p.ParseFile(file)
+	elif string: pp.p.Parse(string, True)
+	else: print "Doh1"
+
+	return pp.proof
+
+    @staticmethod
+    def from_dict(d):
+	"""
+	Reverse the to_dict process
+	"""
+	if d: 
+	    if 'prover' in d and 'principal' in d and 'attribute' in d:
+		ac = ABAC.Context()
+		self = proof(d['prover'], d['principal'], d['attribute'], None)
+		for c in d.get('credential', []):
+		    if ac.load_id_chunk(c) != ABAC.ABAC_CERT_SUCCESS:
+			ac.load_attribute_chunk(c)
+		self.creds = ac.credentials()
+		return self
+	    else:
+		return None
+	else:
+	    return None
Index: fedd/federation/protogeni_access.py
===================================================================
--- fedd/federation/protogeni_access.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/protogeni_access.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -215,5 +215,5 @@
 	    return (None, None)
 
-    def build_access_response(self, alloc_id, services):
+    def build_access_response(self, alloc_id, services, proof):
         """
         Create the SOAP response.
@@ -230,5 +230,6 @@
 		'fedAttr': [
 		    { 'attribute': 'domain', 'value': self.domain } , 
-		]
+		],
+		'proof': proof.to_dict()
             }
 	if self.dragon_endpoint:
@@ -262,5 +263,5 @@
 
 	# Request for this fedd
-	found, match, owners = self.lookup_access(req, fid)
+	found, match, owners, proof = self.lookup_access(req, fid)
 	services, svc_state = self.export_services(req.get('service',[]),
 		None, None)
@@ -288,5 +289,5 @@
 	    raise service_error(service_error.internal, 
 		    "Can't open %s/%s : %s" % (self.certdir, aid, e))
-	return self.build_access_response({ 'fedid': allocID }, None)
+	return self.build_access_response({ 'fedid': allocID }, None, proof)
 
 
@@ -312,5 +313,7 @@
 
 	self.log.debug("[access] deallocation requested for %s", aid)
-	if not self.auth.check_attribute(fid, auth_attr):
+	access_ok , proof = self.auth.check_attribute(fid, auth_attr,
+		with_proof=True)
+	if not access_ok:
 	    self.log.debug("[access] deallocation denied for %s", aid)
 	    raise service_error(service_error.access, "Access Denied")
@@ -327,5 +330,5 @@
 	    self.log.debug("Removing %s" % cf)
 	    os.remove(cf)
-	    return { 'allocID': req['allocID'] } 
+	    return { 'allocID': req['allocID'], 'proof': proof.to_dict() } 
 	else:
 	    self.state_lock.release()
@@ -1251,5 +1254,5 @@
 		cpw, alloc_log)
 
-    def finalize_experiment(self, topo, nodes, aid, alloc_id):
+    def finalize_experiment(self, topo, nodes, aid, alloc_id, proof):
 	# Copy the assigned names into the return topology
 	rvtopo = topo.clone()
@@ -1273,4 +1276,5 @@
 		    'topdldescription': rvtopo.to_dict() },
 		'embedding': embedding,
+		'proof': proof.to_dict(),
 		}
 	retval = copy.deepcopy(self.allocation[aid]['started'])
@@ -1295,5 +1299,7 @@
 	aid = "%s" % auth_attr
 	attrs = req.get('fedAttr', [])
-	if not self.auth.check_attribute(fid, auth_attr):
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    raise service_error(service_error.access, "Access denied")
 	else:
@@ -1356,5 +1362,6 @@
 
 	if rv:
-	    return self.finalize_experiment(topo, nodes, aid, req['allocID'])
+	    return self.finalize_experiment(topo, nodes, aid, req['allocID'],
+		    proof)
 	elif err:
 	    raise service_error(service_error.federant,
@@ -1392,5 +1399,7 @@
 	aid = "%s" % auth_attr
 	attrs = req.get('fedAttr', [])
-	if not self.auth.check_attribute(fid, auth_attr):
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
 	    raise service_error(service_error.access, "Access denied")
 
@@ -1419,5 +1428,5 @@
 	self.stop_segment(segment_commands, user, staging, slice_cred,
 		slice_urn, cf, cpw)
-	return { 'allocID': req['allocID'] }
+	return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
 
     def renew_segment(self, segment_commands, name, scred, slice_urn, interval, 
Index: fedd/federation/remote_service.py
===================================================================
--- fedd/federation/remote_service.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/remote_service.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -16,4 +16,5 @@
 import httplib
 
+from proof import proof
 from service_error import service_error
 from xmlrpclib import ServerProxy, dumps, loads, Fault, Error, Binary
@@ -66,4 +67,7 @@
     # A map used to encapsulate fedids into xmlrpclib.Binary objects
     encap_fedids = (('fedid', to_binary),)
+
+    # fields that are never unicoded, because they represent non strings.
+    do_not_unicode = set(['credential'])
 
     @staticmethod
@@ -172,5 +176,6 @@
 	if isinstance(obj, dict):
 	    for k in obj.keys():
-		obj[k] = remote_service_base.make_unicode(obj[k])
+		if k not in remote_service_base.do_not_unicode:
+		    obj[k] = remote_service_base.make_unicode(obj[k])
 	    return obj
 	elif isinstance(obj, basestring) and not isinstance(obj, unicode):
@@ -513,6 +518,10 @@
 			    'desc': e.fault.string or "Something Weird" }
 		if ee:
+		    if 'proof' in ee: 
+			pl = [ proof.from_dict(p) for p in ee['proof']]
+		    else: 
+			pl = None
 		    raise service_error(ee.get('code', 'no code'), 
-			    ee.get('desc','no desc'))
+			    ee.get('desc','no desc'), proof=pl)
 		else:
 		    raise service_error(service_error.internal,
Index: fedd/federation/server.py
===================================================================
--- fedd/federation/server.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/server.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -136,5 +136,5 @@
 	    self.send_fault(f)
 	    resp = None
-	
+
 	if resp != None:
 	    sw = SoapWriter()
@@ -201,4 +201,13 @@
 		de._errstr=e.code_string()
 		de._desc=e.desc
+		for p in e.proof:
+		    dp = ns0.proofType_Def(ns0.proofType_Def.schema, 
+			    "proof").pyclass()
+		    dp._prover = p.prover
+		    dp._principal = p.principal
+		    dp._attribute = p.attribute
+		    dp._credential = p.creds_to_certs()
+		    if de._proof: de._proof.append(dp)
+		    else: de._proof = [dp]
 		if  e.is_server_error():
 		    raise Fault(Fault.Server, e.code_string(), detail=de)
Index: fedd/federation/service_error.py
===================================================================
--- fedd/federation/service_error.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/service_error.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -28,7 +28,9 @@
 	    federant, connect)
 
-    def __init__(self, code=None, desc=None, from_string=None):
+    def __init__(self, code=None, desc=None, from_string=None, proof=None):
 	self.code = code
 	self.desc = desc
+	self.proof = proof or []
+	if not isinstance (self.proof, list): self.proof = [ proof ]
 	if code == None:
 	    self.set_code_from_string(from_string)
Index: fedd/federation/skeleton_access.py
===================================================================
--- fedd/federation/skeleton_access.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/skeleton_access.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -183,6 +183,9 @@
 	aid = "%s" % auth_attr
 	# Authorization check
-	if not self.auth.check_attribute(fid, auth_attr):
-	    raise service_error(service_error.access, "Access denied")
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
+	    raise service_error(service_error.access, "Access denied", 
+		    proof=proof)
 	else:
 	    # See if this is a replay of an earlier succeeded StartSegment -
@@ -242,5 +245,6 @@
 		'allocID': req['allocID'],
 		'allocationLog': "Allocatation complete",
-		'segmentdescription': { 'topdldescription': topo.to_dict() }
+		'segmentdescription': { 'topdldescription': topo.to_dict() },
+		'proof': proof.to_dict(),
 		}
 	retval = copy.deepcopy(self.state[aid]['started'])
@@ -266,6 +270,9 @@
 	self.log.debug("Terminate request for %s" %aid)
 	# Check authorization
-	if not self.auth.check_attribute(fid, auth_attr):
-	    raise service_error(service_error.access, "Access denied")
+	access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
+		with_proof=True)
+	if not access_ok:
+	    raise service_error(service_error.access, "Access denied", 
+		    proof=proof)
 
 	# Authorized: remove the integer from the allocation.  A more complex
@@ -281,3 +288,3 @@
 	self.state_lock.release()
    
-	return { 'allocID': req['allocID'] }
+	return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
Index: fedd/federation/thread_pool.py
===================================================================
--- fedd/federation/thread_pool.py	(revision 2627eb377cd2380a86775e63b5cf1637ffe01c8f)
+++ fedd/federation/thread_pool.py	(revision f938d668ad17ef80f4b9d549d56c8c151cbf4206)
@@ -4,4 +4,5 @@
 import time
 from threading import Lock, Thread, Condition
+from service_error import service_error
 
 class thread_pool:
