175 lines
4.6 KiB
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
|
|
}
|