Implement CRL based certificate database
This commit is contained in:
parent
ffa5a14a72
commit
c816d97888
5 changed files with 214 additions and 36 deletions
|
@ -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)
|
||||
|
||||
|
|
150
pkg/crlcertdb/crlcertdb.go
Normal file
150
pkg/crlcertdb/crlcertdb.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
35
pkg/filewatcher/filewatcher.go
Normal file
35
pkg/filewatcher/filewatcher.go
Normal 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)
|
||||
}
|
|
@ -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…
Reference in a new issue