[java-identity-provider] branch master updated: IDP-1275 - Deferred decryption of private key
Scott Cantor
cantor.2 at osu.edu
Tue Jul 31 17:06:43 EDT 2018
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch master
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=a6ce06113d03b3e76958d6bf5fbdd57c76fa496f
The following commit(s) were added to refs/heads/master by this push:
new a6ce061 IDP-1275 - Deferred decryption of private key
a6ce061 is described below
commit a6ce06113d03b3e76958d6bf5fbdd57c76fa496f
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Jul 31 17:06:39 2018 -0400
IDP-1275 - Deferred decryption of private key
https://issues.shibboleth.net/jira/browse/IDP-1275
Lightly tested webflow with some defaults/examples.
---
.../idp/admin/impl/UnlockDataSealers.java | 150 ++++++++++++++++++
.../idp/admin/impl/UnlockPrivateKeys.java | 176 +++++++++++++++++++++
.../main/resources/conf/admin/general-admin.xml | 9 ++
.../resources/system/conf/general-admin-system.xml | 1 +
.../main/resources/system/conf/global-system.xml | 5 +-
.../main/resources/system/conf/webflow-config.xml | 1 +
.../system/flows/admin/unlock-keys-beans.xml | 45 ++++++
.../system/flows/admin/unlock-keys-flow.xml | 65 ++++++++
.../resources/system/messages/messages.properties | 11 ++
.../src/main/resources/views/admin/unlock-keys.vm | 96 +++++++++++
10 files changed, 557 insertions(+), 2 deletions(-)
diff --git a/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/UnlockDataSealers.java b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/UnlockDataSealers.java
new file mode 100644
index 0000000..cc73106
--- /dev/null
+++ b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/UnlockDataSealers.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.admin.impl;
+
+import java.security.KeyException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.security.BasicKeystoreKeyStrategy;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+
+/**
+ * Action that sets keystore and key passwords for one or more DataSealer KeyStrategy
+ * objects based on query parameters.
+ *
+ * <p>The only type supported is the basic strategy type provided with the software.</p>
+ *
+ * <p>An error event will be signaled after the first unsuccessful unlock operation.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#INVALID_MESSAGE}
+ *
+ * @since 3.4.0
+ */
+public class UnlockDataSealers extends AbstractProfileAction {
+
+ /** Name of keystore password parameter. */
+ @Nonnull @NotEmpty public static final String KEYSTORE_PASSWORD_PARAM_NAME = "keystorePassword";
+
+ /** Name of key password parameter. */
+ @Nonnull @NotEmpty public static final String KEY_PASSWORD_PARAM_NAME = "keyPassword";
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(UnlockDataSealers.class);
+
+ /** Key source(s) to operate on. */
+ @Nonnull @NonnullElements private Collection<BasicKeystoreKeyStrategy> keyStrategies;
+
+ /** Constructor. */
+ public UnlockDataSealers() {
+ keyStrategies = Collections.emptyList();
+ }
+
+ /**
+ * Set the {@link BasicKeystoreKeyStrategy} objects to inject passwords into.
+ *
+ * @param strategies objects to unlock
+ */
+ public void setKeyStrategies(@Nullable @NonnullElements final Collection<BasicKeystoreKeyStrategy> strategies) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ if (strategies != null && !strategies.isEmpty()) {
+ keyStrategies = new ArrayList<>(Collections2.filter(strategies, Predicates.notNull()));
+ } else {
+ keyStrategies = Collections.emptyList();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext) || keyStrategies.isEmpty()) {
+ return false;
+ } else if (getHttpServletRequest() == null) {
+ log.warn("{} No HttpServletRequest available", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(final ProfileRequestContext profileRequestContext) {
+
+ final HttpServletRequest request = getHttpServletRequest();
+
+ final String[] keystorePasswords = request.getParameterValues(KEYSTORE_PASSWORD_PARAM_NAME);
+ final String[] keyPasswords = request.getParameterValues(KEY_PASSWORD_PARAM_NAME);
+
+ if (keystorePasswords == null || keyPasswords == null || keystorePasswords.length != keyStrategies.size()
+ || keyPasswords.length != keyStrategies.size()) {
+ log.warn("{} Password parameter count does not match size of configured KeyStrategy inputs",
+ getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ int i = 0;
+ final Iterator<BasicKeystoreKeyStrategy> iter = keyStrategies.iterator();
+ while (iter.hasNext()) {
+ final BasicKeystoreKeyStrategy ks = iter.next();
+
+ if (keystorePasswords[i] == null || keyPasswords[i] == null) {
+ log.warn("{} Empty password supplied at index {}", getLogPrefix(), i);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ ks.setKeystorePassword(keystorePasswords[i]);
+ ks.setKeyPassword(keyPasswords[i]);
+
+ try {
+ ks.getDefaultKey();
+ } catch (final KeyException e) {
+ log.warn("{} Failed to unlock key strategy in collection with index {}", getLogPrefix(), i);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ i++;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/UnlockPrivateKeys.java b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/UnlockPrivateKeys.java
new file mode 100644
index 0000000..0b4758d
--- /dev/null
+++ b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/UnlockPrivateKeys.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.admin.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+
+import org.cryptacular.EncodingException;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.security.credential.MutableCredential;
+import org.opensaml.security.crypto.KeySupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.Resource;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+
+/**
+ * Action that creates private key objects and injects them into existing
+ * {@link MutableCredential} objects.
+ *
+ * <p>An error event will be signaled after the first unsuccessful unlock operation.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#INVALID_MESSAGE}
+ *
+ * @since 3.4.0
+ */
+public class UnlockPrivateKeys extends AbstractProfileAction {
+
+ /** Name of private key password parameter. */
+ @Nonnull @NotEmpty public static final String KEY_PASSWORD_PARAM_NAME = "privateKeyPassword";
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(UnlockPrivateKeys.class);
+
+ /** Credentials to operate on. */
+ @Nonnull @NonnullElements private Collection<MutableCredential> credentials;
+
+ /** Keys to load. */
+ @Nonnull @NonnullElements private Collection<Resource> keyResources;
+
+ /** Constructor. */
+ public UnlockPrivateKeys() {
+ credentials = Collections.emptyList();
+ keyResources = Collections.emptyList();
+ }
+
+ /**
+ * Set the credentials to load keys into.
+ *
+ * @param creds credentials to load keys into
+ */
+ public void setCredentials(@Nullable @NonnullElements final Collection<MutableCredential> creds) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ if (creds != null && !creds.isEmpty()) {
+ credentials = new ArrayList<>(Collections2.filter(creds, Predicates.notNull()));
+ } else {
+ credentials = Collections.emptyList();
+ }
+ }
+
+ /**
+ * Set the key resources to load.
+ *
+ * @param keys key resources to load
+ */
+ public void setKeyResources(@Nullable @NonnullElements final Collection<Resource> keys) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ if (keys != null && !keys.isEmpty()) {
+ keyResources = new ArrayList<>(Collections2.filter(keys, Predicates.notNull()));
+ } else {
+ keyResources = Collections.emptyList();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (keyResources.size() != credentials.size()) {
+ throw new ComponentInitializationException("Size of credential and key resource collections don't match.");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext) || credentials.isEmpty() || keyResources.isEmpty()) {
+ return false;
+ } else if (getHttpServletRequest() == null) {
+ log.warn("{} No HttpServletRequest available", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(final ProfileRequestContext profileRequestContext) {
+
+ final String[] keyPasswords = getHttpServletRequest().getParameterValues(KEY_PASSWORD_PARAM_NAME);
+
+ if (keyPasswords == null || keyPasswords.length != credentials.size()) {
+ log.warn("{} Password parameter count does not match number of configured credentials", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ int i = 0;
+ Iterator<MutableCredential> c_iter = credentials.iterator();
+ Iterator<Resource> k_iter = keyResources.iterator();
+ while (c_iter.hasNext() && k_iter.hasNext()) {
+ final MutableCredential cred = c_iter.next();
+ final Resource resource = k_iter.next();
+
+ if (keyPasswords[i] == null) {
+ log.warn("{} Empty password supplied at index {}", getLogPrefix(), i);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ log.info("{} Attempting unlock of private key in {}", getLogPrefix(), resource.getDescription());
+
+ try (final InputStream is = resource.getInputStream()) {
+ cred.setPrivateKey(KeySupport.decodePrivateKey(is, keyPasswords[i].toCharArray()));
+ log.info("{} Unlocked and injected private key in {}", getLogPrefix(), resource.getDescription());
+ } catch (final KeyException | IOException | EncodingException e) {
+ log.warn("{} Failed to unlock private key with index {}", getLogPrefix(), i, e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ i++;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/conf/admin/general-admin.xml b/idp-conf/src/main/resources/conf/admin/general-admin.xml
index 5301fe7..ca1d877 100644
--- a/idp-conf/src/main/resources/conf/admin/general-admin.xml
+++ b/idp-conf/src/main/resources/conf/admin/general-admin.xml
@@ -59,6 +59,15 @@
c:id="http://shibboleth.net/ns/profiles/metrics"
p:loggingId="Metrics"
p:policyNameLookupStrategy-ref="shibboleth.metrics.AccessPolicyStrategy" />
+
+ <!-- Attended Startup Unlock -->
+ <!--
+ <bean parent="shibboleth.AdminFlow"
+ c:id="http://shibboleth.net/ns/profiles/unlock-keys"
+ p:loggingId="UnlockKeys"
+ p:authenticated="true"
+ p:policyName="AccessByAdminUser" />
+ -->
</util:list>
diff --git a/idp-conf/src/main/resources/system/conf/general-admin-system.xml b/idp-conf/src/main/resources/system/conf/general-admin-system.xml
index 32d1066..1404dc6 100644
--- a/idp-conf/src/main/resources/system/conf/general-admin-system.xml
+++ b/idp-conf/src/main/resources/system/conf/general-admin-system.xml
@@ -14,6 +14,7 @@
<import resource="../../conf/admin/general-admin.xml" />
<import resource="../../conf/admin/metrics.xml" />
+ <import resource="conditional:${idp.home}/conf/admin/unlock-keys.xml" />
<!-- A parent bean to default some of the flow boilerplate. -->
diff --git a/idp-conf/src/main/resources/system/conf/global-system.xml b/idp-conf/src/main/resources/system/conf/global-system.xml
index 4e6102f..bf32695 100644
--- a/idp-conf/src/main/resources/system/conf/global-system.xml
+++ b/idp-conf/src/main/resources/system/conf/global-system.xml
@@ -209,14 +209,15 @@
p:keystoreType="%{idp.sealer.storeType:JCEKS}"
p:keystoreResource="%{idp.sealer.storeResource:}"
p:keyVersionResource="%{idp.sealer.versionResource:}"
- p:keystorePassword="%{idp.sealer.storePassword:}"
p:keyAlias="%{idp.sealer.aliasBase:secret}"
+ p:keystorePassword="%{idp.sealer.storePassword:}"
p:keyPassword="%{idp.sealer.keyPassword:}"
p:updateInterval="%{idp.sealer.updateInterval:PT15M}" />
<bean id="shibboleth.DataSealer" lazy-init="true"
class="net.shibboleth.utilities.java.support.security.DataSealer"
- p:keyStrategy-ref="shibboleth.DataSealerKeyStrategy" />
+ p:keyStrategy-ref="shibboleth.DataSealerKeyStrategy"
+ p:lockedAtStartup="#{ '%{idp.sealer.storePassword:}'.isEmpty() or '%{idp.sealer.keyPassword:}'.isEmpty() }" />
<!-- This is a convenience for compatibility with the examples for configuring this in V2. -->
<alias name="shibboleth.DataSealer" alias="shibboleth.TransientIDDataSealer"/>
diff --git a/idp-conf/src/main/resources/system/conf/webflow-config.xml b/idp-conf/src/main/resources/system/conf/webflow-config.xml
index b8e3cb0..6cb8c1d 100644
--- a/idp-conf/src/main/resources/system/conf/webflow-config.xml
+++ b/idp-conf/src/main/resources/system/conf/webflow-config.xml
@@ -30,6 +30,7 @@
<entry key="admin/mdquery" value="../system/flows/admin/mdquery-flow.xml" />
<entry key="admin/metrics" value="../system/flows/admin/metrics-flow.xml" />
<entry key="admin/storage" value="../system/flows/admin/storage-flow.xml" />
+ <entry key="admin/unlock-keys" value="../system/flows/admin/unlock-keys-flow.xml" />
<!-- Proprietary logout flow. -->
<entry key="Logout" value="../system/flows/logout/logout-flow.xml" />
diff --git a/idp-conf/src/main/resources/system/flows/admin/unlock-keys-beans.xml b/idp-conf/src/main/resources/system/flows/admin/unlock-keys-beans.xml
new file mode 100644
index 0000000..5fa1c57
--- /dev/null
+++ b/idp-conf/src/main/resources/system/flows/admin/unlock-keys-beans.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans
+ xmlns="http://www.springframework.org/schema/beans"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+ <import resource="admin-abstract-beans.xml" />
+
+ <!-- Profile ID for flow. -->
+ <bean id="shibboleth.AdminProfileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/unlock-keys" />
+
+ <!-- Default operation/resource suppliers for access checks. -->
+
+ <bean id="shibboleth.AdminOperationLookupStrategy" class="com.google.common.base.Functions"
+ factory-method="constant" c:_0="unlock" />
+
+ <bean id="shibboleth.AdminResourceLookupStrategy" class="com.google.common.base.Functions"
+ factory-method="constant" c:_0="keys" />
+
+ <!-- Work beans. -->
+
+ <bean id="PostResponsePopulateAuditContext" parent="shibboleth.AbstractPopulateAuditContext"
+ p:fieldExtractors="#{getObject('shibboleth.PostResponseAuditExtractors') ?: getObject('shibboleth.DefaultPostResponseAuditExtractors')}" />
+
+ <bean id="UnlockDataSealers" class="net.shibboleth.idp.admin.impl.UnlockDataSealers" scope="prototype"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+ p:keyStrategies="#{getObject('shibboleth.unlock-keys.KeyStrategies')}" />
+
+ <bean id="UnlockPrivateKeys" class="net.shibboleth.idp.admin.impl.UnlockPrivateKeys" scope="prototype"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+ p:credentials="#{getObject('shibboleth.unlock-keys.Credentials')}"
+ p:keyResources="#{getObject('shibboleth.unlock-keys.PrivateKeys')}" />
+
+</beans>
diff --git a/idp-conf/src/main/resources/system/flows/admin/unlock-keys-flow.xml b/idp-conf/src/main/resources/system/flows/admin/unlock-keys-flow.xml
new file mode 100644
index 0000000..7a25554
--- /dev/null
+++ b/idp-conf/src/main/resources/system/flows/admin/unlock-keys-flow.xml
@@ -0,0 +1,65 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+ parent="admin.abstract">
+
+ <!-- Start action. -->
+
+ <action-state id="InitializeProfileRequestContext">
+ <evaluate expression="InitializeProfileRequestContext" />
+ <evaluate expression="'proceed'" />
+
+ <!-- Branch to determine if authentication is required. -->
+ <transition on="proceed" to="DoAdminPreamble" />
+ </action-state>
+
+ <!-- Resume actual flow processing. -->
+
+ <action-state id="DoProfileWork">
+ <evaluate expression="CheckAccess" />
+ <evaluate expression="PostResponsePopulateAuditContext" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="PromptForPasswords" />
+ </action-state>
+
+ <view-state id="PromptForPasswords" view="admin/unlock-keys">
+ <on-render>
+ <evaluate expression="environment" result="viewScope.environment" />
+ <evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
+ <evaluate expression="T(net.shibboleth.utilities.java.support.codec.HTMLEncoder)" result="viewScope.encoder" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().containsBean('shibboleth.CustomViewContext') ? flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CustomViewContext') : null" result="viewScope.custom" />
+ </on-render>
+
+ <transition on="proceed" to="UnlockKeys" />
+ <transition on="cancel" to="end" />
+ </view-state>
+
+ <action-state id="UnlockKeys">
+ <evaluate expression="UnlockDataSealers" />
+ <evaluate expression="UnlockPrivateKeys" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="end" />
+ <transition on="InvalidMessage" to="PromptForPasswords" />
+ </action-state>
+
+ <!-- Terminus -->
+
+ <end-state id="end" view="admin/unlock-keys">
+ <on-entry>
+ <evaluate expression="WriteAuditLog" />
+ <evaluate expression="environment" result="requestScope.environment" />
+ <evaluate expression="opensamlProfileRequestContext" result="requestScope.profileRequestContext" />
+ <evaluate expression="T(net.shibboleth.utilities.java.support.codec.HTMLEncoder)" result="requestScope.encoder" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="requestScope.request" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="requestScope.response" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().containsBean('shibboleth.CustomViewContext') ? flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CustomViewContext') : null" result="requestScope.custom" />
+ </on-entry>
+ </end-state>
+
+ <bean-import resource="unlock-keys-beans.xml" />
+
+</flow>
diff --git a/idp-conf/src/main/resources/system/messages/messages.properties b/idp-conf/src/main/resources/system/messages/messages.properties
index c0a2f9c..29eae36 100644
--- a/idp-conf/src/main/resources/system/messages/messages.properties
+++ b/idp-conf/src/main/resources/system/messages/messages.properties
@@ -177,6 +177,17 @@ idp.impersonate.header = Account Impersonation
idp.impersonate.login-as = Login as
idp.impersonate.proceed = Proceed
+# General messages related to key-unlocking admin flow
+
+idp.unlock-keys.title = Attended Restart Key Unlock
+idp.unlock-keys.keystorePassword = DataSealer Keystore Password
+idp.unlock-keys.keyPassword = DataSealer Key Password
+idp.unlock-keys.privateKeyPassword = Private Key Password
+idp.unlock-keys.unlock = Unlock
+idp.unlock-keys.cancel = Cancel
+idp.unlock-keys.complete = The system is unlocked and ready for use.
+idp.unlock-keys.error = Unlock failed; check log for specific message.
+
# Triples consisting of a TOU key, and a title and text for each set of terms.
# The default implementation uses the SP name as the key, but this can be overriden.
diff --git a/idp-conf/src/main/resources/views/admin/unlock-keys.vm b/idp-conf/src/main/resources/views/admin/unlock-keys.vm
new file mode 100644
index 0000000..e91123e
--- /dev/null
+++ b/idp-conf/src/main/resources/views/admin/unlock-keys.vm
@@ -0,0 +1,96 @@
+##
+## Velocity Template for Attended Startup Unlock Utility
+##
+## Velocity context will contain the following properties:
+## flowRequestContext - the Spring Web Flow RequestContext
+## request - HttpServletRequest
+## response - HttpServletResponse
+## profileRequestContext
+## environment - Spring Environment object for property resolution
+## custom - arbitrary object injected by deployer
+##
+#set ($title = $springMacroRequestContext.getMessage("idp.title", "Web Login Service"))
+#set ($titleSuffix = $springMacroRequestContext.getMessage("idp.unlock-keys.title", "Attended Restart Key Unlock"))
+#set ($eventId = $profileRequestContext.getSubcontext("org.opensaml.profile.context.EventContext").getEvent())
+#set ($state = $flowRequestContext.getCurrentState().getId())
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
+ <title>$title - $titleSuffix</title>
+ <link rel="stylesheet" type="text/css" href="$request.getContextPath()/css/main.css">
+ </head>
+
+ <body>
+ <div class="wrapper">
+ <div class="container">
+ <header>
+ <img src="$request.getContextPath()#springMessage("idp.logo")" alt="#springMessageText("idp.logo.alt-text", "logo")">
+ <h3>$title - $titleSuffix</h3>
+ </header>
+
+ <div class="content">
+ #if ($state == "end")
+ <strong>#springMessageText("idp.unlock-keys.complete", "The system is unlocked and ready for use.")</strong>
+ <p><a hef="$request.getContextPath()/profile/SAML2/SSO/Unsolicited?providerId=https://sp.example.org/shibboleth">Validation Link</a></p>
+ #else
+ #if ($eventId == "InvalidMessage")
+ <p class="form-element form-error">
+ #springMessageText("idp.unlock-keys.error", "Unlock failed; check log for specific message.")
+ </p>
+ <br/><br/>
+ #end
+
+ <form action="$flowExecutionUrl" method="post">
+
+ <!--
+ If you have multiple key strategies defined, you'll need multiple pairs of form fields for
+ the passwords, labeled in the order they're fed into the shibboleth.unlock-keys.KeyStrategies
+ bean. If you have none, remove the fields.
+ -->
+
+ <div class="form-element-wrapper">
+ <label for="password">#springMessageText("idp.unlock-keys.keystorePassword", "DataSealer Keystore Password")</label>
+ <input class="form-element form-field" id="password" name="keystorePassword" type="password">
+ </div>
+
+ <div class="form-element-wrapper">
+ <label for="password">#springMessageText("idp.unlock-keys.keyPassword", "DataSealer Key Password")</label>
+ <input class="form-element form-field" id="password" name="keyPassword" type="password">
+ </div>
+
+ <!--
+ If you have multiple private keys defined, you'll need a form field for each passwords, labeled in the order
+ they're fed into the shibboleth.unlock-keys.Credentials/PrivateKeys beans. If you have none, remove the fields.
+ -->
+
+ <div class="form-element-wrapper">
+ <label for="password">#springMessageText("idp.unlock-keys.privateKeyPassword", "Private Key Password")</label>
+ <input class="form-element form-field" id="password" name="privateKeyPassword" type="password">
+ </div>
+
+ <div class="form-element-wrapper">
+ <button class="form-element form-button" type="submit" name="_eventId_proceed"
+ >#springMessageText("idp.unlock-keys.unlock", "Unlock")</button>
+ </div>
+
+ <div class="form-element-wrapper">
+ <button class="form-element form-button" type="submit" name="_eventId_cancel"
+ >#springMessageText("idp.unlock-keys.cancel", "Cancel")</button>
+ </div>
+
+ </form>
+ #end
+ </div>
+ </div>
+
+ <footer>
+ <div class="container container-footer">
+ <p class="footer-text">#springMessageText("idp.footer", "Insert your footer text here.")</p>
+ </div>
+ </footer>
+
+ </div>
+ </body>
+</html>
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list