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 is 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/rest/agent/keyrequests/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 test_ca_connection_cert_not_found(self, mock_load_cert, mock_ca_subject): """CA connectivity check for a cert that doesn't exist""" m_api.Command.cert_show.reset_mock() m_api.Command.config_show.side_effect = subject_base m_api.Command.cert_show.side_effect = CertificateOperationError( message='Certificate operation cannot be completed: ' 'EXCEPTION (Certificate serial number 0x0 not found)' ) mock_load_cert.return_value = [IPACertificate()] mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), f'O={m_api.env.realm}') framework = object() registry.initialize(framework, config.Config) f = DogtagCertsConnectivityCheck(registry) self.results = capture_results(f) assert len(self.results) == 1 result = self.results.results[0] assert result.result == constants.ERROR assert result.source == 'ipahealthcheck.dogtag.ca' assert result.check == 'DogtagCertsConnectivityCheck' assert result.kw.get('key') == 'cert_show_1' assert result.kw.get('serial') == '1' assert result.kw.get('msg') == 'Serial number not found: {error}'
def test_ca_connection_down(self, mock_load_cert, mock_ca_subject): """CA connectivity check with the CA down""" m_api.Command.cert_show.side_effect = CertificateOperationError( message='Certificate operation cannot be completed: ' 'Unable to communicate with CMS (503)' ) m_api.Command.config_show.side_effect = subject_base mock_load_cert.return_value = [IPACertificate()] mock_ca_subject.return_value = DN(('cn', 'Certificate Authority'), f'O={m_api.env.realm}') framework = object() registry.initialize(framework, config.Config) f = DogtagCertsConnectivityCheck(registry) self.results = capture_results(f) assert len(self.results) == 1 result = self.results.results[0] assert result.result == constants.ERROR assert result.source == 'ipahealthcheck.dogtag.ca' assert result.check == 'DogtagCertsConnectivityCheck' assert result.kw.get('msg') == ( 'Request for certificate failed: {error}' )
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: with open(self.pwd_file, "r") as f: self.password = f.readline().strip() except IOError: self.password = '' # set up key db for crypto functions try: nss.nss_init(self.sec_dir) except Exception as 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 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/rest/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 issue_server_cert(self, certreq_fname, cert_fname): self.setup_cert_request() if self.host_name is None: raise RuntimeError("CA Host is not set.") f = open(certreq_fname, "r") csr = f.readlines() f.close() csr = "".join(csr) # We just want the CSR bits, make sure there is nothing else csr = pkcs10.strip_header(csr) params = { 'profileId': dogtag.DEFAULT_PROFILE, 'cert_request_type': 'pkcs10', 'requestor_name': 'IPA Installer', 'cert_request': csr, 'xmlOutput': 'true' } # Send the request to the CA f = open(self.passwd_fname, "r") password = f.readline() f.close() result = dogtag.https_request(self.host_name, 8443, "/ca/ee/ca/profileSubmitSSLClient", self.secdir, password, "ipaCert", **params) http_status, _http_headers, http_body = result root_logger.debug("CA answer: %s", http_body) if http_status != 200: raise CertificateOperationError( error=_('Unable to communicate with CMS (status %d)') % http_status) # The result is an XML blob. Pull the certificate out of that doc = xml.dom.minidom.parseString(http_body) item_node = doc.getElementsByTagName("b64") try: try: cert = item_node[0].childNodes[0].data except IndexError: raise RuntimeError("Certificate issuance failed") finally: doc.unlink() # base64-decode the result for uniformity cert = base64.b64decode(cert) # Write the certificate to a file. It will be imported in a later # step. This file will be read later to be imported. f = open(cert_fname, "w") f.write(cert) f.close()
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 is None) or (security_data is 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/rest/agent/keyrequests/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 issue_server_cert(self, certreq_fname, cert_fname): self.setup_cert_request() if self.host_name is None: raise RuntimeError("CA Host is not set.") with open(certreq_fname, "rb") as f: csr = f.read() # We just want the CSR bits, make sure there is no thing else csr = strip_csr_header(csr).decode('utf8') params = { 'profileId': dogtag.DEFAULT_PROFILE, 'cert_request_type': 'pkcs10', 'requestor_name': 'IPA Installer', 'cert_request': csr, 'xmlOutput': 'true' } # Send the request to the CA result = dogtag.https_request(self.host_name, 8443, url="/ca/ee/ca/profileSubmitSSLClient", cafile=api.env.tls_ca_cert, client_certfile=paths.RA_AGENT_PEM, client_keyfile=paths.RA_AGENT_KEY, **params) http_status, _http_headers, http_body = result logger.debug("CA answer: %r", http_body) if http_status != 200: raise CertificateOperationError( error=_('Unable to communicate with CMS (status %d)') % http_status) # The result is an XML blob. Pull the certificate out of that doc = xml.dom.minidom.parseString(http_body) item_node = doc.getElementsByTagName("b64") try: try: cert = item_node[0].childNodes[0].data except IndexError: raise RuntimeError("Certificate issuance failed") finally: doc.unlink() # base64-decode the result for uniformity cert = base64.b64decode(cert) # Write the certificate to a file. It will be imported in a later # step. This file will be read later to be imported. with open(cert_fname, "wb") as f: f.write(cert)
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 is None: raise CertificateOperationError( error=_('Bad argument to cancel_recovery_request')) # Call CMS http_status, http_reason_phrase, _http_headers, _http_body = \ self._request('/kra/rest/agent/keyrequests/' + request_id + '/cancel', 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 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 is None: raise CertificateOperationError( error=_('Bad argument to list_security_data')) get_args = "clientID=" + quote_plus(client_id) if key_state is not None: get_args = get_args + "&status=" + quote_plus(key_state) if next_id is not 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/rest/agent/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 is not None: get_args = get_args + "&requestState=" + quote_plus(request_state) if request_type is not None: get_args = get_args + "&requestType=" + quote_plus(request_type) if client_id is not None: get_args = get_args + "&clientID=" + quote_plus(client_id) if next_id is not 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/rest/agent/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 test_ca_connection_down(self): """CA connectivity check with the CA down""" m_api.Command.cert_show.side_effect = CertificateOperationError( message='Certificate operation cannot be completed: ' 'Unable to communicate with CMS (503)') framework = object() registry.initialize(framework, config.Config) f = DogtagCertsConnectivityCheck(registry) self.results = capture_results(f) assert len(self.results) == 1 result = self.results.results[0] assert result.result == constants.ERROR assert result.source == 'ipahealthcheck.dogtag.ca' assert result.check == 'DogtagCertsConnectivityCheck' assert 'Unable to communicate' in result.kw.get('msg')
def test_ca_connection_cert_not_found(self): """CA connectivity check for a cert that doesn't exist""" m_api.Command.cert_show.reset_mock() m_api.Command.cert_show.side_effect = CertificateOperationError( message='Certificate operation cannot be completed: ' 'EXCEPTION (Certificate serial number 0x0 not found)') framework = object() registry.initialize(framework, config.Config) f = DogtagCertsConnectivityCheck(registry) self.results = capture_results(f) assert len(self.results) == 1 result = self.results.results[0] assert result.result == constants.SUCCESS assert result.source == 'ipahealthcheck.dogtag.ca' assert result.check == 'DogtagCertsConnectivityCheck'
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 is 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 = b64encode( nss.pub_wrap_sym_key(self.mechanism, public_key, session_key)) wrapped_passphrase = None if passphrase is not None: # wrap passphrase with session key wrapped_session_key = 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/rest/agent/keys/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 is None: iv = nss.data_to_hex(b64decode(parse_result['nonce_data'])) parse_result['data'] = self.symmetric_unwrap( b64decode(parse_result['wrapped_data']), session_key, iv) return parse_result
def issue_server_cert(self, certreq_fname, cert_fname): self.setup_cert_request() if self.self_signed_ca: p = subprocess.Popen([ "/usr/bin/certutil", "-d", self.secdir, "-C", "-c", self.cacert_name, "-i", certreq_fname, "-o", cert_fname, "-m", next_serial(), "-v", self.valid_months, "-f", self.passwd_fname, "-1", "-5" ], stdin=subprocess.PIPE, stdout=subprocess.PIPE) # Bah - this sucks, but I guess it isn't possible to fully # control this with command line arguments. # # What this is requesting is: # -1 (Create key usage extension) # 2 - Key encipherment # 9 - done # n - not critical # # -5 (Create netscape cert type extension) # 1 - SSL Server # 9 - done # n - not critical p.stdin.write("2\n9\nn\n1\n9\nn\n") p.wait() else: if self.host_name is None: raise RuntimeError("CA Host is not set.") f = open(certreq_fname, "r") csr = f.readlines() f.close() csr = "".join(csr) # We just want the CSR bits, make sure there is nothing else csr = pkcs10.strip_header(csr) params = { 'profileId': 'caIPAserviceCert', 'cert_request_type': 'pkcs10', 'requestor_name': 'IPA Installer', 'cert_request': csr, 'xmlOutput': 'true' } # Send the request to the CA f = open(self.passwd_fname, "r") password = f.readline() f.close() result = dogtag.https_request( self.host_name, api.env.ca_ee_install_port or dogtag.configured_constants().EE_SECURE_PORT, "/ca/ee/ca/profileSubmitSSLClient", self.secdir, password, "ipaCert", **params) http_status, http_reason_phrase, http_headers, http_body = result if http_status != 200: raise CertificateOperationError( error=_('Unable to communicate with CMS (%s)') % http_reason_phrase) # The result is an XML blob. Pull the certificate out of that doc = xml.dom.minidom.parseString(http_body) item_node = doc.getElementsByTagName("b64") try: try: cert = item_node[0].childNodes[0].data except IndexError: raise RuntimeError("Certificate issuance failed") finally: doc.unlink() # base64-decode the result for uniformity cert = base64.b64decode(cert) # Write the certificate to a file. It will be imported in a later # step. This file will be read later to be imported. f = open(cert_fname, "w") f.write(cert) f.close() return