如果我在Mac或Linux中有实际的文件和Bash shell,我如何查询证书文件何时到期?不是网站,实际上是证书文件本身,假设我有csr、key、pem和chain文件。


当前回答

变成bash变量

由于这个问题被标记为bash,我经常使用UNIX EPOCH来存储日期,这对于计算$EPOCHSECONDS剩余的时间很有用,并通过printf '%(dateFmt)T bashism格式化输出:

{ read -r certStart;read -r certEnd;}< <(date -f <(cut -d = -f 2 <(
    openssl x509 -dates -noout -in "$file")) +%s)

Then

printf '%-6s %(%a %d %b %Y, %H %Z)T\n' start $certStart end $certEnd
start  Mon 01 Nov 2004, 17 UTC
end    Mon 01 Jan 2035, 05 UTC

示例,列出/etc/ssl/certs的内容并计算剩余天数:

for file in /etc/ssl/certs/*pem;do
    { read -r certStart;read -r certEnd;}< <(
        date -f <(cut -d = -f 2 <(
            openssl x509 -dates -noout -in "$file")) +%s)
    printf "%(%d %b %Y %T)T - %(%d %b %Y %T)T: %6d %s\n" \
        $certStart $certEnd $(( (certEnd - EPOCHSECONDS)/86400 )) ${file##*/}
done
05 May 2011 09:37:37 - 31 Dec 2030 09:37:37:   3034 ACCVRAIZ1.pem
26 Oct 2010 08:38:03 - 26 Oct 2040 08:38:03:   6620 Buypass_Class_2_Root_CA.pem
19 Jan 2010 00:00:00 - 18 Jan 2038 23:59:59:   5609 COMODO_RSA_Certification_Authority.pem
13 Nov 2012 00:00:00 - 19 Jan 2038 03:14:07:   5609 GlobalSign_ECC_Root_CA_-_R4.pem
06 Apr 2001 07:29:40 - 06 Apr 2021 07:29:40:   -522 Sonera_Class_2_Root_CA.pem
29 Jun 2004 17:39:16 - 29 Jun 2034 17:39:16:   4310 Starfield_Class_2_CA.pem
04 Feb 2016 12:32:16 - 31 Dec 2029 17:23:16:   2669 TrustCor_RootCert_CA-1.pem
01 Nov 2004 17:14:04 - 01 Jan 2035 05:37:19:   4495 XRamp_Global_CA_Root.pem
...

更完整的bash x509阅读:

