2020-09-24 04:38:29 -04:00
|
|
|
/*
|
|
|
|
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 moduli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var reSkipLine, _ = regexp.Compile(`^\s*(#.*)?$`)
|
|
|
|
|
|
|
|
// Marshal returns the /etc/ssh/moduli format of m.
|
|
|
|
// Format of: Time Type Tests Tries Size Generator Modulus
|
2021-05-04 03:39:43 -04:00
|
|
|
func (m *Moduli) Marshal() (bytesOut []byte, err error) {
|
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
var b bytes.Buffer
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
b.Write([]byte(header))
|
2021-07-03 23:01:58 -04:00
|
|
|
for _, i := range m.Groups {
|
2020-09-24 04:38:29 -04:00
|
|
|
line, err := i.marshalEntry()
|
|
|
|
if err != nil {
|
2021-05-04 03:39:43 -04:00
|
|
|
return nil, err
|
2020-09-24 04:38:29 -04:00
|
|
|
} else {
|
|
|
|
b.Write(line)
|
|
|
|
}
|
|
|
|
}
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2021-07-03 23:01:58 -04:00
|
|
|
b.Write([]byte("\n"))
|
|
|
|
|
2021-05-04 03:39:43 -04:00
|
|
|
bytesOut = b.Bytes()
|
|
|
|
|
|
|
|
return
|
2020-09-24 04:38:29 -04:00
|
|
|
}
|
|
|
|
|
2021-05-04 03:39:43 -04:00
|
|
|
// marshalEntry is used to parse an Entry into the moduli(5) format.
|
|
|
|
func (m *Entry) marshalEntry() (sum []byte, err error) {
|
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
mod := hex.EncodeToString(m.Modulus.Bytes())
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
s := fmt.Sprintf(
|
|
|
|
"%v %v %v %v %v %v %v\n",
|
|
|
|
m.Time.Format(timeFormat),
|
|
|
|
string(m.Type),
|
|
|
|
string(m.Tests),
|
|
|
|
string(m.Trials),
|
2021-05-04 03:39:43 -04:00
|
|
|
strconv.Itoa(int(m.Size)-1), // see this thread https://twitter.com/SysAdm_Podcast/status/1386714803679399940
|
2020-09-24 04:38:29 -04:00
|
|
|
string(m.Generator),
|
|
|
|
mod,
|
|
|
|
)
|
2021-05-04 03:39:43 -04:00
|
|
|
|
|
|
|
sum = []byte(s)
|
|
|
|
|
|
|
|
return
|
2020-09-24 04:38:29 -04:00
|
|
|
}
|
|
|
|
|
2021-05-04 03:39:43 -04:00
|
|
|
// Unmarshal populates a Moduli from the /etc/ssh/moduli format.
|
|
|
|
func Unmarshal(data []byte, m *Moduli) (err error) {
|
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
var lines []string
|
2020-09-27 03:23:58 -04:00
|
|
|
var entries []Entry
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
lines = strings.Split(string(data), "\n")
|
|
|
|
for _, line := range lines {
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2020-09-27 03:23:58 -04:00
|
|
|
e := Entry{}
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
if reSkipLine.MatchString(line) {
|
|
|
|
continue
|
|
|
|
}
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
l := strings.Fields(line)
|
|
|
|
if err := unmarshalEntry(l, e); err != nil {
|
2021-05-04 03:39:43 -04:00
|
|
|
return
|
2020-09-24 04:38:29 -04:00
|
|
|
}
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
entries = append(entries, e)
|
|
|
|
}
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2021-07-03 23:01:58 -04:00
|
|
|
m.Groups = entries
|
2021-05-04 03:39:43 -04:00
|
|
|
|
|
|
|
return
|
2020-09-24 04:38:29 -04:00
|
|
|
}
|
|
|
|
|
2021-05-04 03:39:43 -04:00
|
|
|
// unmarshalEntry unmarshals a single line from an /etc/ssh/moduli into an Entry.
|
|
|
|
func unmarshalEntry(line []string, m Entry) (err error) {
|
|
|
|
|
|
|
|
var modb []byte
|
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
if len(line) != 7 {
|
2021-05-04 03:39:43 -04:00
|
|
|
err = errors.New("field count mismatch")
|
|
|
|
return
|
2020-09-24 04:38:29 -04:00
|
|
|
}
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
if m.Time, err = time.Parse(timeFormat, line[0]); err != nil {
|
2021-05-04 03:39:43 -04:00
|
|
|
return
|
2020-09-24 04:38:29 -04:00
|
|
|
}
|
2021-05-04 03:39:43 -04:00
|
|
|
// 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).
|
2020-09-24 04:38:29 -04:00
|
|
|
// Type, Tests, Trials, Size, Generator
|
2021-05-04 03:39:43 -04:00
|
|
|
conv := [5]uint16{}
|
2020-09-24 04:38:29 -04:00
|
|
|
for idx := 1; idx <= 5; idx++ {
|
|
|
|
v := line[idx]
|
|
|
|
newv, err := strconv.Atoi(v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-04 03:39:43 -04:00
|
|
|
conv[idx-1] = uint16(newv)
|
2020-09-24 04:38:29 -04:00
|
|
|
}
|
2021-05-04 03:39:43 -04:00
|
|
|
|
|
|
|
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])
|
|
|
|
|
2020-09-24 04:38:29 -04:00
|
|
|
// And the modulus convert to big.Int.
|
2021-05-04 03:39:43 -04:00
|
|
|
if modb, err = hex.DecodeString(line[6]); err != nil {
|
|
|
|
return
|
2020-09-24 04:38:29 -04:00
|
|
|
}
|
|
|
|
m.Modulus = big.Int{}
|
|
|
|
m.Modulus.SetBytes(modb)
|
2021-05-04 03:39:43 -04:00
|
|
|
|
|
|
|
return
|
2020-09-24 04:38:29 -04:00
|
|
|
}
|
2020-09-27 03:23:58 -04:00
|
|
|
|
2021-05-04 03:39:43 -04:00
|
|
|
func (m *Moduli) Harden() (err error) {
|
|
|
|
|
2020-09-27 03:23:58 -04:00
|
|
|
var entries []Entry
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2021-07-03 23:01:58 -04:00
|
|
|
for _, e := range m.Groups {
|
2021-05-04 03:39:43 -04:00
|
|
|
|
|
|
|
e.Time = time.Now()
|
|
|
|
|
2020-09-27 03:23:58 -04:00
|
|
|
if e.Size >= minBits {
|
|
|
|
entries = append(entries, e)
|
|
|
|
}
|
|
|
|
}
|
2021-07-03 23:01:58 -04:00
|
|
|
m.Groups = entries
|
2021-05-04 03:39:43 -04:00
|
|
|
|
2021-07-03 23:01:58 -04:00
|
|
|
if len(m.Groups) < recMinMod {
|
2021-05-04 03:39:43 -04:00
|
|
|
err = errors.New("does not meet recommended minimum moduli")
|
|
|
|
return
|
2020-09-27 03:23:58 -04:00
|
|
|
}
|
|
|
|
|
2021-05-04 03:39:43 -04:00
|
|
|
// TODO: find way of testing/sieving primes
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|