從 openssl 設定檔讀取配置

從 openssl 設定檔讀取配置

我正在努力尋找在 shell 腳本中讀取 openssl 配置值的提示或解決方案。讓我為您提供更多細節。

我有一個openssl.conf文件包含以下簡化內容:

[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

我正在編寫 bash 腳本,它將查看每個證書頒發機構的 dir/certs 目錄,即我需要獲取值/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配置手動的(https://www.openssl.org/docs/manmaster/man5/config.html),這表示配置庫“可用於讀取設定檔”。據我了解,該庫由 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-Z、、0-9組成_
  • name可以由a-zA-Z0-9_;.和組成,
  • value以單一結尾(\反斜線)繼續下一行
  • 有些序列需要轉義,\如下:\\, \\$, \#, \n, \b, \r,\t

現在你有了一個像 這樣的關聯數組SSL_CONF[section,name]=value.include=(.*)一旦遇到文件,就會遞歸地解析它們。.pragma=dollarid:true也會進行處理,以便您可以準確地解析變數。

現在你還有最後一個問題:配置變數。它們目前被指定為${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>

您需要的兩個值位於 和${SSL_CONF[ca_one,certs]}${SSL_CONF[ca_two,certs]}範例腳本回顯這些值)。

小注意事項:

  • bash -version5.03.3(1)、openssl versionOpenSSL 1.1.1g
  • wc -l openssl.cnf824、167 grep -c '[^\\]=.*[^\\]\$' openssl.cnftime ./ssl_include.sh openssl.cnf 0米0.854秒
  • 如果$var在引用時未分配,openssl則會失敗。腳本沒有
  • 任何部分之前設定的所有配置選項都設定為SSL_CONF[,name].這可以避免與 發生衝突[default][default]已檢查第一的
  • 任何無效的轉義序列應該被刪除適當地, 但有一個 openssl 錯誤(請參閱 中的“錯誤” man config這阻止我驗證
  • 無法.pragma使用openssl,因此我無法驗證我的實作。有人可以給我一個工作.pragma配置,以便我可以刪除此註釋嗎?
  • 尚未測試libressl
  • openssl將 an 拼接.import到聲明它的檔案中。如果您的匯入已被[section]定義,那麼您就正在[section]向前邁進。它造成了許多令人困惑的錯誤

相關內容