
在开发应用程序的过程中,越来越多地出现授权和身份验证以及一般的信息安全方面的问题,每个人都有不同的狂热态度来解决这些问题。鉴于最近几年我的活动领域是金融部门的软件开发,特别是风险计算系统,我无法绕过这一事实,特别是考虑到相关的教育。因此,在本文的框架中,我决定涵盖这一主题,并告诉我在设置应用程序过程中必须面对的问题。
介绍
. , - - nginx apache, dmz. . , -. - , - , GUI . - , LDAP , .
RADIUS, Kerberos OAuth/OpenID . - , base64, - JsonWebToken, . , , , , , , .
SSL- — UAT . Java JKS, .
, — , . , , — .
, , , .
PKCS 12 , Java, JKS (Java KeyStore). JDK keytool.
, keystore, :
keytool -genkey -alias example.com -keyalg RSA -keystore keystore.jks -keysize 2048
, JKS Java
keytool- (CSR) Java keystore
keytool -certreq -alias example.com -keystore keystore.jks -file example.com.csr
- CA
keytool -import -trustcacerts -alias root -file Thawte.crt -keystore keystore.jks
keytool -import -trustcacerts -alias example.com -file example.com.crt -keystore keystore.jks
- keystore
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048
keytool -printcert -v -file example.com.crt
- keystore
keytool -list -v -keystore keystore.jks
- keystore
keytool -list -v -keystore keystore.jks -alias example.com
- keystore
keytool -delete -alias example.com -keystore keystore.jks
- keystore
keytool -storepasswd -new new_storepass -keystore keystore.jks
- keystore
keytool -export -alias example.com -file example.com.crt -keystore keystore.jks
keytool -list -v -keystore $JAVA_HOME/jre/lib/security/cacerts
- trustStore
keytool -import -trustcacerts -file /path/to/ca/ca.pem -alias CA_ALIAS -keystore $JAVA_HOME/jre/lib/security/cacerts
KeyStore & TrustStore
JKS, , KeyStore TrustStore. , JKS . (KeyStore) , . TrustStore , . , Linux /usr/local/share/ca-certificates/
Java cacerts, java.home\lib\security
changeit.
, JDK/JRE , prod/uat .
KeyStore & TrustStore.
SSL NettyServer
, (protobuf) (wss), SSL netty . , , .withSslContext(), .
NettyServer.builder()
.addHttpListener(
NetworkInterfaceBuilder.forPort(serviceUri.getPort())
.withSslContext(sslContextFactory.getSslContext()),
PathHandler.path()
.addExactPath(serviceUri.getPath(), createServiceHandler()
)
.build();
SSL- — SslContextBuilder — forServer forClient. , trustManager keyManager. — TrustManagerFactory KeyManagerFactory.
SslContextBuilder.forServer(getKeyManagerFactory())
.trustManager(getTrustManagerFactory())
.build();
, trustManager- jks ,
private TrustManagerFactory getTrustManagerFactory() throws Exception {
final TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final KeyStore keyStore = KeyStore.getInstance("JKS");
final InputStream trustStoreFile = getTrustStoreFile();
keyStore.load(trustStoreFile, trustStorePassword.toCharArray());
tmFactory.init(keyStore);
return tmFactory;
}
KeyStore .
private KeyManagerFactory getKeyManagerFactory() throws Exception {
final KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final KeyStore keyStore = KeyStore.getInstance("JKS");
final InputStream keyStoreFile = getKeyStoreFile();
keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
kmFactory.init(keyStore, keyPassword.toCharArray());
return kmFactory;
}
, SSL NettyServer.
SSL gRPC / RSocket
, , gRPC RSocket . Netty , . .
SSL Spring Boot RestController
Java Spring DI. , Spring Boot , . , , . RestController, , . , , Spring Boot c .
server.ssl.key-store-type=JKS
server.ssl.key-store=classpath:cert.jks
server.ssl.key-store-password=changeit
server.ssl.key-alias=key
trust.store=classpath:cert.jks
trust.store.password=changeit
. , , , WebServerFactoryCustomizer, Jetty, Tomcat Undertow.
, lambda c Connector. setSecure(true), ProtocolHandler-, jks c keystore trustStore . , tomcat :
@Bean
WebServerFactoryCustomizer<ConfigurableWebServerFactory> containerCustomizer() throws Exception {
TomcatConnectorCustomizer customizer;
String absoluteKeystoreFile = keystoreFile.getAbsolutePath();
String absoluteTruststoreFile = truststoreFile.getAbsolutePath();
boolean sslEnabled = checkSslSettings(absoluteKeystoreFile, absoluteTruststoreFile, keystorePass);
if (sslEnabled) {
customizer = (connector) -> {
connector.setPort(port);
connector.setSecure(true);
connector.setScheme("https");
Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler();
proto.setSSLEnabled(true);
proto.setClientAuth(clientAuth);
proto.setKeystoreFile(absoluteKeystoreFile);
proto.setKeystorePass(keystorePass);
proto.setTruststoreFile(absoluteTruststoreFile);
proto.setTruststorePass(truststorePass);
proto.setKeystoreType(keyStoreType);
proto.setKeyAlias(keyAlias);
proto.setCiphers(chiphers);
};
} else {
customizer = (connector) -> {
connector.setPort(port);
connector.setSecure(false);
if (sslEnabled) {
log.error("Key- or Trust-store file — {} or {} — is not found, or keystore password is missing, REST service on port {} is disabled", absoluteKeystoreFile, absoluteTruststoreFile, port);
((Http11NioProtocol) connector.getProtocolHandler()).setMaxConnections(0);
}
};
}
return (ConfigurableWebServerFactory factory) -> {
ConfigurableTomcatWebServerFactory tomcatWebServerFactory = (ConfigurableTomcatWebServerFactory) factory;
tomcatWebServerFactory.addConnectorCustomizers(customizer);
};
«» - , .
TLS/SSL
keystore , java.security.* , .
jks KeyStore KeyPair, X509Certificate, , .
bouncyastle, Java, Java Cryptography Architecture (JCA) Java Cryptography Extension (JCE).
kotlin Java github (https://github.com/bcgit/bc-java https://github.com/bcgit/bc-kotlin).
keyStore :
KeyStore generateKeyStore(String password) {
X509Certificate2 ca = X509Certificate2Builder()
.setSubject("CN=CA")
.build()
X509Certificate2 int = X509Certificate2Builder()
.setIssuer(ca)
.setSubject("CN=Intermediate")
.build()
X509Certificate2 cert = X509Certificate2Builder()
.setIssuer(int)
.setSubject("CN=Child")
.setIntermediate(false)
.build()
return KeyStoreBuilder()
.addTrustedCertificate("test-ca", ca)
.addPrivateKey("test-pk", cert.keyPair, password, asList(cert, int, ca))
.build()
}
, , CA, cert, , ( ), KeyPair keyPair X509Certificate2, X509Certificate.
, .
, — X509Certificate2Builder KeyStoreBuilder. java java.security.KeyStore.Builder, — newInstance, - . .
setEntry KeyStore , Entry (TrustedCertificateEntry PrivateKeyEntry).
KeyStore#setEntry setEntry(String alias, Entry entry, ProtectionParameter protParam) 3 , item KeyStoreBuilder#build() :
public KeyStore build() {
try {
KeyStore keyStore = KeyStore.getInstance("JKS", "SUN");
keyStore.load(null, null);
for (Item it : entries.values()) {
keyStore.setEntry(it.alias, it.entry, it.parameter);
}
return keyStore;
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e.getMessage(), e);
}
entries KeyStore#setEntry, , setEntry addPrivateKey
public KeyStoreBuilder addPrivateKey(String alias, KeyPair pair, String password, List<X509Certificate> chain) {
addEntry(alias,
new KeyStore.PrivateKeyEntry(pair.getPrivate(), chain),
new KeyStore.PasswordProtection(password.toCharArray()));
return this;
}
addTrustedCertificate,
public KeyStoreBuilder addTrustedCertificate(String alias, X509Certificate cert) {
addEntry(alias, new KeyStore.TrustedCertificateEntry(cert), null);
return this;
}
keyStore.
C X509 , build(). , , build() X509Certificate2, , , :
public X509Certificate2 build() {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
final X500Name subject = new X500Name(this.subject);
final KeyPair subjectKeyPair = newKeyPair(subjectKeyStrength);
final boolean selfSigned = this.issuer == null;
final X500Name issuer = selfSigned ? subject : new X500Name(this.issuer.getSubjectDN().getName());
final KeyPair issuerKeyPair = selfSigned ? subjectKeyPair : this.issuer.getKeyPair();
final Date notBefore = new Date();
final Date notAfter = new Date(notBefore.getTime() + 20L * 365 * 24 * 60 * 60 * 1000);
final BigInteger serialNumber = BigInteger.valueOf(SERIALS.incrementAndGet());
final SubjectPublicKeyInfo subjectKeyInfo = SubjectPublicKeyInfo.getInstance(subjectKeyPair.getPublic().getEncoded());
final X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
issuer, serialNumber, notBefore, notAfter, subject, subjectKeyInfo);
final AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
final AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
try {
final BcX509ExtensionUtils extensionUtils = new BcX509ExtensionUtils();
builder.addExtension(
new ASN1ObjectIdentifier("2.5.29.14"),
false,
extensionUtils.createSubjectKeyIdentifier(
SubjectPublicKeyInfo.getInstance(subjectKeyPair.getPublic().getEncoded()))
);
builder.addExtension(
new ASN1ObjectIdentifier("2.5.29.35"),
false,
extensionUtils.createAuthorityKeyIdentifier(
SubjectPublicKeyInfo.getInstance(issuerKeyPair.getPublic().getEncoded()))
);
builder.addExtension(
new ASN1ObjectIdentifier("2.5.29.19"),
false,
new BasicConstraints(intermediate));
AsymmetricKeyParameter privateKey = PrivateKeyFactory.createKey(issuerKeyPair.getPrivate().getEncoded());
ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
.build(privateKey);
X509Certificate cert = new JcaX509CertificateConverter()
.setProvider("BC")
.getCertificate(builder.build(signer));
return new X509Certificate2(cert, subjectKeyPair);
} catch (IOException | OperatorCreationException | CertificateException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private static KeyPair newKeyPair(int subjectKeyStrength) {
try {
if (subjectKeyStrength <= 0) {
subjectKeyStrength = DEFAULT_KEY_STRENGTH;
}
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGen.initialize(subjectKeyStrength, SecureRandom.getInstance("SHA1PRNG"));
return keyPairGen.generateKeyPair();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
bouncycastle.
bouncycastle, .
java.security.KeyPairGenerator, -. RSA c SHA1PRNG.
, , , .
, , . , .
, , .
, , .
, , Spring boot , , Spring Initializr
@SpringBootApplication
@RestController
public class Server {
static String certFile = System.getProperty("user.home") + "/cert.jks";
static String defaultPassword = "changeit";
@GetMapping("/hello")
public String hello() {
System.out.println("request /hello");
return "hello";
}
@Bean
WebServerFactoryCustomizer<ConfigurableWebServerFactory> containerCustomizer() {
TomcatConnectorCustomizer customizer = (connector) -> {
connector.setPort(8080);
connector.setSecure(true);
connector.setScheme("https");
Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler();
proto.setSSLEnabled(true);
proto.setClientAuth("true");
proto.setKeystoreFile(certFile);
proto.setKeystorePass(defaultPassword);
proto.setTruststoreFile(certFile);
proto.setTruststorePass(defaultPassword);
proto.setKeystoreType("JKS");
};
return (ConfigurableWebServerFactory factory) -> {
ConfigurableTomcatWebServerFactory tomcatWebServerFactory = (ConfigurableTomcatWebServerFactory) factory;
tomcatWebServerFactory.addConnectorCustomizers(customizer);
};
}
public static void main(String[] args) {
SpringApplication.run(Server.class, args);
}
}
class Client {
RestTemplate restTemplate() throws Exception {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(new URL(Server.certFile), Server.defaultPassword.toCharArray())
.build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
public static void main(String[] args) {
String response = new RestTemplate().getForObject("https://localhost:8080/hello", String.class);
System.out.println("received " + response);
}
}
cert.jks , , .