| // 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. Invalid signatures or parse |
| // failures will result in a ParseError. |
| func ParseResponse(bytes []byte) (*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") |
| } |
| } |
| |
| 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. |
| Hash crypto.Hash |
| } |
| |
| func (opts *RequestOptions) hash() crypto.Hash { |
| if opts == nil { |
| // 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, |
| }, |
| }, |
| }, |
| }, |
| }) |
| } |