Skip to content

Commit

Permalink
Prepare for DST Root CA X3 expiration and the woes that come with it
Browse files Browse the repository at this point in the history
  • Loading branch information
g-andrade committed Sep 1, 2021
1 parent 2829fb9 commit 067678c
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Changed

- partial chain validation to prepare for DST Root CA X3 expiration

### Removed

- unnecessary handling of partial chains on OTP 23.3.4.5+ or OTP 24.0.4+
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ xref: $(REBAR3)
@$(REBAR3) as hardcoded_authorities_update xref

test: $(REBAR3)
@make -C test/cross_signing/
@$(REBAR3) do eunit, ct, cover

cover: test
Expand Down
47 changes: 41 additions & 6 deletions src/tls_certificate_check_shared_state.erl
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ needs_partial_chain_handler() ->
find_trusted_authority(EncodedCertificates) ->
SharedState = get_latest_shared_state(),
TrustedPublicKeys = #{} = SharedState#shared_state.trusted_public_keys,
find_trusted_authority_recur(EncodedCertificates, TrustedPublicKeys).
Now = universal_time_in_certificate_format(),
find_trusted_authority_recur(EncodedCertificates, Now, TrustedPublicKeys).

-spec maybe_update_shared_state(binary()) -> ok | {error, term()}.
maybe_update_shared_state(EncodedAuthorities) ->
Expand Down Expand Up @@ -391,16 +392,50 @@ latest_shared_state_key() ->
throw({application_either_not_started_or_not_ready, tls_certificate_check})
end.

