Implement CRL based certificate database

main
Jan Dittberner 2 years ago committed by Jan Dittberner
parent ffa5a14a72
commit c816d97888

@ -32,6 +32,7 @@ import (
"syscall"
"time"
"git.cacert.org/cacert-goocsp/pkg/crlcertdb"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
@ -47,11 +48,12 @@ import (
/* constants for configuration keys */
const (
coIssuers = "issuers"
issuerCaCert = "caCertificate"
issuerReCert = "responderCertificate"
issuerReKey = "responderKey"
issuerCertList = "certificateList"
coIssuers = "issuers"
issuerCaCert = "caCertificate"
issuerReCert = "responderCertificate"
issuerReKey = "responderKey"
issuerDbType = "dbType"
issuerDbFile = "dbFile"
)
func main() {
@ -97,11 +99,18 @@ func main() {
}
}
var ErrUnknownDBType = errors.New("unknown certificate db type")
const (
dbTypeCRL = "crl"
dbTypeOpenSSL = "openssl"
)
func configureIssuers(ctx context.Context, issuerConfigs []*koanf.Koanf, opts []ocspsource.Option) []ocspsource.Option {
for number, issuerConfig := range issuerConfigs {
hasErrors := false
for _, item := range []string{issuerCaCert, issuerReCert, issuerReKey, issuerCertList} {
for _, item := range []string{issuerCaCert, issuerReCert, issuerReKey, issuerDbType, issuerDbFile} {
if v := issuerConfig.String(item); v == "" {
logrus.Warnf("%s parameter for issuers entry %d is missing", item, number)
@ -136,7 +145,17 @@ func configureIssuers(ctx context.Context, issuerConfigs []*koanf.Koanf, opts []
continue
}
certDb, err := opensslcertdb.NewCertDB(ctx, issuerConfig.String(issuerCertList))
var certDb ocspsource.CertificateDatabase
switch issuerConfig.String(issuerDbType) {
case dbTypeOpenSSL:
certDb, err = opensslcertdb.NewCertDB(ctx, issuerConfig.String(issuerDbFile))
case dbTypeCRL:
certDb, err = crlcertdb.NewCertDB(ctx, issuerConfig.String(issuerDbFile))
default:
err = ErrUnknownDBType
}
if err != nil {
logrus.Errorf("could not create certificate db %d: %v", number, err)

@ -0,0 +1,150 @@
/*
Copyright 2022 CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package crlcertdb
import (
"context"
"crypto/x509"
"fmt"
"io/ioutil"
"math/big"
"path/filepath"
"git.cacert.org/cacert-goocsp/pkg/filewatcher"
"git.cacert.org/cacert-goocsp/pkg/ocsp"
"git.cacert.org/cacert-goocsp/pkg/ocspsource"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
)
type CRLCertDB struct {
crlPath string
content map[string]*ocsp.Response
}
func NewCertDB(ctx context.Context, crlPath string) (*CRLCertDB, error) {
absFile, err := filepath.Abs(crlPath)
if err != nil {
return nil, fmt.Errorf("cold not determine absolute file name of %s: %w", crlPath, err)
}
certDb := &CRLCertDB{crlPath: absFile, content: make(map[string]*ocsp.Response)}
err = certDb.update()
if err != nil {
return nil, err
}
go func(ctx context.Context) {
watcherCtx, cancel := context.WithCancel(ctx)
filewatcher.Watch(watcherCtx, certDb.crlPath, certDb.watchCRLFile)
<-ctx.Done()
cancel()
}(ctx)
return certDb, nil
}
func (d *CRLCertDB) update() error {
crlBytes, err := ioutil.ReadFile(d.crlPath)
if err != nil {
return fmt.Errorf("could not read CRL from %s: %w", d.crlPath, err)
}
crl, err := x509.ParseCRL(crlBytes)
if err != nil {
return fmt.Errorf("could not parse CRL from %s: %w", d.crlPath, err)
}
var update *ocspsource.CertificateUpdate
for _, entry := range crl.TBSCertList.RevokedCertificates {
update = &ocspsource.CertificateUpdate{
Serial: entry.SerialNumber,
Status: ocsp.Revoked,
RevokedAt: entry.RevocationTime,
RevocationReason: ocsp.Unspecified,
}
d.UpdateCertificate(update)
}
logrus.Infof(
"parsed CRL '%s', found information for %d certificates",
d.crlPath,
len(crl.TBSCertList.RevokedCertificates),
)
return nil
}
func (d *CRLCertDB) UpdateCertificate(update *ocspsource.CertificateUpdate) {
d.content[update.Serial.Text(16)] = &ocsp.Response{
Status: update.Status,
SerialNumber: update.Serial,
RevokedAt: update.RevokedAt,
RevocationReason: update.RevocationReason,
}
}
func (d *CRLCertDB) LookupResponseTemplate(number *big.Int) *ocsp.Response {
serial := number.Text(16)
if response, ok := d.content[serial]; ok {
return response
}
logrus.Debugf("received request for certificate %s that is not in our data source", serial)
// RFC https://datatracker.ietf.org/doc/html/rfc6960#section-2.2 states that certificates we cannot answer for
// should be marked as good.
response := &ocsp.Response{
Status: ocsp.Good,
SerialNumber: number,
}
return response
}
func (d *CRLCertDB) watchCRLFile(watcher *fsnotify.Watcher) {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write == fsnotify.Write {
if event.Name == d.crlPath {
logrus.Infof("modified: %s", event.Name)
err := d.update()
if err != nil {
logrus.Error(err)
}
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
logrus.Errorf("error from watcher: %w", err)
}
}
}

@ -0,0 +1,35 @@
package filewatcher
import (
"context"
"path"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
)
// Watch creates file system monitoring for the given file and runs the callback on changes.
func Watch(ctx context.Context, filepath string, callback func(watcher *fsnotify.Watcher)) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
logrus.Fatalf("could not create file watcher: %v", err)
}
defer func(watcher *fsnotify.Watcher) {
_ = watcher.Close()
logrus.Infof("stopped watching for %s changes", filepath)
}(watcher)
go callback(watcher)
err = watcher.Add(path.Dir(filepath))
if err != nil {
logrus.Fatalf("could not watch %s: %v", filepath, err)
}
logrus.Infof("watching for changes on %s", filepath)
<-ctx.Done()
logrus.Infof("ending background tasks for %s", filepath)
}

@ -442,7 +442,7 @@ type Response struct {
// MalformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01}
// InternalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02}
// TryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03}
// SigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05}
// SigRequiredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05}
// UnauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06}
// )

@ -25,11 +25,11 @@ import (
"io"
"math/big"
"os"
"path"
"path/filepath"
"strings"
"time"
"git.cacert.org/cacert-goocsp/pkg/filewatcher"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
@ -85,7 +85,7 @@ func NewCertDB(ctx context.Context, fileName string) (*OpenSSLCertDB, error) {
go func(ctx context.Context) {
watcherCtx, cancel := context.WithCancel(ctx)
certDb.WatchChanges(watcherCtx)
filewatcher.Watch(watcherCtx, certDb.fileName, certDb.watchIndexFile)
<-ctx.Done()
cancel()
@ -158,32 +158,6 @@ func (o *OpenSSLCertDB) LookupResponseTemplate(number *big.Int) *ocsp.Response {
return response
}
// WatchChanges creates file system monitoring for the index.txt file of the OpenSSLCertDB
func (o *OpenSSLCertDB) WatchChanges(ctx context.Context) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
logrus.Fatalf("could not create file watcher: %v", err)
}
defer func(watcher *fsnotify.Watcher) {
_ = watcher.Close()
logrus.Infof("stopped watching for %s changes", o.fileName)
}(watcher)
go o.watchIndexFile(watcher)
err = watcher.Add(path.Dir(o.fileName))
if err != nil {
logrus.Fatalf("could not watch %s: %v", o.fileName, err)
}
logrus.Infof("watching for changes on %s", o.fileName)
<-ctx.Done()
logrus.Infof("ending background tasks for %s", o.fileName)
}
func (o *OpenSSLCertDB) watchIndexFile(watcher *fsnotify.Watcher) {
for {
select {

Loading…
Cancel
Save