Paul van Brouwershaven pointed out that it would be better to pass in the issuing certificate and have the verification be done in the OCSP package than to expect the caller to deal with the difference between responses with and without a responder certificate. R=golang-dev, dayveday, paul CC=golang-dev https://golang.org/cl/11220043
348 lines
11 KiB
Go
348 lines
11 KiB
Go
// 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 (
|
|
"crypto"
|
|
_ "crypto/sha1"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"math/big"
|
|
"time"
|
|
)
|
|
|
|
var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})
|
|
|
|
// These are internal structures that reflect the ASN.1 structure of an OCSP
|
|
// response. See RFC 2560, section 4.2.
|
|
|
|
const (
|
|
ocspSuccess = 0
|
|
ocspMalformed = 1
|
|
ocspInternalError = 2
|
|
ocspTryLater = 3
|
|
ocspSigRequired = 4
|
|
ocspUnauthorized = 5
|
|
)
|
|
|
|
type certID struct {
|
|
HashAlgorithm pkix.AlgorithmIdentifier
|
|
NameHash []byte
|
|
IssuerKeyHash []byte
|
|
SerialNumber *big.Int
|
|
}
|
|
|
|
type responseASN1 struct {
|
|
Status asn1.Enumerated
|
|
Response responseBytes `asn1:"explicit,tag:0"`
|
|
}
|
|
|
|
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:1,explicit,tag:0"`
|
|
RequestorName pkix.RDNSequence `asn1:"optional,explicit,tag:1"`
|
|
KeyHash []byte `asn1:"optional,explicit,tag:2"`
|
|
ProducedAt time.Time
|
|
Responses []singleResponse
|
|
}
|
|
|
|
type singleResponse struct {
|
|
CertID certID
|
|
Good asn1.Flag `asn1:"explicit,tag:0,optional"`
|
|
Revoked revokedInfo `asn1:"explicit,tag:1,optional"`
|
|
Unknown asn1.Flag `asn1:"explicit,tag:2,optional"`
|
|
ThisUpdate time.Time
|
|
NextUpdate time.Time `asn1:"explicit,tag:0,optional"`
|
|
}
|
|
|
|
type revokedInfo struct {
|
|
RevocationTime time.Time
|
|
Reason int `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, 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}
|
|
)
|
|
|
|
// 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 {
|
|
switch {
|
|
case oid.Equal(oidSignatureMD2WithRSA):
|
|
return x509.MD2WithRSA
|
|
case oid.Equal(oidSignatureMD5WithRSA):
|
|
return x509.MD5WithRSA
|
|
case oid.Equal(oidSignatureSHA1WithRSA):
|
|
return x509.SHA1WithRSA
|
|
case oid.Equal(oidSignatureSHA256WithRSA):
|
|
return x509.SHA256WithRSA
|
|
case oid.Equal(oidSignatureSHA384WithRSA):
|
|
return x509.SHA384WithRSA
|
|
case oid.Equal(oidSignatureSHA512WithRSA):
|
|
return x509.SHA512WithRSA
|
|
case oid.Equal(oidSignatureDSAWithSHA1):
|
|
return x509.DSAWithSHA1
|
|
case oid.Equal(oidSignatureDSAWithSHA256):
|
|
return x509.DSAWithSHA256
|
|
case oid.Equal(oidSignatureECDSAWithSHA1):
|
|
return x509.ECDSAWithSHA1
|
|
case oid.Equal(oidSignatureECDSAWithSHA256):
|
|
return x509.ECDSAWithSHA256
|
|
case oid.Equal(oidSignatureECDSAWithSHA384):
|
|
return x509.ECDSAWithSHA384
|
|
case oid.Equal(oidSignatureECDSAWithSHA512):
|
|
return x509.ECDSAWithSHA512
|
|
}
|
|
return x509.UnknownSignatureAlgorithm
|
|
}
|
|
|
|
// This is the exposed reflection of the internal OCSP structures.
|
|
|
|
const (
|
|
// Good means that the certificate is valid.
|
|
Good = iota
|
|
// Revoked means that the certificate has been deliberately revoked.
|
|
Revoked = iota
|
|
// Unknown means that the OCSP responder doesn't know about the certificate.
|
|
Unknown = iota
|
|
// ServerFailed means that the OCSP responder failed to process the request.
|
|
ServerFailed = iota
|
|
)
|
|
|
|
// Response represents an OCSP response. See RFC 2560.
|
|
type Response struct {
|
|
// Status is one of {Good, Revoked, Unknown, ServerFailed}
|
|
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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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
|
|
// signatures or parse failures will result in a ParseError.
|
|
func ParseResponse(bytes []byte, 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")
|
|
}
|
|
|
|
ret := new(Response)
|
|
if resp.Status != ocspSuccess {
|
|
ret.Status = ServerFailed
|
|
return ret, nil
|
|
}
|
|
|
|
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(basicResp.Certificates) > 1 {
|
|
return nil, ParseError("OCSP response contains bad number of certificates")
|
|
}
|
|
|
|
if len(basicResp.TBSResponseData.Responses) != 1 {
|
|
return nil, ParseError("OCSP response contains bad number of responses")
|
|
}
|
|
|
|
ret.TBSResponseData = basicResp.TBSResponseData.Raw
|
|
ret.Signature = basicResp.Signature.RightAlign()
|
|
ret.SignatureAlgorithm = getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm)
|
|
|
|
if len(basicResp.Certificates) > 0 {
|
|
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 OCSP signature")
|
|
}
|
|
|
|
if issuer != nil {
|
|
if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil {
|
|
return nil, ParseError("bad signature on embedded certificate")
|
|
}
|
|
}
|
|
} else if issuer != nil {
|
|
if err := ret.CheckSignatureFrom(issuer); err != nil {
|
|
return nil, ParseError("bad OCSP signature")
|
|
}
|
|
}
|
|
|
|
r := basicResp.TBSResponseData.Responses[0]
|
|
|
|
ret.SerialNumber = r.CertID.SerialNumber
|
|
|
|
switch {
|
|
case bool(r.Good):
|
|
ret.Status = Good
|
|
case bool(r.Unknown):
|
|
ret.Status = Unknown
|
|
default:
|
|
ret.Status = Revoked
|
|
ret.RevokedAt = r.Revoked.RevocationTime
|
|
ret.RevocationReason = r.Revoked.Reason
|
|
}
|
|
|
|
ret.ProducedAt = basicResp.TBSResponseData.ProducedAt
|
|
ret.ThisUpdate = r.ThisUpdate
|
|
ret.NextUpdate = r.NextUpdate
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// 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"`
|
|
RequestList []request
|
|
}
|
|
|
|
type request struct {
|
|
Cert certID
|
|
}
|
|
|
|
// 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
|
|
var hashOID asn1.ObjectIdentifier
|
|
switch hashFunc {
|
|
case crypto.SHA1:
|
|
hashOID = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
|
|
case crypto.SHA256:
|
|
hashOID = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1})
|
|
case crypto.SHA384:
|
|
hashOID = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2})
|
|
case crypto.SHA512:
|
|
hashOID = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3})
|
|
default:
|
|
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)
|
|
|
|
return asn1.Marshal(ocspRequest{
|
|
tbsRequest{
|
|
Version: 0,
|
|
RequestList: []request{
|
|
{
|
|
Cert: certID{
|
|
pkix.AlgorithmIdentifier{
|
|
Algorithm: hashOID,
|
|
Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
|
|
},
|
|
issuerNameHash,
|
|
issuerKeyHash,
|
|
cert.SerialNumber,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|