From 7f98e7aa15993b060fb0c3253c14f653eb53e3bd Mon Sep 17 00:00:00 2001 From: brent s Date: Sat, 3 Jul 2021 23:01:58 -0400 Subject: [PATCH] yeah so as a temporary measure, i'm using ssh-keygen for now. but i'll need to natively incorporate it still. --- config/func.go | 2 +- dh/func_gen.go | 138 ++++++++++++++++++++++++++++++++++++++++++++- go.mod | 1 + go.sum | 2 + moduli/func.go | 18 +++--- moduli/parser.go | 13 +++-- moduli/struct.go | 4 +- utils/func.go | 143 +++++++++++++++++++++++++++++++++++++++++++++++ utils/struct.go | 21 +++++++ 9 files changed, 323 insertions(+), 19 deletions(-) create mode 100644 utils/func.go diff --git a/config/func.go b/config/func.go index 19dbdcf..ba01d6e 100644 --- a/config/func.go +++ b/config/func.go @@ -31,7 +31,7 @@ import ( "r00t2.io/sysutils/paths" ) -func TestConfig(config *[]byte) (bool, error) { +func ChkConfig(config *[]byte) (bool, error) { var sysPaths []string var binPath string var tmpConf *os.File diff --git a/dh/func_gen.go b/dh/func_gen.go index f9cf9ac..bbb73e6 100644 --- a/dh/func_gen.go +++ b/dh/func_gen.go @@ -18,6 +18,17 @@ package dh +import ( + "io/ioutil" + "os" + "os/exec" + "strings" + + "github.com/Masterminds/semver" + "r00t2.io/sshsecure/moduli" + "r00t2.io/sshsecure/utils" +) + /* OpenSSH does prime generation and primality checking a *little* weird. @@ -35,4 +46,129 @@ package dh And that's why I'm a sad panda and porting moduli.c to native Golang. */ -func SieveLarge() +// Generate builds a slice of moduli.Entry entries. +// TODO: DO THIS NATIVELY. Idealy with goroutines and a channel? buffer? for the primes and sieving. +func Generate() (moduliEntries []moduli.Entry, err error) { + + var outFile *os.File + var filteredFile string + var raw []byte + var m *moduli.Moduli = new(moduli.Moduli) + + if outFile, err = genPrimes(); err != nil { + return + } + + if filteredFile, err = sieve(outFile); err != nil { + return + } + + if raw, err = ioutil.ReadFile(filteredFile); err != nil { + return + } + + if err = moduli.Unmarshal(raw, m); err != nil { + return + } + + moduliEntries = m.Groups + + return +} + +// genPrimes builds a slice of acceptable primes for sieve. +// TODO: DO THIS NATIVELY. +func genPrimes() (outFile *os.File, err error) { + + var cmdStr []string = []string{"ssh-keygen", "-q"} + var cmdStr2 []string + var cmd *exec.Cmd + var sshVer utils.SshVerInfo + // These are various versions we need to compare against for determining features. + var ver81 *semver.Version + + if outFile, err = ioutil.TempFile("", ".SSHSecure.moduli.*"); err != nil { + return + } + if err = outFile.Close(); err != nil { + return + } + if err = os.Remove(outFile.Name()); err != nil { + return + } + + if sshVer, err = utils.GetSshVer(""); err != nil { + return + } + + // They changed the command syntax/options in 8.1. We need to determine if we're at or above that version or not. + if ver81, err = semver.NewVersion("8.1.0"); err != nil { + return + } + + if sshVer.SemVer.Compare(ver81) >= 0 { + cmdStr2 = []string{ + "-M", "generate", + "-O", "bits=4096", + outFile.Name(), + } + } else { + cmdStr2 = []string{ + "-b", "4096", + "-G", outFile.Name(), + } + } + cmdStr = append(cmdStr, cmdStr2...) + + cmd = exec.Command(cmdStr[0], strings.Join(cmdStr[1:], " ")) + if err = cmd.Run(); err != nil { + return + } + + return +} + +// sieve filters out unsuitable primes. See #1 and #2 in the comments at the top of this file. +// TODO: DO THIS NATIVELY. +func sieve(inFile *os.File) (newFile string, err error) { + + var cmdStr []string = []string{"ssh-keygen", "-q"} + var cmdStr2 []string + var cmd *exec.Cmd + var sshVer utils.SshVerInfo + // These are various versions we need to compare against for determining features. + var ver81 *semver.Version + + if sshVer, err = utils.GetSshVer(""); err != nil { + return + } + + newFile = inFile.Name() + ".filtered" + + // They changed the command syntax/options in 8.1. We need to determine if we're at or above that version or not. + if ver81, err = semver.NewVersion("8.1.0"); err != nil { + return + } + + if sshVer.SemVer.Compare(ver81) >= 0 { + cmdStr2 = []string{ + "-M", "screen", + "-O", "bits=4096", + "-f", inFile.Name(), + newFile, + } + } else { + cmdStr2 = []string{ + "-T", newFile, + "-f", inFile.Name(), + } + } + cmdStr = append(cmdStr, cmdStr2...) + + cmd = exec.Command(cmdStr[0], strings.Join(cmdStr[1:], " ")) + if err = cmd.Run(); err != nil { + return + } + + return +} diff --git a/go.mod b/go.mod index 6659f3f..eb61ce1 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/Luzifer/go-dhparam v1.1.0 + github.com/Masterminds/semver v1.5.0 github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a github.com/go-restruct/restruct v1.2.0-alpha github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 7630b34..4bf0bc9 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Luzifer/go-dhparam v1.1.0 h1:uJXDwqAVy1H4zWjmsYVmaa9yUD2Pm3SsdW4KU8d27zc= github.com/Luzifer/go-dhparam v1.1.0/go.mod h1:3Kuj59C67/G2EzQHjUzAryaAa70K5fqvStR2VkFLszU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU= diff --git a/moduli/func.go b/moduli/func.go index 5919aee..24c41dd 100644 --- a/moduli/func.go +++ b/moduli/func.go @@ -24,14 +24,14 @@ import ( "errors" "fmt" "net/http" - `time` + "time" - `github.com/Luzifer/go-dhparam` + "github.com/Luzifer/go-dhparam" "golang.org/x/crypto/sha3" ) // NewModuli returns a Moduli populated with Entry items. -func NewModuli(usePreGen ... bool) (m *Moduli, err error) { +func NewModuli(usePreGen ...bool) (m *Moduli, err error) { var doPreGen bool @@ -117,13 +117,13 @@ func Generate(m *Moduli) (err error) { var e Entry e = Entry{ - Time: time.Now(), - Size: bitLen, + Time: time.Now(), + Size: bitLen, Generator: uint8(generator), /* - Type: 0, - Tests: 0, - Trials: 0, + Type: 0, + Tests: 0, + Trials: 0, */ } @@ -143,7 +143,7 @@ func Generate(m *Moduli) (err error) { e.Modulus = *dh.P // TODO: https://stackoverflow.com/questions/18499352/golang-concurrency-how-to-append-to-the-same-slice-from-different-goroutines - m.Params = append(m.Params, e) + m.Groups = append(m.Groups, e) } } diff --git a/moduli/parser.go b/moduli/parser.go index 2fe255a..34291a4 100644 --- a/moduli/parser.go +++ b/moduli/parser.go @@ -34,13 +34,12 @@ var reSkipLine, _ = regexp.Compile(`^\s*(#.*)?$`) // Marshal returns the /etc/ssh/moduli format of m. // Format of: Time Type Tests Tries Size Generator Modulus -// TODO: remember to write newline at end func (m *Moduli) Marshal() (bytesOut []byte, err error) { var b bytes.Buffer b.Write([]byte(header)) - for _, i := range m.Params { + for _, i := range m.Groups { line, err := i.marshalEntry() if err != nil { return nil, err @@ -49,6 +48,8 @@ func (m *Moduli) Marshal() (bytesOut []byte, err error) { } } + b.Write([]byte("\n")) + bytesOut = b.Bytes() return @@ -98,7 +99,7 @@ func Unmarshal(data []byte, m *Moduli) (err error) { entries = append(entries, e) } - m.Params = entries + m.Groups = entries return } @@ -149,7 +150,7 @@ func (m *Moduli) Harden() (err error) { var entries []Entry - for _, e := range m.Params { + for _, e := range m.Groups { e.Time = time.Now() @@ -157,9 +158,9 @@ func (m *Moduli) Harden() (err error) { entries = append(entries, e) } } - m.Params = entries + m.Groups = entries - if len(m.Params) < recMinMod { + if len(m.Groups) < recMinMod { err = errors.New("does not meet recommended minimum moduli") return } diff --git a/moduli/struct.go b/moduli/struct.go index f2cbbe8..8343c67 100644 --- a/moduli/struct.go +++ b/moduli/struct.go @@ -23,10 +23,10 @@ import ( "time" ) -// Moduli contains all data needed for generated /etc/ssh/moduli. of ModuliEntry entries. +// Moduli contains all data needed for generated /etc/ssh/moduli of Entry entries. type Moduli struct { Header string - Params []Entry + Groups []Entry } // Entry is a struct reflecting the format of a single /etc/ssh/moduli entry. See moduli(5) for details. diff --git a/utils/func.go b/utils/func.go new file mode 100644 index 0000000..0649b52 --- /dev/null +++ b/utils/func.go @@ -0,0 +1,143 @@ +package utils + +import ( + "fmt" + "os/exec" + "regexp" + "strconv" + "strings" + "time" + + "github.com/Masterminds/semver" +) + +// GetSshVer returns an SshVerInfo. If verStr is an empty string, get the version automatically. +func GetSshVer(verStr string) (ver SshVerInfo, err error) { + + var newVerStr string + var sslVer OpenSslInfo + var sslVerStr string + var ptrn *regexp.Regexp + var matches []string + var verSlice [3]uint + var n int + var sver *semver.Version + + if verStr == "" { + var out []byte + cmd := exec.Command("ssh", "-V") + if err = cmd.Run(); err != nil { + return + } + if out, err = cmd.Output(); err != nil { + return + } + verStr = string(out) + } + + if ptrn, err = regexp.Compile(`^(?:Open|Sun_)SSH_(?P[0-9.]+)(?:p?(?P[0-9.]+))?,\s*(?P.*)?$`); err != nil { + return + } + + matches = ptrn.FindStringSubmatch(verStr) + + // Reformat verStr to the combined version string. + newVerStr = fmt.Sprintf( + "%v.%v", + matches[ptrn.SubexpIndex("sshver")], + matches[ptrn.SubexpIndex("patchver")], + ) + + sslVerStr = matches[ptrn.SubexpIndex("sslverstr")] + + if sslVer, err = GetSslVer(sslVerStr); err != nil { + return + } + + for idx, s := range strings.Split(newVerStr, ".") { + if n, err = strconv.Atoi(s); err != nil { + return + } + verSlice[idx] = uint(n) + } + + if sver, err = semver.NewVersion(newVerStr); err != nil { + return + } + + ver = SshVerInfo{ + Major: verSlice[0], + Minor: verSlice[1], + Patch: verSlice[2], + Raw: verStr, + SslInfo: sslVer, + SemVer: *sver, + } + + return +} + +// GetSslVer returns an OpenSslInfo. If verStr is an empty string, get the version automatically. +func GetSslVer(verStr string) (ver OpenSslInfo, err error) { + + var matches []string + var verSlice [3]uint + var newVerStr string + var ptrn *regexp.Regexp + var sver *semver.Version + // TODO: confirm the day is zero-padded. + var tfmt string = `02 Jan 2006` + var n int + var t time.Time + + if verStr == "" { + var out []byte + cmd := exec.Command("openssl", "version") + if err = cmd.Run(); err != nil { + return + } + if out, err = cmd.Output(); err != nil { + return + } + verStr = string(out) + } + + if ptrn, err = regexp.Compile(`^(?:Open|Sun_)SSL\s+(FIPS\s+)?(?P[0-9.]+)(?P[A-Za-z]+)?\s+(?P