SSHSecure/dh/func_gen.go

175 lines
4.6 KiB
Go

/*
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 <https://www.gnu.org/licenses/>.
*/
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
}