151 lines
3.7 KiB
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"
|
|
|
|
"git.t-juice.club/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
|
|
}
|