151 lines
3.6 KiB
Go
151 lines
3.6 KiB
Go
|
/*
|
||
|
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)
|
||
|
}
|
||
|
}
|
||
|
}
|