19
19
% %
20
20
21
21
-module (pubkey_ocsp ).
22
+ -feature (maybe_expr ,enable ).
22
23
-include (" public_key.hrl" ).
23
24
24
25
-export ([find_single_response /3 ,
25
26
get_acceptable_response_types_extn /0 ,
26
27
get_nonce_extn /1 ,
27
- get_ocsp_responder_id /1 ,
28
- ocsp_status /1 ,
29
- verify_ocsp_response /3 ,
30
- decode_ocsp_response /1 ]).
28
+ status /1 ,
29
+ verify_response /5 ,
30
+ decode_response /1 ]).
31
31
% % Tracing
32
32
-export ([handle_trace /3 ]).
33
33
34
- -spec get_ocsp_responder_id (# 'Certificate' {}) -> binary ().
35
- get_ocsp_responder_id (# 'Certificate' {tbsCertificate = TbsCert }) ->
36
- public_key :der_encode (
37
- 'ResponderID' , {byName , TbsCert # 'TBSCertificate' .subject }).
38
-
39
34
-spec get_nonce_extn (undefined | binary ()) -> undefined | # 'Extension' {}.
40
35
get_nonce_extn (undefined ) ->
41
36
undefined ;
@@ -45,10 +40,29 @@ get_nonce_extn(Nonce) when is_binary(Nonce) ->
45
40
extnValue = Nonce
46
41
}.
47
42
48
- -spec verify_ocsp_response (# 'BasicOCSPResponse' {}, list (), undefined | binary ()) ->
43
+ -spec verify_response (# 'BasicOCSPResponse' {}, list (), undefined | binary (),
44
+ public_key :cert (), fun ()) ->
49
45
{ok , term ()} | {error , term ()}.
50
- verify_ocsp_response (OCSPResponse , ResponderCerts , Nonce ) ->
51
- do_verify_ocsp_response (OCSPResponse , ResponderCerts , Nonce ).
46
+ verify_response (# 'BasicOCSPResponse' {
47
+ tbsResponseData = ResponseData ,
48
+ signatureAlgorithm = SignatureAlgo ,
49
+ signature = Signature },
50
+ ResponderCerts , Nonce , IssuerCert ,
51
+ IsTrustedResponderFun ) ->
52
+ # 'ResponseData' {responderID = ResponderID ,
53
+ producedAt = ProducedAt } = ResponseData ,
54
+ maybe
55
+ ok ?= verify_past_timestamp (ProducedAt ),
56
+ ok ?= verify_signature (
57
+ public_key :der_encode ('ResponseData' , ResponseData ),
58
+ SignatureAlgo # 'AlgorithmIdentifier' .algorithm ,
59
+ Signature , ResponderCerts ,
60
+ ResponderID , IssuerCert , IsTrustedResponderFun ),
61
+ verify_nonce (ResponseData , Nonce )
62
+ else
63
+ {error , Reason } ->
64
+ {error , Reason }
65
+ end .
52
66
53
67
-spec get_acceptable_response_types_extn () -> # 'Extension' {}.
54
68
get_acceptable_response_types_extn () ->
@@ -67,15 +81,15 @@ find_single_response(Cert, IssuerCert, SingleResponseList) ->
67
81
SerialNum = get_serial_num (Cert ),
68
82
match_single_response (IssuerName , IssuerKey , SerialNum , SingleResponseList ).
69
83
70
- -spec ocsp_status ({atom (), term ()}) -> atom () | {atom () , {atom () , term ()}}.
71
- ocsp_status ({good , _ }) ->
72
- valid ;
73
- ocsp_status ({unknown , Reason }) ->
74
- {bad_cert , {revocation_status_undetermined , Reason }};
75
- ocsp_status ({revoked , Reason }) ->
76
- {bad_cert , {revoked , Reason }}.
84
+ -spec status ({atom (), term ()}) -> ok | {error , {bad_cert , term ()}}.
85
+ status ({good , _ }) ->
86
+ ok ;
87
+ status ({unknown , Reason }) ->
88
+ {error , { bad_cert , {revocation_status_undetermined , Reason } }};
89
+ status ({revoked , Reason }) ->
90
+ {error , { bad_cert , {revoked , Reason } }}.
77
91
78
- decode_ocsp_response (ResponseDer ) ->
92
+ decode_response (ResponseDer ) ->
79
93
Resp = public_key :der_decode ('OCSPResponse' , ResponseDer ),
80
94
case Resp # 'OCSPResponse' .responseStatus of
81
95
successful ->
@@ -92,16 +106,23 @@ match_single_response(_IssuerName, _IssuerKey, _SerialNum, []) ->
92
106
match_single_response (IssuerName , IssuerKey , SerialNum ,
93
107
[# 'SingleResponse' {
94
108
certID = # 'CertID' {hashAlgorithm = Algo } = CertID } =
95
- Response | Responses ]) ->
109
+ SingleResponse | Tail ]) ->
110
+ # 'SingleResponse' {thisUpdate = ThisUpdate ,
111
+ nextUpdate = NextUpdate } = SingleResponse ,
96
112
HashType = public_key :pkix_hash_type (Algo # 'AlgorithmIdentifier' .algorithm ),
97
113
case (SerialNum == CertID # 'CertID' .serialNumber ) andalso
98
114
(crypto :hash (HashType , IssuerName ) == CertID # 'CertID' .issuerNameHash ) andalso
99
- (crypto :hash (HashType , IssuerKey ) == CertID # 'CertID' .issuerKeyHash ) of
115
+ (crypto :hash (HashType , IssuerKey ) == CertID # 'CertID' .issuerKeyHash ) andalso
116
+ verify_past_timestamp (ThisUpdate ) == ok andalso
117
+ verify_next_update (NextUpdate ) == ok of
100
118
true ->
101
- {ok , Response };
119
+ {ok , SingleResponse };
102
120
false ->
103
- match_single_response (IssuerName , IssuerKey , SerialNum , Responses )
104
- end .
121
+ match_single_response (IssuerName , IssuerKey , SerialNum , Tail )
122
+ end ;
123
+ match_single_response (IssuerName , IssuerKey , SerialNum ,
124
+ [_BadSingleResponse | Tail ]) ->
125
+ match_single_response (IssuerName , IssuerKey , SerialNum , Tail ).
105
126
106
127
get_serial_num (# 'OTPCertificate' {tbsCertificate = TbsCert }) ->
107
128
TbsCert # 'OTPTBSCertificate' .serialNumber .
@@ -113,24 +134,7 @@ decode_response_bytes(#'ResponseBytes'{
113
134
decode_response_bytes (# 'ResponseBytes' {responseType = RespType }) ->
114
135
{error , {ocsp_response_type_not_supported , RespType }}.
115
136
116
- do_verify_ocsp_response (# 'BasicOCSPResponse' {
117
- tbsResponseData = ResponseData ,
118
- signatureAlgorithm = SignatureAlgo ,
119
- signature = Signature },
120
- ResponderCerts , Nonce ) ->
121
- # 'ResponseData' {responderID = ResponderID } = ResponseData ,
122
- case verify_ocsp_signature (
123
- public_key :der_encode ('ResponseData' , ResponseData ),
124
- SignatureAlgo # 'AlgorithmIdentifier' .algorithm ,
125
- Signature , ResponderCerts ,
126
- ResponderID ) of
127
- ok ->
128
- verify_ocsp_nonce (ResponseData , Nonce );
129
- {error , Reason } ->
130
- {error , Reason }
131
- end .
132
-
133
- verify_ocsp_nonce (ResponseData , Nonce ) ->
137
+ verify_nonce (ResponseData , Nonce ) ->
134
138
# 'ResponseData' {responses = Responses , responseExtensions = ResponseExtns } =
135
139
ResponseData ,
136
140
case get_nonce_value (ResponseExtns ) of
@@ -153,31 +157,91 @@ get_nonce_value([#'Extension'{
153
157
get_nonce_value ([_Extn | Rest ]) ->
154
158
get_nonce_value (Rest ).
155
159
156
- verify_ocsp_signature (ResponseDataDer , SignatureAlgo , Signature ,
157
- Certs , ResponderID ) ->
158
- case find_responder_cert (ResponderID , Certs ) of
159
- {ok , Cert } ->
160
- do_verify_ocsp_signature (
161
- ResponseDataDer , Signature , SignatureAlgo , Cert );
162
- {error , Reason } ->
163
- {error , Reason }
160
+ verify_signature (_ , _ , _ , [], _ , _ , _ ) ->
161
+ {error , ocsp_responder_cert_not_found };
162
+ verify_signature (ResponseDataDer , SignatureAlgo , Signature ,
163
+ [ResponderCert | RCs ], ResponderID , IssuerCert ,
164
+ IsTrustedResponderFun ) ->
165
+ maybe
166
+ true ?= is_responder_cert (ResponderID , ResponderCert ),
167
+ true ?= is_authorized_responder (ResponderCert , IssuerCert ,
168
+ IsTrustedResponderFun ),
169
+ ok ?= do_verify_signature (ResponseDataDer , Signature , SignatureAlgo ,
170
+ ResponderCert )
171
+ else
172
+ _ ->
173
+ verify_signature (ResponseDataDer , SignatureAlgo , Signature ,
174
+ RCs , ResponderID , IssuerCert ,
175
+ IsTrustedResponderFun )
164
176
end .
165
177
166
- find_responder_cert (_ResponderID , []) ->
167
- {error , ocsp_responder_cert_not_found };
168
- find_responder_cert (ResponderID , [Cert | TCerts ]) ->
169
- case is_responder (ResponderID , Cert ) of
178
+ verify_past_timestamp (Timestamp ) ->
179
+ {Now , TimestampSec } = get_time_in_sec (Timestamp ),
180
+ verify_timestamp (Now , TimestampSec , past_timestamp ).
181
+
182
+ verify_future_timestamp (Timestamp ) ->
183
+ {Now , TimestampSec } = get_time_in_sec (Timestamp ),
184
+ verify_timestamp (Now , TimestampSec , future_timestamp ).
185
+
186
+ verify_timestamp (Now , Timestamp , past_timestamp ) when Timestamp =< Now ->
187
+ ok ;
188
+ verify_timestamp (Now , Timestamp , future_timestamp ) when Now =< Timestamp ->
189
+ ok ;
190
+ verify_timestamp (_ , _ , _ ) ->
191
+ {error , ocsp_stale_response }.
192
+
193
+ get_time_in_sec (Timestamp ) ->
194
+ Now = calendar :datetime_to_gregorian_seconds (calendar :universal_time ()),
195
+ TimestampSec = pubkey_cert :time_str_2_gregorian_sec (
196
+ {generalTime , Timestamp }),
197
+ {Now , TimestampSec }.
198
+
199
+ verify_next_update (asn1_NOVALUE ) ->
200
+ ok ;
201
+ verify_next_update (NextUpdate ) ->
202
+ verify_future_timestamp (NextUpdate ).
203
+
204
+ is_responder_cert ({byName , Name }, # cert {otp = Cert }) ->
205
+ public_key :der_encode ('Name' , Name ) == get_subject_name (Cert );
206
+ is_responder_cert ({byKey , Key }, # cert {otp = Cert }) ->
207
+ Key == crypto :hash (sha , get_public_key (Cert )).
208
+
209
+ is_authorized_responder (CombinedResponderCert = # cert {otp = ResponderCert },
210
+ IssuerCert , IsTrustedResponderFun ) ->
211
+ Case1 =
212
+ % % the CA who issued the certificate in question signed the
213
+ % % response
214
+ fun () ->
215
+ ResponderCert == IssuerCert
216
+ end ,
217
+ Case2 =
218
+ % % a CA Designated Responder (Authorized Responder, defined in
219
+ % % Section 4.2.2.2) who holds a specially marked certificate
220
+ % % issued directly by the CA, indicating that the responder may
221
+ % % issue OCSP responses for that CA (id-kp-OCSPSigning)
222
+ fun () ->
223
+ public_key :pkix_is_issuer (ResponderCert , IssuerCert ) andalso
224
+ designated_for_ocsp_signing (ResponderCert )
225
+ end ,
226
+ Case3 =
227
+ % % a Trusted Responder whose public key is trusted by the requestor
228
+ fun () ->
229
+ IsTrustedResponderFun (CombinedResponderCert )
230
+ end ,
231
+
232
+ case lists :any (fun (E ) -> E () end , [Case1 , Case2 , Case3 ]) of
170
233
true ->
171
- { ok , Cert } ;
234
+ true ;
172
235
false ->
173
- find_responder_cert ( ResponderID , TCerts )
236
+ not_authorized_responder
174
237
end .
175
238
176
- do_verify_ocsp_signature (ResponseDataDer , Signature , AlgorithmID , Cert ) ->
239
+ do_verify_signature (ResponseDataDer , Signature , AlgorithmID ,
240
+ # cert {otp = ResponderCert }) ->
177
241
{DigestType , _SignatureType } = public_key :pkix_sign_types (AlgorithmID ),
178
242
case public_key :verify (
179
243
ResponseDataDer , DigestType , Signature ,
180
- get_public_key_rec (Cert )) of
244
+ get_public_key_rec (ResponderCert )) of
181
245
true ->
182
246
ok ;
183
247
false ->
@@ -188,11 +252,6 @@ get_public_key_rec(#'OTPCertificate'{tbsCertificate = TbsCert}) ->
188
252
PKInfo = TbsCert # 'OTPTBSCertificate' .subjectPublicKeyInfo ,
189
253
PKInfo # 'OTPSubjectPublicKeyInfo' .subjectPublicKey .
190
254
191
- is_responder ({byName , Name }, Cert ) ->
192
- public_key :der_encode ('Name' , Name ) == get_subject_name (Cert );
193
- is_responder ({byKey , Key }, Cert ) ->
194
- Key == crypto :hash (sha , get_public_key (Cert )).
195
-
196
255
get_subject_name (# 'OTPCertificate' {tbsCertificate = TbsCert }) ->
197
256
public_key :pkix_encode ('Name' , TbsCert # 'OTPTBSCertificate' .subject , otp ).
198
257
@@ -207,22 +266,26 @@ enc_pub_key({DsaInt, #'Dss-Parms'{}}) when is_integer(DsaInt) ->
207
266
enc_pub_key ({# 'ECPoint' {point = Key }, _ECParam }) ->
208
267
Key .
209
268
269
+ designated_for_ocsp_signing (OtpCert ) ->
270
+ TBSCert = OtpCert # 'OTPCertificate' .tbsCertificate ,
271
+ TBSExtensions = TBSCert # 'OTPTBSCertificate' .extensions ,
272
+ Extensions = pubkey_cert :extensions_list (TBSExtensions ),
273
+ case pubkey_cert :select_extension (?'id-ce-extKeyUsage' , Extensions ) of
274
+ undefined ->
275
+ false ;
276
+ # 'Extension' {extnValue = KeyUses } ->
277
+ lists :member (?'id-kp-OCSPSigning' , KeyUses )
278
+ end .
279
+
210
280
% %%################################################################
211
281
% %%#
212
282
% %%# Tracing
213
283
% %%#
214
- handle_trace (csp ,
215
- {call , {? MODULE , do_verify_ocsp_response , [BasicOcspResponse | _ ]}}, Stack ) ->
216
- # 'BasicOCSPResponse' {
217
- tbsResponseData =
218
- # 'ResponseData' {responderID = ResponderID ,
219
- producedAt = ProducedAt }} = BasicOcspResponse ,
220
- {io_lib :format (" ResponderId = ~W producedAt = ~p " , [ResponderID , 5 , ProducedAt ]), Stack };
221
284
handle_trace (csp ,
222
285
{call , {? MODULE , match_single_response ,
223
286
[_IssuerName , _IssuerKey , _SerialNum ,
224
287
[# 'SingleResponse' {thisUpdate = ThisUpdate ,
225
- nextUpdate = NextUpdate }]]}}, Stack ) ->
288
+ nextUpdate = NextUpdate } | _ ]]}}, Stack ) ->
226
289
{io_lib :format (" ThisUpdate = ~p NextUpdate = ~p " , [ThisUpdate , NextUpdate ]), Stack };
227
290
handle_trace (csp ,
228
291
{call , {? MODULE , is_responder , [Id , Cert ]}}, Stack ) ->
0 commit comments