Как отобразить альтернативное имя субъекта сертификата?

Самый близкий ответ, который я нашел, использует "grep".

> openssl x509 -text -noout -in cert.pem | grep DNS

Есть ли лучший способ сделать это? Я предпочитаю только командную строку.

Благодарю.

13 ответов

Решение

Обратите внимание, что вы можете ограничить вывод -text только расширения, добавив следующую опцию:

-certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux

то есть:

openssl x509 -text -noout -in cert.pem \
  -certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux

Тем не менее, вам все равно придется применить логику разбора текста, чтобы получить только Subject Alternative Name,

Если этого недостаточно, я думаю, вам нужно написать небольшую программу, которая использует библиотеку openssl для извлечения конкретного поля, которое вы ищете. Вот несколько примеров программ, которые показывают, как анализировать сертификат, включая извлечение полей расширения, таких как Subject Alternative Name:

https://zakird.com/2013/10/13/certificate-parsing-with-openssl

Обратите внимание, что вам не нужно использовать openssl и C, если вы идете по пути программирования... вы можете выбрать свой любимый язык и ASN.1 парсер библиотеки, и используйте это. Например, в Java вы можете использовать http://jac-asn1.sourceforge.net/ и многие другие.

Более новые версии openssl имеют опцию -ext, которая позволяет вам печатать только запись subjectAltName. Я использую OpenSSL 1.1.1b в Debian 9.9

openssl x509 -noout -ext subjectAltName -in cert.pem

Хотя вам все равно нужно разобрать вывод.

Изменение было сделано в https://github.com/openssl/openssl/issues/3932

Взято с /questions/16317151/kak-proverit-alternativnyie-imena-subekta-dlya-sertifikata-ssltls/16317165#16317165

$ true | openssl s_client -connect example.com:443 | openssl x509 -noout -text | grep DNS:

пример

$ true | openssl s_client -connect localhost:8443 | openssl x509 -noout -text | grep DNS:
depth=2 C = US, ST = NC, L = SomeCity, O = SomeCompany Security, OU = SomeOU, CN = SomeCN
verify error:num=19:self signed certificate in certificate chain
DONE
                DNS:localhost, DNS:127.0.0.1, DNS:servername1.somedom.com, DNS:servername2.somedom.local

Вот мое решение (с использованием openssl и sed):

первый удар

sed -ne '
    s/^\( *\)Subject:/\1/p;
    /X509v3 Subject Alternative Name/{
        N;
        s/^.*\n//;
      :a;
        s/^\( *\)\(.*\), /\1\2\n\1/;
        ta;
        p;
        q;
    }' < <(openssl x509 -in cert.pem -noout -text)

может быть написано:

sed -ne 's/^\( *\)Subject:/\1/p;/X509v3 Subject Alternative Name/{
    N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; }' < <(
    openssl x509 -in cert.pem -noout -text )

и может сделать что-то вроде:

         CN=www.example.com
                DNS:il0001.sample.com
                DNS:example.com
                DNS:demodomain.com
                DNS:testsite.com
                DNS:www.il0001.sample.com
                DNS:www.il0001.sample.com.vsite.il0001.sample.com
                DNS:www.example.com
                DNS:www.example.com.vsite.il0001.sample.com
                DNS:www.demodomain.com
                DNS:www.demodomain.com.vsite.il0001.sample.com
                DNS:www.testsite.com
                DNS:www.testsite.com.vsite.il0001.sample.com

То же самое для живого сервера

sed -ne 's/^\( *\)Subject:/\1/p;/X509v3 Subject Alternative Name/{
    N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; }' < <(
    openssl x509 -noout -text -in <(
        openssl s_client -ign_eof 2>/dev/null <<<$'HEAD / HTTP/1.0\r\n\r' \
            -connect google.com:443 ) )

