Skip to content

Commit 88fb485

Browse files
committed
implement storedvalueencoder (refactoring)
1 parent 35a19de commit 88fb485

29 files changed

+432
-181
lines changed

server/src/main/java/password/pwm/config/StoredValue.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
package password.pwm.config;
2222

23+
import lombok.Builder;
24+
import lombok.Value;
2325
import password.pwm.error.PwmException;
2426
import password.pwm.util.java.XmlElement;
2527
import password.pwm.util.secure.PwmSecurityKey;
@@ -30,7 +32,15 @@
3032

3133
public interface StoredValue extends Serializable
3234
{
33-
List<XmlElement> toXmlValues( String valueElementName, PwmSecurityKey pwmSecurityKey );
35+
@Value
36+
@Builder
37+
class OutputConfiguration
38+
{
39+
private StoredValueEncoder.SecureOutputMode secureOutputMode;
40+
private PwmSecurityKey pwmSecurityKey;
41+
}
42+
43+
List<XmlElement> toXmlValues( String valueElementName, OutputConfiguration outputConfiguration );
3444

3545
Object toNativeObject( );
3646

@@ -40,8 +50,6 @@ public interface StoredValue extends Serializable
4050

4151
String toDebugString( Locale locale );
4252

43-
boolean requiresStoredUpdate( );
44-
4553
int currentSyntaxVersion( );
4654

4755
interface StoredValueFactory
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*
2+
* Password Management Servlets (PWM)
3+
* http://www.pwm-project.org
4+
*
5+
* Copyright (c) 2006-2009 Novell, Inc.
6+
* Copyright (c) 2009-2019 The PWM Project
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package password.pwm.config;
22+
23+
import lombok.Value;
24+
import password.pwm.PwmConstants;
25+
import password.pwm.error.ErrorInformation;
26+
import password.pwm.error.PwmError;
27+
import password.pwm.error.PwmOperationalException;
28+
import password.pwm.util.java.JsonUtil;
29+
import password.pwm.util.java.StringUtil;
30+
import password.pwm.util.logging.PwmLogger;
31+
import password.pwm.util.secure.PwmBlockAlgorithm;
32+
import password.pwm.util.secure.PwmRandom;
33+
import password.pwm.util.secure.PwmSecurityKey;
34+
import password.pwm.util.secure.SecureEngine;
35+
36+
import java.io.Serializable;
37+
import java.util.Arrays;
38+
import java.util.Collections;
39+
import java.util.List;
40+
import java.util.Optional;
41+
42+
public class StoredValueEncoder
43+
{
44+
public enum SecureOutputMode
45+
{
46+
Plain( new PlaintextModeEngine(), "PLAIN" + DELIMITER, "PLAINTEXT" + DELIMITER, "RAW" + DELIMITER ),
47+
Stripped( new StrippedModeEngine(), "REMOVED" + DELIMITER ),
48+
Encoded( new EncodedModeEngine(), "ENC-PW" + DELIMITER, "ENCODED" + DELIMITER ),;
49+
50+
private final List<String> prefixes;
51+
private final SecureOutputEngine secureOutputEngine;
52+
53+
SecureOutputMode( final SecureOutputEngine secureOutputEngine, final String... prefixes )
54+
{
55+
this.secureOutputEngine = secureOutputEngine;
56+
this.prefixes = Collections.unmodifiableList( Arrays.asList( prefixes ) );
57+
}
58+
59+
public List<String> getPrefixes()
60+
{
61+
return prefixes;
62+
}
63+
64+
public String getPrefix()
65+
{
66+
return prefixes.iterator().next();
67+
}
68+
69+
public SecureOutputEngine getSecureOutputEngine()
70+
{
71+
return secureOutputEngine;
72+
}
73+
}
74+
75+
76+
private static final PwmLogger LOGGER = PwmLogger.forClass( StoredValueEncoder.class );
77+
private static final String DELIMITER = ":";
78+
79+
public static Optional<String> decode(
80+
final String input,
81+
final StoredValue.OutputConfiguration outputConfiguration
82+
)
83+
throws PwmOperationalException
84+
{
85+
return decode( input, outputConfiguration.getSecureOutputMode(), outputConfiguration.getPwmSecurityKey() );
86+
}
87+
88+
89+
public static Optional<String> decode(
90+
final String input,
91+
final SecureOutputMode modeHint,
92+
final PwmSecurityKey pwmSecurityKey
93+
)
94+
throws PwmOperationalException
95+
{
96+
if ( StringUtil.isEmpty( input ) )
97+
{
98+
return Optional.empty();
99+
}
100+
101+
final ParsedInput parsedInput = ParsedInput.parseInput( input );
102+
final SecureOutputMode requestedMode = modeHint == null ? SecureOutputMode.Plain : modeHint;
103+
final SecureOutputMode effectiveMode = parsedInput.getSecureOutputMode() == null
104+
? requestedMode
105+
: parsedInput.getSecureOutputMode();
106+
return Optional.ofNullable( effectiveMode.getSecureOutputEngine().decode( parsedInput, pwmSecurityKey ) );
107+
}
108+
109+
public static String encode( final String realValue, final SecureOutputMode mode, final PwmSecurityKey pwmSecurityKey )
110+
throws PwmOperationalException
111+
{
112+
return mode.getSecureOutputEngine().encode( realValue, pwmSecurityKey );
113+
}
114+
115+
@Value
116+
private static class ParsedInput
117+
{
118+
private SecureOutputMode secureOutputMode;
119+
private String value;
120+
121+
static ParsedInput parseInput( final String value )
122+
{
123+
if ( !StringUtil.isEmpty( value ) )
124+
{
125+
for ( final SecureOutputMode mode : SecureOutputMode.values() )
126+
{
127+
for ( final String prefix : mode.getPrefixes() )
128+
{
129+
if ( value.startsWith( prefix ) )
130+
{
131+
return new ParsedInput( mode, value.substring( prefix.length() ) );
132+
}
133+
}
134+
}
135+
}
136+
137+
return new ParsedInput( null, value );
138+
}
139+
}
140+
141+
private interface SecureOutputEngine
142+
{
143+
String encode( String rawOutput, PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException;
144+
145+
String decode( ParsedInput input, PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException;
146+
}
147+
148+
149+
private static class PlaintextModeEngine implements SecureOutputEngine
150+
{
151+
@Override
152+
public String encode( final String rawValue, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException
153+
{
154+
return SecureOutputMode.Plain.getPrefix() + rawValue;
155+
}
156+
157+
@Override
158+
public String decode( final ParsedInput input, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException
159+
{
160+
return input.getValue();
161+
}
162+
}
163+
164+
private static class StrippedModeEngine implements SecureOutputEngine
165+
{
166+
@Override
167+
public String encode( final String rawValue, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException
168+
{
169+
return SecureOutputMode.Plain.getPrefix() + rawValue;
170+
}
171+
172+
@Override
173+
public String decode( final ParsedInput input, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException
174+
{
175+
return PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT;
176+
}
177+
}
178+
179+
private static class EncodedModeEngine implements SecureOutputEngine
180+
{
181+
@Override
182+
public String encode( final String rawValue, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException
183+
{
184+
if ( rawValue == null )
185+
{
186+
return SecureOutputMode.Encoded.getPrefix();
187+
}
188+
189+
// make sure value isn't already encoded
190+
if ( ParsedInput.parseInput( rawValue ).getSecureOutputMode() == null )
191+
{
192+
try
193+
{
194+
final String salt = PwmRandom.getInstance().alphaNumericString( 32 );
195+
final StoredPwData storedPwData = new StoredPwData( salt, rawValue );
196+
final String jsonData = JsonUtil.serialize( storedPwData );
197+
final String encryptedValue = SecureEngine.encryptToString( jsonData, pwmSecurityKey, PwmBlockAlgorithm.CONFIG );
198+
return SecureOutputMode.Encoded.getPrefix() + encryptedValue;
199+
}
200+
catch ( final Exception e )
201+
{
202+
final String errorMsg = "unable to encrypt password value for setting: " + e.getMessage();
203+
final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg );
204+
throw new PwmOperationalException( errorInfo );
205+
}
206+
}
207+
208+
return rawValue;
209+
}
210+
211+
@Override
212+
public String decode( final ParsedInput input, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException
213+
{
214+
try
215+
{
216+
final String pwValueSuffix = input.getValue( );
217+
final String decryptedValue = SecureEngine.decryptStringValue( pwValueSuffix, pwmSecurityKey, PwmBlockAlgorithm.CONFIG );
218+
final StoredPwData storedPwData = JsonUtil.deserialize( decryptedValue, StoredPwData.class );
219+
return storedPwData.getValue();
220+
}
221+
catch ( final Exception e )
222+
{
223+
final String errorMsg = "unable to decrypt password value for setting: " + e.getMessage();
224+
final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg );
225+
LOGGER.warn( errorInfo.toDebugStr() );
226+
throw new PwmOperationalException( errorInfo );
227+
}
228+
}
229+
}
230+
231+
@Value
232+
private static class StoredPwData implements Serializable
233+
{
234+
private String salt;
235+
private String value;
236+
}
237+
238+
}

server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public enum ConfigurationProperty
2828
NOTES( "notes" ),
2929
PASSWORD_HASH( "configPasswordHash" ),
3030
SAVE_CONFIG_ON_START( "saveConfigOnStart" ),
31-
MODIFIFICATION_TIMESTAMP( "modificationTimestamp" ),
31+
MODIFICATION_TIMESTAMP( "modificationTimestamp" ),
3232
IMPORT_LDAP_CERTIFICATES( "importLdapCertificates" ),;
3333

3434
private final String key;

server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import password.pwm.config.PwmSettingTemplate;
2929
import password.pwm.config.PwmSettingTemplateSet;
3030
import password.pwm.config.StoredValue;
31+
import password.pwm.config.StoredValueEncoder;
3132
import password.pwm.config.value.LocalizedStringValue;
3233
import password.pwm.config.value.StringValue;
3334
import password.pwm.config.value.ValueFactory;
@@ -213,7 +214,7 @@ Optional<StoredConfigData.ValueAndMetaTuple> readSetting( final PwmSetting setti
213214
return Optional.of( new StoredConfigData.ValueAndMetaTuple( key, storedValue, metaData ) );
214215

215216
}
216-
catch ( PwmException e )
217+
catch ( final PwmException e )
217218
{
218219
final String errorMsg = "unexpected error reading setting '" + setting.getKey() + "' profile '" + profileID + "', error: " + e.getMessage();
219220
throw new IllegalStateException( errorMsg );
@@ -287,7 +288,7 @@ Instant readModifyTime()
287288
{
288289
return JavaHelper.parseIsoToInstant( modifyTimeString );
289290
}
290-
catch ( Exception e )
291+
catch ( final Exception e )
291292
{
292293
LOGGER.error( "error parsing root last modified timestamp: " + e.getMessage() );
293294
}
@@ -315,7 +316,7 @@ private PwmSettingTemplate readTemplateValue( final PwmSetting pwmSetting )
315316
final String strValue = ( String ) ValueFactory.fromXmlValues( pwmSetting, settingElement.get(), null ).toNativeObject();
316317
return JavaHelper.readEnumFromString( PwmSettingTemplate.class, null, strValue );
317318
}
318-
catch ( IllegalStateException e )
319+
catch ( final IllegalStateException e )
319320
{
320321
LOGGER.error( "error reading template", e );
321322
}
@@ -365,7 +366,7 @@ private Optional<ValueMetaData> readMetaDataFromXmlElement( final StoredConfigIt
365366
{
366367
instant = JavaHelper.parseIsoToInstant( modifyTimeValue );
367368
}
368-
catch ( DateTimeParseException e )
369+
catch ( final DateTimeParseException e )
369370
{
370371
e.printStackTrace();
371372
}
@@ -381,7 +382,7 @@ private Optional<ValueMetaData> readMetaDataFromXmlElement( final StoredConfigIt
381382
{
382383
userIdentity = UserIdentity.fromDelimitedKey( modifyUserValue );
383384
}
384-
catch ( DateTimeParseException | PwmUnrecoverableException e )
385+
catch ( final DateTimeParseException | PwmUnrecoverableException e )
385386
{
386387
LOGGER.trace( () -> "unable to parse userIdentity metadata for key " + key.toString() );
387388
}
@@ -481,14 +482,17 @@ static XmlElement makeSettingsXmlElement( final StoredConfiguration storedConfig
481482
for ( final String profileID : storedConfiguration.profilesForSetting( pwmSetting ) )
482483
{
483484
final StoredValue storedValue = storedConfiguration.readSetting( pwmSetting, profileID );
484-
settingsElement.addContent( makeSettingXmlElement( pwmSetting, profileID, storedValue, pwmSecurityKey ) );
485+
final XmlElement settingElement = makeSettingXmlElement( pwmSetting, profileID, storedValue, pwmSecurityKey );
486+
decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromSetting( pwmSetting, profileID ), settingElement );
487+
settingsElement.addContent( settingsElement );
485488
}
486489
}
487490
else
488491
{
489492
final StoredValue storedValue = storedConfiguration.readSetting( pwmSetting, null );
490-
settingsElement.addContent( makeSettingXmlElement( pwmSetting, null, storedValue, pwmSecurityKey ) );
491-
493+
final XmlElement settingElement = makeSettingXmlElement( pwmSetting, null, storedValue, pwmSecurityKey );
494+
decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromSetting( pwmSetting, null ), settingElement );
495+
settingsElement.addContent( settingsElement );
492496
}
493497
}
494498
}
@@ -532,7 +536,12 @@ static XmlElement makeSettingXmlElement(
532536
settingElement.setComment( commentLines );
533537
}
534538

535-
final List<XmlElement> valueElements = storedValue.toXmlValues( StoredConfigXmlConstants.XML_ELEMENT_VALUE, pwmSecurityKey );
539+
final StoredValue.OutputConfiguration outputConfiguration = StoredValue.OutputConfiguration.builder()
540+
.pwmSecurityKey( pwmSecurityKey )
541+
.secureOutputMode( StoredValueEncoder.SecureOutputMode.Encoded )
542+
.build();
543+
544+
final List<XmlElement> valueElements = storedValue.toXmlValues( StoredConfigXmlConstants.XML_ELEMENT_VALUE, outputConfiguration );
536545
settingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_SYNTAX_VERSION, String.valueOf( storedValue.currentSyntaxVersion() ) );
537546
settingElement.addContent( valueElements );
538547
return settingElement;
@@ -573,6 +582,7 @@ private static XmlElement makePropertiesElement( final StoredConfiguration store
573582
final XmlElement propertyElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_PROPERTY );
574583
propertyElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY, configurationProperty.getKey() );
575584
propertyElement.addText( s );
585+
decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromConfigurationProperty( configurationProperty ), propertyElement );
576586
propertiesElement.addContent( propertyElement );
577587
}
578588
);
@@ -599,14 +609,16 @@ private static List<XmlElement> makeLocaleBundleXmlElements( final StoredConfigu
599609
final Map<String, String> localeBundleMap = storedConfiguration.readLocaleBundleMap( pwmLocaleBundle, key );
600610
for ( final Map.Entry<String, String> entry : localeBundleMap.entrySet() )
601611
{
602-
final XmlElement valueElement = xmlFactory.newElement( "value" );
612+
final XmlElement valueElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_VALUE );
603613
if ( !StringUtil.isEmpty( entry.getKey() ) )
604614
{
605-
valueElement.setAttribute( "locale", entry.getKey() );
615+
valueElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_LOCALE, entry.getKey() );
606616
}
607617
valueElement.addText( entry.getValue() );
608618
localeBundleElement.addContent( valueElement );
609619
}
620+
621+
decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, key ), localeBundleElement );
610622
returnList.add( localeBundleElement );
611623
}
612624
}

0 commit comments

Comments
 (0)