Checking in some work. Keygen is done (with confirmation of data formatting output pending), and dh params is way easier than I thought it would be. We shouldn't need to regenerate dhparams. i *think*.
This commit is contained in:
parent
b80b823c02
commit
c22786204a
1
TODO
1
TODO
@ -19,6 +19,7 @@
|
|||||||
-- Check if haveged is running. If not and installed, start it.
|
-- Check if haveged is running. If not and installed, start it.
|
||||||
-- Generate moduli
|
-- Generate moduli
|
||||||
-- Render to /etc/ssh/moduli format
|
-- Render to /etc/ssh/moduli format
|
||||||
|
--- custom moduli marshaler/unmarshaler? (e.g. https://stackoverflow.com/a/50211222)
|
||||||
-- Write to dest
|
-- Write to dest
|
||||||
|
|
||||||
- Config
|
- Config
|
||||||
|
2
go.mod
2
go.mod
@ -1,4 +1,4 @@
|
|||||||
module git.square-r00t.net/sshsecure
|
module r00t2.io/sshsecure
|
||||||
|
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
|
@ -17,3 +17,55 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package moduli
|
package moduli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"r00t2.io/sshsecure/sharedconsts"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
const (
|
||||||
|
// Where to find an up-to-date copy of the upstream moduli and its SHA3-512 (NIST) checksum.
|
||||||
|
pregenURL string = "https://anongit.mindrot.org/openssh.git/tree/moduli"
|
||||||
|
// This is the best way I could think of to verify integrity, since the file itself doesn't have a signature or anything like that.
|
||||||
|
pregenCksum string = "106EDB19A936608D065D2E8E81F7BDE7" +
|
||||||
|
"434AF80EF81102E9440B99ACB98FBEF8" +
|
||||||
|
"CC2F4B6BFD76828337BDB1F2CF34D859" +
|
||||||
|
"045285DCE6B0DE7D7D93A9EE61F8CC96"
|
||||||
|
// The tag name to use for struct tags (marshal/unmarshaling)
|
||||||
|
parseTag string = "sshmoduli"
|
||||||
|
// The recommended minimum moduli to have available.
|
||||||
|
recMinMod int = 400
|
||||||
|
// The
|
||||||
|
)
|
||||||
|
|
||||||
|
// The header line on the /etc/ssh/moduli file.
|
||||||
|
var header = string(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"# %v\n"+
|
||||||
|
"# Time Type Tests Tries Size Generator Modulus\n", sharedconsts.IDCmnt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// For parsing/rendering /etc/ssh/moduli
|
||||||
|
const (
|
||||||
|
// Golang has no strftime formatting codes. It operates on *display of a specific time*.
|
||||||
|
// What a dumb language.
|
||||||
|
timeFormat string = "20060102150405" // %Y%m%d%H%M%S
|
||||||
|
)
|
||||||
|
|
||||||
|
// For validation
|
||||||
|
var (
|
||||||
|
validTypes = []uint8{
|
||||||
|
0, // Unknown, not tested
|
||||||
|
2, // "Safe" prime; (p-1)/2 is also prime.
|
||||||
|
4, // Sophie Germain; 2p+1 is also prime.
|
||||||
|
}
|
||||||
|
validTests = []byte{
|
||||||
|
0x00, // Not tested.
|
||||||
|
0x01, // Composite number - not prime.
|
||||||
|
0x02, // Sieve of Eratosthenes.
|
||||||
|
0x04, // Probabilistic Miller-Rabin primality tests.
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -17,3 +17,44 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package moduli
|
package moduli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPregen() ([]byte, error) {
|
||||||
|
// get the pregenerated moduli
|
||||||
|
resp, err := http.Get(pregenURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, errors.New(fmt.Sprintf("returned status code %v: %v", resp.StatusCode, resp.Status))
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
b := make([]byte, resp.ContentLength)
|
||||||
|
if _, err = resp.Body.Read(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// and compare the SHA3-512 (NIST) checksum.
|
||||||
|
s := sha3.New512()
|
||||||
|
if _, err = s.Write(b); err != nil {
|
||||||
|
// TODO: return nil instead of b?
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
goodCksum, err := hex.DecodeString(pregenCksum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We just compare the bytestrings.
|
||||||
|
if bytes.Compare(s.Sum(nil), goodCksum) != 0 {
|
||||||
|
return nil, errors.New("checksums do not match")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
119
moduli/parser.go
Normal file
119
moduli/parser.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
// TODO: remember to write newline at end
|
||||||
|
func (m *Moduli) Marshal() ([]byte, 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
|
||||||
|
} else {
|
||||||
|
b.Write(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalEntry is used to parse a specific DH entry into the moduli.
|
||||||
|
func (m *ModuliEntry) marshalEntry() ([]byte, 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),
|
||||||
|
string(m.Generator),
|
||||||
|
mod,
|
||||||
|
)
|
||||||
|
return []byte(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal writes the Moduli format into m from the /etc/ssh/moduli format in data.
|
||||||
|
func Unmarshal(data []byte, m Moduli) error {
|
||||||
|
var lines []string
|
||||||
|
var entries []ModuliEntry
|
||||||
|
lines = strings.Split(string(data), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
e := ModuliEntry{}
|
||||||
|
if reSkipLine.MatchString(line) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
l := strings.Fields(line)
|
||||||
|
if err := unmarshalEntry(l, e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
entries = append(entries, e)
|
||||||
|
}
|
||||||
|
m.Params = entries
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalEntry(line []string, m ModuliEntry) error {
|
||||||
|
if len(line) != 7 {
|
||||||
|
return errors.New("field count mismatch")
|
||||||
|
}
|
||||||
|
if m.Time, err = time.Parse(timeFormat, line[0]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 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.
|
||||||
|
// Type, Tests, Trials, Size, Generator
|
||||||
|
conv := [5]uint8{}
|
||||||
|
for idx := 1; idx <= 5; idx++ {
|
||||||
|
v := line[idx]
|
||||||
|
newv, err := strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conv[idx-1] = uint8(newv)
|
||||||
|
}
|
||||||
|
m.Type = conv[0]
|
||||||
|
m.Tests = conv[1]
|
||||||
|
m.Trials = conv[2]
|
||||||
|
m.Size = conv[3]
|
||||||
|
m.Generator = conv[4]
|
||||||
|
// And the modulus convert to big.Int.
|
||||||
|
modb, err := hex.DecodeString(line[6])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Modulus = big.Int{}
|
||||||
|
m.Modulus.SetBytes(modb)
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,4 +4,5 @@ https://access.redhat.com/blogs/766093/posts/2177481
|
|||||||
https://security.stackexchange.com/a/113058
|
https://security.stackexchange.com/a/113058
|
||||||
https://github.com/Luzifer/go-dhparam
|
https://github.com/Luzifer/go-dhparam
|
||||||
https://github.com/mimoo/test_DHparams
|
https://github.com/mimoo/test_DHparams
|
||||||
https://github.com/hyperreality/cryptopals-2/blob/517c1907b2041e6f7ef18930eca2aa3a24fb73d8/dh.go
|
https://github.com/hyperreality/cryptopals-2/blob/517c1907b2041e6f7ef18930eca2aa3a24fb73d8/dh.go
|
||||||
|
https://sosedoff.com/2016/07/16/golang-struct-tags.html
|
@ -17,3 +17,62 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package moduli
|
package moduli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Moduli contains all data needed for generated /etc/ssh/moduli. of ModuliEntry entries.
|
||||||
|
type Moduli struct {
|
||||||
|
Header string
|
||||||
|
Params []ModuliEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moduli is a struct reflecting the format of a single /etc/ssh/moduli entry. See moduli(5) for details.
|
||||||
|
type ModuliEntry struct {
|
||||||
|
Time time.Time // YYYYMMDDHHSS
|
||||||
|
/*
|
||||||
|
// man 5 moduli:
|
||||||
|
Decimal number specifying the internal structure of the prime modulus. Supported types are:
|
||||||
|
0 Unknown, not tested.
|
||||||
|
2 "Safe" prime; (p-1)/2 is also prime.
|
||||||
|
4 Sophie Germain; 2p+1 is also prime.
|
||||||
|
Moduli candidates initially produced by ssh-keygen(1) are Sophie Germain primes (type 4).
|
||||||
|
Further primality testing with ssh-keygen(1) produces safe prime moduli (type 2) that are ready for use in sshd(8).
|
||||||
|
Other types are not used by OpenSSH.
|
||||||
|
*/
|
||||||
|
Type uint8
|
||||||
|
/*
|
||||||
|
// man 5 moduli:
|
||||||
|
Decimal number indicating the type of primality tests that the number has been
|
||||||
|
subjected to represented as a bitmask of the following values:
|
||||||
|
0x00 Not tested.
|
||||||
|
0x01 Composite number – not prime.
|
||||||
|
0x02 Sieve of Eratosthenes.
|
||||||
|
0x04 Probabilistic Miller-Rabin primality tests.
|
||||||
|
The ssh-keygen(1) moduli candidate generation uses the Sieve of Eratosthenes (flag 0x02).
|
||||||
|
Subsequent ssh-keygen(1) primality tests are Miller-Rabin tests (flag 0x04).
|
||||||
|
*/
|
||||||
|
Tests uint8
|
||||||
|
/*
|
||||||
|
// man 5 moduli:
|
||||||
|
Decimal number indicating the number of primality trials that have been performed on the modulus.
|
||||||
|
*/
|
||||||
|
Trials uint8
|
||||||
|
/*
|
||||||
|
// man 5 moduli:
|
||||||
|
Decimal number indicating the size of the prime in bits.
|
||||||
|
*/
|
||||||
|
Size uint8
|
||||||
|
/*
|
||||||
|
// man 5 moduli:
|
||||||
|
The recommended generator for use with this modulus (hexadecimal).
|
||||||
|
*/
|
||||||
|
Generator uint8
|
||||||
|
/*
|
||||||
|
// man 5 moduli:
|
||||||
|
The modulus itself in hexadecimal.
|
||||||
|
*/
|
||||||
|
Modulus big.Int
|
||||||
|
}
|
||||||
|
13
sharedconsts/const.go
Normal file
13
sharedconsts/const.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package sharedconsts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// "Meta". Used for comment strings, etc.
|
||||||
|
const (
|
||||||
|
projName = "SSHSecure"
|
||||||
|
projUrl = "https://git.square-r00t.net/SSHSecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
var IDCmnt = string(fmt.Sprintf("Autogenerated by %v (%v)", projName, projUrl))
|
@ -23,10 +23,6 @@ const (
|
|||||||
KeyV1Magic string = "openssh-key-v1"
|
KeyV1Magic string = "openssh-key-v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// "Meta". Used for comment strings, etc.
|
|
||||||
|
|
||||||
const projUrl = "https://git.square-r00t.net/SSHSecure"
|
|
||||||
|
|
||||||
// Defaults.
|
// Defaults.
|
||||||
const (
|
const (
|
||||||
defCipher string = CipherAes256Ctr
|
defCipher string = CipherAes256Ctr
|
||||||
|
142
sshkeys/func.go
142
sshkeys/func.go
@ -19,10 +19,12 @@
|
|||||||
package sshkeys
|
package sshkeys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`bytes`
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"r00t2.io/sshsecure/sharedconsts"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (k *EncryptedSSHKeyV1) validate() error {
|
func (k *EncryptedSSHKeyV1) validate() error {
|
||||||
@ -86,7 +88,7 @@ func (k *EncryptedSSHKeyV1) Generate(force bool) error {
|
|||||||
// Currently, OpenSSH has an option for multiple private keys. However, it is hardcoded to 1.
|
// Currently, OpenSSH has an option for multiple private keys. However, it is hardcoded to 1.
|
||||||
// If multiple key support is added in the future, will need to re-tool how I do this, perhaps, in the future. TODO.
|
// If multiple key support is added in the future, will need to re-tool how I do this, perhaps, in the future. TODO.
|
||||||
pk := SSHPrivKey{
|
pk := SSHPrivKey{
|
||||||
Comment: fmt.Sprintf("Autogenerated via SSHSecure (%v)", projUrl),
|
Comment: sharedconsts.IDCmnt,
|
||||||
}
|
}
|
||||||
pk.Checksum = make([]byte, 4)
|
pk.Checksum = make([]byte, 4)
|
||||||
if _, err := rand.Read(pk.Checksum); err != nil {
|
if _, err := rand.Read(pk.Checksum); err != nil {
|
||||||
@ -181,7 +183,7 @@ func (k *EncryptedSSHKeyV1) buildKeybuf() error {
|
|||||||
// Since this is encrypted, the private key blobs are encrypted.
|
// Since this is encrypted, the private key blobs are encrypted.
|
||||||
// OpenSSH keys currently only support 1 keypair but support more *in theory*. This *seems* to be how they plan on doing it.
|
// OpenSSH keys currently only support 1 keypair but support more *in theory*. This *seems* to be how they plan on doing it.
|
||||||
// But boy, is it a pain. So much wasted RAM and CPU cycles. They should use terminating byte sequences IMHO but whatever.
|
// But boy, is it a pain. So much wasted RAM and CPU cycles. They should use terminating byte sequences IMHO but whatever.
|
||||||
for _, i := range k.Keys { // 4.0.0 and 4.0.1
|
for _, i := range k.Keys { // 4.0.0 and 4.0.1
|
||||||
switch k.CipherName {
|
switch k.CipherName {
|
||||||
case CipherAes256Ctr:
|
case CipherAes256Ctr:
|
||||||
i.BlockSize = k.Crypt.Cipher.BlockSize()
|
i.BlockSize = k.Crypt.Cipher.BlockSize()
|
||||||
@ -209,7 +211,7 @@ func (k *SSHKeyV1) buildKeybuf() error {
|
|||||||
// Add the keypairs.
|
// Add the keypairs.
|
||||||
// OpenSSH keys currently only support 1 keypair but support more *in theory*. This *seems* to be how they plan on doing it.
|
// OpenSSH keys currently only support 1 keypair but support more *in theory*. This *seems* to be how they plan on doing it.
|
||||||
// But boy, is it a pain. So much wasted RAM and CPU cycles. They should use terminating byte sequences IMHO but whatever.
|
// But boy, is it a pain. So much wasted RAM and CPU cycles. They should use terminating byte sequences IMHO but whatever.
|
||||||
for _, i := range k.Keys { // 4.0.0 and 4.0.1
|
for _, i := range k.Keys { // 4.0.0 and 4.0.1
|
||||||
i.BlockSize = 8
|
i.BlockSize = 8
|
||||||
kbPtr, err := i.keyBlob(nil, false)
|
kbPtr, err := i.keyBlob(nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -224,19 +226,19 @@ func (k *SSHKeyV1) addHeader() error {
|
|||||||
// TODO: error handling for each <buf>.Write()?
|
// TODO: error handling for each <buf>.Write()?
|
||||||
// First we need to do some prep for the plaintext header.
|
// First we need to do some prep for the plaintext header.
|
||||||
var kdfOptsBytes []byte
|
var kdfOptsBytes []byte
|
||||||
kdfOptsBytes = k.getKdfOptBytes() // 3.0.0
|
kdfOptsBytes = k.getKdfOptBytes() // 3.0.0
|
||||||
cipherBytes := []byte(k.CipherName) // 1.0
|
cipherBytes := []byte(k.CipherName) // 1.0
|
||||||
kdf := []byte(k.KDFName) // 2.0.0
|
kdf := []byte(k.KDFName) // 2.0.0
|
||||||
// This is just cast to an array for visual readability.
|
// This is just cast to an array for visual readability.
|
||||||
commonHeader := [][]byte{
|
commonHeader := [][]byte{
|
||||||
[]byte(KeyV1Magic + "\x00"), // 0
|
[]byte(KeyV1Magic + "\x00"), // 0
|
||||||
getBytelenByteArr(cipherBytes), // 1.0
|
getBytelenByteArr(cipherBytes), // 1.0
|
||||||
cipherBytes, // 1.0.0
|
cipherBytes, // 1.0.0
|
||||||
getBytelenByteArr(kdf), // 2.0
|
getBytelenByteArr(kdf), // 2.0
|
||||||
kdf, // 2.0.0
|
kdf, // 2.0.0
|
||||||
getBytelenByteArr(kdfOptsBytes), // 3.0
|
getBytelenByteArr(kdfOptsBytes), // 3.0
|
||||||
kdfOptsBytes, // 3.0.0
|
kdfOptsBytes, // 3.0.0
|
||||||
getByteInt(len(k.Keys)), // 4.0
|
getByteInt(len(k.Keys)), // 4.0
|
||||||
}
|
}
|
||||||
for _, v := range commonHeader {
|
for _, v := range commonHeader {
|
||||||
if _, err := k.Buffer.Write(v); err != nil {
|
if _, err := k.Buffer.Write(v); err != nil {
|
||||||
@ -247,28 +249,28 @@ func (k *SSHKeyV1) addHeader() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k *EncryptedSSHKeyV1) getKdfOptBytes() []byte {
|
func (k *EncryptedSSHKeyV1) getKdfOptBytes() []byte {
|
||||||
var kdfOptsBytes []byte // 3.0.0
|
var kdfOptsBytes []byte // 3.0.0
|
||||||
// This is *probably* more efficient than using a buffer just for these bytes.
|
// This is *probably* more efficient than using a buffer just for these bytes.
|
||||||
kdfOptsBytes = append(kdfOptsBytes, byte(len(k.KDFOpts.Salt))) // 3.0.0.0
|
kdfOptsBytes = append(kdfOptsBytes, byte(len(k.KDFOpts.Salt))) // 3.0.0.0
|
||||||
kdfOptsBytes = append(kdfOptsBytes, k.KDFOpts.Salt...) // 3.0.0.0.0
|
kdfOptsBytes = append(kdfOptsBytes, k.KDFOpts.Salt...) // 3.0.0.0.0
|
||||||
kdfOptsBytes = append(kdfOptsBytes, byte(k.KDFOpts.Rounds)) // 3.0.0.1
|
kdfOptsBytes = append(kdfOptsBytes, byte(k.KDFOpts.Rounds)) // 3.0.0.1
|
||||||
return kdfOptsBytes
|
return kdfOptsBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *SSHKeyV1) getKdfOptBytes() []byte {
|
func (k *SSHKeyV1) getKdfOptBytes() []byte {
|
||||||
var kdfOptsBytes []byte // 3.0.0
|
var kdfOptsBytes []byte // 3.0.0
|
||||||
// No-op; unencrypted keys' KDFOpts are encapsulated by a single null byte (which the caller implements).
|
// No-op; unencrypted keys' KDFOpts are encapsulated by a single null byte (which the caller implements).
|
||||||
return kdfOptsBytes
|
return kdfOptsBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) {
|
func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) {
|
||||||
// TODO: error handling for each <buf>.Write()?
|
// TODO: error handling for each <buf>.Write()?
|
||||||
var keypairBytes bytes.Buffer // (4.0's children)
|
var keypairBytes bytes.Buffer // (4.0's children)
|
||||||
var pubkeyBytes bytes.Buffer // 4.0.0 children (4.0.0 itself is handled before writing to keypairBytes)
|
var pubkeyBytes bytes.Buffer // 4.0.0 children (4.0.0 itself is handled before writing to keypairBytes)
|
||||||
var privkeyBytes bytes.Buffer // 4.0.1 (and children)
|
var privkeyBytes bytes.Buffer // 4.0.1 (and children)
|
||||||
pubkeyName := []byte(pk.PublicKey.KeyType) // (4.0.0.0.0, cast to var because I'm lazy)
|
pubkeyName := []byte(pk.PublicKey.KeyType) // (4.0.0.0.0, cast to var because I'm lazy)
|
||||||
pubkeyBytes.Write(getBytelenByteArr(pubkeyName)) // 4.0.0.0
|
pubkeyBytes.Write(getBytelenByteArr(pubkeyName)) // 4.0.0.0
|
||||||
pubkeyBytes.Write(pubkeyName) // 4.0.0.0.0
|
pubkeyBytes.Write(pubkeyName) // 4.0.0.0.0
|
||||||
// TODO: Optimize?
|
// TODO: Optimize?
|
||||||
/*
|
/*
|
||||||
THE PUBLIC KEY
|
THE PUBLIC KEY
|
||||||
@ -280,17 +282,17 @@ func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) {
|
|||||||
pubkeyBytes.Write(pk.PublicKey.Key.([]byte)) // 4.0.0.1.0
|
pubkeyBytes.Write(pk.PublicKey.Key.([]byte)) // 4.0.0.1.0
|
||||||
case KeyRsa:
|
case KeyRsa:
|
||||||
// How messy.
|
// How messy.
|
||||||
var en bytes.Buffer // 4.0.0.1 and 4.0.0.2
|
var en bytes.Buffer // 4.0.0.1 and 4.0.0.2
|
||||||
// TODO: does e need getByteInt()?
|
// TODO: does e need getByteInt()?
|
||||||
e := pk.PublicKey.Key.E.Bytes() // 4.0.0.1.0
|
e := pk.PublicKey.Key.E.Bytes() // 4.0.0.1.0
|
||||||
// TODO: does n need nullbyte prefix?
|
// TODO: does n need nullbyte prefix?
|
||||||
n := pk.PublicKey.Key.N.Bytes() // 4.0.0.2.0
|
n := pk.PublicKey.Key.N.Bytes() // 4.0.0.2.0
|
||||||
en.Write(getBytelenByteArr(e)) // 4.0.0.1
|
en.Write(getBytelenByteArr(e)) // 4.0.0.1
|
||||||
en.Write(e) // 4.0.0.1.0
|
en.Write(e) // 4.0.0.1.0
|
||||||
en.Write(getBytelenByteArr(n)) // 4.0.0.2
|
en.Write(getBytelenByteArr(n)) // 4.0.0.2
|
||||||
en.Write(n) // 4.0.0.2.0
|
en.Write(n) // 4.0.0.2.0
|
||||||
pubkeyBytes.Write(getBytelenByteArr(en.Bytes())) // 4.0.0
|
pubkeyBytes.Write(getBytelenByteArr(en.Bytes())) // 4.0.0
|
||||||
if _, err := en.WriteTo(&pubkeyBytes); err != nil { // (4.0.0 children)
|
if _, err := en.WriteTo(&pubkeyBytes); err != nil { // (4.0.0 children)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,7 +302,7 @@ func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) {
|
|||||||
*/
|
*/
|
||||||
// First we need two checksums.
|
// First we need two checksums.
|
||||||
for i := 1; i <= 2; i++ {
|
for i := 1; i <= 2; i++ {
|
||||||
privkeyBytes.Write(pk.Checksum) // 4.0.1.0 and 4.0.1.1
|
privkeyBytes.Write(pk.Checksum) // 4.0.1.0 and 4.0.1.1
|
||||||
}
|
}
|
||||||
// And then add the public keys. Yes, the public keys are included in the private keys.
|
// And then add the public keys. Yes, the public keys are included in the private keys.
|
||||||
privkeyBytes.Write(getBytelenByteArr(pubkeyName)) // 4.0.1.2.0
|
privkeyBytes.Write(getBytelenByteArr(pubkeyName)) // 4.0.1.2.0
|
||||||
@ -308,57 +310,57 @@ func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) {
|
|||||||
switch pk.PublicKey.KeyType {
|
switch pk.PublicKey.KeyType {
|
||||||
case KeyEd25519:
|
case KeyEd25519:
|
||||||
// This is easy.
|
// This is easy.
|
||||||
privkeyBytes.Write(pubkeyBytes.Bytes()) // 4.0.1.2.1
|
privkeyBytes.Write(pubkeyBytes.Bytes()) // 4.0.1.2.1
|
||||||
if _, err := pubkeyBytes.WriteTo(&privkeyBytes); err != nil { // 4.0.1.2.1.0
|
if _, err := pubkeyBytes.WriteTo(&privkeyBytes); err != nil { // 4.0.1.2.1.0
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case KeyRsa:
|
case KeyRsa:
|
||||||
// This is not. We more or less have to do the same thing as the public key, BUT with e and n flipped. Gorram it.
|
// This is not. We more or less have to do the same thing as the public key, BUT with e and n flipped. Gorram it.
|
||||||
var ne bytes.Buffer // (4.0.1.2 children)
|
var ne bytes.Buffer // (4.0.1.2 children)
|
||||||
// TODO: does n need nullbyte prefix?
|
// TODO: does n need nullbyte prefix?
|
||||||
n := pk.PublicKey.Key.N.Bytes() // 4.0.1.2.1.0
|
n := pk.PublicKey.Key.N.Bytes() // 4.0.1.2.1.0
|
||||||
// TODO: does e need getByteInt()?
|
// TODO: does e need getByteInt()?
|
||||||
e := pk.PublicKey.Key.E.Bytes() // 4.0.1.2.2.0
|
e := pk.PublicKey.Key.E.Bytes() // 4.0.1.2.2.0
|
||||||
ne.Write(getBytelenByteArr(n)) // 4.0.1.2.1
|
ne.Write(getBytelenByteArr(n)) // 4.0.1.2.1
|
||||||
ne.Write(n) // 4.0.1.2.1.0
|
ne.Write(n) // 4.0.1.2.1.0
|
||||||
ne.Write(getBytelenByteArr(e)) // 4.0.1.2.2
|
ne.Write(getBytelenByteArr(e)) // 4.0.1.2.2
|
||||||
ne.Write(e) // 4.0.1.2.2.0
|
ne.Write(e) // 4.0.1.2.2.0
|
||||||
if _, err := ne.WriteTo(&privkeyBytes); err != nil { // (4.0.1.2 children)
|
if _, err := ne.WriteTo(&privkeyBytes); err != nil { // (4.0.1.2 children)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// And then we add the *actual* private keys.
|
// And then we add the *actual* private keys.
|
||||||
switch pk.PublicKey.KeyType {
|
switch pk.PublicKey.KeyType {
|
||||||
case KeyEd25519:
|
case KeyEd25519:
|
||||||
privkeyBytes.Write(getBytelenByteArr(pk.KeyAlt)) // 4.0.1.3
|
privkeyBytes.Write(getBytelenByteArr(pk.KeyAlt)) // 4.0.1.3
|
||||||
privkeyBytes.Write(pk.KeyAlt) // 4.0.1.3.0
|
privkeyBytes.Write(pk.KeyAlt) // 4.0.1.3.0
|
||||||
case KeyRsa:
|
case KeyRsa:
|
||||||
var dcpq bytes.Buffer // 4.0.1.3 to 4.0.1.6
|
var dcpq bytes.Buffer // 4.0.1.3 to 4.0.1.6
|
||||||
d := pk.Key.D.Bytes() // 4.0.1.3.0
|
d := pk.Key.D.Bytes() // 4.0.1.3.0
|
||||||
crt := pk.Key.Precomputed.Qinv.Bytes() // 4.0.1.4.0
|
crt := pk.Key.Precomputed.Qinv.Bytes() // 4.0.1.4.0
|
||||||
// TODO: does p need nullbyte prefix?
|
// TODO: does p need nullbyte prefix?
|
||||||
p := pk.Key.Primes[0].Bytes() // 4.0.1.5.0
|
p := pk.Key.Primes[0].Bytes() // 4.0.1.5.0
|
||||||
// TODO: does q need nullbyte prefix?
|
// TODO: does q need nullbyte prefix?
|
||||||
q := pk.Key.Primes[1].Bytes() // 4.0.1.6.0
|
q := pk.Key.Primes[1].Bytes() // 4.0.1.6.0
|
||||||
dcpq.Write(getBytelenByteArr(d)) // 4.0.1.3
|
dcpq.Write(getBytelenByteArr(d)) // 4.0.1.3
|
||||||
dcpq.Write(d) // 4.0.1.3.0
|
dcpq.Write(d) // 4.0.1.3.0
|
||||||
dcpq.Write(getBytelenByteArr(crt)) // 4.0.1.4
|
dcpq.Write(getBytelenByteArr(crt)) // 4.0.1.4
|
||||||
dcpq.Write(crt) // 4.0.1.4.0
|
dcpq.Write(crt) // 4.0.1.4.0
|
||||||
dcpq.Write(getBytelenByteArr(p)) // 4.0.1.5
|
dcpq.Write(getBytelenByteArr(p)) // 4.0.1.5
|
||||||
dcpq.Write(p) // 4.0.1.5.0
|
dcpq.Write(p) // 4.0.1.5.0
|
||||||
dcpq.Write(getBytelenByteArr(q)) // 4.0.1.6
|
dcpq.Write(getBytelenByteArr(q)) // 4.0.1.6
|
||||||
dcpq.Write(q) // 4.0.1.6.0
|
dcpq.Write(q) // 4.0.1.6.0
|
||||||
if _, err := dcpq.WriteTo(&privkeyBytes); err != nil { // 4.0.1.3 to 4.0.1.6
|
if _, err := dcpq.WriteTo(&privkeyBytes); err != nil { // 4.0.1.3 to 4.0.1.6
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add the comment.
|
// Add the comment.
|
||||||
privkeyBytes.Write(getBytelenByteArr([]byte(pk.Comment))) // 4.0.1.4 (ED25519), 4.0.1.7 (RSA)
|
privkeyBytes.Write(getBytelenByteArr([]byte(pk.Comment))) // 4.0.1.4 (ED25519), 4.0.1.7 (RSA)
|
||||||
privkeyBytes.Write([]byte(pk.Comment)) // 4.0.1.4.0 (ED25519), 4.0.1.7.0 (RSA)
|
privkeyBytes.Write([]byte(pk.Comment)) // 4.0.1.4.0 (ED25519), 4.0.1.7.0 (RSA)
|
||||||
// Add padding
|
// Add padding
|
||||||
pad := 0
|
pad := 0
|
||||||
n := 0
|
n := 0
|
||||||
for len(privkeyBytes.Bytes()) % pk.BlockSize != 0 { // 4.0.1.5 (ED25519), 4.0.1.8 (RSA)
|
for len(privkeyBytes.Bytes())%pk.BlockSize != 0 { // 4.0.1.5 (ED25519), 4.0.1.8 (RSA)
|
||||||
n++
|
n++
|
||||||
pad = n & pk.BlockSize
|
pad = n & pk.BlockSize
|
||||||
privkeyBytes.Write(getSingleByteInt(pad))
|
privkeyBytes.Write(getSingleByteInt(pad))
|
||||||
@ -377,15 +379,15 @@ func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) {
|
|||||||
encBytes = []byte{}
|
encBytes = []byte{}
|
||||||
}
|
}
|
||||||
// Get the respective lengths and add child buffers to buffer.
|
// Get the respective lengths and add child buffers to buffer.
|
||||||
keypairBytes.Write(getByteInt(len(pubkeyBytes.Bytes()))) // 4.0.0
|
keypairBytes.Write(getByteInt(len(pubkeyBytes.Bytes()))) // 4.0.0
|
||||||
if _, err := pubkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.0 children)
|
if _, err := pubkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.0 children)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
keypairBytes.Write(getByteInt(len(privkeyBytes.Bytes()))) // 4.0.1
|
keypairBytes.Write(getByteInt(len(privkeyBytes.Bytes()))) // 4.0.1
|
||||||
if _, err := privkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.1 children)
|
if _, err := privkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.1 children)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Done!
|
// Done!
|
||||||
kpSlice := keypairBytes.Bytes()
|
kpSlice := keypairBytes.Bytes()
|
||||||
return &kpSlice, nil
|
return &kpSlice, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user