Prepare las llaves (en ambos lados):
[ -f ~ /.ssh/id_ed25519 ] && [ -f ~ /.ssh/id_ed25519.pub ] || ssh-keygen -t ed25519
scp ~ /.ssh/id_ed25519.pub remote:from_remote_side/
io.ReadWriteCloser
cifrado, fácil:
// Generate (if not exists) and read ED25519 keys
identity , err := secureio . NewIdentity ( `/home/user/.ssh` )
// Read remote identity
remoteIdentity , err := secureio . NewRemoteIdentityFromPublicKey ( `/home/user/from_remote_side/id_ed25519.pub` )
// Create a connection
conn , err := net . Dial ( "udp" , "10.0.0.2:1234" )
// Create an encrypted connection (and exchange keys using ECDH and verify remote side by ED25519 signature).
session := identity . NewSession ( remoteIdentity , conn , nil , nil )
session . Start ( context . Background ())
// Use it!
// Write to it
_ , err = session . Write ( someData )
// Or/and read from it
_ , err = session . Read ( someData )
Configure el receptor:
session . SetHandlerFuncs ( secureio . MessageTypeChannel ( 0 ), func ( payload [] byte ) {
fmt . Println ( "I received a payload:" , payload )
}, func ( err error ) {
panic ( err )
})
Enviar un mensaje sincrónicamente:
_ , err := session . WriteMessage ( secureio . MessageTypeChannel ( 0 ), payload )
O
Enviar un mensaje de forma asíncrona:
// Schedule the sending of the payload
sendInfo := session . WriteMessageAsync ( secureio . MessageTypeChannel ( 0 ), payload )
[.. your another stuff here if you want ..]
// Wait until the real sending
<- sendInfo . Done ()
// Here you get the error if any:
err := sendInfo. Err
// It's not necessary, but helps to reduce the pressure on GC (so to optimize CPU and RAM utilization)
sendInfo . Release ()
Se puede crear un MessageType para un canal personalizado mediante la función MessageTypeChannel(channelID uint32)
. channelID
es un número personalizado para identificar qué flujo es (para conectar un remitente con el receptor apropiado en el lado remoto). En los ejemplos anteriores se usó 0
como valor channelID
, pero podría ser cualquier valor en el rango: 0 <= x <= 2**31
.
También hay un MessageType especial MessageTypeReadWrite
que se utiliza para Read()
/ Write()
predeterminado. Pero puedes redirigir este flujo a un controlador personalizado.
SessionOptions.MaxPayloadSize
.SessionOptions.SendDelay
en &[]time.Duration{0}[0]
. La prueba se realizó con comunicación a través de un socket UNIX.
BenchmarkSessionWriteRead1-8 10000 118153 ns/op 0.01 MB/s 468 B/op 8 allocs/op
BenchmarkSessionWriteRead16-8 10000 118019 ns/op 0.14 MB/s 455 B/op 8 allocs/op
BenchmarkSessionWriteRead1024-8 9710 119238 ns/op 8.59 MB/s 441 B/op 8 allocs/op
BenchmarkSessionWriteRead32000-8 6980 173441 ns/op 184.50 MB/s 488 B/op 9 allocs/op
BenchmarkSessionWriteRead64000-8 3994 310038 ns/op 206.43 MB/s 629 B/op 9 allocs/op
BenchmarkSessionWriteMessageAsyncRead1-8 2285032 539 ns/op 1.86 MB/s 0 B/op 0 allocs/op
BenchmarkSessionWriteMessageAsyncRead16-8 2109264 572 ns/op 27.99 MB/s 2 B/op 0 allocs/op
BenchmarkSessionWriteMessageAsyncRead1024-8 480385 2404 ns/op 425.87 MB/s 15 B/op 0 allocs/op
BenchmarkSessionWriteMessageAsyncRead32000-8 30163 39131 ns/op 817.76 MB/s 162 B/op 5 allocs/op
BenchmarkSessionWriteMessageAsyncRead64000-8 15435 77898 ns/op 821.59 MB/s 317 B/op 10 allocs/op
Este paquete está diseñado para ser asincrónico, por lo que básicamente Write
es un contenedor estúpido alrededor del código de WriteMessageAsync
. Para obtener un mayor rendimiento, fusiona todos los mensajes recopilados en 50 microsegundos en uno, los envía y luego los vuelve a dividir. Permite reducir la cantidad de llamadas al sistema y otros gastos generales. Entonces, para lograr 1,86 MiB/s en mensajes de 1 byte, debe enviar muchos de ellos de forma asincrónica (entre sí), de modo que se fusionen mientras se envían/reciben a través de la conexión backend.
Además, estos 800MiB/s tienen más que ver con el caso del host local. Y un caso de red más realista (si tenemos MTU ~= 1400) es:
BenchmarkSessionWriteMessageAsyncRead1300_max1400-8 117862 10277 ns/op 126.49 MB/s 267 B/op 10 allocs/op
El lado remoto se autentica mediante una firma ED25519 (del mensaje de intercambio de claves).
El intercambio de claves se realiza a través de ECDH con X25519. Si se configura un PSK, entonces se concatena PSK con un valor de sal constante, se aplica hash con blake3.Sum256
y sha3.Sum256
y se usa para XOR la clave (intercambiada).
El valor resultante se utiliza como clave de cifrado para XChaCha20. Esta clave se llama cipherKey
dentro del código.
La clave (recibida a través de ECDH) se actualiza cada minuto. Entonces, a su vez, la cipherKey
también se actualiza cada minuto.
Un SessionID
se intercambia en los primeros mensajes de intercambio de claves. SessionID
es una combinación de UnixNano (de cuando se inicializó la sesión) y un entero aleatorio de 64 bits.
Además, cada paquete comienza con un PacketID
de texto sin formato único (para una sesión) (en realidad, si se configura PSK, entonces PacketID
se cifra con una clave derivada como un hash de un PSK salado).
La combinación de PacketID
y SessionID
se usa como IV/NONCE para XChaCha20 y cipherKey
se usa como clave.
Vale la pena mencionar que PacketID
pretende ser único sólo dentro de una sesión. Entonces comienza con cero y luego aumenta en 1 para cada mensaje siguiente. Por lo tanto, si el reloj del sistema falla, la unicidad de NONCE está garantizada por el valor aleatorio de 64 bits de SessionID
.
La autenticación de mensajes se realiza mediante Poly1305. Como clave para Poly1305 se utilizó un hash blake3.Sum256 de:
PacketID
y cipherKey
XOR-ed por un valor constante. Se supone que PacketID
solo aumenta. Los ID de paquete recibidos se recuerdan en una ventana limitada de valores. Si se recibió un paquete con el mismo PacketID
(como ya era) o con un PackerID
menor (que el valor mínimo posible en la ventana), entonces el paquete simplemente se ignora.
Una sesión exitosa con la configuración predeterminada pasa por estados/fases/etapas:
Esta es la etapa donde se analizan todas las opciones y se inicializan todas las rutinas requeridas.
Después de eso, *Session
cambiará al estado "Intercambio de claves".
En esta etapa, ambas partes (la local y la remota) están intercambiando claves públicas ECDH para obtener una clave compartida simétrica (ver "Diseño de seguridad").
Además, si se establece una identidad remota, verificamos si coincide e ignoramos cualquier mensaje de intercambio de claves con cualquier otra clave pública ED25519 (no la confunda con la clave pública ECDH): los pares de claves ED25519 son estáticos para cada parte (y generalmente son predeterminados). definido), mientras que los pares de claves ECDH se generan para cada intercambio de claves.
Además, cada clave de intercambio de claves se verifica mediante la clave pública (pasada con el mensaje).
Con la configuración predeterminada, cada parte también envía mensajes de confirmación para verificar si los mensajes fueron recibidos y percibidos. Y (con la configuración predeterminada) cada parte espera un mensaje de confirmación del lado remoto (consulte también SessionOptions.AnswersMode
).
Si todo es exitoso aquí entonces la *Session
pasa a la fase de "Negociación". Pero el proceso de intercambio de claves todavía se realiza periódicamente en segundo plano.
En esta etapa intentamos determinar qué tamaño de paquetes puede manejar el io.Writer
subyacente. Así que intentamos enviar paquetes de 3 tamaños diferentes y ver cuál de ellos podrá hacer un viaje de ida y vuelta. Luego repita el procedimiento en un intervalo más corto. Y así hasta 4 veces.
Este comportamiento se puede habilitar o deshabilitar mediante SessionOptions.NegotiatorOptions.Enable
.
Cuando finalice este procedimiento (o si está deshabilitado), la *Session
cambia al estado "Establecido".
Este es el estado en el que la *Session
funciona normalmente, por lo que puedes enviar y recibir mensajes a través de ella. Y los mensajes que se intentaron enviar antes de llegar a esta etapa se enviarán tan pronto como *Session
llegue a esta etapa.
Closing
es un estado de transición antes de convertirse Closed
. Si se alcanza el estado Closed
, significa que la sesión está inactiva y nunca más le sucederá nada.
Async
para escrituras sincronizadas.notewakeup
en lugar de Cond.Wait