Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add OCSP status worker #336

Merged
merged 11 commits into from
May 8, 2018
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
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
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
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
}