Skip to content

Commit 1c25fe2

Browse files
committed
Introduce Support for Reading RSA Keys
Fixes: gh-6494
1 parent 22c8f63 commit 1c25fe2

File tree

10 files changed

+683
-3
lines changed

10 files changed

+683
-3
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,12 +18,12 @@
1818
import java.util.Collections;
1919
import java.util.List;
2020
import java.util.Map;
21-
2221
import javax.servlet.Filter;
2322

2423
import org.springframework.beans.factory.BeanClassLoaderAware;
2524
import org.springframework.beans.factory.annotation.Autowired;
2625
import org.springframework.beans.factory.annotation.Value;
26+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
2727
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2828
import org.springframework.context.annotation.Bean;
2929
import org.springframework.context.annotation.Configuration;
@@ -40,6 +40,7 @@
4040
import org.springframework.security.config.annotation.SecurityConfigurer;
4141
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
4242
import org.springframework.security.config.annotation.web.builders.WebSecurity;
43+
import org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor;
4344
import org.springframework.security.context.DelegatingApplicationListener;
4445
import org.springframework.security.web.FilterChainProxy;
4546
import org.springframework.security.web.FilterInvocation;
@@ -159,6 +160,11 @@ public void setFilterChainProxySecurityConfigurer(
159160
this.webSecurityConfigurers = webSecurityConfigurers;
160161
}
161162

