Как отобразить альтернативное имя субъекта сертификата?
Самый близкий ответ, который я нашел, использует "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
$ 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