fix: Initial commit
This commit is contained in:
18
go.mod
Normal file
18
go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module ocspcrl
|
||||
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0
|
||||
github.com/cloudflare/cfssl v1.6.5
|
||||
golang.org/x/crypto v0.32.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.7 // indirect
|
||||
github.com/jmhodges/clock v1.2.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
)
|
||||
40
go.sum
Normal file
40
go.sum
Normal file
@@ -0,0 +1,40 @@
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/cloudflare/cfssl v1.6.5 h1:46zpNkm6dlNkMZH/wMW22ejih6gIaJbzL2du6vD7ZeI=
|
||||
github.com/cloudflare/cfssl v1.6.5/go.mod h1:Bk1si7sq8h2+yVEDrFJiz3d7Aw+pfjjJSZVaD+Taky4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/google/certificate-transparency-go v1.1.7 h1:IASD+NtgSTJLPdzkthwvAG1ZVbF2WtFg4IvoA68XGSw=
|
||||
github.com/google/certificate-transparency-go v1.1.7/go.mod h1:FSSBo8fyMVgqptbfF6j5p/XNdgQftAhSmXcIxV9iphE=
|
||||
github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
|
||||
github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
106
internal/ocsp_source/source.go
Normal file
106
internal/ocsp_source/source.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package ocsp_source
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
type CrlSource struct {
|
||||
caCertificate *x509.Certificate
|
||||
responderCertificate *x509.Certificate
|
||||
responderKey crypto.Signer
|
||||
crl *x509.RevocationList
|
||||
}
|
||||
|
||||
func NewCrlSource(caCertificate *x509.Certificate, responderKeyPair tls.Certificate) *CrlSource {
|
||||
return &CrlSource{
|
||||
caCertificate: caCertificate,
|
||||
responderCertificate: responderKeyPair.Leaf,
|
||||
responderKey: responderKeyPair.PrivateKey.(crypto.Signer),
|
||||
}
|
||||
}
|
||||
|
||||
func (source *CrlSource) LoadCrlFromFile(path string) error {
|
||||
crlContent, openCrlError := os.ReadFile(path)
|
||||
if openCrlError != nil {
|
||||
return openCrlError
|
||||
}
|
||||
block, rest := pem.Decode(crlContent)
|
||||
if len(rest) > 0 {
|
||||
return fmt.Errorf("failed to decode crl")
|
||||
}
|
||||
crl, parseCrlError := x509.ParseRevocationList(block.Bytes)
|
||||
if parseCrlError != nil {
|
||||
return parseCrlError
|
||||
}
|
||||
source.crl = crl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (source *CrlSource) Response(request *ocsp.Request) ([]byte, http.Header, error) {
|
||||
var buildResponseError error
|
||||
var response []byte
|
||||
|
||||
for _, entry := range source.crl.RevokedCertificateEntries {
|
||||
// if the serial number is not the one we are looking for, skip
|
||||
if entry.SerialNumber.Cmp(request.SerialNumber) != 0 {
|
||||
continue
|
||||
}
|
||||
response, buildResponseError = source.buildRevokedResponse(entry.SerialNumber, entry.RevocationTime)
|
||||
break
|
||||
}
|
||||
if len(response) == 0 {
|
||||
response, buildResponseError = source.buildOkResponse(request.SerialNumber)
|
||||
}
|
||||
if buildResponseError != nil {
|
||||
return func() []byte { rsp, _ := source.buildServerErrorResponse(); return rsp }(), nil, buildResponseError
|
||||
}
|
||||
|
||||
return response, nil, nil
|
||||
}
|
||||
|
||||
func (source *CrlSource) buildRevokedResponse(serialNumber *big.Int, revocationTime time.Time) ([]byte, error) {
|
||||
return source.buildResponse(ocsp.Response{
|
||||
SerialNumber: serialNumber,
|
||||
Status: ocsp.Revoked,
|
||||
ThisUpdate: time.Now(),
|
||||
Certificate: source.responderCertificate,
|
||||
RevokedAt: revocationTime,
|
||||
RevocationReason: ocsp.Unspecified,
|
||||
})
|
||||
}
|
||||
|
||||
func (source *CrlSource) buildOkResponse(serialNumber *big.Int) (ocspResponse []byte, err error) {
|
||||
return source.buildResponse(ocsp.Response{
|
||||
SerialNumber: serialNumber,
|
||||
Status: ocsp.Good,
|
||||
ThisUpdate: time.Now(),
|
||||
NextUpdate: time.Now().Add(time.Hour),
|
||||
Certificate: source.responderCertificate,
|
||||
})
|
||||
}
|
||||
|
||||
func (source *CrlSource) buildServerErrorResponse() (ocspResponse []byte, err error) {
|
||||
return source.buildResponse(ocsp.Response{
|
||||
Status: ocsp.ServerFailed,
|
||||
ThisUpdate: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
func (source *CrlSource) buildResponse(template ocsp.Response) (ocspResponse []byte, err error) {
|
||||
ocspResponse, err = ocsp.CreateResponse(
|
||||
source.caCertificate,
|
||||
source.responderCertificate,
|
||||
template,
|
||||
source.responderKey)
|
||||
return
|
||||
}
|
||||
91
main.go
Normal file
91
main.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
cfocsp "github.com/cloudflare/cfssl/ocsp"
|
||||
|
||||
"ocspcrl/internal/ocsp_source"
|
||||
)
|
||||
|
||||
type responder struct {
|
||||
certificatePath string
|
||||
keyPath string
|
||||
}
|
||||
|
||||
type crlSourceFile struct {
|
||||
path string
|
||||
}
|
||||
|
||||
type addresses struct {
|
||||
ocsp string
|
||||
crl string
|
||||
}
|
||||
|
||||
type configuration struct {
|
||||
responder *responder
|
||||
caCrtPath string
|
||||
crlSourceType string
|
||||
crlSourceFile *crlSourceFile
|
||||
addresses *addresses
|
||||
}
|
||||
|
||||
func main() {
|
||||
config := &configuration{
|
||||
responder: &responder{},
|
||||
crlSourceFile: &crlSourceFile{},
|
||||
addresses: &addresses{},
|
||||
}
|
||||
app := kingpin.New("ocspcrl", "OCSP responder / CRL server")
|
||||
app.HelpFlag.Short('h')
|
||||
app.Flag("responder.certificate-path", "Path to the responder certificate").Envar("RESPONDER_CERTIFICATE_PATH").Required().ExistingFileVar(&config.responder.certificatePath)
|
||||
app.Flag("responder.key-path", "Path to the responder key").Envar("RESPONDER_KEY_PATH").Required().ExistingFileVar(&config.responder.keyPath)
|
||||
app.Flag("ca-crt-path", "Path to the CA certificate").Envar("CA_CRL_PATH").Required().ExistingFileVar(&config.caCrtPath)
|
||||
app.Flag("crl-source-type", "Type of CRL source").Envar("CRL_SOURCE").Default("file").EnumVar(&config.crlSourceType, "file")
|
||||
app.Flag("source.file.path", "Path to the CRL file").Envar("SOURCE_FILE_PATH").ExistingFileVar(&config.crlSourceFile.path)
|
||||
app.Flag("ocsp.listen-address", "Address for ocsp endpoint").Envar("OCSP_LISTEN_ADDRESS").Default(":8080").StringVar(&config.addresses.ocsp)
|
||||
app.Flag("crl.listen-address", "Address for crl endpoint").Envar("CRL_LISTEN_ADDRESS").Default(":8081").StringVar(&config.addresses.crl)
|
||||
kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||
|
||||
responderKeyPair, loadResponderKeyPairError := tls.LoadX509KeyPair(config.responder.certificatePath, config.responder.keyPath)
|
||||
if loadResponderKeyPairError != nil {
|
||||
panic(loadResponderKeyPairError)
|
||||
}
|
||||
|
||||
caCrtContent, openCaCrtError := os.ReadFile(config.caCrtPath)
|
||||
if openCaCrtError != nil {
|
||||
panic(openCaCrtError)
|
||||
}
|
||||
block, rest := pem.Decode(caCrtContent)
|
||||
if len(rest) > 0 {
|
||||
panic("failed to decode ca certificate")
|
||||
}
|
||||
caCertificate, loadCaCertificateError := x509.ParseCertificate(block.Bytes)
|
||||
if loadCaCertificateError != nil {
|
||||
panic(loadCaCertificateError)
|
||||
}
|
||||
|
||||
source := ocsp_source.NewCrlSource(caCertificate, responderKeyPair)
|
||||
loadCrlError := source.LoadCrlFromFile(config.crlSourceFile.path)
|
||||
if loadCrlError != nil {
|
||||
panic(loadCrlError)
|
||||
}
|
||||
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
responder := cfocsp.NewResponder(source, nil)
|
||||
listenError := http.ListenAndServe(config.addresses.ocsp, responder)
|
||||
if listenError != nil {
|
||||
panic(listenError)
|
||||
}
|
||||
|
||||
// TODO: Implement CRL server
|
||||
}
|
||||
Reference in New Issue
Block a user