Chiudere correttamente SSLSocket

Voglio implementare un proxy SSL in Java. Fondamentalmente apro due socket browser-proxy , proxy-server , ed eseguo due thread che scriverebbero a proxy-server ciò che leggono da browser-proxy e viceversa. Ogni thread è simile a questo:

 while (true) { nr = in.read(buffer); if (nr == -1) System.out.println(sockin.toString()+" EOF "+nr); if (nr == -1) break; out.write(buffer, 0, nr); } sockin.shutdownInput(); sockout.shutdownOutput(); // now the second thread will receive -1 on read 

Ogni thread chiuderà solo la presa di input, in modo che alla fine entrambe le prese siano chiuse.

Ma cosa devo fare se voglio usare un SSLSocket ? Sembra che i metodi shutdownOutput/Input non siano supportati lì. Ecco l’eccezione che ottengo.

 Exception in thread "Thread-35" java.lang.UnsupportedOperationException: \ The method shutdownInput() is not supported in SSLSocket at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.shutdownInput(Unknown Source) 

Quello che mi è venuto in mente è:

 try { while (true) { nr = in.read(buffer); if (nr == -1) System.out.println(sockin.toString()+" EOF "+nr); if (nr == -1) break; out.write(buffer, 0, nr); } // possible race condition if by some mysterious way both threads would get EOF if (!sockin.isClosed()) sockin.close(); if (!sockout.isClosed()) sockout.close(); } catch (SocketException e) {/*ignore expected "socket closed" exception, */} 

Devo intercettare e ignorare l’eccezione di end of socket ogni volta che il mio socket termina.

Le mie domande sono:

  1. Se shutdownInput() non è supportato, come mai ottengo una risposta -1 da un SSLSocket .
  2. C’è una soluzione migliore per la mia agonia? Non penso che ci sia nulla di sano, dal momento che uno dei thread potrebbe essere già nel metodo di read bloccante quando l’altro thread lo segnala “I’m done”. E l’unico modo che vedo per cacciarlo dal thread di blocco è chiudendo il socket e sollevando un’eccezione “socket chiuso”.

    (Potresti indovinare usare una empio combinazione di messaggi multithreading e lettura asincrona dei dati per segnalare all’altro thread il tuo lavoro è terminato, ma è così orribile che temo che lo stile poliziotto di IntelliJ verrà dopo di me solo per pensare a it …)

Chiarimento: so che shutdownInput e shutdownOutput non hanno senso, dal momento che non è ansible avere una connessione TLS half-duplex, per specifica. Ma dato che, come posso terminare una connessione TLS in Java senza ottenere un’eccezione?

Per favore, non dirmi che shutdownIput non ha senso per TLS. Lo so, questo non è quello che sto chiedendo . Sto chiedendo cos’altro posso usare, per chiudere SSLSocket correttamente (da qui il titolo, “Chiudere correttamente SSLSocket”.

Questa era la mia risposta iniziale (temporaneamente cancellata), ma apparentemente non era abbastanza buona, dato che era -2’ed abbastanza rapidamente … Credo che dovrei aggiungere ulteriori spiegazioni.

Non è ansible chiudere la metà della connessione per i socket SSL / TLS per rispettare il protocollo TLS, come specificato nella sezione sugli avvisi di chiusura .

Il client e il server devono condividere la conoscenza che la connessione sta terminando al fine di evitare un attacco di troncamento. Ciascuna delle parti può avviare lo scambio di messaggi di chiusura.

close_notify Questo messaggio notifica al destinatario che il mittente non invierà altri messaggi su questa connessione. La sessione diventa non processabile se qualsiasi connessione viene interrotta senza i messaggi close_notify corretti con un livello uguale all’avviso.

Ciascuna parte può avviare una chiusura inviando un avviso close_notify. Qualsiasi dato ricevuto dopo un avviso di chiusura viene ignorato.

Ogni parte deve inviare un avviso close_notify prima di chiudere il lato di scrittura della connessione. È necessario che l’altra parte risponda con un avviso close_notify e chiudere immediatamente la connessione, ignorando eventuali scritture in sospeso. Non è necessario che l’iniziatore della chiusura attenda l’avviso close_notify che risponde prima di chiudere il lato di lettura della connessione.

Sebbene si desideri chiudere solo metà della connessione (input o output tramite shutdownInput() / shutdownOutput() ), il layer TLS sottostante deve ancora inviare questo pacchetto close_notify prima di arrestare realmente la comunicazione, altrimenti verrà considerato come un attacco di troncamento.

La soluzione è abbastanza semplice: usa solo close() su SSLSocket s: non chiude semplicemente la connessione come farebbe per i normali socket TCP, ma lo fa in modo pulito secondo le specifiche SSL / TLS.

EDIT : ulteriori spiegazioni .

La risposta breve è ancora: use close() , potrebbe anche essere necessario scoprire quando è opportuno farlo dal protocollo su SSL / TLS.

(Giusto per chiarire, quello che stai cercando di fare è effettivamente un proxy “Man-In-The-Middle” (MITM). Avresti bisogno che il client sia configurato per fidarti del certificato del proxy, possibilmente come se fosse il server certificato se questo è inteso per accadere in modo trasparente.come le connessioni HTTPS funzionano attraverso un proxy HTTP è una domanda diversa ).

In alcuni dei tuoi commenti alla tua altra domanda correlata , ho avuto l’impressione che tu pensassi che non restituire -1 fosse “rompere il protocollo TLS”. Questo non riguarda il protocollo, ma l’API: il modo in cui le strutture di programmazione (classi, metodi, funzioni, …) vengono fornite all’utente (programmatore) dello stack TLS per poter utilizzare TLS. SSLSocket fa semplicemente parte di un’API per fornire ai programmatori la possibilità di utilizzare SSL / TLS in modo simile a Socket normale. La specifica TLS non parla affatto di socket. Mentre TLS (e il suo precessore, SSL) sono stati creati con lo scopo di rendere ansible questo tipo di astrazione, alla fine della giornata SSLSocket è proprio questo: un’astrazione. In linea con i principi di progettazione OOP, SSLSocket eredita Socket e tenta di attaccare il più vicino ansible al comportamento di Socket . Tuttavia, a causa del modo in cui funziona SSL / TLS (e poiché un SSLSocket trova effettivamente in cima a un normale Socket ), non può esserci una mapping esatta di tutte le funzionalità.

In particolare, l’area di transizione da un normale socket TCP a un socket SSL / TLS non può essere modellata in modo trasparente. Alcune scelte arbitrarie (es. Come avviene la stretta di mano) dovevano essere fatte durante la progettazione della class SSLSocket : è un compromesso tra non (a) esporre troppa specificità di TLS all’utente SSLSocket , (b) fare funzionare il meccanismo TLS e (c) mappare tutto questo all’interfaccia esistente fornita dalla superclass ( Socket ). Queste scelte hanno inevitabilmente un impatto sulla funzionalità di SSLSocket , ed è per questo che la sua pagina API Javadoc ha una quantità relativamente grande di testo. SSLSocket.close() , in termini di API, deve rispettare la descrizione di Socket.close() . InputStream / OutputStream ottenuti da SSLSocket non sono uguali a quelli del Socket sottostante, che (a meno che non lo si sia convertito in modo esplicito ) potrebbe non essere visualizzato.

SSLSocket è progettato per essere utilizzabile il più strettamente ansible come se fosse un semplice Socket , e l’ InputStream si ottiene è progettato per comportarsi il più vicino ansible ad un stream di input basato su file. A proposito, questo non è specifico per Java. Anche i socket Unix in C sono usati con un descrittore di file e read() . Queste astrazioni sono convenienti e funzionano la maggior parte del tempo, purché tu sappia quali sono i loro limiti.

Quando si parla di read() , anche in C, la nozione di EOF deriva da una terminazione di fine file, ma in realtà non si tratta di file. Il problema con i socket TCP è che, mentre è ansible rilevare quando la parte remota ha scelto di chiudere la connessione quando invia FIN , non è ansible rilevare che non abbia, se non invia nulla. Quando non si legge nulla da un socket, non c’è modo di distinguere tra una connessione intriggers e una connessione interrotta. Pertanto, quando si parla di programmazione di rete, fare affidamento sulla lettura di -1 da InputStream se è soddisfacente, ma non si fa mai affidamento esclusivamente su questo, altrimenti si potrebbe finire con connessioni non rilasciate e non funzionanti. Mentre è “educato” per una festa chiudere correttamente la metà della connessione TCP (in un modo come il party remoto legge -1 sul suo InputStream o qualcosa di equivalente, come descritto in Orderly Versus Abortive Connection Release in Java ), gestire questo caso non è è sufficiente Questo è il motivo per cui i protocolli sulla parte superiore del TCP sono generalmente progettati per dare un’indicazione di quando hanno finito di inviare dati : ad esempio, SMTP invia QUIT (e ha terminatori specifici), HTTP 1.1 utilizza la codifica Content-Length o Chunked.

(A proposito, se non sei soddisfatto dell’astrazione fornita da SSLSocket , prova a utilizzare direttamente SSLEngine . È probabile che sia più difficile e non cambierà ciò che la specifica TLS dice sull’invio di close_notify .)

In generale con TCP, è necessario sapere, a livello di protocollo dell’applicazione, quando interrompere l’invio e la ricezione di dati (per evitare connessioni non rilasciate e / o affidarsi a errori di timeout). Questa frase nella specifica TLS rende questa pratica generalmente un requisito più forte quando si tratta di TLS: ” È necessario che l’altra parte risponda con un avviso close_notify e chiuda la connessione scartando immediatamente eventuali scritture in sospeso. ” devo sincronizzare in qualche modo, tramite il protocollo dell’applicazione, o la parte remota potrebbe avere ancora qualcosa da scrivere (specialmente se stai permettendo richieste / risposte in pipeline).

Se il proxy che stai scrivendo è per intercettare le connessioni HTTPS (come un MITM), puoi esaminare il contenuto delle richieste. Anche se le richieste HTTP sono pipeline, puoi contare il numero di risposte inviate dal server di destinazione (e aspettarti quando finiscono, tramite i delimitatori Content-Length o chunk).

Tuttavia, non sarà ansible avere un “intercettore” TLS generico e trasparente. TLS è progettato per garantire il trasporto tra due parti e non averne uno nel mezzo. Mentre la maggior parte del meccanismo per ottenere tale protezione (evitando un MITM) viene eseguita tramite il certificato del server, alcuni altri meccanismi TLS si intrometteranno (l’avviso di chiusura è uno di questi). Ricorda che stai effettivamente creando due connessioni TLS distinte, il fatto che siano difficili da unire come non dovrebbe essere sorprendente, considerando lo scopo di TLS.

SSLSocket.close() è il modo giusto per chiudere un SSLSocket , ma il protocollo dell’applicazione ti aiuterà anche a determinare quando è opportuno farlo.

La semantica SSL per le connessioni di chiusura è diversa da TCP, quindi shutdownInput e shutdownOutput non hanno realmente senso, almeno a livello di controllo disponibile in SSLSocket. SSLSocket fondamentalmente ti solleva dal carico dell’elaborazione dei record SSL e dell’elaborazione dei messaggi SSL handshake. Gestisce anche l’elaborazione degli avvisi SSL e uno di questi è l’avviso close_notify. Ecco il testo di RFC 2246 sul significato di questo avviso.

 "...Either party may initiate a close by sending a close_notify alert. Any data received after a closure alert is ignored. Each party is required to send a close_notify alert before closing the write side of the connection. It is required that the other party respond with a close_notify alert of its own and close down the connection immediately, discarding any pending writes. It is not required for the initiator of the close to wait for the responding close_notify alert before closing the read side of the connection...." 

Java fornisce ai suicidi un’altra API, l’API SSLEngine. Non toccarlo, ti farà male.

Per risolvere il tuo problema devi fare quanto segue:

 OutputStream sockOutOStream = sockout.getOutputStream(); sockOutOStream.write(new byte[0]); sockOutOStream.flush(); sockout.close(); 

Dall’altra parte la parte che legge dal socket riceverà il messaggio di lunghezza -1 invece di SocketException generata.