TooTallNate / Java-WebSocket

A barebones WebSocket client and server implementation written in 100% Java.
http://tootallnate.github.io/Java-WebSocket
MIT License
10.35k stars 2.56k forks source link

set up ssl cause throwing NoSuchAlgorithmException: KeyStore JKS implementation not found #1410

Closed sepideh69a closed 1 month ago

sepideh69a commented 1 month ago

my android app is going to be a server which allows a web to connect to it as a client . i have a SocketServerManger which handle the task properly when i connect to my android device via ws connection . but i want my clients can connect to the android device server with wss connection . here is my manager :

`class SocketServerManger(port: Int, val socketListener: List, context: Context) : WebSocketServer(InetSocketAddress(port)) {

var connection: WebSocket? = null

private val _isConnectionOpen = MutableStateFlow(false)
val isConnectionOpen: StateFlow<Boolean> = _isConnectionOpen

override fun onStart() {
     println("WebSocket server started")
}

override fun onOpen(conn: WebSocket?, handshake: ClientHandshake?) {

    connection = conn
    socketListener.forEach { it.onConnected() }
}

override fun onClose(conn: WebSocket?, code: Int, reason: String?, remote: Boolean) {
           socketListener.forEach { it.onDisconnected(code, reason) }
}

override fun onMessage(conn: WebSocket?, message: String?) {

    socketListener.forEach { it.onMessage(conn, message) }

}

override fun onError(conn: WebSocket?, ex: Exception?) {
     socketListener.forEach { it.onError(ex) }
    ex?.printStackTrace()
}

suspend fun sendMessagesUntilSuccess(timeoutMillis: Long = 50000, message: String): Boolean {
    var success = false
    while (!success) {
        serverLog(
            "while (!success)",
            "timeOutTag"
        )
        success = sendMessageWithTimeout(timeoutMillis, message)
        if (!success) {
                      // Add a delay before retrying to avoid continuous retries and potential rate limiting
            delay(1000)
        }
    }
    return success
}

 suspend fun sendMessageWithTimeout(timeoutMillis: Long = 5000, message: String): Boolean {
    return withContext(Dispatchers.IO) {

        return@withContext try {
            withTimeout(timeoutMillis) {
                val result = CoroutineScope(Dispatchers.IO).async {
                    try {
                        serverLog("sendMessageWithTimeout try : $message", "timeOutTag")
                        if (connection != null && connection!!.isOpen) {
                            connection!!.send(message)
                            true
                        } else {

                            false
                        }
                    } catch (e: org.java_websocket.exceptions.WebsocketNotConnectedException) {
                        socketListener.forEach { it.onException(e) }
                        false
                    }
                }.await()

                result
            }
        } catch (e: TimeoutCancellationException) {
            socketListener.forEach { it.onException(e) }
            false
        } catch (e: Exception) {
            socketListener.forEach { it.onException(e) }
            false
        }
    }
}

fun isConnectionOpen() = connection?.isOpen ?: false

fun isPortAvailable(port: Int): Boolean {
    return try {
        ServerSocket(port).close()
        true
    } catch (e: IOException) {
        false
    }
}

}`

i add the below code to set up ssl/tls:

` init { createSSLContext(context)?.let { setWebSocketFactory(DefaultSSLWebSocketServerFactory(it)) }

}

private fun createSSLContext(context: Context): SSLContext? {
    try {
        val keystoreFileName = "keystore.jks"
        val keystorePassword = "socket123"
        val keystoreInputStream: InputStream =  context.assets.open(keystoreFileName)

        val keyStore = KeyStore.getInstance("JKS")
        keyStore.load(keystoreInputStream, keystorePassword.toCharArray())
        keystoreInputStream.close()

        val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
        keyManagerFactory.init(keyStore, keystorePassword.toCharArray())

        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(keyManagerFactory.keyManagers, null, SecureRandom())
        return sslContext
    }catch (e : Exception){
        serverLog("createSSLContext ${e.message}")
    }
  return null
}

` but it keeps throwing the exception:

