summaryrefslogtreecommitdiffstats
path: root/base/common/python/pki/key.py
diff options
context:
space:
mode:
Diffstat (limited to 'base/common/python/pki/key.py')
-rw-r--r--base/common/python/pki/key.py350
1 files changed, 254 insertions, 96 deletions
diff --git a/base/common/python/pki/key.py b/base/common/python/pki/key.py
index 0d1dd36f3..7d93e783a 100644
--- a/base/common/python/pki/key.py
+++ b/base/common/python/pki/key.py
@@ -23,6 +23,7 @@
Module containing the Python client classes for the KeyClient and
KeyRequestClient REST API on a DRM
'''
+import base64
import pki.encoder as encoder
import json
import pki
@@ -287,13 +288,24 @@ class KeyClient(object):
PASS_PHRASE_TYPE = "passPhrase"
ASYMMETRIC_KEY_TYPE = "asymmetricKey"
- def __init__(self, connection):
+ def __init__(self, connection, crypto, transport_cert_nick=None):
''' Constructor '''
self.connection = connection
self.headers = {'Content-type': 'application/json',
'Accept': 'application/json'}
self.keyURL = '/rest/agent/keys'
self.keyRequestsURL = '/rest/agent/keyrequests'
+ self.crypto = crypto
+
+ if transport_cert_nick != None:
+ self.crypto.initialize()
+ self.transport_cert = crypto.get_cert(transport_cert_nick)
+ else:
+ self.transport_cert = None
+
+ def set_transport_cert(self, transport_cert_nick):
+ ''' Set the transport certificate for crypto operations '''
+ self.transport_cert = self.crypto.get_cert(transport_cert_nick)
@pki.handle_exceptions()
def list_keys(self, client_key_id=None, status=None, max_results=None,
@@ -310,55 +322,6 @@ class KeyClient(object):
return KeyInfoCollection.from_json(response.json())
@pki.handle_exceptions()
- def retrieve_key(self, data):
- ''' Retrieve a secret from the DRM.
-
- @param: data - a KeyRecoveryRequest containing the keyId of the
- secret being retrieved, the request_id of the approved recovery
- request and a wrapping mechanism. More details at
- KRAClient.retrieve_key.
-
- Returns a KeyData object containing the wrapped secret.
- '''
- url = self.keyURL + '/retrieve'
- keyRequest = json.dumps(data, cls=encoder.CustomTypeEncoder, sort_keys=True)
- response = self.connection.post(url, keyRequest, self.headers)
- return KeyData.from_json(response.json())
-
- @pki.handle_exceptions()
- def request_key_retrieval(self, key_id, request_id, trans_wrapped_session_key=None,
- session_wrapped_passphrase=None, passphrase=None, nonce_data=None):
- ''' Retrieve a secret from the DRM.
-
- The secret (which is referenced by key_id) can be retrieved only if the
- recovery request (referenced by request_id) is approved. key_id and request_id
- are required.
-
- Data must be provided to wrap the recovered secret. This can either be
- a) a 56-bit DES3 symmetric key, wrapped by the DRM transport key, and
- passed in trans_wrapped_session_key
- b) a passphrase. In this case, the passphrase must be wrapped by a 56-bit
- symmetric key ("the session key" and passed in session_wrapped_passphrase,
- and the session key must be wrapped by the DRM transport key and passed
- in trans_wrapped_session_key
- c) a passphrase for a p12 file. If the key being recovered is an asymmetric
- key, then it is possible to pass in the passphrase for the P12 file to
- be generated. This is passed in as passphrase
-
- nonce_data may also be passed as a salt.
-
- Returns a KeyData object containing the wrapped secret.
- '''
- request = KeyRecoveryRequest(key_id=key_id,
- request_id=request_id,
- trans_wrapped_session_key=trans_wrapped_session_key,
- session_wrapped_passphrase=session_wrapped_passphrase,
- nonce_data=nonce_data,
- passphrase=passphrase)
-
- return self.retrieve_key(request)
-
- @pki.handle_exceptions()
def list_requests(self, request_state=None, request_type=None, client_key_id=None,
start=None, page_size=None, max_results=None, max_time=None):
''' List/Search key requests in the DRM.
@@ -381,19 +344,25 @@ class KeyClient(object):
return KeyRequestInfo.from_json(response.json())
@pki.handle_exceptions()
- def create_request(self, request):
- ''' Submit an archival, recovery or key generation request
- to the DRM.
+ def get_key_info(self, key_id):
+ ''' Get the info in the KeyRecord for a specific secret in the DRM. '''
+ url = self.keyURL + '/' + key_id
+ response = self.connection.get(url, headers=self.headers)
+ return KeyInfo.from_json(response.json())
- @param request - is either a KeyArchivalRequest,
- KeyRecoverRequest or SymKeyGenerationRequest.
+ @pki.handle_exceptions()
+ def get_active_key_info(self, client_key_id):
+ ''' Get the info in the KeyRecord for the active secret in the DRM. '''
+ url = self.keyURL + '/active/' + urllib.quote_plus(client_key_id)
+ response = self.connection.get(url, headers=self.headers)
+ return KeyInfo.from_json(response.json())
- returns a KeyRequestResponse object.
- '''
- url = self.keyRequestsURL
- key_request = json.dumps(request, cls=encoder.CustomTypeEncoder, sort_keys=True)
- response = self.connection.post(url, key_request, self.headers)
- return KeyRequestResponse.from_json(response.json())
+ @pki.handle_exceptions()
+ def modify_key_status(self, key_id, status):
+ ''' Modify the status of a key '''
+ url = self.keyURL + '/' + key_id
+ params = {'status':status}
+ self.connection.post(url, None, headers=self.headers, params=params)
@pki.handle_exceptions()
def approve_request(self, request_id):
@@ -413,45 +382,98 @@ class KeyClient(object):
url = self.keyRequestsURL + '/' + request_id + '/cancel'
self.connection.post(url, self.headers)
+ def generate_pki_archive_options(self, trans_wrapped_session_key, session_wrapped_secret):
+ ''' Return a PKIArchiveOptions structure for archiving a secret
+
+ Takes in a session key wrapped by the DRM transport certificate,
+ and a secret wrapped with the session key and creates a PKIArchiveOptions
+ structure to be used when archiving a secret
+ '''
+ pass
+
+ def generate_archive_options(self, secret):
+ ''' Return a PKIArchiveOptions structure for archiving a secret.
+
+ This method uses NSS calls to do the following:
+ 1) generate a session key
+ 2) wrap the session key with the transport key
+ 3) wrap the secret with the session key
+ 4) create the PKIArchiveOptions structure using the results of
+ (2) and (3)
+
+ This method expects initialize_nss() to have been called previously.
+ '''
+ session_key = self.crypto.generate_symmetric_key()
+ trans_wrapped_session_key = self.crypto.asymmetric_wrap(session_key, self.transport_cert)
+ wrapped_secret = self.crypto.symmetric_wrap(secret, session_key)
+
+ return self.generate_pki_archive_options(trans_wrapped_session_key, wrapped_secret)
+
@pki.handle_exceptions()
- def request_recovery(self, key_id, request_id=None, session_wrapped_passphrase=None,
- trans_wrapped_session_key=None, b64certificate=None, nonce_data=None):
- ''' Create a request to recover a secret.
+ def create_request(self, request):
+ ''' Submit an archival, recovery or key generation request
+ to the DRM.
- To retrieve a symmetric key or passphrase, the only parameter that is required is
- the keyId. It is possible (but not required) to pass in the session keys/passphrase
- and nonceData for the retrieval at this time. Those parameters are documented
- in the docstring for retrieve_key below.
+ @param request - is either a KeyArchivalRequest,
+ KeyRecoverRequest or SymKeyGenerationRequest.
- To retrieve an asymmetric key, the keyId and the the base-64 encoded certificate
- is required.
+ returns a KeyRequestResponse object.
'''
- request = KeyRecoveryRequest(key_id=key_id,
- request_id=request_id,
- trans_wrapped_session_key=trans_wrapped_session_key,
- session_wrapped_passphrase=session_wrapped_passphrase,
- certificate=b64certificate,
- nonce_data=nonce_data)
+ url = self.keyRequestsURL
+ key_request = json.dumps(request, cls=encoder.CustomTypeEncoder, sort_keys=True)
+ response = self.connection.post(url, key_request, self.headers)
+ return KeyRequestResponse.from_json(response.json())
+
+ @pki.handle_exceptions()
+ def generate_symmetric_key(self, client_key_id, algorithm, size, usages):
+ ''' Generate and archive a symmetric key on the DRM.
+
+ Return a KeyRequestResponse which contains a KeyRequestInfo
+ object that describes the URL for the request and generated key.
+ '''
+ request = SymKeyGenerationRequest(client_key_id=client_key_id,
+ key_size=size,
+ key_algorithm=algorithm,
+ key_usages=usages)
return self.create_request(request)
@pki.handle_exceptions()
- def request_archival(self, client_key_id, data_type, wrapped_private_data,
+ def archive_key(self, client_key_id, data_type, private_data=None,
+ wrapped_private_data=None,
key_algorithm=None, key_size=None):
''' Archive a secret (symmetric key or passphrase) on the DRM.
Requires a user-supplied client ID. There can be only one active
key with a specified client ID. If a record for a duplicate active
- key exists, an exception is thrown.
+ key exists, a BadRequestException is thrown.
data_type can be one of the following:
+ KeyRequestResource.SYMMETRIC_KEY_TYPE,
+ KeyRequestResource.ASYMMETRIC_KEY_TYPE,
+ KeyRequestResource.PASS_PHRASE_TYPE
+
+ key_algorithm and key_size are applicable to symmetric keys only.
+ If a symmetric key is being archived, these parameters are required.
wrapped_private_data consists of a PKIArchiveOptions structure, which
can be constructed using either generate_archive_options() or
generate_pki_archive_options() below.
- key_algorithm and key_size are applicable to symmetric keys only.
- If a symmetric key is being archived, these parameters are required.
+ private_data is the secret that is to be archived.
+
+ Callers must specify EITHER wrapped_private_data OR private_data.
+ If wrapped_private_data is specified, then this data is forwarded to the
+ DRM unchanged. Otherwise, the private_data is converted to a
+ PKIArchiveOptions structure using the functions below.
+
+ The function returns a KeyRequestResponse object containing a KeyRequestInfo
+ object with details about the archival request and key archived.
'''
+ if wrapped_private_data == None:
+ if private_data == None:
+ raise ValueError("No data provided to be archived")
+ wrapped_private_data = self.generate_archive_options(private_data)
+
request = KeyArchivalRequest(client_key_id=client_key_id,
data_type=data_type,
wrapped_private_data=wrapped_private_data,
@@ -460,26 +482,162 @@ class KeyClient(object):
return self.create_request(request)
@pki.handle_exceptions()
- def get_key_info(self, key_id):
- ''' Get the info in the KeyRecord for a specific secret in the DRM. '''
- url = self.keyURL + '/' + key_id
- response = self.connection.get(url, headers=self.headers)
- return KeyInfo.from_json(response.json())
+ def recover_key(self, key_id, request_id=None, session_wrapped_passphrase=None,
+ trans_wrapped_session_key=None, b64certificate=None, nonce_data=None):
+ ''' Create a request to recover a secret.
+
+ To retrieve a symmetric key or passphrase, the only parameter that is required is
+ the keyId. It is possible (but not required) to pass in the session keys/passphrase
+ and nonceData for the retrieval at this time. Those parameters are documented
+ in the docstring for retrieve_key below.
+
+ To retrieve an asymmetric key, the keyId and the the base-64 encoded certificate
+ is required.
+ '''
+ request = KeyRecoveryRequest(key_id=key_id,
+ request_id=request_id,
+ trans_wrapped_session_key=trans_wrapped_session_key,
+ session_wrapped_passphrase=session_wrapped_passphrase,
+ certificate=b64certificate,
+ nonce_data=nonce_data)
+ return self.create_request(request)
@pki.handle_exceptions()
- def get_active_key_info(self, client_key_id):
- ''' Get the info in the KeyRecord for the active secret in the DRM. '''
- url = self.keyURL + '/active/' + urllib.quote_plus(client_key_id)
- response = self.connection.get(url, headers=self.headers)
- print response
- return KeyInfo.from_json(response.json())
+ def retrieve_key_data(self, data):
+ ''' Retrieve a secret from the DRM.
+
+ @param: data - a KeyRecoveryRequest containing the keyId of the
+ secret being retrieved, the request_id of the approved recovery
+ request and a wrapping mechanism. More details at
+ KRAClient.retrieve_key.
+
+ Returns a KeyData object containing the wrapped secret.
+ '''
+ url = self.keyURL + '/retrieve'
+ keyRequest = json.dumps(data, cls=encoder.CustomTypeEncoder, sort_keys=True)
+ response = self.connection.post(url, keyRequest, self.headers)
+ return KeyData.from_json(response.json())
@pki.handle_exceptions()
- def modify_key_status(self, key_id, status):
- ''' Modify the status of a key '''
- url = self.keyURL + '/' + key_id
- params = {'status':status}
- self.connection.post(url, None, headers=self.headers, params=params)
+ def retrieve_key(self, key_id, trans_wrapped_session_key=None):
+ ''' Retrieve a secret (passphrase or symmetric key) from the DRM.
+
+ This function generates a key recovery request, approves it, and retrieves
+ the secret referred to by key_id. This assumes that only one approval is required
+ to authorize the recovery.
+
+ To ensure data security in transit, the data will be returned encrypted by a session
+ key (56 bit DES3 symmetric key) - which is first wrapped (encrypted) by the public
+ key of the DRM transport certificate before being sent to the DRM. The
+ parameter trans_wrapped_session_key refers to this wrapped session key.
+
+ There are two ways of using this function:
+
+ 1) trans_wrapped_session_key is not provided by caller.
+
+ In this case, the function will call CryptoUtil methods to generate and wrap the
+ session key. The function will return the tuple (KeyData, unwrapped_secret)
+
+ 2) The trans_wrapped_session_key is provided by the caller.
+
+ In this case, the function will simply pass the data to the DRM, and will return the secret
+ wrapped in the session key. The secret will still need to be unwrapped by the caller.
+
+ The function will return the tuple (KeyData, None), where the KeyData structure includes the
+ wrapped secret and some nonce data to be used as a salt when unwrapping.
+ '''
+ key_provided = True
+ if (trans_wrapped_session_key == None):
+ key_provided = False
+ session_key = self.crypto.generate_symmetric_key()
+ trans_wrapped_session_key = self.crypto.asymmetric_wrap(session_key,
+ self.transport_cert)
+
+ response = self.recover_key(key_id)
+ request_id = response.get_request_id()
+ self.approve_request(request_id)
+
+ request = KeyRecoveryRequest(
+ key_id=key_id,
+ request_id=request_id,
+ trans_wrapped_session_key=base64.encodestring(trans_wrapped_session_key))
+
+ key_data = self.retrieve_key_data(request)
+ if key_provided:
+ return key_data, None
+
+ unwrapped_key = self.crypto.symmetric_unwrap(
+ base64.decodestring(key_data.wrappedPrivateData),
+ session_key,
+ nonce_iv=base64.decodestring(key_data.nonceData))
+ return key_data, unwrapped_key
+
+ @pki.handle_exceptions()
+ def retrieve_key_by_passphrase(self, key_id, passphrase=None,
+ trans_wrapped_session_key=None,
+ session_wrapped_passphrase=None,
+ nonce_data=None):
+ ''' Retrieve a secret (passphrase or symmetric key) from the DRM using a passphrase.
+
+ This function generates a key recovery request, approves it, and retrieves
+ the secret referred to by key_id. This assumes that only one approval is required
+ to authorize the recovery.
+
+ The secret is secured in transit by wrapping the secret with a passphrase using
+ PBE encryption.
+
+ There are two ways of using this function:
+
+ 1) A passphrase is provided by the caller.
+
+ In this case, CryptoUtil methods will be called to create the data to securely send the
+ passphrase to the DRM. Basically, three pieces of data will be sent:
+
+ - the passphrase wrapped by a 56 bit DES3 symmetric key (the session key). This
+ is referred to as the parameter session_wrapped_passphrase above.
+
+ - the session key wrapped with the public key in the DRM transport certificate. This
+ is referred to as the trans_wrapped_session_key above.
+
+ - ivps nonce data, referred to as nonce_data
+
+ The function will return the tuple (KeyData, unwrapped_secret)
+
+ 2) The caller provides the trans_wrapped_session_key, session_wrapped_passphrase
+ and nonce_data.
+
+ In this case, the data will simply be passed to the DRM. The function will return
+ the secret encrypted by the passphrase using PBE Encryption. The secret will still
+ need to be decrypted by the caller.
+
+ The function will return the tuple (KeyData, None)
+ '''
+ pass
+
+ @pki.handle_exceptions()
+ def retrieve_key_by_pkcs12(self, key_id, certificate, passphrase):
+ ''' Retrieve an asymmetric private key and return it as PKCS12 data.
+
+ This function generates a key recovery request, approves it, and retrieves
+ the secret referred to by key_id in a PKCS12 file. This assumes that only
+ one approval is required to authorize the recovery.
+
+ This function requires the following parameters:
+ - key_id : the ID of the key
+ - certificate: the certificate associated with the private key
+ - passphrase: A passphrase for the pkcs12 file.
+
+ The function returns a KeyData object.
+ '''
+ response = self.recover_key(key_id, b64certificate=certificate)
+ request_id = response.get_request_id()
+ self.approve_request(request_id)
+
+ request = KeyRecoveryRequest(key_id=key_id,
+ request_id=request_id,
+ passphrase=passphrase)
+
+ return self.retrieve_key_data(request)
encoder.NOTYPES['Attribute'] = pki.Attribute
encoder.NOTYPES['AttributeList'] = pki.AttributeList