OpenSSL c_rehash translation

[email protected]
Newsgroups gmane.os.netbsd.devel.crypto
Message-ID <[email protected]>
I wanted to use a hashed certificate directory as the CApath argument to
OpenSSL's SSL_CTX_load_verify_locations().  The normal OpenSSL distribution
installs the Perl script c_rehash from the tools directory to automate the
process of building a directory of hash-as-filename symlinks to the
contained certificates.  This script is not installed on NetBSD since Perl is
not in the base system.  I have translated the script to a shell script and
tested it on a few situations.  I'm posting it here so others may find and
use it as necessary.

8<-----8<-----8<------8<------8<-----8<-----8<------8<------
#!/bin/sh
#
# Ben Secrest <[email protected]>
#
# sh c_rehash script, scan all files in a directory
# and add symbolic links to their hash values.
#
# based on the c_rehash perl script distributed with openssl
#
# LICENSE: See OpenSSL license
# ^^acceptable?^^
#

# default certificate location
DIR=/etc/openssl

# for filetype bitfield
IS_CERT=$(( 1 << 0 ))
IS_CRL=$(( 1 << 1 ))


#
# check to see if a file is a certificate file or a CRL file
# arguments:
#       1. the filename to be scanned
# returns:
#       bitfield of file type; uses ${IS_CERT} and ${IS_CRL}
#
check_file()
{
    local IS_TYPE=0

    # make IFS a newline so we can process grep output line by line
    local OLDIFS=${IFS}
    IFS=$( printf "\n" )

    # XXX: could be more efficient to have two 'grep -m' but is -m portable?
    for LINE in $( grep '^-----BEGIN .*-----' ${1} )
    do
	if echo ${LINE} \
	    | grep -q -E '^-----BEGIN (X509 |TRUSTED )?CERTIFICATE-----'
	then
	    IS_TYPE=$(( ${IS_TYPE} | ${IS_CERT} ))

	    if [ $(( ${IS_TYPE} & ${IS_CRL} )) -ne 0 ]
	    then
	    	break
	    fi
	elif echo ${LINE} | grep -q '^-----BEGIN X509 CRL-----'
	then
	    IS_TYPE=$(( ${IS_TYPE} | ${IS_CRL} ))

	    if [ $(( ${IS_TYPE} & ${IS_CERT} )) -ne 0 ]
	    then
	    	break
	    fi
	fi
    done

    # restore IFS
    IFS=${OLDIFS}

    return ${IS_TYPE}
}


#
# use openssl to fingerprint a file
#    arguments:
#	1. the filename to fingerprint
#	2. the method to use (x509, crl)
#    returns:
#	none
#    assumptions:
#	user will capture output from last stage of pipeline
#
fingerprint()
{
    ${SSL_CMD} ${2} -fingerprint -noout -in ${1} | sed 's/^.*=//' | tr -d ':'
}


#
# link_hash - create links to certificate files
#    arguments:
#       1. the filename to create a link for
#	2. the type of certificate being linked (x509, crl)
#    returns:
#	0 on success, 1 otherwise
#
link_hash()
{
    local FINGERPRINT=$( fingerprint ${1} ${2} )
    local HASH=$( ${SSL_CMD} ${2} -hash -noout -in ${1} )
    local SUFFIX=0
    local LINKFILE=''
    local TAG=''

    if [ ${2} = "crl" ]
    then
    	TAG='r'
    fi

    LINKFILE=${HASH}.${TAG}${SUFFIX}

    while [ -f ${LINKFILE} ]
    do
	if [ ${FINGERPRINT} = $( fingerprint ${LINKFILE} ${2} ) ]
	then
	    printf "WARNING: Skipping duplicate file ${1}\n" >&2
	    return 1
	fi	

	SUFFIX=$(( ${SUFFIX} + 1 ))
	LINKFILE=${HASH}.${TAG}${SUFFIX}
    done

    printf "${1} => ${LINKFILE}\n"

    # assume any system with a POSIX shell will either support symlinks or
    # do something to handle this gracefully
    ln -s ${1} ${LINKFILE}

    return 0
}


# hash_dir create hash links in a given directory
hash_dir()
{
    printf "Doing ${1}\n"

    cd ${1}

    for FILE in *
    do
	# no files in directory at all, no point in continuing
	if ! [ -f ${FILE} ]
	then
	    return 1
	fi

        if echo ${FILE} | grep -q -E '^[[:xdigit:]]{8}\.r?[[:digit:]]+$' \
	    	&& [ -h "${FILE}" ]
        then
            rm ${FILE}
        fi
    done

    for FILE in *.pem
    do
	# no pem files so FILE gets set to the unexpanded *.pem
	if ! [ -f ${FILE} ]
	then
	    break
	fi

	check_file ${FILE}
        local FILE_TYPE=${?}
	local TYPE_STR=''

        if [ $(( ${FILE_TYPE} & ${IS_CERT} )) -ne 0 ]
        then
            TYPE_STR='x509'
        elif [ $(( ${FILE_TYPE} & ${IS_CRL} )) -ne 0 ]
        then
            TYPE_STR='crl'
        else
            printf "WARNING: ${FILE} does not contain a certificate or CRL: skipping\n" >&2
	    continue
        fi

	link_hash ${FILE} ${TYPE_STR}
    done
}


# choose the name of an ssl application
if [ -n "${OPENSSL}" ]
then
    SSL_CMD=${OPENSSL}
else
    SSL_CMD=openssl
    OPENSSL=${SSL_CMD}
    export ${OPENSSL}
fi

# fix paths
PATH=${PATH}:${DIR}/bin
export PATH

# confirm existance/executability of ssl command
if ! [ -x $( which ${SSL_CMD} ) ]
then
    printf "${0}: rehashing skipped ('openssl' program not available)\n" >&2
    exit 0
fi

# determine which directories to process
# XXX: can't handle directories with spaces in names
# XXX: ...use \n as dir separator and manipulate IFS?
if [ ${#} -gt 0 ]
then
    DIRLIST=${*}
elif [ -n "${SSL_CERT_DIR}" ]
then
    DIRLIST=$( echo ${SSL_CERT_DIR} | tr ':' ' ' )
else
    DIRLIST=${DIR}/certs
fi

# process directories
for CERT_DIR in ${DIRLIST}
do
    if [ -d ${CERT_DIR} -a -w ${CERT_DIR} ]
    then
        hash_dir ${CERT_DIR}
    fi
done
8<-----8<-----8<------8<------8<-----8<-----8<------8<------
-- 
Ben Secrest <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.