Может выводить:

         C=US, ST=California, L=Mountain View, O=Google Inc, CN=*.google.com
                DNS:*.google.com
                DNS:*.android.com
                DNS:*.appengine.google.com
                DNS:*.cloud.google.com
                DNS:*.gcp.gvt2.com
                DNS:*.google-analytics.com
                DNS:*.google.ca
                DNS:*.google.cl
                DNS:*.google.co.in
                DNS:*.google.co.jp
                DNS:*.google.co.uk
                DNS:*.google.com.ar
                DNS:*.google.com.au
                DNS:*.google.com.br
                DNS:*.google.com.co
                DNS:*.google.com.mx
                DNS:*.google.com.tr
                DNS:*.google.com.vn
                DNS:*.google.de
                DNS:*.google.es
                DNS:*.google.fr
                DNS:*.google.hu
                DNS:*.google.it
                DNS:*.google.nl
                DNS:*.google.pl
                DNS:*.google.pt
                DNS:*.googleadapis.com
                DNS:*.googleapis.cn
                DNS:*.googlecommerce.com
                DNS:*.googlevideo.com
                DNS:*.gstatic.cn
                DNS:*.gstatic.com
                DNS:*.gvt1.com
                DNS:*.gvt2.com
                DNS:*.metric.gstatic.com
                DNS:*.urchin.com
                DNS:*.url.google.com
                DNS:*.youtube-nocookie.com
                DNS:*.youtube.com
                DNS:*.youtubeeducation.com
                DNS:*.ytimg.com
                DNS:android.clients.google.com
                DNS:android.com
                DNS:developer.android.google.cn
                DNS:g.co
                DNS:goo.gl
                DNS:google-analytics.com
                DNS:google.com
                DNS:googlecommerce.com
                DNS:urchin.com
                DNS:www.goo.gl
                DNS:youtu.be
                DNS:youtube.com
                DNS:youtubeeducation.com

Оболочка POSIX сейчас

Как < <(...) это башизм, нужно написать ту же команду:

openssl x509 -in cert.pem -noout -text | sed -ne '
  s/^\( *\)Subject:/\1/p;
  /X509v3 Subject Alternative Name/{
      N;
      s/^.*\n//;
    :a;
      s/^\( *\)\(.*\), /\1\2\n\1/;
      ta;
      p;
      q;
  }'

а также

printf 'HEAD / HTTP/1.0\r\n\r\n' |
    openssl s_client -ign_eof 2>/dev/null -connect google.com:443 |
    openssl x509 -noout -text |
    sed -ne 's/^\( *\)Subject:/\1/p;/X509v3 Subject Alternative Name/{
        N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; }'

Очень простое решение с использованием grep

openssl x509 -in /path/to/x509/cert -noout -text|grep -oP '(?<=DNS:|IP Address:)[^,]+'|sort -uV

Для сертификата Google это выводит:

android.clients.google.com
android.com
developer.android.google.cn
g.co
goo.gl
google.com
googlecommerce.com
google-analytics.com
hin.com
urchin.com
www.goo.gl
youtu.be
youtube.com
youtubeeducation.com
*.android.com
*.appengine.google.com
*.cloud.google.com
*.gcp.gvt2.com
*.googleadapis.com
*.googleapis.cn
*.googlecommerce.com
*.googlevideo.com
*.google.ca
*.google.cl
*.google.com
*.google.com.ar
*.google.com.au
*.google.com.br
*.google.com.co
*.google.com.mx
*.google.com.tr
*.google.com.vn
*.google.co.in
*.google.co.jp
*.google.co.uk
*.google.de
*.google.es
*.google.fr
*.google.hu
*.google.it
*.google.nl
*.google.pl
*.google.pt
*.gstatic.cn
*.gstatic.com
*.gvt1.com
*.gvt2.com
*.metric.gstatic.com
*.urchin.com
*.url.google.com
*.youtubeeducation.com
*.youtube.com
*.ytimg.com
*.google-analytics.com
*.youtube-nocookie.com

Как отобразить альтернативное имя субъекта сертификата?

