d2ea21d0d8
* use certmagic for more extensible/robust ACME cert handling * accept TOS based on config option Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>
789 lines
25 KiB
Go
Vendored
789 lines
25 KiB
Go
Vendored
// 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 it's 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. The response must contain
|
|
// only one certificate status. To parse the status of a specific certificate
|
|
// from a response which may contain multiple statuses, use ParseResponseForCert
|
|
// instead.
|
|
//
|
|
// If the response contains an embedded certificate, then that certificate will
|
|
// be used to verify the response signature. If the response contains an
|
|
// embedded certificate and issuer is not nil, then issuer will be used to verify
|
|
// the signature on the embedded certificate.
|
|
//
|
|
// If the response does not contain an embedded certificate and issuer is not
|
|
// nil, then issuer will be used to verify the response signature.
|
|
//
|
|
// 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 acts identically to ParseResponse, except it supports
|
|
// parsing responses that contain multiple statuses. If the response contains
|
|
// multiple statuses and cert is not nil, then ParseResponseForCert will return
|
|
// the first status which contains a matching serial, otherwise it will return an
|
|
// error. If cert is nil, then the first status in the response will be returned.
|
|
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 len(rest) > 0 {
|
|
return nil, ParseError("trailing data in OCSP response")
|
|
}
|
|
|
|
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,
|
|
},
|
|
})
|
|
}
|