for file in /etc/ssl/certs/*pem;do
    mapfile -t x509 < <(openssl x509 -noout -dates -subject -in "$file")
    x509=("${x509[@]#*=}")
    mapfile -t dates < <(IFS=$'\n';date -f - <<<"${x509[*]::2}" +%s) 
    str="${x509[-1]}"
    declare -A Subj='([CN]="${file##*/}")'
    while [[ "$str" ]] ;do
        lhs=${str%%=*} rhs=${str#$lhs= } rhs=${rhs%% = *} rhs=${rhs%, *}
        Subj[${lhs// }]="$rhs"
        str=${str#"$lhs= $rhs"} str=${str#, }
    done
    printf "%(%d %b %Y %T)T - %(%d %b %Y %T)T: %s\n" \
        ${dates[@]} "${Subj[CN]}"
done
05 May 2011 09:37:37 - 31 Dec 2030 09:37:37:   3034 ACCVRAIZ1
26 Oct 2010 08:38:03 - 26 Oct 2040 08:38:03:   6620 Buypass Class 2 Root CA
19 Jan 2010 00:00:00 - 18 Jan 2038 23:59:59:   5609 COMODO RSA Certification Authority
13 Nov 2012 00:00:00 - 19 Jan 2038 03:14:07:   5609 GlobalSign
06 Apr 2001 07:29:40 - 06 Apr 2021 07:29:40:   -522 Sonera Class2 CA
29 Jun 2004 17:39:16 - 29 Jun 2034 17:39:16:   4310 Starfield_Class_2_CA.pem
04 Feb 2016 12:32:16 - 31 Dec 2029 17:23:16:   2669 TrustCor RootCert CA-1
01 Nov 2004 17:14:04 - 01 Jan 2035 05:37:19:   4495 XRamp Global Certification Authority
...

注意:有些证书在主题中没有CN字段。对于这个,我已经初始化$Subj数组通过设置CN字段filename: declare -A Subj='([CN]="${file##*/}")'

完整bash脚本

这里共享一个完整的bash脚本,显示所有来自命令行参数的证书,可以通过文件,域名或IPv4地址。将在一行中输出过去的天数,剩余天数,替代域的数量和所有alt:

#!/bin/bash

showCert() {
    local x509 dates lhs rhs str alts
    mapfile -t x509 < <(
        openssl x509 -noout -dates -subject -ext subjectAltName -in "$1")
    x509=("${x509[@]#*=}")
    mapfile -t dates < <(IFS=$'\n';date -f - <<<"${x509[*]::2}" +%s)
    str="${x509[2]}"
    local -A Subj;Subj[CN]="${file##*/}"
    while [[ -n "$str" ]]; do
        lhs=${str%%=*} rhs=${str#$lhs= } rhs=${rhs%% = *} rhs=${rhs%, *}
        Subj[${lhs// }]="$rhs"
        str=${str#"$lhs= $rhs"} str=${str#, }
    done
    read -ra alts <<<"${x509[4]//,}"
    alts=("${alts[@]#*:}")
    printf "  %(%d %b %Y %H:%M)T %(%d %b %Y %H:%M)T %6d %6d %-30s %3d %s\n" \
        "${dates[@]}" $(((dates[1]-EPOCHSECONDS)/86400)) $(((EPOCHSECONDS-
          dates[0])/86400)) "${Subj[CN]}" "${#alts[@]}" "${alts[*]}" 
}
checkIsIpv4() { # throw an error if not valid IPv4
    local _iPointer _i _a _vareq=()
    for _i ;do
        case $_i in *[^0-9.]* ) return 1 ;; esac
        read -ra _a <<<"${_i//./ }"
        [ ${#_a[@]} -eq 4 ] || return 1
        for _iPointer in "${_a[@]}" ;do
            (( _iPointer == ( _iPointer & 255 ) ))  || return 2
        done
    done
}
checkIsLabel() {
    ((${#1}<4 || ${#1}>253)) && return 1
    [[ -z ${1//[a-zA-Z0-9.-]} ]] || return 2
    [[ -z ${1//.} ]] && return 3
    set -- ${1//./ }
    (($#<2 )) && return 4
    :
}
printf '  %-17s %-17s %6s %6s %-30s %2s\n' Not\ before Not\ after left \
       past Common\ Name Alt 

for arg ;do
    if [ -f "$arg" ] ;then
        showCert "$arg"
    elif checkIsLabel "$arg" || checkIsIpv4 "$arg" ;then
        showCert <(openssl s_client -ign_eof -connect "$arg:443" \
                           <<<$'HEAD / HTTP/1.0\r\n\r' 2> /dev/null)
    else
        echo "Unknown argument: '$arg'."
    fi
done

使用示例:

./certShow.sh /etc/ssl/certs/ssl-cert-snakeoil.pem www.example.com
  Not before        Not after           left   past Common Name                    Alt
  08 Sep 2021 16:49 06 Sep 2031 16:49   3277    372 hostname.local                   1 hostname.local
  14 Mar 2022 00:00 14 Mar 2023 23:59    179    186 www.example.org                  8 www.example.org example.net example.edu example.com example.org www.example.com www.example.edu www.example.net

其他回答

openssl:

openssl x509 -enddate -noout -in file.pem

输出在表单上:

notAfter=Nov  3 22:23:50 2014 GMT

还可以参阅MikeW的回答,了解如何轻松地检查证书是否过期,或者是否会在某个时间段内过期,而不必解析上面的日期。

如果你只想知道证书是否已经过期(或者将在接下来的N秒内过期),openssl x509的-checkend <seconds>选项将告诉你:

if openssl x509 -checkend 86400 -noout -in file.pem
then
  echo "Certificate is good for another day!"
else
  echo "Certificate has expired or will do so within 24 hours!"
  echo "(or is invalid/not found)"
fi

这样你就不必自己进行日期/时间比较了。

在上面的例子中,如果证书没有过期,Openssl将返回0(零)的退出码,并且在接下来的86400秒内不会这样做。如果证书已经过期或已经过期——或者出现其他错误,如文件无效/不存在——则返回代码为1。

(当然,它假设时间/日期设置正确)

请注意,旧版本的openssl有一个错误,这意味着如果在checkend中指定的时间太大,将总是返回0 (https://github.com/openssl/openssl/issues/6180)。

这里有一个bash函数,它检查所有服务器,假设您使用的是DNS循环。注意,这需要GNU日期,不能在Mac OS上工作

function check_certs () {
  if [ -z "$1" ]
  then
    echo "domain name missing"
    exit 1
  fi
  name="$1"
  shift

  now_epoch=$( date +%s )

  dig +noall +answer $name | while read _ _ _ _ ip;
  do
    echo -n "$ip:"
    expiry_date=$( echo | openssl s_client -showcerts -servername $name -connect $ip:443 2>/dev/null | openssl x509 -inform pem -noout -enddate | cut -d "=" -f 2 )
    echo -n " $expiry_date";
    expiry_epoch=$( date -d "$expiry_date" +%s )
    expiry_days="$(( ($expiry_epoch - $now_epoch) / (3600 * 24) ))"
    echo "    $expiry_days days"
  done
}

输出的例子:

$ check_certs stackoverflow.com
151.101.1.69: Aug 14 12:00:00 2019 GMT    603 days
151.101.65.69: Aug 14 12:00:00 2019 GMT    603 days
151.101.129.69: Aug 14 12:00:00 2019 GMT    603 days
151.101.193.69: Aug 14 12:00:00 2019 GMT    603 days

命令:

# cat {key_name} | openssl x509 -noout -enddate
Example: # cat tower.cert | openssl x509 -noout -enddate

结果:

notAfter=Dec  7 04:03:32 2023 GMT

一行检查true/false,如果域名证书将在一段时间后过期(例如。15天):

openssl x509 -checkend $(( 24*3600*15 )) -noout -in <(openssl s_client -showcerts -connect my.domain.com:443 </dev/null 2>/dev/null | openssl x509 -outform PEM)
if [ $? -eq 0 ]; then
  echo 'good'
else
  echo 'bad'
fi