ezshare/certs/certservice.go

151 lines
3.7 KiB
Go

package certs
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"time"
"gitea.benny.dog/torjus/ezshare/store"
)
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().UnixMilli()),
Subject: pkix.Name{
CommonName: id,
Organization: []string{"ezshare"},
Country: []string{"No"},
Locality: []string{"Oslo"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(0, 0, 30),
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)
}
signed, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, nil, err
}
if err := cs.store.StoreCertificate(signed); err != nil {
return nil, nil, 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)
}
revoked, err := cs.store.IsRevoked(cert.SerialNumber.String())
if err != nil {
return "", fmt.Errorf("unable to check if revoked: %w", err)
}
if revoked {
return "", fmt.Errorf("certificate is revoked")
}
return cert.Subject.CommonName, nil
}