292 lines
7.6 KiB
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
|
|
}
|