From d0d3098f5c93bc545b3fc9fec18d2418b3ab7f74 Mon Sep 17 00:00:00 2001
From: Lesterpig <git@lesterpig.com>
Date: Tue, 17 Nov 2015 20:02:40 +0100
Subject: [PATCH] [auth] Add privkey library

---
 auth/privkey.go      | 87 ++++++++++++++++++++++++++++++++++++++
 auth/privkey_test.go | 99 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 186 insertions(+)
 create mode 100644 auth/privkey.go
 create mode 100644 auth/privkey_test.go

diff --git a/auth/privkey.go b/auth/privkey.go
new file mode 100644
index 0000000..dc6530c
--- /dev/null
+++ b/auth/privkey.go
@@ -0,0 +1,87 @@
+// Package auth provides simple ways to handle user authentication
+package auth
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+)
+
+// Cipher used to encrypt private key content in PEM format
+var Cipher = x509.PEMCipherAES256
+
+// GeneratePrivateKey builds a private key of given size from default random
+func GeneratePrivateKey(bits int) (*rsa.PrivateKey, error) {
+	key, err := rsa.GenerateKey(rand.Reader, bits)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return key, nil
+}
+
+// PrivateKeyToEncryptedPEM builds a PEM-encoded array of bytes from a private key and a password.
+// If pwd is empty, then the resulting PEM will not be encrypted.
+func PrivateKeyToEncryptedPEM(key *rsa.PrivateKey, pwd string) ([]byte, error) {
+	var err error
+
+	block := &pem.Block{
+		Type:  "RSA PRIVATE KEY",
+		Bytes: x509.MarshalPKCS1PrivateKey(key),
+	}
+
+	if pwd != "" {
+		block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(pwd), Cipher)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return pem.EncodeToMemory(block), nil
+}
+
+// PrivateKeyToPEM produces a unencrypted PEM-encoded array of bytes from a private key.
+func PrivateKeyToPEM(key *rsa.PrivateKey) []byte {
+	p, _ := PrivateKeyToEncryptedPEM(key, "")
+	return p
+}
+
+// EncryptedPEMToPrivateKey tries to decrypt and decode a PEM-encoded array of bytes to a private key.
+// If pwd is empty, then the function will not try to decrypt the PEM block.
+//
+// In case of wrong password, the returned error will be equals to x509.IncorrectPasswordError
+func EncryptedPEMToPrivateKey(data []byte, pwd string) (*rsa.PrivateKey, error) {
+	var err error
+
+	block, _ := pem.Decode(data)
+	decodedData := block.Bytes
+
+	if pwd != "" {
+		decodedData, err = x509.DecryptPEMBlock(block, []byte(pwd))
+
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	key, err := x509.ParsePKCS1PrivateKey(decodedData)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return key, nil
+}
+
+// PEMToPrivateKey tries to decode a plain PEM-encoded array of bytes to a private key.
+func PEMToPrivateKey(data []byte) (*rsa.PrivateKey, error) {
+	return EncryptedPEMToPrivateKey(data, "")
+}
+
+// IsPEMEncrypted tests whether a PEM-encoded array of bytes is encrypted or not.
+func IsPEMEncrypted(data []byte) bool {
+	var block, _ = pem.Decode(data)
+	return x509.IsEncryptedPEMBlock(block)
+}
diff --git a/auth/privkey_test.go b/auth/privkey_test.go
new file mode 100644
index 0000000..36ae5e2
--- /dev/null
+++ b/auth/privkey_test.go
@@ -0,0 +1,99 @@
+package auth
+
+import (
+	"crypto/x509"
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+func TestGeneratePrivateKey(t *testing.T) {
+	_, err := GeneratePrivateKey(1024)
+
+	if err != nil {
+		t.Fail()
+	}
+
+	if !testing.Short() {
+		GeneratePrivateKey(4096)
+	}
+}
+
+func TestPrivateKeyToPEM(t *testing.T) {
+	key, _ := GeneratePrivateKey(2048)
+	res := PrivateKeyToPEM(key)
+
+	if IsPEMEncrypted(res) {
+		t.Fail()
+	}
+}
+
+func TestPrivateKeyToEncryptedPEM(t *testing.T) {
+	key, _ := GeneratePrivateKey(2048)
+	res, err := PrivateKeyToEncryptedPEM(key, "password")
+
+	if !IsPEMEncrypted(res) || err != nil {
+		t.Fail()
+	}
+}
+
+func TestPEMToPrivateKey(t *testing.T) {
+	key, _ := GeneratePrivateKey(2048)
+	key2, err := PEMToPrivateKey(PrivateKeyToPEM(key))
+	if !reflect.DeepEqual(key, key2) || err != nil {
+		t.Fail()
+	}
+}
+
+func TestEncryptedPEMToPrivateKey(t *testing.T) {
+	key, _ := GeneratePrivateKey(2048)
+	res, _ := PrivateKeyToEncryptedPEM(key, "password")
+
+	goodKey, err := EncryptedPEMToPrivateKey(res, "password")
+
+	if !reflect.DeepEqual(key, goodKey) || err != nil {
+		t.Fail()
+	}
+
+	badKey, err := EncryptedPEMToPrivateKey(res, "badpass")
+
+	if badKey != nil || err != x509.IncorrectPasswordError {
+		t.Fail()
+	}
+}
+
+func ExampleEncryptedPEMToPrivateKey() {
+
+	// Generate a new private key for example
+	key, err := GeneratePrivateKey(2048)
+
+	if err != nil {
+		panic(err)
+	}
+
+	// Get the encrypted PEM data
+
+	p, err := PrivateKeyToEncryptedPEM(key, "myPassword")
+
+	if err != nil {
+		panic(err)
+	}
+
+	// Reverse the process
+
+	newKey, err := EncryptedPEMToPrivateKey(p, "badPassword")
+
+	if err == x509.IncorrectPasswordError {
+		fmt.Println("Bad password")
+	}
+
+	newKey, err = EncryptedPEMToPrivateKey(p, "myPassword")
+
+	if newKey != nil && err == nil {
+		fmt.Println("OK")
+	}
+
+	// Output:
+	// Bad password
+	// OK
+}
-- 
GitLab