Skip to content

Commit

Permalink
Add helper binary for bulk certificate processing.
Browse files Browse the repository at this point in the history
Instead of launching 'openssl x509' for each file, use small binary to
process files in bulk. This optimization improves performance more than
7 times (measured on store generation). Performance of 'clrtrust
generate' is important for first-time booting.
  • Loading branch information
busykai committed Apr 16, 2018
1 parent f7f936a commit 35da189
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 20 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.o
clrtrust-helper
clrtrust
25 changes: 21 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
# Copyright 2017 Intel Corporation.
all:
@true

LDLIBS := -lcrypto
CFLAGS := -W -Wall -Werror -std=gnu9x

BINDIR := /usr/bin
LIBEXECDIR := /usr/libexec

.PHONY: build install check clean

build: clrtrust-helper clrtrust

clrtrust-helper: clrtrust-helper.o

clrtrust: clrtrust.in
cat clrtrust.in | sed -e 's:LIBEXEC_CONFIG_VALUE:$(LIBEXECDIR):' > $@
chmod +x clrtrust

install:
install -D --mode=0755 clrtrust ${INSTALL_ROOT}/usr/bin/clrtrust
install -D --mode=0755 clrtrust ${INSTALL_ROOT}${BINDIR}/clrtrust
install -D --mode=0755 clrtrust-helper ${INSTALL_ROOT}${LIBEXECDIR}/clrtrust-helper

check:
check: build
bats -t test

clean:
rm -rf clrtrust-helper clrtrust-helper.o clrtrust
164 changes: 164 additions & 0 deletions clrtrust-helper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright © 2018 Intel Corporation.
*
* See COPYING for terms.
*/

/* This is a helper application which is meant to be used with clrtrust, the
* Clear Linux Trust Store management tool. It is created for performance
* reasons, to process certificates in bulk as opposed to running openssl for
* each certificate file.
*
* It reads the list of files, one filename per line, from the standard input
* and produces output in form of: <filename>\t<fingerprint or hash>.
*
* Two modes are supported. If '-f' switch is specified on the command line, for
* each file a SHA-1 fingerprint is calculated. If '-s' is specified, then
* subject hash is produced.
*/

#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/* process return codes */
#define CTH_EOK 0 /* success. */
#define CTH_EERR 1 /* incorrect invocation, cannot start. */
#define CTH_EINV 2 /* invalid input, cannot produce result. */

typedef enum {
MODE_FINGER = 1, /* output fingerprint */
MODE_HASH, /* output subject hash */
MODE_INVALID = 100
} runmode_t;

void print_help() {
char *help = "clrtrust-helper [-f|-s]\n"
"Helper utility for clrtrust. Reads the list of files from"
" stdin, one per line, and produces either fingerprint or"
" subject hash for each file on stdout in form:\n"
" <filename><TAB><fingerprint or subject hash>\n";
puts(help);
}

