openssl 設定ファイルから設定を読み込む

openssl 設定ファイルから設定を読み込む

シェル スクリプトで openssl 構成値を読み取るためのヒントや解決策を見つけるのに苦労しています。もう少し詳細を説明しましょう。

を持っていますオープンSSL次の簡略化された内容のファイル:

[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

私は、すべての認証局のdir/certsディレクトリを調べるbashスクリプトを書いています。つまり、値を取得する必要があります。/home/auhorities/ca_one/certsそして/home/auhorities/ca_two/certsファイルを作成します。今のところ、次の解決策に落ち着きました。

#!/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

しかし、将来スクリプトの機能が更新された場合、これは理想的ではないと思います。私は、将来の私や同僚が認証局のエントリをより便利な方法でナビゲートできるようにするソリューションを探しています。たとえば、opensslを見つけました。設定マニュアル(詳しくは、manmaster の man5.config を参照してください。)によると、設定ライブラリは「設定ファイルの読み取りに使用できます」。私の理解では、このライブラリは次のようなopensslツールによって内部的に使用されます。要求またはその他。openssl ユーティリティのいくつかを使用して、構成ファイルからエントリを読み取ることは可能ですか? おそらく何かが欠けているのでしょうが、それが可能かどうかはわかりません。これがオプションでない場合、このような状況にはどのように対処しますか?

よろしくお願いします。

答え1

openssl.cnf を解析または検証するための公式のコマンドライン ツールはありません。bashただし、互換性のある関数を構築しました。マニュアル設定確かに何度も読み返す必要があるものです。

重要: 設定が壊れていると、これは機能しません。修正してください。

openssl.cnfテストのために、この例は少し汚くする必要があります。

#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

次に、設定ファイルをサニタイズします。コメント、空行、末尾/先頭の空白、[ section ]ラベルとname = valueペア内の空白を削除します。

結果のname=valueペアをSSL_CONF[section,name]=value次のように割り当てます。

#!/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)
}

従うべき論理は次のとおりです。

  • コメント、空行、先頭/末尾の空白は無視されます
  • [ section_name ]name = value内部の空白は無視されます
  • .include.pragma必要としません=。これは下位互換性のために許可されました
  • .includeファイル内のどこにでも置くことができます
  • .include /dir/*.cnf定義されたディレクトリに含まれます*.conf.includeインクルードされたファイル内の処理を無効にします
  • section名前は、、、、および$varから構成されます。a-zA-Z0-9_
  • namea-z、、、、、、およびから構成されますA-Z0-9_;.,
  • value1つの(バックスラッシュ)で終わると次\の行に進みます
  • いくつかのシーケンスでは、次のようにエスケープする必要があり\ます: \\、、、、、、、\\$\#\n\b\r\t

これで、 のような連想配列ができましたSSL_CONF[section,name]=value.include=(.*)ファイルは、見つかるとすぐに再帰的に解析されます。.pragma=dollarid:trueも処理されるため、変数を正確に解析できます。

ここで最後の問題が 1 つあります。設定変数現在は${var}、、、、、、${section::var}として${ENV::var}割り当てられています$var$section::var$ENV::var そして$(var)、、 (誰が知っていたでしょう$(section::var)$(ENV::var)?)幸いなことに、配列をループしSSL_CONFて実際の値を割り当てることができます。

#!/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

これで、すべての変数が計算された値になりました。

完全な関数とサンプルコードは次のとおりです。

#!/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
}

使用法:ssl_include <file/dir>

必要な 2 つの値は${SSL_CONF[ca_one,certs]}とにあります${SSL_CONF[ca_two,certs]}(サンプル スクリプトはこれらの値をエコーし​​ます)。

マイナーノート:

  • bash -version5.03.3(1)、openssl versionOpenSSL1.1.1g
  • wc -l openssl.cnf824, grep -c '[^\\]=.*[^\\]\$' openssl.cnf167,time ./ssl_include.sh openssl.cnf 0分0.854秒
  • $var参照時までに割り当てられていない場合はopenssl失敗します。脚本は
  • セクションの前に設定されたすべての設定オプションは として設定されますSSL_CONF[,name]。これにより との衝突が回避されます[default][default]がチェックされます初め
  • 無効なエスケープシーケンスすべき削除されるきちんと、 しかしopenssl のバグがあります (「バグ」を参照man config)それは私が検証するのを妨げる
  • .pragmaを動作させることができないopensslため、実装を検証できません。誰か.pragmaこのメモを削除できるような実用的な設定を作ってくれませんか?
  • まだテストされていlibresslません
  • openssl.importは、宣言されたファイルに を接合します。インポートに が[section]定義されている場合は、その[section]インポートが先に進みません。多くの混乱を招くエラーの原因となる

関連情報