From 3514f176a55db0c9052c3857c6fcba35726945dc Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Fri, 16 Jun 2017 15:38:06 +0200
Subject: ssl,public_key: Provide details for CRL check failiures when
 revokation state can not be determined

---
 lib/public_key/doc/src/public_key.xml | 16 ++++++++++++----
 lib/public_key/include/public_key.hrl |  3 ++-
 lib/public_key/src/pubkey_crl.erl     | 35 +++++++++++++++++++++--------------
 lib/public_key/src/public_key.erl     | 29 ++++++++++++++++++++++-------
 lib/ssl/src/ssl_handshake.erl         | 14 +++++++++-----
 5 files changed, 66 insertions(+), 31 deletions(-)

diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index 04966ffb9c..8d4191d112 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -621,8 +621,8 @@ fun(OtpCert :: #'OTPCertificate'{},
        <v>OTPCertificate =  #'OTPCertificate'{}</v>
        <v>DPAndCRLs  = [{DP::#'DistributionPoint'{}, {DerCRL::der_encoded(), CRL::#'CertificateList'{}}}] </v>
        <v>Options = proplists:proplist()</v>
-       <v>CRLStatus() =  valid | {bad_cert, revocation_status_undetermined} |
-       {bad_cert, {revoked, crl_reason()}}</v>
+       <v>CRLStatus() =  valid | {bad_cert, revocation_status_undetermined} |  {bad_cert, {revocation_status_undetermined,
+       {bad_crls, Details::term()}}} | {bad_cert, {revoked, crl_reason()}}</v>
      </type>
      <desc>
       <p>Performs CRL validation. It is intended to be called from
@@ -650,7 +650,7 @@ fun(OtpCert :: #'OTPCertificate'{},
 	<tag>{issuer_fun, fun()}</tag>
 	<item>
 	  <p>The fun has the following type specification:</p>
-
+	  
 	  <code>
 fun(#'DistributionPoint'{}, #'CertificateList'{},
     {rdnSequence,[#'AttributeTypeAndValue'{}]}, term()) ->
@@ -660,7 +660,15 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
 	  that has signed the CRL. 
 	  </p>
 	  <code> fun(DP, CRL, Issuer, UserState) -> {ok, RootCert, CertChain}</code>
-	</item>	
+	</item>
+
+	<tag>{undetermined_details, boolean()}</tag>
+	<item>
+	  <p>Defaults to false. When revocation status can not be
+	  determined, and this option is set to true, details of why no
+	  CRLs where accepted are included in the return value.</p>
+	</item>
+
       </taglist>
     </desc>
    </func>
diff --git a/lib/public_key/include/public_key.hrl b/lib/public_key/include/public_key.hrl
index a1e7dd31bc..663e1856ac 100644
--- a/lib/public_key/include/public_key.hrl
+++ b/lib/public_key/include/public_key.hrl
@@ -70,7 +70,8 @@
 	  reasons_mask,
 	  cert_status,
 	  interim_reasons_mask,
-	  valid_ext
+	  valid_ext,
+          details
 	 }).
 
 -record('ECPoint', {
diff --git a/lib/public_key/src/pubkey_crl.erl b/lib/public_key/src/pubkey_crl.erl
index 33bef91827..3621e9c0da 100644
--- a/lib/public_key/src/pubkey_crl.erl
+++ b/lib/public_key/src/pubkey_crl.erl
@@ -58,7 +58,8 @@ validate(OtpCert, OtherDPCRLs, DP, {DerCRL, CRL}, {DerDeltaCRL, DeltaCRL},
 init_revokation_state() ->
     #revoke_state{reasons_mask = sets:new(),
 		  interim_reasons_mask = sets:new(),
-		  cert_status = unrevoked}.
+		  cert_status = unrevoked,
+                  details = []}.
 
 fresh_crl(_, {undefined, undefined}, _) ->
     %% Typically happens when there is no delta CRL that covers a CRL
@@ -152,9 +153,10 @@ verify_crl(OtpCert, DP, CRL, DerCRL, DeltaCRL, DerDeltaCRL, OtherDPCRLs,
 					       RevokedState,
 					       CRL, DerCRL, DeltaCRL, DerDeltaCRL,
 					       IssuerFun, TrustedOtpCert, Path, OtherDPCRLs, IDP);
-		_ ->
-		    {invalid, State0#revoke_state{valid_ext = ValidExt}}
-	    end;
+	        _ ->
+                    Details = RevokedState#revoke_state.details,
+		    {invalid, RevokedState#revoke_state{valid_ext = ValidExt, details = [{{bad_crl, no_issuer_cert_chain}, CRL} | Details]}}
+            end;
 	{error, issuer_not_found} ->
 	    case Fun(DP, CRL, issuer_not_found, AdditionalArgs) of
 		{ok, TrustedOtpCert, Path} ->
@@ -163,13 +165,16 @@ verify_crl(OtpCert, DP, CRL, DerCRL, DeltaCRL, DerDeltaCRL, OtherDPCRLs,
 					       DerDeltaCRL, IssuerFun,
 					       TrustedOtpCert, Path, OtherDPCRLs, IDP);
 		_ ->
-		    {invalid, {skip, State0}}
-	    end
+                    Details = State0#revoke_state.details,
+		    {invalid, {skip, State0#revoke_state{details = [{{bad_crl, no_issuer_cert_chain}, CRL} | Details] }}}
+            end
     catch
-	throw:{bad_crl, invalid_issuer} ->
-	    {invalid, {skip, State0}};
-	throw:_ ->
-	    {invalid, State0#revoke_state{valid_ext = ValidExt}}
+	throw:{bad_crl, invalid_issuer} = Reason ->
+            Details = RevokedState#revoke_state.details,
+	    {invalid, {skip, RevokedState#revoke_state{details = [{Reason, CRL} | Details]}}};
+	throw:Reason ->
+            Details = RevokedState#revoke_state.details,
+	    {invalid, RevokedState#revoke_state{details =  [{Reason, CRL} | Details]}}
     end.
 
 verify_mask_and_signatures(Revoked, DeltaRevoked, RevokedState, CRL, DerCRL, DeltaCRL, DerDeltaCRL,
@@ -183,10 +188,12 @@ verify_mask_and_signatures(Revoked, DeltaRevoked, RevokedState, CRL, DerCRL, Del
 				     TrustedOtpCert, Path, IssuerFun, OtherDPCRLs, IDP),
 	{valid, Revoked, DeltaRevoked, RevokedState#revoke_state{reasons_mask = ReasonsMask}, IDP}
     catch
-	throw:_ ->
-	    {invalid, RevokedState};
+	throw:Reason ->
+            Details = RevokedState#revoke_state.details,
+	    {invalid, RevokedState#revoke_state{details =  [{Reason, CRL} | Details]}};
 	error:{badmatch, _} ->
-	    {invalid, RevokedState}
+            Details = RevokedState#revoke_state.details,
+	    {invalid, RevokedState#revoke_state{details = [{{bad_crl, invalid_signature}, CRL} | Details]}}
     end.
 
 
@@ -356,7 +363,7 @@ verify_scope(#'OTPCertificate'{tbsCertificate = TBSCert}, #'DistributionPoint'{c
     verify_scope(DPName, IDPName, Names, TBSCert, IDP).
 
 verify_scope(asn1_NOVALUE, _, asn1_NOVALUE, _, _) ->
-    throw({bad_crl, scope_error1});
+    throw({bad_crl, scope_error});
 verify_scope(asn1_NOVALUE, IDPName, DPIssuerNames, TBSCert, IDP) ->
     verify_dp_name(IDPName, DPIssuerNames),
     verify_dp_bools(TBSCert, IDP);
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 6651e9510e..341118e5a2 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -789,8 +789,9 @@ pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options)
 %--------------------------------------------------------------------
 -spec pkix_crls_validate(#'OTPCertificate'{},
 			 [{DP::#'DistributionPoint'{}, {DerCRL::binary(), CRL::#'CertificateList'{}}}],
-			 Options :: proplists:proplist()) -> valid | {bad_cert, revocation_status_undetermined}
-								| {bad_cert, {revoked, crl_reason()}}.
+			 Options :: proplists:proplist()) -> valid | {bad_cert, revocation_status_undetermined} |
+                                                             {bad_cert, {revocation_status_undetermined, Reason::term()}} |
+                                                             {bad_cert, {revoked, crl_reason()}}.
 
 %% Description: Performs a CRL validation according to RFC 5280.
 %%--------------------------------------------------------------------
@@ -1121,8 +1122,13 @@ der_cert(#'OTPCertificate'{} = Cert) ->
 der_cert(Der) when is_binary(Der) ->
     Der.
 
-pkix_crls_validate(_, [],_, _, _) ->
-    {bad_cert, revocation_status_undetermined};
+pkix_crls_validate(_, [],_, Options, #revoke_state{details = Details}) ->
+     case proplists:get_value(undetermined_details, Options, false) of
+         false ->
+             {bad_cert, revocation_status_undetermined};
+         true ->
+             {bad_cert, {revocation_status_undetermined, {bad_crls, format_details(Details)}}}
+     end;
 pkix_crls_validate(OtpCert, [{DP, CRL, DeltaCRL} | Rest],  All, Options, RevokedState0) ->
     CallBack = proplists:get_value(update_crl, Options, fun(_, CurrCRL) ->
 							       CurrCRL
@@ -1142,9 +1148,14 @@ pkix_crls_validate(OtpCert, [{DP, CRL, DeltaCRL} | Rest],  All, Options, Revoked
 do_pkix_crls_validate(OtpCert, [{DP, CRL, DeltaCRL} | Rest],  All, Options, RevokedState0) ->
     OtherDPCRLs = All -- [{DP, CRL, DeltaCRL}],
     case pubkey_crl:validate(OtpCert, OtherDPCRLs, DP, CRL, DeltaCRL, Options, RevokedState0) of
-	{undetermined, _, _} when Rest == []->
-	    {bad_cert, revocation_status_undetermined};
-	{undetermined, _, RevokedState} when Rest =/= []->
+	{undetermined, unrevoked, #revoke_state{details = Details}} when Rest == []->
+            case proplists:get_value(undetermined_details, Options, false) of
+                false ->
+                    {bad_cert, revocation_status_undetermined};
+                true ->
+                    {bad_cert, {revocation_status_undetermined, {bad_crls, Details}}}
+            end;
+	{undetermined, unrevoked, RevokedState} when Rest =/= []->
 	    pkix_crls_validate(OtpCert, Rest, All, Options, RevokedState);
 	{finished, unrevoked} ->
 	    valid;
@@ -1417,3 +1428,7 @@ to_lower_ascii(C) -> C.
 to_string(S) when is_list(S) -> S;
 to_string(B) when is_binary(B) -> binary_to_list(B).
 
+format_details([]) ->
+    no_relevant_crls;
+format_details(Details) ->
+    Details.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 3cf466e78f..5110f2d271 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -1611,8 +1611,11 @@ path_validation_alert({bad_cert, unknown_critical_extension}) ->
     ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE);
 path_validation_alert({bad_cert, {revoked, _}}) ->
     ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED);
-path_validation_alert({bad_cert, revocation_status_undetermined}) ->
-    ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
+%%path_validation_alert({bad_cert, revocation_status_undetermined}) ->
+%%   ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
+path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) ->
+    Alert = ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE),
+    Alert#alert{reason = Details};
 path_validation_alert({bad_cert, selfsigned_peer}) ->
     ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
 path_validation_alert({bad_cert, unknown_ca}) ->
@@ -2189,7 +2192,8 @@ crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, C
 				     ssl_crl:trusted_cert_and_path(CRL, Issuer, {CertPath,
                                                                                  DBInfo})
 			     end, {CertDbHandle, CertDbRef}}}, 
-	       {update_crl, fun(DP, CRL) -> Callback:fresh_crl(DP, CRL) end}
+	       {update_crl, fun(DP, CRL) -> Callback:fresh_crl(DP, CRL) end},
+               {undetermined_details, true}
 	      ],
     case dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) of
 	no_dps ->
@@ -2199,7 +2203,7 @@ crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, C
 	DpsAndCRLs ->  %% This DP list may be empty if relevant CRLs existed 
 	    %% but could not be retrived, will result in {bad_cert, revocation_status_undetermined}
 	    case public_key:pkix_crls_validate(OtpCert, DpsAndCRLs, Options) of
-		{bad_cert, revocation_status_undetermined} ->
+		{bad_cert, {revocation_status_undetermined, _}} ->
 		    crl_check_same_issuer(OtpCert, Check, dps_and_crls(OtpCert, Callback, 
 								       CRLDbHandle, same_issuer), Options);
 		Other ->
@@ -2209,7 +2213,7 @@ crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, C
 
 crl_check_same_issuer(OtpCert, best_effort, Dps, Options) ->		
     case public_key:pkix_crls_validate(OtpCert, Dps, Options) of 
-	{bad_cert, revocation_status_undetermined}  ->
+	{bad_cert, {revocation_status_undetermined, _}}   ->
 	    valid;
 	Other ->
 	    Other
-- 
cgit v1.2.3