Add certificate service

This commit is contained in:
Torjus Håkestad 2021-12-05 01:01:05 +01:00
parent a1737dd2f6
commit 13ecf469f4
3 changed files with 218 additions and 1 deletions

133
certs/certservice.go Normal file
View File

@ -0,0 +1,133 @@
package certs
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"gitea.benny.dog/torjus/ezshare/store"
"math/big"
"time"
)
type CertService struct {
caCert *x509.Certificate
caKey crypto.Signer
store store.CertificateStore
}
func NewCertService(s store.CertificateStore, certBytes, keyBytes []byte) (*CertService, error) {
// Try to decode key as PEM
keyBlock, _ := pem.Decode(keyBytes)
if keyBlock != nil {
if keyBlock.Type != "EC PRIVATE KEY" {
return nil, fmt.Errorf("private key is not of type EC PRIVATE KEY: %s", keyBlock.Type)
}
keyBytes = keyBlock.Bytes
}
// Try to decode cert as PEM
certBlock, _ := pem.Decode(certBytes)
if certBlock != nil {
if certBlock.Type != "CERTIFICATE" {
return nil, fmt.Errorf("certificate is not of type CERTIFICATE: %s", certBlock.Type)
}
certBytes = certBlock.Bytes
}
caCert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, fmt.Errorf("unable to parse certificate: %w", err)
}
if !caCert.IsCA {
return nil, fmt.Errorf("certificate is not CA")
}
caKey, err := x509.ParseECPrivateKey(keyBytes)
if err != nil {
return nil, fmt.Errorf("unable to parse private key: %w", err)
}
return &CertService{caCert: caCert, caKey: caKey, store: s}, nil
}
func (cs *CertService) NewClient(id string) ([]byte, []byte, error) {
cert := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
CommonName: id,
Organization: []string{"ezshare"},
Country: []string{"No"},
Locality: []string{"Oslo"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
certPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
certPrivKeyBytes, err := x509.MarshalECPrivateKey(certPrivKey)
if err != nil {
return nil, nil, err
}
certBytes, err := x509.CreateCertificate(rand.Reader, cert, cs.caCert, &certPrivKey.PublicKey, cs.caKey)
if err != nil {
return nil, nil, err
}
keyPEM := new(bytes.Buffer)
if err := pem.Encode(keyPEM, &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: certPrivKeyBytes,
}); err != nil {
return nil, nil, fmt.Errorf("unable to encode client private key: %w", err)
}
certPEM := new(bytes.Buffer)
if err := pem.Encode(certPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
}); err != nil {
return nil, nil, fmt.Errorf("unable to encode client private key: %w", err)
}
return certPEM.Bytes(), keyPEM.Bytes(), nil
}
func (cs *CertService) VerifyClient(certBytes []byte) (string, error) {
// Try to decode cert as PEM
certBlock, _ := pem.Decode(certBytes)
if certBlock != nil {
if certBlock.Type != "CERTIFICATE" {
return "", fmt.Errorf("certificate is not of type CERTIFICATE: %s", certBlock.Type)
}
certBytes = certBlock.Bytes
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return "", fmt.Errorf("unable to parse certificate: %w", err)
}
rootPool := x509.NewCertPool()
rootPool.AddCert(cs.caCert)
if _, err := cert.Verify(x509.VerifyOptions{
Roots: rootPool,
}); err != nil {
return "", fmt.Errorf("unable to verify: %w", err)
}
return cert.Subject.CommonName, nil
}

84
certs/certservice_test.go Normal file
View File

@ -0,0 +1,84 @@
package certs_test
import (
"crypto/x509"
"encoding/pem"
"gitea.benny.dog/torjus/ezshare/certs"
"gitea.benny.dog/torjus/ezshare/store"
"github.com/google/uuid"
"testing"
)
func TestCertService(t *testing.T) {
t.Run("TestManualVerifyClientCertificate", func(t *testing.T) {
s := store.NewMemoryStore()
caKeyBytes, caCertBytes, err := certs.GenCACert()
if err != nil {
t.Fatalf("Error generating ca cert: %s", err)
}
svc, err := certs.NewCertService(s, caCertBytes, caKeyBytes)
if err != nil {
t.Fatalf("Unable to create service: %s", err)
}
clientCertPEM, _, err := svc.NewClient("test")
if err != nil {
t.Fatalf("Unable to create client certificate: %s", err)
}
caCert, err := x509.ParseCertificate(caCertBytes)
if err != nil {
t.Fatalf("Unable to parse CA certificate: %s", err)
}
certPool := x509.NewCertPool()
certPool.AddCert(caCert)
clientCertPEMBlock, _ := pem.Decode(clientCertPEM)
if clientCertPEMBlock == nil {
t.Fatalf("Client does not contain PEM-encoded data")
}
if clientCertPEMBlock.Type != "CERTIFICATE" {
t.Fatal("Client cert is not certificate")
}
clientCert, err := x509.ParseCertificate(clientCertPEMBlock.Bytes)
if err != nil {
t.Fatalf("Could not parse client certificate: %s", err)
}
if _, err := clientCert.Verify(x509.VerifyOptions{Roots: certPool}); err != nil {
t.Fatalf("Could not verify client certificate: %s", err)
}
})
t.Run("TestVerifyClientCertificate", func(t *testing.T) {
s := store.NewMemoryStore()
caKeyBytes, caCertBytes, err := certs.GenCACert()
if err != nil {
t.Fatalf("Error generating ca cert: %s", err)
}
svc, err := certs.NewCertService(s, caCertBytes, caKeyBytes)
if err != nil {
t.Fatalf("Unable to create service: %s", err)
}
clientID := uuid.Must(uuid.NewRandom()).String()
clientCertPEM, _, err := svc.NewClient(clientID)
if err != nil {
t.Fatalf("Unable to create client certificate: %s", err)
}
id, err := svc.VerifyClient(clientCertPEM)
if err != nil {
t.Fatalf("Failed to verify certificate: %s", err)
}
if id != clientID {
t.Fatalf("Verify returned wrong id. Got %s want %s", id, clientID)
}
})
}

View File

@ -142,7 +142,7 @@ func GenCert(caPub, caPrivKey []byte, dnsNames []string) (priv, pub []byte, err
} }
cert := &x509.Certificate{ cert := &x509.Certificate{
SerialNumber: big.NewInt(1658), SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{ Subject: pkix.Name{
Organization: []string{"ezshare"}, Organization: []string{"ezshare"},
Country: []string{"No"}, Country: []string{"No"},