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