2 from OpenSSL import crypto
3 from M2Crypto import X509
4 from M2Crypto import SSL
11 CRED_GRANT_TIME = 3600000 #in seconds
12 ACC_GRANT_TIME = 10000000
13 TOP_LEVEL_CERTS_DIR = "trusted_certs"
17 creds= ["","","","","","","","","","","","","","","","","","","","","","","","",""]
20 creds[2] = "remove_self"
22 creds[4] = "update_self"
24 creds[6] = "lookup_self"
26 creds[8] = "getCredential"
27 creds[9] = "getAccounting"
28 creds[10] = "getTicket" # = embed privilege in the docs
29 creds[11] = "splitTicket"
30 creds[12] = "redeemTicket"
34 creds[16] = "listSlices"
35 creds[17] = "getStatus"
36 creds[18] = "getSlice"
38 creds[20] = "delegate"
39 creds[21] = "instantiate"
46 self.acc = Accounting() #accounting information
47 self.cred = Credential() #credential information
49 #info_certs looks like: [{'hrn':None, 'uuid':0}]
55 return self.info_certs[0]['hrn']
57 return self.info_certs[0]['uuid']
59 #info_certs looks like: [{'operation_set':None, 'on_interfaces':None}]
60 #operation_set looks like: [0:['register','update','update_self',..], 1:['getTicket', 'splitTicket',..],..]
61 #on_interfaces looks like: [{'lbl':0, 'name':'planetlab.jp.jaist', 'type':'registry'},..{}]
64 self.info_certs = [] #list of operations and list of sites on which operations can be performed
67 return self.info_certs[0]
69 #type: 'accounting' or 'credential'
70 #folder: directory of that the certificate should reside
71 #reg_type: 'slice' or 'component'
72 #hrn: name of the object to get certificates for
73 #server: the server instance to do operations calls with in the internal tree
74 #internal_tree: tree for internal cert renewals
75 #auth_addr: authority to ask for certificates
76 #sec: security module to perform auth. protocol with the remote peer
77 #return 0: no renewal done, 1: renewal done, None: error
78 def renew_cert(type, folder, reg_type, hrn, server, internal_tree, auth_addr, sec):
79 #check if necessary to renew
80 if type == 'accounting':
81 fname = folder+'/acc_file'
83 fname = folder+'/cred_file'
84 if os.path.exists(fname) and is_valid_chain(fname):
87 parent_hrn = obtain_authority(hrn)
88 hrn_suffix = get_leaf(hrn)
89 id_file = folder+'/'+hrn_suffix+'.cert'
91 if not os.path.exists(folder) or not os.path.exists(id_file):
92 print 'Id file for '+hrn+' does not exist.\n'
94 #decide if remote call
97 dbinfo = determine_dbinfo(parent_hrn, internal_tree)
101 if type == 'accounting':
102 #obtain the accounting from parent, write to file
104 g["hrn"] = parent_hrn
105 g['registry'] = reg_type
106 g["account_name"] = hrn
108 record = {'g_params':g, 'p_params':p}
109 dbinfo = determine_dbinfo(parent_hrn, internal_tree)
110 parentkeyinfo = internal_tree.determine_keyinfo(parent_hrn, server, type)
111 open(fname, 'w').write(server.getAccounting(record, dbinfo, parentkeyinfo, X509.load_cert(id_file)))
113 #obtain the credential from parent, write to file
115 g["hrn"] = parent_hrn
116 if reg_type == 'slice':
117 g['cred_name'] = 'registry:slc'
119 g['cred_name'] = 'registry:comp'
121 record = {'g_params':g, 'p_params':p}
122 dbinfo = determine_dbinfo(parent_hrn, internal_tree)
123 id = crypto.load_certificate(crypto.FILETYPE_PEM, open(id_file).read())
125 parentkeyinfo = internal_tree.determine_keyinfo(parent_hrn, server, type)
126 open(fname, 'w').write(server.getCredential(record, dbinfo, parentkeyinfo, peerinfo))
127 else: #if not local call
128 if obtain_authority(hrn) == '': #we are at the root
129 id_key_file = folder+'/'+hrn_suffix+'.pkey'
130 id_cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(id_file).read())
131 id_pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, open(id_key_file).read())
132 peer_cred_str = open(fname).read()
133 c_pem = X509.load_cert_string("-----BEGIN CERTIFICATE-----"+peer_cred_str.split("-----BEGIN CERTIFICATE-----")[1])
134 if type == 'accounting':
135 cert_uuid = c_pem.get_ext("subjectAltName").get_value().split('http://')[1].split('#')[2].split('uuid:')[1]
136 acc = create_acc(id_cert, id_pkey, id_cert.get_pubkey(), hrn, cert_uuid)
137 open(fname, 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, acc))
139 rights = c_pem.get_ext("subjectAltName").get_value().split('http://')[1].split('credential_set:')[1]
140 cred = create_cred(id_cert, id_pkey, id_cert.get_pubkey(), 'Registry credentials', rights)
141 open(fname, 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cred))
144 g = {"hrn":obtain_authority(hrn)}
145 if type == 'accounting':
146 operation = "getAccounting";
148 g['registry'] = reg_type
149 g["account_name"] = hrn
151 operation = "getCredential"
153 if reg_type == 'slice':
154 g['cred_name'] = 'registry:slc'
156 g['cred_name'] = 'registry:comp'
158 message = {'opname':operation, 'g_params':g, 'p_params':p}
159 #connect to authority
160 server = SSL.Connection(sec.ctx)
161 server.connect(auth_addr)
162 peer = sec.auth_protocol(server)
163 #do the query and get the result
164 server.write(str(message))
165 result = server.read(MAX_CERT_SIZE*MAX_CERT_CHAIN)
166 open(fname, 'w').write(result)
169 #obtains a data structure for credentials out of a given credential string
170 #return info_cert: {'operation_set':{'register','remove',..}, 'on_interfaces':{{'lbl':'0', 'type':'registry:slc','name':'planetlab.jp'}, ..}}
171 def get_cred_info(credstr):
172 info_cert = {'operation_set':{}, 'on_interfaces':{}}
173 set = credstr.split('#')[1].split('credential_set:')[1]
174 set_arr = set.split(')')
175 for item in set_arr[0:len(set_arr)-1]:
176 item = item.split('(')[1].split('-')
177 if info_cert['operation_set'].has_key(item[1]):
178 info_cert['operation_set'][item[1]].append(creds[int(item[0])])
180 info_cert['operation_set'][item[1]] = [creds[int(item[0])]]
182 arr = credstr.split('#')
183 intlist = arr[2:len(arr)]
184 for interface in intlist:
185 iarr = interface.split(':')
186 newint = {'lbl':iarr[0]}
189 newint['type'] ='registry:slc'
191 newint['type'] ='registry:comp'
192 newint['name'] = iarr[3]
193 elif iarr[1] == 'comp':
194 newint['type'] ='component'
195 newint['name'] = iarr[2]
196 interface_list.append(newint)
197 info_cert['on_interfaces'] = interface_list
200 #given two credential statements, check if the first one can delegate the second one
201 #example: "'register' right on 'planetlab.jp' registry interface" is able to delegate a right called "'register' right on 'planetlab.jp.jaist' registry interface"
202 def check_delegation(info_cert1, info_cert2):
204 for interface in info_cert1['on_interfaces']:
205 is_reg = interface['type'] == 'registry:slc' or interface['type'] == 'registry:comp'
206 if is_reg and 'register' in info_cert1['operation_set'][interface['lbl']]:
208 for child_int in info_cert2['on_interfaces']:
209 if child_int['type'] == interface['type'] and not check_authority(child_int['name'],interface['name']):
217 Create a credential certificate given the parameters:
218 'authcert': the authority certificate
219 'authkey': the authority key
220 'pubkey': the public key belonging to the object to which the credential will be granted
221 'cname': the name of the credential being generated. It should be 'registry credentials' or a silce name like 'planetlab.jp.slice1'
222 'rights': - the sequence representing the set of operations/rights on specific interfaces
223 ex: (2-0)(4-0)(6-0)(7-0)(8-0)(9-0)(0-0)(1-1)(2-1)(3-1)(4-1)(5-1)(6-1)(7-1)(8-1)(9-1)#0:reg:planetlab.jp#1:reg:planetlab.jp.jaist
224 'time': time in seconds how long the life of credential is
226 def create_cred(authcert, authkey, pubkey, cname, rights, time=CRED_GRANT_TIME):
227 #calculate the credset
228 if rights == "slice":
229 rights = "(10-1)(11-1)(12-1)(13-1)(14-1)(15-1)(16-1)(17-1)(18-1)(23-1)"
231 rights = "(0-0)(1-0)(2-0)(3-0)(4-0)(5-0)(6-0)(7-0)(8-0)(9-0)(16-1)(17-1)(18-1)(19-1)"
233 rights = "(0-0)(1-0)(2-0)(3-0)(4-0)(5-0)(6-0)(7-0)(8-0)(9-0)(16-1)(17-1)(18-1)"
235 # check if the rights is in correct format ######################
237 #create the CSR for the credential certificate
238 key = createKeyPair(TYPE_RSA, 1024)
239 certReq = createCertRequest(key, {"CN" : cname})
240 certReq.set_pubkey(pubkey)
241 #create the credential certificate, return
242 content = "#credential_set:"+rights
243 ext = ("subjectAltName", 1, "URI:http://"+content)
245 cert = createCertificate(certReq, (authcert, authkey), 0, (0, int(time)), exts)
249 Create an accountability certificate given the parameters:
250 'authc': the name of the authority certificate file
251 'authk': the name of the authority key file
252 'pubkey': the name of the GID file belonging to the object to which the accountability will be assigned
253 'name': the name of the object, which is the accountability information itself
254 'uuid': the uuid of the object
255 'time': the date and time when the life of accounting information
257 def create_acc(authcert, authkey, pubkey, name, uuid, time=ACC_GRANT_TIME):
258 #create the CSR for the credential certificate
259 key = createKeyPair(TYPE_RSA, 1024)
260 certReq = createCertRequest(key, {"CN" : "GENI Accounting"})
261 certReq.set_pubkey(pubkey)
262 #create the accountability certificate, write into file
263 content = "#hrn:"+name+"#uuid:"+uuid
264 ext = ("subjectAltName", 1, "URI:http://"+content)
266 cert = createCertificate(certReq, (authcert, authkey), 0, (0, int(time)), exts)
270 Create self signed certificate and private key.
272 def create_self_cert(name):
273 key = createKeyPair(TYPE_RSA, 1024)
274 certReq = createCertRequest(key, {"CN":name})
275 cert = createCertificate(certReq, (certReq, key), 0, (0, 60*60*24*365*5)) # five years
276 open(name+'.pkey', 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
277 open(name+'.cert', 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
279 def verify_callback(preverify_ok, ctx):
283 def __init__(self, mode, id_file, id_key_file, acc_file, cred_file):
284 self.top_level_certs = []
286 file_list = os.listdir(TOP_LEVEL_CERTS_DIR)
287 for auth_file in file_list:
288 # XXX SMBAKER: fix .svn directory
289 if os.path.isfile(os.path.join(TOP_LEVEL_CERTS_DIR, auth_file)):
290 self.top_level_certs.append(X509.load_cert(TOP_LEVEL_CERTS_DIR+"/"+auth_file))
292 self.id_file = id_file
293 self.id_key_file = id_key_file
294 self.my_cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(id_file).read())
295 self.my_key = crypto.load_privatekey(crypto.FILETYPE_PEM, open(id_key_file).read())
296 self.acc_file = acc_file
297 self.cred_file = cred_file
299 self.ctx = SSL.Context()
300 self.ctx.load_cert(self.id_file,self.id_key_file)
301 self.ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9, callback=verify_callback)
303 # - exchange the accounting chains, store peer's accounting in peer.acc
304 # - check TTLs in certificates
305 # - check validity of the chain, but do not check top level's trustedness
306 #return 1: means there is a structural error in the chain
307 #return 2: one of the TTLs in the chain not valid
308 #return 3: public keys do not form a valid chain
309 def exchange_accounting(self, conn, peer):
311 if self.mode == 'server':
313 peer_acc_str = conn.read(MAX_CERT_SIZE*MAX_CERT_CHAIN)
315 acc_str = open(self.acc_file).read() #read the certificate chain from file
317 elif self.mode == 'client':
319 acc_str = open(self.acc_file).read() #read the certificate chain from file
322 peer_acc_str = conn.read(MAX_CERT_SIZE*MAX_CERT_CHAIN)
324 #construct peer_acc data structure
326 if peer_acc_str[0:27] != "-----BEGIN CERTIFICATE-----":
327 peer_acc.info_certs = "anonymous"
330 #divide the received chain into certificates
331 arr = peer_acc_str.split("-----BEGIN CERTIFICATE-----")
332 arr = arr[1:len(arr)]
333 for i in range(len(arr)):
334 arr[i] = "-----BEGIN CERTIFICATE-----"+arr[i]
336 c_pem = X509.load_cert_string(c_str)
337 hrn = c_pem.get_ext("subjectAltName").get_value().split('http://')[1].split('#')[1].split('hrn:')[1]
338 uuid = c_pem.get_ext("subjectAltName").get_value().split('http://')[1].split('#')[2].split('uuid:')[1]
339 peer_acc.info_certs.append({'hrn':hrn, 'uuid':uuid})
340 peer_acc.cert_chain.append(c_pem)
342 print "No valid chain received.\n"
344 #if structure is ok, go on with other checks
347 #check ttl for the first certificate in the chain:
348 if not (check_valid(peer_acc.cert_chain[0])):
352 for i in range(1, len(peer_acc.cert_chain)):
353 prevCert = peer_acc.cert_chain[i-1]
354 curCert = peer_acc.cert_chain[i]
356 if not check_valid(curCert):
358 #chain validity checks
359 if not prevCert.verify(curCert.get_pubkey()) :
363 elif chain_ok == False:
367 # - check the pubkey of first certificate if it matches the peer public key
368 # - check the name hierarchy
369 # - check the top level authority's trustedness
370 # return 0: accounting verified
371 # return 1: pubkey does not match peer pubkey
372 # return 2: name hierarch does not imply hrn
373 # return 3: top level authority is unknown
374 # return 4: unidentified error
375 def verify_accounting(self, peer):
380 if peer.acc.info_certs == 'anonymous':
383 #check the pubkey of the peer
384 if peer.acc.cert_chain[0].get_pubkey().as_pem(cipher=None) != peer.cert.get_pubkey().as_pem(cipher=None):
387 #check the name hierarchy
388 for i in range(len(peer.acc.info_certs)-1):
389 if check_authority(peer.acc.info_certs[i]['hrn'], peer.acc.info_certs[i+1]['hrn']) == False:
392 #check if the certificate ends with a sign of a trusted top level authority
395 last_cert_pubkey_pem = peer.acc.cert_chain[len(peer.acc.cert_chain)-1].get_pubkey().as_pem(cipher=None)
396 for cert in self.top_level_certs:
397 auth_pubkey_pem = cert.get_pubkey().as_pem(cipher=None)
398 if last_cert_pubkey_pem == auth_pubkey_pem:
404 if pubkey_ok == False:
406 elif hierarchy_ok == False:
408 elif trusted_auth == False:
412 print "Exception in verify_accounting:", e
415 # - exchange the credential chains, store peer's credential in peer.cred
416 # - check TTLs in certificates
417 # - check validity of the chain, but do not check top level's trustedness
418 #return 1: means there is a structural error in the chain
419 #return 2: one of the TTLs in the chain not valid
420 #return 3: public keys do not form a valid chain
421 def exchange_credential(self, conn, peer):
423 if self.mode == 'server':
425 peer_cred_str = conn.read(MAX_CERT_SIZE*MAX_CERT_CHAIN)
427 cred_str = open(self.cred_file).read() #read the certificate chain from file
429 elif self.mode == 'client':
431 cred_str = open(self.cred_file).read() #read the certificate chain from file
434 peer_cred_str = conn.read(MAX_CERT_SIZE*MAX_CERT_CHAIN)
436 #construct peer_cred data structure
437 peer_cred = peer.cred
438 if peer_cred_str[0:27] != "-----BEGIN CERTIFICATE-----":
439 peer_cred.info_certs = "no_credential"
442 #divide the received chain into certificates
443 arr = peer_cred_str.split("-----BEGIN CERTIFICATE-----")
444 arr = arr[1:len(arr)]
445 for i in range(len(arr)):
446 arr[i] = "-----BEGIN CERTIFICATE-----"+arr[i]
448 c_pem = X509.load_cert_string(c_str)
449 credstr = c_pem.get_ext("subjectAltName").get_value().split('http://')[1]
450 peer_cred.info_certs.append(get_cred_info(credstr))
451 peer_cred.cert_chain.append(c_pem)
453 print "Exception in exchange_credential:", e
454 print "No valid chain received.\n"
456 #if structure is ok, go on with other checks
459 #check ttl for the first certificate in the chain:
460 if not (check_valid(peer_cred.cert_chain[0])):
464 for i in range(1, len(peer_cred.cert_chain)):
465 prevCert = peer_cred.cert_chain[i-1]
466 curCert = peer_cred.cert_chain[i]
468 if not check_valid(curCert):
470 #chain validity check
471 if not prevCert.verify(curCert.get_pubkey()) :
475 elif chain_ok == False:
479 # - check the pubkey of first certificate if it matches the peer public key
480 # - check the delegation hierarchy: the delegated rights must be granted by an authority of the entity
481 # - check the top level authority's trustedness
482 # return 0: credential verified
483 # return 1: pubkey does not match peer pubkey
484 # return 2: delegation hierarchy is invalid
485 # return 3: top level authority is unknown
486 # return 4: unidentified error
487 def verify_credential(self, peer):
492 if peer.cred.info_certs == 'no_credential':
495 #check the pubkey of the peer
496 if peer.cred.cert_chain[0].get_pubkey().as_pem(cipher=None) != peer.cert.get_pubkey().as_pem(cipher=None):
499 #check the delegation hierarchy
500 for i in range(len(peer.cred.info_certs)-1):
501 if not check_delegation(peer.cred.info_certs[i+1], peer.cred.info_certs[i]) :
504 #check if the certificate ends with a sign of a trusted top level authority
507 last_cert_pubkey_pem = peer.cred.cert_chain[len(peer.cred.cert_chain)-1].get_pubkey().as_pem(cipher=None)
508 for cert in self.top_level_certs:
509 auth_pubkey_pem = cert.get_pubkey().as_pem(cipher=None)
510 if last_cert_pubkey_pem == auth_pubkey_pem:
516 if pubkey_ok == False:
518 elif hierarchy_ok == False:
520 elif trusted_auth == False:
526 def check_authorization(self, acc, cred, op_request):
528 allow_self = False #admission for ops on only the caller itself
530 opname = op_request['opname']
531 #anonymously callable functions are allowed always
532 if opname == 'getCredential' or opname == 'getAccounting':
535 g_params = op_request['g_params']
536 p_params = op_request['p_params']
537 target_hrn = g_params['hrn']
539 rec_type = g_params['type']
540 #determine which registry the call is on (slice or component)
541 if rec_type == 'user' or rec_type == 'slice' or rec_type == 'SA':
545 operation_set = cred.get_cred()['operation_set']
546 on_interfaces = cred.get_cred()['on_interfaces']
548 if opname == "update" or opname == "remove" or opname == "lookup":
550 #check callable operations within the credential
551 for interface in on_interfaces:
552 if interface['type'] == 'registry:'+reg_type and check_authority(target_hrn, interface['name']) and operation_set.has_key(interface['lbl']):
553 if opname in operation_set[interface['lbl']]:
556 elif is_self_op and opname + '_self' in operation_set[interface['lbl']] and acc.get_hrn() == target_hrn:
558 #if operation is allowed in name, perform additional checks for parameters
559 if allow or allow_self:
560 if opname == 'update':
561 if 'ttl' in g_params or 'uuid' in g_params or 'pointer' in g_params or (not allow and 'rights' in g_params):
565 if allow or allow_self:
570 print "exception in check_authorization:", e
573 def auth_protocol(self, conn):
574 if not conn.verify_ok():
575 v = conn.get_verify_result()
576 print "peer verification failed\n"
578 peer = PeerInfo() #keep the peer data who is currently logged in
580 peer_pem = conn.get_peer_cert()
582 #set the acc field and check it
583 result1 = result2 = -1
584 result1 = self.exchange_accounting(conn, peer)
586 if self.mode == 'server':
587 peer_decision = conn.read()
589 conn.write("ACC CHAIN_STRUCTURE_ERROR")
591 conn.write("ACC TTL_EXPIRED")
593 conn.write("ACC CHAIN_VERIFY_ERROR")
595 result2 = self.verify_accounting(peer)
597 conn.write("ACC SSL_PUBKEY_MISMATCH")
599 conn.write("ACC HRN_HIERARCHY_MISMATCH")
601 conn.write("ACC UNKNOWN_AUTHORITY")
603 conn.write("ACC UN-IDENTIFIED_ERROR")
606 if self.mode == 'client':
607 peer_decision = conn.read()
608 if (result1 != 0) or (result2 != 0) or peer_decision != "ACC OK" :
609 #close the connection and exit
613 #set the credential field and check it
614 result1 = result2 = -1
615 result1 = self.exchange_credential(conn,peer)
617 if self.mode == 'server':
618 peer_decision = conn.read()
620 conn.write("CRED CHAIN_STRUCTURE_ERROR")
622 conn.write("CRED TTL_EXPIRED")
624 conn.write("CRED CHAIN_VERIFY_ERROR")
626 result2 = self.verify_credential(peer)
628 conn.write("CRED SSL_PUBKEY_MISMATCH")
630 conn.write("CRED INVALID_DELEGATION")
632 conn.write("CRED UNKNOWN_AUTHORITY")
634 conn.write("CRED UN-IDENTIFIED_ERROR")
636 conn.write("CRED OK")
637 if self.mode == 'client':
638 peer_decision = conn.read()
639 if (result1 != 0) or (result2 != 0) or peer_decision != "CRED OK" :
640 #close the connection and exit
645 def is_valid_chain(chain_file):
646 chain_str = open(chain_file).read()
647 if chain_str[0:27] != "-----BEGIN CERTIFICATE-----":
650 #divide the received chain into certificates
651 arr = chain_str.split("-----BEGIN CERTIFICATE-----")
652 arr = arr[1:len(arr)]
653 for i in range(len(arr)):
654 arr[i] = "-----BEGIN CERTIFICATE-----"+arr[i]
657 c_pem = X509.load_cert_string(c_str)
658 if not check_valid(c_pem):