From 7e5474d0fcb910e6b3147dc68d00bf34be919e02 Mon Sep 17 00:00:00 2001
From: Lesterpig <git@lesterpig.com>
Date: Mon, 23 Nov 2015 23:59:49 +0100
Subject: [PATCH] [auth] Add cert functions and update privkey.go

---
 auth/cert.go         | 109 +++++++++++++++++++++++++++++
 auth/cert_test.go    | 160 +++++++++++++++++++++++++++++++++++++++++++
 auth/privkey.go      |  15 +---
 auth/privkey_test.go |  30 +++++---
 4 files changed, 290 insertions(+), 24 deletions(-)
 create mode 100644 auth/cert.go
 create mode 100644 auth/cert_test.go

diff --git a/auth/cert.go b/auth/cert.go
new file mode 100644
index 0000000..615808a
--- /dev/null
+++ b/auth/cert.go
@@ -0,0 +1,109 @@
+package auth
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"math/big"
+	"time"
+)
+
+// GetCertificateRequest creates a request to be sent to any authoritative signer, as a PEM-encoded array of bytes.
+//
+// It can be safely sent via the network.
+func GetCertificateRequest(country, organization, unit, mail string, key *rsa.PrivateKey) ([]byte, error) {
+	template := &x509.CertificateRequest{
+		Subject: pkix.Name{
+			Country:            []string{country},
+			Organization:       []string{organization},
+			OrganizationalUnit: []string{unit},
+			CommonName:         mail,
+		},
+	}
+
+	der, err := x509.CreateCertificateRequest(rand.Reader, template, key)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return pem.EncodeToMemory(&pem.Block{
+		Type:  "CERTIFICATE REQUEST",
+		Bytes: der,
+	}), nil
+}
+
+// PEMToCertificateRequest tries to decode a PEM-encoded array of bytes to a certificate request
+func PEMToCertificateRequest(data []byte) (*x509.CertificateRequest, error) {
+	block, _ := pem.Decode(data)
+	return x509.ParseCertificateRequest(block.Bytes)
+}
+
+// GetCertificate builds a certificate from a certificate request and an authoritative certificate (CA), as a PEM-encoded array of bytes.
+// This function assumes that the identity of the signee is valid.
+//
+// The serial has to be unique.
+//
+// The generated certificate can safely be distributed to unknown actors.
+func GetCertificate(days int, serial int64, req *x509.CertificateRequest, parent *x509.Certificate, key *rsa.PrivateKey) ([]byte, error) {
+
+	template := &x509.Certificate{
+		SerialNumber: big.NewInt(serial),
+		Subject:      req.Subject,
+		NotBefore:    time.Now(),
+		NotAfter:     time.Now().AddDate(0, 0, days),
+		IsCA:         false,
+	}
+
+	der, err := x509.CreateCertificate(rand.Reader, template, parent, req.PublicKey, key)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return pem.EncodeToMemory(&pem.Block{
+		Type:  "CERTIFICATE",
+		Bytes: der,
+	}), nil
+}
+
+// GetSelfSignedCertificate builds a CA certificate from a private key, as a PEM-encoded array of bytes.
+//
+// The serial has to be unique.
+//
+// The generated certificate should be distributed to any other actor in the network under this CA.
+func GetSelfSignedCertificate(days int, serial int64, country, organization, unit, cn string, key *rsa.PrivateKey) ([]byte, error) {
+
+	template := &x509.Certificate{
+		SerialNumber: big.NewInt(serial),
+		Subject: pkix.Name{
+			Country:            []string{country},
+			Organization:       []string{organization},
+			OrganizationalUnit: []string{unit},
+			CommonName:         cn,
+		},
+		NotBefore: time.Now(),
+		NotAfter:  time.Now().AddDate(0, 0, days),
+		IsCA:      true,
+	}
+
+	der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return pem.EncodeToMemory(&pem.Block{
+		Type:  "CERTIFICATE",
+		Bytes: der,
+	}), nil
+
+}
+
+// PEMToCertificate tries to decode a PEM-encoded array of bytes to a certificate
+func PEMToCertificate(data []byte) (*x509.Certificate, error) {
+	block, _ := pem.Decode(data)
+	return x509.ParseCertificate(block.Bytes)
+}
diff --git a/auth/cert_test.go b/auth/cert_test.go
new file mode 100644
index 0000000..68888c2
--- /dev/null
+++ b/auth/cert_test.go
@@ -0,0 +1,160 @@
+package auth
+
+import (
+	"crypto/rsa"
+	"fmt"
+	"os"
+	"testing"
+)
+
+var pkey *rsa.PrivateKey
+var csrFixture = `-----BEGIN CERTIFICATE REQUEST-----
+MIIBgjCB7AIBADBDMQswCQYDVQQGEwJGUjEMMAoGA1UECgwDT1JHMRAwDgYDVQQL
+DAdPUkdVTklUMRQwEgYDVQQDDAt0ZXN0QGdvLnRsZDCBnzANBgkqhkiG9w0BAQEF
+AAOBjQAwgYkCgYEAtbEFS3VyXHAcNzZ49XKgXzv9SaBszbHWAXmuQlgH4dyjL7OX
+w6NOpjSrIW2MVN99/boW1CilMpJzyMRkfkYg2u/HQw1KRUqP62Tl9FIbFjO+rITC
+JI4fHsMpOh6+oWw62wf9mbKfL+kmFTTTfAWZpcE/R8IM+vJK4R+6DE7qvXMCAwEA
+AaAAMA0GCSqGSIb3DQEBCwUAA4GBAJKNtd8IsxkJyWnOoJjckX+djFxoCNgo7JS1
+6evVTU3esDRQ0P4T6oqn4D+yGQlRNtO6/Ko1D9Vv8v14hG7ZJ23Xr6PNBCQEB1a4
+vzcnqUbk1ftU8qbOoTEEElEEeGu/gaDYjHPt/P9apngZpV3KXVAepAyRRLdXPfKa
+Shc+gMEf
+-----END CERTIFICATE REQUEST-----
+`
+var crtFixture = `-----BEGIN CERTIFICATE-----
+MIICRDCCAa2gAwIBAgIJAIf+q5v9t+rTMA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV
+BAYTAkZSMQwwCgYDVQQKDANPUkcxEDAOBgNVBAsMB09SR1VOSVQxCzAJBgNVBAMM
+AkNBMCAXDTE1MTEyMDE1NDMwOVoYDzQ3NTMxMDE2MTU0MzA5WjA6MQswCQYDVQQG
+EwJGUjEMMAoGA1UECgwDT1JHMRAwDgYDVQQLDAdPUkdVTklUMQswCQYDVQQDDAJD
+QTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqNbQWl2UZiOmJcZDA5x2H2U5
+m2qC0D3NNPv0jNOm6shGTKhcLH1W8DrDtv5NjRWN1XTpfy0VkrsoyPpsU6PFFzZC
+GmkCoXKBD/dvDNrid2MgbURzx+0a+EmUfFh0+tVP2Dzy0zgb/FZWkM6HT0VQ8KAb
+SmlRBctiujDV1RBUOm8CAwEAAaNQME4wHQYDVR0OBBYEFNSIxFzdlyGUGBnqsKpd
+bS4te57xMB8GA1UdIwQYMBaAFNSIxFzdlyGUGBnqsKpdbS4te57xMAwGA1UdEwQF
+MAMBAf8wDQYJKoZIhvcNAQELBQADgYEAECSnGMqnlgyBdqTC02/Uo1jiPqJjLZV1
+TRJFHxs4JPAsff+rdAQ1TQVfaNnvAkAoXVzM34xGPkJserMUBc7aQ61WrByGImai
+RqEe6wUqHGuH2blNt+2LSSuFWuR02+LxsJARDVSLViAS3lNgXlgGnzOaRs31iwwU
+czHnSiYoCog=
+-----END CERTIFICATE-----
+`
+var keyFixture = `-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQCo1tBaXZRmI6YlxkMDnHYfZTmbaoLQPc00+/SM06bqyEZMqFws
+fVbwOsO2/k2NFY3VdOl/LRWSuyjI+mxTo8UXNkIaaQKhcoEP928M2uJ3YyBtRHPH
+7Rr4SZR8WHT61U/YPPLTOBv8VlaQzodPRVDwoBtKaVEFy2K6MNXVEFQ6bwIDAQAB
+AoGAWaKZwK/XvhYE+h70qvEgwPAzkjAMvNNio1Nz9GPVROYIdGAZd0Efq6/3Aaqm
+r1UXFJDZ+buMrXaRY4mXgxv54MjkX4d4KRVfAfIRbZQlP1jjnT6eRFhLFGOZe6pT
+FUxMWO8wJDPwuNJgHFZOm+Ja3v2Hgvt6wgqu+l1onx4RNtECQQDZZbPPtgqTIsrv
+BO0PZvL2BLFpg55NUYvpm57JU0/wU+rqG27gVUx7IYGWQ7J61BRIwmMR9HwrDRtE
+EwPDelXnAkEAxtHJqHdElDcJGH0Um1WVjd2U229Imo9FBTbvFjj8uaJwk1MYhwks
+fUcvD6+s0uoKZAdHogQBM7nN5OrtrnjWOQJBAM+Fpf/BZpbNv6oqqaDqRUNTd4eh
+fJuSHF0DkK/eN5DSioyvY0gCJN/lPC6UsOtPR42tAaVCHMV73Ws+O3l+bkECQGvT
+pRGHtZrIildMpttjvBtXe/7SSMcCQoWEeIBN4cpvraxI2bmKoSVEcOKJ/SnaIk6D
+oDbfAyPhdifbvZHtGQkCQQCDH0Jo3JY7TlOripsIWm8hyikOzw9Lfonhbvnaofjt
+amR9w6/SM5D0y20NqMVCmJxHWYW9sRIfZOmRjprYbczH
+-----END RSA PRIVATE KEY-----
+`
+
+func TestMain(m *testing.M) {
+	pkey, _ = GeneratePrivateKey(1024)
+	os.Exit(m.Run())
+}
+
+func TestGetCertificateRequest(t *testing.T) {
+
+	res, err := GetCertificateRequest("FR", "ORG", "ORGUNIT", "test@go.tld", pkey)
+
+	if res == nil || err != nil {
+		t.Fatal(err)
+	}
+
+	if res[0] != '-' {
+		t.Fatalf("Bad format\n%s", res)
+	}
+}
+
+func TestPEMToCertificateRequest(t *testing.T) {
+
+	res, err := PEMToCertificateRequest([]byte(csrFixture))
+
+	if res == nil || err != nil {
+		t.Fatal(err)
+	}
+
+	if res.Subject.Country[0] != "FR" {
+		t.Fatal("Wrong country: ", res.Subject.Country)
+	}
+
+	if res.Subject.CommonName != "test@go.tld" {
+		t.Fatal("Wrong CN: ", res.Subject.CommonName)
+	}
+
+}
+
+func TestGetSelfSignedCertificate(t *testing.T) {
+	res, err := GetSelfSignedCertificate(10, 20, "FR", "TEST", "TEST UNIT", "My Cn", pkey)
+
+	if res == nil || err != nil {
+		t.Fatal(err)
+	}
+
+	if res[0] != '-' {
+		t.Fatalf("Bad format\n%s", res)
+	}
+
+}
+
+func TestPEMToCertificate(t *testing.T) {
+
+	res, err := PEMToCertificate([]byte(crtFixture))
+
+	if res == nil || err != nil {
+		t.Fatal(err)
+	}
+
+	if res.Subject.CommonName != "CA" {
+		t.Fatal("Wrong CN: ", res.Subject.CommonName)
+	}
+
+	if res.Issuer.CommonName != "CA" {
+		t.Fatal("Wrong issuer: ", res.Issuer.CommonName)
+	}
+
+}
+
+func TestGetCertificate(t *testing.T) {
+
+	req, _ := PEMToCertificateRequest([]byte(csrFixture))
+	crt, _ := PEMToCertificate([]byte(crtFixture))
+	key, _ := PEMToPrivateKey([]byte(keyFixture))
+
+	res, err := GetCertificate(10, 21, req, crt, key)
+
+	if res == nil || err != nil {
+		t.Fatal(err)
+	}
+
+	if res[0] != '-' {
+		t.Fatalf("Bad format\n%s", res)
+	}
+
+}
+
+func ExampleGetCertificate() {
+
+	// Load elements from PEM files
+	certificateRequest, _ := PEMToCertificateRequest([]byte(csrFixture))
+	signerCertificate, _ := PEMToCertificate([]byte(crtFixture))
+	signerKey, _ := PEMToPrivateKey([]byte(keyFixture))
+
+	// Generate the certificate for 365 days with a serial of 0x10 (16)
+	cert, err := GetCertificate(365, int64(0x10), certificateRequest, signerCertificate, signerKey)
+
+	if cert == nil || err != nil {
+		fmt.Println(err)
+	} else {
+		fmt.Println("OK")
+	}
+
+	// Output:
+	// OK
+
+}
diff --git a/auth/privkey.go b/auth/privkey.go
index dc6530c..b866b10 100644
--- a/auth/privkey.go
+++ b/auth/privkey.go
@@ -13,13 +13,7 @@ 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
+	return rsa.GenerateKey(rand.Reader, bits)
 }
 
 // PrivateKeyToEncryptedPEM builds a PEM-encoded array of bytes from a private key and a password.
