Estou lutando para encontrar dicas ou soluções para ler valores de configuração do openssl em um script de shell. Deixe-me fornecer mais detalhes.
eu tenho umopenssl.confarquivo com o seguinte conteúdo 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
Estou escrevendo um script bash, que examinará o diretório dir/certs para cada autoridade de certificação, ou seja, preciso obter valores/home/autoridades/ca_one/certse/home/autoridades/ca_two/certsformar o arquivo. Por enquanto, acabei com a seguinte solução:
#!/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
No entanto, temo que isso não seja o ideal se a funcionalidade do script for atualizada no futuro. Estou procurando uma solução que permita ao futuro eu ou colegas navegar pelas entradas da autoridade de certificação de uma maneira mais conveniente. Por exemplo, encontrei o opensslconfiguraçãomanual(https://www.openssl.org/docs/manmaster/man5/config.html), que diz queconfiguraçãobiblioteca "pode ser usada para ler arquivos de configuração". Pelo que entendi, esta biblioteca é usada internamente por ferramentas openssl comoca, solicitaçãoou outro. É então possível ler as entradas do arquivo de configuração usando alguns dos utilitários openssl? Provavelmente estou faltando alguma coisa, mas simplesmente não consigo descobrir se é possível ou não. Se esta não for uma opção, como você lidaria com tal situação?
Agradeço antecipadamente.
Responder1
Não existe uma ferramenta oficial de linha de comando para analisar ou validar o openssl.cnf. No entanto, construí uma bash
função compatível.configuração do homemé certamente algo que você precisa ler e reler.
IMPORTANTE: Se sua configuração estiver quebrada, isso não funcionará. Consertá-lo!
O exemplo openssl.cnf
precisa ser um pouco mais sujo para fins de teste:
#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
Agora limpe o arquivo de configuração - remova comentários, linhas vazias, espaços em branco à direita/à esquerda e espaços em branco dentro de [ section ]
rótulos e name = value
pares.
Atribua quaisquer name=value
pares resultantes SSL_CONF[section,name]=value
da seguinte forma:
#!/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)
}
A lógica seguida foi:
- comentários, linhas em branco, espaços em branco iniciais/finais são ignorados
[ section_name ]
ename = value
o espaço em branco interno é ignorado.include
e.pragma
não requer um=
. Foi permitida compatibilidade com versões anteriores.include
pode estar em qualquer lugar do arquivo.include /dir/
inclui*.cnf
e*.conf
no diretório definido. Desativa.include
o processamento nos arquivos incluídossection
e$var
os nomes podem consistir ema-z
,A-Z
,0-9
e_
name
pode consistir ema-z
,A-Z
,0-9
,_
,;
,.
, e,
- um
value
final em uma única\
(barra invertida) continua para a próxima linha - algumas sequências exigem escape da
\
seguinte maneira:\\
,\\$
,\#
,\n
,\b
,\r
,\t
Agora você tem um array associativo como SSL_CONF[section,name]=value
. Os .include=(.*)
arquivos são analisados recursivamente assim que são encontrados. .pragma=dollarid:true
também é processado para que você possa analisar variáveis com precisão.
Agora você tem um último problema:VARIÁVEIS DE CONFIGURAÇÃO. Eles estão atualmente atribuídos como ${var}
, ${section::var}
, ${ENV::var}
, $var
, $section::var
,$ENV::var
E $(var)
, $(section::var)
, $(ENV::var)
(quem diria?). Felizmente podemos percorrer nosso SSL_CONF
array e atribuir os valores reais:
#!/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
E agora todas as suas variáveis são seus valores computados!
Aqui está a função completa e o código de exemplo:
#!/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>
Seus dois valores desejados estão em ${SSL_CONF[ca_one,certs]}
e ${SSL_CONF[ca_two,certs]}
(o script de exemplo ecoa esses 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- Se
$var
não for atribuído no momento em que for referenciado,openssl
falhará.O roteiro não - todas as opções de configuração definidas antes de qualquer seção serem definidas como
SSL_CONF[,name]
. Isso evita colisões com[default]
.[default]
está checadoprimeiro. - qualquer sequência de escape inválidadeveser excluídoapropriadamente, mashá um bug do openssl (veja "Bugs" em
man config
)isso me impede de verificar - não consigo
.pragma
trabalharopenssl
, então não posso verificar minha implementação.alguém pode me fazer uma.pragma
configuração funcional para que eu possa remover esta nota? - ainda não testado em
libressl
, etc. openssl
emenda um.import
no arquivo onde foi declarado. Se a sua importação tiver um[section]
definido, você está avançando[section]
.É responsável por muitos erros confusos