Add certificate service
This commit is contained in:
parent
a1737dd2f6
commit
13ecf469f4
133
certs/certservice.go
Normal file
133
certs/certservice.go
Normal 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
84
certs/certservice_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -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"},
|
||||||
|
Loading…
Reference in New Issue
Block a user