@@ -66,13 +60,8 @@ func EncryptedPEMToPrivateKey(data []byte, pwd string) (*rsa.PrivateKey, error)
 		}
 	}
 
-	key, err := x509.ParsePKCS1PrivateKey(decodedData)
-
-	if err != nil {
-		return nil, err
-	}
+	return x509.ParsePKCS1PrivateKey(decodedData)
 
-	return key, nil
 }
 
 // PEMToPrivateKey tries to decode a plain PEM-encoded array of bytes to a private key.
diff --git a/auth/privkey_test.go b/auth/privkey_test.go
index 36ae5e2..6897342 100644
--- a/auth/privkey_test.go
+++ b/auth/privkey_test.go
@@ -11,7 +11,7 @@ func TestGeneratePrivateKey(t *testing.T) {
 	_, err := GeneratePrivateKey(1024)
 
 	if err != nil {
-		t.Fail()
+		t.Fatal(err)
 	}
 
 	if !testing.Short() {
@@ -20,52 +20,60 @@ func TestGeneratePrivateKey(t *testing.T) {
 }
 
 func TestPrivateKeyToPEM(t *testing.T) {
-	key, _ := GeneratePrivateKey(2048)
+	key, _ := GeneratePrivateKey(512)
 	res := PrivateKeyToPEM(key)
 
+	if res[0] != '-' {
+		t.Fatalf("Bad format\n%s", res)
+	}
+
 	if IsPEMEncrypted(res) {
-		t.Fail()
+		t.Fatal("Result is encrypted")
 	}
 }
 
 func TestPrivateKeyToEncryptedPEM(t *testing.T) {
-	key, _ := GeneratePrivateKey(2048)
+	key, _ := GeneratePrivateKey(512)
 	res, err := PrivateKeyToEncryptedPEM(key, "password")
 
+	if res[0] != '-' {
+		t.Fatalf("Bad format\n%s", res)
+	}
+
 	if !IsPEMEncrypted(res) || err != nil {
-		t.Fail()
+		t.Fatal("Result is not encrypted: ", err)
 	}
 }
 
 func TestPEMToPrivateKey(t *testing.T) {
-	key, _ := GeneratePrivateKey(2048)
+	key, _ := GeneratePrivateKey(512)
 	key2, err := PEMToPrivateKey(PrivateKeyToPEM(key))
 	if !reflect.DeepEqual(key, key2) || err != nil {
-		t.Fail()
+		t.Fatal(err)
 	}
 }
 
 func TestEncryptedPEMToPrivateKey(t *testing.T) {
-	key, _ := GeneratePrivateKey(2048)
+	key, _ := GeneratePrivateKey(512)
 	res, _ := PrivateKeyToEncryptedPEM(key, "password")
 
 	goodKey, err := EncryptedPEMToPrivateKey(res, "password")
 
 	if !reflect.DeepEqual(key, goodKey) || err != nil {
-		t.Fail()
+		t.Fatal(err)
 	}
 
 	badKey, err := EncryptedPEMToPrivateKey(res, "badpass")
 
 	if badKey != nil || err != x509.IncorrectPasswordError {
-		t.Fail()
+		t.Fatal(err)
 	}
 }
 
 func ExampleEncryptedPEMToPrivateKey() {
 
 	// Generate a new private key for example
-	key, err := GeneratePrivateKey(2048)
+	key, err := GeneratePrivateKey(512)
 
 	if err != nil {
 		panic(err)
-- 
GitLab