diff --git a/auth/cert.go b/auth/cert.go new file mode 100644 index 0000000000000000000000000000000000000000..615808ab77f44a1e5346a52a4497120a076b7b43 --- /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 0000000000000000000000000000000000000000..68888c2d9fe2054943336f9d49c55350400433d2 --- /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 dc6530c78fce828ef8af57fec97fcb1c0065fc87..b866b10750154ae9a844fe4d310849cf231eecff 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 36ae5e238b4e20f08c2ff1029a31da7d353648ef..6897342bd13a68d1eaabaa9e55c61760cdb48364 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)