File 4542-ssl-Refactor-to-avoid-re-encoding-of-decoded-certs.patch of Package erlang
From 58b7b1b37a349bf854ac1346201b49edacd2fc65 Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Tue, 21 Sep 2021 17:16:01 +0200
Subject: [PATCH 2/5] ssl: Refactor to avoid re-encoding of decoded certs
Let public key define the record #cert{} that can be used to provide
the original DER certs received from the peer and the decoded version
together as input to certificate handling functions such as
public:pkix_path_validation/3
will be more efficient as we can avoid conversions and also a way for
applications such as ssl to tolerate certs with some ASN1-encoding errors
that can be worked around.
---
lib/public_key/doc/src/public_key.xml | 62 +++++++++++++++++----------
lib/public_key/include/public_key.hrl | 5 +++
lib/public_key/src/public_key.erl | 43 +++++++++++++------
lib/ssl/src/ssl_certificate.erl | 22 ++++------
lib/ssl/src/ssl_crl.erl | 2 +-
lib/ssl/src/ssl_handshake.erl | 13 ++++--
lib/ssl/src/ssl_internal.hrl | 1 -
7 files changed, 94 insertions(+), 54 deletions(-)
diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index 4f569f2c6d..f4eb4bdefd 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -64,6 +64,14 @@
</desc>
</datatype>
+ <datatype>
+ <name name="key_oid_name"/>
+ <desc>
+ <p>Macro names for key object identifiers used by prefixing with ?</p>
+ </desc>
+ </datatype>
+
+
<datatype>
<name name="der_encoded"/>
<desc>
@@ -100,11 +108,14 @@
<datatype>
<name name="public_key"/>
<name name="rsa_public_key"/>
+ <name name="dss_public_key"/>
<name name="rsa_pss_public_key"/>
<name name="dsa_public_key"/>
<name name="ec_public_key"/>
+ <name name="public_key_params"/>
<name name="ecpk_parameters"/>
<name name="ecpk_parameters_api"/>
+ <name name="public_key_info"/>
<desc>
</desc>
</datatype>
@@ -139,6 +150,12 @@
</desc>
</datatype>
+ <datatype>
+ <name name="ed_oid_name"/>
+ <desc>
+ Macro names for object identifiers for EDDSA curves used by prefixing with ?
+ </desc>
+ </datatype>
<datatype>
<name name="key_params"/>
@@ -146,6 +163,7 @@
</desc>
</datatype>
+
<datatype>
<name name="digest_type"/>
<desc>
@@ -153,17 +171,32 @@
</datatype>
<datatype>
- <name name="crl_reason"/>
+ <name name="cert_id"/>
<desc>
</desc>
</datatype>
+ <datatype>
+ <name name="cert"/>
+ <desc>
+ </desc>
+ </datatype>
+
+
<datatype>
- <name name="cert_id"/>
+ <name name="bad_cert_reason"/>
<desc>
</desc>
</datatype>
+
+ <datatype>
+ <name name="crl_reason"/>
+ <desc>
+ </desc>
+ </datatype>
+
+
<datatype>
<name name="issuer_name"/>
<desc>
@@ -401,33 +434,16 @@
</func>
<func>
- <name since="OTP R16B">pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, {PublicKeyInfo, PolicyTree}} | {error, {bad_cert, Reason}} </name>
+ <name since="OTP R16B" name="pkix_path_validation" arity="3"/>
<fsummary>Performs a basic path validation according to RFC 5280.</fsummary>
- <type>
- <v>TrustedCert = #'OTPCertificate'{} | der_encoded() | atom()</v>
- <d>Normally a trusted certificate, but it can also be a path-validation
- error that can be discovered while
- constructing the input to this function and that is to be run through the <c>verify_fun</c>.
- Examples are <c>unknown_ca</c> and <c>selfsigned_peer.</c>
- </d>
- <v>CertChain = [der_encoded()]</v>
- <d>A list of DER-encoded certificates in trust order ending with the peer certificate.</d>
- <v>Options = proplists:proplist()</v>
- <v>PublicKeyInfo = {?'rsaEncryption' | ?'id-RSASSA-PSS'| ?'id-dsa',
- rsa_public_key() | integer(), 'NULL' | 'RSASSA-PSS-params'{} | 'Dss-Parms'{}}</v>
- <v>PolicyTree = term()</v>
- <d>At the moment this is always an empty list as policies are not currently supported.</d>
- <v>Reason = cert_expired | invalid_issuer | invalid_signature | name_not_permitted |
- missing_basic_constraint | invalid_key_usage | {revoked, crl_reason()} | atom()
- </v>
- </type>
<desc>
<p>
Performs a basic path validation according to
<url href="http://www.ietf.org/rfc/rfc5280.txt">RFC 5280.</url>
However, CRL validation is done separately by <seemfa
marker="#pkix_crls_validate/3">pkix_crls_validate/3 </seemfa> and is to be called
- from the supplied <c>verify_fun</c>.
+ from the supplied <c>verify_fun</c>. The optional policy tree check is currently not implemented
+ but an empty place holder list is returned instead.
</p>
<p>Available options:</p>
@@ -473,7 +489,7 @@ fun(OtpCert :: #'OTPCertificate'{},
</item>
</taglist>
- <p>Possible reasons for a bad certificate: </p>
+ <p>Explanations of reasons for a bad certificate: </p>
<taglist>
<tag>cert_expired</tag>
<item><p>Certificate is no longer valid as its expiration date has passed.</p></item>
diff --git a/lib/public_key/include/public_key.hrl b/lib/public_key/include/public_key.hrl
index 98d032bfdd..bea4b5bc06 100644
--- a/lib/public_key/include/public_key.hrl
+++ b/lib/public_key/include/public_key.hrl
@@ -78,6 +78,11 @@
point
}).
+-record(cert, {
+ der :: public_key:der_encoded(),
+ otp :: #'OTPCertificate'{}
+ }).
+
-define(unspecified, 0).
-define(keyCompromise, 1).
-define(cACompromise, 2).
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 6ecb902db5..0371917009 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -103,10 +103,12 @@
-type private_key() :: rsa_private_key() | rsa_pss_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() .
-type rsa_public_key() :: #'RSAPublicKey'{}.
-type rsa_private_key() :: #'RSAPrivateKey'{}.
--type rsa_pss_public_key() :: {#'RSAPublicKey'{}, #'RSASSA-PSS-params'{}}.
+-type dss_public_key() :: integer().
+-type rsa_pss_public_key() :: {rsa_pss_public_key(), #'RSASSA-PSS-params'{}}.
-type rsa_pss_private_key() :: { #'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}.
-type dsa_private_key() :: #'DSAPrivateKey'{}.
--type dsa_public_key() :: {integer(), #'Dss-Parms'{}}.
+-type dsa_public_key() :: {dss_public_key(), #'Dss-Parms'{}}.
+-type public_key_params() :: 'NULL' | #'RSASSA-PSS-params'{} | {namedCurve, oid()} | #'ECParameters'{} | #'Dss-Parms'{}.
-type ecpk_parameters() :: {ecParameters, #'ECParameters'{}} | {namedCurve, Oid::tuple()}.
-type ecpk_parameters_api() :: ecpk_parameters() | #'ECParameters'{} | {namedCurve, Name::crypto:ec_named_curve()}.
-type ec_public_key() :: {#'ECPoint'{}, ecpk_parameters_api()}.
@@ -114,7 +116,8 @@
-type ed_public_key() :: {#'ECPoint'{}, ed_params()} | {ed_pub, ed25519|ed448, Key::binary()}.
-type ed_private_key() :: #'ECPrivateKey'{parameters :: ed_params()} |
{ed_pri, ed25519|ed448, Pub::binary(), Priv::binary()}.
--type ed_params() :: {namedCurve, ?'id-Ed25519' | ?'id-Ed448'}.
+-type ed_oid_name() :: 'id-Ed25519' | 'id-Ed448'.
+-type ed_params() :: {namedCurve, ed_oid_name()}.
-type key_params() :: #'DHParameter'{} | {namedCurve, oid()} | #'ECParameters'{} |
{rsa, Size::integer(), PubExp::integer()}.
-type der_encoded() :: binary().
@@ -151,7 +154,11 @@
-type cert_id() :: {SerialNr::integer(), issuer_name()} .
-type issuer_name() :: {rdnSequence,[[#'AttributeTypeAndValue'{}]]} .
-
+-type bad_cert_reason() :: cert_expired | invalid_issuer | invalid_signature | name_not_permitted | missing_basic_constraint | invalid_key_usage |
+ {revoked, crl_reason()} | atom().
+-type cert() :: #cert{} | der_encoded() | #'OTPCertificate'{}.
+-type public_key_info() :: {key_oid_name(), rsa_public_key() | #'ECPoint'{} | dss_public_key(), public_key_params()}.
+-type key_oid_name() :: 'rsaEncryption' | 'id-RSASSA-PSS' | 'id-ecPublicKey' | 'id-Ed25519' | 'id-Ed448' | 'id-dsa'.
-define(UINT32(X), X:32/unsigned-big-integer).
@@ -1095,16 +1102,22 @@ pkix_normalize_name(Issuer) ->
pubkey_cert:normalize_general_name(Issuer).
%%--------------------------------------------------------------------
--spec pkix_path_validation(Cert::binary()| #'OTPCertificate'{} | atom(),
- CertChain :: [binary() | #'OTPCertificate'{}] ,
- Options :: [{atom(),term()}]) ->
- {ok, {PublicKeyInfo :: term(),
- PolicyTree :: term()}} |
- {error, {bad_cert, Reason :: term()}}.
+-spec pkix_path_validation(Cert, CertChain, Options) ->
+ {ok, {PublicKeyInfo, PolicyTree}} |
+ {error, {bad_cert, Reason :: bad_cert_reason()}}
+ when
+ Cert::binary()| #'OTPCertificate'{} | atom(),
+ CertChain :: [cert()],
+ Options :: [Option],
+ Option :: {verify_fun, {fun(), term()}} | {max_path_lengh, integer()},
+ PublicKeyInfo :: public_key_info(),
+ PolicyTree :: list().
+
%% Description: Performs a basic path validation according to RFC 5280.
%%--------------------------------------------------------------------
pkix_path_validation(TrustedCert, CertChain, Options)
when is_binary(TrustedCert) ->
+
OtpCert = pkix_decode_cert(TrustedCert, otp),
pkix_path_validation(OtpCert, CertChain, Options);
pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options)
@@ -1641,13 +1654,17 @@ validate(Cert, #path_validation_state{working_issuer_name = Issuer,
otp_cert(Der) when is_binary(Der) ->
pkix_decode_cert(Der, otp);
-otp_cert(#'OTPCertificate'{} =Cert) ->
- Cert.
+otp_cert(#'OTPCertificate'{} = Cert) ->
+ Cert;
+otp_cert(#cert{otp = OtpCert}) ->
+ OtpCert.
der_cert(#'OTPCertificate'{} = Cert) ->
pkix_encode('OTPCertificate', Cert, otp);
der_cert(Der) when is_binary(Der) ->
- Der.
+ Der;
+der_cert(#cert{der = DerCert}) ->
+ DerCert.
pkix_crls_validate(_, [],_, Options, #revoke_state{details = Details}) ->
case proplists:get_value(undetermined_details, Options, false) of
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index cbeb4e4521..acdc85255f 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -88,7 +88,7 @@
%%--------------------------------------------------------------------
-spec trusted_cert_and_paths([der_cert()], db_handle(), certdb_ref(), fun()) ->
- [{der_cert() | unknown_ca | invalid_issuer | selfsigned_peer, [der_cert()]}].
+ [{#cert{} | unknown_ca | invalid_issuer | selfsigned_peer, [#cert{}]}].
%%
%% Description: Construct input to public_key:pkix_path_validation/3,
%% If the ROOT cert is not found {bad_cert, unknown_ca} will be returned
@@ -103,7 +103,7 @@ trusted_cert_and_paths([Peer], CertDbHandle, CertDbRef, PartialChainHandler) ->
Chain = [#cert{der=Peer, otp=OtpCert}],
case public_key:pkix_is_self_signed(OtpCert) of
true ->
- [{selfsigned_peer, [Peer]}];
+ [{selfsigned_peer, Chain}];
false ->
[handle_incomplete_chain(Chain, PartialChainHandler, {unknown_ca, Chain},
CertDbHandle, CertDbRef)]
@@ -122,11 +122,10 @@ trusted_cert_and_paths(Chain0, CertDbHandle, CertDbRef, PartialChainHandler) ->
PartialChainHandler,
Result,
CertDbHandle, CertDbRef);
- {Root, NewChain}->
- decoded_chain(Root, NewChain)
+ {_Root, _NewChain} = Result ->
+ Result
end
end, Paths).
-
%%--------------------------------------------------------------------
-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(),
certdb_ref() | {extracted, list()}) ->
@@ -362,9 +361,8 @@ do_certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _, ListD
{ok, undefined, lists:reverse(Chain)}
end.
-find_alternative_root([OtpCert | _], CertDbHandle, CertDbRef, InvalidatedList) ->
- Cert = public_key:pkix_encode('OTPCertificate', OtpCert, otp),
- case find_issuer(#cert{der=Cert, otp=OtpCert}, CertDbHandle, CertDbRef, [], InvalidatedList) of
+find_alternative_root([Cert | _], CertDbHandle, CertDbRef, InvalidatedList) ->
+ case find_issuer(Cert, CertDbHandle, CertDbRef, [], InvalidatedList) of
{error, issuer_not_found} ->
unknown_ca;
{ok, {SerialNr, IssuerId}} ->
@@ -663,14 +661,12 @@ handle_incomplete_chain([#cert{}=Peer| _] = Chain0, PartialChainHandler, Default
true ->
{Root, Chain} = handle_partial_chain(lists:reverse(ChainCandidate), PartialChainHandler,
CertDbHandle, CertDbRef),
- decoded_chain(Root, Chain);
+ {Root, Chain};
false ->
- {Root, Chain} = Default,
- decoded_chain(Root, Chain)
+ Default
end;
_ ->
- {Root, Chain} = Default,
- decoded_chain(Root, Chain)
+ Default
end.
extraneous_chains(Certs) ->
diff --git a/lib/ssl/src/ssl_crl.erl b/lib/ssl/src/ssl_crl.erl
index 5e35e89b78..d3b66df870 100644
--- a/lib/ssl/src/ssl_crl.erl
+++ b/lib/ssl/src/ssl_crl.erl
@@ -105,7 +105,7 @@ find_issuer(IsIssuerFun, Db, _) ->
Result
end.
-verify_crl_issuer(CRL, ErlCertCandidate, Issuer, NotIssuer) ->
+verify_crl_issuer(CRL, #cert{otp = ErlCertCandidate}, Issuer, NotIssuer) ->
TBSCert = ErlCertCandidate#'OTPCertificate'.tbsCertificate,
case public_key:pkix_normalize_name(TBSCert#'OTPTBSCertificate'.subject) of
Issuer ->
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index f7dc641fb3..63fbf5ffff 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -3726,9 +3726,9 @@ path_validate([], _, _, _, _, _, _, _, _, _, Error) ->
path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
Version, SslOptions, CertExt, InvalidatedList, Error) ->
CB = path_validation_cb(Version),
- case CB:path_validation(TrustedCert, Path, ServerName,
- Role, CertDbHandle, CertDbRef, CRLDbHandle,
- Version, SslOptions, CertExt) of
+ case CB:path_validation(trusted_unwrap(TrustedCert), Path, ServerName,
+ Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) of
{error, {bad_cert, root_cert_expired}} = NewError ->
NewInvalidatedList = [TrustedCert | InvalidatedList],
Alt = ssl_certificate:find_cross_sign_root_paths(Path, CertDbHandle, CertDbRef, NewInvalidatedList),
@@ -3745,6 +3745,13 @@ path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, Cert
Result
end.
+trusted_unwrap(#cert{otp = TrustedCert}) ->
+ TrustedCert;
+trusted_unwrap(#'OTPCertificate'{} = TrustedCert) ->
+ TrustedCert;
+trusted_unwrap(ErrAtom) when is_atom(ErrAtom) ->
+ ErrAtom.
+
%% Call basic path validation algorithm in public_key pre TLS-1.3
path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version,
#{verify_fun := VerifyFun,
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 3a8568d1e3..b56ba24d78 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -252,7 +252,6 @@
max_size %% max early data size allowed by this ticket
}).
--record(cert, {der, otp}).
-endif. % -ifdef(ssl_internal).
--
2.31.1