Skip to content

Commit 65d3b0d

Browse files
committed
Add ResourceKeyConverterAdapter
Simplifies publishing RsaKeyConverters with @ConfigurationPropertiesBinding Issue gh-9316
1 parent 06b748c commit 65d3b0d

File tree

4 files changed

+258
-63
lines changed

4 files changed

+258
-63
lines changed

config/src/main/java/org/springframework/security/config/crypto/RsaKeyConversionServicePostProcessor.java

Lines changed: 11 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@
1818

1919
import java.beans.PropertyEditor;
2020
import java.beans.PropertyEditorSupport;
21-
import java.io.ByteArrayInputStream;
22-
import java.io.IOException;
23-
import java.io.InputStream;
24-
import java.io.UncheckedIOException;
25-
import java.nio.charset.StandardCharsets;
2621
import java.security.interfaces.RSAPrivateKey;
2722
import java.security.interfaces.RSAPublicKey;
2823

@@ -32,9 +27,8 @@
3227
import org.springframework.core.convert.ConversionService;
3328
import org.springframework.core.convert.converter.Converter;
3429
import org.springframework.core.convert.converter.ConverterRegistry;
35-
import org.springframework.core.io.DefaultResourceLoader;
36-
import org.springframework.core.io.Resource;
3730
import org.springframework.core.io.ResourceLoader;
31+
import org.springframework.security.converter.ResourceKeyConverterAdapter;
3832
import org.springframework.security.converter.RsaKeyConverters;
3933
import org.springframework.util.Assert;
4034
import org.springframework.util.StringUtils;
@@ -50,30 +44,32 @@ public class RsaKeyConversionServicePostProcessor implements BeanFactoryPostProc
5044

5145
private static final String CONVERSION_SERVICE_BEAN_NAME = "conversionService";
5246

53-
private ResourceLoader resourceLoader = new DefaultResourceLoader();
47+
private ResourceKeyConverterAdapter<RSAPublicKey> x509 = new ResourceKeyConverterAdapter<>(RsaKeyConverters.x509());
48+
49+
private ResourceKeyConverterAdapter<RSAPrivateKey> pkcs8 = new ResourceKeyConverterAdapter<>(
50+
RsaKeyConverters.pkcs8());
5451

5552
public void setResourceLoader(ResourceLoader resourceLoader) {
5653
Assert.notNull(resourceLoader, "resourceLoader cannot be null");
57-
this.resourceLoader = resourceLoader;
54+
this.x509.setResourceLoader(resourceLoader);
55+
this.pkcs8.setResourceLoader(resourceLoader);
5856
}
5957

6058
@Override
6159
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
6260
if (hasUserDefinedConversionService(beanFactory)) {
6361
return;
6462
}
65-
Converter<String, RSAPrivateKey> pkcs8 = pkcs8();
66-
Converter<String, RSAPublicKey> x509 = x509();
6763
ConversionService service = beanFactory.getConversionService();
6864
if (service instanceof ConverterRegistry) {
6965
ConverterRegistry registry = (ConverterRegistry) service;
70-
registry.addConverter(String.class, RSAPrivateKey.class, pkcs8);
71-
registry.addConverter(String.class, RSAPublicKey.class, x509);
66+
registry.addConverter(String.class, RSAPrivateKey.class, this.pkcs8);
67+
registry.addConverter(String.class, RSAPublicKey.class, this.x509);
7268
}
7369
else {
7470
beanFactory.addPropertyEditorRegistrar((registry) -> {
75-
registry.registerCustomEditor(RSAPublicKey.class, new ConverterPropertyEditorAdapter<>(x509));
76-
registry.registerCustomEditor(RSAPrivateKey.class, new ConverterPropertyEditorAdapter<>(pkcs8));
71+
registry.registerCustomEditor(RSAPublicKey.class, new ConverterPropertyEditorAdapter<>(this.x509));
72+
registry.registerCustomEditor(RSAPrivateKey.class, new ConverterPropertyEditorAdapter<>(this.pkcs8));
7773
});
7874
}
7975
}
@@ -83,54 +79,6 @@ private boolean hasUserDefinedConversionService(ConfigurableListableBeanFactory
8379
&& beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
8480
}
8581

