Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions lib/public_key/src/pubkey_ocsp.erl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
%% Tracing
-export([handle_trace/3]).

%% 100 KB — aligned with OpenSSL OSSL_HTTP_DEFAULT_MAX_RESP_LEN.
%% Typical OCSP responses are 1–5 KB.
-define(MAX_OCSP_RESPONSE_SIZE, 102400).

-spec get_nonce_extn(undefined | binary()) -> undefined | #'Extension'{}.
get_nonce_extn(undefined) ->
undefined;
Expand Down Expand Up @@ -92,7 +96,8 @@ status({unknown, Reason}, _) ->
status({revoked, Reason}, _) ->
{error, {bad_cert, {revoked, Reason}}}.

decode_response(ResponseDer) ->
decode_response(ResponseDer)
when byte_size(ResponseDer) =< ?MAX_OCSP_RESPONSE_SIZE ->
Resp = public_key:der_decode('OCSPResponse', ResponseDer),
case Resp#'OCSPResponse'.responseStatus of
successful ->
Expand All @@ -101,7 +106,9 @@ decode_response(ResponseDer) ->
);
Error ->
{error, Error}
end.
end;
decode_response(ResponseDer) when is_binary(ResponseDer) ->
{error, {ocsp_response_too_large, byte_size(ResponseDer)}}.

%%--------------------------------------------------------------------
match_single_response(_IssuerName, _IssuerKey, _SerialNum, []) ->
Expand All @@ -113,9 +120,12 @@ match_single_response(IssuerName, IssuerKey, SerialNum,
#'SingleResponse'{thisUpdate = ThisUpdate,
nextUpdate = NextUpdate} = SingleResponse,
HashType = public_key:pkix_hash_type(Algo#'CertID_hashAlgorithm'.algorithm),
case (SerialNum == CertID#'CertID'.serialNumber) andalso
(crypto:hash(HashType, IssuerName) == CertID#'CertID'.issuerNameHash) andalso
(crypto:hash(HashType, IssuerKey) == CertID#'CertID'.issuerKeyHash) andalso
SerialMatch = (SerialNum == CertID#'CertID'.serialNumber),
NameHashMatch = hash_equals(crypto:hash(HashType, IssuerName),
CertID#'CertID'.issuerNameHash),
KeyHashMatch = hash_equals(crypto:hash(HashType, IssuerKey),
CertID#'CertID'.issuerKeyHash),
case SerialMatch andalso NameHashMatch andalso KeyHashMatch andalso
verify_past_timestamp(ThisUpdate) == ok andalso
verify_next_update(NextUpdate) == ok of
true ->
Expand Down Expand Up @@ -214,7 +224,7 @@ verify_next_update(NextUpdate) ->
is_responder_cert({byName, Name}, #cert{otp = Cert}) ->
public_key:der_encode('Name', Name) == get_subject_name(Cert);
is_responder_cert({byKey, Key}, #cert{otp = Cert}) ->
Key == crypto:hash(sha, get_public_key(Cert)).
hash_equals(Key, crypto:hash(sha, get_public_key(Cert))).

is_authorized_responder(CombinedResponderCert = #cert{otp = ResponderCert},
IssuerCert, IsTrustedResponderFun) ->
Expand Down Expand Up @@ -295,6 +305,16 @@ designated_for_ocsp_signing(OtpCert) ->
lists:member(?'id-kp-OCSPSigning', KeyUses)
end.

%% Constant-time comparison that handles mismatched sizes gracefully.
%% crypto:hash_equals/2 requires equal-length binaries. If sizes differ,
%% the CertID cannot match (hash algorithm mismatch). No timing concern:
%% the expected length is determined by the hashAlgorithm OID in the same
%% CertID, which the sender chose — not a secret.
hash_equals(A, B) when byte_size(A) =:= byte_size(B) ->
crypto:hash_equals(A, B);
hash_equals(_, _) ->
false.

%%%################################################################
%%%#
%%%# Tracing
Expand Down
Loading