В сертификате X509 может быть несколько SAN. Ниже приводится вики OpenSSL на клиенте SSL / TLS. Он перебирает имена и печатает их.

Вы получаете X509* из функции как SSL_get_peer_certificate из соединения TLS, d2i_X509 по памяти или PEM_read_bio_X509 из файловой системы.

void print_san_name(const char* label, X509* const cert)
{
    int success = 0;
    GENERAL_NAMES* names = NULL;
    unsigned char* utf8 = NULL;

    do
    {
        if(!cert) break; /* failed */

        names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0 );
        if(!names) break;

        int i = 0, count = sk_GENERAL_NAME_num(names);
        if(!count) break; /* failed */

        for( i = 0; i < count; ++i )
        {
            GENERAL_NAME* entry = sk_GENERAL_NAME_value(names, i);
            if(!entry) continue;

            if(GEN_DNS == entry->type)
            {
                int len1 = 0, len2 = -1;

                len1 = ASN1_STRING_to_UTF8(&utf8, entry->d.dNSName);
                if(utf8) {
                    len2 = (int)strlen((const char*)utf8);
                }

                if(len1 != len2) {
                    fprintf(stderr, "  Strlen and ASN1_STRING size do not match (embedded null?): %d vs %d\n", len2, len1);
                }

                /* If there's a problem with string lengths, then     */
                /* we skip the candidate and move on to the next.     */
                /* Another policy would be to fails since it probably */
                /* indicates the client is under attack.              */
                if(utf8 && len1 && len2 && (len1 == len2)) {
                    fprintf(stdout, "  %s: %s\n", label, utf8);
                    success = 1;
                }

                if(utf8) {
                    OPENSSL_free(utf8), utf8 = NULL;
                }
            }
            else
            {
                fprintf(stderr, "  Unknown GENERAL_NAME type: %d\n", entry->type);
            }
        }

    } while (0);

    if(names)
        GENERAL_NAMES_free(names);

    if(utf8)
        OPENSSL_free(utf8);

    if(!success)
        fprintf(stdout, "  %s: <not available>\n", label);

}

Используйте openssl _-extвариант, см . здесьopenssl x509параметры команды и здесь доступны-extensionsфлаги, которые вы можете использовать.

Пример вывода:

      $ openssl x509 -noout -ext subjectAltName -in /etc/core/.pki/kong.pem 
X509v3 Subject Alternative Name: 
        DNS:localhost, DNS:dr.dev.local, DNS:pnpserver.dev.local, DNS:kong, DNS:kong.core-system, DNS:kong.core-system.svc, DNS:kong.core-system.svc.cluster, DNS:kong.core-system.svc.cluster.local, DNS:kong-frontend, DNS:kong-frontend.core-system, DNS:kong-frontend.core-system.svc, DNS:kong-frontend.core-system.svc.cluster, DNS:kong-frontend.core-system.svc.cluster.local, IP Address:196.196.196.101, IP Address:10.23.214.43, IP Address:192.168.101.99, IP Address:192.168.101.100, IP Address:196.196.196.100, IP Address:10.23.214.44

дд

Ты можешь использовать awk чтобы приблизиться к SAN, добавив в awk заявление:

openssl x509 -in mycertfile.crt -text -noout \
  -certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_subject,no_issuer,no_pubkey,no_sigdump,no_aux \
 | awk '/X509v3 Subject Alternative Name/','/X509v3 Basic Constraints/'

Вот как мы можем это сделать с помощью awk.

'/Subject: C=/{printf $NF"\n"} соответствует любой строке с шаблоном /Subject: C= а также {printf $NF"\n"} просто печатает последнее поле с новой строкой.

/DNS:/{x=gsub(/ *DNS:/, ""); printf "SANS=" $0"\n"} соответствует строке с шаблоном DNS:. gsub используется для замены ненужных DNS: перед каждым fqdn. printf "SANS="$0"\n" печатает всю строку с новой строкой.