Caused by: java.security.KeyStoreException: java.security.NoSuchAlgorithmException: KeyStore JKS implementation not found

it throw it on this line :

val keyStore = KeyStore.getInstance("JKS")

marci4 commented 1 month ago

Android only supports BKS as far as I remember

sepideh69a commented 1 month ago

Android only supports BKS as far as I remember

i changed the keystore to bks and edit code as ` private fun createSSLContext(context: Context): SSLContext? { try { val keystoreFileName = "myketstore.bks" val keystorePassword = "mypassword" val keystoreInputStream: InputStream = context.assets.open(keystoreFileName)

        val keyStore = KeyStore.getInstance("BKS")
        keyStore.load(keystoreInputStream, keystorePassword.toCharArray())
        keystoreInputStream.close()

        val keyManagerFactory =
            KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
        keyManagerFactory.init(keyStore, keystorePassword.toCharArray())

        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(keyManagerFactory.keyManagers, null, SecureRandom())
        return sslContext
    } catch (e: Exception) {
        serverLog("createSSLContext ${e.message}")
    }
    return null
}`

now the exception i talked about in previous post is solved but i cannot connect to server yet . it fails . when i test it with postman it gives the error Client network socket disconnected before secure TLS connection was established

marci4 commented 1 month ago

See https://github.com/TooTallNate/Java-WebSocket/wiki/Getting-a-SSLContext-from-different-sources#getting-a-sslcontext-using-a-keystore-on-android for an android example.

Apart from this, I dont think we can help you much here. This question are something for stackoverflow.

sepideh69 commented 1 month ago

i figured it out . setting up ssl on android requirs some steps which should be followed correctly otherwise you encounter some issues.

1/ Install OpenSSL

2/Generate a Private Key

Use OpenSSL to generate a private key openssl genrsa -out key.pem 2048

3/ Create a Certificate Signing Request (CSR) Use the private key to create a CSR

openssl req -new -key key.pem -out csr.pem

4/ Generate a Self-Signed Certificate You can generate a self-signed certificate using the private key and CSR.

openssl x509 -req -days 365 -in csr.pem -signkey key.pem -out certificate.pem

5/ Convert the Certificate to the Appropriate Format

android supports bks . first you should convert the certificate to PKCS#12 format with the extension .p12 or .pfx then to bks .

openssl pkcs12 -export -out certificate.p12 -inkey key.pem -in certificate.pem -name "alias"

keytool -importkeystore -srckeystore certificate.p12 -srcstoretype PKCS12 -destkeystore certificate.bks -deststoretype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "D:\bcprov-debug-jdk18on-1.78.1.jar

6/ put the bks file format in your android project . i put it in my assets .

7/ set up ssl in your code :

` init {

    createSSLContext(context)?.let {

        setWebSocketFactory(DefaultSSLWebSocketServerFactory(it))
    }

}

private fun createSSLContext(context: Context): SSLContext? {

    val keystoreFileName = "certificate.bks"
    val keystorePassword = "yourpassword"
    val keystoreInputStream: InputStream = context.assets.open(keystoreFileName)

    try {

        val keystore = KeyStore.getInstance("BKS")

        keystore.load(keystoreInputStream, keystorePassword.toCharArray())

        val keyManagerFactory = KeyManagerFactory.getInstance("X509")
        keyManagerFactory.init(keystore, keystorePassword.toCharArray())
        val tmf : TrustManagerFactory = TrustManagerFactory.getInstance("X509");
        tmf.init(keystore)

        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(keyManagerFactory.keyManagers,tmf.trustManagers, null)
        return sslContext
    } catch (e: Exception) {

        keystoreInputStream.close()
    }
    return null
}`

following these steps eventually resulted in connecting with wss. hope help someone :)