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