diff --git a/lib/public_key/src/pubkey_ocsp.erl b/lib/public_key/src/pubkey_ocsp.erl index f7e5d7dac629..4e1b35bac86e 100644 --- a/lib/public_key/src/pubkey_ocsp.erl +++ b/lib/public_key/src/pubkey_ocsp.erl @@ -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; @@ -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 -> @@ -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, []) -> @@ -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 -> @@ -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) -> @@ -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