package ssh
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/subtle"
"encoding/binary"
"errors"
"fmt"
"io"
"math/big"
"golang.org/x/crypto/curve25519"
)
const (
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
kexAlgoDH14SHA256 = "diffie-hellman-group14-sha256"
kexAlgoDH16SHA512 = "diffie-hellman-group16-sha512"
kexAlgoECDH256 = "ecdh-sha2-nistp256"
kexAlgoECDH384 = "ecdh-sha2-nistp384"
kexAlgoECDH521 = "ecdh-sha2-nistp521"
kexAlgoCurve25519SHA256LibSSH = "curve25519-sha256@libssh.org"
kexAlgoCurve25519SHA256 = "curve25519-sha256"
kexAlgoDHGEXSHA1 = "diffie-hellman-group-exchange-sha1"
kexAlgoDHGEXSHA256 = "diffie-hellman-group-exchange-sha256"
)
type kexResult struct {
H []byte
K []byte
HostKey []byte
Signature []byte
Hash crypto .Hash
SessionID []byte
}
type handshakeMagics struct {
clientVersion, serverVersion []byte
clientKexInit, serverKexInit []byte
}
func (m *handshakeMagics ) write (w io .Writer ) {
writeString (w , m .clientVersion )
writeString (w , m .serverVersion )
writeString (w , m .clientKexInit )
writeString (w , m .serverKexInit )
}
type kexAlgorithm interface {
Server(p packetConn , rand io .Reader , magics *handshakeMagics , s AlgorithmSigner , algo string ) (*kexResult , error )
Client(p packetConn , rand io .Reader , magics *handshakeMagics ) (*kexResult , error )
}
type dhGroup struct {
g, p, pMinus1 *big .Int
hashFunc crypto .Hash
}
func (group *dhGroup ) diffieHellman (theirPublic , myPrivate *big .Int ) (*big .Int , error ) {
if theirPublic .Cmp (bigOne ) <= 0 || theirPublic .Cmp (group .pMinus1 ) >= 0 {
return nil , errors .New ("ssh: DH parameter out of bounds" )
}
return new (big .Int ).Exp (theirPublic , myPrivate , group .p ), nil
}
func (group *dhGroup ) Client (c packetConn , randSource io .Reader , magics *handshakeMagics ) (*kexResult , error ) {
var x *big .Int
for {
var err error
if x , err = rand .Int (randSource , group .pMinus1 ); err != nil {
return nil , err
}
if x .Sign () > 0 {
break
}
}
X := new (big .Int ).Exp (group .g , x , group .p )
kexDHInit := kexDHInitMsg {
X : X ,
}
if err := c .writePacket (Marshal (&kexDHInit )); err != nil {
return nil , err
}
packet , err := c .readPacket ()
if err != nil {
return nil , err
}
var kexDHReply kexDHReplyMsg
if err = Unmarshal (packet , &kexDHReply ); err != nil {
return nil , err
}
ki , err := group .diffieHellman (kexDHReply .Y , x )
if err != nil {
return nil , err
}
h := group .hashFunc .New ()
magics .write (h )
writeString (h , kexDHReply .HostKey )
writeInt (h , X )
writeInt (h , kexDHReply .Y )
K := make ([]byte , intLength (ki ))
marshalInt (K , ki )
h .Write (K )
return &kexResult {
H : h .Sum (nil ),
K : K ,
HostKey : kexDHReply .HostKey ,
Signature : kexDHReply .Signature ,
Hash : group .hashFunc ,
}, nil
}
func (group *dhGroup ) Server (c packetConn , randSource io .Reader , magics *handshakeMagics , priv AlgorithmSigner , algo string ) (result *kexResult , err error ) {
packet , err := c .readPacket ()
if err != nil {
return
}
var kexDHInit kexDHInitMsg
if err = Unmarshal (packet , &kexDHInit ); err != nil {
return
}
var y *big .Int
for {
if y , err = rand .Int (randSource , group .pMinus1 ); err != nil {
return
}
if y .Sign () > 0 {
break
}
}
Y := new (big .Int ).Exp (group .g , y , group .p )
ki , err := group .diffieHellman (kexDHInit .X , y )
if err != nil {
return nil , err
}
hostKeyBytes := priv .PublicKey ().Marshal ()
h := group .hashFunc .New ()
magics .write (h )
writeString (h , hostKeyBytes )
writeInt (h , kexDHInit .X )
writeInt (h , Y )
K := make ([]byte , intLength (ki ))
marshalInt (K , ki )
h .Write (K )
H := h .Sum (nil )
sig , err := signAndMarshal (priv , randSource , H , algo )
if err != nil {
return nil , err
}
kexDHReply := kexDHReplyMsg {
HostKey : hostKeyBytes ,
Y : Y ,
Signature : sig ,
}
packet = Marshal (&kexDHReply )
err = c .writePacket (packet )
return &kexResult {
H : H ,
K : K ,
HostKey : hostKeyBytes ,
Signature : sig ,
Hash : group .hashFunc ,
}, err
}
type ecdh struct {
curve elliptic .Curve
}
func (kex *ecdh ) Client (c packetConn , rand io .Reader , magics *handshakeMagics ) (*kexResult , error ) {
ephKey , err := ecdsa .GenerateKey (kex .curve , rand )
if err != nil {
return nil , err
}
kexInit := kexECDHInitMsg {
ClientPubKey : elliptic .Marshal (kex .curve , ephKey .PublicKey .X , ephKey .PublicKey .Y ),
}
serialized := Marshal (&kexInit )
if err := c .writePacket (serialized ); err != nil {
return nil , err
}
packet , err := c .readPacket ()
if err != nil {
return nil , err
}
var reply kexECDHReplyMsg
if err = Unmarshal (packet , &reply ); err != nil {
return nil , err
}
x , y , err := unmarshalECKey (kex .curve , reply .EphemeralPubKey )
if err != nil {
return nil , err
}
secret , _ := kex .curve .ScalarMult (x , y , ephKey .D .Bytes ())
h := ecHash (kex .curve ).New ()
magics .write (h )
writeString (h , reply .HostKey )
writeString (h , kexInit .ClientPubKey )
writeString (h , reply .EphemeralPubKey )
K := make ([]byte , intLength (secret ))
marshalInt (K , secret )
h .Write (K )
return &kexResult {
H : h .Sum (nil ),
K : K ,
HostKey : reply .HostKey ,
Signature : reply .Signature ,
Hash : ecHash (kex .curve ),
}, nil
}
func unmarshalECKey(curve elliptic .Curve , pubkey []byte ) (x , y *big .Int , err error ) {
x , y = elliptic .Unmarshal (curve , pubkey )
if x == nil {
return nil , nil , errors .New ("ssh: elliptic.Unmarshal failure" )
}
if !validateECPublicKey (curve , x , y ) {
return nil , nil , errors .New ("ssh: public key not on curve" )
}
return x , y , nil
}
func validateECPublicKey(curve elliptic .Curve , x , y *big .Int ) bool {
if x .Sign () == 0 && y .Sign () == 0 {
return false
}
if x .Cmp (curve .Params ().P ) >= 0 {
return false
}
if y .Cmp (curve .Params ().P ) >= 0 {
return false
}
if !curve .IsOnCurve (x , y ) {
return false
}
return true
}
func (kex *ecdh ) Server (c packetConn , rand io .Reader , magics *handshakeMagics , priv AlgorithmSigner , algo string ) (result *kexResult , err error ) {
packet , err := c .readPacket ()
if err != nil {
return nil , err
}
var kexECDHInit kexECDHInitMsg
if err = Unmarshal (packet , &kexECDHInit ); err != nil {
return nil , err
}
clientX , clientY , err := unmarshalECKey (kex .curve , kexECDHInit .ClientPubKey )
if err != nil {
return nil , err
}
ephKey , err := ecdsa .GenerateKey (kex .curve , rand )
if err != nil {
return nil , err
}
hostKeyBytes := priv .PublicKey ().Marshal ()
serializedEphKey := elliptic .Marshal (kex .curve , ephKey .PublicKey .X , ephKey .PublicKey .Y )
secret , _ := kex .curve .ScalarMult (clientX , clientY , ephKey .D .Bytes ())
h := ecHash (kex .curve ).New ()
magics .write (h )
writeString (h , hostKeyBytes )
writeString (h , kexECDHInit .ClientPubKey )
writeString (h , serializedEphKey )
K := make ([]byte , intLength (secret ))
marshalInt (K , secret )
h .Write (K )
H := h .Sum (nil )
sig , err := signAndMarshal (priv , rand , H , algo )
if err != nil {
return nil , err
}
reply := kexECDHReplyMsg {
EphemeralPubKey : serializedEphKey ,
HostKey : hostKeyBytes ,
Signature : sig ,
}
serialized := Marshal (&reply )
if err := c .writePacket (serialized ); err != nil {
return nil , err
}
return &kexResult {
H : H ,
K : K ,
HostKey : reply .HostKey ,
Signature : sig ,
Hash : ecHash (kex .curve ),
}, nil
}
func ecHash(curve elliptic .Curve ) crypto .Hash {
bitSize := curve .Params ().BitSize
switch {
case bitSize <= 256 :
return crypto .SHA256
case bitSize <= 384 :
return crypto .SHA384
}
return crypto .SHA512
}
var kexAlgoMap = map [string ]kexAlgorithm {}
func init() {
p , _ := new (big .Int ).SetString ("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF" , 16 )
kexAlgoMap [kexAlgoDH1SHA1 ] = &dhGroup {
g : new (big .Int ).SetInt64 (2 ),
p : p ,
pMinus1 : new (big .Int ).Sub (p , bigOne ),
hashFunc : crypto .SHA1 ,
}
p , _ = new (big .Int ).SetString ("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF" , 16 )
group14 := &dhGroup {
g : new (big .Int ).SetInt64 (2 ),
p : p ,
pMinus1 : new (big .Int ).Sub (p , bigOne ),
}
kexAlgoMap [kexAlgoDH14SHA1 ] = &dhGroup {
g : group14 .g , p : group14 .p , pMinus1 : group14 .pMinus1 ,
hashFunc : crypto .SHA1 ,
}
kexAlgoMap [kexAlgoDH14SHA256 ] = &dhGroup {
g : group14 .g , p : group14 .p , pMinus1 : group14 .pMinus1 ,
hashFunc : crypto .SHA256 ,
}
p , _ = new (big .Int ).SetString ("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF" , 16 )
kexAlgoMap [kexAlgoDH16SHA512 ] = &dhGroup {
g : new (big .Int ).SetInt64 (2 ),
p : p ,
pMinus1 : new (big .Int ).Sub (p , bigOne ),
hashFunc : crypto .SHA512 ,
}
kexAlgoMap [kexAlgoECDH521 ] = &ecdh {elliptic .P521 ()}
kexAlgoMap [kexAlgoECDH384 ] = &ecdh {elliptic .P384 ()}
kexAlgoMap [kexAlgoECDH256 ] = &ecdh {elliptic .P256 ()}
kexAlgoMap [kexAlgoCurve25519SHA256 ] = &curve25519sha256 {}
kexAlgoMap [kexAlgoCurve25519SHA256LibSSH ] = &curve25519sha256 {}
kexAlgoMap [kexAlgoDHGEXSHA1 ] = &dhGEXSHA {hashFunc : crypto .SHA1 }
kexAlgoMap [kexAlgoDHGEXSHA256 ] = &dhGEXSHA {hashFunc : crypto .SHA256 }
}
type curve25519sha256 struct {}
type curve25519KeyPair struct {
priv [32 ]byte
pub [32 ]byte
}
func (kp *curve25519KeyPair ) generate (rand io .Reader ) error {
if _ , err := io .ReadFull (rand , kp .priv [:]); err != nil {
return err
}
curve25519 .ScalarBaseMult (&kp .pub , &kp .priv )
return nil
}
var curve25519Zeros [32 ]byte
func (kex *curve25519sha256 ) Client (c packetConn , rand io .Reader , magics *handshakeMagics ) (*kexResult , error ) {
var kp curve25519KeyPair
if err := kp .generate (rand ); err != nil {
return nil , err
}
if err := c .writePacket (Marshal (&kexECDHInitMsg {kp .pub [:]})); err != nil {
return nil , err
}
packet , err := c .readPacket ()
if err != nil {
return nil , err
}
var reply kexECDHReplyMsg
if err = Unmarshal (packet , &reply ); err != nil {
return nil , err
}
if len (reply .EphemeralPubKey ) != 32 {
return nil , errors .New ("ssh: peer's curve25519 public value has wrong length" )
}
var servPub , secret [32 ]byte
copy (servPub [:], reply .EphemeralPubKey )
curve25519 .ScalarMult (&secret , &kp .priv , &servPub )
if subtle .ConstantTimeCompare (secret [:], curve25519Zeros [:]) == 1 {
return nil , errors .New ("ssh: peer's curve25519 public value has wrong order" )
}
h := crypto .SHA256 .New ()
magics .write (h )
writeString (h , reply .HostKey )
writeString (h , kp .pub [:])
writeString (h , reply .EphemeralPubKey )
ki := new (big .Int ).SetBytes (secret [:])
K := make ([]byte , intLength (ki ))
marshalInt (K , ki )
h .Write (K )
return &kexResult {
H : h .Sum (nil ),
K : K ,
HostKey : reply .HostKey ,
Signature : reply .Signature ,
Hash : crypto .SHA256 ,
}, nil
}
func (kex *curve25519sha256 ) Server (c packetConn , rand io .Reader , magics *handshakeMagics , priv AlgorithmSigner , algo string ) (result *kexResult , err error ) {
packet , err := c .readPacket ()
if err != nil {
return
}
var kexInit kexECDHInitMsg
if err = Unmarshal (packet , &kexInit ); err != nil {
return
}
if len (kexInit .ClientPubKey ) != 32 {
return nil , errors .New ("ssh: peer's curve25519 public value has wrong length" )
}
var kp curve25519KeyPair
if err := kp .generate (rand ); err != nil {
return nil , err
}
var clientPub , secret [32 ]byte
copy (clientPub [:], kexInit .ClientPubKey )
curve25519 .ScalarMult (&secret , &kp .priv , &clientPub )
if subtle .ConstantTimeCompare (secret [:], curve25519Zeros [:]) == 1 {
return nil , errors .New ("ssh: peer's curve25519 public value has wrong order" )
}
hostKeyBytes := priv .PublicKey ().Marshal ()
h := crypto .SHA256 .New ()
magics .write (h )
writeString (h , hostKeyBytes )
writeString (h , kexInit .ClientPubKey )
writeString (h , kp .pub [:])
ki := new (big .Int ).SetBytes (secret [:])
K := make ([]byte , intLength (ki ))
marshalInt (K , ki )
h .Write (K )
H := h .Sum (nil )
sig , err := signAndMarshal (priv , rand , H , algo )
if err != nil {
return nil , err
}
reply := kexECDHReplyMsg {
EphemeralPubKey : kp .pub [:],
HostKey : hostKeyBytes ,
Signature : sig ,
}
if err := c .writePacket (Marshal (&reply )); err != nil {
return nil , err
}
return &kexResult {
H : H ,
K : K ,
HostKey : hostKeyBytes ,
Signature : sig ,
Hash : crypto .SHA256 ,
}, nil
}
type dhGEXSHA struct {
hashFunc crypto .Hash
}
const (
dhGroupExchangeMinimumBits = 2048
dhGroupExchangePreferredBits = 2048
dhGroupExchangeMaximumBits = 8192
)
func (gex *dhGEXSHA ) Client (c packetConn , randSource io .Reader , magics *handshakeMagics ) (*kexResult , error ) {
kexDHGexRequest := kexDHGexRequestMsg {
MinBits : dhGroupExchangeMinimumBits ,
PreferedBits : dhGroupExchangePreferredBits ,
MaxBits : dhGroupExchangeMaximumBits ,
}
if err := c .writePacket (Marshal (&kexDHGexRequest )); err != nil {
return nil , err
}
packet , err := c .readPacket ()
if err != nil {
return nil , err
}
var msg kexDHGexGroupMsg
if err = Unmarshal (packet , &msg ); err != nil {
return nil , err
}
if msg .P .BitLen () < dhGroupExchangeMinimumBits || msg .P .BitLen () > dhGroupExchangeMaximumBits {
return nil , fmt .Errorf ("ssh: server-generated gex p is out of range (%d bits)" , msg .P .BitLen ())
}
pMinusOne := new (big .Int ).Sub (msg .P , bigOne )
if msg .G .Cmp (bigOne ) <= 0 || msg .G .Cmp (pMinusOne ) >= 0 {
return nil , fmt .Errorf ("ssh: server provided gex g is not safe" )
}
pHalf := new (big .Int ).Rsh (msg .P , 1 )
x , err := rand .Int (randSource , pHalf )
if err != nil {
return nil , err
}
X := new (big .Int ).Exp (msg .G , x , msg .P )
kexDHGexInit := kexDHGexInitMsg {
X : X ,
}
if err := c .writePacket (Marshal (&kexDHGexInit )); err != nil {
return nil , err
}
packet , err = c .readPacket ()
if err != nil {
return nil , err
}
var kexDHGexReply kexDHGexReplyMsg
if err = Unmarshal (packet , &kexDHGexReply ); err != nil {
return nil , err
}
if kexDHGexReply .Y .Cmp (bigOne ) <= 0 || kexDHGexReply .Y .Cmp (pMinusOne ) >= 0 {
return nil , errors .New ("ssh: DH parameter out of bounds" )
}
kInt := new (big .Int ).Exp (kexDHGexReply .Y , x , msg .P )
if kInt .Cmp (bigOne ) <= 0 || kInt .Cmp (pMinusOne ) >= 0 {
return nil , fmt .Errorf ("ssh: derived k is not safe" )
}
h := gex .hashFunc .New ()
magics .write (h )
writeString (h , kexDHGexReply .HostKey )
binary .Write (h , binary .BigEndian , uint32 (dhGroupExchangeMinimumBits ))
binary .Write (h , binary .BigEndian , uint32 (dhGroupExchangePreferredBits ))
binary .Write (h , binary .BigEndian , uint32 (dhGroupExchangeMaximumBits ))
writeInt (h , msg .P )
writeInt (h , msg .G )
writeInt (h , X )
writeInt (h , kexDHGexReply .Y )
K := make ([]byte , intLength (kInt ))
marshalInt (K , kInt )
h .Write (K )
return &kexResult {
H : h .Sum (nil ),
K : K ,
HostKey : kexDHGexReply .HostKey ,
Signature : kexDHGexReply .Signature ,
Hash : gex .hashFunc ,
}, nil
}
func (gex dhGEXSHA ) Server (c packetConn , randSource io .Reader , magics *handshakeMagics , priv AlgorithmSigner , algo string ) (result *kexResult , err error ) {
packet , err := c .readPacket ()
if err != nil {
return
}
var kexDHGexRequest kexDHGexRequestMsg
if err = Unmarshal (packet , &kexDHGexRequest ); err != nil {
return
}
p , _ := new (big .Int ).SetString ("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF" , 16 )
g := big .NewInt (2 )
msg := &kexDHGexGroupMsg {
P : p ,
G : g ,
}
if err := c .writePacket (Marshal (msg )); err != nil {
return nil , err
}
packet , err = c .readPacket ()
if err != nil {
return
}
var kexDHGexInit kexDHGexInitMsg
if err = Unmarshal (packet , &kexDHGexInit ); err != nil {
return
}
pHalf := new (big .Int ).Rsh (p , 1 )
y , err := rand .Int (randSource , pHalf )
if err != nil {
return
}
Y := new (big .Int ).Exp (g , y , p )
pMinusOne := new (big .Int ).Sub (p , bigOne )
if kexDHGexInit .X .Cmp (bigOne ) <= 0 || kexDHGexInit .X .Cmp (pMinusOne ) >= 0 {
return nil , errors .New ("ssh: DH parameter out of bounds" )
}
kInt := new (big .Int ).Exp (kexDHGexInit .X , y , p )
hostKeyBytes := priv .PublicKey ().Marshal ()
h := gex .hashFunc .New ()
magics .write (h )
writeString (h , hostKeyBytes )
binary .Write (h , binary .BigEndian , uint32 (dhGroupExchangeMinimumBits ))
binary .Write (h , binary .BigEndian , uint32 (dhGroupExchangePreferredBits ))
binary .Write (h , binary .BigEndian , uint32 (dhGroupExchangeMaximumBits ))
writeInt (h , p )
writeInt (h , g )
writeInt (h , kexDHGexInit .X )
writeInt (h , Y )
K := make ([]byte , intLength (kInt ))
marshalInt (K , kInt )
h .Write (K )
H := h .Sum (nil )
sig , err := signAndMarshal (priv , randSource , H , algo )
if err != nil {
return nil , err
}
kexDHGexReply := kexDHGexReplyMsg {
HostKey : hostKeyBytes ,
Y : Y ,
Signature : sig ,
}
packet = Marshal (&kexDHGexReply )
err = c .writePacket (packet )
return &kexResult {
H : H ,
K : K ,
HostKey : hostKeyBytes ,
Signature : sig ,
Hash : gex .hashFunc ,
}, err
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .