347 lines
11 KiB
Go
347 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 "golang.org/x/crypto/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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|