int main(int argc, char **argv) {

runmode_t runmode = MODE_INVALID; /* global switch: whether to produce fingerprint
or subject hash. */

BIO *inbio = NULL;

/* fingerprint-related variables */
const EVP_MD *finger_type = NULL;
unsigned int finger_sz;
unsigned char finger[EVP_MAX_MD_SIZE];

/* subjecthash-related variables */
unsigned long subject_hash;

unsigned int i;
char c;

char *fname = NULL;
size_t sz = 0;

/* return code */
int ret = CTH_EOK;

while ((c = getopt(argc, argv, "fs")) != -1) {
switch (c) {
case 'f':
if (runmode == MODE_INVALID) {
runmode = MODE_FINGER;
} else {
print_help();
return CTH_EERR;
}
break;
case 's':
if (runmode == MODE_INVALID) {
runmode = MODE_HASH;
} else {
print_help();
return CTH_EERR;
}
break;
default:
printf("Unrecognized option -%c.\n", c);
print_help();
return CTH_EERR;
}
}

if (runmode == MODE_INVALID) {
print_help();
return CTH_EERR;
}

OpenSSL_add_all_algorithms();

finger_type = EVP_sha1();

inbio = BIO_new(BIO_s_file());

while (getline(&fname, &sz, stdin) != -1) {
int fname_len = strlen(fname);
int err = 1;
FILE *fp = NULL;
X509 *cert = NULL;

if (fname_len < 1) continue;
if (fname[fname_len-1] == '\n') {
fname[fname_len-1] = '\0';
}

if (!(fp = fopen(fname, "r"))) {
goto wrap_up;
}

if (!BIO_set_fp(inbio, fp, BIO_NOCLOSE)) {
goto wrap_up;
}

if (!(cert = PEM_read_bio_X509(inbio, NULL, 0, NULL))) {
goto wrap_up;
}

switch (runmode) {
case MODE_FINGER:
if (!X509_digest(cert, finger_type, finger, &finger_sz)) {
goto wrap_up;
}
printf("%s\t", fname);
for (i=0; i < finger_sz; i++) {
printf("%02X", finger[i]);
if (i < finger_sz-1) printf(":");
}
putc('\n', stdout);
err = 0;
break;
case MODE_HASH:
subject_hash = X509_subject_name_hash(cert);
printf("%s\t%08lx\n", fname, subject_hash);
err = 0;
break;
default:
return CTH_EERR;
}

wrap_up:
if (fp) fclose(fp);
if (cert) X509_free(cert);
if (err) {
printf("%s\tERROR\n", fname);
ret = CTH_EINV;
}
}

if (fname) free(fname);

return ret;
}
94 changes: 78 additions & 16 deletions clrtrust → clrtrust.in
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,29 @@ EINVAL=22 # invalid argument
EBADST=127 # invalid state / health check does not pass
EERR=255 # general error

##### LOCATION OF THE HELPER
# always favor env override. if not, prefer the helper that is found next to the
# script itself (it indicates the development environment). if not, look at the
# configured location.
LIBEXEC_DIR=LIBEXEC_CONFIG_VALUE
CLR_HELPER_NAME=clrtrust-helper
for helper_dir in ${CLR_LIBEXEC_DIR:+"${CLR_LIBEXEC_DIR}"} "$(dirname $0)" "${LIBEXEC_DIR}"; do
if [ -x "${helper_dir}/${CLR_HELPER_NAME}" ]; then
CLR_HELPER_CMD="${helper_dir}/${CLR_HELPER_NAME}"
break
fi
done

if [ ! -x "$CLR_HELPER_CMD" ]; then
1>&2 echo "Cannot locate the helper executable (${CLR_HELPER_NAME})"
exit $EBADST
fi