163+
@Bean
164+
public static BeanFactoryPostProcessor conversionServicePostProcessor() {
165+
return new RsaKeyConversionServicePostProcessor();
166+
}
167+
162168
@Bean
163169
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
164170
ConfigurableListableBeanFactory beanFactory) {

config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,10 +20,12 @@
2020
import java.util.List;
2121

2222
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
2324
import org.springframework.context.ApplicationContext;
2425
import org.springframework.context.annotation.Bean;
2526
import org.springframework.context.annotation.Configuration;
2627
import org.springframework.core.annotation.Order;
28+
import org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor;
2729
import org.springframework.security.config.web.server.ServerHttpSecurity;
2830
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
2931
import org.springframework.security.web.server.SecurityWebFilterChain;
@@ -66,6 +68,11 @@ public CsrfRequestDataValueProcessor requestDataValueProcessor() {
6668
return new CsrfRequestDataValueProcessor();
6769
}
6870

71+
@Bean
72+
public static BeanFactoryPostProcessor conversionServicePostProcessor() {
73+
return new RsaKeyConversionServicePostProcessor();
74+
}
75+
6976
private List<SecurityWebFilterChain> getSecurityWebFilterChains() {
7077
List<SecurityWebFilterChain> result = this.securityWebFilterChains;
7178
if (ObjectUtils.isEmpty(result)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright 2002-2019 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.config.crypto;
18+
19+
import java.beans.PropertyEditorSupport;
20+
import java.io.ByteArrayInputStream;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.io.UncheckedIOException;
24+
import java.nio.charset.StandardCharsets;
25+
import java.security.interfaces.RSAPrivateKey;
26+
import java.security.interfaces.RSAPublicKey;
27+
28+
import org.springframework.beans.BeansException;
29+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
30+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
31+
import org.springframework.core.convert.ConversionService;
32+
import org.springframework.core.convert.converter.Converter;
33+
import org.springframework.core.convert.converter.ConverterRegistry;
34+
import org.springframework.core.io.DefaultResourceLoader;
35+
import org.springframework.core.io.Resource;
36+
import org.springframework.core.io.ResourceLoader;
37+
import org.springframework.security.converter.RsaKeyConverters;
38+
import org.springframework.util.Assert;
39+
import org.springframework.util.StringUtils;
40+
41+
/**
42+
* Adds {@link RsaKeyConverters} to the configured {@link ConversionService} or {@link PropertyEditor}s
43+
*
44+
* @author Josh Cummings
45+
* @since 5.2
46+
*/
47+
public class RsaKeyConversionServicePostProcessor implements BeanFactoryPostProcessor {
48+
private static final String CONVERSION_SERVICE_BEAN_NAME = "conversionService";
49+
50+
private ResourceLoader resourceLoader = new DefaultResourceLoader();
51+
52+
public void setResourceLoader(ResourceLoader resourceLoader) {
53+
Assert.notNull(resourceLoader, "resourceLoader cannot be null");
54+
this.resourceLoader = resourceLoader;
55+
}
56+
57+
/**
58+
* {@inheritDoc}
59+
*/
60+
@Override
61+
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
62+
if (hasUserDefinedConversionService(beanFactory)) {
63+
return;
64+
}
65+
66+
Converter<String, RSAPrivateKey> pkcs8 = pkcs8();
67+
Converter<String, RSAPublicKey> x509 = x509();
68+
69+
ConversionService service = beanFactory.getConversionService();
70+
if (service instanceof ConverterRegistry) {
71+
ConverterRegistry registry = (ConverterRegistry) service;
72+
registry.addConverter(String.class, RSAPrivateKey.class, pkcs8);
73+
registry.addConverter(String.class, RSAPublicKey.class, x509);
74+
} else {
75+
beanFactory.addPropertyEditorRegistrar(registry -> {
76+
registry.registerCustomEditor(RSAPublicKey.class, new ConverterPropertyEditorAdapter<>(x509));
77+
registry.registerCustomEditor(RSAPrivateKey.class, new ConverterPropertyEditorAdapter<>(pkcs8));
78+
});
79+
}
80+
}
81+
82+
private boolean hasUserDefinedConversionService(ConfigurableListableBeanFactory beanFactory) {
83+
return beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
84+
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
85+
}
86+
87+
private Converter<String, RSAPrivateKey> pkcs8() {
88+
Converter<String, InputStream> pemInputStreamConverter = pemInputStreamConverter();
89+
Converter<InputStream, RSAPrivateKey> pkcs8KeyConverter = autoclose(RsaKeyConverters.pkcs8());
90+
return pair(pemInputStreamConverter, pkcs8KeyConverter);
91+
}
92+
93+
private Converter<String, RSAPublicKey> x509() {
94+
Converter<String, InputStream> pemInputStreamConverter = pemInputStreamConverter();
95+
Converter<InputStream, RSAPublicKey> x509KeyConverter = autoclose(RsaKeyConverters.x509());
96+
return pair(pemInputStreamConverter, x509KeyConverter);
97+
}
98+
99+
private Converter<String, InputStream> pemInputStreamConverter() {
100+
return source -> source.startsWith("-----") ?
101+
toInputStream(source) : toInputStream(this.resourceLoader.getResource(source));
102+
}
103+
104+
private InputStream toInputStream(String raw) {
105+
return new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8));
106+
}
107+
108+
private InputStream toInputStream(Resource resource) {
109+
try {
110+
return resource.getInputStream();
111+
} catch (IOException e) {
112+
throw new UncheckedIOException(e);
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+
} catch (IOException e) {
121+
throw new UncheckedIOException(e);
122+
}
123+
};
124+
}
125+
126+
private <S, T, I> Converter<S, T> pair(Converter<S, I> one, Converter<I, T> two) {
127+
return source -> {
128+
I intermediary = one.convert(source);
129+
return two.convert(intermediary);
130+
};
131+
}
132+
133+
private static class ConverterPropertyEditorAdapter<T> extends PropertyEditorSupport {
134+
private final Converter<String, T> converter;
135+
136+
public ConverterPropertyEditorAdapter(Converter<String, T> converter) {
137+
this.converter = converter;
138+
}
139+
140+
@Override
141+
public String getAsText() {
142+
return null;
143+
}
144+
145+
@Override
146+
public void setAsText(String text) throws IllegalArgumentException {
147+
if (StringUtils.hasText(text)) {
148+
setValue(this.converter.convert(text));
149+
} else {
150+
setValue(null);
151+
}
152+
}
153+
}
154+
}

0 commit comments

Comments
 (0)