➤ echo | openssl s_client -connect google.com:443 2>&1 | openssl x509 -noout -text |  awk '/Subject: C=/{printf $NF"\n"} /DNS:/{x=gsub(/ *DNS:/, ""); printf "SANS=" $0"\n"}'
CN=*.google.com
SANS=*.google.com,*.android.com,*.appengine.google.com,*.cloud.google.com,*.crowdsource.google.com,*.g.co,*.gcp.gvt2.com,*.gcpcdn.gvt1.com,*.ggpht.cn,*.gkecnapps.cn,*.google-analytics.com,*.google.ca,*.google.cl,*.google.co.in,*.google.co.jp,*.google.co.uk,*.google.com.ar,*.google.com.au,*.google.com.br,*.google.com.co,*.google.com.mx,*.google.com.tr,*.google.com.vn,*.google.de,*.google.es,*.google.fr,*.google.hu,*.google.it,*.google.nl,*.google.pl,*.google.pt,*.googleadapis.com,*.googleapis.cn,*.googlecnapps.cn,*.googlecommerce.com,*.googlevideo.com,*.gstatic.cn,*.gstatic.com,*.gstaticcnapps.cn,*.gvt1.com,*.gvt2.com,*.metric.gstatic.com,*.urchin.com,*.url.google.com,*.wear.gkecnapps.cn,*.youtube-nocookie.com,*.youtube.com,*.youtubeeducation.com,*.youtubekids.com,*.yt.be,*.ytimg.com,android.clients.google.com,android.com,developer.android.google.cn,developers.android.google.cn,g.co,ggpht.cn,gkecnapps.cn,goo.gl,google-analytics.com,google.com,googlecnapps.cn,googlecommerce.com,source.android.google.cn,urchin.com,www.goo.gl,youtu.be,youtube.com,youtubeeducation.com,youtubekids.com,yt.be

➤

Разобрано в массив

Еще один вариант, если кто-то предпочитает. Это позволит захватить любое количество альтернативных имен, «поместив» каждое в массив Bash. Ниже кода синтаксического анализа я объясню, как получить любую отдельную запись, все записи, количество записей и объясню работу команды по каналам.

Предположим, у нас есть сертификат, который содержит:

       [...]
X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Subject Alternative Name:
                IP Address:127.0.0.1, IP Address:10.0.2.2, DNS:localhost
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
[...]

IP Address:127.0.0.1, IP Address:10.0.2.2, DNS:localhost, конечно, являясь нашей целью)

Разобрать код

      $ ALT_NAMES=($(openssl x509 -in cert.pem -noout -text | grep -A 1 "Subject Alternative Name" | tail -1 | sed -E "s/ //g" | tr "," "\n" | sed -E "s/^.*:(\S*)/\1/g" | tr "\n" " "))

$ # Let's see what we got
$ echo "${ALT_NAMES[*]}"
  127.0.0.1 10.0.2.2 localhost

$ # Hey, great! How about just the second one?
$ echo "${ALT_NAMES[1]}"     # (Zero-indexed array)
  10.0.2.2

$ # Okay... total count?
$ echo "${#ALT_NAMES[*]}"
  3

Как дела еще раз?

      # Defining an array ( VAR=(<space separated values>) )...