# get absolute path to the helper (to be able to run in subshells)
if [[ "${CLR_HELPER_CMD}" != /* ]]; then
CLR_HELPER_CMD=$(realpath "${CLR_HELPER_CMD}")
fi

##### LOCK FILE PATH
# absolute dirs where we can have lock, must be writeable. potentially, the
# choice of lock directory could differ between two parallel runs of clrtrust,
Expand Down Expand Up @@ -135,21 +158,34 @@ find_certs() {
local dir=$1
if [ -z $dir ]; then return 255; fi
if [ ! -d $dir ]; then return 255; fi
find $dir -maxdepth 1 -type f | while read f; do
finger=$(openssl x509 -in "${f}" -noout -fingerprint -sha1 2>/dev/null)
if [ $? -ne 0 ]; then
1>&2 echo "WARNING: file ${f} is not a certificate"
continue
fi
finger=${finger#SHA1 Fingerprint=}
printf "%s\t%s\n" "${f}" "${finger}"
done
return 0
find $dir -maxdepth 1 -type f | "${CLR_HELPER_CMD}" -f

# NB: CLR_HELPER_CMD will return 2 exit status if some of the certificates
# cannot be opened/processed. calls to find_certs rely on this fact
# throughout this script.
}

find_all_certs() {
local ret
local nret
find_certs $CLR_CLEAR_TRUST_SRC/trusted
ret=$?
find_certs $CLR_LOCAL_TRUST_SRC/trusted
nret=$?
if [ $nret -ne 0 ]; then
ret=$nret
fi
return $ret
}

filter_bad_certs() {
local bad_cert_files=$(echo "$1" | sed -ne '/\tERROR$/ { s/\tERROR$//; p }')
if [ -n "$bad_cert_files" ]; then
for bad_file in "$bad_cert_files"; do
1>&2 echo "$bad_file is not a PEM-encoded X.509 certificate. Skipping..."
done
fi
echo "$1" | sed -e '/\tERROR$/d'
}

# a c_rehash implementation for creating openssl-style CApath. this
Expand All @@ -166,14 +202,20 @@ c_rehash_internal() {
fi
(
cd "$dir"
find . -maxdepth 1 -type f | while read f; do
name=$(openssl x509 -in "$f" -noout -subject_hash 2>/dev/null)
hashes=$(find . -maxdepth 1 -type f | "${CLR_HELPER_CMD}" -s)
if [ $? -ne 0 ]; then
return 1
fi
echo "$hashes" | while IFS=$'\t' read f h; do
lnno=0
while ! ln -s "$f" "${name}.${lnno}" && ((lnno++ < 100)); do
while ! ln -s "$f" "${h}.${lnno}" && ((lnno++ < 100)); do
:
done 2>/dev/null
done
)
if [ $? -ne 0 ]; then
return 1
fi
return 0
}

Expand Down Expand Up @@ -338,7 +380,7 @@ cmd_generate() {
local dup_certs_cnt
local distrust_certs_cnt
local tmp
local ret
local ret=0
local opt_skip_check # this option is used when called internally after the
# a check has been performed
local opt_force=0
Expand Down Expand Up @@ -375,6 +417,10 @@ cmd_generate() {

# find all the certificates
ca_certs=$(find_all_certs)
if [ $? -eq 2 ]; then
ca_certs=$(filter_bad_certs "$ca_certs")
ret=$EBADST
fi

if [ -z "${ca_certs}" ] && is_system_store && [ $opt_force -ne 1 ]; then
1>&2 cat <<EOF
Expand All @@ -401,6 +447,10 @@ $tmp"

# remove the distrusted ones
distrust_certs=$(find_certs $CLR_LOCAL_TRUST_SRC/distrusted)
if [ $? -eq 2 ]; then
distrust_certs=$(filter_bad_certs "$distrust_certs")
ret=$EBADST
fi
ca_certs_cnt=$(echo "$ca_certs" | wc -l)
if [ -n "$distrust_certs" ]; then
distrust_certs_cnt=$(echo "$distrust_certs" | wc -l)
Expand Down Expand Up @@ -467,7 +517,7 @@ EOF

echo "Trust store generated at ${CLR_TRUST_STORE}"

return 0
return $ret
}

print_add_help() {
Expand Down Expand Up @@ -525,7 +575,15 @@ $1"

files=$(echo "$files" | sed -e '1d')
ca_certs=$(find_all_certs)
if [ $? -eq 2 ]; then
ca_certs=$(filter_bad_certs "$ca_certs")
ret=$EBADST
fi
distrusted_certs=$(find_certs $CLR_LOCAL_TRUST_SRC/distrusted)
if [ $? -eq 2 ]; then
distrusted_certs=$(filter_bad_certs "$distrusted_certs")
ret=$EBADST
fi

err=$(mktemp)
tmp=$(mktemp)
Expand All @@ -543,7 +601,7 @@ $1"
fi
finger=$(openssl x509 -in "${f}" -noout -fingerprint -sha1 2>$tmp)
if [ $? -ne 0 ]; then
1>&2 echo "$f is not an X.509 certificate. Skipping..."
1>&2 echo "$f is not a PEM-encoded X.509 certificate. Skipping..."
cat $tmp
continue
fi
Expand Down Expand Up @@ -732,6 +790,10 @@ $1"
files=$(cat $out)
ids=$(echo $ids && (echo "$files" | cut -f 2))
certs=$(find_all_certs)
if [ $? -eq 2 ]; then
certs=$(filter_bad_certs "$certs")
ret=$EINVAL
fi
for id in $ids; do
f=$(echo "$certs" | grep $id)
if [ $? -eq 0 ]; then
Expand Down
Loading

0 comments on commit 35da189

Please sign in to comment.