You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
792 lines
25 KiB
792 lines
25 KiB
// 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 { |
|
Raw []byte |
|
|
|
// 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{ |
|
Raw: bytes, |
|
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 populate 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, |
|
}, |
|
}) |
|
}
|
|
|