86-
private Converter<String, RSAPrivateKey> pkcs8() {
87-
Converter<String, InputStream> pemInputStreamConverter = pemInputStreamConverter();
88-
Converter<InputStream, RSAPrivateKey> pkcs8KeyConverter = autoclose(RsaKeyConverters.pkcs8());
89-
return pair(pemInputStreamConverter, pkcs8KeyConverter);
90-
}
91-
92-
private Converter<String, RSAPublicKey> x509() {
93-
Converter<String, InputStream> pemInputStreamConverter = pemInputStreamConverter();
94-
Converter<InputStream, RSAPublicKey> x509KeyConverter = autoclose(RsaKeyConverters.x509());
95-
return pair(pemInputStreamConverter, x509KeyConverter);
96-
}
97-
98-
private Converter<String, InputStream> pemInputStreamConverter() {
99-
return (source) -> source.startsWith("-----") ? toInputStream(source)
100-
: toInputStream(this.resourceLoader.getResource(source));
101-
}
102-
103-
private InputStream toInputStream(String raw) {
104-
return new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8));
105-
}
106-
107-
private InputStream toInputStream(Resource resource) {
108-
try {
109-
return resource.getInputStream();
110-
}
111-
catch (IOException ex) {
112-
throw new UncheckedIOException(ex);
113-
}
114-
}
115-
116-
private <T> Converter<InputStream, T> autoclose(Converter<InputStream, T> inputStreamKeyConverter) {
117-
return (inputStream) -> {
118-
try (InputStream is = inputStream) {
119-
return inputStreamKeyConverter.convert(is);
120-
}
121-
catch (IOException ex) {
122-
throw new UncheckedIOException(ex);
123-
}
124-
};
125-
}
126-
127-
private <S, T, I> Converter<S, T> pair(Converter<S, I> one, Converter<I, T> two) {
128-
return (source) -> {
129-
I intermediary = one.convert(source);
130-
return two.convert(intermediary);
131-
};
132-
}
133-
13482
private static class ConverterPropertyEditorAdapter<T> extends PropertyEditorSupport {
13583

13684
private final Converter<String, T> converter;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.converter;
18+
19+
import java.io.ByteArrayInputStream;
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.io.UncheckedIOException;
23+
import java.nio.charset.StandardCharsets;
24+
import java.security.Key;
25+
26+
import org.springframework.core.convert.converter.Converter;
27+
import org.springframework.core.io.DefaultResourceLoader;
28+
import org.springframework.core.io.Resource;
29+
import org.springframework.core.io.ResourceLoader;
30+
import org.springframework.util.Assert;
31+
32+
/**
33+
* Adapts any {@link Key} {@link Converter} into once that will first extract that key
34+
* from a resource.
35+
*
36+
* By default, keys can be read from the file system, the classpath, and from HTTP
37+
* endpoints. This can be customized by providing a {@link ResourceLoader}
38+
*
39+
* @author Josh Cummings
40+
* @since 5.5
41+
*/
42+
public class ResourceKeyConverterAdapter<T extends Key> implements Converter<String, T> {
43+
44+
private ResourceLoader resourceLoader = new DefaultResourceLoader();
45+
46+
private final Converter<String, T> delegate;
47+
48+
/**
49+
* Construct a {@link ResourceKeyConverterAdapter} with the provided parameters
50+
* @param delegate converts a stream of key material into a {@link Key}
51+
*/
52+
public ResourceKeyConverterAdapter(Converter<InputStream, T> delegate) {
53+
this.delegate = pemInputStreamConverter().andThen(autoclose(delegate));
54+
}
55+
56+
/**
57+
* {@inheritDoc}
58+
*/
59+
@Override
60+
public T convert(String source) {
61+
return this.delegate.convert(source);
62+
}
63+
64+
/**
65+
* Use this {@link ResourceLoader} to read the key material
66+
* @param resourceLoader the {@link ResourceLoader} to use
67+
*/
68+
public void setResourceLoader(ResourceLoader resourceLoader) {
69+
Assert.notNull(resourceLoader, "resourceLoader cannot be null");
70+
this.resourceLoader = resourceLoader;
71+
}
72+
73+
private Converter<String, InputStream> pemInputStreamConverter() {
74+
return (source) -> source.startsWith("-----") ? toInputStream(source)
75+
: toInputStream(this.resourceLoader.getResource(source));
76+
}
77+
78+
private InputStream toInputStream(String raw) {
79+
return new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8));
80+
}
81+
82+
private InputStream toInputStream(Resource resource) {
83+
try {
84+
return resource.getInputStream();
85+
}
86+
catch (IOException ex) {
87+
throw new UncheckedIOException(ex);
88+
}
89+
}
90+
91+
private <T> Converter<InputStream, T> autoclose(Converter<InputStream, T> inputStreamKeyConverter) {
92+
return (inputStream) -> {
93+
try (InputStream is = inputStream) {
94+
return inputStreamKeyConverter.convert(is);
95+
}
96+
catch (IOException ex) {
97+
throw new UncheckedIOException(ex);
98+
}
99+
};
100+
}
101+
102+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.converter;
18+
19+
import java.security.interfaces.RSAPrivateKey;
20+
21+
import org.junit.Before;
22+
import org.junit.Test;
23+
24+
import org.springframework.core.io.DefaultResourceLoader;
25+
import org.springframework.core.io.Resource;
26+
import org.springframework.core.io.ResourceLoader;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
/**
31+
* Tests for {@link ResourceKeyConverterAdapter}
32+
*/
33+
public class ResourceKeyConverterAdapterTests {
34+
35+
// @formatter:off
36+
private static final String PKCS8_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n"
37+
+ "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCMk7CKSTfu3QoV\n"
38+
+ "HoPVXxwZO+qweztd36cVWYqGOZinrOR2crWFu50AgR2CsdIH0+cqo7F4Vx7/3O8i\n"
39+
+ "RpYYZPe2VoO5sumzJt8P6fS80/TAKjhJDAqgZKRJTgGN8KxCM6p/aJli1ZeDBqiV\n"
40+
+ "v7vJJe+ZgJuPGRS+HMNa/wPxEkqqXsglcJcQV1ZEtfKXSHB7jizKpRL38185SyAC\n"
41+
+ "pwyjvBu6Cmm1URfhQo88mf239ONh4dZ2HoDfzN1q6Ssu4F4hgutxr9B0DVLDP5u+\n"
42+
+ "WFrm3nsJ76zf99uJ+ntMUHJ+bY+gOjSlVWIVBIZeAaEGKCNWRk/knjvjbijpvm3U\n"
43+
+ "acGlgdL3AgMBAAECggEACxxxS7zVyu91qI2s5eSKmAQAXMqgup6+2hUluc47nqUv\n"
44+
+ "uZz/c/6MPkn2Ryo+65d4IgqmMFjSfm68B/2ER5FTcvoLl1Xo2twrrVpUmcg3BClS\n"
45+
+ "IZPuExdhVNnxjYKEWwcyZrehyAoR261fDdcFxLRW588efIUC+rPTTRHzAc7sT+Ln\n"
46+
+ "t/uFeYNWJm3LaegOLoOmlMAhJ5puAWSN1F0FxtRf/RVgzbLA9QC975SKHJsfWCSr\n"
47+
+ "IZyPsdeaqomKaF65l8nfqlE0Ua2L35gIOGKjUwb7uUE8nI362RWMtYdoi3zDDyoY\n"
48+
+ "hSFbgjylCHDM0u6iSh6KfqOHtkYyJ8tUYgVWl787wQKBgQDYO3wL7xuDdD101Lyl\n"
49+
+ "AnaDdFB9fxp83FG1cWr+t7LYm9YxGfEUsKHAJXN6TIayDkOOoVwIl+Gz0T3Z06Bm\n"
50+
+ "eBGLrB9mrVA7+C7NJwu5gTMlzP6HxUR9zKJIQ/VB1NUGM77LSmvOFbHc9Q0+z8EH\n"
51+
+ "X5WO516a3Z7lNtZJcCoPOtu2rwKBgQCmbj41Fh+SSEUApCEKms5ETRpe7LXQlJgx\n"
52+
+ "yW7zcJNNuIb1C3vBLPxjiOTMgYKOeMg5rtHTGLT43URHLh9ArjawasjSAr4AM3J4\n"
53+
+ "xpoi/sKGDdiKOsuDWIGfzdYL8qyTHSdpZLQsCTMRiRYgAHZFPgNa7SLZRfZicGlr\n"
54+
+ "GHN1rJW6OQKBgEjiM/upyrJSWeypUDSmUeAZMpA6aWkwsfHgmtnkfUn5rQa74cDB\n"
55+
+ "kKO9e+D7LmOR3z+SL/1NhGwh2SE07dncGr3jdGodfO/ZxZyszozmeaECKcEFwwJM\n"
56+
+ "GV8WWPKplGwUwPiwywmZ0mvRxXcoe73KgBS88+xrSwWjqDL0tZiQlEJNAoGATkei\n"
57+
+ "GMQMG3jEg9Wu+NbxV6zQT3+U0MNjhl9RQU1c63x0dcNt9OFc4NAdlZcAulRTENaK\n"
58+
+ "OHjxffBM0hH+fySx8m53gFfr2BpaqDX5f6ZGBlly1SlsWZ4CchCVsc71nshipi7I\n"
59+
+ "k8HL9F5/OpQdDNprJ5RMBNfkWE65Nrcsb1e6oPkCgYAxwgdiSOtNg8PjDVDmAhwT\n"
60+
+ "Mxj0Dtwi2fAqQ76RVrrXpNp3uCOIAu4CfruIb5llcJ3uak0ZbnWri32AxSgk80y3\n"
61+
+ "EWiRX/WEDu5znejF+5O3pI02atWWcnxifEKGGlxwkcMbQdA67MlrJLFaSnnGpNXo\n"
62+
+ "yPfcul058SOqhafIZQMEKQ==\n"
63+
+ "-----END PRIVATE KEY-----";
64+
// @formatter:on
65+
66+
private static final String PKCS8_PRIVATE_KEY_LOCATION = "classpath:org/springframework/security/converter/simple.priv";
67+
68+
private ResourceKeyConverterAdapter<RSAPrivateKey> adapter;
69+
70+
@Before
71+
public void setup() {
72+
this.adapter = new ResourceKeyConverterAdapter<>(RsaKeyConverters.pkcs8());
73+
}
74+
75+
@Test
76+
public void convertWhenUsingAdapterForRawKeyThenOk() {
77+
RSAPrivateKey key = this.adapter.convert(PKCS8_PRIVATE_KEY);
78+
assertThat(key.getModulus().bitLength()).isEqualTo(2048);
79+
}
80+
81+
@Test
82+
public void convertWhenReferringToClasspathPublicKeyThenConverts() {
83+
RSAPrivateKey key = this.adapter.convert(PKCS8_PRIVATE_KEY_LOCATION);
84+
assertThat(key.getModulus().bitLength()).isEqualTo(2048);
85+
}
86+
87+
@Test
88+
public void convertWhenReferringToClasspathPrivateKeyThenConverts() {
89+
this.adapter.setResourceLoader(new CustomResourceLoader());
90+
RSAPrivateKey key = this.adapter.convert("custom:simple.priv");
91+
assertThat(key.getModulus().bitLength()).isEqualTo(2048);
92+
}
93+
94+
private static class CustomResourceLoader implements ResourceLoader {
95+
96+
private final ResourceLoader delegate = new DefaultResourceLoader();
97+
98+
@Override
99+
public Resource getResource(String location) {
100+
if (location.startsWith("classpath:")) {
101+
return this.delegate.getResource(location);
102+
}
103+
else if (location.startsWith("custom:")) {
104+
String[] parts = location.split(":");
105+
return this.delegate.getResource("classpath:org/springframework/security/converter/" + parts[1]);
106+
}
107+
throw new IllegalArgumentException("unsupported resource");
108+
}
109+
110+
@Override
111+
public ClassLoader getClassLoader() {
112+
return this.delegate.getClassLoader();
113+
}
114+
115+
}
116+
117+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCMk7CKSTfu3QoV
3+
HoPVXxwZO+qweztd36cVWYqGOZinrOR2crWFu50AgR2CsdIH0+cqo7F4Vx7/3O8i
4+
RpYYZPe2VoO5sumzJt8P6fS80/TAKjhJDAqgZKRJTgGN8KxCM6p/aJli1ZeDBqiV
5+
v7vJJe+ZgJuPGRS+HMNa/wPxEkqqXsglcJcQV1ZEtfKXSHB7jizKpRL38185SyAC
6+
pwyjvBu6Cmm1URfhQo88mf239ONh4dZ2HoDfzN1q6Ssu4F4hgutxr9B0DVLDP5u+
7+
WFrm3nsJ76zf99uJ+ntMUHJ+bY+gOjSlVWIVBIZeAaEGKCNWRk/knjvjbijpvm3U
8+
acGlgdL3AgMBAAECggEACxxxS7zVyu91qI2s5eSKmAQAXMqgup6+2hUluc47nqUv
9+
uZz/c/6MPkn2Ryo+65d4IgqmMFjSfm68B/2ER5FTcvoLl1Xo2twrrVpUmcg3BClS
10+
IZPuExdhVNnxjYKEWwcyZrehyAoR261fDdcFxLRW588efIUC+rPTTRHzAc7sT+Ln
11+
t/uFeYNWJm3LaegOLoOmlMAhJ5puAWSN1F0FxtRf/RVgzbLA9QC975SKHJsfWCSr
12+
IZyPsdeaqomKaF65l8nfqlE0Ua2L35gIOGKjUwb7uUE8nI362RWMtYdoi3zDDyoY
13+
hSFbgjylCHDM0u6iSh6KfqOHtkYyJ8tUYgVWl787wQKBgQDYO3wL7xuDdD101Lyl
14+
AnaDdFB9fxp83FG1cWr+t7LYm9YxGfEUsKHAJXN6TIayDkOOoVwIl+Gz0T3Z06Bm
15+
eBGLrB9mrVA7+C7NJwu5gTMlzP6HxUR9zKJIQ/VB1NUGM77LSmvOFbHc9Q0+z8EH
16+
X5WO516a3Z7lNtZJcCoPOtu2rwKBgQCmbj41Fh+SSEUApCEKms5ETRpe7LXQlJgx
17+
yW7zcJNNuIb1C3vBLPxjiOTMgYKOeMg5rtHTGLT43URHLh9ArjawasjSAr4AM3J4
18+
xpoi/sKGDdiKOsuDWIGfzdYL8qyTHSdpZLQsCTMRiRYgAHZFPgNa7SLZRfZicGlr
19+
GHN1rJW6OQKBgEjiM/upyrJSWeypUDSmUeAZMpA6aWkwsfHgmtnkfUn5rQa74cDB
20+
kKO9e+D7LmOR3z+SL/1NhGwh2SE07dncGr3jdGodfO/ZxZyszozmeaECKcEFwwJM
21+
GV8WWPKplGwUwPiwywmZ0mvRxXcoe73KgBS88+xrSwWjqDL0tZiQlEJNAoGATkei
22+
GMQMG3jEg9Wu+NbxV6zQT3+U0MNjhl9RQU1c63x0dcNt9OFc4NAdlZcAulRTENaK
23+
OHjxffBM0hH+fySx8m53gFfr2BpaqDX5f6ZGBlly1SlsWZ4CchCVsc71nshipi7I
24+
k8HL9F5/OpQdDNprJ5RMBNfkWE65Nrcsb1e6oPkCgYAxwgdiSOtNg8PjDVDmAhwT
25+
Mxj0Dtwi2fAqQ76RVrrXpNp3uCOIAu4CfruIb5llcJ3uak0ZbnWri32AxSgk80y3
26+
EWiRX/WEDu5znejF+5O3pI02atWWcnxifEKGGlxwkcMbQdA67MlrJLFaSnnGpNXo
27+
yPfcul058SOqhafIZQMEKQ==
28+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)