find_trusted_authority_recur([EncodedCertificate | NextEncodedCertificates], TrustedPublicKeys) ->
universal_time_in_certificate_format() ->
% http://erlang.org/doc/apps/public_key/public_key_records.html
% * {utcTime, "YYMMDDHHMMSSZ"
% * {generalTime, "YYYYMMDDHHMMSSZ"}

{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time(),
IoData = io_lib:format("~4..0B~2..0B~2..0B" "~2..0B~2..0B~2..0BZ",
[Year, Month, Day, Hour, Minute, Second]),
lists:flatten(IoData).

find_trusted_authority_recur([EncodedCertificate | NextEncodedCertificates], Now, TrustedPublicKeys) ->
Certificate = public_key:pkix_decode_cert(EncodedCertificate, otp),
#'OTPCertificate'{tbsCertificate = TbsCertificate} = Certificate,
#'OTPTBSCertificate'{subjectPublicKeyInfo = PublicKeyInfo} = TbsCertificate,
#'OTPTBSCertificate'{subjectPublicKeyInfo = PublicKeyInfo,
validity = Validity} = TbsCertificate,

case maps:is_key(PublicKeyInfo, TrustedPublicKeys) of
case is_certificate_valid(Validity, Now)
andalso maps:is_key(PublicKeyInfo, TrustedPublicKeys)
of
true ->
{trusted_ca, EncodedCertificate};
false ->
find_trusted_authority_recur(NextEncodedCertificates, TrustedPublicKeys)
find_trusted_authority_recur(NextEncodedCertificates, Now, TrustedPublicKeys)
end;
find_trusted_authority_recur([], _TrustedPublicKeys) ->
find_trusted_authority_recur([], _Now, _TrustedPublicKeys) ->
unknown_ca.

is_certificate_valid(Validity, Now) ->
#'Validity'{notBefore = NotBefore, notAfter = NotAfter} = Validity,
compare_certificate_timestamps(NotAfter, Now) =/= lesser
andalso compare_certificate_timestamps(NotBefore, Now) =/= greater.

compare_certificate_timestamps({utcTime, String}, Now) ->
compare_certificate_timestamps_("20" ++ String, Now);
compare_certificate_timestamps({generalTime, String}, Now) ->
compare_certificate_timestamps_(String, Now).

compare_certificate_timestamps_([X|A], [Y|B]) ->
if X < Y ->
lesser;
X > Y ->
greater;
true ->
compare_certificate_timestamps_(A, B)
end;
compare_certificate_timestamps_([], []) ->
equal.
3 changes: 3 additions & 0 deletions test/cross_signing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.csr
*.pem
*.srl
141 changes: 141 additions & 0 deletions test/cross_signing/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

all: good_ca_store_for_expiry
all: bad_ca_store_for_expiry
all: ca_store1_for_cross_signing
all: ca_store2_for_cross_signing
all: localhost_chain_for_expiry
all: localhost_chain_for_cross_signing

good_ca_store_for_expiry: new_ca
good_ca_store_for_expiry: expired_ca
good_ca_store_for_expiry:
cat expired_ca.pem new_ca.pem >good_ca_store_for_expiry.pem
.PHONY: good_ca_store_for_expiry

bad_ca_store_for_expiry: expired_ca
bad_ca_store_for_expiry:
cat expired_ca.pem >bad_ca_store_for_expiry.pem
.PHONY: bad_ca_store_for_expiry

ca_store1_for_cross_signing: third_ca
cat new_ca.pem >ca_store1_for_cross_signing.pem

ca_store2_for_cross_signing: third_ca
cat third_ca.pem >ca_store2_for_cross_signing.pem

localhost_chain_for_expiry: localhost
localhost_chain_for_expiry: regular_intermediate_cert
localhost_chain_for_expiry: cross_signed_bad_intermediate_cert
cat \
localhost.pem \
regular_intermediate_cert.pem \
cross_signed_bad_intermediate_cert.pem \
>localhost_chain_for_expiry.pem
.PHONY: localhost_chain

localhost_chain_for_cross_signing: localhost
localhost_chain_for_cross_signing: regular_intermediate_cert
localhost_chain_for_cross_signing: cross_signed_good_intermediate_cert
cat \
localhost.pem \
regular_intermediate_cert.pem \
cross_signed_good_intermediate_cert.pem \
>localhost_chain_for_cross_signing.pem
.PHONY: localhost_chain

localhost: regular_intermediate_cert
localhost: localhost.csr
localhost:
faketime -f '-5y' openssl x509 \
-req \
-in localhost.csr \
-CA regular_intermediate_cert.pem \
-CAkey regular_intermediate_cert_key.pem \
-CAcreateserial \
-out localhost.pem \
-days 3600 \
-sha256
.PHONY: localhost

regular_intermediate_cert: new_ca
regular_intermediate_cert: regular_intermediate_cert.csr
faketime -f '-5y' openssl x509 \
-req \
-in regular_intermediate_cert.csr \
-extfile intermediate_ca.ext \
-extensions v3_intermediate_ca \
-CA new_ca.pem \
-CAkey new_ca_key.pem \
-CAcreateserial \
-out regular_intermediate_cert.pem \
-days 3600 \
-sha256
.PHONY: regular_intermediate_cert

cross_signed_bad_intermediate_cert: expired_ca
cross_signed_bad_intermediate_cert: new_ca.csr
faketime -f '-5y' openssl x509 \
-req \
-in new_ca.csr \
-extfile intermediate_ca.ext \
-extensions v3_intermediate_ca \
-CA expired_ca.pem \
-CAkey expired_ca_key.pem \
-CAcreateserial \
-out cross_signed_bad_intermediate_cert.pem \
-days 3600 \
-sha256
.PHONY: cross_signed_bad_intermediate_cert

cross_signed_good_intermediate_cert: third_ca
cross_signed_good_intermediate_cert: new_ca.csr
faketime -f '-5y' openssl x509 \
-req \
-in new_ca.csr \
-extfile intermediate_ca.ext \
-extensions v3_intermediate_ca \
-CA third_ca.pem \
-CAkey third_ca_key.pem \
-CAcreateserial \
-out cross_signed_good_intermediate_cert.pem \
-days 3600 \
-sha256
.PHONY: cross_signed_good_intermediate_cert

expired_ca: faketime
expired_ca: expired_ca_key.pem
faketime -f '-5y' openssl req -x509 \
-new -nodes \
-key expired_ca_key.pem \
-sha256 \
-days 1800 \
-subj "/CN=Expired CA" \
-out expired_ca.pem
.PHONY: expired_ca

faketime:
./install_faketime.sh
.PHONY: faketime

.PRECIOUS: %_ca # prevens removal of what is considered an intermediate file (FIXME untested on macos)
%_ca: %_ca_key.pem
faketime -f '-5y' openssl req -x509 \
-new -nodes \
-key $*_ca_key.pem \
-sha256 \
-days 3600 \
-subj "/CN=$*_ca" \
-out $*_ca.pem
.PHONY: %_ca

.PRECIOUS: %_key.pem # prevens removal of what is considered an intermediate file (FIXME untested on macos)
%.csr: %_key.pem
openssl req \
-new \
-key $*_key.pem \
-subj "/CN=$*" \
-out $@

.PRECIOUS: %_key.pem # prevens removal of what is considered an intermediate file (FIXME untested on macos)
%_key.pem:
openssl genrsa -out $@ 2048
25 changes: 25 additions & 0 deletions test/cross_signing/install_faketime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

set -eu

function is_installed {
which $1 >/dev/null;
}


if is_installed faketime; then
exit
fi

if is_installed brew; then
brew install faketime # FIXME untested
exit
fi

if is_installed apt-get; then
DEBIAN_FRONTEND=noninteractive sudo apt-get --yes install faketime
exit
fi

>&2 echo "I don't know how to install faketime in your system"
exit 1
8 changes: 8 additions & 0 deletions test/cross_signing/intermediate_ca.ext
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# From: https://stackoverflow.com/questions/52500165/problem-verifying-a-self-created-openssl-root-intermediate-and-end-user-certifi

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:1
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
Loading

0 comments on commit 067678c

Please sign in to comment.