-
Notifications
You must be signed in to change notification settings - Fork 87
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
Changes from 5 commits
ed5e893
9834c9d
94bc909
d6f078d
8e9352e
48a9f1e
24aa920
908f3bc
c1228a0
d92cc6c
8246015
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
} | ||
|
||
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)) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jvehent Done! |
||
return results, nil | ||
} |
There was a problem hiding this comment.
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.