[tls] Support build time injection for tls example

www.google.com seems to refresh certificate every 3 months. It just
broke the tls example application as it has been using fixed time so
far. The CL adds support for injecting custom/build time as the time
source. This is done by auto-generating source code for implementing the
time_t time(time_t *) C API. The time_t value to return is passed from a
gn argument, or default to build time if not specified.

CRL checking is disabled by default as the in-tree test CRLs have
already expired. This will cause load failure when build time is used.
Will consider build-time CRL download and injection for demo purpose.

Change-Id: I6d6339a6203d2f3ba2b06f8afac5af04e3a5e683
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/experimental/+/39465
Commit-Queue: Yecheng Zhao <zyecheng@google.com>
Reviewed-by: Ali Zhang <alizhang@google.com>
diff --git a/applications/tls_example/BUILD.gn b/applications/tls_example/BUILD.gn
index 11631c0..757b7c7 100644
--- a/applications/tls_example/BUILD.gn
+++ b/applications/tls_example/BUILD.gn
@@ -22,6 +22,11 @@
   tls_backend = "backends/tls/boringssl"
   transport_backend = "backends/transport/teensy_ethernet"
   bloaty_config = teensy_bloaty_config
+
+  # Build time will be used as the time source for certificate expiration check
+  # by default. To use a different specified time. Add the following argument:
+  #
+  # time = "03/29/2021 12:00:00:00"
 }
 
 tls_client_example("picotls_teensy_ethernet") {
diff --git a/applications/tls_example/sysdeps.cc b/applications/tls_example/sysdeps.cc
index e4531c5..2ed71a8 100644
--- a/applications/tls_example/sysdeps.cc
+++ b/applications/tls_example/sysdeps.cc
@@ -21,28 +21,6 @@
 #include "pw_log/log.h"
 
 extern "C" {
-// libraries such as boringssl, picotls, wolfssl use time() to get current
-// date/time for certificate time check. For demo purpose, the following fakes
-// this function and provides a pre-set date.
-//
-// TLS_EXAMPLE_TIME specifies the current time in seconds since epoch. One easy
-// way to figure out this value for a certain date is to use a one-line python
-// code:
-//
-// import datetime
-// datetime.datetime(2021,5,21,0,0).timestamp()
-//
-// The above gives the seconds since epoch for 05/21/2021 00:00
-#ifndef TLS_EXAMPLE_TIME
-// The CRL used in the example is only valid before 2021 March.
-#define TLS_EXAMPLE_TIME 1614240000  // 2021-02-25 00:00:00
-#endif
-
-time_t time(time_t* timer) {
-  time_t ret = TLS_EXAMPLE_TIME;
-  return timer ? *timer = ret : ret;
-}
-
 // boringssl reads from file "dev/urandom" for generating randome bytes.
 // For demo purpose, we fake these file io functions.
 
diff --git a/applications/tls_example/time_injection/generate_time_code.py b/applications/tls_example/time_injection/generate_time_code.py
new file mode 100644
index 0000000..3c1f232
--- /dev/null
+++ b/applications/tls_example/time_injection/generate_time_code.py
@@ -0,0 +1,96 @@
+# Copyright 2021 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Generate time for injection
+
+The script generates a .c file that implement time_t time(time_t *) c API
+that returns a pre-set time. This is used to inject a time for the tls
+example application. The template of the code is
+
+#include <sys/time.h>
+
+static time_t injected_time = <time>;
+time_t time(time_t* timer) {
+  return timer ? *timer = injected_time : injected_time;
+}
+
+Usage:
+    inject specified time: genearte_time_code <output> -t "03/29/2021 12:00:00"
+    inject current time: genearte_time_code <output>
+"""
+import datetime
+from datetime import datetime
+import time
+import argparse
+import subprocess
+
+TIME_FORMAT = "%m/%d/%Y %H:%M:%S"
+
+HEADER = """// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <sys/time.h>
+
+// libraries such as boringssl, picotls, wolfssl use time() to get current
+// date/time for certificate time check. For demo purpose, the following fakes
+// this function and provides a pre-set date.
+
+"""
+
+FUNCTION = """
+time_t time(time_t* t) {
+  return t ? *t = injected_time : injected_time;
+}
+"""
+
+
+def parse_args():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("out", help="path for output header file")
+    parser.add_argument("--time", "-t", help="time to inject")
+    return parser.parse_args()
+
+
+if __name__ == "__main__":
+    args = parse_args()
+    if args.time:
+        time_stamp = datetime.strptime(args.time, TIME_FORMAT).timestamp()
+    else:
+        time_stamp = datetime.now().timestamp()
+    time_stamp = int(time_stamp)
+
+    # Generate source file
+    with open(args.out, "w") as header:
+        header.write(HEADER)
+        string_date = datetime.fromtimestamp(time_stamp).strftime(TIME_FORMAT)
+        header.write(f'// {string_date}\n')
+        header.write(f'static time_t injected_time = {int(time_stamp)};\n')
+        header.write(FUNCTION)
+        header.write("\n")
+
+    subprocess.run([
+        "clang-format",
+        "-i",
+        args.out,
+    ])
diff --git a/applications/tls_example/tls_client_example.cc b/applications/tls_example/tls_client_example.cc
index 45c91eb..8632ed1 100644
--- a/applications/tls_example/tls_client_example.cc
+++ b/applications/tls_example/tls_client_example.cc
@@ -38,10 +38,19 @@
 // PEM format certificate.
 const char kCACertChain[] = {GLOBAL_SIGN_CA_CRT GTS_CA_101_CRT};
 
-// The following CRLs are downloaded previously from the above CAs and for
-// demo purpose only. In practice, CRLs should be downloaded from CA at
-// run-time.
+#ifdef CRL_CHECK
+// The following are CRLs previously downloaded from the above CAs and they
+// are likely to be expired when application is compiled. To perform CRL
+// check, make sure that you download the latest CRL from CA into folder
+// applications/tls_example/trust_store and re-run
+// applications/tls_example/trust_store/generate_cert_crl_header_file.py
+// to update the header. See applications/tls_example/trust_store/README.md
+// for more detail.
+//
+// TODO(zyecheng): Alternatively, consider build time CRL download and
+// injection for demo purpose.
 const char kCrls[] = {GLOBAL_SIGN_CA_CRL GTS_CA_101_CRL};
+#endif
 
 void TlsClientExample() {
   TlsInterface* tls = CreateTls();
@@ -73,11 +82,13 @@
     MyAbort();
   }
 
+#ifdef CRL_CHECK
   // Loads CRLs.
   if (int status = tls->LoadCrl(kCrls, sizeof(kCrls)); status < 0) {
     PW_LOG_INFO("Failed to load crls, %d", status);
     MyAbort();
   }
+#endif
 
   // Performs TLS handshake.
   PW_LOG_INFO("Performing handshake...");
diff --git a/applications/tls_example/tls_client_example.gni b/applications/tls_example/tls_client_example.gni
index 03736cb..87719b8 100644
--- a/applications/tls_example/tls_client_example.gni
+++ b/applications/tls_example/tls_client_example.gni
@@ -14,10 +14,26 @@
 
 import("//build_overrides/pigweed.gni")
 import("$dir_pw_bloat/bloat.gni")
+import("$dir_pw_build/python_action.gni")
 import("$dir_pw_build/target_types.gni")
 
 # A convenient template for generating example applications with different
 # tls/transport backends.
+#
+# The template accepts the following arguments:
+#
+#   tls_backend         Required. A target specifying the tls library.
+#
+#   transport_backend:  Required. A target specifying the transport implementation.
+#
+#   no_bloaty:          Whether to generate bloaty size report. Default
+#                       to true
+#
+#   bloaty_config:      An optional config file for bloaty report
+#
+#   time:               An optional date string to inject as time. If not spedified,
+#                       build time will be used. Must be format
+#                       month/day/year hour:minute:second
 template("tls_client_example") {
   assert(defined(invoker.tls_backend) && invoker.tls_backend != "",
          "must provide a tls backend")
@@ -34,6 +50,20 @@
     ]
   }
 
+  time_injection_target_name = target_name + "_time_injection"
+  time_source_code_output = "$target_gen_dir/$target_name/injected_time.c"
+  pw_python_action(time_injection_target_name) {
+    script = "//applications/tls_example/time_injection/generate_time_code.py"
+    outputs = [ time_source_code_output ]
+    args = [ rebase_path(time_source_code_output) ]
+    if (defined(invoker.time)) {
+      args += [
+        "-t",
+        invoker.time,
+      ]
+    }
+  }
+
   executable_exclude_vars = [
     "tls_backend",
     "transport_backend",
@@ -46,15 +76,18 @@
     if (!defined(sources)) {
       sources = []
     }
+    time_source_file = get_target_outputs(":$time_injection_target_name")
     sources += [
       "sysdeps.cc",
       "tls_client_example.cc",
+      time_source_file[0],
     ]
 
     if (!defined(deps)) {
       deps = []
     }
     deps += [
+      ":$time_injection_target_name",
       "$dir_pw_log",
       "$dir_pw_spin_delay",
       "$dir_pw_sys_io",