diff --git a/.gitignore b/.gitignore
index 625fa1993ba055091a429ea6e42ddda06da25fc7..239f05a75a3154ed87d3f292a51d1309cb4fadc0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,3 @@ Cargo.lock
 
 # Prevent people from commiting sensitive files.
 crypto_data/
-src/ctap/key_material.rs
-
diff --git a/Cargo.toml b/Cargo.toml
index 624c37ada605fecce84ac0a0fbf69583b8b3b321..2994acfe053943df6a0ff52d36f2b8e8901e3b2b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,9 @@ panic_console = ["libtock/panic_console"]
 [dev-dependencies]
 elf2tab = "0.4.0"
 
+[build-dependencies]
+openssl = "0.10"
+
 [profile.dev]
 panic = "abort"
 lto = true # Link Time Optimization usually reduces size of binaries and static libraries
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000000000000000000000000000000000000..053277eeac0645096fa24d0d16056e54e3aab808
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,78 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//      http://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.
+
+extern crate openssl;
+
+use openssl::asn1;
+use openssl::ec;
+use openssl::nid::Nid;
+use openssl::pkey::PKey;
+use openssl::x509;
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+
+fn main() {
+    println!("cargo:rerun-if-changed=crypto_data/opensk.key");
+    println!("cargo:rerun-if-changed=crypto_data/opensk_cert.pem");
+
+    let out_dir = env::var_os("OUT_DIR").unwrap();
+    let priv_key_bin_path = Path::new(&out_dir).join("opensk_pkey.bin");
+    let cert_bin_path = Path::new(&out_dir).join("opensk_cert.bin");
+    let aaguid_bin_path = Path::new(&out_dir).join("opensk_aaguid.bin");
+
+    // Load the OpenSSL PEM ECC key
+    let ecc_data = include_bytes!("crypto_data/opensk.key");
+    let pkey = ec::EcKey::private_key_from_pem(ecc_data)
+        .ok()
+        .expect("Failed to load OpenSK private key file");
+
+    // Check key validity
+    pkey.check_key().unwrap();
+    assert_eq!(pkey.group().curve_name(), Some(Nid::X9_62_PRIME256V1));
+
+    let mut priv_key = pkey.private_key().to_vec();
+    if priv_key.len() == 33 && priv_key[0] == 0 {
+        priv_key.remove(0);
+    }
+    assert_eq!(priv_key.len(), 32);
+
+    // Create the raw private key out of the OpenSSL data
+    let mut priv_key_bin_file = File::create(&priv_key_bin_path).unwrap();
+    priv_key_bin_file.write_all(&priv_key).unwrap();
+
+    // Convert the PEM certificate to DER and extract the serial for AAGUID
+    let input_pem_cert = include_bytes!("crypto_data/opensk_cert.pem");
+    let cert = x509::X509::from_pem(input_pem_cert)
+        .ok()
+        .expect("Failed to load OpenSK certificate");
+
+    // Do some sanity check on the certificate
+    assert!(cert
+        .public_key()
+        .unwrap()
+        .public_eq(&PKey::from_ec_key(pkey).unwrap()));
+    let today = asn1::Asn1Time::days_from_now(0).unwrap();
+    assert!(cert.not_after() > today);
+    assert!(cert.not_before() <= today);
+
+    let mut cert_bin_file = File::create(&cert_bin_path).unwrap();
+    cert_bin_file.write_all(&cert.to_der().unwrap()).unwrap();
+
+    let mut aaguid_bin_file = File::create(&aaguid_bin_path).unwrap();
+    let mut serial = cert.serial_number().to_bn().unwrap().to_vec();
+    serial.resize(16, 0);
+    aaguid_bin_file.write_all(&serial).unwrap();
+}
diff --git a/src/ctap/key_material.rs b/src/ctap/key_material.rs
new file mode 100644
index 0000000000000000000000000000000000000000..56f5252fe89b351bc9f1036b08a1f9efe57bc366
--- /dev/null
+++ b/src/ctap/key_material.rs
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//      http://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.
+
+pub const AAGUID: [u8; 16] = *include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
+
+pub const ATTESTATION_CERTIFICATE: &[u8] =
+    include_bytes!(concat!(env!("OUT_DIR"), "/opensk_cert.bin"));
+
+pub const ATTESTATION_PRIVATE_KEY: [u8; 32] =
+    *include_bytes!(concat!(env!("OUT_DIR"), "/opensk_pkey.bin"));
diff --git a/tools/gen_key_materials.sh b/tools/gen_key_materials.sh
index 260d8ebafebde381f5bebc5f3d30a72b55159e33..414626a3e5b297c645c53782c79a5feb9da649ae 100644
--- a/tools/gen_key_materials.sh
+++ b/tools/gen_key_materials.sh
@@ -36,6 +36,7 @@ generate_crypto_materials () {
     exit 1
   fi
 
+  force_generate="$1"
   mkdir -p crypto_data
   if [ ! -f "${ca_priv_key}" ]
   then
@@ -60,12 +61,12 @@ generate_crypto_materials () {
       -sha256
   fi
 
-  if [ ! -f "${opensk_key}" ]
+  if [ "${force_generate}" = "Y" -o ! -f "${opensk_key}" ]
   then
     "${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_key}"
   fi
 
-  if [ ! -f "${opensk_cert_name}.pem" ]
+  if [ "${force_generate}" = "Y" -o ! -f "${opensk_cert_name}.pem" ]
   then
     "${openssl}" req \
       -new \
@@ -83,87 +84,4 @@ generate_crypto_materials () {
       -out "${opensk_cert_name}.pem" \
       -sha256
   fi
-
-  local cert_mtime=$(stat --printf="%Y" "${opensk_cert_name}.pem")
-  local rust_file_mtime=0
-  # Only take into consideration the mtime of the file if it exists and if we're not forcing
-  # the rust file to be re-generated.
-  if [ -f "${rust_file}" -a "x$1" != "xY" ]
-  then
-    rust_file_mtime=$(stat --printf="%Y" "${rust_file}")
-  fi
-  if [ $cert_mtime -gt $rust_file_mtime ]
-  then
-    local cert_size=$("${openssl}" x509 \
-      -in "${opensk_cert_name}.pem" \
-      -outform der 2>/dev/null \
-      | wc -c)
-    local cert_serial_hex=$("${openssl}" x509 \
-      -in "${opensk_cert_name}.pem" \
-      -noout \
-      -serial \
-      | cut -d'=' -f2)
-    # Pad with zeroes in case the serial is too short. We don't care if the
-    # serial is longer than 32 characters as we will only process the first 32
-    # characters in the loop later.
-    cert_serial_hex="${cert_serial_hex}00000000000000000000000000000000"
-
-    # Create header
-    echo "// This file had been generated by OpenSK deploy.sh script" > "${rust_file}"
-    echo "" >> "${rust_file}"
-
-    echo "pub const AAGUID: [u8; 16] = [" >> "${rust_file}"
-    for i in `seq 0 2 30`
-    do
-      echo -n "0x${cert_serial_hex:$i:2}, " >> "${rust_file}"
-    done
-    echo "" >> "${rust_file}"
-    echo "];" >> "${rust_file}"
-    echo "" >> "${rust_file}"
-
-    echo "pub const ATTESTATION_CERTIFICATE: [u8; ${cert_size}] = [" >> "${rust_file}"
-    "${openssl}" x509 \
-      -in "${opensk_cert_name}.pem" \
-      -outform der 2>/dev/null \
-      | xxd -i >> "${rust_file}"
-    echo "];" >> "${rust_file}"
-    echo "" >> "${rust_file}"
-
-    # Private key is tricky to extract as we want the raw value and not the DER encoding
-    # Example output of openssl ec -in file.key -noout -text:
-    # read EC key
-    # Private-Key: (256 bit)
-    # priv:
-    #     47:b3:58:b8:f0:09:1d:72:b1:03:34:62:9a:c7:b2:
-    #     b2:e1:06:28:15:69:d4:82:b5:4e:21:6d:98:bf:65:
-    #     98:34
-    # pub:
-    #     04:32:84:a1:3c:90:db:3f:db:d7:fb:ff:e9:00:c8:
-    #     8a:a1:79:2e:95:2e:7c:86:ec:19:03:97:6e:7c:d6:
-    #     67:eb:28:56:f1:d8:dd:cb:ae:ce:b9:cb:e4:6d:9d:
-    #     1d:76:96:fc:48:9b:2d:d5:80:86:04:3d:f9:fe:6c:
-    #     f3:9a:45:bc:b1
-    # ASN1 OID: prime256v1
-    # NIST CURVE: P-256
-    #
-    # The awk script starts printing lines after seeing a line starting with
-    # "priv:" and stops printing as soon as it reaches a line that doesn't start
-    # with a space.
-    # The sed script then converts the output into a proper hex-encode byte
-    # array by replacing the initial spaces on each line with "0x", replacing
-    # the semicolons at the end of each line by commas and replacing all
-    # remainging semicolons by ". 0x".
-    echo "pub const ATTESTATION_PRIVATE_KEY: [u8; 32] = [" >> "${rust_file}"
-    "${openssl}" ec \
-      -in "${opensk_key}" \
-      -noout \
-      -text 2>/dev/null \
-      | awk '/^priv:/{p=1;next}/^[^ ]/{p=0}p' \
-      | sed -e 's/^  */0x/;s/:$/,/;s/:/, 0x/g' >> "${rust_file}"
-    echo "];" >> "${rust_file}"
-    echo "" >> "${rust_file}"
-
-    # If the tool is installed, prettify the file. It will catch syntax errors earlier.
-    which rustfmt > /dev/null 2>&1 && rustfmt "${rust_file}"
-  fi
 }