diff options
-rw-r--r-- | src/certtool-args.def | 2 | ||||
-rw-r--r-- | src/certtool-common.c | 12 | ||||
-rw-r--r-- | src/certtool.c | 286 | ||||
-rw-r--r-- | tests/suite/Makefile.am | 2 | ||||
-rwxr-xr-x | tests/suite/certtool-pkcs11.sh | 176 | ||||
-rwxr-xr-x | tests/suite/testpkcs11.softhsm | 14 |
6 files changed, 299 insertions, 193 deletions
diff --git a/src/certtool-args.def b/src/certtool-args.def index 04967b94f2..f832f0f9a9 100644 --- a/src/certtool-args.def +++ b/src/certtool-args.def @@ -197,7 +197,7 @@ flag = { name = load-ca-certificate; descrip = "Loads the certificate authority's certificate file"; arg-type = string; - doc = "This option can be used with a file"; + doc = "This can be either a file or a PKCS #11 URL"; }; flag = { diff --git a/src/certtool-common.c b/src/certtool-common.c index 04af0e9429..85196629bf 100644 --- a/src/certtool-common.c +++ b/src/certtool-common.c @@ -590,6 +590,16 @@ gnutls_x509_crt_t load_ca_cert(unsigned mand, common_info_st * info) exit(1); } + if (gnutls_url_is_supported(info->ca) != 0) { + ret = gnutls_x509_crt_import_url(crt, info->ca, 0); + if (ret < 0) { + fprintf(stderr, "error importing --load-ca-certificate: %s: %s\n", + info->ca, gnutls_strerror(ret)); + exit(1); + } + return crt; + } + dat.data = (void *) read_binary_file(info->ca, &size); dat.size = size; @@ -602,7 +612,7 @@ gnutls_x509_crt_t load_ca_cert(unsigned mand, common_info_st * info) ret = gnutls_x509_crt_import(crt, &dat, info->incert_format); free(dat.data); if (ret < 0) { - fprintf(stderr, "importing --load-ca-certificate: %s: %s\n", + fprintf(stderr, "error importing --load-ca-certificate: %s: %s\n", info->ca, gnutls_strerror(ret)); exit(1); } diff --git a/src/certtool.c b/src/certtool.c index f3db41874d..5cd1a928b3 100644 --- a/src/certtool.c +++ b/src/certtool.c @@ -65,7 +65,7 @@ void smime_to_pkcs7(void); void pkcs12_info(common_info_st *); void generate_pkcs12(common_info_st *); void generate_pkcs8(common_info_st *); -static void verify_chain(void); +static void verify_chain(common_info_st * cinfo); void verify_crl(common_info_st * cinfo); void verify_pkcs7(common_info_st * cinfo, const char *purpose, unsigned display_data); void pubkey_info(gnutls_x509_crt_t crt, common_info_st *); @@ -1269,7 +1269,7 @@ static void cmd_parser(int argc, char **argv) else if (HAVE_OPT(VERIFY_PROVABLE_PRIVKEY)) verify_provable_privkey(&cinfo); else if (HAVE_OPT(VERIFY_CHAIN)) - verify_chain(); + verify_chain(&cinfo); else if (HAVE_OPT(VERIFY)) verify_certificate(&cinfo); else if (HAVE_OPT(VERIFY_CRL)) @@ -2284,10 +2284,6 @@ static gnutls_x509_trust_list_t load_tl(common_info_st * cinfo) { gnutls_x509_trust_list_t list; int ret; - FILE *fp; - gnutls_datum_t tmp = {NULL, 0}, tmp2 = {NULL, 0}; - char *cas, *crls; - size_t ca_size, crl_size; ret = gnutls_x509_trust_list_init(&list, 0); if (ret < 0) { @@ -2305,94 +2301,39 @@ static gnutls_x509_trust_list_t load_tl(common_info_st * cinfo) } fprintf(stderr, "Loaded system trust (%d CAs available)\n", ret); } else if (cinfo->ca != NULL) { - fp = fopen(cinfo->ca, "r"); - if (fp == NULL) { - fprintf(stderr, "Could not open %s\n", cinfo->ca); - exit(1); - } - - cas = (void *) fread_file(fp, &ca_size); - if (cas == NULL) { - fprintf(stderr, "Error reading CA list"); - exit(1); - } - - tmp.data = (void *) cas; - tmp.size = ca_size; - fclose(fp); - - if (cinfo->crl) { - fp = fopen(cinfo->crl, "r"); - if (fp == NULL) { - fprintf(stderr, "Could not open %s\n", cinfo->crl); - exit(1); - } - - crls = (void *) fread_file(fp, &crl_size); - if (crls == NULL) { - fprintf(stderr, "Error reading CRL list"); - exit(1); - } - - fclose(fp); - - tmp2.data = (void *) crls; - tmp2.size = crl_size; - } - - ret = - gnutls_x509_trust_list_add_trust_mem(list, &tmp, - tmp2.data?&tmp2:NULL, - cinfo->incert_format, - 0, 0); + ret = gnutls_x509_trust_list_add_trust_file(list, cinfo->ca, cinfo->crl, cinfo->incert_format, 0, 0); if (ret < 0) { - int ret2 = - gnutls_x509_trust_list_add_trust_mem(list, &tmp, - tmp2.data?&tmp2:NULL, - GNUTLS_X509_FMT_PEM, - 0, 0); + int ret2 = gnutls_x509_trust_list_add_trust_file(list, cinfo->ca, cinfo->crl, GNUTLS_X509_FMT_PEM, 0, 0); if (ret2 >= 0) ret = ret2; } if (ret < 0) { - fprintf(stderr, "gnutls_x509_trust_add_trust_mem: %s\n", + fprintf(stderr, "gnutls_x509_trust_add_trust_file: %s\n", gnutls_strerror(ret)); exit(1); } - free(tmp.data); - free(tmp2.data); - fprintf(stderr, "Loaded CAs (%d available)\n", ret); } return list; } -/* Will verify a certificate chain. If no CA certificates - * are provided, then the last certificate in the certificate - * chain is used as a CA. - * - * If @system is non-zero then the system's CA will be used. +/* Loads from a certificate chain, the last certificate on the + * trusted list. In addition it will load any CRLs if present. */ -static int -_verify_x509_mem(const void *cert, int cert_size, const void *ca, - int ca_size, unsigned system, const char *purpose, - const char *hostname, const char *email) +static gnutls_x509_trust_list_t load_tl_from_cert_chain(const char *cert, int cert_size) { - int ret; - unsigned i; gnutls_datum_t tmp; gnutls_x509_crt_t *x509_cert_list = NULL; - gnutls_x509_crt_t *x509_ca_list = NULL; - gnutls_x509_crt_t *pca_list = NULL; gnutls_x509_crl_t *x509_crl_list = NULL; - unsigned int x509_ncerts, x509_ncrls = 0, x509_ncas = 0; + unsigned x509_ncerts, x509_ncrls = 0; + unsigned i; + int ret; gnutls_x509_trust_list_t list; - unsigned int output; - unsigned vflags; + /* otherwise load the certificates in the trust list */ ret = gnutls_x509_trust_list_init(&list, 0); if (ret < 0) { fprintf(stderr, "gnutls_x509_trust_list_init: %s\n", @@ -2400,105 +2341,104 @@ _verify_x509_mem(const void *cert, int cert_size, const void *ca, exit(1); } - if (system != 0) { - ret = gnutls_x509_trust_list_add_system_trust(list, 0, 0); - if (ret < 0) { - fprintf(stderr, "Error loading system trust: %s\n", - gnutls_strerror(ret)); - exit(1); - } - fprintf(stderr, "Loaded system trust (%d CAs available)\n", ret); - - x509_ncas = ret; + tmp.data = (void *) cert; + tmp.size = cert_size; - tmp.data = (void *) cert; - tmp.size = cert_size; - - /* ignore errors. CRLs might not be given */ - ret = - gnutls_x509_crt_list_import2(&x509_cert_list, - &x509_ncerts, &tmp, - GNUTLS_X509_FMT_PEM, 0); - if (ret < 0 || x509_ncerts < 1) { - fprintf(stderr, "error parsing CRTs: %s\n", - gnutls_strerror(ret)); - exit(1); - } + ret = gnutls_x509_crt_list_import2(&x509_cert_list, &x509_ncerts, &tmp, GNUTLS_X509_FMT_PEM, 0); + if (ret < 0 || x509_ncerts < 1) { + fprintf(stderr, "error parsing CRTs: %s\n", + gnutls_strerror(ret)); + exit(1); + } - } else { - if (ca == NULL) { - tmp.data = (void *) cert; - tmp.size = cert_size; - } else { - tmp.data = (void *) ca; - tmp.size = ca_size; + ret = + gnutls_x509_crl_list_import2(&x509_crl_list, + &x509_ncrls, &tmp, + GNUTLS_X509_FMT_PEM, 0); + if (ret < 0) { + x509_crl_list = NULL; + x509_ncrls = 0; + } - /* Load CAs */ - ret = - gnutls_x509_crt_list_import2(&x509_ca_list, - &x509_ncas, &tmp, - GNUTLS_X509_FMT_PEM, - 0); - if (ret < 0 || x509_ncas < 1) { - fprintf(stderr, "error parsing CAs: %s\n", - gnutls_strerror(ret)); - exit(1); - } - } - pca_list = x509_ca_list; + /* add CAs */ + ret = + gnutls_x509_trust_list_add_cas(list, &x509_cert_list[x509_ncerts - 1], + 1, 0); + if (ret < 0) { + fprintf(stderr, "gnutls_x509_trust_add_cas: %s\n", + gnutls_strerror(ret)); + exit(1); + } + /* add CRLs */ + if (x509_ncrls > 0) { ret = - gnutls_x509_crl_list_import2(&x509_crl_list, - &x509_ncrls, &tmp, - GNUTLS_X509_FMT_PEM, 0); + gnutls_x509_trust_list_add_crls(list, x509_crl_list, + x509_ncrls, 0, 0); if (ret < 0) { - x509_crl_list = NULL; - x509_ncrls = 0; + fprintf(stderr, "gnutls_x509_trust_add_crls: %s\n", + gnutls_strerror(ret)); + exit(1); } + } + if (x509_ncerts > 1) { + for (i=0;i<x509_ncerts-1;i++) + gnutls_x509_crt_deinit(x509_cert_list[i]); + } + gnutls_free(x509_cert_list); + gnutls_free(x509_crl_list); - tmp.data = (void *) cert; - tmp.size = cert_size; + return list; +} - /* ignore errors. CRLs might not be given */ - ret = - gnutls_x509_crt_list_import2(&x509_cert_list, - &x509_ncerts, &tmp, - GNUTLS_X509_FMT_PEM, 0); - if (ret < 0 || x509_ncerts < 1) { - fprintf(stderr, "error parsing CRTs: %s\n", - gnutls_strerror(ret)); - exit(1); - } +/* Will verify a certificate chain. If no CA certificates + * are provided, then the last certificate in the certificate + * chain is used as a CA. + * + * If @system is non-zero then the system's CA will be used. + */ +static int +_verify_x509_mem(const void *cert, int cert_size, common_info_st *cinfo, + unsigned use_system_trust, /* if ca_file == NULL */ + const char *purpose, + const char *hostname, const char *email) +{ + int ret; + unsigned i; + gnutls_datum_t tmp; + gnutls_x509_crt_t *x509_cert_list = NULL; + unsigned int x509_ncerts; + gnutls_x509_trust_list_t list; + unsigned int output; + unsigned vflags; - if (ca == NULL) { - pca_list = &x509_cert_list[x509_ncerts - 1]; - x509_ncas = 1; + if (use_system_trust != 0 || cinfo->ca != NULL) { + list = load_tl(cinfo); + if (list == NULL) { + fprintf(stderr, "error loading trust list\n"); } - ret = - gnutls_x509_trust_list_add_cas(list, pca_list, - x509_ncas, 0); - if (ret < 0) { - fprintf(stderr, "gnutls_x509_trust_add_cas: %s\n", - gnutls_strerror(ret)); - exit(1); + } else { + list = load_tl_from_cert_chain(cert, cert_size); + if (list == NULL) { + fprintf(stderr, "error loading trust list\n"); } + } - ret = - gnutls_x509_trust_list_add_crls(list, x509_crl_list, - x509_ncrls, 0, 0); - if (ret < 0) { - fprintf(stderr, "gnutls_x509_trust_add_crls: %s\n", - gnutls_strerror(ret)); - exit(1); - } + tmp.data = (void *) cert; + tmp.size = cert_size; + ret = + gnutls_x509_crt_list_import2(&x509_cert_list, + &x509_ncerts, &tmp, + GNUTLS_X509_FMT_PEM, 0); + if (ret < 0 || x509_ncerts < 1) { + fprintf(stderr, "error parsing CRTs: %s\n", + gnutls_strerror(ret)); + exit(1); } - fprintf(stdout, "Loaded %d certificates, %d CAs and %d CRLs\n\n", - x509_ncerts, x509_ncas, x509_ncrls); - vflags = GNUTLS_VERIFY_DO_NOT_ALLOW_SAME; if (HAVE_OPT(VERIFY_ALLOW_BROKEN)) @@ -2555,18 +2495,10 @@ _verify_x509_mem(const void *cert, int cert_size, const void *ca, fprintf(outfile, "\n\n"); - gnutls_x509_trust_list_deinit(list, 0); + gnutls_x509_trust_list_deinit(list, 1); for (i=0;i<x509_ncerts;i++) gnutls_x509_crt_deinit(x509_cert_list[i]); gnutls_free(x509_cert_list); - if (x509_ca_list != NULL) { - for (i=0;i<x509_ncas;i++) - gnutls_x509_crt_deinit(x509_ca_list[i]); - gnutls_free(x509_ca_list); - } - for (i=0;i<x509_ncrls;i++) - gnutls_x509_crl_deinit(x509_crl_list[i]); - gnutls_free(x509_crl_list); if (output != 0) exit(EXIT_FAILURE); @@ -2598,18 +2530,23 @@ static void print_verification_res(FILE * out, unsigned int output) gnutls_free(pout.data); } -static void verify_chain(void) +static void verify_chain(common_info_st * cinfo) { char *buf; size_t size; + if (cinfo->ca != NULL) { + fprintf(stderr, "This option cannot be combined with --load-ca-certificate\n"); + exit(1); + } + buf = (void *) fread_file(infile, &size); if (buf == NULL) { fprintf(stderr, "Error reading chain"); exit(1); } - _verify_x509_mem(buf, size, NULL, 0, 0, OPT_ARG(VERIFY_PURPOSE), + _verify_x509_mem(buf, size, cinfo, 0, OPT_ARG(VERIFY_PURPOSE), OPT_ARG(VERIFY_HOSTNAME), OPT_ARG(VERIFY_EMAIL)); free(buf); } @@ -2618,8 +2555,7 @@ static void verify_certificate(common_info_st * cinfo) { char *cert; char *cas = NULL; - size_t cert_size, ca_size = 0; - FILE *ca_file; + size_t cert_size; cert = (void *) fread_file(infile, &cert_size); if (cert == NULL) { @@ -2627,24 +2563,8 @@ static void verify_certificate(common_info_st * cinfo) exit(1); } - if (cinfo->ca != NULL) { - ca_file = fopen(cinfo->ca, "r"); - if (ca_file == NULL) { - fprintf(stderr, "Could not open %s\n", cinfo->ca); - exit(1); - } - - cas = (void *) fread_file(ca_file, &ca_size); - if (cas == NULL) { - fprintf(stderr, "Error reading CA list"); - exit(1); - } - - fclose(ca_file); - } - - _verify_x509_mem(cert, cert_size, cas, ca_size, - (cinfo->ca != NULL) ? 0 : 1, OPT_ARG(VERIFY_PURPOSE), + _verify_x509_mem(cert, cert_size, cinfo, 1, + OPT_ARG(VERIFY_PURPOSE), OPT_ARG(VERIFY_HOSTNAME), OPT_ARG(VERIFY_EMAIL)); free(cert); free(cas); diff --git a/tests/suite/Makefile.am b/tests/suite/Makefile.am index 6f1c4b08cb..ae7c647f5c 100644 --- a/tests/suite/Makefile.am +++ b/tests/suite/Makefile.am @@ -90,7 +90,7 @@ EXTRA_DIST += testcompat-main-polarssl testcompat-main-openssl testcompat-common testpkcs11.pkcs15 testpkcs11.softhsm testpkcs11.sc-hsm nodist_check_SCRIPTS = testsrn.sh chain.sh invalid-cert.sh \ testrng.sh testcompat-polarssl.sh testcompat-openssl.sh \ - testrandom.sh pkcs7-cat + testrandom.sh pkcs7-cat certtool-pkcs11.sh if ENABLE_PKCS11 nodist_check_SCRIPTS += testpkcs11.sh crl-test diff --git a/tests/suite/certtool-pkcs11.sh b/tests/suite/certtool-pkcs11.sh new file mode 100755 index 0000000000..7bc0784249 --- /dev/null +++ b/tests/suite/certtool-pkcs11.sh @@ -0,0 +1,176 @@ +#!/bin/bash + +# Copyright (C) 2016 Nikos Mavrogiannopoulos +# +# This file is part of GnuTLS. +# +# GnuTLS is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at +# your option) any later version. +# +# GnuTLS is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GnuTLS; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +srcdir="${srcdir:-.}" +P11TOOL="${P11TOOL:-../../src/p11tool${EXEEXT}}" +CERTTOOL="${CERTTOOL:-../../src/certtool${EXEEXT}}" +DIFF="${DIFF:-diff -b -B}" +SERV="${SERV:-../../src/gnutls-serv${EXEEXT}} -q" +CLI="${CLI:-../../src/gnutls-cli${EXEEXT}}" +RETCODE=0 + +if ! test -z "${VALGRIND}"; then + VALGRIND="${LIBTOOL:-libtool} --mode=execute valgrind --leak-check=full" +fi + +TMPFILE="verify-pkcs11.debug" +CERTTOOL_PARAM="--stdout-info" + +if test "${WINDIR}" != ""; then + exit 77 +fi + +P11TOOL="${VALGRIND} ${P11TOOL} --batch" + +. ${srcdir}/../scripts/common.sh + +rm -f "${TMPFILE}" + +exit_error () { + echo "check ${TMPFILE} for additional debugging information" + echo "" + echo "" + tail "${TMPFILE}" + exit 1 +} + +check_for_datefudge + +# $1: token +# $2: PIN +# $3: filename +# $4: label +write_ca_cert () { + export GNUTLS_PIN="$2" + filename="$3" + token="$1" + label="$4" + + echo -n "* Writing the CA certificate... " + ${P11TOOL} ${ADDITIONAL_PARAM} --mark-ca --mark-trusted --no-mark-private --so-login --write --label "$label" --load-certificate "${filename}" "${token}" >>"${TMPFILE}" 2>&1 + if test $? = 0; then + echo ok + else + echo failed + exit_error + fi + +} + +# $1: token +# $2: PIN +# $3: filename +write_ca_privkey () { + export GNUTLS_PIN="$2" + filename="$3" + token="$1" + + echo -n "* Writing the CA private key... " + ${P11TOOL} ${ADDITIONAL_PARAM} --login --write --label CA-key --load-privkey "${filename}" "${token}" >>"${TMPFILE}" 2>&1 + if test $? = 0; then + echo ok + else + echo failed + exit_error + fi +} + +# $1: URL +# $2: cert file to verify +verify_certificate_test() { + url=$1 + file=$2 + + echo -n "* Verifying a certificate... " + datefudge -s "2015-10-10" \ + $CERTTOOL ${ADDITIONAL_PARAM} --verify --load-ca-certificate "$url" --infile "$file" >>"${TMPFILE}" 2>&1 + if test $? = 0; then + echo ok + else + echo "failed $file with $url" + exit_error + fi +} + +generate_cert() { + url=$1 + + echo -n "* Generating a certificate... " + $CERTTOOL ${ADDITIONAL_PARAM} --generate-certificate --load-ca-certificate "$url" --load-ca-privkey "${srcdir}/pkcs11-certs/ca.key" --load-privkey "${srcdir}/pkcs11-certs/server.key" --template "${srcdir}/pkcs11-certs/server-tmpl" >>"${TMPFILE}" 2>&1 + if test $? = 0; then + echo ok + else + echo "failed generation with $url" + exit_error + fi +} + +generate_cert_with_key() { + ca_url=$1 + ca_key_url=$2 + + echo -n "* Generating a certificate (privkey in pkcs11)... " + $CERTTOOL ${ADDITIONAL_PARAM} --generate-certificate --load-ca-certificate "${ca_url}" --load-ca-privkey "${ca_key_url}" --load-privkey "${srcdir}/pkcs11-certs/server.key" --template "${srcdir}/pkcs11-certs/server-tmpl" >>"${TMPFILE}" 2>&1 + if test $? = 0; then + echo ok + else + echo "failed generation with $url" + exit_error + fi +} + +echo "Testing PKCS11 verification" + +# erase SC + +type="softhsm" + +. "${srcdir}/testpkcs11.${type}" + +export GNUTLS_PIN=12345678 +export GNUTLS_SO_PIN=00000000 + +init_card "${GNUTLS_PIN}" "${GNUTLS_SO_PIN}" + +# find token name +TOKEN=`${P11TOOL} ${ADDITIONAL_PARAM} --list-tokens pkcs11:token=Nikos|grep URL|grep token=GnuTLS-Test|sed 's/\s*URL\: //g'` + +echo "* Token: ${TOKEN}" +if test "x${TOKEN}" = x; then + echo "Could not find generated token" + exit_error +fi + +write_ca_cert "${TOKEN}" "${GNUTLS_PIN}" "${srcdir}/pkcs11-certs/ca.crt" "CA" + +verify_certificate_test "${TOKEN};object=CA;object-type=cert" "${srcdir}/pkcs11-certs/server.crt" +verify_certificate_test "${TOKEN};object=CA;object-type=cert" "${srcdir}/pkcs11-certs/client.crt" +generate_cert "${TOKEN};object=CA;object-type=cert" + +write_ca_privkey "${TOKEN}" "${GNUTLS_PIN}" "${srcdir}/pkcs11-certs/ca.key" + +generate_cert_with_key "${TOKEN};object=CA;object-type=cert" "${TOKEN};object=CA-key;object-type=private" + +if test ${RETCODE} = 0; then + echo "* All tests succeeded" +fi +rm -f "${TMPFILE}" + +exit 0 diff --git a/tests/suite/testpkcs11.softhsm b/tests/suite/testpkcs11.softhsm index 3ec5076318..4f7f9f10d8 100755 --- a/tests/suite/testpkcs11.softhsm +++ b/tests/suite/testpkcs11.softhsm @@ -35,7 +35,7 @@ init_card () { PUK="$2" if test -x "/usr/bin/softhsm2-util"; then - export SOFTHSM2_CONF="softhsm-testpkcs11.config" + export SOFTHSM2_CONF="softhsm-testpkcs11.$$.config.tmp" SOFTHSM_TOOL="/usr/bin/softhsm2-util" ${SOFTHSM_TOOL} --version|grep "2.0.0" >/dev/null 2>&1 if test $? = 0; then @@ -45,7 +45,7 @@ init_card () { fi if test -x "/usr/bin/softhsm"; then - export SOFTHSM_CONF="softhsm-testpkcs11.config" + export SOFTHSM_CONF="softhsm-testpkcs11.$$.config.tmp" SOFTHSM_TOOL="/usr/bin/softhsm" fi @@ -55,13 +55,13 @@ init_card () { fi if test -z "${SOFTHSM_CONF}"; then - rm -rf ./softhsm-testpkcs11.db - mkdir -p ./softhsm-testpkcs11.db + rm -rf ./softhsm-testpkcs11.$$.tmp + mkdir -p ./softhsm-testpkcs11.$$.tmp echo "objectstore.backend = file" > "${SOFTHSM2_CONF}" - echo "directories.tokendir = ./softhsm-testpkcs11.db" >> "${SOFTHSM2_CONF}" + echo "directories.tokendir = ./softhsm-testpkcs11.$$.tmp" >> "${SOFTHSM2_CONF}" else - rm -rf ./softhsm-testpkcs11.db - echo "0:./softhsm-testpkcs11.db" > "${SOFTHSM_CONF}" + rm -rf ./softhsm-testpkcs11.$$.tmp + echo "0:./softhsm-testpkcs11.$$.tmp" > "${SOFTHSM_CONF}" fi |