Skip to content

Commit a71eac0

Browse files
committed
fix OCSP
1 parent 86fe82a commit a71eac0

File tree

5 files changed

+140
-60
lines changed

5 files changed

+140
-60
lines changed

.lic.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
author: Mikhail Knyazhev <[email protected]>
22
lic_short: "BSD 3-Clause"
33
lic_file: LICENSE
4+
ignore_files:
5+
- pki/internal/xocsp/ocsp.go

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.25.0
55
require (
66
go.osspkg.com/casecheck v0.3.0
77
go.osspkg.com/errors v0.4.0
8+
go.osspkg.com/ioutils v0.7.3
89
go.osspkg.com/random v0.5.0
910
go.osspkg.com/syncing v0.4.3
1011
golang.org/x/crypto v0.43.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ go.osspkg.com/casecheck v0.3.0 h1:x15blEszElbrHrEH5H02JIIhGIg/lGZzIt1kQlD3pwM=
22
go.osspkg.com/casecheck v0.3.0/go.mod h1:TRFXDMFJEOtnlp3ET2Hix3osbxwPWhvaiT/HfD3+gBA=
33
go.osspkg.com/errors v0.4.0 h1:E17+WyUzTXEHCTxGm8lOMPOOojzHG1lsOuQtTVGoATQ=
44
go.osspkg.com/errors v0.4.0/go.mod h1:s75ZovPemYtrCtRPVsbQNq9MgMbmLMK1NEypr+uwjXI=
5+
go.osspkg.com/ioutils v0.7.3 h1:QF+Ra0bHoU3MGMGH5PGdV2lRLq1rWPdv/OB+v5UTjkI=
6+
go.osspkg.com/ioutils v0.7.3/go.mod h1:RO/43IM//Wq8RnLvEzivDAuM37mnLW3eWxTCVmkUaY4=
57
go.osspkg.com/random v0.5.0 h1:6x2CQ5Vb6PVyuGi6Ao3K6Pr2fzVviBPCEEJC5HQNSmg=
68
go.osspkg.com/random v0.5.0/go.mod h1:lsg3FI87PQdjhVWIVo2GXyPBclipljUxjMlWqRl2cck=
79
go.osspkg.com/syncing v0.4.3 h1:XioXG9zje1LNCsfQhNHkNPCQqPSJZHWTzM8Xig2zvAU=

pki/internal/xocsp/ocsp.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//FORK: golang.org/x/crypto/ocsp
2+
13
// Copyright 2013 The Go Authors. All rights reserved.
24
// Use of this source code is governed by a BSD-style
35
// license that can be found in the LICENSE file.
@@ -293,10 +295,6 @@ const (
293295
Revoked = 1
294296
// Unknown means that the OCSP responder doesn't know about the certificate.
295297
Unknown = 2
296-
// ServerFailed is unused and was never used (see
297-
// https://go-review.googlesource.com/#/c/18944). ParseResponse will
298-
// return a ResponseError when an error response is parsed.
299-
ServerFailed = 3
300298
)
301299

302300
// The enumerated reasons for revoking a certificate. See RFC 5280.
@@ -327,7 +325,7 @@ type Request struct {
327325
func (req *Request) Marshal() ([]byte, error) {
328326
hashAlg := getOIDFromHashAlgorithm(req.HashAlgorithm)
329327
if hashAlg == nil {
330-
return nil, errors.New("Unknown hash algorithm")
328+
return nil, errors.New("unknown hash algorithm")
331329
}
332330
return asn1.Marshal(ocspRequest{
333331
tbsRequest{
@@ -479,6 +477,8 @@ func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
479477
// multiple statuses and cert is not nil, then ParseResponseForCert will return
480478
// the first status which contains a matching serial, otherwise it will return an
481479
// error. If cert is nil, then the first status in the response will be returned.
480+
//
481+
//nolint:gocyclo
482482
func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Response, error) {
483483
var resp responseASN1
484484
rest, err := asn1.Unmarshal(bytes, &resp)
@@ -687,7 +687,9 @@ func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte
687687
// If template.IssuerHash is not set, SHA1 will be used.
688688
//
689689
// The ProducedAt date is automatically set to the current date, to the nearest minute.
690-
func CreateResponse(issuer, responderCert *x509.Certificate, template Response, priv crypto.Signer) ([]byte, error) {
690+
func CreateResponse(
691+
status ResponseStatus, issuer, responderCert *x509.Certificate, template Response, priv crypto.Signer,
692+
) ([]byte, error) {
691693
var publicKeyInfo struct {
692694
Algorithm pkix.AlgorithmIdentifier
693695
PublicKey asn1.BitString
@@ -792,7 +794,7 @@ func CreateResponse(issuer, responderCert *x509.Certificate, template Response,
792794
}
793795

794796
return asn1.Marshal(responseASN1{
795-
Status: asn1.Enumerated(Success),
797+
Status: asn1.Enumerated(status),
796798
Response: responseBytes{
797799
ResponseType: idPKIXOCSPBasic,
798800
Response: responseDER,

pki/ocsp.go

Lines changed: 126 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,89 +7,162 @@ package pki
77

88
import (
99
"context"
10+
"crypto"
1011
"crypto/x509/pkix"
11-
"io"
12+
"fmt"
13+
"math/big"
1214
"net/http"
1315
"time"
1416

17+
"go.osspkg.com/ioutils"
18+
1519
"go.osspkg.com/encrypt/pki/internal/xocsp"
16-
"golang.org/x/crypto/ocsp"
1720
)
1821

1922
type OCSPStatusResolver interface {
20-
OCSPStatusResolve(ctx context.Context, r *ocsp.Request) (OCSPStatus, error)
23+
OCSPStatusResolve(ctx context.Context, r *OCSPRequest) (*OCSPResponse, error)
2124
}
2225

2326
type OCSPStatus int
2427

2528
const (
26-
OCSPStatusUnknown OCSPStatus = ocsp.Unknown
27-
OCSPStatusGood OCSPStatus = ocsp.Good
28-
OCSPStatusRevoked OCSPStatus = ocsp.Revoked
29-
OCSPStatusServerFailed OCSPStatus = ocsp.ServerFailed
29+
OCSPStatusGood OCSPStatus = xocsp.Good
30+
OCSPStatusUnknown OCSPStatus = xocsp.Unknown
31+
OCSPStatusRevoked OCSPStatus = xocsp.Revoked
3032
)
3133

32-
type OCSPServer struct {
33-
CA Certificate
34-
Resolver OCSPStatusResolver
35-
UpdateInterval time.Duration
36-
}
34+
type OCSPRevocationReason int
3735

38-
func (v *OCSPServer) HTTPHandler(w http.ResponseWriter, r *http.Request) {
39-
defer r.Body.Close() //nolint:errcheck
40-
raw, err := io.ReadAll(r.Body)
41-
if err != nil {
42-
http.Error(w, "invalid request", http.StatusBadRequest)
43-
return
44-
}
36+
const (
37+
// OCSPRevocationReasonUnspecified
38+
// Unspecified (code 0): A general, default reason when a more specific one isn't applicable.
39+
OCSPRevocationReasonUnspecified OCSPRevocationReason = 0
40+
// OCSPRevocationReasonKeyCompromise
41+
// Key Compromise (code 1): The most critical reason, indicating that the
42+
// private key associated with the certificate has been compromised or is suspected of being compromised.
43+
OCSPRevocationReasonKeyCompromise OCSPRevocationReason = 1
44+
// OCSPRevocationReasonCACompromise
45+
// CA Compromise (code 2): The certificate authority that issued the certificate has been compromised.
46+
OCSPRevocationReasonCACompromise OCSPRevocationReason = 2
47+
// OCSPRevocationReasonAffiliationChanged
48+
// Affiliation Changed (code 3): The certificate holder's relationship with the organization has changed,
49+
// such as termination of employment.
50+
OCSPRevocationReasonAffiliationChanged OCSPRevocationReason = 3
51+
// OCSPRevocationReasonSuperseded
52+
// Superseded (code 4): The certificate has been replaced by a new one,
53+
// often because of a normal lifecycle event like a password change or a legal name change.
54+
OCSPRevocationReasonSuperseded OCSPRevocationReason = 4
55+
// OCSPRevocationReasonCessationOfOperation
56+
// Cessation of Operation (code 5): The system or service for which the certificate was issued is no longer in
57+
// operation.
58+
OCSPRevocationReasonCessationOfOperation OCSPRevocationReason = 5
59+
// OCSPRevocationReasonCertificateHold
60+
// Certificate Hold (code 6): Used for temporary invalidation, such as when a certificate's status is under review.
61+
OCSPRevocationReasonCertificateHold OCSPRevocationReason = 6
62+
)
4563

46-
req, err := xocsp.ParseRequest(raw)
47-
if err != nil {
48-
http.Error(w, "invalid ocsp data", http.StatusBadRequest)
49-
return
64+
type (
65+
OCSPServer struct {
66+
CA Certificate
67+
Resolver OCSPStatusResolver
68+
UpdateInterval time.Duration
69+
OnError func(err error)
5070
}
51-
52-
var nonce []byte
53-
for _, extension := range req.Extensions {
54-
if extension.Id.Equal(xocsp.OIDNonce) {
55-
nonce = extension.Value
56-
}
71+
OCSPRequest struct {
72+
HashAlgorithm crypto.Hash
73+
IssuerNameHash []byte
74+
IssuerKeyHash []byte
75+
SerialNumber *big.Int
76+
Extensions []pkix.Extension
5777
}
5878

59-
status, err := v.Resolver.OCSPStatusResolve(r.Context(), &ocsp.Request{
60-
HashAlgorithm: req.HashAlgorithm,
61-
IssuerNameHash: req.IssuerNameHash,
62-
IssuerKeyHash: req.IssuerKeyHash,
63-
SerialNumber: req.SerialNumber,
64-
})
65-
if err != nil {
66-
http.Error(w, err.Error(), http.StatusInternalServerError)
67-
return
79+
OCSPResponse struct {
80+
Status OCSPStatus
81+
RevokedAt time.Time
82+
RevocationReason OCSPRevocationReason
6883
}
84+
)
85+
86+
func (v *OCSPServer) HTTPHandler(w http.ResponseWriter, r *http.Request) {
87+
reqStatus := xocsp.Success
6988

7089
template := xocsp.Response{
71-
Status: int(status),
72-
SerialNumber: req.SerialNumber,
73-
ThisUpdate: time.Now(),
74-
NextUpdate: time.Now().Add(v.UpdateInterval),
75-
ProducedAt: time.Now(),
76-
Certificate: v.CA.Crt,
90+
Status: int(OCSPStatusUnknown),
91+
ThisUpdate: time.Now().Truncate(time.Minute).UTC(),
92+
NextUpdate: time.Now().Add(v.UpdateInterval).Truncate(time.Minute).UTC(),
93+
Certificate: v.CA.Crt,
7794
}
7895

79-
if len(nonce) > 0 {
80-
template.Extensions = append(template.Extensions, pkix.Extension{
81-
Id: xocsp.OIDNonce,
82-
Critical: false,
83-
Value: nonce,
84-
})
96+
var (
97+
err error
98+
raw []byte
99+
)
100+
101+
if raw, err = ioutils.ReadAll(r.Body); err == nil {
102+
103+
var req *xocsp.Request
104+
if req, err = xocsp.ParseRequest(raw); err == nil {
105+
106+
template.SerialNumber = req.SerialNumber
107+
108+
for _, extension := range req.Extensions {
109+
if extension.Id.Equal(xocsp.OIDNonce) {
110+
template.Extensions = append(template.Extensions, pkix.Extension{
111+
Id: xocsp.OIDNonce,
112+
Critical: false,
113+
Value: extension.Value,
114+
})
115+
break
116+
}
117+
}
118+
119+
var resp *OCSPResponse
120+
if resp, err = v.Resolver.OCSPStatusResolve(r.Context(), &OCSPRequest{
121+
HashAlgorithm: req.HashAlgorithm,
122+
IssuerNameHash: req.IssuerNameHash,
123+
IssuerKeyHash: req.IssuerKeyHash,
124+
SerialNumber: req.SerialNumber,
125+
Extensions: req.Extensions,
126+
}); err == nil {
127+
128+
template.Status = int(resp.Status)
129+
130+
if resp.Status == OCSPStatusRevoked {
131+
template.RevokedAt = resp.RevokedAt
132+
133+
switch resp.RevocationReason {
134+
case OCSPRevocationReasonKeyCompromise, OCSPRevocationReasonCACompromise,
135+
OCSPRevocationReasonAffiliationChanged, OCSPRevocationReasonSuperseded,
136+
OCSPRevocationReasonCessationOfOperation, OCSPRevocationReasonCertificateHold:
137+
template.RevocationReason = int(resp.RevocationReason)
138+
default:
139+
template.RevocationReason = int(OCSPRevocationReasonUnspecified)
140+
}
141+
}
142+
}
143+
}
144+
}
145+
146+
if err != nil {
147+
if v.OnError != nil {
148+
v.OnError(fmt.Errorf("ocsp: request processing: %w", err))
149+
}
150+
reqStatus = xocsp.InternalError
85151
}
86152

87-
resp, err := xocsp.CreateResponse(v.CA.Crt, v.CA.Crt, template, v.CA.Key)
153+
resp, err := xocsp.CreateResponse(reqStatus, v.CA.Crt, v.CA.Crt, template, v.CA.Key)
88154
if err != nil {
89-
http.Error(w, err.Error(), http.StatusInternalServerError)
155+
if v.OnError != nil {
156+
v.OnError(fmt.Errorf("ocsp: create response: %w", err))
157+
}
158+
http.Error(w, "internal error", http.StatusInternalServerError)
90159
return
91160
}
92161

93162
w.Header().Set("Content-Type", "application/ocsp-response")
94-
w.Write(resp) //nolint:errcheck
163+
if _, err = w.Write(resp); err != nil {
164+
if v.OnError != nil {
165+
v.OnError(fmt.Errorf("ocsp: write response: %w", err))
166+
}
167+
}
95168
}

0 commit comments

Comments
 (0)