blob: 0051d8c8d11e4bf5c3196c506580717e908ab96d [file] [edit]
#!/bin/bash
# Copyright 2026 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
#
# 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.
#
#
# Creates a private root CA and, issued by it, EC P-256 keys and certificates
# for a C2PA claim signer and a Time Stamping Authority (TSA). The output is
# intended for local testing of tools:asset_write and tools:c2pa_validate only.
#
# Usage: create_test_pki.sh [output_dir]
#
# output_dir defaults to ./test_pki and must not already contain keys.
#
# The certificates follow the C2PA certificate profile checked by the
# validator: X.509 v3 with the digitalSignature key usage. The claim signer
# certificate carries the C2PA claim signing EKU (1.3.6.1.4.1.62558.2.1) and no
# anyExtendedKeyUsage. The TSA certificate carries a critical timeStamping EKU
# as required by RFC 3161.
#
# Keep root_ca_key.pem private: anyone holding it can issue certificates that
# validators configured with root_ca_cert.pem will trust.
#
# Requires OpenSSL 1.1.1 or later.
set -euo pipefail
readonly CURVE="P-256"
readonly ROOT_CA_DAYS=3650
readonly LEAF_DAYS=365
readonly ORGANIZATION="Credentio Test"
#######################################
# Writes the OpenSSL config with the certificate extensions for each profile.
# Arguments:
# Path of the config file to write.
#######################################
write_openssl_config() {
cat > "$1" <<'EOF'
[ req ]
distinguished_name = req_dn
[ req_dn ]
[ root_ca ]
basicConstraints = critical, CA:TRUE
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
# C2PA claim signer: digitalSignature KU and the C2PA claim signing EKU.
[ claim_signer ]
basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature
extendedKeyUsage = 1.3.6.1.4.1.62558.2.1
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
# RFC 3161 TSA: timeStamping must be the only EKU and marked critical.
[ tsa ]
basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, timeStamping
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
EOF
}
#######################################
# Generates an EC private key in PKCS#8 PEM format.
# Arguments:
# Output key path.
#######################################
generate_key() {
openssl genpkey -algorithm EC -pkeyopt "ec_paramgen_curve:${CURVE}" \
-out "$1" 2>/dev/null
chmod 600 "$1"
}
#######################################
# Creates the self-signed root CA key and certificate.
# Arguments:
# Output directory.
#######################################
create_root_ca() {
local dir="$1"
generate_key "${dir}/root_ca_key.pem"
openssl req -x509 -new -key "${dir}/root_ca_key.pem" -sha256 \
-days "${ROOT_CA_DAYS}" \
-subj "/O=${ORGANIZATION}/CN=${ORGANIZATION} Root CA" \
-config "${dir}/openssl.cnf" -extensions root_ca \
-out "${dir}/root_ca_cert.pem"
}
#######################################
# Creates a key and a certificate issued by the root CA.
# Arguments:
# Output directory.
# Name of the profile section in openssl.cnf; also the output file prefix.
# Common name of the certificate subject.
#######################################
issue_leaf() {
local dir="$1"
local profile="$2"
local common_name="$3"
generate_key "${dir}/${profile}_key.pem"
openssl req -new -key "${dir}/${profile}_key.pem" \
-subj "/O=${ORGANIZATION}/CN=${common_name}" \
-config "${dir}/openssl.cnf" |
openssl x509 -req -sha256 -days "${LEAF_DAYS}" \
-CA "${dir}/root_ca_cert.pem" -CAkey "${dir}/root_ca_key.pem" \
-set_serial "0x$(openssl rand -hex 16)" \
-extfile "${dir}/openssl.cnf" -extensions "${profile}" \
-out "${dir}/${profile}_cert.pem" 2>/dev/null
}
main() {
if [[ $# -gt 1 ]]; then
echo "Usage: $0 [output_dir]" >&2
exit 1
fi
local dir="${1:-test_pki}"
if ! command -v openssl >/dev/null; then
echo "Error: openssl not found in PATH." >&2
exit 1
fi
if compgen -G "${dir}/*_key.pem" >/dev/null; then
echo "Error: ${dir} already contains keys; refusing to overwrite." >&2
exit 1
fi
mkdir -p "${dir}"
write_openssl_config "${dir}/openssl.cnf"
create_root_ca "${dir}"
issue_leaf "${dir}" claim_signer "${ORGANIZATION} Claim Signer"
issue_leaf "${dir}" tsa "${ORGANIZATION} TSA"
openssl verify -CAfile "${dir}/root_ca_cert.pem" \
"${dir}/claim_signer_cert.pem" "${dir}/tsa_cert.pem" >/dev/null
local abs_dir
abs_dir="$(cd "${dir}" && pwd)"
cat <<EOF
Created test PKI in ${abs_dir}:
asset_write flags:
--claim_signer_key=${abs_dir}/claim_signer_key.pem
--claim_signer_cert=${abs_dir}/claim_signer_cert.pem
--tsa_key=${abs_dir}/tsa_key.pem
--tsa_cert=${abs_dir}/tsa_cert.pem
c2pa_validate flags:
--claim_signer_trust=${abs_dir}/root_ca_cert.pem
--tsa_trust=${abs_dir}/root_ca_cert.pem
These credentials are for local testing only. Keep root_ca_key.pem private.
EOF
}
main "$@"