zgrab2/lib/ssh/kex_gex.go

292 lines
7.6 KiB
Go

package ssh
import (
"crypto"
"crypto/rand"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"math/big"
ztoolsKeys "github.com/zmap/zgrab/ztools/keys"
)
const (
kexAlgoDHGEXSHA1 = "diffie-hellman-group-exchange-sha1"
kexAlgoDHGEXSHA256 = "diffie-hellman-group-exchange-sha256"
)
// Messages
type kexDHGexGroupMsg struct {
P *big.Int `sshtype:"31"`
G *big.Int
}
type kexDHGexInitMsg struct {
X *big.Int `sshtype:"32"`
}
type kexDHGexReplyMsg struct {
HostKey []byte `sshtype:"33"`
Y *big.Int
Signature []byte
}
type kexDHGexRequestMsg struct {
MinBits uint32 `sshtype:"34"`
PreferedBits uint32
MaxBits uint32
}
type gexJsonLog struct {
Parameters *ztoolsKeys.DHParams `json:"dh_params,omitempty"`
ServerSignature *JsonSignature `json:"server_signature,omitempty"`
ServerHostKey *ServerHostKeyJsonLog `json:"server_host_key,omitempty"`
}
type dhGEXSHA struct {
g, p *big.Int
hashFunc crypto.Hash
JsonLog *gexJsonLog
}
func (gex *dhGEXSHA) GetNew(keyType string) kexAlgorithm {
switch keyType {
case kexAlgoDHGEXSHA1:
ret := new(dhGEXSHA)
ret.hashFunc = crypto.SHA1
ret.JsonLog = new(gexJsonLog)
ret.JsonLog.Parameters = new(ztoolsKeys.DHParams)
return ret
case kexAlgoDHGEXSHA256:
ret := new(dhGEXSHA)
ret.hashFunc = crypto.SHA256
ret.JsonLog = new(gexJsonLog)
ret.JsonLog.Parameters = new(ztoolsKeys.DHParams)
return ret
default:
panic("Unimplemented GEX selected")
}
}
func (gex *dhGEXSHA) MarshalJSON() ([]byte, error) {
return json.Marshal(gex.JsonLog)
}
func (gex *dhGEXSHA) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
if theirPublic.Sign() <= 0 || theirPublic.Cmp(gex.p) >= 0 {
return nil, fmt.Errorf("ssh: DH parameter out of bounds")
}
return new(big.Int).Exp(theirPublic, myPrivate, gex.p), nil
}
func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
// Send GexRequest
kexDHGexRequest := kexDHGexRequestMsg{
MinBits: uint32(pkgConfig.GexMinBits),
PreferedBits: uint32(pkgConfig.GexPreferredBits),
MaxBits: uint32(pkgConfig.GexMaxBits),
}
if err := c.writePacket(Marshal(&kexDHGexRequest)); err != nil {
return nil, err
}
// *Receive GexGroup*
packet, err := c.readPacket()
if err != nil {
return nil, err
}
var kexDHGexGroup kexDHGexGroupMsg
if err = Unmarshal(packet, &kexDHGexGroup); err != nil {
return nil, err
}
if gex.JsonLog != nil {
gex.JsonLog.Parameters.Prime = kexDHGexGroup.P
gex.JsonLog.Parameters.Generator = kexDHGexGroup.G
}
// reject if p's bit length < pkgConfig.GexMinBits or > pkgConfig.GexMaxBits
if kexDHGexGroup.P.BitLen() < int(pkgConfig.GexMinBits) || kexDHGexGroup.P.BitLen() > int(pkgConfig.GexMaxBits) {
return nil, fmt.Errorf("Server-generated gex p (dont't ask) is out of range (%d bits)", kexDHGexGroup.P.BitLen())
}
gex.p = kexDHGexGroup.P
gex.g = kexDHGexGroup.G
// *Send GexInit
x, err := rand.Int(randSource, gex.p)
if err != nil {
return nil, err
}
X := new(big.Int).Exp(gex.g, x, gex.p)
kexDHGexInit := kexDHGexInitMsg{
X: X,
}
if gex.JsonLog != nil && pkgConfig.Verbose {
gex.JsonLog.Parameters.ClientPrivate = x
gex.JsonLog.Parameters.ClientPublic = X
}
if err := c.writePacket(Marshal(&kexDHGexInit)); err != nil {
return nil, err
}
// Receive GexReply
packet, err = c.readPacket()
if err != nil {
return nil, err
}
var kexDHGexReply kexDHGexReplyMsg
if err = Unmarshal(packet, &kexDHGexReply); err != nil {
return nil, err
}
if gex.JsonLog != nil {
gex.JsonLog.Parameters.ServerPublic = kexDHGexReply.Y
gex.JsonLog.ServerSignature = new(JsonSignature)
gex.JsonLog.ServerSignature.Raw = kexDHGexReply.Signature
gex.JsonLog.ServerSignature.Parsed, _, _ = parseSignatureBody(kexDHGexReply.Signature)
gex.JsonLog.ServerHostKey = LogServerHostKey(kexDHGexReply.HostKey)
}
kInt, err := gex.diffieHellman(kexDHGexReply.Y, x)
if err != nil {
return nil, err
}
h := gex.hashFunc.New()
magics.write(h)
writeString(h, kexDHGexReply.HostKey)
binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexMinBits))
binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexPreferredBits))
binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexMaxBits))
writeInt(h, gex.p)
writeInt(h, gex.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 Signer) (result *kexResult, err error) {
// *Receive GexRequest*
packet, err := c.readPacket()
if err != nil {
return
}
var kexDHGexRequest kexDHGexRequestMsg
if err = Unmarshal(packet, &kexDHGexRequest); err != nil {
return
}
// smoosh the user's preferred size into our own limits
if kexDHGexRequest.PreferedBits > uint32(pkgConfig.GexMaxBits) {
kexDHGexRequest.PreferedBits = uint32(pkgConfig.GexMaxBits)
}
if kexDHGexRequest.PreferedBits < uint32(pkgConfig.GexMinBits) {
kexDHGexRequest.PreferedBits = uint32(pkgConfig.GexMinBits)
}
// fix min/max if they're inconsistent. technically, we could just pout
// and hang up, but there's no harm in giving them the benefit of the
// doubt and just picking a bitsize for them.
if kexDHGexRequest.MinBits > kexDHGexRequest.PreferedBits {
kexDHGexRequest.MinBits = kexDHGexRequest.PreferedBits
}
if kexDHGexRequest.MaxBits < kexDHGexRequest.PreferedBits {
kexDHGexRequest.MaxBits = kexDHGexRequest.PreferedBits
}
// *Send GexGroup*
// generate prime
// TODO: Not implemented yet, should load primes from /etc/ssh/moduli
gex.p, _ = new(big.Int).SetString("D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96361507", 16)
gex.g = big.NewInt(5)
kexDHGexGroup := kexDHGexGroupMsg{
P: gex.p,
G: gex.g,
}
if err := c.writePacket(Marshal(&kexDHGexGroup)); err != nil {
return nil, err
}
// *Receive GexInit
packet, err = c.readPacket()
if err != nil {
return
}
var kexDHGexInit kexDHGexInitMsg
if err = Unmarshal(packet, &kexDHGexInit); err != nil {
return
}
y, err := rand.Int(randSource, gex.p)
if err != nil {
return
}
Y := new(big.Int).Exp(gex.g, y, gex.p)
kInt, err := gex.diffieHellman(kexDHGexInit.X, y)
if err != nil {
return nil, err
}
hostKeyBytes := priv.PublicKey().Marshal()
h := gex.hashFunc.New()
magics.write(h)
writeString(h, hostKeyBytes)
binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexMinBits))
binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexPreferredBits))
binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexMaxBits))
writeInt(h, gex.p)
writeInt(h, gex.g)
writeInt(h, kexDHGexInit.X)
writeInt(h, Y)
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
h.Write(K)
H := h.Sum(nil)
// H is already a hash, but the hostkey signing will apply its
// own key-specific hash algorithm.
sig, err := signAndMarshal(priv, randSource, H)
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,
}, nil
}