commit f0332b21b23e6952c339d400050c3669f97e5c6b Author: Florian Bauer Date: Tue Jan 21 07:19:02 2025 +0100 fix: Initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ac569a2 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9af9c82 --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/ocsp_source/source.go b/internal/ocsp_source/source.go new file mode 100644 index 0000000..694726f --- /dev/null +++ b/internal/ocsp_source/source.go @@ -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 +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..69c13ad --- /dev/null +++ b/main.go @@ -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 +} diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..bf2a242 --- /dev/null +++ b/test.sh @@ -0,0 +1 @@ +openssl ocsp -CAfile ../../ca/ca.crt -url http://127.0.0.1:8080 -issuer ../../ca/ca.crt -resp_text -cert ../../test.crt