Implement CRL based certificate database

This commit is contained in:
Jan Dittberner 2022-07-26 19:00:04 +02:00 committed by Jan Dittberner
parent ffa5a14a72
commit c816d97888
5 changed files with 214 additions and 36 deletions

View file

@ -32,6 +32,7 @@ import (
"syscall" "syscall"
"time" "time"
"git.cacert.org/cacert-goocsp/pkg/crlcertdb"
"github.com/knadh/koanf" "github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/providers/file"
@ -47,11 +48,12 @@ import (
/* constants for configuration keys */ /* constants for configuration keys */
const ( const (
coIssuers = "issuers" coIssuers = "issuers"
issuerCaCert = "caCertificate" issuerCaCert = "caCertificate"
issuerReCert = "responderCertificate" issuerReCert = "responderCertificate"
issuerReKey = "responderKey" issuerReKey = "responderKey"
issuerCertList = "certificateList" issuerDbType = "dbType"
issuerDbFile = "dbFile"
) )
func main() { 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 { func configureIssuers(ctx context.Context, issuerConfigs []*koanf.Koanf, opts []ocspsource.Option) []ocspsource.Option {
for number, issuerConfig := range issuerConfigs { for number, issuerConfig := range issuerConfigs {
hasErrors := false 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 == "" { if v := issuerConfig.String(item); v == "" {
logrus.Warnf("%s parameter for issuers entry %d is missing", item, number) 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 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 { if err != nil {
logrus.Errorf("could not create certificate db %d: %v", number, err) logrus.Errorf("could not create certificate db %d: %v", number, err)

150
pkg/crlcertdb/crlcertdb.go Normal file
View file

@ -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)
}
}
}

View file

@ -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)
}

View file

@ -442,7 +442,7 @@ type Response struct {
// MalformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01} // MalformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01}
// InternalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02} // InternalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02}
// TryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03} // 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} // UnauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06}
// ) // )

View file

@ -25,11 +25,11 @@ import (
"io" "io"
"math/big" "math/big"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"git.cacert.org/cacert-goocsp/pkg/filewatcher"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -85,7 +85,7 @@ func NewCertDB(ctx context.Context, fileName string) (*OpenSSLCertDB, error) {
go func(ctx context.Context) { go func(ctx context.Context) {
watcherCtx, cancel := context.WithCancel(ctx) watcherCtx, cancel := context.WithCancel(ctx)
certDb.WatchChanges(watcherCtx) filewatcher.Watch(watcherCtx, certDb.fileName, certDb.watchIndexFile)
<-ctx.Done() <-ctx.Done()
cancel() cancel()
@ -158,32 +158,6 @@ func (o *OpenSSLCertDB) LookupResponseTemplate(number *big.Int) *ocsp.Response {
return 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) { func (o *OpenSSLCertDB) watchIndexFile(watcher *fsnotify.Watcher) {
for { for {
select { select {