diff --git a/connect.go b/connect.go index c686dd0d..8458467e 100644 --- a/connect.go +++ b/connect.go @@ -19,7 +19,9 @@ import ( "time" "github.com/dustin/go-humanize" - "github.com/schollz/messagebox/keypair" + homedir "github.com/mitchellh/go-homedir" + "github.com/schollz/croc/keypair" + "github.com/schollz/croc/randomstring" "github.com/schollz/peerdiscovery" "github.com/schollz/progressbar" tarinator "github.com/schollz/tarinator-go" @@ -77,8 +79,33 @@ func NewConnection(config *AppConfig) (*Connection, error) { c.Yes = config.Yes c.rate = config.Rate c.Local = config.Local - c.keypair, _ = keypair.New() + + // make or load keypair + homedir, err := homedir.Dir() + if err != nil { + return c, err + } + pathToCrocConfig := path.Join(homedir, ".config", "croc", "keys") + if _, errExists := os.Stat(pathToCrocConfig); os.IsNotExist(errExists) { + // path/to/whatever does not exist + os.MkdirAll(path.Join(homedir, ".config", "croc"), 0700) + keys, _ := keypair.New() + err = ioutil.WriteFile(pathToCrocConfig, []byte(keys.String()), 0644) + if err != nil { + return c, err + } + } + keypairBytes, err := ioutil.ReadFile(pathToCrocConfig) + if err != nil { + return c, err + } + c.keypair, err = keypair.Load(string(keypairBytes)) + if err != nil { + return c, err + } + fmt.Fprintf(os.Stderr, "Your public key: %s\n", c.keypair.Public) + if c.Debug { SetLogLevel("debug") } else { @@ -395,7 +422,10 @@ func (c *Connection) runClient(serverName string) error { return } if id == 0 { - passphraseString := RandStringBytesMaskImprSrc(20) + passphraseString, err := randomstring.GenerateRandomString(30) + if err != nil { + panic(err) + } log.Debugf("passphrase: [%s]", passphraseString) encryptedPassword, err := c.keypair.Encrypt([]byte(passphraseString), publicKeyRecipient) if err != nil { diff --git a/keypair/keypair.go b/keypair/keypair.go new file mode 100644 index 00000000..5626786a --- /dev/null +++ b/keypair/keypair.go @@ -0,0 +1,146 @@ +package keypair + +import ( + crypto_rand "crypto/rand" + "encoding/json" + "errors" + "io" + + "github.com/mr-tron/base58/base58" + "golang.org/x/crypto/nacl/box" +) + +type KeyPair struct { + Public string `json:"public"` + Private string `json:"private,omitempty"` + private *[32]byte + public *[32]byte +} + +func (kp KeyPair) String() string { + b, _ := json.Marshal(kp) + return string(b) +} + +// Load will load from a string +func Load(keypairString string) (kp KeyPair, err error) { + err = json.Unmarshal([]byte(keypairString), &kp) + if err != nil { + return + } + kp, err = New(kp) + return +} + +// New will generate a new key pair, or reload a keypair +// from a public key or a public-private key pair. +func New(kpLoad ...KeyPair) (kp KeyPair, err error) { + kp = KeyPair{} + if len(kpLoad) > 0 { + kp.Public = kpLoad[0].Public + kp.Private = kpLoad[0].Private + } else { + kp.Public, kp.Private, err = generateKeyPair() + if err != nil { + return + } + } + kp.public, err = keyToBytes(kp.Public) + if err != nil { + return + } + if len(kp.Private) > 0 { + kp.private, err = keyToBytes(kp.Private) + if err != nil { + return + } + } + return +} + +func generateKeyPair() (publicKey, privateKey string, err error) { + publicKeyBytes, privateKeyBytes, err := box.GenerateKey(crypto_rand.Reader) + if err != nil { + return + } + + publicKey = base58.FastBase58Encoding(publicKeyBytes[:]) + privateKey = base58.FastBase58Encoding(privateKeyBytes[:]) + return +} + +func keyToBytes(s string) (key *[32]byte, err error) { + var keyBytes []byte + keyBytes, err = base58.FastBase58Decoding(s) + if err != nil { + return + } + + key = new([32]byte) + copy(key[:], keyBytes[:32]) + return +} + +// Encrypt a message for a recipient +func (kp KeyPair) Encrypt(msg []byte, recipientPublicKey string) (encrypted []byte, err error) { + recipient, err := New(KeyPair{Public: recipientPublicKey}) + if err != nil { + return + } + encrypted, err = encryptWithKeyPair(msg, kp.private, recipient.public) + return +} + +// Decrypt a message +func (kp KeyPair) Decrypt(encrypted []byte, senderPublicKey string) (msg []byte, err error) { + sender, err := New(KeyPair{Public: senderPublicKey}) + if err != nil { + return + } + msg, err = decryptWithKeyPair(encrypted, sender.public, kp.private) + return +} + +func encryptWithKeyPair(msg []byte, senderPrivateKey, recipientPublicKey *[32]byte) (encrypted []byte, err error) { + // You must use a different nonce for each message you encrypt with the + // same key. Since the nonce here is 192 bits long, a random value + // provides a sufficiently small probability of repeats. + var nonce [24]byte + if _, err = io.ReadFull(crypto_rand.Reader, nonce[:]); err != nil { + return + } + // This encrypts msg and appends the result to the nonce. + encrypted = box.Seal(nonce[:], msg, &nonce, recipientPublicKey, senderPrivateKey) + return +} + +func decryptWithKeyPair(enc []byte, senderPublicKey, recipientPrivateKey *[32]byte) (decrypted []byte, err error) { + // The recipient can decrypt the message using their private key and the + // sender's public key. When you decrypt, you must use the same nonce you + // used to encrypt the message. One way to achieve this is to store the + // nonce alongside the encrypted message. Above, we stored the nonce in the + // first 24 bytes of the encrypted text. + var decryptNonce [24]byte + copy(decryptNonce[:], enc[:24]) + var ok bool + decrypted, ok = box.Open(nil, enc[24:], &decryptNonce, senderPublicKey, recipientPrivateKey) + if !ok { + err = errors.New("keypair decryption failed") + } + return +} + +// sliceForAppend takes a slice and a requested number of bytes. It returns a +// slice with the contents of the given slice followed by that many bytes and a +// second slice that aliases into it and contains only the extra bytes. If the +// original slice has sufficient capacity then no allocation is performed. +func sliceForAppend(in []byte, n int) (head, tail []byte) { + if total := len(in) + n; cap(in) >= total { + head = in[:total] + } else { + head = make([]byte, total) + copy(head, in) + } + tail = head[len(in):] + return +} diff --git a/randomstring/randomstring.go b/randomstring/randomstring.go new file mode 100644 index 00000000..059f8b2f --- /dev/null +++ b/randomstring/randomstring.go @@ -0,0 +1,66 @@ +package randomstring + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "io" +) + +// Adapted from https://elithrar.github.io/article/generating-secure-random-numbers-crypto-rand/ + +func init() { + assertAvailablePRNG() +} + +func assertAvailablePRNG() { + // Assert that a cryptographically secure PRNG is available. + // Panic otherwise. + buf := make([]byte, 1) + + _, err := io.ReadFull(rand.Reader, buf) + if err != nil { + panic(fmt.Sprintf("crypto/rand is unavailable: Read() failed with %#v", err)) + } +} + +// GenerateRandomBytes returns securely generated random bytes. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + // Note that err == nil only if we read len(b) bytes. + if err != nil { + return nil, err + } + + return b, nil +} + +// GenerateRandomString returns a securely generated random string. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomString(n int) (string, error) { + const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" + bytes, err := GenerateRandomBytes(n) + if err != nil { + return "", err + } + for i, b := range bytes { + bytes[i] = letters[b%byte(len(letters))] + } + return string(bytes), nil +} + +// GenerateRandomStringURLSafe returns a URL-safe, base64 encoded +// securely generated random string. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomStringURLSafe(n int) (string, error) { + b, err := GenerateRandomBytes(n) + return base64.URLEncoding.EncodeToString(b), err +} diff --git a/randomstring/randomstring_test.go b/randomstring/randomstring_test.go new file mode 100644 index 00000000..02fb3c3b --- /dev/null +++ b/randomstring/randomstring_test.go @@ -0,0 +1,15 @@ +package randomstring + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRandomString(t *testing.T) { + r, err := GenerateRandomString(20) + assert.Nil(t, err) + assert.Equal(t, 20, len(r)) + fmt.Println(r) +}