From 26ef7c67d296c7a9abc133460bb79c85381adb7b Mon Sep 17 00:00:00 2001 From: Klaus Tockloth Date: Sun, 23 Sep 2018 09:31:10 +0200 Subject: [PATCH] initial release --- .gitignore | 3 + Gopkg.lock | 17 + Gopkg.toml | 34 ++ README.md | 119 ++++ main.go | 450 ++++++++++++++ makefile | 58 ++ testsuite.sh | 198 ++++++ vendor/golang.org/x/crypto/AUTHORS | 3 + vendor/golang.org/x/crypto/CONTRIBUTORS | 3 + vendor/golang.org/x/crypto/LICENSE | 27 + vendor/golang.org/x/crypto/PATENTS | 22 + vendor/golang.org/x/crypto/ocsp/ocsp.go | 781 ++++++++++++++++++++++++ 12 files changed, 1715 insertions(+) create mode 100644 .gitignore create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 README.md create mode 100644 main.go create mode 100644 makefile create mode 100644 testsuite.sh create mode 100644 vendor/golang.org/x/crypto/AUTHORS create mode 100644 vendor/golang.org/x/crypto/CONTRIBUTORS create mode 100644 vendor/golang.org/x/crypto/LICENSE create mode 100644 vendor/golang.org/x/crypto/PATENTS create mode 100644 vendor/golang.org/x/crypto/ocsp/ocsp.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e275006 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +certstate +*.out +build/* diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..72f921e --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,17 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + digest = "1:a9508d88987b33aa1933d1bb3516b9d442d2f812bfef5a7e2be7c00c83685255" + name = "golang.org/x/crypto" + packages = ["ocsp"] + pruneopts = "UT" + revision = "0e37d006457bf46f9e6692014ba72ef82c33022c" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = ["golang.org/x/crypto/ocsp"] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..6ed78ba --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "master" + name = "golang.org/x/crypto" + +[prune] + go-tests = true + unused-packages = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..6e46445 --- /dev/null +++ b/README.md @@ -0,0 +1,119 @@ +# certstate + +## Purpose + +'certstate' is a simple tool (helper) to monitor the validity of your public key certificates (digital certificate, ssl certificate, tls certificate, X.509 certificate). It grabs the certificate, checks the OCSP state (staple, service) and prints the collected data as plain text. It's up to you, to monitor the data and alarm someone if a certificate has become invalid or threatens to become invalid. + +## Usage + +```txt +$ ./certstate -help + +Program: + Name : ./certstate + Release : 0.1.0 - 2018/09/23 + Purpose : monitor public key certificate + Info : Prints public key certificate details offered by tls service. + +What does this tool do? + - connects to a tls service and grabs the public key certificate + - if certificate contains OCSP stapling data: parses the data + - if certificate contains link to OCSP service: requests the status + - prints out a subset (the important part) of the collected data + +Possible return values: + - 0 = OK + - >0 = NOK + +How to check the validity of a public key certificate? + - assess 'NotBefore' value of leaf certificate + - assess 'NotAfter' value of leaf certificate + - assess 'OCSPState (Stapled)' value + - assess 'OCSPState (Service)' value + +Possible 'OCSPState' values: + - Good + - Revoked + - Unknown + - ServerFailed + - error: unrecognised OCSP status + +Possible 'KeyUsage' values (binary): + - 000000001 = DigitalSignature + - 000000010 = ContentCommitment + - 000000100 = KeyEncipherment + - 000001000 = DataEncipherment + - 000010000 = KeyAgreement + - 000100000 = CertSign + - 001000000 = CRLSign + - 010000000 = EncipherOnly + - 100000000 = DecipherOnly + +Usage: + ./certstate [-timeout=sec] address:port + +Examples: + ./certstate example.com:443 + ./certstate -timeout=7 example.com:443 + +Options: + -timeout int + communication timeout in seconds (default 19) + +Arguments: + address:port + address (name/ip) and port of tls service + +Remarks: + - The timeout setting will be used: + + as connection timeout when requesting the tls service + + as overall timeout when requesting the OCSP service + - empty or invalid values are not printed + +Reference output: + + TLSService : example.com:443 + Timeout : 19 + Timestamp : 2018-09-22 18:49:40 +0200 CEST + + SignatureAlgorithm : SHA256-RSA + PublicKeyAlgorithm : RSA + Version : 3 + SerialNumber : 19132437207909210467858529073412672688 + Subject : CN=www.example.org,OU=Technology,O=Internet Corporation for Assigned Names and Numbers,L=Los Angeles,ST=California,C=US + Issuer : CN=DigiCert SHA2 High Assurance Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US + NotBefore : 2015-11-03 00:00:00 +0000 UTC + NotAfter : 2018-11-28 12:00:00 +0000 UTC + KeyUsage : 5 (101, KeyEncipherment, DigitalSignature) + IsCA : false + DNSNames : www.example.org, example.com, example.edu, example.net, example.org, www.example.com, www.example.edu, www.example.net + OCSPServer : http://ocsp.digicert.com + IssuingCertificateURL : http://cacerts.digicert.com/DigiCertSHA2HighAssuranceServerCA.crt + CRLDistributionPoints : http://crl3.digicert.com/sha2-ha-server-g4.crl, http://crl4.digicert.com/sha2-ha-server-g4.crl + + SignatureAlgorithm : SHA256-RSA + PublicKeyAlgorithm : RSA + Version : 3 + SerialNumber : 6489877074546166222510380951761917343 + Subject : CN=DigiCert SHA2 High Assurance Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US + Issuer : CN=DigiCert High Assurance EV Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US + NotBefore : 2013-10-22 12:00:00 +0000 UTC + NotAfter : 2028-10-22 12:00:00 +0000 UTC + KeyUsage : 97 (1100001, CRLSign, CertSign, DigitalSignature) + IsCA : true + OCSPServer : http://ocsp.digicert.com + CRLDistributionPoints : http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl + + OCSPState (Stapled) : Good + OCSPState (Service) : Good +``` + +## Remarks + +The master branch is used for program development and may be unstable. Use only stable releases for production. + +## Releases + +### 0.1.0, 2018/09/23 + +- initial release diff --git a/main.go b/main.go new file mode 100644 index 0000000..6646597 --- /dev/null +++ b/main.go @@ -0,0 +1,450 @@ +/* +Purpose: +- monitor public key certificate + +Description: +- Prints public key certificate details offered by tls service. + +Releases: +- 0.1.0 - 2018/09/23 : initial release + +Author: +- Klaus Tockloth + +Copyright and license: +- Copyright (c) 2018 Klaus Tockloth +- MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the Software), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +The software is provided 'as is', without warranty of any kind, express or implied, including +but not limited to the warranties of merchantability, fitness for a particular purpose and +noninfringement. In no event shall the authors or copyright holders be liable for any claim, +damages or other liability, whether in an action of contract, tort or otherwise, arising from, +out of or in connection with the software or the use or other dealings in the software. + +Contact (eMail): +- freizeitkarte@googlemail.com + +Remarks: +- Useful commands: + openssl s_client -showcerts -connect example.com:443 + +Links: +- https://www.feistyduck.com/library/openssl-cookbook/online/ch-testing-with-openssl.html +- https://godoc.org/golang.org/x/crypto/ocsp +- https://github.com/xenolf/lego/blob/master/acme/crypto.go +*/ + +package main + +import ( + "bytes" + "crypto" + "crypto/tls" + "crypto/x509" + "errors" + "flag" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "strings" + "time" + + "golang.org/x/crypto/ocsp" +) + +// general program info +var ( + progName = os.Args[0] + progVersion = "0.1.0" + progDate = "2018/09/23" + progPurpose = "monitor public key certificate" + progInfo = "Prints public key certificate details offered by tls service." +) + +// global settings +var timeout *int + +/* +main starts this program +*/ +func main() { + + timeout = flag.Int("timeout", 19, "communication timeout in seconds") + + flag.Usage = printUsage + flag.Parse() + + // at least one argument required + if len(flag.Args()) == 0 { + fmt.Printf("\nError:\n address:port argument (tls service) required.\n") + printUsage() + } + + // check timeout setting + if *timeout < 1 { + fmt.Printf("\nError:\n Invalid setting <%v> for -timeout option.\n", *timeout) + printUsage() + } + + service := flag.Args()[0] + + config := &tls.Config{ + InsecureSkipVerify: true, + } + + // connect to service with timeout + dialer := &net.Dialer{ + Timeout: time.Duration(*timeout) * time.Second, + } + // fmt.Printf("\nConnecting to %q ...\n\n", service) + conn, err := tls.DialWithDialer(dialer, "tcp", service, config) + if err != nil { + fmt.Printf("Error: unable to connect to service, error = %v\n", err) + os.Exit(1) + } + + fmt.Printf("TLSService : %s\n", service) + fmt.Printf("Timeout : %d\n", *timeout) + fmt.Printf("Timestamp : %s\n\n", time.Now().Format("2006-01-02 15:04:05 -0700 MST")) + + // log connection details + logConnectionState(conn) + + // shut down the connection + err = conn.Close() + if err != nil { + fmt.Printf("Error: unable to close connection, error = %v\n", err) + os.Exit(1) + } + + os.Exit(0) +} + +/* +logConnectionState logs some data from the connection state +*/ +func logConnectionState(conn *tls.Conn) { + + state := conn.ConnectionState() + // fmt.Printf("Connection State ...\n%v\n", spew.Sdump(state)) + + var issuerCertificate *x509.Certificate + var issuerCommonName string + + // print some public key certificate data + for index, certificate := range state.PeerCertificates { + // fmt.Printf("certificate ...\n%s\n", spew.Sdump(certificate)) + if index == 0 { + issuerCommonName = certificate.Issuer.CommonName + } + if index > 0 { + if issuerCommonName == certificate.Subject.CommonName { + issuerCertificate = certificate + } + } + fmt.Printf("SignatureAlgorithm : %s\n", certificate.SignatureAlgorithm) + fmt.Printf("PublicKeyAlgorithm : %s\n", certificate.PublicKeyAlgorithm) + fmt.Printf("Version : %v\n", certificate.Version) + fmt.Printf("SerialNumber : %s\n", certificate.SerialNumber) + fmt.Printf("Subject : %s\n", certificate.Subject) + fmt.Printf("Issuer : %s\n", certificate.Issuer) + fmt.Printf("NotBefore : %s\n", certificate.NotBefore) + fmt.Printf("NotAfter : %s\n", certificate.NotAfter) + keyUsages := buildKeyUsages(certificate.KeyUsage) + fmt.Printf("KeyUsage : %v (%b, %s)\n", certificate.KeyUsage, certificate.KeyUsage, strings.Join(keyUsages, ", ")) + if certificate.BasicConstraintsValid { + fmt.Printf("IsCA : %t\n", certificate.IsCA) + } + if len(certificate.DNSNames) > 0 { + fmt.Printf("DNSNames : %s\n", strings.Join(certificate.DNSNames, ", ")) + } + if len(certificate.OCSPServer) > 0 { + fmt.Printf("OCSPServer : %s\n", strings.Join(certificate.OCSPServer, ", ")) + } + if len(certificate.IssuingCertificateURL) > 0 { + fmt.Printf("IssuingCertificateURL : %s\n", strings.Join(certificate.IssuingCertificateURL, ", ")) + } + if len(certificate.CRLDistributionPoints) > 0 { + fmt.Printf("CRLDistributionPoints : %s\n", strings.Join(certificate.CRLDistributionPoints, ", ")) + } + fmt.Printf("\n") + } + + // stapled OCSP response from server (if any) + if state.OCSPResponse != nil { + if issuerCertificate != nil { + ocspState := evaluateOCSPResponse(state.OCSPResponse, issuerCertificate) + fmt.Printf("OCSPState (Stapled) : %s\n", ocspState) + } + } + + // response from OCSP server (if defined) + if len(state.PeerCertificates[0].OCSPServer) > 0 { + if issuerCertificate != nil { + leafCertificate := state.PeerCertificates[0] + ocspServer := state.PeerCertificates[0].OCSPServer[0] + ocspRawData, err := fetchOCSPResponseFromService(leafCertificate, issuerCertificate, ocspServer) + if err != nil { + fmt.Printf("Error: unable to fetch OCSP state from service, error = %v\n", err) + os.Exit(1) + } + ocspState := evaluateOCSPResponse(ocspRawData, issuerCertificate) + fmt.Printf("OCSPState (Service) : %s\n", ocspState) + } + } +} + +/* +printUsage prints the usage of this program +*/ +func printUsage() { + + fmt.Printf("\nProgram:\n") + fmt.Printf(" Name : %s\n", progName) + fmt.Printf(" Release : %s - %s\n", progVersion, progDate) + fmt.Printf(" Purpose : %s\n", progPurpose) + fmt.Printf(" Info : %s\n", progInfo) + + fmt.Printf("\n" + + "What does this tool do?\n" + + " - connects to a tls service and grabs the public key certificate\n" + + " - if certificate contains OCSP stapling data: parses the data\n" + + " - if certificate contains link to OCSP service: requests the status\n" + + " - prints out a subset (the important part) of the collected data\n" + + "\n" + + "Possible return values:\n" + + " - 0 = OK\n" + + " - >0 = NOK\n" + + "\n" + + "How to check the validity of a public key certificate?\n" + + " - assess 'NotBefore' value of leaf certificate\n" + + " - assess 'NotAfter' value of leaf certificate\n" + + " - assess 'OCSPState (Stapled)' value\n" + + " - assess 'OCSPState (Service)' value\n" + + "\n" + + "Possible 'OCSPState' values:\n" + + " - Good\n" + + " - Revoked\n" + + " - Unknown\n" + + " - ServerFailed\n" + + " - error: unrecognised OCSP status\n" + + "\n" + + "Possible 'KeyUsage' values (binary):\n" + + " - 000000001 = DigitalSignature\n" + + " - 000000010 = ContentCommitment\n" + + " - 000000100 = KeyEncipherment\n" + + " - 000001000 = DataEncipherment\n" + + " - 000010000 = KeyAgreement\n" + + " - 000100000 = CertSign\n" + + " - 001000000 = CRLSign\n" + + " - 010000000 = EncipherOnly\n" + + " - 100000000 = DecipherOnly\n") + + fmt.Printf("\nUsage:\n") + fmt.Printf(" %s [-timeout=sec] address:port\n", progName) + + fmt.Printf("\nExamples:\n") + fmt.Printf(" %s example.com:443\n", progName) + fmt.Printf(" %s -timeout=7 example.com:443\n", progName) + + fmt.Printf("\nOptions:\n") + flag.PrintDefaults() + + fmt.Printf("\nArguments:\n") + fmt.Printf(" address:port\n") + fmt.Printf(" address (name/ip) and port of tls service\n") + + fmt.Printf("\nRemarks:\n" + + " - The timeout setting will be used:\n" + + " + as connection timeout when requesting the tls service\n" + + " + as overall timeout when requesting the OCSP service\n" + + " - empty or invalid values are not printed\n") + + fmt.Printf("\nReference output:\n%s\n", referenceOutput) + + os.Exit(1) +} + +/* +evaluateOCSPResponse evaluates the OCSP response +*/ +func evaluateOCSPResponse(bytes []byte, issuer *x509.Certificate) string { + + if issuer == nil { + return "error: unsufficient arguments" + } + + r, err := ocsp.ParseResponse(bytes, issuer) + if err != nil { + return "error: parsing OSCP response failed" + } + + // fmt.Printf("OSCP response details ...\n") + // fmt.Printf("Status : %v\n", r.Status) + // fmt.Printf("SerialNumber : %v\n", r.SerialNumber) + // fmt.Printf("ProducedAt : %v\n", r.ProducedAt) + // fmt.Printf("ThisUpdate : %v\n", r.ThisUpdate) + // fmt.Printf("NextUpdate : %v\n", r.NextUpdate) + // fmt.Printf("RevokedAt : %v\n", r.RevokedAt) + // fmt.Printf("RevocationReason : %v\n\n", r.RevocationReason) + + switch r.Status { + case ocsp.Good: + return "Good" + case ocsp.Revoked: + return "Revoked" + case ocsp.Unknown: + return "Unknown" + case ocsp.ServerFailed: + return "ServerFailed" + default: + return "error: unrecognised OCSP status" + } +} + +/* +fetchOCSPResponseFromService fetches certificate state from corresponding OCSP service +*/ +func fetchOCSPResponseFromService(clientCert, issuerCert *x509.Certificate, ocspServer string) ([]byte, error) { + + opts := &ocsp.RequestOptions{Hash: crypto.SHA1} + + buffer, err := ocsp.CreateRequest(clientCert, issuerCert, opts) + if err != nil { + message := fmt.Sprintf("error: error <%v> at ocsp.CreateRequest()", err) + return nil, errors.New(message) + } + + httpRequest, err := http.NewRequest(http.MethodPost, ocspServer, bytes.NewBuffer(buffer)) + if err != nil { + message := fmt.Sprintf("error: error <%v> at http.NewRequest()", err) + return nil, errors.New(message) + } + + ocspURL, err := url.Parse(ocspServer) + if err != nil { + message := fmt.Sprintf("error: error <%v> at url.Parse()", err) + return nil, errors.New(message) + } + + httpRequest.Header.Add("Content-Type", "application/ocsp-request") + httpRequest.Header.Add("Accept", "application/ocsp-response") + httpRequest.Header.Add("host", ocspURL.Host) + + httpClient := &http.Client{ + Timeout: time.Duration(*timeout) * time.Second, + } + + httpResponse, err := httpClient.Do(httpRequest) + if err != nil { + message := fmt.Sprintf("error: error <%v> at httpClient.Do()", err) + return nil, errors.New(message) + } + defer httpResponse.Body.Close() + + output, err := ioutil.ReadAll(httpResponse.Body) + if err != nil { + message := fmt.Sprintf("error: error <%v> at ioutil.ReadAll()", err) + return nil, errors.New(message) + } + + return output, nil +} + +/* +buildKeyUsages builds an ordered slice with all key usages +*/ +func buildKeyUsages(keyUsage x509.KeyUsage) []string { + + var keyUsageList []string + + if keyUsage >= (1 << 9) { + keyUsageList = append(keyUsageList, "UnknownUsage") + } + if Has(keyUsage, x509.KeyUsageDecipherOnly) { + keyUsageList = append(keyUsageList, "DecipherOnly") + } + if Has(keyUsage, x509.KeyUsageEncipherOnly) { + keyUsageList = append(keyUsageList, "EncipherOnly") + } + if Has(keyUsage, x509.KeyUsageCRLSign) { + keyUsageList = append(keyUsageList, "CRLSign") + } + if Has(keyUsage, x509.KeyUsageCertSign) { + keyUsageList = append(keyUsageList, "CertSign") + } + if Has(keyUsage, x509.KeyUsageKeyAgreement) { + keyUsageList = append(keyUsageList, "KeyAgreement") + } + if Has(keyUsage, x509.KeyUsageDataEncipherment) { + keyUsageList = append(keyUsageList, "DataEncipherment") + } + if Has(keyUsage, x509.KeyUsageKeyEncipherment) { + keyUsageList = append(keyUsageList, "KeyEncipherment") + } + if Has(keyUsage, x509.KeyUsageContentCommitment) { + keyUsageList = append(keyUsageList, "ContentCommitment") + } + + if Has(keyUsage, x509.KeyUsageDigitalSignature) { + keyUsageList = append(keyUsageList, "DigitalSignature") + } + + return keyUsageList +} + +// Has tests a bit +func Has(b, flag x509.KeyUsage) bool { + + return b&flag != 0 +} + +// reference output (for 'example.com:443') +var referenceOutput = ` + TLSService : example.com:443 + Timeout : 19 + Timestamp : 2018-09-22 18:49:40 +0200 CEST + + SignatureAlgorithm : SHA256-RSA + PublicKeyAlgorithm : RSA + Version : 3 + SerialNumber : 19132437207909210467858529073412672688 + Subject : CN=www.example.org,OU=Technology,O=Internet Corporation for Assigned Names and Numbers,L=Los Angeles,ST=California,C=US + Issuer : CN=DigiCert SHA2 High Assurance Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US + NotBefore : 2015-11-03 00:00:00 +0000 UTC + NotAfter : 2018-11-28 12:00:00 +0000 UTC + KeyUsage : 5 (101, KeyEncipherment, DigitalSignature) + IsCA : false + DNSNames : www.example.org, example.com, example.edu, example.net, example.org, www.example.com, www.example.edu, www.example.net + OCSPServer : http://ocsp.digicert.com + IssuingCertificateURL : http://cacerts.digicert.com/DigiCertSHA2HighAssuranceServerCA.crt + CRLDistributionPoints : http://crl3.digicert.com/sha2-ha-server-g4.crl, http://crl4.digicert.com/sha2-ha-server-g4.crl + + SignatureAlgorithm : SHA256-RSA + PublicKeyAlgorithm : RSA + Version : 3 + SerialNumber : 6489877074546166222510380951761917343 + Subject : CN=DigiCert SHA2 High Assurance Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US + Issuer : CN=DigiCert High Assurance EV Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US + NotBefore : 2013-10-22 12:00:00 +0000 UTC + NotAfter : 2028-10-22 12:00:00 +0000 UTC + KeyUsage : 97 (1100001, CRLSign, CertSign, DigitalSignature) + IsCA : true + OCSPServer : http://ocsp.digicert.com + CRLDistributionPoints : http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl + + OCSPState (Stapled) : Good + OCSPState (Service) : Good +` diff --git a/makefile b/makefile new file mode 100644 index 0000000..5105b92 --- /dev/null +++ b/makefile @@ -0,0 +1,58 @@ +# makefile to cross compile the certstate tool / utility +# list all cross compile possibilities: go tool dist list +# +# makefile adapted from this example: +# http://stackoverflow.com/documentation/go/1020/cross-compilation#t=201703051136361578518 +# +# version 1.0.0 - 2018/09/23: initial release + +appname := certstate +sources := $(wildcard *.go) + +build = GOOS=$(1) GOARCH=$(2) go build -o build/$(appname)$(3) +tar = cd build && tar -cvzf $(appname)_$(1)_$(2).tar.gz $(appname)$(3) && rm $(appname)$(3) +zip = cd build && zip $(appname)_$(1)_$(2).zip $(appname)$(3) && rm $(appname)$(3) + +.PHONY: all windows darwin linux clean + +all: linux darwin windows + +clean: + rm -rf build/ + +# ----- linux builds ----- +linux: build/$(appname)_linux_arm.tar.gz build/$(appname)_linux_arm64.tar.gz build/$(appname)_linux_386.tar.gz build/$(appname)_linux_amd64.tar.gz + +build/$(appname)_linux_386.tar.gz: $(sources) + $(call build,linux,386,) + $(call tar,linux,386) + +build/$(appname)_linux_amd64.tar.gz: $(sources) + $(call build,linux,amd64,) + $(call tar,linux,amd64) + +build/$(appname)_linux_arm.tar.gz: $(sources) + $(call build,linux,arm,) + $(call tar,linux,arm) + +build/$(appname)_linux_arm64.tar.gz: $(sources) + $(call build,linux,arm64,) + $(call tar,linux,arm64) + +# ----- darwin (macOS) builds ----- +darwin: build/$(appname)_darwin_amd64.tar.gz + +build/$(appname)_darwin_amd64.tar.gz: $(sources) + $(call build,darwin,amd64,) + $(call tar,darwin,amd64) + +# ----- windows builds ----- +windows: build/$(appname)_windows_386.zip build/$(appname)_windows_amd64.zip + +build/$(appname)_windows_386.zip: $(sources) + $(call build,windows,386,.exe) + $(call zip,windows,386,.exe) + +build/$(appname)_windows_amd64.zip: $(sources) + $(call build,windows,amd64,.exe) + $(call zip,windows,amd64,.exe) diff --git a/testsuite.sh b/testsuite.sh new file mode 100644 index 0000000..5bb0863 --- /dev/null +++ b/testsuite.sh @@ -0,0 +1,198 @@ +#!/bin/sh + +# ------------------------------------ +# Function: +# - Testsuite. +# +# Version: +# - 0.1.0 - 2018/09/22 +# +# Usage: +# - sh testsuite.sh >testsuite.out +# ------------------------------------ + +# set -o xtrace +set -o verbose + +# define application name +appname=./certstate + +# Referenz-Websites +# ------------------------------------ +$appname example.com:443 + +# Liste der 50 meistaufgerufenen Websites (Welt, Wikipedia, 21.09.2018) +# ------------------------------------ +$appname google.com:443 +$appname youtube.com:443 +$appname facebook.com:443 +$appname baidu.com:443 +$appname wikipedia.org:443 +$appname yahoo.com:443 +$appname google.co.in:443 +$appname reddit.com:443 +$appname qq.com:443 +$appname amazon.com:443 +$appname taobao.com:443 +$appname tmall.com:443 +$appname twitter.com:443 +$appname vk.com:443 +$appname live.com:443 +$appname sohu.com:443 +$appname instagram.com:443 +$appname google.co.jp:443 +$appname sina.com.cn:443 +$appname jd.com:443 +$appname weibo.com:443 +$appname 360.cn:443 +$appname google.de:443 +$appname google.co.uk:443 +$appname google.com.br:443 +$appname list.tmall.com:443 +$appname google.fr:443 +$appname google.ru:443 +$appname yandex.ru:443 +$appname linkedin.com:443 +$appname netflix.com:443 +$appname google.it:443 +$appname google.com.hk:443 +$appname google.es:443 +$appname t.co:443 +$appname pornhub.com:443 +$appname ebay.com:443 +$appname alipay.com:443 +$appname google.com.mx:443 +$appname google.ca:443 +$appname yahoo.co.jp:443 +$appname twitch.tv:443 +$appname xvideos.com:443 +$appname bing.com:443 +$appname microsoft.com:443 +$appname ok.ru:443 +$appname imgur.com:443 +$appname aliexpress.com:443 +$appname mail.ru:443 +$appname office.com:443 + +# Liste der 50 meistaufgerufenen Websites (Deutschland, Wikipedia, Stand 21.09.2018) +# ------------------------------------ +$appname google.de:443 +$appname youtube.com:443 +$appname google.com:443 +$appname amazon.de:443 +$appname facebook.com:443 +$appname ebay.de:443 +$appname wikipedia.org:443 +$appname vk.com:443 +$appname ebay-kleinanzeigen.de:443 +$appname web.de:443 +$appname ok.ru:443 +$appname gmx.net:443 +$appname yahoo.com:443 +$appname t-online.de:443 +$appname livejasmin.com:443 +$appname reddit.com:443 +$appname mail.ru:443 +$appname paypal.com:443 +$appname instagram.com:443 +$appname google.com.ua:443 +$appname twitter.com:443 +$appname xhamster.com:443 +$appname chip.de:443 +$appname spiegel.de:443 +$appname bing.com:443 +$appname bild.de:443 +$appname live.com:443 +$appname yandex.ru:443 +$appname google.ru:443 +$appname pornhub.com:443 +$appname twitch.tv:443 +$appname otto.de:443 +$appname netflix.com:443 +$appname whatsapp.com:443 +$appname dhl.de:443 +$appname focus.de:443 +$appname txxx.com:443 +$appname idealo.de:443 +$appname postbank.de:443 +$appname telekom.com:443 +$appname welt.de:443 +$appname microsoft.com:443 +$appname amazon.com:443 +$appname xvideos.com:443 +$appname tumblr.com:443 +$appname linkedin.com:443 +$appname pinterest.de:443 +$appname bahn.de:443 +$appname wordpress.com:443 +$appname mobile.de:443 + +# Liste der 50 meistaufgerufenen Websites (Schweiz, Wikipedia, Stand 21.09.2018) +# ------------------------------------ +$appname google.ch:443 +$appname youtube.com:443 +$appname google.com:443 +$appname facebook.com:443 +$appname wikipedia.org:443 +$appname bluewin.ch:443 +$appname livejasmin.com:443 +$appname reddit.com:443 +$appname yahoo.com:443 +$appname live.com:443 +$appname 20min.ch:443 +$appname blick.ch:443 +$appname amazon.de:443 +$appname twitter.com:443 +$appname srf.ch:443 +$appname google.de:443 +$appname instagram.com:443 +$appname ricardo.ch:443 +$appname bongacams.com:443 +$appname sbb.ch:443 +$appname pornhub.com:443 +$appname postfinance.ch:443 +$appname digitec.ch:443 +$appname xhamster.com:443 +$appname vk.com:443 +$appname gmx.net:443 +$appname gmx.ch:443 +$appname ubs.com:443 +$appname admin.ch:443 +$appname xvideos.com:443 +$appname whatsapp.com:443 +$appname swisscom.ch:443 +$appname Search.ch:443 +$appname netflix.com:443 +$appname aliexpress.com:443 +$appname amazon.com:443 +$appname apple.com:443 +$appname tagesanzeiger.ch:443 +$appname microsoft.com:443 +$appname tutti.ch:443 +$appname paypal.com:443 +$appname twitch.tv:443 +$appname dropbox.ch:443 +$appname stackoverflow.com:443 +$appname wordpress.com:443 +$appname txxx.com:443 +$appname raiffeisen.ch:443 +$appname xnxx.com:443 +$appname github.com:443 + +# Spezielle Websites +# ------------------------------------ +$appname porsche.com:443 +$appname highway.porsche.com:443 +$appname freizeitkarte-osm.de:443 + +# IPv4 Addresses +# ------------------------------------ +# example.com, 93.184.216.34 +$appname 93.184.216.34:443 +# freizeitkarte-osm.de, 138.201.250.238 +$appname 138.201.250.238:443 + +# not working: IPv6 Addresses (syntax?, link: ip6.nl) +# ------------------------------------ +# google.com, 2a00:1450:400e:809::200e +# $appname [2a00:1450:400e:809::200e]:443 diff --git a/vendor/golang.org/x/crypto/AUTHORS b/vendor/golang.org/x/crypto/AUTHORS new file mode 100644 index 0000000..2b00ddb --- /dev/null +++ b/vendor/golang.org/x/crypto/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at https://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/crypto/CONTRIBUTORS b/vendor/golang.org/x/crypto/CONTRIBUTORS new file mode 100644 index 0000000..1fbd3e9 --- /dev/null +++ b/vendor/golang.org/x/crypto/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at https://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/crypto/LICENSE b/vendor/golang.org/x/crypto/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/golang.org/x/crypto/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/crypto/PATENTS b/vendor/golang.org/x/crypto/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/crypto/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/crypto/ocsp/ocsp.go b/vendor/golang.org/x/crypto/ocsp/ocsp.go new file mode 100644 index 0000000..5edc9c9 --- /dev/null +++ b/vendor/golang.org/x/crypto/ocsp/ocsp.go @@ -0,0 +1,781 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ocsp parses OCSP responses as specified in RFC 2560. OCSP responses +// are signed messages attesting to the validity of a certificate for a small +// period of time. This is used to manage revocation for X.509 certificates. +package ocsp // import "golang.org/x/crypto/ocsp" + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "math/big" + "strconv" + "time" +) + +var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1}) + +// ResponseStatus contains the result of an OCSP request. See +// https://tools.ietf.org/html/rfc6960#section-2.3 +type ResponseStatus int + +const ( + Success ResponseStatus = 0 + Malformed ResponseStatus = 1 + InternalError ResponseStatus = 2 + TryLater ResponseStatus = 3 + // Status code four is unused in OCSP. See + // https://tools.ietf.org/html/rfc6960#section-4.2.1 + SignatureRequired ResponseStatus = 5 + Unauthorized ResponseStatus = 6 +) + +func (r ResponseStatus) String() string { + switch r { + case Success: + return "success" + case Malformed: + return "malformed" + case InternalError: + return "internal error" + case TryLater: + return "try later" + case SignatureRequired: + return "signature required" + case Unauthorized: + return "unauthorized" + default: + return "unknown OCSP status: " + strconv.Itoa(int(r)) + } +} + +// ResponseError is an error that may be returned by ParseResponse to indicate +// that the response itself is an error, not just that its indicating that a +// certificate is revoked, unknown, etc. +type ResponseError struct { + Status ResponseStatus +} + +func (r ResponseError) Error() string { + return "ocsp: error from server: " + r.Status.String() +} + +// These are internal structures that reflect the ASN.1 structure of an OCSP +// response. See RFC 2560, section 4.2. + +type certID struct { + HashAlgorithm pkix.AlgorithmIdentifier + NameHash []byte + IssuerKeyHash []byte + SerialNumber *big.Int +} + +// https://tools.ietf.org/html/rfc2560#section-4.1.1 +type ocspRequest struct { + TBSRequest tbsRequest +} + +type tbsRequest struct { + Version int `asn1:"explicit,tag:0,default:0,optional"` + RequestorName pkix.RDNSequence `asn1:"explicit,tag:1,optional"` + RequestList []request +} + +type request struct { + Cert certID +} + +type responseASN1 struct { + Status asn1.Enumerated + Response responseBytes `asn1:"explicit,tag:0,optional"` +} + +type responseBytes struct { + ResponseType asn1.ObjectIdentifier + Response []byte +} + +type basicResponse struct { + TBSResponseData responseData + SignatureAlgorithm pkix.AlgorithmIdentifier + Signature asn1.BitString + Certificates []asn1.RawValue `asn1:"explicit,tag:0,optional"` +} + +type responseData struct { + Raw asn1.RawContent + Version int `asn1:"optional,default:0,explicit,tag:0"` + RawResponderID asn1.RawValue + ProducedAt time.Time `asn1:"generalized"` + Responses []singleResponse +} + +type singleResponse struct { + CertID certID + Good asn1.Flag `asn1:"tag:0,optional"` + Revoked revokedInfo `asn1:"tag:1,optional"` + Unknown asn1.Flag `asn1:"tag:2,optional"` + ThisUpdate time.Time `asn1:"generalized"` + NextUpdate time.Time `asn1:"generalized,explicit,tag:0,optional"` + SingleExtensions []pkix.Extension `asn1:"explicit,tag:1,optional"` +} + +type revokedInfo struct { + RevocationTime time.Time `asn1:"generalized"` + Reason asn1.Enumerated `asn1:"explicit,tag:0,optional"` +} + +var ( + oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} + oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4} + oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} + oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} + oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} + oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} + oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} + oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2} + oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} + oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} + oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} + oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} +) + +var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{ + crypto.SHA1: asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}), + crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}), + crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}), + crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}), +} + +// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below +var signatureAlgorithmDetails = []struct { + algo x509.SignatureAlgorithm + oid asn1.ObjectIdentifier + pubKeyAlgo x509.PublicKeyAlgorithm + hash crypto.Hash +}{ + {x509.MD2WithRSA, oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */}, + {x509.MD5WithRSA, oidSignatureMD5WithRSA, x509.RSA, crypto.MD5}, + {x509.SHA1WithRSA, oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1}, + {x509.SHA256WithRSA, oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256}, + {x509.SHA384WithRSA, oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384}, + {x509.SHA512WithRSA, oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512}, + {x509.DSAWithSHA1, oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1}, + {x509.DSAWithSHA256, oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256}, + {x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1}, + {x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256}, + {x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384}, + {x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512}, +} + +// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below +func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) { + var pubType x509.PublicKeyAlgorithm + + switch pub := pub.(type) { + case *rsa.PublicKey: + pubType = x509.RSA + hashFunc = crypto.SHA256 + sigAlgo.Algorithm = oidSignatureSHA256WithRSA + sigAlgo.Parameters = asn1.RawValue{ + Tag: 5, + } + + case *ecdsa.PublicKey: + pubType = x509.ECDSA + + switch pub.Curve { + case elliptic.P224(), elliptic.P256(): + hashFunc = crypto.SHA256 + sigAlgo.Algorithm = oidSignatureECDSAWithSHA256 + case elliptic.P384(): + hashFunc = crypto.SHA384 + sigAlgo.Algorithm = oidSignatureECDSAWithSHA384 + case elliptic.P521(): + hashFunc = crypto.SHA512 + sigAlgo.Algorithm = oidSignatureECDSAWithSHA512 + default: + err = errors.New("x509: unknown elliptic curve") + } + + default: + err = errors.New("x509: only RSA and ECDSA keys supported") + } + + if err != nil { + return + } + + if requestedSigAlgo == 0 { + return + } + + found := false + for _, details := range signatureAlgorithmDetails { + if details.algo == requestedSigAlgo { + if details.pubKeyAlgo != pubType { + err = errors.New("x509: requested SignatureAlgorithm does not match private key type") + return + } + sigAlgo.Algorithm, hashFunc = details.oid, details.hash + if hashFunc == 0 { + err = errors.New("x509: cannot sign with hash function requested") + return + } + found = true + break + } + } + + if !found { + err = errors.New("x509: unknown SignatureAlgorithm") + } + + return +} + +// TODO(agl): this is taken from crypto/x509 and so should probably be exported +// from crypto/x509 or crypto/x509/pkix. +func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) x509.SignatureAlgorithm { + for _, details := range signatureAlgorithmDetails { + if oid.Equal(details.oid) { + return details.algo + } + } + return x509.UnknownSignatureAlgorithm +} + +// TODO(rlb): This is not taken from crypto/x509, but it's of the same general form. +func getHashAlgorithmFromOID(target asn1.ObjectIdentifier) crypto.Hash { + for hash, oid := range hashOIDs { + if oid.Equal(target) { + return hash + } + } + return crypto.Hash(0) +} + +func getOIDFromHashAlgorithm(target crypto.Hash) asn1.ObjectIdentifier { + for hash, oid := range hashOIDs { + if hash == target { + return oid + } + } + return nil +} + +// This is the exposed reflection of the internal OCSP structures. + +// The status values that can be expressed in OCSP. See RFC 6960. +const ( + // Good means that the certificate is valid. + Good = iota + // Revoked means that the certificate has been deliberately revoked. + Revoked + // Unknown means that the OCSP responder doesn't know about the certificate. + Unknown + // ServerFailed is unused and was never used (see + // https://go-review.googlesource.com/#/c/18944). ParseResponse will + // return a ResponseError when an error response is parsed. + ServerFailed +) + +// The enumerated reasons for revoking a certificate. See RFC 5280. +const ( + Unspecified = 0 + KeyCompromise = 1 + CACompromise = 2 + AffiliationChanged = 3 + Superseded = 4 + CessationOfOperation = 5 + CertificateHold = 6 + + RemoveFromCRL = 8 + PrivilegeWithdrawn = 9 + AACompromise = 10 +) + +// Request represents an OCSP request. See RFC 6960. +type Request struct { + HashAlgorithm crypto.Hash + IssuerNameHash []byte + IssuerKeyHash []byte + SerialNumber *big.Int +} + +// Marshal marshals the OCSP request to ASN.1 DER encoded form. +func (req *Request) Marshal() ([]byte, error) { + hashAlg := getOIDFromHashAlgorithm(req.HashAlgorithm) + if hashAlg == nil { + return nil, errors.New("Unknown hash algorithm") + } + return asn1.Marshal(ocspRequest{ + tbsRequest{ + Version: 0, + RequestList: []request{ + { + Cert: certID{ + pkix.AlgorithmIdentifier{ + Algorithm: hashAlg, + Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */}, + }, + req.IssuerNameHash, + req.IssuerKeyHash, + req.SerialNumber, + }, + }, + }, + }, + }) +} + +// Response represents an OCSP response containing a single SingleResponse. See +// RFC 6960. +type Response struct { + // Status is one of {Good, Revoked, Unknown} + Status int + SerialNumber *big.Int + ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time + RevocationReason int + Certificate *x509.Certificate + // TBSResponseData contains the raw bytes of the signed response. If + // Certificate is nil then this can be used to verify Signature. + TBSResponseData []byte + Signature []byte + SignatureAlgorithm x509.SignatureAlgorithm + + // IssuerHash is the hash used to compute the IssuerNameHash and IssuerKeyHash. + // Valid values are crypto.SHA1, crypto.SHA256, crypto.SHA384, and crypto.SHA512. + // If zero, the default is crypto.SHA1. + IssuerHash crypto.Hash + + // RawResponderName optionally contains the DER-encoded subject of the + // responder certificate. Exactly one of RawResponderName and + // ResponderKeyHash is set. + RawResponderName []byte + // ResponderKeyHash optionally contains the SHA-1 hash of the + // responder's public key. Exactly one of RawResponderName and + // ResponderKeyHash is set. + ResponderKeyHash []byte + + // Extensions contains raw X.509 extensions from the singleExtensions field + // of the OCSP response. When parsing certificates, this can be used to + // extract non-critical extensions that are not parsed by this package. When + // marshaling OCSP responses, the Extensions field is ignored, see + // ExtraExtensions. + Extensions []pkix.Extension + + // ExtraExtensions contains extensions to be copied, raw, into any marshaled + // OCSP response (in the singleExtensions field). Values override any + // extensions that would otherwise be produced based on the other fields. The + // ExtraExtensions field is not populated when parsing certificates, see + // Extensions. + ExtraExtensions []pkix.Extension +} + +// These are pre-serialized error responses for the various non-success codes +// defined by OCSP. The Unauthorized code in particular can be used by an OCSP +// responder that supports only pre-signed responses as a response to requests +// for certificates with unknown status. See RFC 5019. +var ( + MalformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01} + InternalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02} + TryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03} + SigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05} + UnauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06} +) + +// CheckSignatureFrom checks that the signature in resp is a valid signature +// from issuer. This should only be used if resp.Certificate is nil. Otherwise, +// the OCSP response contained an intermediate certificate that created the +// signature. That signature is checked by ParseResponse and only +// resp.Certificate remains to be validated. +func (resp *Response) CheckSignatureFrom(issuer *x509.Certificate) error { + return issuer.CheckSignature(resp.SignatureAlgorithm, resp.TBSResponseData, resp.Signature) +} + +// ParseError results from an invalid OCSP response. +type ParseError string + +func (p ParseError) Error() string { + return string(p) +} + +// ParseRequest parses an OCSP request in DER form. It only supports +// requests for a single certificate. Signed requests are not supported. +// If a request includes a signature, it will result in a ParseError. +func ParseRequest(bytes []byte) (*Request, error) { + var req ocspRequest + rest, err := asn1.Unmarshal(bytes, &req) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, ParseError("trailing data in OCSP request") + } + + if len(req.TBSRequest.RequestList) == 0 { + return nil, ParseError("OCSP request contains no request body") + } + innerRequest := req.TBSRequest.RequestList[0] + + hashFunc := getHashAlgorithmFromOID(innerRequest.Cert.HashAlgorithm.Algorithm) + if hashFunc == crypto.Hash(0) { + return nil, ParseError("OCSP request uses unknown hash function") + } + + return &Request{ + HashAlgorithm: hashFunc, + IssuerNameHash: innerRequest.Cert.NameHash, + IssuerKeyHash: innerRequest.Cert.IssuerKeyHash, + SerialNumber: innerRequest.Cert.SerialNumber, + }, nil +} + +// ParseResponse parses an OCSP response in DER form. It only supports +// responses for a single certificate. If the response contains a certificate +// then the signature over the response is checked. If issuer is not nil then +// it will be used to validate the signature or embedded certificate. +// +// Invalid responses and parse failures will result in a ParseError. +// Error responses will result in a ResponseError. +func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) { + return ParseResponseForCert(bytes, nil, issuer) +} + +// ParseResponseForCert parses an OCSP response in DER form and searches for a +// Response relating to cert. If such a Response is found and the OCSP response +// contains a certificate then the signature over the response is checked. If +// issuer is not nil then it will be used to validate the signature or embedded +// certificate. +// +// Invalid responses and parse failures will result in a ParseError. +// Error responses will result in a ResponseError. +func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Response, error) { + var resp responseASN1 + rest, err := asn1.Unmarshal(bytes, &resp) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, ParseError("trailing data in OCSP response") + } + + if status := ResponseStatus(resp.Status); status != Success { + return nil, ResponseError{status} + } + + if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) { + return nil, ParseError("bad OCSP response type") + } + + var basicResp basicResponse + rest, err = asn1.Unmarshal(resp.Response.Response, &basicResp) + if err != nil { + return nil, err + } + + if n := len(basicResp.TBSResponseData.Responses); n == 0 || cert == nil && n > 1 { + return nil, ParseError("OCSP response contains bad number of responses") + } + + var singleResp singleResponse + if cert == nil { + singleResp = basicResp.TBSResponseData.Responses[0] + } else { + match := false + for _, resp := range basicResp.TBSResponseData.Responses { + if cert.SerialNumber.Cmp(resp.CertID.SerialNumber) == 0 { + singleResp = resp + match = true + break + } + } + if !match { + return nil, ParseError("no response matching the supplied certificate") + } + } + + ret := &Response{ + TBSResponseData: basicResp.TBSResponseData.Raw, + Signature: basicResp.Signature.RightAlign(), + SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm), + Extensions: singleResp.SingleExtensions, + SerialNumber: singleResp.CertID.SerialNumber, + ProducedAt: basicResp.TBSResponseData.ProducedAt, + ThisUpdate: singleResp.ThisUpdate, + NextUpdate: singleResp.NextUpdate, + } + + // Handle the ResponderID CHOICE tag. ResponderID can be flattened into + // TBSResponseData once https://go-review.googlesource.com/34503 has been + // released. + rawResponderID := basicResp.TBSResponseData.RawResponderID + switch rawResponderID.Tag { + case 1: // Name + var rdn pkix.RDNSequence + if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &rdn); err != nil || len(rest) != 0 { + return nil, ParseError("invalid responder name") + } + ret.RawResponderName = rawResponderID.Bytes + case 2: // KeyHash + if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &ret.ResponderKeyHash); err != nil || len(rest) != 0 { + return nil, ParseError("invalid responder key hash") + } + default: + return nil, ParseError("invalid responder id tag") + } + + if len(basicResp.Certificates) > 0 { + // Responders should only send a single certificate (if they + // send any) that connects the responder's certificate to the + // original issuer. We accept responses with multiple + // certificates due to a number responders sending them[1], but + // ignore all but the first. + // + // [1] https://github.com/golang/go/issues/21527 + ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes) + if err != nil { + return nil, err + } + + if err := ret.CheckSignatureFrom(ret.Certificate); err != nil { + return nil, ParseError("bad signature on embedded certificate: " + err.Error()) + } + + if issuer != nil { + if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil { + return nil, ParseError("bad OCSP signature: " + err.Error()) + } + } + } else if issuer != nil { + if err := ret.CheckSignatureFrom(issuer); err != nil { + return nil, ParseError("bad OCSP signature: " + err.Error()) + } + } + + for _, ext := range singleResp.SingleExtensions { + if ext.Critical { + return nil, ParseError("unsupported critical extension") + } + } + + for h, oid := range hashOIDs { + if singleResp.CertID.HashAlgorithm.Algorithm.Equal(oid) { + ret.IssuerHash = h + break + } + } + if ret.IssuerHash == 0 { + return nil, ParseError("unsupported issuer hash algorithm") + } + + switch { + case bool(singleResp.Good): + ret.Status = Good + case bool(singleResp.Unknown): + ret.Status = Unknown + default: + ret.Status = Revoked + ret.RevokedAt = singleResp.Revoked.RevocationTime + ret.RevocationReason = int(singleResp.Revoked.Reason) + } + + return ret, nil +} + +// RequestOptions contains options for constructing OCSP requests. +type RequestOptions struct { + // Hash contains the hash function that should be used when + // constructing the OCSP request. If zero, SHA-1 will be used. + Hash crypto.Hash +} + +func (opts *RequestOptions) hash() crypto.Hash { + if opts == nil || opts.Hash == 0 { + // SHA-1 is nearly universally used in OCSP. + return crypto.SHA1 + } + return opts.Hash +} + +// CreateRequest returns a DER-encoded, OCSP request for the status of cert. If +// opts is nil then sensible defaults are used. +func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte, error) { + hashFunc := opts.hash() + + // OCSP seems to be the only place where these raw hash identifiers are + // used. I took the following from + // http://msdn.microsoft.com/en-us/library/ff635603.aspx + _, ok := hashOIDs[hashFunc] + if !ok { + return nil, x509.ErrUnsupportedAlgorithm + } + + if !hashFunc.Available() { + return nil, x509.ErrUnsupportedAlgorithm + } + h := opts.hash().New() + + var publicKeyInfo struct { + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString + } + if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil { + return nil, err + } + + h.Write(publicKeyInfo.PublicKey.RightAlign()) + issuerKeyHash := h.Sum(nil) + + h.Reset() + h.Write(issuer.RawSubject) + issuerNameHash := h.Sum(nil) + + req := &Request{ + HashAlgorithm: hashFunc, + IssuerNameHash: issuerNameHash, + IssuerKeyHash: issuerKeyHash, + SerialNumber: cert.SerialNumber, + } + return req.Marshal() +} + +// CreateResponse returns a DER-encoded OCSP response with the specified contents. +// The fields in the response are populated as follows: +// +// The responder cert is used to populate the responder's name field, and the +// certificate itself is provided alongside the OCSP response signature. +// +// The issuer cert is used to puplate the IssuerNameHash and IssuerKeyHash fields. +// +// The template is used to populate the SerialNumber, Status, RevokedAt, +// RevocationReason, ThisUpdate, and NextUpdate fields. +// +// If template.IssuerHash is not set, SHA1 will be used. +// +// The ProducedAt date is automatically set to the current date, to the nearest minute. +func CreateResponse(issuer, responderCert *x509.Certificate, template Response, priv crypto.Signer) ([]byte, error) { + var publicKeyInfo struct { + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString + } + if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil { + return nil, err + } + + if template.IssuerHash == 0 { + template.IssuerHash = crypto.SHA1 + } + hashOID := getOIDFromHashAlgorithm(template.IssuerHash) + if hashOID == nil { + return nil, errors.New("unsupported issuer hash algorithm") + } + + if !template.IssuerHash.Available() { + return nil, fmt.Errorf("issuer hash algorithm %v not linked into binary", template.IssuerHash) + } + h := template.IssuerHash.New() + h.Write(publicKeyInfo.PublicKey.RightAlign()) + issuerKeyHash := h.Sum(nil) + + h.Reset() + h.Write(issuer.RawSubject) + issuerNameHash := h.Sum(nil) + + innerResponse := singleResponse{ + CertID: certID{ + HashAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: hashOID, + Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */}, + }, + NameHash: issuerNameHash, + IssuerKeyHash: issuerKeyHash, + SerialNumber: template.SerialNumber, + }, + ThisUpdate: template.ThisUpdate.UTC(), + NextUpdate: template.NextUpdate.UTC(), + SingleExtensions: template.ExtraExtensions, + } + + switch template.Status { + case Good: + innerResponse.Good = true + case Unknown: + innerResponse.Unknown = true + case Revoked: + innerResponse.Revoked = revokedInfo{ + RevocationTime: template.RevokedAt.UTC(), + Reason: asn1.Enumerated(template.RevocationReason), + } + } + + rawResponderID := asn1.RawValue{ + Class: 2, // context-specific + Tag: 1, // Name (explicit tag) + IsCompound: true, + Bytes: responderCert.RawSubject, + } + tbsResponseData := responseData{ + Version: 0, + RawResponderID: rawResponderID, + ProducedAt: time.Now().Truncate(time.Minute).UTC(), + Responses: []singleResponse{innerResponse}, + } + + tbsResponseDataDER, err := asn1.Marshal(tbsResponseData) + if err != nil { + return nil, err + } + + hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(priv.Public(), template.SignatureAlgorithm) + if err != nil { + return nil, err + } + + responseHash := hashFunc.New() + responseHash.Write(tbsResponseDataDER) + signature, err := priv.Sign(rand.Reader, responseHash.Sum(nil), hashFunc) + if err != nil { + return nil, err + } + + response := basicResponse{ + TBSResponseData: tbsResponseData, + SignatureAlgorithm: signatureAlgorithm, + Signature: asn1.BitString{ + Bytes: signature, + BitLength: 8 * len(signature), + }, + } + if template.Certificate != nil { + response.Certificates = []asn1.RawValue{ + {FullBytes: template.Certificate.Raw}, + } + } + responseDER, err := asn1.Marshal(response) + if err != nil { + return nil, err + } + + return asn1.Marshal(responseASN1{ + Status: asn1.Enumerated(Success), + Response: responseBytes{ + ResponseType: idPKIXOCSPBasic, + Response: responseDER, + }, + }) +}