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{
|
||||
SerialNumber: big.NewInt(1658),
|
||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"ezshare"},
|
||||
Country: []string{"No"},
|
||||
|
Loading…
Reference in New Issue
Block a user