ALT_NAMES=([...]

   # ... expand the following expression into the contents of said array...
         $(

   # ... the OpenSSL command we're using to read the cert...
         openssl x509 -in cert.pem -noout -text | 

   # ... grep for the Alt Names. The "-A 1" (read: -A(fter) 1) gets the next line after our match, too...
         grep -A 1 "Subject Alternative Name" | 
#result: "            X509v3 Subject Alternative Name:
                         IP Address:127.0.0.1, IP Address:10.0.2.2, DNS:localhost"

   # ... (that extra line being the only one we actually wanted anyway)...
         tail -1 | 
#result: "               IP Address:127.0.0.1, IP Address:10.0.2.2, DNS:localhost"

   # ... strip all whitespace...
         sed -E "s/ //g" | 
#result: "IPAddress:127.0.0.1,IPAddress:10.0.2.2,DNS:localhost"

   # ... Text Replace the commas with new lines...
         tr "," "\n" | 
#result: "IPAddress:127.0.0.1
          IPAddress:10.0.2.2
          DNS:localhost"

   # ... so we can discard all the text from start of each line to ":"...
         sed -E "s/^.*:(\S*)/\1/g" | 
#result: "127.0.0.1
          10.0.2.2
          localhost"

   # ... then knock them all back to a single, space-delimited line...
         tr "\n" " "
#result: "127.0.0.1 10.0.2.2 localhost"

   # ... before terminating our expansion...
         )
#result: "127.0.0.1 10.0.2.2 localhost"

# ... and finally conclude by ending the array declaration.
)
#result: "ALT_NAMES=(127.0.0.1 10.0.2.2 localhost)"

TLDR

      $ ALT_NAMES=($(openssl x509 -in cert.pem -noout -text | grep -A 1 "Subject Alternative Name" | tail -1 | sed -E "s/ //g" | tr "," "\n" | sed -E "s/^.*:(\S*)/\1/g" | tr "\n" " "))
$ printf "Number of entries: ${#ALT_NAMES[*]}\nAll entry values: ${ALT_NAMES[*]}\nSingle entry: ${ALT_NAMES[1]}"

# Outputs:
    Number of entries: 3
    All entry values: 127.0.0.1 10.0.2.2 localhost
    Single entry: 10.0.2.2

Я знаю, что уже слишком поздно для вечеринки... но, возможно, когда-нибудь это поможет кому-то с похожей потребностью. <3

(Редактировать: Эй, ооочень... Я не гуру Bash... Я просто часто им пользуюсь. Если у кого-то есть лучший способ выполнить это или оптимизировать/улучшить его, пожалуйста: напишите мне комментарий ниже? Мне нравится освоить новые техники!)

Улучшенное решение на основе awk (hat-tip: @RandomW):

openssl x509 -in certfile -text -noout \
  -certopt no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux \
| awk '/X509v3 Subject Alternative Name:/ {san=1;next} 
      san && /^ *X509v3/ {exit} 
      san { sub(/DNS:/,"",$1);print $1}'

Это распечатывает список, как и grep а также sed решения также найдены здесь. Разница в том, что существует более жесткий контроль над тем, где находится информация. Если выходной формат когда-либо изменится, эта версия будет более надежной и лучше передаст изменения. Печатается только текст между "Альтернативным именем субъекта" и самым следующим разделом "X509v3", а весь необязательный предшествующий текст "DNS:" удаляется.

android.clients.google.com
android.com
developer.android.google.cn
g.co
goo.gl
...

Добавление альтернативы Python. Обязательным условием является наличие строки с записями "DNS:".

# Fetch the certificate details (subprocess, OpenSSL module, etc)
# dnsstring contains the "DNS:" line of the "openssl" output
# Example of how to get the string of DNS names from a string output of the certificate
for idx, line in enumerate(certoutput.split()):
    if ' X509v3 Authority Key Identifier:' in line:
        dnsstring = certoutput.split()[idx + 1]

# Get a list
[x.replace('DNS:', '').replace(',', '') for x in dnsstring]

# Format to a comma separated string
', '.join([x.replace('DNS:', '').replace(',', '') for x in dnsstring])

Пример командной строки:

true | \
  openssl s_client -showcerts -connect google.com:443 2>/dev/null | \
  openssl x509 -noout -text 2>/dev/null | grep " DNS:" | \
  python -c"import sys; print ', '.join([x.replace('DNS:', '').replace(',', '') for x in sys.stdin.readlines()[0].split()])"

Выход:

*.google.com, *.android.com, <etc>

Может быть, этого достаточно

openssl x509 -in cert.pem -noout -text -certopt ca_default,no_sigdump
Другие вопросы по тегам