Есть ли реализация DTLS в JSSE?
Я хочу реализовать клиент DTLS 1.0 в Java и, немного погуглив, обнаружил, что JSSERefGuide говорит следующее:
JSSE API поддерживает SSL версии 2.0 и 3.0 и TLS версии 1.0. Эти протоколы безопасности инкапсулируют обычный двунаправленный сокет потока, а JSSE API добавляет прозрачную поддержку аутентификации, шифрования и защиты целостности. Реализация JSSE, поставляемая с JDK, поддерживает SSL 3.0, TLS (1.0, 1.1 и 1.2) и DTLS (версии 1.0 и 1.2). Он не реализует SSL 2.0.
Поэтому я подумал, что смогу реализовать его на чистом Java без использования какой-либо библиотеки (например, BouncyCastle)
Но когда я пытаюсь запустить (и некоторые другие, такие как DTLSv1.2, DTLSv1...):
final SSLContext sslContext = SSLContext.getInstance("DTLSv1.0", "SunJSSE");
Это бросает:
Exception in thread "main" java.security.NoSuchAlgorithmException: no such algorithm: DTLSv1.0 for provider SunJSSE
at sun.security.jca.GetInstance.getService(GetInstance.java:87)
at sun.security.jca.GetInstance.getInstance(GetInstance.java:206)
at javax.net.ssl.SSLContext.getInstance(SSLContext.java:199)
в то время как, например, следующие работы:
final SSLContext sslContext = SSLContext.getInstance("TLSv1.2", "SunJSSE");
Перечисление всех провайдеров безопасности Я не нахожу ничего DTLS вообще.
Так есть ли на самом деле реализация DTLS? И если так, как вы должны его использовать?
5 ответов
Документ правильный, и вы получаете исключение, потому что нет протокола DTLS: https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html
Выбор DTLS наступает в момент создания сокета, так как это будет один из типов TCP или дейтаграмм. В начале это будет выглядеть так:
DatagramSocket s = new DatagramSocket();
...
final SSLContext sslContext = SSLContext.getInstance("TLSv1.0", "SunJSSE");
sslContext.init(null, yourSSLTrustManager, null);
SSLSocketFactory factory = (SSLSocketFactory)sslContext.getSocketFactory();
SSLSocket daSocket = (SSLSocket) factory.createSocket(s, host, port, false);
Вы можете использовать https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/test/jdk/javax/net/ssl/DTLS/DTLSOverDatagram.java (или https://github.com/twosigma/OpenJDK/blob/master/test/jdk/javax/net/ssl/DTLS/DTLSOverDatagram.java, то же самое)
Для человека, который убил мой предыдущий ответ из-за ссылок: даже если ссылка разорвется, это не проблема - потому что, взглянув на ссылку, вы легко увидите, что DTLSOverDatagram является частью официальных тестов open-jdk 11 - так что даже если ссылка исчезнет, вы легко сможете найти другие источники.
Хотя это тесты для реализации DTLS, с небольшим рефакторингом их можно использовать в качестве основы для дейтаграмм DTLS over (udp-). И для клиента, и для сервера - по сути, они почти одинаковы.
Другой вариант — использовать DTLS из собственной библиотеки, например mbedtls. Вот библиотека, которая предоставляет API Kotlin (совместимый с Java): https://github.com/open-coap/kotlin-mbedtls .
Основываясь на образце из JDK и множестве проб и ошибок, я написал следующий код, который работает в моем случае:
public static final int MAXIMUM_PACKET_SIZE = 1500;
private final DatagramSocket socket;
private final SSLEngine engine;
public DtlsSocket( @Nonnull DatagramSocket socket, KeyManager km, TrustManager tm, boolean isClient ) {
try {
this.socket = socket;
SSLContext sslCtx = SSLContext.getInstance( "DTLS" );
KeyManager[] kms = { km };
TrustManager[] tms = { tm };
sslCtx.init( kms, tms, null );
engine = sslCtx.createSSLEngine();
engine.setNeedClientAuth( true );
engine.setUseClientMode( isClient );
SSLParameters paras = engine.getSSLParameters();
paras.setMaximumPacketSize( MAXIMUM_PACKET_SIZE );
engine.setSSLParameters( paras );
engine.beginHandshake();
byte[] receiveBytes = new byte[MAXIMUM_PACKET_SIZE];
DatagramPacket receivePacket = new DatagramPacket( receiveBytes, 0, MAXIMUM_PACKET_SIZE );
ByteBuffer receiveBuffer = ByteBuffer.wrap( receiveBytes );
receiveBuffer.limit( 0 );
ByteBuffer emptyBuffer = ByteBuffer.allocate( 0 );
byte[] sendData = new byte[32768];
DatagramPacket sendPacket = new DatagramPacket( sendData, 0, 32768 );
ByteBuffer sendBuffer = ByteBuffer.wrap( sendData );
while( true ) {
SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
switch( hs ) {
case NEED_UNWRAP:
case NEED_UNWRAP_AGAIN:
if( !receiveBuffer.hasRemaining() && hs != SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN ) {
socket.receive( receivePacket );
receiveBuffer.position( 0 );
receiveBuffer.limit( receivePacket.getLength() );
}
checkStatus( engine.unwrap( receiveBuffer, emptyBuffer ) );
break;
case NEED_WRAP:
checkStatus( engine.wrap( emptyBuffer, sendBuffer.clear() ) );
sendPacket.setLength( sendBuffer.position() );
socket.send( sendPacket );
break;
case NEED_TASK:
Runnable runnable;
while( (runnable = engine.getDelegatedTask()) != null ) {
runnable.run();
}
break;
case NOT_HANDSHAKING:
return;
default:
// should never occur
throw new SSLException( "handshake error " + hs );
}
}
} catch( Exception ex ) {
throw ErrorCode.throwAny( ex );
}
}
private void checkStatus( SSLEngineResult engineResult ) throws IOException {
if( engineResult.getStatus() != SSLEngineResult.Status.OK ) {
throw new SSLException( "handshake error " + engineResult.getStatus() + " with handshake state " + engineResult.getHandshakeStatus() );
}
}