diff --git a/auth/privkey.go b/auth/privkey.go new file mode 100644 index 0000000000000000000000000000000000000000..dc6530c78fce828ef8af57fec97fcb1c0065fc87 --- /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 0000000000000000000000000000000000000000..36ae5e238b4e20f08c2ff1029a31da7d353648ef --- /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 +}