Estoy luchando por encontrar una pista o solución para leer los valores de configuración de openssl en un script de shell. Déjame brindarte un poco más de detalles.
Yo tengo unopenssl.conffichero con el siguiente contenido simplificado:
[ca_one]
dir = /home/auhorities/ca_one
certs = $dir/certs
database = $dir/index.txt
serial = $dir/serial
[ca_two]
dir = /home/auhorities/ca_two
certs = $dir/certs
database = $dir/index.txt
serial = $dir/serial
Estoy escribiendo un script bash, que buscará en el directorio dir/certs de cada autoridad de certificación, es decir, necesito obtener valores./home/autoridades/ca_one/certsy/home/autoridades/ca_two/certsformar el archivo. Por ahora, terminé con la siguiente solución:
#!/usr/bin/env bash
for certs_dir in $(grep -E ^dir.+ openssl.conf | tr -d ' ' | cut -d '=' -f 2); do
echo "$certs_dir/certs"
done
Sin embargo, me temo que esto no es ideal si la funcionalidad del script se actualizará en el futuro. Estoy buscando una solución que me permita a mí o a mis colegas del futuro navegar por las entradas de la autoridad de certificación de una manera más cómoda. Por ejemplo, encontré opensslconfiguraciónmanual(https://www.openssl.org/docs/manmaster/man5/config.html), que dice que elconfiguraciónbiblioteca "se puede utilizar para leer archivos de configuración". Hasta donde tengo entendido, esta biblioteca es utilizada internamente por herramientas openssl comoCalifornia, requ otro. ¿Es entonces posible leer las entradas del archivo de configuración utilizando algunas de las utilidades de openssl? Probablemente me esté perdiendo algo, pero no puedo saber si es posible o no. Si esto no es una opción, ¿cómo afrontaría usted una situación así?
Gracias de antemano.
Respuesta1
No existe una herramienta de línea de comando oficial para analizar o validar openssl.cnf. Sin embargo , construí una bash
función compatible.configuración de hombreSin duda es algo que necesitas leer y releer.
IMPORTANTE: Si su configuración no funciona, esto no funcionará. ¡Arreglalo!
El ejemplo openssl.cnf
necesita ser un poco más sucio para poder realizar pruebas:
#set this! it forces ${var}/$(var); treats $var as a string literal
.pragma = dollarid:true
#with a huge config you are eventually going to want to section it out
.include my_other_conf.cnf
#safe variable mapping, always use to avoid config errors
DS = / #set a default variable value (DS = \ in Windows)
DS = ${ENV::DS} #value above used if DS isn't mapped in the shell
[ca_one]
#indent every section for readability
dir = /home/auhorities/ca_one
certs = $dir/certs
database = $dir/index.txt
serial = $dir/serial
[ca_two]
dir = /home/auhorities/ca_two
certs = $dir/certs
database = $dir/index.txt
serial = $dir/serial
[ section_test0 ] #this is just how nasty (but valid) things get
space_test= " space " ' test '
var_test_ = boop
var_test.0 = ${var_test_} #local syntax
var_test.1 = $(var_test_) #alt syntax
var_test.2 = ${section_test0::var_test_} #$section::name syntax
var_test.3 = ${ENV::DS} #$ENV::name syntax
dollarid_test = $var_test_ #$dollarid:off only
escape_test = H\C3\A0 N\E1\BB\99i \ \# \\
test_multiline= 123 \\ \ \
456\
#789
Ahora desinfecte el archivo de configuración: elimine comentarios, líneas vacías, espacios en blanco al final/inicial y espacios en blanco dentro de [ section ]
etiquetas y name = value
pares.
Asigne los name=value
pares resultantes como SSL_CONF[section,name]=value
con lo siguiente:
#!/bin/bash
declare -A SSL_CONF #initialize our config array
declare SSL_CONF_SECTION #set last section
function ssl_include(){
local m a id d="$3" c="${1:-$OPENSSL_CONF}" e='a-zA-Z0-9_' #start in last section. dollarid:false (default). set conf file. regex to match sections
[[ ! -r "$c" ]] && return #the file isn't readable
SSL_CONF_SECTION="${2/ /}" #set section
[ -d "$c" ] && local d='nodir' c="\"${c%/}/*.cnf\" \"${c%/}/*.conf\"" #conf is a dir
while IFS= read -r l || [ -n "$l" ]; do #build SSL_CONF array
l="${l%%[^\\]#*}" #remove comment
if [ "$m" != '' ]; then #last line ended with /
[[ "$l" =~ [^\\]\\$ ]] && l="${l:0:-1}" || m='' #check for continued multiline
SSL_CONF[${SSL_CONF_SECTION// /},${m}]="${SSL_CONF[${SSL_CONF_SECTION// /},${m}]}${l//[^\\]\\$/}" && continue #add current line to last conf and move to next line
fi
l="${l#"${l%%[![:space:]]*}"}"; l="${l%"${l##*[![:space:]]}"}"; [[ "$l" == '' ]] && continue #remove leading/trailing whitespace, then skip empty lines
if [[ "$l" =~ ^\.include[[:space:]]*=[[:space:]]*(.*)$ ]]; then #include additional files
[ "$d" == 'nodir' ] && continue #dir loaded conf files cant include further
local d='no' i="${BASH_REMATCH[1]}" o="${OPENSSL_CONF_INCLUDE:-${c%/*}}" #no variable parsing, store last match, handle default include path
[[ ! "$i" =~ ^\.{0,2}/ ]] && i="${o%/}/${i}" #append default path to relative paths
for f in "$i"; do [ -r "$f" ] && ssl_include "$f" "${SSL_CONF_SECTION// /} " "$d"; done #parse additional configuration files, keeping section
continue
fi
[[ "${SSL_CONF_SECTION// /}" == '' && "$l" =~ ^\.pragma[[:space:]]*=[[:space:]]*(dollarid)[[:space:]]*:[[:space:]]*(true|on)$ ]] && id=${BASH_REMATCH[2]} && continue #see how local variables are parsed
[[ "$l" =~ ^\[[[:space:]]*([${e}]+)[[:space:]]*\]$ ]] && SSL_CONF_SECTION=${BASH_REMATCH[1]} && continue #set section name
if [[ "$l" =~ ^([${e},\;.]+)[[:space:]]*=[[:space:]]*(.+)$ ]]; then #name value pair
local n="${BASH_REMATCH[1]}" v="${BASH_REMATCH[2]}"
[[ "$v" =~ [^\\]\\$ ]] && o="$n" #found a multiline value
SSL_CONF[${SSL_CONF_SECTION// /},${n}]="${v//\\[^nbrt\$\\\#]/}" && continue #add name value to SSL_CONF array
fi
done< <(cat $c 2>/dev/null) #loop through the config(s)
}
La lógica seguida fue:
- Se ignoran los comentarios, las líneas en blanco y los espacios en blanco iniciales y finales.
[ section_name ]
yname = value
el espacio en blanco interno se ignora.include
y.pragma
no requiere un=
. Se permitió la compatibilidad con versiones anteriores..include
puede estar en cualquier parte del archivo.include /dir/
incluye*.cnf
y*.conf
en el directorio definido. Deshabilita.include
el procesamiento en los archivos incluidos.section
y$var
los nombres pueden consistir ena-z
,A-Z
,0-9
y_
name
puede consistir ena-z
,A-Z
,0-9
,_
,;
,.
, y,
- un
value
final en una sola\
(barra invertida) continúa en la siguiente línea - algunas secuencias requieren escapar de
\
esta manera:\\
,\\$
,\#
,\n
,\b
,\r
,\t
Ahora tienes una matriz asociativa como SSL_CONF[section,name]=value
. Los .include=(.*)
archivos se analizan de forma recursiva tan pronto como se encuentran. .pragma=dollarid:true
también se procesa para que pueda analizar variables con precisión.
Ahora tienes un último problema:VARIABLES DE CONFIGURACIÓN. Actualmente están asignados como ${var}
, ${section::var}
, ${ENV::var}
, $var
, $section::var
,$ENV::var
Y $(var)
, $(section::var)
, $(ENV::var)
(¿quién lo diría?). Afortunadamente, podemos recorrer nuestra SSL_CONF
matriz y asignar los valores reales:
#!/bin/bash
[ "$id" != '' ] && a='\{' #ignore not bracketed variables
local f="[^\\\\]*(\\\$(\(|\{|${a})([${e}]+)(::([${e}]+))?(\)|\}|))" # match $var ${var} $(var) unless preceded by \
for k in "${!SSL_CONF[@]}"; do #loop through our array looking for variables
local o #last value placeholder
while [ "${SSL_CONF[$k]}" != "$o" ]; do #only loop if the variable changed
o="${SSL_CONF[$k]}" #set loop to exit on no change
if [[ "${SSL_CONF[$k]}" =~ $f ]] && \
[[ "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '' || "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '()' || "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '{}' ]] #brackets match
then #the value contains a variable
local r=' #' #replacement indicator (illegal value)
[[ "$r" == ' #' && ${SSL_CONF[${k%%,*},${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[${k%%,*},${BASH_REMATCH[3]}]}" #local variable
[[ "$r" == ' #' && ${SSL_CONF[default,${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[default,${BASH_REMATCH[3]}]}" #'default' variable
[[ "$r" == ' #' && ${SSL_CONF[,${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[,${BASH_REMATCH[3]}]}" #default variable
if [ "${BASH_REMATCH[5]}" != '' ]; then #variable is from another section, default, or ENV
[[ "$r" == ' #' && "${BASH_REMATCH[3]}" == "ENV" ]] && r="${!BASH_REMATCH[5]:-${SSL_CONF[,${BASH_REMATCH[5]}]}}" #environment variable
[[ "$r" == ' #' && ${SSL_CONF[${BASH_REMATCH[3]},${BASH_REMATCH[5]}]+isset} ]] && r="${SSL_CONF[${BASH_REMATCH[3]},${BASH_REMATCH[5]}]}" #section variable
fi
[ "$r" != ' #' ] && SSL_CONF[$k]="${SSL_CONF[$k]//${BASH_REMATCH[1]}/$r}" #replace our variable with the value
fi
done
done
¡Y ahora todas tus variables son sus valores calculados!
Aquí está la función completa y el código de ejemplo:
#!/bin/bash
declare -A SSL_CONF #initialize our config array
declare SSL_CONF_SECTION #set last section
function ssl_include(){
local m a id d="$3" c="${1:-$OPENSSL_CONF}" e='a-zA-Z0-9_' #start in last section. dollarid:false (default). set conf file. regex to match sections
[[ ! -r "$c" ]] && return #the file isn't readable
SSL_CONF_SECTION="${2/ /}" #set section
[ -d "$c" ] && local d='nodir' c="\"${c%/}/*.cnf\" \"${c%/}/*.conf\"" #conf is a dir
while IFS= read -r l || [ -n "$l" ]; do #build SSL_CONF array
l="${l%%[^\\]#*}" #remove comment
if [ "$m" != '' ]; then #last line ended with /
[[ "$l" =~ [^\\]\\$ ]] && l="${l:0:-1}" || m='' #check for continued multiline
SSL_CONF[${SSL_CONF_SECTION// /},${m}]="${SSL_CONF[${SSL_CONF_SECTION// /},${m}]}${l//[^\\]\\$/}" && continue #add current line to last conf and move to next line
fi
l="${l#"${l%%[![:space:]]*}"}"; l="${l%"${l##*[![:space:]]}"}"; [[ "$l" == '' ]] && continue #remove leading/trailing whitespace, then skip empty lines
if [[ "$l" =~ ^\.include[[:space:]]*=[[:space:]]*(.*)$ ]]; then #include additional files
[ "$d" == 'nodir' ] && continue #dir loaded conf files cant include further
local d='no' i="${BASH_REMATCH[1]}" o="${OPENSSL_CONF_INCLUDE:-${c%/*}}" #no variable parsing, store last match, handle default include path
[[ ! "$i" =~ ^\.{0,2}/ ]] && i="${o%/}/${i}" #append default path to relative paths
for f in "$i"; do [ -r "$f" ] && ssl_include "$f" "${SSL_CONF_SECTION// /} " "$d"; done #parse additional configuration files, keeping section
continue
fi
[[ "${SSL_CONF_SECTION// /}" == '' && "$l" =~ ^\.pragma[[:space:]]*=[[:space:]]*(dollarid)[[:space:]]*:[[:space:]]*(true|on)$ ]] && id=${BASH_REMATCH[2]} && continue #see how local variables are parsed
[[ "$l" =~ ^\[[[:space:]]*([${e}]+)[[:space:]]*\]$ ]] && SSL_CONF_SECTION=${BASH_REMATCH[1]} && continue #set section name
if [[ "$l" =~ ^([${e},\;.]+)[[:space:]]*=[[:space:]]*(.+)$ ]]; then #name value pair
local n="${BASH_REMATCH[1]}" v="${BASH_REMATCH[2]}"
[[ "$v" =~ [^\\]\\$ ]] && o="$n" #found a multiline value
SSL_CONF[${SSL_CONF_SECTION// /},${n}]="${v//\\[^nbrt\$\\\#]/}" && continue #add name value to SSL_CONF array
fi
done< <(cat $c 2>/dev/null) #loop through the config(s)
[ "$d" != '' ] && return #don't parse variables in included files, just return the section name
[ "$id" != '' ] && a='\{' #ignore not bracketed variables
local f="[^\\\\]*(\\\$(\(|\{|${a})([${e}]+)(::([${e}]+))?(\)|\}|))" # match $var ${var} $(var) unless preceded by \
for k in "${!SSL_CONF[@]}"; do #loop through our array looking for variables
local o #last value placeholder
while [ "${SSL_CONF[$k]}" != "$o" ]; do #only loop if the variable changed
o="${SSL_CONF[$k]}" #set loop to exit on no change
if [[ "${SSL_CONF[$k]}" =~ $f ]] && \
[[ "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '' || "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '()' || "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '{}' ]] #brackets match
then #the value contains a variable
local r=' #' #replacement indicator (illegal value)
[[ "$r" == ' #' && ${SSL_CONF[${k%%,*},${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[${k%%,*},${BASH_REMATCH[3]}]}" #local variable
[[ "$r" == ' #' && ${SSL_CONF[default,${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[default,${BASH_REMATCH[3]}]}" #'default' variable
[[ "$r" == ' #' && ${SSL_CONF[,${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[,${BASH_REMATCH[3]}]}" #default variable
if [ "${BASH_REMATCH[5]}" != '' ]; then #variable is from another section, default, or ENV
[[ "$r" == ' #' && "${BASH_REMATCH[3]}" == "ENV" ]] && r="${!BASH_REMATCH[5]:-${SSL_CONF[,${BASH_REMATCH[5]}]}}" #environment variable
[[ "$r" == ' #' && ${SSL_CONF[${BASH_REMATCH[3]},${BASH_REMATCH[5]}]+isset} ]] && r="${SSL_CONF[${BASH_REMATCH[3]},${BASH_REMATCH[5]}]}" #section variable
fi
[ "$r" != ' #' ] && SSL_CONF[$k]="${SSL_CONF[$k]//${BASH_REMATCH[1]}/$r}" #replace our variable with the value
fi
done
done
}
Uso:ssl_include <file/dir>
Sus dos valores deseados están en ${SSL_CONF[ca_one,certs]}
y ${SSL_CONF[ca_two,certs]}
(el script de ejemplo refleja estos valores).
Notas menores:
bash -version
5.03.3(1),openssl version
OpenSSL 1.1.1gwc -l openssl.cnf
824,grep -c '[^\\]=.*[^\\]\$' openssl.cnf
167,time ./ssl_include.sh openssl.cnf
0m0.854s- Si
$var
no se asigna en el momento en que se hace referencia,openssl
fallará.El guión no - Todas las opciones de configuración establecidas antes de cualquier sección se configuran como
SSL_CONF[,name]
. Esto evita colisiones con[default]
.[default]
está chequeadoprimero. - cualquier secuencia de escape no válidadeberíaser eliminadoadecuadamente, perohay un error de openssl (ver "Errores" en
man config
)eso me impide verificar - No puedo empezar
.pragma
a trabajaropenssl
, por lo que no puedo verificar mi implementación.¿Alguien puede hacerme una.pragma
configuración que funcione para poder eliminar esta nota? - aún no probado en
libressl
, etc. openssl
empalma un.import
en el archivo donde fue declarado. Si tu importación tiene un[section]
definido, estás en ese[section]
camino hacia adelante.Es responsable de muchos errores confusos.