拒绝基于 RSA public 密钥大小的双向 TLS gRPC 连接
Rejecting mutual TLS gRPC connection based on RSA public key size
我有一个 gRPC 服务器,它使用双向 TLS 进行加密和身份验证。因此,连接到此服务器的每个客户端都提供了一个 SSL 证书,我想 拒绝来自 public 密钥大小小于 2048 位的客户端的连接 。似乎还没有直接的方法来做到这一点。
我可以用 ServerInterceptor
这样
public class SSLInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
try {
SSLSession sslSession = call.getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION);
RSAPublicKeyImpl pk = (RSAPublicKeyImpl) sslSession.getPeerCertificates()[0].getPublicKey();
if (pk.getModulus().bitLength() < 2048) {
// reject call
}
// proceed with the call
} catch (SSLPeerUnverifiedException e) {
// do something
}
...
}
}
这样做很糟糕,因为
- 验证在连接已经完成后进行
成立。
- 只有在 request/call 时才会触发验证。
- 每次调用都涉及额外的验证开销。
- 在验证失败的情况下,只有呼叫被拒绝,而不是与客户端的连接。
理想情况下
- 验证是在连接建立阶段完成的。 (或在客户端和服务器之间创建通道期间的某个时间点)
- 验证失败将阻止连接的创建和设置,稍后会断开连接。
- 客户端在每个会话中仅验证一次,并且在该会话期间进行的所有调用都不会产生任何开销。
我怎样才能做得更好?
您可以通过向 Netty 的 SslContextBuilder
提供您自己的 javax.net.ssl.TrustManagerFactory
来自定义证书检查。您可能想要实施 X509ExtendedTrustManager
,进行检查,然后委托给 "real" 实施以进行其余的证书链检查。
您可以像这样获取默认配置 TrustManagerFactory
:
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
// loop through tmf.getTrustManagers() checking for one implementing X509TrustManager
如果 gRPC 允许您访问 SSLEngine,以下内容可能会有所帮助:
我使用 SSLEngineSimpleDemo(它是 Oracle jssesamples.zip 的一部分)制作了一个模型,以在创建 SSLEngine 的地方设置自定义 AlgorithmConstraints:
private void createSSLEngines() throws Exception {
...
serverEngine = sslc.createSSLEngine();
...
// Set custom AlgorithmConstraints on the SSL engine
SSLParameters sslParams = sslc.getSupportedSSLParameters();
sslParams.setAlgorithmConstraints( new MyAlgorithmConstraints() );
serverEngine.setSSLParameters(sslParams);
class MyAlgorithmConstraints 类似于:
import java.security.AlgorithmConstraints;
import java.security.AlgorithmParameters;
import java.security.CryptoPrimitive;
import java.security.Key;
import java.security.interfaces.RSAKey;
import java.util.Set;
public class MyAlgorithmConstraints implements AlgorithmConstraints {
@Override
public boolean permits(Set<CryptoPrimitive> primitives, String algorithm, AlgorithmParameters parameters) {
return true;
}
@Override
public boolean permits(Set<CryptoPrimitive> primitives, Key key) {
boolean permitted = permittedRSAKey(primitives, key);
return permitted;
}
@Override
public boolean permits(Set<CryptoPrimitive> primitives, String algorithm, Key key, AlgorithmParameters parameters) {
boolean permitted = permittedRSAKey(primitives, key);
return permitted;
}
private boolean permittedRSAKey(Set<CryptoPrimitive> primitives, Key key) {
boolean permitted = true;
if (primitives.contains(CryptoPrimitive.KEY_AGREEMENT) && key instanceof RSAKey) {
int length = ((RSAKey)key).getModulus().bitLength();
if (length < 2040) {
permitted = false;
System.out.println("+*+*+* MyConstraints: short RSA key not allowed ["+length+"]");
}
}
return permitted;
}
}
根据 How to get the size of a RSA key in Java 中的警告,在密钥前导零的情况下选择长度 2040,但您也可以将条件反转为 length > 1024 permitted=true
。
我有一个 gRPC 服务器,它使用双向 TLS 进行加密和身份验证。因此,连接到此服务器的每个客户端都提供了一个 SSL 证书,我想 拒绝来自 public 密钥大小小于 2048 位的客户端的连接 。似乎还没有直接的方法来做到这一点。
我可以用 ServerInterceptor
这样
public class SSLInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
try {
SSLSession sslSession = call.getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION);
RSAPublicKeyImpl pk = (RSAPublicKeyImpl) sslSession.getPeerCertificates()[0].getPublicKey();
if (pk.getModulus().bitLength() < 2048) {
// reject call
}
// proceed with the call
} catch (SSLPeerUnverifiedException e) {
// do something
}
...
}
}
这样做很糟糕,因为
- 验证在连接已经完成后进行 成立。
- 只有在 request/call 时才会触发验证。
- 每次调用都涉及额外的验证开销。
- 在验证失败的情况下,只有呼叫被拒绝,而不是与客户端的连接。
理想情况下
- 验证是在连接建立阶段完成的。 (或在客户端和服务器之间创建通道期间的某个时间点)
- 验证失败将阻止连接的创建和设置,稍后会断开连接。
- 客户端在每个会话中仅验证一次,并且在该会话期间进行的所有调用都不会产生任何开销。
我怎样才能做得更好?
您可以通过向 Netty 的 SslContextBuilder
提供您自己的 javax.net.ssl.TrustManagerFactory
来自定义证书检查。您可能想要实施 X509ExtendedTrustManager
,进行检查,然后委托给 "real" 实施以进行其余的证书链检查。
您可以像这样获取默认配置 TrustManagerFactory
:
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
// loop through tmf.getTrustManagers() checking for one implementing X509TrustManager
如果 gRPC 允许您访问 SSLEngine,以下内容可能会有所帮助:
我使用 SSLEngineSimpleDemo(它是 Oracle jssesamples.zip 的一部分)制作了一个模型,以在创建 SSLEngine 的地方设置自定义 AlgorithmConstraints:
private void createSSLEngines() throws Exception {
...
serverEngine = sslc.createSSLEngine();
...
// Set custom AlgorithmConstraints on the SSL engine
SSLParameters sslParams = sslc.getSupportedSSLParameters();
sslParams.setAlgorithmConstraints( new MyAlgorithmConstraints() );
serverEngine.setSSLParameters(sslParams);
class MyAlgorithmConstraints 类似于:
import java.security.AlgorithmConstraints;
import java.security.AlgorithmParameters;
import java.security.CryptoPrimitive;
import java.security.Key;
import java.security.interfaces.RSAKey;
import java.util.Set;
public class MyAlgorithmConstraints implements AlgorithmConstraints {
@Override
public boolean permits(Set<CryptoPrimitive> primitives, String algorithm, AlgorithmParameters parameters) {
return true;
}
@Override
public boolean permits(Set<CryptoPrimitive> primitives, Key key) {
boolean permitted = permittedRSAKey(primitives, key);
return permitted;
}
@Override
public boolean permits(Set<CryptoPrimitive> primitives, String algorithm, Key key, AlgorithmParameters parameters) {
boolean permitted = permittedRSAKey(primitives, key);
return permitted;
}
private boolean permittedRSAKey(Set<CryptoPrimitive> primitives, Key key) {
boolean permitted = true;
if (primitives.contains(CryptoPrimitive.KEY_AGREEMENT) && key instanceof RSAKey) {
int length = ((RSAKey)key).getModulus().bitLength();
if (length < 2040) {
permitted = false;
System.out.println("+*+*+* MyConstraints: short RSA key not allowed ["+length+"]");
}
}
return permitted;
}
}
根据 How to get the size of a RSA key in Java 中的警告,在密钥前导零的情况下选择长度 2040,但您也可以将条件反转为 length > 1024 permitted=true
。