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"
|
"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
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}
|
// 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}
|
||||||
// )
|
// )
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue