Skip to content
This repository was archived by the owner on Dec 1, 2025. It is now read-only.
3 changes: 2 additions & 1 deletion tlsobs-scanner/workerconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package main

import (
_ "github.com/mozilla/tls-observatory/worker/awsCertlint"
_ "github.com/mozilla/tls-observatory/worker/crlWorker"
_ "github.com/mozilla/tls-observatory/worker/caaWorker"
_ "github.com/mozilla/tls-observatory/worker/crlWorker"
_ "github.com/mozilla/tls-observatory/worker/evCheckerWorker"
_ "github.com/mozilla/tls-observatory/worker/mozillaEvaluationWorker"
_ "github.com/mozilla/tls-observatory/worker/mozillaGradingWorker"
_ "github.com/mozilla/tls-observatory/worker/ocspStatus"
_ "github.com/mozilla/tls-observatory/worker/sslLabsClientSupport"
_ "github.com/mozilla/tls-observatory/worker/symantecDistrust"
_ "github.com/mozilla/tls-observatory/worker/top1m"
Expand Down
1 change: 1 addition & 0 deletions tlsobs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
_ "github.com/mozilla/tls-observatory/worker/crlWorker"
_ "github.com/mozilla/tls-observatory/worker/mozillaEvaluationWorker"
_ "github.com/mozilla/tls-observatory/worker/mozillaGradingWorker"
_ "github.com/mozilla/tls-observatory/worker/ocspStatus"
_ "github.com/mozilla/tls-observatory/worker/sslLabsClientSupport"
_ "github.com/mozilla/tls-observatory/worker/symantecDistrust"
_ "github.com/mozilla/tls-observatory/worker/top1m"
Expand Down
167 changes: 167 additions & 0 deletions worker/ocspStatus/ocspStatus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package ocspStatus

import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"

"github.com/mozilla/tls-observatory/logger"
"github.com/mozilla/tls-observatory/worker"
"golang.org/x/crypto/ocsp"
)

var (
workerName = "ocspStatus"
workerDesc = "Determines a certificate's status via OCSP'"

httpClient = &http.Client{
Transport: &http.Transport{
TLSHandshakeTimeout: 30 * time.Second,
},
Timeout: 60 * time.Second,
}

log = logger.GetLogger()
)

func init() {
runner := new(ocspStatus)

worker.RegisterPrinter(workerName, worker.Info{Runner: runner, Description: workerDesc})
worker.RegisterWorker(workerName, worker.Info{Runner: runner, Description: workerDesc})
}

type Result struct {
Status int `json:"status"`
RevokedAt time.Time `json:"revoked_at,omitempty"`
}

type ocspStatus struct{}

func (w ocspStatus) Run(in worker.Input, resChan chan worker.Result) {
res := worker.Result{
WorkerName: workerName,
Success: false,
}

x509Cert, err := in.Certificate.ToX509()
if err != nil {
resChan <- worker.Result{
Success: false,
WorkerName: workerName,
Errors: []string{fmt.Sprintf("%s for certificate %d", err.Error(), in.Certificate.ID)},
Result: nil,
}
return
}

issuerEncoded := in.CertificateChain.Certs[len(in.CertificateChain.Certs)-1] // last cert in chain
x509IssuerCertEncoded, err := base64.StdEncoding.DecodeString(issuerEncoded)
if err != nil {
resChan <- worker.Result{
Success: false,
WorkerName: workerName,
Errors: []string{fmt.Sprintf("%s decoding issuer for certificate %d", err.Error(), in.Certificate.ID)},
Result: nil,
}
return
}
x509Issuer, err := x509.ParseCertificate(x509IssuerCertEncoded)
if err != nil {
resChan <- worker.Result{
Success: false,
WorkerName: workerName,
Errors: []string{fmt.Sprintf("%s parsing issuer for certificate %d", err.Error(), in.Certificate.ID)},
Result: nil,
}
return
}

// grab OCSP response
opts := &ocsp.RequestOptions{
Hash: crypto.SHA256,
}
req, err := ocsp.CreateRequest(x509Cert, x509Issuer, opts)
if err != nil {
resChan <- worker.Result{
Success: false,
WorkerName: workerName,
Errors: []string{fmt.Sprintf("%s creating OCSP response for certificate %d", err.Error(), in.Certificate.ID)},
Result: nil,
}
return
}
httpResponse, err := http.Post(x509Cert.OCSPServer[0], "application/ocsp-request", bytes.NewReader(req))
if err != nil {
resChan <- worker.Result{
Success: false,
WorkerName: workerName,
Errors: []string{fmt.Sprintf("%s making OCSP response for certificate %d", err.Error(), in.Certificate.ID)},
Result: nil,
}
return
}

// parse response
output, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
resChan <- worker.Result{
Success: false,
WorkerName: workerName,
Errors: []string{fmt.Sprintf("%s reading OCSP response for certificate %d", err.Error(), in.Certificate.ID)},
Result: nil,
}
return
}
resp, err := ocsp.ParseResponse(output, x509Issuer)
if err != nil {
resChan <- worker.Result{
Success: false,
WorkerName: workerName,
Errors: []string{fmt.Sprintf("%s parsing OCSP response for certificate %d", err.Error(), in.Certificate.ID)},
Result: nil,
}
return
}

status := Result{
Status: resp.Status,
}
if resp.Status == ocsp.Revoked {
status.RevokedAt = resp.RevokedAt
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The revoked_at field is still stored here, but it looks to be detected by .IsZero(), which doesn't really make sense as a revocation time anyway.

The status field is always checked in the printer anyway.

tls_observatory=# select * from analysis where worker_name='ocspStatus' and scan_id=5; 
 id | scan_id | worker_name | success |                       output                        
----+---------+-------------+---------+-----------------------------------------------------
 25 |       5 | ocspStatus  | t       | {"status": 0, "revoked_at": "0001-01-01T00:00:00Z"}


out, err := json.Marshal(status)
if err != nil {
res.Success = false
res.Errors = append(res.Errors, err.Error())
} else {
res.Success = true
res.Result = out
}
resChan <- res
}

func (ocspStatus) AnalysisPrinter(input []byte, printAll interface{}) (results []string, err error) {
var result Result
if err = json.Unmarshal(input, &result); err != nil {
return nil, fmt.Errorf("ocspStatus Worker: failed to parse results: err=%v", err)
}

results = []string{"* OCSP Status: "}
switch result.Status {
case ocsp.Good:
results = append(results, fmt.Sprintf(" - Not revoked"))
case ocsp.Revoked:
results = append(results, fmt.Sprintf(" - Revoked at %s\n", result.RevokedAt.Format(time.RFC3339)))
default:
results = append(results, fmt.Sprintf(" - Unknown status code %d\n", result.Status))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets put everything on a single line like we did for the CAA and CRL workers. The output should be fmt.Sprintf("* OCSP: <status>"). Also, don't include a newline, the printer does that automatically.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jvehent Done!

return results, nil
}