Commit 53fdbf52 authored by Dufour Quentin's avatar Dufour Quentin
Browse files

Merge branch '94_crypto_lib' into 'master'

Add crypto library and CI

The crypto library is a simple way to manage x509 certificates that will be used by DFSS modules.

It is able to:

- Create private keys
- Create self-signed root certificates
- Create certificate requests
- Create certificates from requests
- Keys and certificates are exported/imported in PEM format

See merge request !3
parents 14afae25 421687c4
Pipeline #44 passed with stage
stages:
- test
Unit tests:
stage: test
tags:
- golang
- mongo # Require an available mongo service
- strong # Disable this build on small runners
services:
- "lesterpig/mongo:latest" # Use this light version of mongo
#artifacts: # Waiting GitLab 8.2.1...
# paths:
# - "coverage.html"
script:
- "go test -coverprofile c.out -v ./..."
- "go tool cover -html=c.out -o coverage.html"
ARM tests:
stage: test
tags:
- arm
script:
- "go test -cover -short -v ./..."
Code lint:
stage: test
allow_failure: True
tags:
- golang
- lint
script:
- "go get github.com/alecthomas/gometalinter"
- "gometalinter --install"
- "gometalinter -t --deadline=60s ./..."
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)
}
package auth
import (
"crypto/rsa"
"crypto/x509"
"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("Certificate generated")
}
// Check certificate validity
roots := x509.NewCertPool()
roots.AddCert(signerCertificate)
signeeCertificate, _ := PEMToCertificate(cert)
_, err = signeeCertificate.Verify(x509.VerifyOptions{
Roots: roots,
})
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Certificate authenticated")
}
// Output:
// Certificate generated
// Certificate authenticated
}
// 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) {
return rsa.GenerateKey(rand.Reader, bits)
}
// 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
}
}
return x509.ParsePKCS1PrivateKey(decodedData)
}
// 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)
}
package auth
import (
"crypto/x509"
"fmt"
"reflect"
"testing"
)
func TestGeneratePrivateKey(t *testing.T) {
_, err := GeneratePrivateKey(512)
if err != nil {
t.Fatal(err)
}
if !testing.Short() {
_, err = GeneratePrivateKey(2048)
if err != nil {
t.Fatal(err)
}
}
}
func TestPrivateKeyToPEM(t *testing.T) {
key, _ := GeneratePrivateKey(512)
res := PrivateKeyToPEM(key)
if res[0] != '-' {
t.Fatalf("Bad format\n%s", res)
}
if IsPEMEncrypted(res) {
t.Fatal("Result is encrypted")
}
}
func TestPrivateKeyToEncryptedPEM(t *testing.T) {
key, _ := GeneratePrivateKey(512)
res, err := PrivateKeyToEncryptedPEM(key, "password")
if res[0] != '-' {
t.Fatalf("Bad format\n%s", res)
}
if !IsPEMEncrypted(res) || err != nil {
t.Fatal("Result is not encrypted: ", err)
}
}
func TestPEMToPrivateKey(t *testing.T) {
key, _ := GeneratePrivateKey(512)
key2, err := PEMToPrivateKey(PrivateKeyToPEM(key))
if !reflect.DeepEqual(key, key2) || err != nil {
t.Fatal(err)
}
}
func TestEncryptedPEMToPrivateKey(t *testing.T) {
key, _ := GeneratePrivateKey(512)
res, _ := PrivateKeyToEncryptedPEM(key, "password")
goodKey, err := EncryptedPEMToPrivateKey(res, "password")
if !reflect.DeepEqual(key, goodKey) || err != nil {
t.Fatal(err)
}
badKey, err := EncryptedPEMToPrivateKey(res, "badpass")
if badKey != nil || err != x509.IncorrectPasswordError {
t.Fatal(err)
}
}
func ExampleEncryptedPEMToPrivateKey() {
// Generate a new private key for example
key, err := GeneratePrivateKey(512)
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
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment