/* SSHSecure - a program to harden OpenSSH from defaults Copyright (C) 2020 Brent Saner This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ 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. The seemingly go-to package for DH parameter generation in Golang, github.com/Luzifer/go-dhparam, does implement safety checking in a way I believe to be safe (with the huge caveat that I am nowhere near a professional, expert, guru, etc. in mathematics, cryptography, or the like). However, it is incompatible with OpenSSH's methodology for DH parameter generation. 1.) First, primes are generated via the Sieve of Eratosthenes. a.) They must also be Sophie Germain primes (where p is selected prime, 2p+1 is also prime). 2.) Then they are filtered via Probabilistic Miller-Rabin primality tests (on both q and p, where q is (p-1)/2). 3.) OpenSSH fully supports generators of 2, 3, and 5 whereas go-dhparam only fully supports 2 and 5. And that's why I'm a sad panda and porting moduli.c to native Golang. */ // 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 }