/* 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 sshkeys import ( "bytes" "crypto/rand" "errors" "fmt" "r00t2.io/sshsecure/sharedconsts" ) func (k *EncryptedSSHKeyV1) validate() error { if k.Passphrase == nil { return errors.New("cannot use encrypted key with empty passphrase") } var validCipher bool var validKDF bool var validKT bool for _, v := range allowed_ciphers { if v == k.CipherName { validCipher = true break } } for _, v := range allowed_kdfnames { if v == k.KDFName { validKDF = true break } } for _, v := range allowed_keytypes { if v == k.DefKeyType { validKT = true } } if !validCipher || !validKDF || !validKT { return errors.New("invalid CipherName, KDFName, or DefKeyType specified") } return nil } func (k *EncryptedSSHKeyV1) Generate(force bool) error { if k.DefKeyType == "" { k.DefKeyType = defKeyType } if k.KDFName == "" { k.KDFName = defKDF } if k.CipherName == "" { k.CipherName = defCipher } if err := k.validate(); err != nil { return err } if len(k.Keys) > 0 && !force { return nil // Already generated. } if k.KDFOpts.Salt == nil { k.KDFOpts.Salt = make([]byte, defSaltLen) if _, err := rand.Read(k.KDFOpts.Salt); err != nil { return err } } if k.KDFOpts.Rounds == 0 { k.KDFOpts.Rounds = defRounds } if k.DefKeyType == KeyEd25519 { k.KeySize = keyEd25519 } // 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: sharedconsts.IDCmnt, } pk.Checksum = make([]byte, 4) if _, err := rand.Read(pk.Checksum); err != nil { return err } switch k.DefKeyType { case KeyRsa: if err := pk.generateRsa(); err != nil { return err } case KeyEd25519: if err := pk.generateEd25519(); err != nil { return err } default: return errors.New("unknown key type; could not generate private/public keypair") } k.Keys = append(k.Keys, pk) // We also need an encrypter/decrypter since this is an encrypted key. if err := k.setCrypt(); err != nil { return err } // And then we need to build the key buffer. if err := k.buildKeybuf(); err != nil { return err } return nil } func (k *SSHKeyV1) validate() error { var validKT bool for _, v := range allowed_keytypes { if v == k.DefKeyType { validKT = true } } if !validKT { return errors.New("invalid DefKeyType specified") } return nil } func (k *SSHKeyV1) Generate(force bool) error { if len(k.Keys) > 0 && !force { return nil // Already generated. } if k.DefKeyType == KeyEd25519 { k.KeySize = keyEd25519 } k.CipherName = CipherNull k.KDFName = KdfNull // 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), } pk.Checksum = make([]byte, 4) if _, err := rand.Read(pk.Checksum); err != nil { return err } switch k.DefKeyType { case KeyRsa: if err := pk.generateRsa(); err != nil { return err } case KeyEd25519: if err := pk.generateEd25519(); err != nil { return err } default: return errors.New("unknown key type; could not generate private/public keypair") } k.Keys = append(k.Keys, pk) if err := k.buildKeybuf(); err != nil { return err } return nil } // buildKeybuf is where an EncryptedSSHKeyV1 key buffer is actually assembled. // Entries are commented with their annotated reference. // (see ref/(encrypted|plain)/(public|private).(rsa|ed25519) and ref/format.(ed25519|rsa) for details) func (k *EncryptedSSHKeyV1) buildKeybuf() error { // TODO: error handling for each .Write()? // Before anything, we want a clean buffer. Just in case. k.Buffer.Reset() // Add the common header. if err := k.addHeader(); err != nil { return err } // Add the keypairs. // 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 switch k.CipherName { case CipherAes256Ctr: i.BlockSize = k.Crypt.Cipher.BlockSize() } kbPtr, err := i.keyBlob(&k.Crypt, true) if err != nil { return err } k.Buffer.Write(*kbPtr) } return nil } // buildKeybuf is where an SSHKeyV1 key buffer is actually assembled. // Entries are commented with their annotated reference. // (see ref/(encrypted|plain)/(public|private).(rsa|ed25519) and ref/format.(ed25519|rsa) for details) func (k *SSHKeyV1) buildKeybuf() error { // TODO: error handling for each .Write()? // Before anything, we want a clean buffer. Just in case. k.Buffer.Reset() // Add the common header. if err := k.addHeader(); err != nil { return err } // 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 i.BlockSize = 8 kbPtr, err := i.keyBlob(nil, false) if err != nil { return err } k.Buffer.Write(*kbPtr) } return nil } 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 // 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 } for _, v := range commonHeader { if _, err := k.Buffer.Write(v); err != nil { return err } } return nil } func (k *EncryptedSSHKeyV1) getKdfOptBytes() []byte { 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 return kdfOptsBytes } func (k *SSHKeyV1) getKdfOptBytes() []byte { 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 // TODO: Optimize? /* THE PUBLIC KEY This is unencrypted, even if it's an encrypted key. */ switch pk.PublicKey.KeyType { case KeyEd25519: pubkeyBytes.Write(getBytelenByteArr(pk.PublicKey.Key.([]byte))) // 4.0.0.1 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 // TODO: does e need getByteInt()? 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) return nil, err } } /* THE PRIVATE KEY This is encrypted if it's an encrypted key, otherwise it's just plain readable bytes. */ // First we need two checksums. for i := 1; i <= 2; i++ { 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 privkeyBytes.Write(pubkeyName) // 4.0.1.2.0.0 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 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) // TODO: does n need nullbyte prefix? 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) 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 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 // TODO: does p need nullbyte prefix? 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 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) // Add padding pad := 0 n := 0 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)) } // Check if the private key should be encrypted or not. if encrypt { // We encrypt here to a "new" buffer. // We actually just clear the buffer and then write the new encrypted data to it, and then clear the intermediate byte slice. if c == nil || c.Stream == nil { return nil, errors.New("if encrypting, you must specify a populated SSHCrypt in c") } var encBytes []byte c.Stream.XORKeyStream(encBytes, privkeyBytes.Bytes()) privkeyBytes.Reset() privkeyBytes.Write(encBytes) 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) return nil, err } 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 }