Implement CRL based certificate database
parent
ffa5a14a72
commit
c816d97888
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue