diff --git a/TODO b/TODO index 7c8e358..45c53f9 100644 --- a/TODO +++ b/TODO @@ -19,6 +19,7 @@ -- Check if haveged is running. If not and installed, start it. -- Generate moduli -- Render to /etc/ssh/moduli format +--- custom moduli marshaler/unmarshaler? (e.g. https://stackoverflow.com/a/50211222) -- Write to dest - Config diff --git a/go.mod b/go.mod index 6da26fe..2549b8b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.square-r00t.net/sshsecure +module r00t2.io/sshsecure go 1.15 diff --git a/moduli/const.go b/moduli/const.go index df5bbf7..bf5cec0 100644 --- a/moduli/const.go +++ b/moduli/const.go @@ -17,3 +17,55 @@ */ 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. + } +) diff --git a/moduli/func.go b/moduli/func.go index df5bbf7..e946058 100644 --- a/moduli/func.go +++ b/moduli/func.go @@ -17,3 +17,44 @@ */ 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 +} diff --git a/moduli/parser.go b/moduli/parser.go new file mode 100644 index 0000000..d503bee --- /dev/null +++ b/moduli/parser.go @@ -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 . +*/ + +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 +} diff --git a/moduli/ref/sources b/moduli/ref/sources index 02a9624..ba2788a 100644 --- a/moduli/ref/sources +++ b/moduli/ref/sources @@ -4,4 +4,5 @@ https://access.redhat.com/blogs/766093/posts/2177481 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 \ No newline at end of file +https://github.com/hyperreality/cryptopals-2/blob/517c1907b2041e6f7ef18930eca2aa3a24fb73d8/dh.go +https://sosedoff.com/2016/07/16/golang-struct-tags.html \ No newline at end of file diff --git a/moduli/struct.go b/moduli/struct.go index df5bbf7..ccff1f2 100644 --- a/moduli/struct.go +++ b/moduli/struct.go @@ -17,3 +17,62 @@ */ 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 +} diff --git a/sharedconsts/const.go b/sharedconsts/const.go new file mode 100644 index 0000000..854ab83 --- /dev/null +++ b/sharedconsts/const.go @@ -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)) diff --git a/sshkeys/const.go b/sshkeys/const.go index 8eaddd2..77df225 100644 --- a/sshkeys/const.go +++ b/sshkeys/const.go @@ -23,10 +23,6 @@ const ( KeyV1Magic string = "openssh-key-v1" ) -// "Meta". Used for comment strings, etc. - -const projUrl = "https://git.square-r00t.net/SSHSecure" - // Defaults. const ( defCipher string = CipherAes256Ctr diff --git a/sshkeys/func.go b/sshkeys/func.go index 8ef71ca..305fdfe 100644 --- a/sshkeys/func.go +++ b/sshkeys/func.go @@ -19,10 +19,12 @@ package sshkeys import ( - `bytes` + "bytes" "crypto/rand" "errors" "fmt" + + "r00t2.io/sshsecure/sharedconsts" ) 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. // 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{ - Comment: fmt.Sprintf("Autogenerated via SSHSecure (%v)", projUrl), + Comment: sharedconsts.IDCmnt, } pk.Checksum = make([]byte, 4) 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. // 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. - 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 { case CipherAes256Ctr: i.BlockSize = k.Crypt.Cipher.BlockSize() @@ -209,7 +211,7 @@ func (k *SSHKeyV1) buildKeybuf() error { // 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. // 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 kbPtr, err := i.keyBlob(nil, false) if err != nil { @@ -224,19 +226,19 @@ func (k *SSHKeyV1) addHeader() error { // TODO: error handling for each .Write()? // First we need to do some prep for the plaintext header. var kdfOptsBytes []byte - kdfOptsBytes = k.getKdfOptBytes() // 3.0.0 - cipherBytes := []byte(k.CipherName) // 1.0 - kdf := []byte(k.KDFName) // 2.0.0 + kdfOptsBytes = k.getKdfOptBytes() // 3.0.0 + cipherBytes := []byte(k.CipherName) // 1.0 + kdf := []byte(k.KDFName) // 2.0.0 // This is just cast to an array for visual readability. commonHeader := [][]byte{ - []byte(KeyV1Magic + "\x00"), // 0 - getBytelenByteArr(cipherBytes), // 1.0 - cipherBytes, // 1.0.0 - getBytelenByteArr(kdf), // 2.0 - kdf, // 2.0.0 - getBytelenByteArr(kdfOptsBytes), // 3.0 - kdfOptsBytes, // 3.0.0 - getByteInt(len(k.Keys)), // 4.0 + []byte(KeyV1Magic + "\x00"), // 0 + getBytelenByteArr(cipherBytes), // 1.0 + cipherBytes, // 1.0.0 + getBytelenByteArr(kdf), // 2.0 + kdf, // 2.0.0 + getBytelenByteArr(kdfOptsBytes), // 3.0 + kdfOptsBytes, // 3.0.0 + getByteInt(len(k.Keys)), // 4.0 } for _, v := range commonHeader { if _, err := k.Buffer.Write(v); err != nil { @@ -247,28 +249,28 @@ func (k *SSHKeyV1) addHeader() error { } 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. - 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, byte(k.KDFOpts.Rounds)) // 3.0.0.1 + 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, byte(k.KDFOpts.Rounds)) // 3.0.0.1 return kdfOptsBytes } 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). return kdfOptsBytes } func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) { // TODO: error handling for each .Write()? - 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 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) - pubkeyBytes.Write(getBytelenByteArr(pubkeyName)) // 4.0.0.0 - pubkeyBytes.Write(pubkeyName) // 4.0.0.0.0 + 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 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) + pubkeyBytes.Write(getBytelenByteArr(pubkeyName)) // 4.0.0.0 + pubkeyBytes.Write(pubkeyName) // 4.0.0.0.0 // TODO: Optimize? /* 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 case KeyRsa: // 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()? - 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? - n := pk.PublicKey.Key.N.Bytes() // 4.0.0.2.0 - en.Write(getBytelenByteArr(e)) // 4.0.0.1 - en.Write(e) // 4.0.0.1.0 - en.Write(getBytelenByteArr(n)) // 4.0.0.2 - en.Write(n) // 4.0.0.2.0 - pubkeyBytes.Write(getBytelenByteArr(en.Bytes())) // 4.0.0 - if _, err := en.WriteTo(&pubkeyBytes); err != nil { // (4.0.0 children) + n := pk.PublicKey.Key.N.Bytes() // 4.0.0.2.0 + en.Write(getBytelenByteArr(e)) // 4.0.0.1 + en.Write(e) // 4.0.0.1.0 + en.Write(getBytelenByteArr(n)) // 4.0.0.2 + en.Write(n) // 4.0.0.2.0 + pubkeyBytes.Write(getBytelenByteArr(en.Bytes())) // 4.0.0 + if _, err := en.WriteTo(&pubkeyBytes); err != nil { // (4.0.0 children) return nil, err } } @@ -300,7 +302,7 @@ func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) { */ // First we need two checksums. 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. 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 { case KeyEd25519: // This is easy. - privkeyBytes.Write(pubkeyBytes.Bytes()) // 4.0.1.2.1 - if _, err := pubkeyBytes.WriteTo(&privkeyBytes); err != nil { // 4.0.1.2.1.0 + privkeyBytes.Write(pubkeyBytes.Bytes()) // 4.0.1.2.1 + if _, err := pubkeyBytes.WriteTo(&privkeyBytes); err != nil { // 4.0.1.2.1.0 return nil, err } 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. - var ne bytes.Buffer // (4.0.1.2 children) + var ne bytes.Buffer // (4.0.1.2 children) // 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()? - e := pk.PublicKey.Key.E.Bytes() // 4.0.1.2.2.0 - ne.Write(getBytelenByteArr(n)) // 4.0.1.2.1 - ne.Write(n) // 4.0.1.2.1.0 - ne.Write(getBytelenByteArr(e)) // 4.0.1.2.2 - ne.Write(e) // 4.0.1.2.2.0 - if _, err := ne.WriteTo(&privkeyBytes); err != nil { // (4.0.1.2 children) + e := pk.PublicKey.Key.E.Bytes() // 4.0.1.2.2.0 + ne.Write(getBytelenByteArr(n)) // 4.0.1.2.1 + ne.Write(n) // 4.0.1.2.1.0 + ne.Write(getBytelenByteArr(e)) // 4.0.1.2.2 + ne.Write(e) // 4.0.1.2.2.0 + if _, err := ne.WriteTo(&privkeyBytes); err != nil { // (4.0.1.2 children) return nil, err } } // And then we add the *actual* private keys. switch pk.PublicKey.KeyType { case KeyEd25519: - privkeyBytes.Write(getBytelenByteArr(pk.KeyAlt)) // 4.0.1.3 - privkeyBytes.Write(pk.KeyAlt) // 4.0.1.3.0 + privkeyBytes.Write(getBytelenByteArr(pk.KeyAlt)) // 4.0.1.3 + privkeyBytes.Write(pk.KeyAlt) // 4.0.1.3.0 case KeyRsa: - var dcpq bytes.Buffer // 4.0.1.3 to 4.0.1.6 - d := pk.Key.D.Bytes() // 4.0.1.3.0 - crt := pk.Key.Precomputed.Qinv.Bytes() // 4.0.1.4.0 + var dcpq bytes.Buffer // 4.0.1.3 to 4.0.1.6 + d := pk.Key.D.Bytes() // 4.0.1.3.0 + crt := pk.Key.Precomputed.Qinv.Bytes() // 4.0.1.4.0 // 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? - q := pk.Key.Primes[1].Bytes() // 4.0.1.6.0 - dcpq.Write(getBytelenByteArr(d)) // 4.0.1.3 - dcpq.Write(d) // 4.0.1.3.0 - dcpq.Write(getBytelenByteArr(crt)) // 4.0.1.4 - dcpq.Write(crt) // 4.0.1.4.0 - dcpq.Write(getBytelenByteArr(p)) // 4.0.1.5 - dcpq.Write(p) // 4.0.1.5.0 - dcpq.Write(getBytelenByteArr(q)) // 4.0.1.6 - dcpq.Write(q) // 4.0.1.6.0 - if _, err := dcpq.WriteTo(&privkeyBytes); err != nil { // 4.0.1.3 to 4.0.1.6 + q := pk.Key.Primes[1].Bytes() // 4.0.1.6.0 + dcpq.Write(getBytelenByteArr(d)) // 4.0.1.3 + dcpq.Write(d) // 4.0.1.3.0 + dcpq.Write(getBytelenByteArr(crt)) // 4.0.1.4 + dcpq.Write(crt) // 4.0.1.4.0 + dcpq.Write(getBytelenByteArr(p)) // 4.0.1.5 + dcpq.Write(p) // 4.0.1.5.0 + dcpq.Write(getBytelenByteArr(q)) // 4.0.1.6 + dcpq.Write(q) // 4.0.1.6.0 + if _, err := dcpq.WriteTo(&privkeyBytes); err != nil { // 4.0.1.3 to 4.0.1.6 return nil, err } } // Add the comment. - 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(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) // Add padding pad := 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++ pad = n & pk.BlockSize privkeyBytes.Write(getSingleByteInt(pad)) @@ -377,15 +379,15 @@ func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) { encBytes = []byte{} } // Get the respective lengths and add child buffers to buffer. - keypairBytes.Write(getByteInt(len(pubkeyBytes.Bytes()))) // 4.0.0 - if _, err := pubkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.0 children) + keypairBytes.Write(getByteInt(len(pubkeyBytes.Bytes()))) // 4.0.0 + if _, err := pubkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.0 children) return nil, err } - keypairBytes.Write(getByteInt(len(privkeyBytes.Bytes()))) // 4.0.1 - if _, err := privkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.1 children) + keypairBytes.Write(getByteInt(len(privkeyBytes.Bytes()))) // 4.0.1 + if _, err := privkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.1 children) return nil, err } // Done! kpSlice := keypairBytes.Bytes() return &kpSlice, nil -} \ No newline at end of file +}