starting moduli.c port from OpenSSH
This commit is contained in:
@@ -21,6 +21,7 @@ package moduli
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
`github.com/Luzifer/go-dhparam`
|
||||
"r00t2.io/sshsecure/sharedconsts"
|
||||
)
|
||||
|
||||
@@ -39,7 +40,21 @@ const (
|
||||
// The recommended minimum moduli to have available.
|
||||
recMinMod int = 400
|
||||
// The minimum bits for filtering. It's generally bits - 1
|
||||
minBits uint8 = 4095
|
||||
minBits uint16 = 4096
|
||||
)
|
||||
|
||||
// Generation iterables.
|
||||
var (
|
||||
genBits = []uint16{
|
||||
4096,
|
||||
6144,
|
||||
7680,
|
||||
8192,
|
||||
}
|
||||
genGenerators = []dhparam.Generator{
|
||||
dhparam.GeneratorTwo,
|
||||
dhparam.GeneratorFive,
|
||||
}
|
||||
)
|
||||
|
||||
// The header line on the /etc/ssh/moduli file.
|
||||
@@ -57,8 +72,7 @@ const (
|
||||
timeFormat string = "20060102150405" // %Y%m%d%H%M%S
|
||||
)
|
||||
|
||||
// For validation. Currently unused.
|
||||
/*
|
||||
// For validation. TODO.
|
||||
var (
|
||||
validTypes = []uint8{
|
||||
0, // Unknown, not tested
|
||||
@@ -72,4 +86,3 @@ var (
|
||||
0x04, // Probabilistic Miller-Rabin primality tests.
|
||||
}
|
||||
)
|
||||
*/
|
||||
|
||||
118
moduli/func.go
118
moduli/func.go
@@ -24,42 +24,130 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
`time`
|
||||
|
||||
`github.com/Luzifer/go-dhparam`
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// getPregen gets the pregenerated moduli from upstream mirror.
|
||||
func getPregen() (Moduli, error) {
|
||||
m := Moduli{}
|
||||
// NewModuli returns a Moduli populated with Entry items.
|
||||
func NewModuli(usePreGen ... bool) (m *Moduli, err error) {
|
||||
|
||||
var doPreGen bool
|
||||
|
||||
m = new(Moduli)
|
||||
|
||||
if usePreGen != nil {
|
||||
doPreGen = usePreGen[0]
|
||||
} else {
|
||||
doPreGen = false
|
||||
}
|
||||
|
||||
if doPreGen {
|
||||
if err = GetPreGen(m); err != nil {
|
||||
return
|
||||
}
|
||||
// This may take a while.
|
||||
if err = m.Harden(); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = Generate(m); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPreGen gets the pregenerated moduli from upstream mirror.
|
||||
func GetPreGen(m *Moduli) (err error) {
|
||||
|
||||
var b []byte
|
||||
var goodCksum []byte
|
||||
var resp *http.Response
|
||||
|
||||
// get the pregenerated moduli
|
||||
resp, err := http.Get(pregenURL)
|
||||
resp, err = http.Get(pregenURL)
|
||||
if err != nil {
|
||||
return m, err
|
||||
return
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return m, errors.New(fmt.Sprintf("returned status code %v: %v", resp.StatusCode, resp.Status))
|
||||
err = errors.New(fmt.Sprintf("returned status code %v: %v", resp.StatusCode, resp.Status))
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b := make([]byte, resp.ContentLength)
|
||||
|
||||
b = make([]byte, resp.ContentLength)
|
||||
if _, err = resp.Body.Read(b); err != nil {
|
||||
return m, err
|
||||
return
|
||||
}
|
||||
|
||||
// and compare the SHA3-512 (NIST) checksum.
|
||||
s := sha3.New512()
|
||||
if _, err = s.Write(b); err != nil {
|
||||
// TODO: return nil instead of b?
|
||||
return m, err
|
||||
return
|
||||
}
|
||||
goodCksum, err := hex.DecodeString(pregenCksum)
|
||||
goodCksum, err = hex.DecodeString(pregenCksum)
|
||||
if err != nil {
|
||||
return m, err
|
||||
return
|
||||
}
|
||||
|
||||
// We just compare the bytestrings.
|
||||
if bytes.Compare(s.Sum(nil), goodCksum) != 0 {
|
||||
return m, errors.New("checksums do not match")
|
||||
err = errors.New("checksums do not match")
|
||||
return
|
||||
}
|
||||
|
||||
if err := Unmarshal(b, m); err != nil {
|
||||
return m, err
|
||||
return
|
||||
}
|
||||
return m, nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Generate generates new moduli with Entry items. It's more secure than using GetPreGen (LogJam), but takes a *LOT* longer.
|
||||
func Generate(m *Moduli) (err error) {
|
||||
|
||||
var dh *dhparam.DH
|
||||
|
||||
for _, bitLen := range genBits {
|
||||
for _, generator := range genGenerators {
|
||||
|
||||
var e Entry
|
||||
|
||||
e = Entry{
|
||||
Time: time.Now(),
|
||||
Size: bitLen,
|
||||
Generator: uint8(generator),
|
||||
/*
|
||||
Type: 0,
|
||||
Tests: 0,
|
||||
Trials: 0,
|
||||
*/
|
||||
}
|
||||
|
||||
if dh, err = dhparam.Generate(int(bitLen), generator, nil); err != nil {
|
||||
continue // TODO: log/print
|
||||
}
|
||||
|
||||
// Check() applies big.Int.ProbablyPrime() (Miller-Rabin - 0x04 in ssh moduli - and Baillie-PSW test), so it's probably fine.
|
||||
if errs, ok := dh.Check(); !ok {
|
||||
_ = errs // TODO: log/print
|
||||
continue
|
||||
} else {
|
||||
e.Time = time.Now()
|
||||
// e.Type =
|
||||
// e.Tests =
|
||||
// e.Trials =
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
102
moduli/parser.go
102
moduli/parser.go
@@ -35,102 +35,136 @@ 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() ([]byte, error) {
|
||||
func (m *Moduli) Marshal() (bytesOut []byte, err error) {
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
b.Write([]byte(header))
|
||||
for _, i := range m.Params {
|
||||
line, err := i.marshalEntry()
|
||||
if err != nil {
|
||||
return b.Bytes(), err
|
||||
return nil, err
|
||||
} else {
|
||||
b.Write(line)
|
||||
}
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
|
||||
bytesOut = b.Bytes()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// marshalEntry is used to parse a specific DH entry into the moduli.
|
||||
func (m *Entry) marshalEntry() ([]byte, error) {
|
||||
// marshalEntry is used to parse an Entry into the moduli(5) format.
|
||||
func (m *Entry) marshalEntry() (sum []byte, err error) {
|
||||
|
||||
mod := hex.EncodeToString(m.Modulus.Bytes())
|
||||
|
||||
s := fmt.Sprintf(
|
||||
"%v %v %v %v %v %v %v\n",
|
||||
m.Time.Format(timeFormat),
|
||||
string(m.Type),
|
||||
string(m.Tests),
|
||||
string(m.Trials),
|
||||
string(m.Size),
|
||||
strconv.Itoa(int(m.Size)-1), // see this thread https://twitter.com/SysAdm_Podcast/status/1386714803679399940
|
||||
string(m.Generator),
|
||||
mod,
|
||||
)
|
||||
return []byte(s), nil
|
||||
|
||||
sum = []byte(s)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal writes the Moduli format into m from the /etc/ssh/moduli format in data.
|
||||
func Unmarshal(data []byte, m Moduli) error {
|
||||
// Unmarshal populates a Moduli from the /etc/ssh/moduli format.
|
||||
func Unmarshal(data []byte, m *Moduli) (err error) {
|
||||
|
||||
var lines []string
|
||||
var entries []Entry
|
||||
|
||||
lines = strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
|
||||
e := Entry{}
|
||||
|
||||
if reSkipLine.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
l := strings.Fields(line)
|
||||
if err := unmarshalEntry(l, e); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
entries = append(entries, e)
|
||||
}
|
||||
|
||||
m.Params = entries
|
||||
return nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func unmarshalEntry(line []string, m Entry) error {
|
||||
var err error
|
||||
// unmarshalEntry unmarshals a single line from an /etc/ssh/moduli into an Entry.
|
||||
func unmarshalEntry(line []string, m Entry) (err error) {
|
||||
|
||||
var modb []byte
|
||||
|
||||
if len(line) != 7 {
|
||||
return errors.New("field count mismatch")
|
||||
err = errors.New("field count mismatch")
|
||||
return
|
||||
}
|
||||
|
||||
if m.Time, err = time.Parse(timeFormat, line[0]); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
// Numeric types. Cast to uint8. There's probably a better way to do this but golang's pretty ugly with this stuff no matter what.
|
||||
// Numeric types. Cast to uint16. There's probably a better way to do this but golang's pretty ugly with this stuff no matter what.
|
||||
// The worst part is all of them are uint8 except size (uint16).
|
||||
// Type, Tests, Trials, Size, Generator
|
||||
conv := [5]uint8{}
|
||||
conv := [5]uint16{}
|
||||
for idx := 1; idx <= 5; idx++ {
|
||||
v := line[idx]
|
||||
newv, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conv[idx-1] = uint8(newv)
|
||||
conv[idx-1] = uint16(newv)
|
||||
}
|
||||
m.Type = conv[0]
|
||||
m.Tests = conv[1]
|
||||
m.Trials = conv[2]
|
||||
m.Size = conv[3]
|
||||
m.Generator = conv[4]
|
||||
|
||||
m.Type = uint8(conv[0])
|
||||
m.Tests = uint8(conv[1])
|
||||
m.Trials = uint8(conv[2])
|
||||
m.Size = conv[3] + 1 // see this thread https://twitter.com/SysAdm_Podcast/status/1386714803679399940
|
||||
m.Generator = uint8(conv[4])
|
||||
|
||||
// And the modulus convert to big.Int.
|
||||
modb, err := hex.DecodeString(line[6])
|
||||
if err != nil {
|
||||
return err
|
||||
if modb, err = hex.DecodeString(line[6]); err != nil {
|
||||
return
|
||||
}
|
||||
m.Modulus = big.Int{}
|
||||
m.Modulus.SetBytes(modb)
|
||||
return nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Moduli) Harden() error {
|
||||
func (m *Moduli) Harden() (err error) {
|
||||
|
||||
var entries []Entry
|
||||
|
||||
for _, e := range m.Params {
|
||||
|
||||
e.Time = time.Now()
|
||||
|
||||
if e.Size >= minBits {
|
||||
entries = append(entries, e)
|
||||
}
|
||||
}
|
||||
m.Params = entries
|
||||
if len(m.Params) < recMinMod {
|
||||
return errors.New("does not meet recommended minimum moduli")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: find way of testing/sieving primes
|
||||
if len(m.Params) < recMinMod {
|
||||
err = errors.New("does not meet recommended minimum moduli")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: find way of testing/sieving primes
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ https://security.stackexchange.com/a/113058
|
||||
https://github.com/Luzifer/go-dhparam
|
||||
https://github.com/mimoo/test_DHparams
|
||||
https://github.com/hyperreality/cryptopals-2/blob/517c1907b2041e6f7ef18930eca2aa3a24fb73d8/dh.go
|
||||
https://sosedoff.com/2016/07/16/golang-struct-tags.html
|
||||
https://sosedoff.com/2016/07/16/golang-struct-tags.html
|
||||
port moduli gen? https://github.com/openssh/openssh-portable/blob/master/moduli.c
|
||||
|
||||
@@ -29,7 +29,7 @@ type Moduli struct {
|
||||
Params []Entry
|
||||
}
|
||||
|
||||
// Moduli is a struct reflecting the format of a single /etc/ssh/moduli entry. See moduli(5) for details.
|
||||
// Entry is a struct reflecting the format of a single /etc/ssh/moduli entry. See moduli(5) for details.
|
||||
type Entry struct {
|
||||
Time time.Time // YYYYMMDDHHSS
|
||||
/*
|
||||
@@ -64,7 +64,7 @@ type Entry struct {
|
||||
// man 5 moduli:
|
||||
Decimal number indicating the size of the prime in bits.
|
||||
*/
|
||||
Size uint8
|
||||
Size uint16
|
||||
/*
|
||||
// man 5 moduli:
|
||||
The recommended generator for use with this modulus (hexadecimal).
|
||||
|
||||
Reference in New Issue
Block a user