diff --git a/application/controllers/TlsclientidentityController.php b/application/controllers/TlsclientidentityController.php
new file mode 100644
index 000000000..01d9b6b84
--- /dev/null
+++ b/application/controllers/TlsclientidentityController.php
@@ -0,0 +1,134 @@
+assertPermission('config/application/tlscert');
+
+ parent::init();
+ }
+
+ public function createAction()
+ {
+ $this->view->form = $form = new CreateForm();
+ $form->setRedirectUrl('tlsclientidentity/edit')
+ ->handleRequest();
+
+ $this->addTitleTab(
+ $this->translate('New Client Identity'),
+ $this->translate('Create A New TLS Client Identity')
+ );
+
+ $this->render('form');
+ }
+
+ public function editAction()
+ {
+ $this->view->editForm = $editForm = new EditForm();
+ $name = $this->params->getRequired('name');
+ $editForm->setOldName($name)
+ ->setRedirectUrl('tlsclientidentity/edit')
+ ->handleRequest();
+
+ $rawIdentity = LocalFileStorage::common('tls/clientidentities')->read(bin2hex($name) . '.pem');
+
+ preg_match(
+ '/-+BEGIN CERTIFICATE-+.+?-+END CERTIFICATE-+/s',
+ $rawIdentity,
+ $cert
+ );
+
+ $this->view->cert = array(
+ 'info' => openssl_x509_parse($cert[0]),
+ 'sha1' => openssl_x509_fingerprint($cert[0], 'sha1'),
+ 'sha256' => openssl_x509_fingerprint($cert[0], 'sha256'),
+ );
+
+ preg_match(
+ '/-+BEGIN PRIVATE KEY-+.+?-+END PRIVATE KEY-+/s',
+ $rawIdentity,
+ $key
+ );
+
+ $keyDetails = openssl_pkey_get_details(openssl_pkey_get_private($key[0]));
+ $pubKey = base64_decode(preg_replace('/-+(?:BEGIN|END) PUBLIC KEY-+/', '', $keyDetails['key']));
+ $this->view->pubKey = array(
+ 'sha1' => hash('sha1', $pubKey),
+ 'sha256' => hash('sha256', $pubKey)
+ );
+
+ $this->addTitleTab(
+ $this->translate('Edit Client Identity'),
+ sprintf($this->translate('Edit TLS Client Identity "%s"'), $name)
+ );
+ }
+
+ public function removeAction()
+ {
+ $clientIdentities = LocalFileStorage::common('tls/clientidentities');
+
+ $name = $this->params->getRequired('name');
+ $fileName = bin2hex($name) . '.pem';
+ $clientIdentities->resolvePath($fileName, true);
+
+ $this->view->form = $form = new ConfirmRemovalForm();
+ $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($name, $fileName, $clientIdentities) {
+ try {
+ $clientIdentities->delete($fileName);
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ Notification::success(
+ sprintf(t('TLS client identity "%s" successfully removed'), $name)
+ );
+ return true;
+ })
+ ->setRedirectUrl('config/tls')
+ ->handleRequest();
+
+ $this->addTitleTab(
+ $this->translate('Remove Client Identity'),
+ sprintf($this->translate('Remove TLS Client Identity "%s"'), $name)
+ );
+
+ $this->render('form');
+ }
+
+ /**
+ * Add primary tab with the given label and title
+ *
+ * @param string $label
+ * @param string $title
+ */
+ protected function addTitleTab($label, $title)
+ {
+ $url = clone $this->getRequest()->getUrl();
+
+ $this->getTabs()->add(
+ preg_replace('~\A.*/~', '', $url->getPath()),
+ array(
+ 'active' => true,
+ 'label' => $label,
+ 'title' => $title,
+ 'url' => $url
+ )
+ );
+ }
+}
diff --git a/application/forms/Config/Tls/ClientIdentity/CreateForm.php b/application/forms/Config/Tls/ClientIdentity/CreateForm.php
new file mode 100644
index 000000000..220072e21
--- /dev/null
+++ b/application/forms/Config/Tls/ClientIdentity/CreateForm.php
@@ -0,0 +1,97 @@
+setName('form_config_tlsclientidentity');
+ $this->setSubmitLabel($this->translate('Create'));
+ }
+
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'label' => $this->translate('Name'),
+ 'description' => $this->translate('The new TLS client identity\'s name'),
+ 'required' => true
+ )
+ );
+
+ $this->addElement(
+ 'file',
+ 'cert',
+ array(
+ 'label' => $this->translate('Certificate (PEM)'),
+ 'description' => $this->translate('The new TLS client certificate'),
+ 'required' => true,
+ 'validators' => array(new TlsCertFileValidator())
+ )
+ );
+
+ $this->addElement(
+ 'file',
+ 'key',
+ array(
+ 'label' => $this->translate('Private Key (PEM)'),
+ 'description' => $this->translate('The new TLS client private key'),
+ 'required' => true,
+ 'validators' => array(new TlsKeyFileValidator())
+ )
+ );
+ }
+
+ public function onSuccess()
+ {
+ $name = $this->getElement('name')->getValue();
+
+ try {
+ /** @var \Zend_Form_Element_File $cert */
+ $cert = $this->getElement('cert');
+
+ if ($cert->isUploaded()) {
+ $cert->getValue();
+ }
+
+ openssl_x509_export('file://' . $cert->getFileName(), $newCert);
+
+ /** @var \Zend_Form_Element_File $key */
+ $key = $this->getElement('key');
+
+ if ($key->isUploaded()) {
+ $key->getValue();
+ }
+
+ openssl_pkey_export('file://' . $key->getFileName(), $newKey);
+
+ $newCertDetails = openssl_pkey_get_details(openssl_pkey_get_public($newCert));
+ $newKeyDetails = openssl_pkey_get_details(openssl_pkey_get_private($newKey));
+
+ if ($newCertDetails['key'] !== $newKeyDetails['key']) {
+ $this->error($this->translate('The public keys of the certificate and the private key don\'t match'));
+ return false;
+ }
+
+ LocalFileStorage::common('tls/clientidentities')->create(bin2hex($name) . '.pem', $newCert . $newKey);
+ } catch (Exception $e) {
+ $this->error($e->getMessage());
+ return false;
+ }
+
+ $this->getRedirectUrl()->setParam('name', $name);
+ }
+}
diff --git a/application/forms/Config/Tls/ClientIdentity/EditForm.php b/application/forms/Config/Tls/ClientIdentity/EditForm.php
new file mode 100644
index 000000000..b79425071
--- /dev/null
+++ b/application/forms/Config/Tls/ClientIdentity/EditForm.php
@@ -0,0 +1,85 @@
+setName('form_config_tlsclientidentity');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ }
+
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'label' => $this->translate('Name'),
+ 'description' => $this->translate('The TLS client identity\'s name'),
+ 'required' => true,
+ 'value' => $this->oldName
+ )
+ );
+
+ $this->addElement(
+ 'hidden',
+ 'old_name',
+ array(
+ 'required' => true,
+ 'disabled' => true,
+ 'value' => $this->oldName
+ )
+ );
+ }
+
+ public function onSuccess()
+ {
+ $name = $this->getElement('name')->getValue();
+
+ if ($name !== $this->oldName) {
+ try {
+ $clientIdentities = LocalFileStorage::common('tls/clientidentities');
+ $oldFileName = bin2hex($this->oldName) . '.pem';
+
+ $clientIdentities->create(bin2hex($name) . '.pem', $clientIdentities->read($oldFileName));
+ $clientIdentities->delete($oldFileName);
+ } catch (Exception $e) {
+ $this->error($e->getMessage());
+ return false;
+ }
+ }
+
+ $this->getRedirectUrl()->setParam('name', $name);
+ }
+
+ /**
+ * Set the TLS client identity's old name
+ *
+ * @param string $oldName
+ *
+ * @return $this
+ */
+ public function setOldName($oldName)
+ {
+ $this->oldName = $oldName;
+
+ return $this;
+ }
+}
diff --git a/application/views/scripts/tlsclientidentity/edit.phtml b/application/views/scripts/tlsclientidentity/edit.phtml
new file mode 100644
index 000000000..22c7851df
--- /dev/null
+++ b/application/views/scripts/tlsclientidentity/edit.phtml
@@ -0,0 +1,78 @@
+success() ? $timezoneDetect->getTimezoneName() : date_default_timezone_get()
+);
+
+$subject = array();
+foreach ($cert['info']['subject'] as $key => $value) {
+ $subject[] = $this->escape("$key = " . var_export($value, true));
+}
+
+$issuer = array();
+foreach ($cert['info']['issuer'] as $key => $value) {
+ $issuer[] = $this->escape("$key = " . var_export($value, true));
+}
+?>
+
+ = /** @var \Icinga\Web\Widget\Tabs $tabs */ $tabs ?>
+
+
+ = /** @var \Icinga\Forms\Config\Tls\RootCaCollection\EditForm $editForm */ $editForm ?>
+
+
= /** @var \Icinga\Web\View $this */ $this->translate('TLS Client Certificate') ?>
+
+
+
+ = $this->escape($this->translate('Subject', 'x509.certificate')) ?> |
+ = implode(' ', $subject) ?> |
+
+
+ = $this->escape($this->translate('Issuer', 'x509.certificate')) ?> |
+ = implode(' ', $issuer) ?> |
+
+
+ = $this->escape($this->translate('Valid from', 'x509.certificate')) ?> |
+ = $this->escape(
+ DateTime::createFromFormat('U', $cert['info']['validFrom_time_t'])
+ ->setTimezone($timeZone)
+ ->format(DateTime::ISO8601)
+ ) ?> |
+
+
+ = $this->escape($this->translate('Valid until', 'x509.certificate')) ?> |
+ = $this->escape(
+ DateTime::createFromFormat('U', $cert['info']['validTo_time_t'])
+ ->setTimezone($timeZone)
+ ->format(DateTime::ISO8601)
+ ) ?> |
+
+
+ = $this->escape($this->translate('SHA256 fingerprint', 'x509.certificate')) ?> |
+ = $this->escape(implode(' ', str_split(strtoupper($cert['sha256']), 2))) ?> |
+
+
+ = $this->escape($this->translate('SHA1 fingerprint', 'x509.certificate')) ?> |
+ = $this->escape(implode(' ', str_split(strtoupper($cert['sha1']), 2))) ?> |
+
+
+
+
= /** @var \Icinga\Web\View $this */ $this->translate('TLS Client Public Key') ?>
+
+
+
+ = $this->escape($this->translate('SHA256 fingerprint', 'x509.certificate')) ?> |
+ = $this->escape(implode(' ', str_split(strtoupper($pubKey['sha256']), 2))) ?> |
+
+
+ = $this->escape($this->translate('SHA1 fingerprint', 'x509.certificate')) ?> |
+ = $this->escape(implode(' ', str_split(strtoupper($pubKey['sha1']), 2))) ?> |
+
+
+
\ No newline at end of file
diff --git a/application/views/scripts/tlsclientidentity/form.phtml b/application/views/scripts/tlsclientidentity/form.phtml
new file mode 100644
index 000000000..67aa7ee70
--- /dev/null
+++ b/application/views/scripts/tlsclientidentity/form.phtml
@@ -0,0 +1,6 @@
+
+ = /** @var \Icinga\Web\Widget\Tabs $tabs */ $tabs ?>
+
+
+ = /** @var Icinga\Web\Form $form */ $form ?>
+
\ No newline at end of file