summaryrefslogtreecommitdiffstats
path: root/base/kra/functional
diff options
context:
space:
mode:
authorEndi Sukma Dewata <edewata@redhat.com>2012-03-24 02:27:47 -0500
committerEndi Sukma Dewata <edewata@redhat.com>2012-03-26 11:43:54 -0500
commit621d9e5c413e561293d7484b93882d985b3fe15f (patch)
tree638f3d75761c121d9a8fb50b52a12a6686c5ac5c /base/kra/functional
parent40d3643b8d91886bf210aa27f711731c81a11e49 (diff)
downloadpki-621d9e5c413e561293d7484b93882d985b3fe15f.tar.gz
pki-621d9e5c413e561293d7484b93882d985b3fe15f.tar.xz
pki-621d9e5c413e561293d7484b93882d985b3fe15f.zip
Removed unnecessary pki folder.
Previously the source code was located inside a pki folder. This folder was created during svn migration and is no longer needed. This folder has now been removed and the contents have been moved up one level. Ticket #131
Diffstat (limited to 'base/kra/functional')
-rw-r--r--base/kra/functional/drmclient.py1014
-rw-r--r--base/kra/functional/drmclient.readme.txt50
-rw-r--r--base/kra/functional/src/com/netscape/cms/servlet/test/DRMRestClient.java266
-rw-r--r--base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java503
-rw-r--r--base/kra/functional/src/com/netscape/cms/servlet/test/GeneratePKIArchiveOptions.java222
5 files changed, 2055 insertions, 0 deletions
diff --git a/base/kra/functional/drmclient.py b/base/kra/functional/drmclient.py
new file mode 100644
index 000000000..e9b0ccb49
--- /dev/null
+++ b/base/kra/functional/drmclient.py
@@ -0,0 +1,1014 @@
+# Authors:
+# Ade Lee <alee@redhat.com>
+#
+# Copyright (C) 2012 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+'''
+
+============================================================
+Python Test client for KRA using the new RESTful interface
+============================================================
+
+This is a python client that can be used to retrieve key requests
+and keys from a KRA using the new RESTful interface. Moreover, given
+a PKIArchiveOptions structure containing either a passphrase or a symmetric
+key, this data can be stored in and retrieved from the KRA.
+
+A sample test execution is provided at the end of the file.
+'''
+
+from lxml import etree
+import nss.nss as nss
+import httplib
+from ipapython import nsslib, ipautil
+from nss.error import NSPRError
+from ipalib.errors import NetworkError, CertificateOperationError
+from urllib import urlencode, quote_plus
+from datetime import datetime
+import logging
+import base64
+
+CERT_HEADER = "-----BEGIN NEW CERTIFICATE REQUEST-----"
+CERT_FOOTER = "-----END NEW CERTIFICATE REQUEST-----"
+
+def _(string):
+ return string
+
+def parse_key_request_info_xml(doc):
+ '''
+ :param doc: The root node of the xml document to parse
+ :returns: result dict
+ :except ValueError:
+
+ After parsing the results are returned in a result dict. The following
+ table illustrates the mapping from the CMS data item to what may be found in
+ the result dict. If a CMS data item is absent it will also be absent in the
+ result dict.
+
+ +----------------------+----------------+-----------------------+---------------+
+ |cms name |cms type |result name |result type |
+ +======================+================+=======================+===============+
+ |requestType |string |request_type |string |
+ +----------------------+----------------+-----------------------+---------------+
+ |requestStatus |string |request_status |string |
+ +----------------------+----------------+-----------------------+---------------+
+ |requestURL |string |request_id |string |
+ +----------------------+----------------+-----------------------+---------------+
+ |keyURL |string |key_id |string |
+ +----------------------+----------------+-----------------------+---------------+
+ '''
+ response = {}
+
+ request_type = doc.xpath('requestType')
+ if len(request_type) == 1:
+ request_type = etree.tostring(request_type[0], method='text',
+ encoding=unicode).strip()
+ response['request_type'] = request_type
+
+ request_status = doc.xpath('requestStatus')
+ if len(request_status) == 1:
+ request_status = etree.tostring(request_status[0], method='text',
+ encoding=unicode).strip()
+ response['request_status'] = request_status
+
+ request_url = doc.xpath('requestURL')
+ if len(request_url) == 1:
+ request_url = etree.tostring(request_url[0], method='text',
+ encoding=unicode).strip()
+ response['request_id'] = request_url.rsplit('/',1)[1]
+
+ key_url = doc.xpath('keyURL')
+ if len(key_url) == 1:
+ key_url = etree.tostring(key_url[0], method='text',
+ encoding=unicode).strip()
+ response['key_id'] = key_url.rsplit('/',1)[1]
+
+ return response
+
+def parse_key_request_infos_xml(doc):
+ '''
+ :param doc: The root node of the xml document to parse
+ :returns: result dict
+ :except ValueError:
+
+ After parsing the results are returned in a result dict. The following
+ table illustrates the mapping from the CMS data item to what may be found in
+ the result dict. If a CMS data item is absent it will also be absent in the
+ result dict.
+
+ +----------------------+------------------------+-----------------------+---------------+
+ |cms name |cms type |result name |result type |
+ +======================+========================+=======================+===============+
+ |next |Link |next_id |unicode [1] |
+ +----------------------+------------------------+-----------------------+---------------+
+ |prev |Link |prev_id |unicode [1] |
+ +----------------------+------------------------+-----------------------+---------------+
+ |info for each request |SecurityDataRequestInfo |request_id [2] |dict |
+ +----------------------+------------------------+-----------------------+---------------+
+
+ [1] prev_id and next_id are the starting ids for the previous and next pages
+ respectively. They are extracted from the href elements of the Link
+ nodes (if they exist)
+ [2] For each key request info returned, we store a dict containing the key request data.
+ See parse_key_request_info_xml for details. Each dict is referenced by the id
+ of the key request (extracted from the key request URL).
+ '''
+ response = {}
+ next_link = doc.xpath('//Link[@rel="next"]/href')
+ if len(next_link) == 1:
+ next_link = etree.tostring(next_link[0], method='text',
+ encoding=unicode).strip()
+ next_link = next_link.rsplit('/',1)[1]
+ response['next_id'] = next_link
+
+ prev_link = doc.xpath('//Link[@rel="previous"]/href')
+ if len(prev_link) == 1:
+ prev_link = etree.tostring(prev_link[0], method='text',
+ encoding=unicode).strip()
+ prev_link = prev_link.rsplit('/', 1)[1]
+ response['prev_id'] = prev_link
+
+ key_request_infos = doc.xpath('//SecurityDataRequestInfo')
+ for key_request in key_request_infos:
+ node = parse_key_request_info_xml(key_request)
+ response[node['request_id']] = node
+
+ return response
+
+def parse_key_data_info_xml(doc):
+ '''
+ :param doc: The root node of the xml document to parse
+ :returns: result dict
+ :except ValueError:
+
+ After parsing the results are returned in a result dict. The following
+ table illustrates the mapping from the CMS data item to what may be found in
+ the result dict. If a CMS data item is absent it will also be absent in the
+ result dict.
+
+ +----------------------+----------------+-----------------------+---------------+
+ |cms name |cms type |result name |result type |
+ +======================+================+=======================+===============+
+ |clientID |string |client_id |string |
+ +----------------------+----------------+-----------------------+---------------+
+ |keyURL |string |key_url |string |
+ +----------------------+----------------+-----------------------+---------------+
+ '''
+ response = {}
+
+ client_id = doc.xpath('clientID')
+ if len(client_id) == 1:
+ client_id = etree.tostring(client_id[0], method='text',
+ encoding=unicode).strip()
+ response['client_id'] = client_id
+
+ key_url = doc.xpath('keyURL')
+ if len(key_url) == 1:
+ key_url = etree.tostring(key_url[0], method='text',
+ encoding=unicode).strip()
+ response['key_url'] = key_url
+
+ return response
+
+def parse_key_data_infos_xml(doc):
+ '''
+ :param doc: The root node of the xml document to parse
+ :returns: result dict
+ :except ValueError:
+
+ After parsing the results are returned in a result dict. The following
+ table illustrates the mapping from the CMS data item to what may be found in
+ the result dict. If a CMS data item is absent it will also be absent in the
+ result dict.
+
+ +----------------------+-----------------+-----------------------+---------------+
+ |cms name |cms type |result name |result type |
+ +======================+=================+=======================+===============+
+ |next |Link |next_id |unicode [1] |
+ +----------------------+-----------------+-----------------------+---------------+
+ |prev |Link |prev_id |unicode [1] |
+ +----------------------+-----------------+-----------------------+---------------+
+ |info for each key |SecurityDataInfo |key_id [2] |dict |
+ +----------------------+-----------------+-----------------------+---------------+
+
+ [1] prev_id and next_id are the starting ids for the previous and next pages
+ respectively. They are extracted from the href elements of the Link
+ nodes (if they exist)
+ [2] For each key info returned, we store a dict containing the key data.
+ See parse_key_data_info_xml for details. Each dict is referenced by the id
+ of the key (extracted from the key URL).
+ '''
+ response = {}
+
+ next_link = doc.xpath('//Link[@rel="next"]/href')
+ if len(next_link) == 1:
+ next_link = etree.tostring(next_link[0], method='text',
+ encoding=unicode).strip()
+ next_link = next_link.rsplit('/',1)[1]
+ response['next_id'] = next_link
+
+ prev_link = doc.xpath('//Link[@rel="previous"]/href')
+ if len(prev_link) == 1:
+ prev_link = etree.tostring(prev_link[0], method='text',
+ encoding=unicode).strip()
+ prev_link = prev_link.rsplit('/', 1)[1]
+ response['prev_id'] = prev_link
+
+ key_data_infos = doc.xpath('//SecurityDataInfo')
+ for key_data in key_data_infos:
+ node = parse_key_data_info_xml(key_data)
+ response[node['key_url'].rsplit('/',1)[1]] = node
+
+ return response
+
+def parse_key_data_xml(doc):
+ '''
+ :param doc: The root node of the xml document to parse
+ :returns: result dict
+ :except ValueError:
+
+ After parsing the results are returned in a result dict.
+
+ +----------------------+----------------+-----------------------+---------------+
+ |cms name |cms type |result name |result type |
+ +======================+================+=======================+===============+
+ |wrappedPrivateData |string |wrapped_data |unicode |
+ +----------------------+----------------+-----------------------+---------------+
+ |nonceData |string |nonce_data |unicode |
+ +----------------------+----------------+-----------------------+---------------+
+
+ '''
+ response = {}
+
+ wrapped_data = doc.xpath('wrappedPrivateData')
+ if len(wrapped_data) == 1:
+ wrapped_data = etree.tostring(wrapped_data[0], method='text',
+ encoding=unicode).strip()
+ response['wrapped_data'] = wrapped_data
+
+ nonce_data = doc.xpath('nonceData')
+ if len(nonce_data) == 1:
+ nonce_data = etree.tostring(nonce_data[0], method='text',
+ encoding=unicode).strip()
+ response['nonce_data'] = nonce_data
+
+ return response
+
+def parse_certificate_data_xml(doc):
+ '''
+ :param doc: The root node of the xml document to parse
+ :returns: result dict
+ :except ValueError:
+
+ After parsing the results are returned in a result dict.
+
+ +----------------------+----------------+-----------------------+---------------+
+ |cms name |cms type |result name |result type |
+ +======================+================+=======================+===============+
+ |b64 |string [1] |cert |unicode |
+ +----------------------+----------------+-----------------------+---------------+
+
+ [1] Base-64 encoded certificate with header and footer
+ '''
+ response = {}
+
+ b64 = doc.xpath('b64')
+ if len(b64) == 1:
+ b64 = etree.tostring(b64[0], method='text',
+ encoding=unicode).strip()
+ response['cert'] = b64.replace(CERT_HEADER, "").replace(CERT_FOOTER, "")
+
+ return response
+
+def https_request(host, port, url, secdir, password, nickname, operation, args, **kw):
+ """
+ :param url: The URL to post to.
+ :param operation: GET, POST, (PUT and DELETE not yet implemented)
+ :param args: arguments for GET command line, or for POST
+ :param kw: Keyword arguments to encode into POST body.
+ :return: (http_status, http_reason_phrase, http_headers, http_body)
+ as (integer, unicode, dict, str)
+
+ Perform a client authenticated HTTPS request
+ """
+ if isinstance(host, unicode):
+ host = host.encode('utf-8')
+ uri = 'https://%s%s' % (ipautil.format_netloc(host, port), url)
+ logging.info('sslget %r', uri)
+
+ request_headers = {"Content-type": "application/xml",
+ "Accept": "application/xml"}
+ if operation == "POST":
+ if args != None:
+ post = args
+ elif kw != None:
+ post = urlencode(kw)
+ request_headers = {"Content-type": "application/x-www-form-urlencoded",
+ "Accept": "text/plain"}
+ try:
+ conn = nsslib.NSSConnection(host, port, dbdir=secdir)
+ conn.set_debuglevel(0)
+ conn.connect()
+ conn.sock.set_client_auth_data_callback(nsslib.client_auth_data_callback,
+ nickname,
+ password, nss.get_default_certdb())
+ if operation == "GET":
+ url = url + "?" + args
+ conn.request("GET", url)
+ elif operation == "POST":
+ conn.request("POST", url, post, request_headers)
+
+ res = conn.getresponse()
+
+ http_status = res.status
+ http_reason_phrase = unicode(res.reason, 'utf-8')
+ http_headers = res.msg.dict
+ http_body = res.read()
+ conn.close()
+ except Exception, e:
+ raise NetworkError(uri=uri, error=str(e))
+
+ return http_status, http_reason_phrase, http_headers, http_body
+
+def http_request(host, port, url, operation, args):
+ """
+ :param url: The URL to post to.
+ :param operation: GET, POST, (PUT and DELETE not yet implemented)
+ :param args: arguments for GET command line, or for POST
+ :return: (http_status, http_reason_phrase, http_headers, http_body)
+ as (integer, unicode, dict, str)
+
+ Perform an HTTP request.
+ """
+ if isinstance(host, unicode):
+ host = host.encode('utf-8')
+ uri = 'http://%s%s' % (ipautil.format_netloc(host, port), url)
+ logging.info('request %r', uri)
+ request_headers = {"Content-type": "application/xml",
+ "Accept": "application/xml"}
+ if operation == "POST":
+ if args != None:
+ post = args
+ else:
+ post = ""
+ conn = httplib.HTTPConnection(host, port)
+ try:
+ if operation == "GET":
+ if args != None:
+ url = url + "?" + args
+ conn.request("GET", url)
+ elif operation == "POST":
+ conn.request("POST", url, post, request_headers)
+
+ res = conn.getresponse()
+
+ http_status = res.status
+ http_reason_phrase = unicode(res.reason, 'utf-8')
+ http_headers = res.msg.dict
+ http_body = res.read()
+ conn.close()
+ except NSPRError, e:
+ raise NetworkError(uri=uri, error=str(e))
+
+ logging.debug('request status %d', http_status)
+ logging.debug('request reason_phrase %r', http_reason_phrase)
+ logging.debug('request headers %s', http_headers)
+ logging.debug('request body %r', http_body)
+
+ return http_status, http_reason_phrase, http_headers, http_body
+
+class kra:
+ """
+ Key Repository Authority backend plugin.
+ """
+
+ POST = "POST"
+ GET = "GET"
+ transport_cert = "byte array with transport cert"
+ mechanism = nss.CKM_DES_CBC_PAD
+ iv = "e4:bb:3b:d3:c3:71:2e:58"
+ fullname = "kra"
+
+
+ def __init__(self, work_dir, kra_host, kra_port, kra_nickname):
+ #crypto
+ self.sec_dir = work_dir
+ self.pwd_file = work_dir + "/pwdfile.txt"
+ self.transport_cert_nickname = kra_nickname
+ self.mechanism = nss.CKM_DES3_CBC_PAD
+ try:
+ f = open(self.pwd_file, "r")
+ self.password = f.readline().strip()
+ f.close()
+ except IOError:
+ self.password = ''
+
+ #set up key db for crypto functions
+ try:
+ nss.nss_init(self.sec_dir)
+ except Exception, e:
+ raise CertificateOperationError(error=_('Error in initializing certdb (%s)') \
+ + e.strerror)
+ self.transport_cert = nss.find_cert_from_nickname(self.transport_cert_nickname)
+
+ # DRM info
+ self.kra_host = kra_host
+ self.kra_agent_port = kra_port
+ '''super(kra, self).__init__()'''
+
+ def setup_contexts(self, mechanism, sym_key, iv):
+ # Get a PK11 slot based on the cipher
+ slot = nss.get_best_slot(mechanism)
+
+ if sym_key == None:
+ sym_key = slot.key_gen(mechanism, None, slot.get_best_key_length(mechanism))
+
+ # If initialization vector was supplied use it, otherwise set it to None
+ if iv:
+ iv_data = nss.read_hex(iv)
+ iv_si = nss.SecItem(iv_data)
+ iv_param = nss.param_from_iv(mechanism, iv_si)
+ else:
+ iv_length = nss.get_iv_length(mechanism)
+ if iv_length > 0:
+ iv_data = nss.generate_random(iv_length)
+ iv_si = nss.SecItem(iv_data)
+ iv_param = nss.param_from_iv(mechanism, iv_si)
+ else:
+ iv_param = None
+
+ # Create an encoding context
+ encoding_ctx = nss.create_context_by_sym_key(mechanism, nss.CKA_ENCRYPT,
+ sym_key, iv_param)
+
+ # Create a decoding context
+ decoding_ctx = nss.create_context_by_sym_key(mechanism, nss.CKA_DECRYPT,
+ sym_key, iv_param)
+
+ return encoding_ctx, decoding_ctx
+
+ def debug(self, message, *args):
+ print message % args
+
+ def _request(self, url, port, operation, args):
+ """
+ :param url: The URL to post to.
+ :param port: The port to post to
+ :param operation: GET/POST/PUT/DELETE (as supported by sslget)
+ :param args: A string containing arguments for a GET or POST request
+ :return: (http_status, http_reason_phrase, http_headers, http_body)
+ as (integer, unicode, dict, str)
+
+ Perform an HTTP request.
+ """
+ return http_request(self.kra_host, port, url, operation, args)
+
+ def _sslget(self, url, port, operation, args, **kw):
+ """
+ :param url: The URL to post to.
+ :param port: The port to post to
+ :param operation: GET/POST/PUT/DELETE (as supported by sslget)
+ :param args: A string containing arguments for a GET or POST request
+ :param kw: Alternatively, keyword arguments to be form-encoded into POST body.
+ :return: (http_status, http_reason_phrase, http_headers, http_body)
+ as (integer, unicode, dict, str)
+
+ Perform an HTTPS request
+ """
+ return https_request(self.kra_host, port, url, self.sec_dir, self.password,
+ self.ipa_certificate_nickname, operation, args, **kw)
+
+ def symmetric_wrap(self, data, wrapping_key):
+ """
+ :param data: Data to be wrapped
+ :param wrapping_key Symmetric key to wrap data
+
+ Wrap (encrypt) data using the supplied symmetric key
+ """
+ encoding_ctx, decoding_ctx = self.setup_contexts(self.mechanism, wrapping_key, self.iv)
+ wrapped_data = encoding_ctx.cipher_op(data) + encoding_ctx.digest_final()
+ return wrapped_data
+
+ def asymmetric_wrap(self, data, wrapping_cert):
+ """
+ :param data: Data to be wrapped
+ :param wrapping_cert Public key to wrap data
+
+ Wrap (encrypt) data using the supplied asymmetric key
+ """
+
+ return None
+
+ def symmetric_unwrap(self, data, wrapping_key, iv = None):
+ """
+ :param data: Data to be unwrapped
+ :param wrapping_key Symmetric key to unwrap data
+
+ Unwrap (decrypt) data using the supplied symmetric key
+ """
+ if iv == None:
+ iv = self.iv
+ encoding_ctx, decoding_ctx = self.setup_contexts(self.mechanism, wrapping_key, iv)
+ unwrapped_data = decoding_ctx.cipher_op(data) + decoding_ctx.digest_final()
+ return unwrapped_data
+
+ def get_parse_result_xml(self, xml_text, parse_func):
+ '''
+ :param xml_text: The XML text to parse
+ :param parse_func: The XML parsing function to apply to the parsed DOM tree.
+ :return: parsed result dict
+
+ Utility routine which parses the input text into an XML DOM tree
+ and then invokes the parsing function on the DOM tree in order
+ to get the parsing result as a dict of key/value pairs.
+ '''
+ parser = etree.XMLParser()
+ doc = etree.fromstring(xml_text, parser)
+ result = parse_func(doc)
+ self.debug("%s() xml_text:\n%s\nparse_result:\n%s" % (parse_func.__name__, xml_text, result))
+ return result
+
+ def create_archival_request(self, client_id, security_data, data_type):
+ """
+ :param :param client_id: identifier to be used for this stored key
+ :param security_data: data blob (PKIArchiveOptions) containing passphrase
+ or symmetric key to be archived
+ :param data_type: data type (symmetricKey, pass_phrase, asymmetricKey)
+ :return doc: xml doc with archival request
+ """
+ self.debug('%s.create_archival_request()', self.fullname)
+ root = etree.Element("SecurityDataArchivalRequest")
+ client_id_element = etree.SubElement(root, "clientId")
+ client_id_element.text = client_id
+ wrapped_private_data_element = etree.SubElement(root, "wrappedPrivateData")
+ wrapped_private_data_element.text = security_data
+ data_type_element = etree.SubElement(root, "dataType")
+ data_type_element.text = data_type
+ return etree.ElementTree(root)
+
+ def create_recovery_request(self, key_id, request_id, session_key, passphrase, nonce = None):
+ """
+ :param key_id: identifier of key to be recovered
+ :param request_id: id for the recovery request
+ :param session_key session key wrapped in transport key
+ :param passphrase passphrase wrapped in session key
+ :return doc: xml doc with archival request
+
+ """
+ self.debug('%s.create_recovery_request()', self.fullname)
+ root = etree.Element("SecurityDataRecoveryRequest")
+ if key_id != None:
+ key_id_element = etree.SubElement(root, "keyId")
+ key_id_element.text = key_id
+ if request_id != None:
+ request_id_element = etree.SubElement(root, "requestId")
+ request_id_element.text = request_id
+ if session_key != None:
+ session_key_element = etree.SubElement(root, "transWrappedSessionKey")
+ session_key_element.text = session_key
+ if passphrase != None:
+ passphrase_element = etree.SubElement(root, "sessionWrappedPassphrase")
+ passphrase_element.text = passphrase
+ if nonce != None:
+ nonce_element = etree.SubElement(root, "nonceData")
+ nonce_element.text = nonce
+ return etree.ElementTree(root)
+
+ def archive_security_data(self, client_id, security_data, data_type):
+ """
+ :param client_id: identifier to be used for this stored key
+ :param security_data: data blob (PKIArchiveOptions) containing passphrase
+ or symmetric key to be archived
+ :param data_type: data type (symmetricKey, pass_phrase, asymmetricKey)
+
+ Archives security data packaged in a PKIArchiveOptions blob
+
+ The command returns a dict with key/value pairs as defined in
+ parse_key_request_info_xml(). These include the request_id of the created
+ archival request, the status of the request, and the key_id of the archived
+ key.
+ """
+ self.debug('%s.archive_security_data()', self.fullname)
+
+ # check clientID and security data
+ if ((client_id == None) or (security_data == None)):
+ raise CertificateOperationError(error=_('Bad arguments to archive_security_data'))
+
+ request = self.create_archival_request(client_id, security_data, data_type)
+
+ #Call CMS
+ http_status, http_reason_phrase, http_headers, http_body = \
+ self._request('/kra/pki/keyrequest/archive',
+ self.kra_agent_port,
+ self.POST,
+ etree.tostring(request.getroot(), encoding='UTF-8'))
+
+ # Parse and handle errors
+ if (http_status != 200):
+ raise CertificateOperationError(error=_('Error in archiving request (%s)') % \
+ http_reason_phrase)
+
+ parse_result = self.get_parse_result_xml(http_body, parse_key_request_info_xml)
+ return parse_result
+
+ def get_transport_cert(self, etag=None):
+ """
+ :param etag: etag info for last cert retrieval from DRM
+
+ Gets the transport certificate from the DRM
+
+ The command returns a dict as defined in parse_certificate_data_xml()
+ """
+ self.debug('%s.get_transport_cert()', self.fullname)
+
+ #Call CMS
+ http_status, http_reason_phrase, http_headers, http_body = \
+ self._request('/kra/pki/config/cert/transport',
+ self.kra_agent_port,
+ self.GET,
+ None)
+
+ self.debug("headers: %s" , http_headers)
+ # Parse and handle errors
+ if (http_status != 200):
+ raise CertificateOperationError(error=_('Error in archiving request (%s)') % \
+ http_reason_phrase)
+
+ parse_result = self.get_parse_result_xml(http_body, parse_certificate_data_xml)
+ return parse_result
+
+ def list_security_data(self, client_id, key_state = None, next_id = None):
+ """
+ :param client_id: identifier to be searched for
+ :param key_state: state for key (active, inactive, all)
+ :param next_id: id for starting key on next page (if more than one page)
+
+ List security data matching the specified client id and state
+
+ The command returns a dict as specified in parse_key_data_infos_xml().
+ """
+ self.debug('%s.list_security_data()', self.fullname)
+ if client_id == None:
+ raise CertificateOperationError(error=_('Bad argument to list_security_data'))
+ get_args = "clientID=" + quote_plus(client_id)
+
+ if key_state != None:
+ get_args = get_args + "&status=" + quote_plus(key_state)
+
+ if next_id != None:
+ # currnently not implemented on server
+ get_args = get_args + "&start=" + quote_plus(next_id)
+
+ #Call CMS
+ http_status, http_reason_phrase, http_headers, http_body = \
+ self._request('/kra/pki/keys',
+ self.kra_agent_port,
+ self.GET,
+ get_args)
+
+ # Parse and handle errors
+ if (http_status != 200):
+ raise CertificateOperationError(error=_('Error in listing keys (%s)') % \
+ http_reason_phrase)
+
+ parse_result = self.get_parse_result_xml(http_body, parse_key_data_infos_xml)
+ return parse_result
+
+ def list_key_requests(self, request_state = None, request_type = None, client_id = None,
+ next_id = None):
+ """
+ :param request_state: state of request (pending, complete, cancelled, rejected, approved)
+ :param request_type: request type (enrollment, recovery)
+ :param next_id: id for starting key on next page (if more than one page)
+
+ List security data matching the specified client id and state
+
+ The command returns a dict as specified in parse_key_request_infos_xml().
+ """
+ self.debug('%s.list_key_requests()', self.fullname)
+ get_args = ""
+
+ if request_state != None:
+ get_args = get_args + "&requestState=" + quote_plus(request_state)
+
+ if request_type != None:
+ get_args = get_args + "&requestType=" + quote_plus(request_type)
+
+ if client_id != None:
+ get_args = get_args + "&clientID=" + quote_plus(client_id)
+
+ if next_id != None:
+ # currnently not implemented on server
+ get_args = get_args + "&start=" + quote_plus(next_id)
+
+ #Call CMS
+ http_status, http_reason_phrase, http_headers, http_body = \
+ self._request('/kra/pki/keyrequests',
+ self.kra_agent_port,
+ self.GET,
+ get_args)
+
+ # Parse and handle errors
+ if (http_status != 200):
+ raise CertificateOperationError(error=_('Error in listing key requests (%s)') % \
+ http_reason_phrase)
+
+ parse_result = self.get_parse_result_xml(http_body, parse_key_request_infos_xml)
+ return parse_result
+
+ def submit_recovery_request(self, key_id):
+ """
+ :param key_id: identifier of data to be recovered
+
+ Create a recovery request for a passphrase or symmetric key
+
+ The command returns a dict as described in the comments to
+ parse_key_request_info_xml(). This data includes the request_id
+ of the created recovery request
+ """
+ self.debug('%s.submit_recovery_request()', self.fullname)
+
+ # check clientID and security data
+ if key_id == None:
+ raise CertificateOperationError(error=_('Bad argument to archive_security_data'))
+
+ request = self.create_recovery_request(key_id, None, None, None)
+
+ #Call CMS
+ http_status, http_reason_phrase, http_headers, http_body = \
+ self._request('/kra/pki/keyrequest/recover',
+ self.kra_agent_port,
+ self.POST,
+ etree.tostring(request.getroot(), encoding='UTF-8'))
+
+ # Parse and handle errors
+ if (http_status != 200):
+ raise CertificateOperationError(error=_('Error in archiving request (%s)') % \
+ http_reason_phrase)
+
+ parse_result = self.get_parse_result_xml(http_body, parse_key_request_info_xml)
+ return parse_result
+
+ def check_request_status(self, request_id):
+ """
+ :param recovery_request_id: identifier of key recovery request
+
+ Check recovery request status
+
+ The command returns a dict with these possible key/value pairs.
+ Some key/value pairs may be absent
+
+ +-----------------+---------------+-------------------------------------- +
+ |result name |result type |comments |
+ +=================+===============+=======================================+
+ |request_status |String | status of request (pending, rejected, |
+ | | | approved) |
+ +-----------------+---------------+---------------------------------------|
+ |approvers_needed |int | If pending, number of approvers |
+ | | | needed |
+ +-----------------+---------------+---------------------------------------+
+ |approvers_list |String | list of approvers |
+ +-----------------+---------------+---------------------------------------+
+ """
+ self.debug('%s.check_request_status()', self.fullname)
+
+ def approve_recovery_request(self, request_id):
+ """
+ :param request_id: identifier of key recovery request
+
+ Approve recovery request
+ """
+ self.debug('%s.approve_recovery_request()', self.fullname)
+ if request_id == None:
+ raise CertificateOperationError(error=_('Bad argument to approve_recovery_request'))
+
+ #Call CMS
+ http_status, http_reason_phrase, http_headers, http_body = \
+ self._request('/kra/pki/keyrequest/approve/'+ request_id,
+ self.kra_agent_port,
+ self.POST,
+ None)
+
+ # Parse and handle errors
+ if (http_status > 399):
+ raise CertificateOperationError(error=_('Error in approving request (%s)') % \
+ http_reason_phrase)
+
+ def reject_recovery_request(self, request_id):
+ """
+ :param recovery_request_id: identifier of key recovery request
+
+ Reject recovery request
+ """
+ self.debug('%s.reject_recovery_request()', self.fullname)
+ if request_id == None:
+ raise CertificateOperationError(error=_('Bad argument to reject_recovery_request'))
+
+ #Call CMS
+ http_status, http_reason_phrase, http_headers, http_body = \
+ self._request('/kra/pki/keyrequest/reject/'+ request_id,
+ self.kra_agent_port,
+ self.POST,
+ None)
+
+ # Parse and handle errors
+ if (http_status > 399):
+ raise CertificateOperationError(error=_('Error in rejecting request (%s)') % \
+ http_reason_phrase)
+
+ def cancel_recovery_request(self, request_id):
+ """
+ :param recovery_request_id: identifier of key recovery request
+
+ Cancel recovery request
+ """
+ self.debug('%s.cancel_recovery_request()', self.fullname)
+ if request_id == None:
+ raise CertificateOperationError(error=_('Bad argument to cancel_recovery_request'))
+
+ #Call CMS
+ http_status, http_reason_phrase, http_headers, http_body = \
+ self._request('/kra/pki/keyrequest/cancel/'+ request_id,
+ self.kra_agent_port,
+ self.POST,
+ None)
+
+ # Parse and handle errors
+ if (http_status > 399):
+ raise CertificateOperationError(error=_('Error in cancelling request (%s)') % \
+ http_reason_phrase)
+
+ def retrieve_security_data(self, recovery_request_id, passphrase=None):
+ """
+ :param recovery_request_id: identifier of key recovery request
+ :param passphrase: passphrase to be used to wrap the data
+
+ Recover the passphrase or symmetric key. We require an approved
+ recovery request.
+
+ If a passphrase is provided, the DRM will return a blob that can be decrypted
+ with the passphrase. If not, then a symmetric key will be created to wrap the
+ data for transport to this server. Upon receipt, the data will be unwrapped
+ and returned unencrypted.
+
+ The command returns a dict with the values described in parse_key_data_xml(),
+ as well as the following field
+
+ +-----------------+---------------+-------------------------------------- +
+ |result name |result type |comments |
+ +=================+===============+=======================================+
+ |data |String | Key data (either wrapped using |
+ | | | passphrase or unwrapped) |
+ +-----------------+---------------+---------------------------------------+
+ """
+ self.debug('%s.retrieve_security_data()', self.fullname)
+
+ if recovery_request_id == None:
+ raise CertificateOperationError(error=_('Bad arguments to retrieve_security_data'))
+
+ # generate symmetric key
+ slot = nss.get_best_slot(self.mechanism)
+ session_key = slot.key_gen(self.mechanism, None, slot.get_best_key_length(self.mechanism))
+
+ # wrap this key with the transport cert
+ public_key = self.transport_cert.subject_public_key_info.public_key
+ wrapped_session_key = base64.b64encode(nss.pub_wrap_sym_key(self.mechanism, public_key, session_key))
+ wrapped_passphrase = None
+ if passphrase != None:
+ # wrap passphrase with session key
+ wrapped_session_key = base64.b64encode(self.symmetric_wrap(passphrase, session_key))
+
+ request = self.create_recovery_request(None, recovery_request_id,
+ wrapped_session_key,
+ wrapped_passphrase)
+
+ #Call CMS
+ http_status, http_reason_phrase, http_headers, http_body = \
+ self._request('/kra/pki/key/retrieve',
+ self.kra_agent_port,
+ self.POST,
+ etree.tostring(request.getroot(), encoding='UTF-8'))
+
+ # Parse and handle errors
+ if (http_status != 200):
+ raise CertificateOperationError(error=_('Error in retrieving security data (%s)') % \
+ http_reason_phrase)
+
+ parse_result = self.get_parse_result_xml(http_body, parse_key_data_xml)
+
+ if passphrase == None:
+ iv = nss.data_to_hex(base64.decodestring(parse_result['nonce_data']))
+ parse_result['data'] = self.symmetric_unwrap(base64.decodestring(parse_result['wrapped_data']),
+ session_key, iv)
+
+ return parse_result
+
+ def recover_security_data(self, key_id, passphrase=None):
+ """
+ :param key_id: identifier of key to be recovered
+ :param passphrase: passphrase to wrap key data for delivery outside of this server
+
+ Recover the key data (symmetric key or passphrase) in a one step process.
+ This is the case when only one approver is required to extract a key such that
+ the agent submitting the recovery request is the only approver required.
+
+ In this case, the request is automatically approved, and the KRA just returns the
+ key data.
+
+ This has not yet been implemented on the server
+ """
+ self.debug('%s.recover_security_data()', self.fullname)
+ pass
+
+""" Sample Test execution starts here """
+import argparse
+
+parser = argparse.ArgumentParser(description="Sample Test execution")
+parser.add_argument('-d', default='/tmp/drmtest', dest='work_dir', help='Working directory')
+parser.add_argument('--options', default='options.out', dest='options_file',
+ help='File containing test PKIArchiveOptions to be archived')
+parser.add_argument('--symkey', default='symkey.out', dest='symkey_file',
+ help='File containing test symkey')
+parser.add_argument('--host', default='localhost', dest='kra_host', help='DRM hostname')
+parser.add_argument('-p', default='10080', type=int, dest='kra_port', help='DRM Port')
+parser.add_argument('-n', default='DRM TransportCert Nickname', dest='kra_nickname',
+ help="DRM Nickname")
+
+args = parser.parse_args()
+work_dir = args.work_dir
+kra_host = args.kra_host
+kra_port = args.kra_port
+kra_nickname = args.kra_nickname
+options_file = args.options_file
+symkey_file = args.symkey_file
+
+test_kra = kra(work_dir, kra_host, kra_port, kra_nickname)
+
+# list requests
+requests = test_kra.list_key_requests()
+print requests
+
+# get transport cert
+transport_cert = test_kra.get_transport_cert()
+print transport_cert
+
+#archive symmetric key
+f = open(work_dir + "/" + options_file)
+wrapped_key = f.read()
+client_id = "Python symmetric key " + datetime.now().strftime("%Y-%m-%d %H:%M")
+response = test_kra.archive_security_data(client_id, wrapped_key,"symmetricKey")
+print response
+
+# list keys with client_id
+response = test_kra.list_security_data(client_id, "active")
+print response
+
+#create recovery request
+key_id = response.keys()[0]
+print key_id
+response = test_kra.submit_recovery_request(key_id)
+print response
+
+# approve recovery request
+request_id = response['request_id']
+test_kra.approve_recovery_request(request_id)
+
+# test invalid request
+print "Testing invalid request ID"
+try:
+ response = test_kra.retrieve_security_data("INVALID")
+ print "Failure: No exception thrown"
+except CertificateOperationError, e:
+ if 'Error in retrieving security data (Bad Request)' == e.error:
+ print "Success: " + e.error
+ else:
+ print "Failure: Wrong error message: " + e.error
+
+# retrieve key
+response = test_kra.retrieve_security_data(request_id)
+print response
+print "retrieved data is " + base64.encodestring(response['data'])
+
+#read original symkey from file
+f = open(work_dir + "/" + symkey_file)
+orig_key = f.read()
+print "orig key is " + orig_key
+
+if orig_key.strip() == base64.encodestring(response['data']).strip():
+ print "Success: the keys match"
+else:
+ print "Failure: keys do not match"
diff --git a/base/kra/functional/drmclient.readme.txt b/base/kra/functional/drmclient.readme.txt
new file mode 100644
index 000000000..833c5ce3c
--- /dev/null
+++ b/base/kra/functional/drmclient.readme.txt
@@ -0,0 +1,50 @@
+Running drmclient.py:
+
+The python drmclient currently requires a little setup to be run.
+
+1. Create a working directory - the code uses /tmp/drmtest
+2. In that directory, create an NSS database. In this doc, we will use the
+ password redhat123 as the password for the NSS db.
+
+ certutil -N -d /tmp/drmtest
+
+3. Add a password file /tmp/drmtest/pwdfile.txt. It should contain the password for
+ the NSS database.
+
+4. Put the transport certificate in a file /tmp/drmtest/transport.crt in binary format.
+
+ certutil -L -d /var/lib/pki-kra/alias -n "DRM Transport Certificate" -a > /tmp/drmtest/transport.asc
+ AtoB /tmp/drmtest/transport.asc /tmp/drmtest/transport.crt
+
+5. Import the transport certificate into the certificate databse in /tmp/drmtest.
+ certutil -A -d /tmp/drmtest -n "DRM Transport Certificate" -i /tmp/drmtest/transport.asc
+
+5. Run GeneratePKIArchiveOptions to generate some test data. Specifically we will be
+ using it to generate a symmetric key and its associated PKIArchoveOptions structure
+ to be archived.
+
+ GeneratePKIArchiveOptions -k /tmp/drmtest/symkey.out -w redhat123 -t /tmp/drmtest -o /tmp/drmtest/options.out
+
+6. Run the python code. You will likely need some python modules - python-lxml, python-nss
+ and ipapython.
+
+ The code has the following usage:
+
+usage: drmclient.py [-h] [-d WORK_DIR] [--options OPTIONS_FILE]
+ [--symkey SYMKEY_FILE] [--host KRA_HOST] [-p KRA_PORT]
+ [-n KRA_NICKNAME]
+
+Sample Test execution
+
+optional arguments:
+ -h, --help show this help message and exit
+ -d WORK_DIR Working directory
+ --options OPTIONS_FILE
+ File containing test PKIArchiveOptions to be archived
+ --symkey SYMKEY_FILE File containing test symkey
+ --host KRA_HOST DRM hostname
+ -p KRA_PORT DRM Port
+ -n KRA_NICKNAME DRM Nickname
+
+For example:
+python pki/base/kra/functional/drmclient.py -d /tmp/drmtest -p 10200 -n "DRM Transport Certificate - alee eclipse domain 2"
diff --git a/base/kra/functional/src/com/netscape/cms/servlet/test/DRMRestClient.java b/base/kra/functional/src/com/netscape/cms/servlet/test/DRMRestClient.java
new file mode 100644
index 000000000..651873b20
--- /dev/null
+++ b/base/kra/functional/src/com/netscape/cms/servlet/test/DRMRestClient.java
@@ -0,0 +1,266 @@
+package com.netscape.cms.servlet.test;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import org.apache.commons.httpclient.ConnectTimeoutException;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.params.HttpConnectionParams;
+import org.apache.commons.httpclient.protocol.Protocol;
+import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
+import org.jboss.resteasy.client.ClientExecutor;
+import org.jboss.resteasy.client.ClientResponse;
+import org.jboss.resteasy.client.ProxyFactory;
+import com.netscape.certsrv.dbs.keydb.KeyId;
+import com.netscape.certsrv.request.RequestId;
+import org.jboss.resteasy.client.core.executors.ApacheHttpClientExecutor;
+import com.netscape.cms.servlet.admin.SystemCertificateResource;
+import com.netscape.cms.servlet.cert.model.CertificateData;
+import com.netscape.cms.servlet.key.KeyResource;
+import com.netscape.cms.servlet.key.KeysResource;
+import com.netscape.cms.servlet.key.model.KeyData;
+import com.netscape.cms.servlet.key.model.KeyDataInfo;
+import com.netscape.cms.servlet.key.model.KeyDataInfos;
+import com.netscape.cms.servlet.request.KeyRequestResource;
+import com.netscape.cms.servlet.request.KeyRequestsResource;
+import com.netscape.cms.servlet.request.model.ArchivalRequestData;
+import com.netscape.cms.servlet.request.model.KeyRequestInfo;
+import com.netscape.cms.servlet.request.model.KeyRequestInfos;
+import com.netscape.cms.servlet.request.model.RecoveryRequestData;
+import com.netscape.cmsutil.util.Utils;
+
+import org.mozilla.jss.ssl.SSLCertificateApprovalCallback;
+import org.mozilla.jss.ssl.SSLClientCertificateSelectionCallback;
+import org.mozilla.jss.ssl.SSLSocket;
+
+public class DRMRestClient {
+
+ // Callback to approve or deny returned SSL server certs
+ // Right now, simply approve the cert.
+ // ToDO: Look into taking this JSS http client code and move it into
+ // its own class to be used by possible future clients.
+ private class ServerCertApprovalCB implements SSLCertificateApprovalCallback {
+
+ public boolean approve(org.mozilla.jss.crypto.X509Certificate servercert,
+ SSLCertificateApprovalCallback.ValidityStatus status) {
+
+ //For now lets just accept the server cert. This is a test tool, being
+ // pointed at a well know kra instance.
+
+
+ if (servercert != null) {
+ System.out.println("Peer cert details: " +
+ "\n subject: " + servercert.getSubjectDN().toString() +
+ "\n issuer: " + servercert.getIssuerDN().toString() +
+ "\n serial: " + servercert.getSerialNumber().toString()
+ );
+ }
+
+ SSLCertificateApprovalCallback.ValidityItem item;
+
+ Enumeration<?> errors = status.getReasons();
+ int i = 0;
+ while (errors.hasMoreElements()) {
+ i++;
+ item = (SSLCertificateApprovalCallback.ValidityItem) errors.nextElement();
+ System.out.println("item " + i +
+ " reason=" + item.getReason() +
+ " depth=" + item.getDepth());
+
+ int reason = item.getReason();
+
+ if (reason ==
+ SSLCertificateApprovalCallback.ValidityStatus.UNTRUSTED_ISSUER ||
+ reason == SSLCertificateApprovalCallback.ValidityStatus.BAD_CERT_DOMAIN) {
+
+ //Allow these two since we haven't necessarily installed the CA cert for trust
+ // and we are choosing "localhost" as the host for this client.
+
+ return true;
+
+ }
+ }
+
+ //For other errors return false
+
+ return false;
+ }
+ }
+
+ private class JSSProtocolSocketFactory implements ProtocolSocketFactory {
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
+
+ SSLSocket sock = createJSSSocket(host,port, null, 0, null);
+ return (Socket) sock;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException,
+ UnknownHostException {
+
+ SSLSocket sock = createJSSSocket(host,port, clientHost, clientPort, null);
+ return (Socket) sock;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params)
+ throws IOException, UnknownHostException, ConnectTimeoutException {
+
+ SSLSocket sock = createJSSSocket(host, port, localAddress, localPort, null);
+ return (Socket) sock;
+ }
+ }
+
+ private SSLSocket createJSSSocket(String host, int port, InetAddress localAddress,
+ int localPort, SSLClientCertificateSelectionCallback clientCertSelectionCallback)
+ throws IOException, UnknownHostException, ConnectTimeoutException {
+
+ SSLSocket sock = new SSLSocket(InetAddress.getByName(host),
+ port,
+ localAddress,
+ localPort,
+ new ServerCertApprovalCB(),
+ null);
+
+ if(sock != null && clientCertNickname != null) {
+ sock.setClientCertNickname(clientCertNickname);
+ }
+
+ return sock;
+
+ }
+ private KeyResource keyClient;
+ private KeysResource keysClient;
+ private KeyRequestsResource keyRequestsClient;
+ private KeyRequestResource keyRequestClient;
+ private SystemCertificateResource systemCertClient;
+
+ private String clientCertNickname = null;
+
+ public DRMRestClient(String baseUri, String clientCertNick) throws MalformedURLException {
+
+ // For SSL we are assuming the caller has already intialized JSS and has
+ // a valid CryptoManager and CryptoToken
+ // optional clientCertNickname is provided for use if required.
+
+
+ URL url = new URL(baseUri);
+
+ String protocol = url.getProtocol();
+ int port = url.getPort();
+
+ clientCertNickname = null;
+ if(protocol != null && protocol.equals("https")) {
+ if (clientCertNick != null) {
+ clientCertNickname = clientCertNick;
+ }
+
+ Protocol.registerProtocol("https",
+ new Protocol(protocol, new JSSProtocolSocketFactory(), port));
+ }
+
+ HttpClient httpclient = new HttpClient();
+ ClientExecutor executor = new ApacheHttpClientExecutor(httpclient);
+
+ systemCertClient = ProxyFactory.create(SystemCertificateResource.class, baseUri, executor);
+ keyRequestsClient = ProxyFactory.create(KeyRequestsResource.class, baseUri, executor);
+ keyRequestClient = ProxyFactory.create(KeyRequestResource.class, baseUri, executor);
+ keysClient = ProxyFactory.create(KeysResource.class, baseUri, executor);
+ keyClient = ProxyFactory.create(KeyResource.class, baseUri, executor);
+ }
+
+ public String getTransportCert() {
+ @SuppressWarnings("unchecked")
+ ClientResponse<CertificateData> response = (ClientResponse<CertificateData>) systemCertClient.getTransportCert();
+ CertificateData certData = response.getEntity();
+ String transportCert = certData.getB64();
+ return transportCert;
+ }
+
+ public Collection<KeyRequestInfo> listRequests(String requestState, String requestType) {
+ KeyRequestInfos infos = keyRequestsClient.listRequests(
+ requestState, requestType, null, new RequestId(0), 100, 100, 10
+ );
+ Collection<KeyRequestInfo> list = infos.getRequests();
+ return list;
+ }
+
+ public KeyRequestInfo archiveSecurityData(byte[] encoded, String clientId, String dataType) {
+ // create archival request
+ ArchivalRequestData data = new ArchivalRequestData();
+ String req1 = Utils.base64encode(encoded);
+ data.setWrappedPrivateData(req1);
+ data.setClientId(clientId);
+ data.setDataType(dataType);
+
+ KeyRequestInfo info = keyRequestClient.archiveKey(data);
+ return info;
+ }
+
+ public KeyDataInfo getKeyData(String clientId, String status) {
+ KeyDataInfos infos = keysClient.listKeys(clientId, status, 100, 10);
+ Collection<KeyDataInfo> list = infos.getKeyInfos();
+ Iterator<KeyDataInfo> iter = list.iterator();
+
+ while (iter.hasNext()) {
+ KeyDataInfo info = iter.next();
+ if (info != null) {
+ // return the first one
+ return info;
+ }
+ }
+ return null;
+ }
+
+ public KeyRequestInfo requestRecovery(KeyId keyId, byte[] rpwd, byte[] rkey, byte[] nonceData) {
+ // create recovery request
+ RecoveryRequestData data = new RecoveryRequestData();
+ data.setKeyId(keyId);
+ if (rpwd != null) {
+ data.setSessionWrappedPassphrase(Utils.base64encode(rpwd));
+ }
+ if (rkey != null) {
+ data.setTransWrappedSessionKey(Utils.base64encode(rkey));
+ }
+
+ if (nonceData != null) {
+ data.setNonceData(Utils.base64encode(nonceData));
+ }
+
+ KeyRequestInfo info = keyRequestClient.recoverKey(data);
+ return info;
+ }
+
+ public void approveRecovery(RequestId recoveryId) {
+ keyRequestClient.approveRequest(recoveryId);
+ }
+
+ public KeyData retrieveKey(KeyId keyId, RequestId requestId, byte[] rpwd, byte[] rkey, byte[] nonceData) {
+ // create recovery request
+ RecoveryRequestData data = new RecoveryRequestData();
+ data.setKeyId(keyId);
+ data.setRequestId(requestId);
+ if (rkey != null) {
+ data.setTransWrappedSessionKey(Utils.base64encode(rkey));
+ }
+ if (rpwd != null) {
+ data.setSessionWrappedPassphrase(Utils.base64encode(rpwd));
+ }
+
+ if (nonceData != null) {
+ data.setNonceData(Utils.base64encode(nonceData));
+ }
+
+ KeyData key = keyClient.retrieveKey(data);
+ return key;
+ }
+}
diff --git a/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java b/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java
new file mode 100644
index 000000000..8d83247b8
--- /dev/null
+++ b/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java
@@ -0,0 +1,503 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.cms.servlet.test;
+
+import java.net.MalformedURLException;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Random;
+
+import org.mozilla.jss.CryptoManager;
+import org.mozilla.jss.crypto.AlreadyInitializedException;
+import org.mozilla.jss.crypto.CryptoToken;
+import org.mozilla.jss.crypto.EncryptionAlgorithm;
+import org.mozilla.jss.crypto.IVParameterSpec;
+import org.mozilla.jss.crypto.KeyGenAlgorithm;
+import org.mozilla.jss.crypto.SymmetricKey;
+import org.mozilla.jss.util.Password;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.PosixParser;
+
+import com.netscape.certsrv.dbs.keydb.KeyId;
+import com.netscape.certsrv.request.RequestId;
+import com.netscape.cms.servlet.base.CMSResourceService;
+import com.netscape.cms.servlet.key.model.KeyData;
+import com.netscape.cms.servlet.key.model.KeyDataInfo;
+import com.netscape.cms.servlet.request.KeyRequestResource;
+import com.netscape.cms.servlet.request.model.KeyRequestInfo;
+import com.netscape.cmsutil.crypto.CryptoUtil;
+import com.netscape.cmsutil.util.Utils;
+
+public class DRMTest {
+
+ public static void usage(Options options) {
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp("DRMTest", options);
+ System.exit(1);
+ }
+
+ public static void main(String args[]) {
+ String host = null;
+ String port = null;
+ String token_pwd = null;
+ String db_dir = "./";
+ String protocol = "http";
+ String clientCertNickname = "KRA Administrator of Instance pki-kra's SjcRedhat Domain ID";
+
+ // parse command line arguments
+ Options options = new Options();
+ options.addOption("h", true, "Hostname of the DRM");
+ options.addOption("p", true, "Port of the DRM");
+ options.addOption("w", true, "Token password");
+ options.addOption("d", true, "Directory for tokendb");
+ options.addOption("s", true, "Attempt Optional Secure SSL connection");
+ options.addOption("c", true, "Optional SSL Client cert Nickname");
+
+ try {
+ CommandLineParser parser = new PosixParser();
+ CommandLine cmd = parser.parse(options, args);
+
+ if (cmd.hasOption("h")) {
+ host = cmd.getOptionValue("h");
+ } else {
+ System.err.println("Error: no hostname provided.");
+ usage(options);
+ }
+
+ if (cmd.hasOption("p")) {
+ port = cmd.getOptionValue("p");
+ } else {
+ System.err.println("Error: no port provided");
+ usage(options);
+ }
+
+ if (cmd.hasOption("w")) {
+ token_pwd = cmd.getOptionValue("w");
+ } else {
+ System.err.println("Error: no token password provided");
+ usage(options);
+ }
+
+ if (cmd.hasOption("d")) {
+ db_dir = cmd.getOptionValue("d");
+ }
+
+ if (cmd.hasOption("s")) {
+ if(cmd.getOptionValue("s") != null && cmd.getOptionValue("s").equals("true")) {
+ protocol = "https";
+ }
+ }
+
+ if (cmd.hasOption("c")) {
+ String nick = cmd.getOptionValue("c");
+
+ if (nick != null && protocol.equals("https")) {
+ clientCertNickname = nick;
+ }
+ }
+
+ } catch (ParseException e) {
+ System.err.println("Error in parsing command line options: " + e.getMessage());
+ usage(options);
+ }
+
+ // used for crypto operations
+ byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
+ IVParameterSpec ivps = null;
+ IVParameterSpec ivps_server = null;
+
+ try {
+ ivps = genIV(8);
+ } catch (Exception e) {
+ log("Can't generate initialization vector use default: " + e.toString());
+ ivps = new IVParameterSpec(iv);
+ }
+
+ CryptoManager manager = null;
+ CryptoToken token = null;
+
+ // used for wrapping to send data to DRM
+ String transportCert = null;
+
+ // Data to be archived
+ SymmetricKey vek = null;
+ String passphrase = null;
+
+ // Session keys and passphrases for recovery
+ SymmetricKey recoveryKey = null;
+ byte[] wrappedRecoveryKey = null;
+ String recoveryPassphrase = null;
+ byte[] wrappedRecoveryPassphrase = null;
+
+ // retrieved data (should match archived data)
+ String wrappedRecoveredKey = null;
+ String recoveredKey = null;
+
+ // various ids used in recovery/archival operations
+ KeyId keyId = null;
+ String clientId = null;
+ RequestId recoveryRequestId = null;
+
+ // Variables for data structures from calls
+ KeyRequestInfo requestInfo = null;
+ KeyData keyData = null;
+ KeyDataInfo keyInfo = null;
+
+ // Initialize token
+ try {
+ CryptoManager.initialize(db_dir);
+ } catch (AlreadyInitializedException e) {
+ // it is ok if it is already initialized
+ } catch (Exception e) {
+ log("INITIALIZATION ERROR: " + e.toString());
+ System.exit(1);
+ }
+
+ // log into token
+ try {
+ manager = CryptoManager.getInstance();
+ token = manager.getInternalKeyStorageToken();
+ Password password = new Password(token_pwd.toCharArray());
+ try {
+ token.login(password);
+ } catch (Exception e) {
+ log("login Exception: " + e.toString());
+ if (!token.isLoggedIn()) {
+ token.initPassword(password, password);
+ }
+ }
+ } catch (Exception e) {
+ log("Exception in logging into token:" + e.toString());
+ }
+
+ // Set base URI and get client
+
+
+ String baseUri = protocol + "://" + host + ":" + port + "/kra/pki";
+ DRMRestClient client;
+ try {
+ client = new DRMRestClient(baseUri, clientCertNickname);
+ } catch (MalformedURLException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ return;
+ }
+
+ // Test 1: Get transport certificate from DRM
+ transportCert = client.getTransportCert();
+ transportCert = transportCert.substring(CMSResourceService.HEADER.length(),
+ transportCert.indexOf(CMSResourceService.TRAILER));
+
+ log("Transport Cert retrieved from DRM: " + transportCert);
+
+ // Test 2: Get list of completed key archival requests
+ log("\n\nList of completed archival requests");
+ Collection<KeyRequestInfo> list = client.listRequests("complete", "securityDataEnrollment");
+ if (list == null) {
+ log("No requests found");
+ } else {
+ Iterator<KeyRequestInfo> iter = list.iterator();
+ while (iter.hasNext()) {
+ KeyRequestInfo info = iter.next();
+ printRequestInfo(info);
+ }
+ }
+
+ // Test 3: Get list of key recovery requests
+ log("\n\nList of completed recovery requests");
+ Collection<KeyRequestInfo> list2 = client.listRequests("complete", "securityDataRecovery");
+ if (list2 == null) {
+ log("No requests found");
+ } else {
+ Iterator<KeyRequestInfo> iter2 = list2.iterator();
+ while (iter2.hasNext()) {
+ KeyRequestInfo info = iter2.next();
+ printRequestInfo(info);
+ }
+ }
+
+ // Test 4: Generate and archive a symmetric key
+ log("Archiving symmetric key");
+ clientId = "UUID: 123-45-6789 VEK " + Calendar.getInstance().getTime().toString();
+ try {
+ vek = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3);
+ byte[] encoded = CryptoUtil.createPKIArchiveOptions(manager, token, transportCert, vek, null,
+ KeyGenAlgorithm.DES3, ivps);
+
+ KeyRequestInfo info = client.archiveSecurityData(encoded, clientId, KeyRequestResource.SYMMETRIC_KEY_TYPE);
+ log("Archival Results:");
+ printRequestInfo(info);
+ keyId = info.getKeyId();
+ } catch (Exception e) {
+ log("Exception in archiving symmetric key:" + e.getMessage());
+ e.printStackTrace();
+ }
+
+ //Test 5: Get keyId for active key with client ID
+
+ log("Getting key ID for symmetric key");
+ keyInfo = client.getKeyData(clientId, "active");
+ KeyId keyId2 = keyInfo.getKeyId();
+ if (keyId2 == null) {
+ log("No archived key found");
+ } else {
+ log("Archived Key found: " + keyId);
+ }
+
+ if (!keyId.equals(keyId2)) {
+ log("Error: key ids from search and archival do not match");
+ } else {
+ log("Success: keyids from search and archival match.");
+ }
+
+ // Test 6: Submit a recovery request for the symmetric key using a session key
+ log("Submitting a recovery request for the symmetric key using session key");
+ try {
+ recoveryKey = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3);
+ wrappedRecoveryKey = CryptoUtil.wrapSymmetricKey(manager, token, transportCert, recoveryKey);
+ KeyRequestInfo info = client.requestRecovery(keyId, null, wrappedRecoveryKey, ivps.getIV());
+ recoveryRequestId = info.getRequestId();
+ } catch (Exception e) {
+ log("Exception in recovering symmetric key using session key: " + e.getMessage());
+ }
+
+ // Test 7: Approve recovery
+ log("Approving recovery request: " + recoveryRequestId);
+ client.approveRecovery(recoveryRequestId);
+
+ // Test 8: Get key
+ log("Getting key: " + keyId);
+
+ keyData = client.retrieveKey(keyId, recoveryRequestId, null, wrappedRecoveryKey, ivps.getIV());
+ wrappedRecoveredKey = keyData.getWrappedPrivateData();
+
+ ivps_server = new IVParameterSpec(Utils.base64decode(keyData.getNonceData()));
+ try {
+ recoveredKey = CryptoUtil.unwrapUsingSymmetricKey(token, ivps_server,
+ Utils.base64decode(wrappedRecoveredKey),
+ recoveryKey, EncryptionAlgorithm.DES3_CBC_PAD);
+ } catch (Exception e) {
+ log("Exception in unwrapping key: " + e.toString());
+ e.printStackTrace();
+ }
+
+ if (!recoveredKey.equals(Utils.base64encode(vek.getEncoded()))) {
+ log("Error: recovered and archived keys do not match!");
+ } else {
+ log("Success: recoverd and archived keys match!");
+ }
+
+ // Test 9: Submit a recovery request for the symmetric key using a passphrase
+ log("Submitting a recovery request for the symmetric key using a passphrase");
+ recoveryPassphrase = "Gimme me keys please";
+
+ try {
+ recoveryKey = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3);
+ wrappedRecoveryPassphrase = CryptoUtil.wrapPassphrase(token, recoveryPassphrase, ivps, recoveryKey,
+ EncryptionAlgorithm.DES3_CBC_PAD);
+ wrappedRecoveryKey = CryptoUtil.wrapSymmetricKey(manager, token, transportCert, recoveryKey);
+
+ requestInfo = client.requestRecovery(keyId, wrappedRecoveryPassphrase, wrappedRecoveryKey, ivps.getIV());
+ recoveryRequestId = requestInfo.getRequestId();
+ } catch (Exception e) {
+ log("Exception in recovering symmetric key using passphrase" + e.toString());
+ e.printStackTrace();
+ }
+
+ //Test 10: Approve recovery
+ log("Approving recovery request: " + recoveryRequestId);
+ client.approveRecovery(recoveryRequestId);
+
+ // Test 11: Get key
+ log("Getting key: " + keyId);
+ keyData = client.retrieveKey(keyId, recoveryRequestId, wrappedRecoveryPassphrase, wrappedRecoveryKey, ivps.getIV());
+ wrappedRecoveredKey = keyData.getWrappedPrivateData();
+
+ try {
+ recoveredKey = CryptoUtil.unwrapUsingPassphrase(wrappedRecoveredKey, recoveryPassphrase);
+ } catch (Exception e) {
+ log("Error: unable to unwrap key using passphrase");
+ e.printStackTrace();
+ }
+
+ if (recoveredKey == null || !recoveredKey.equals(Utils.base64encode(vek.getEncoded()))) {
+ log("Error: recovered and archived keys do not match!");
+ } else {
+ log("Success: recovered and archived keys do match!");
+ }
+
+
+ passphrase = "secret12345";
+ // Test 12: Generate and archive a passphrase
+ clientId = "UUID: 123-45-6789 RKEK " + Calendar.getInstance().getTime().toString();
+ try {
+ byte[] encoded = CryptoUtil.createPKIArchiveOptions(manager, token, transportCert, null, passphrase,
+ KeyGenAlgorithm.DES3, ivps);
+ requestInfo = client.archiveSecurityData(encoded, clientId, KeyRequestResource.PASS_PHRASE_TYPE);
+ log("Archival Results:");
+ printRequestInfo(requestInfo);
+ keyId = requestInfo.getKeyId();
+ } catch (Exception e) {
+ log("Exception in archiving symmetric key:" + e.toString());
+ e.printStackTrace();
+ }
+
+ //Test 13: Get keyId for active passphrase with client ID
+ log("Getting key ID for passphrase");
+ keyInfo = client.getKeyData(clientId, "active");
+ keyId2 = keyInfo.getKeyId();
+ if (keyId2 == null) {
+ log("No archived key found");
+ } else {
+ log("Archived Key found: " + keyId);
+ }
+
+ if (!keyId.equals(keyId2)) {
+ log("Error: key ids from search and archival do not match");
+ } else {
+ log("Success: key ids from search and archival do match!");
+ }
+
+ // Test 14: Submit a recovery request for the passphrase using a session key
+ log("Submitting a recovery request for the passphrase using session key");
+ recoveryKey = null;
+ recoveryRequestId = null;
+ wrappedRecoveryKey = null;
+ try {
+ recoveryKey = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3);
+ wrappedRecoveryKey = CryptoUtil.wrapSymmetricKey(manager, token, transportCert, recoveryKey);
+ wrappedRecoveryPassphrase = CryptoUtil.wrapPassphrase(token, recoveryPassphrase, ivps, recoveryKey,
+ EncryptionAlgorithm.DES3_CBC_PAD);
+ requestInfo = client.requestRecovery(keyId, null, wrappedRecoveryKey, ivps.getIV());
+ recoveryRequestId = requestInfo.getRequestId();
+ } catch (Exception e) {
+ log("Exception in recovering passphrase using session key: " + e.getMessage());
+ }
+
+ // Test 15: Approve recovery
+ log("Approving recovery request: " + recoveryRequestId);
+ client.approveRecovery(recoveryRequestId);
+
+ // Test 16: Get key
+ log("Getting passphrase: " + keyId);
+
+ keyData = client.retrieveKey(keyId, recoveryRequestId, null, wrappedRecoveryKey, ivps.getIV());
+ wrappedRecoveredKey = keyData.getWrappedPrivateData();
+ ivps_server = new IVParameterSpec( Utils.base64decode(keyData.getNonceData()));
+ try {
+ recoveredKey = CryptoUtil.unwrapUsingSymmetricKey(token, ivps_server,
+ Utils.base64decode(wrappedRecoveredKey),
+ recoveryKey, EncryptionAlgorithm.DES3_CBC_PAD);
+ recoveredKey = new String(Utils.base64decode(recoveredKey), "UTF-8");
+ } catch (Exception e) {
+ log("Exception in unwrapping key: " + e.toString());
+ e.printStackTrace();
+ }
+
+ if (recoveredKey == null || !recoveredKey.equals(passphrase)) {
+ log("Error: recovered and archived passphrases do not match!");
+ } else {
+ log("Success: recovered and archived passphrases do match!");
+ }
+
+ // Test 17: Submit a recovery request for the passphrase using a passphrase
+ log("Submitting a recovery request for the passphrase using a passphrase");
+ requestInfo = client.requestRecovery(keyId, wrappedRecoveryPassphrase, wrappedRecoveryKey, ivps.getIV());
+ recoveryRequestId = requestInfo.getRequestId();
+
+ //Test 18: Approve recovery
+ log("Approving recovery request: " + recoveryRequestId);
+ client.approveRecovery(recoveryRequestId);
+
+ // Test 19: Get key
+ log("Getting passphrase: " + keyId);
+ keyData = client.retrieveKey(keyId, recoveryRequestId, wrappedRecoveryPassphrase, wrappedRecoveryKey, ivps.getIV());
+ wrappedRecoveredKey = keyData.getWrappedPrivateData();
+ try {
+ recoveredKey = CryptoUtil.unwrapUsingPassphrase(wrappedRecoveredKey, recoveryPassphrase);
+ recoveredKey = new String(Utils.base64decode(recoveredKey), "UTF-8");
+ } catch (Exception e) {
+ log("Error: cannot unwrap key using passphrase");
+ e.printStackTrace();
+ }
+
+ if (recoveredKey == null || !recoveredKey.equals(passphrase)) {
+ log("Error: recovered and archived passphrases do not match!");
+ } else {
+ log("Success: recovered and archived passphrases do match!");
+ }
+
+ // Test 20: Submit a recovery request for the passphrase using a passphrase
+ //Wait until retrieving key before sending input data.
+
+ log("Submitting a recovery request for the passphrase using a passphrase, wait till end to provide recovery data.");
+ requestInfo = client.requestRecovery(keyId, null, null, null);
+ recoveryRequestId = requestInfo.getRequestId();
+
+ //Test 21: Approve recovery
+ log("Approving recovery request: " + recoveryRequestId);
+ client.approveRecovery(recoveryRequestId);
+
+ // Test 22: Get key
+ log("Getting passphrase: " + keyId);
+ keyData = client.retrieveKey(keyId, recoveryRequestId, wrappedRecoveryPassphrase, wrappedRecoveryKey, ivps.getIV());
+ wrappedRecoveredKey = keyData.getWrappedPrivateData();
+ try {
+ recoveredKey = CryptoUtil.unwrapUsingPassphrase(wrappedRecoveredKey, recoveryPassphrase);
+ recoveredKey = new String(Utils.base64decode(recoveredKey), "UTF-8");
+ } catch (Exception e) {
+ log("Error: Can't unwrap recovered key using passphrase");
+ e.printStackTrace();
+ }
+
+ if (recoveredKey == null || !recoveredKey.equals(passphrase)) {
+ log("Error: recovered and archived passphrases do not match!");
+ } else {
+ log("Success: recovered and archived passphrases do match!");
+ }
+ }
+
+ private static void log(String string) {
+ // TODO Auto-generated method stub
+ System.out.println(string);
+ }
+
+ private static void printRequestInfo(KeyRequestInfo info) {
+ log("KeyRequestURL: " + info.getRequestURL());
+ log("Key URL: " + info.getKeyURL());
+ log("Status: " + info.getRequestStatus());
+ log("Type: " + info.getRequestType());
+ }
+
+ //Use this when we actually create random initialization vectors
+ private static IVParameterSpec genIV(int blockSize) throws Exception {
+ // generate an IV
+ byte[] iv = new byte[blockSize];
+
+ Random rnd = new Random();
+ rnd.nextBytes(iv);
+
+ return new IVParameterSpec(iv);
+ }
+}
diff --git a/base/kra/functional/src/com/netscape/cms/servlet/test/GeneratePKIArchiveOptions.java b/base/kra/functional/src/com/netscape/cms/servlet/test/GeneratePKIArchiveOptions.java
new file mode 100644
index 000000000..604430b57
--- /dev/null
+++ b/base/kra/functional/src/com/netscape/cms/servlet/test/GeneratePKIArchiveOptions.java
@@ -0,0 +1,222 @@
+package com.netscape.cms.servlet.test;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.CharConversionException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.PosixParser;
+import org.mozilla.jss.CryptoManager;
+import org.mozilla.jss.asn1.InvalidBERException;
+import org.mozilla.jss.crypto.AlreadyInitializedException;
+import org.mozilla.jss.crypto.BadPaddingException;
+import org.mozilla.jss.crypto.CryptoToken;
+import org.mozilla.jss.crypto.IVParameterSpec;
+import org.mozilla.jss.crypto.IllegalBlockSizeException;
+import org.mozilla.jss.crypto.KeyGenAlgorithm;
+import org.mozilla.jss.crypto.SymmetricKey;
+import org.mozilla.jss.crypto.SymmetricKey.NotExtractableException;
+import org.mozilla.jss.crypto.TokenException;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cmsutil.crypto.CryptoUtil;
+import com.netscape.cmsutil.util.Utils;
+
+@SuppressWarnings("deprecation")
+public class GeneratePKIArchiveOptions {
+
+ public static void usage(Options options) {
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp("GeneratePKIArchiveOptions", options);
+ System.exit(1);
+ }
+
+ private static void log(String string) {
+ // TODO Auto-generated method stub
+ System.out.println(string);
+ }
+
+ // read in byte array
+ // we must do this somewhere?
+ public static byte[] read(String fname) throws IOException {
+ File file = new File(fname);
+ byte[] result = new byte[(int) file.length()];
+ InputStream input = null;
+ try {
+ int totalBytesRead = 0;
+ input = new BufferedInputStream(new FileInputStream(file));
+ while (totalBytesRead < result.length) {
+ int bytesRemaining = result.length - totalBytesRead;
+ //input.read() returns -1, 0, or more :
+ int bytesRead = input.read(result, totalBytesRead, bytesRemaining);
+ if (bytesRead > 0) {
+ totalBytesRead = totalBytesRead + bytesRead;
+ }
+ }
+ } catch (Exception e) {
+ throw new IOException(e);
+ } finally {
+ if (input != null) {
+ input.close();
+ }
+ }
+
+ return result;
+ }
+
+ public static void write(byte[] aInput, String outFile) throws IOException {
+ OutputStream output = null;
+ try {
+ output = new BufferedOutputStream(new FileOutputStream(outFile));
+ output.write(aInput);
+ } catch (Exception e) {
+ throw new IOException(e);
+ } finally {
+ if (output != null) {
+ output.close();
+ }
+ }
+ }
+
+ private static void write_file(String data, String outFile) throws IOException {
+ FileWriter fstream = new FileWriter(outFile);
+ BufferedWriter out = new BufferedWriter(fstream);
+ out.write(data);
+ //Close the output stream
+ out.close();
+ }
+
+ public static void main(String args[]) throws InvalidKeyException, CertificateEncodingException,
+ CharConversionException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
+ IllegalStateException, TokenException, IOException, IllegalBlockSizeException, BadPaddingException,
+ InvalidBERException, NotExtractableException {
+ String token_pwd = null;
+ String db_dir = "./";
+ String out_file = "./options.out";
+ String transport_file = "./transport.crt";
+ String key_file = "./symkey.out";
+ String passphrase = null;
+ boolean passphraseMode = false;
+
+ // parse command line arguments
+ Options options = new Options();
+ options.addOption("w", true, "Token password (required)");
+ options.addOption("d", true, "Directory for tokendb");
+ options.addOption("p", true, "Passphrase");
+ options.addOption("t", true, "File with transport cert");
+ options.addOption("o", true, "Output file");
+ options.addOption("k", true, "Key file");
+
+ try {
+ CommandLineParser parser = new PosixParser();
+ CommandLine cmd = parser.parse(options, args);
+
+ if (cmd.hasOption("p")) {
+ passphrase = cmd.getOptionValue("p");
+ passphraseMode = true;
+ }
+
+ if (cmd.hasOption("o")) {
+ out_file = cmd.getOptionValue("o");
+ }
+
+ if (cmd.hasOption("k")) {
+ key_file = cmd.getOptionValue("k");
+ }
+
+ if (cmd.hasOption("t")) {
+ transport_file = cmd.getOptionValue("t");
+ }
+
+ if (cmd.hasOption("w")) {
+ token_pwd = cmd.getOptionValue("w");
+ } else {
+ System.err.println("Error: no token password provided");
+ usage(options);
+ }
+
+ if (cmd.hasOption("d")) {
+ db_dir = cmd.getOptionValue("d");
+ }
+
+ } catch (ParseException e) {
+ System.err.println("Error in parsing command line options: " + e.getMessage());
+ usage(options);
+ }
+
+ // used for crypto operations
+ byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
+ IVParameterSpec ivps = new IVParameterSpec(iv);
+ CryptoManager manager = null;
+ CryptoToken token = null;
+
+ // used for wrapping to send data to DRM
+ byte[] tcert = read(transport_file);
+ String transportCert = Utils.base64encode(tcert);
+
+ // Initialize token
+ try {
+ CryptoManager.initialize(db_dir);
+ } catch (AlreadyInitializedException e) {
+ // it is ok if it is already initialized
+ } catch (Exception e) {
+ log("INITIALIZATION ERROR: " + e.toString());
+ System.exit(1);
+ }
+
+ // log into token
+ try {
+ manager = CryptoManager.getInstance();
+ token = manager.getInternalKeyStorageToken();
+ Password password = new Password(token_pwd.toCharArray());
+ try {
+ token.login(password);
+ } catch (Exception e) {
+ log("login Exception: " + e.toString());
+ if (!token.isLoggedIn()) {
+ token.initPassword(password, password);
+ }
+ }
+ } catch (Exception e) {
+ log("Exception in logging into token:" + e.toString());
+ }
+
+ // Data to be archived
+ SymmetricKey vek = null;
+ if (!passphraseMode) {
+ vek = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3);
+ // store vek in file
+ write_file(Utils.base64encode(vek.getKeyData()), key_file);
+ }
+
+ byte[] encoded = null;
+
+ if (passphraseMode) {
+ encoded = CryptoUtil.createPKIArchiveOptions(manager, token, transportCert, null, passphrase,
+ KeyGenAlgorithm.DES3, ivps);
+ } else {
+ encoded = CryptoUtil.createPKIArchiveOptions(manager, token, transportCert, vek, null,
+ KeyGenAlgorithm.DES3, ivps);
+ }
+
+ // write encoded to file
+ write_file(Utils.base64encode(encoded), out_file);
+
+ }
+}