#OpenSSL является библиотекой по работе с криптографией, но в этой статье я затрону только часть функций этого инструмента, а именно создание само-подписанных сертификатов.
При помощи корневого сертификата, мы будем подписывать сертификаты клиентские. Создаём сертификат центра сертификации…
1
| openssl ecparam -genkey -name 'secp384r1' | openssl ec -aes256 -out "_CA.key" && openssl req -new -sha384 -key "_CA.key" -out "_CA.csr" && openssl x509 -req -sha384 -days 3650 -key "_CA.key" -in "_CA.csr" -out "_CA.crt"
|
Где:
-days '3650'
- количество дней, по прошествии которого сертификат центра сертификации станет недействительным.'_CA.key'
- название создаваемого файла с ключом центра сертификации.'_CA.crt'
- название создаваемого файла с сертификатом центра сертификации.
Как только будет готов корневой сертификат, можно приступать к выпуску клиентских сертификатов.
Для начала создаём приватный ключ.
1
| openssl ecparam -genkey -name 'prime256v1' | openssl ec -out 'client.key'
|
Где:
-out 'client.key'
- название создаваемого файла с клиентским приватным ключом.
Выполняем запрос на сертификат.
1
| openssl req -new -key 'client.key' -out 'client.csr'
|
Где:
-key 'client.key'
- файл с приватным ключом клиентского сертификата.-out 'client.csr'
- название создаваемого файла с запросом на подпись клиентского сертификата.
При выполнении запроса будет предложено ввести актуальные данные для будущего сертификата:
Country Name (2 letter code)
- двухбуквенный код страны, в которой юридически находится ваша организация.State or Province Name (full name)
- штат или провинция, в которой юридически находится ваша организация.Locality Name (e.g., city)
- город, в котором юридически находится ваша организация.Organization Name (e.g., company)
- юридически зарегистрированное название вашей организации.Organizational Unit Name (e.g., section)
- название вашего отдела в организации (опционально).Common Name (e.g., server FQDN)
- полное доменное имя (FQDN) (например, www.example.com).Email Address
- ваш адрес email (опционально).A challenge password
- пароль (опционально).An optional company name
- необязательное название компании (опционально).
Самое главное поле это Common Name (e.g., server FQDN)
(CN
), заполнять его необходимо очень внимательно.
В заключительной части остаётся только создать сам сертификат и подписать его.
1
| openssl x509 -req -days 3650 -in 'client.csr' -CA '_CA.crt' -CAkey '_CA.key' -out 'client.crt'
|
Где:
-in 'client.csr'
- файл с запросом клиентского сертификата.-CA '_CA.crt'
- файл с сертификатом центра сертификации.-CAkey '_CA.key'
- файл с ключом центра сертификации.-days '3650'
- количество дней, по прошествии которого клиентский сертификат станет недействительным.-out 'client.crt'
- название создаваемого файла с клиентским сертификатом.
Для того, чтобы импортировать сертификат на клиентские устройства, его необходимо экспортировать в формат P12
. P12
является контейнером, в котором содержится приватный ключ сертификата и сам сертификат.
1
| openssl pkcs12 -export -inkey 'client.key' -in 'client.crt' -out 'client.p12'
|
Где:
-inkey 'client.key'
- файл с приватным клиентским ключом.-in 'client.crt'
- файл с клиентским сертификатом.-out 'client.p12'
- название создаваемого файла-контейнера с приватным ключом и сертификатом.
1
| openssl verify -CAfile '_CA.crt' 'client.crt'
|
-CAfile '_CA.crt'
- файл с сертификатом центра сертификации.'client.crt'
- файл с клиентским сертификатом.
1
| openssl x509 -in 'client.crt' -text
|
Где:
-in 'client.crt'
- файл с клиентским сертификатом.
При выполнении команды, терминал покажет информацию со всеми основными сведениями о сертификате (пример ниже).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| openssl x509 -in 'client.crt' -text
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
49:14:b8:78:17:a1:ca:c4:6a:41:1b:23:f3:8a:8d:36:e0:9e:69:81
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = CA
Validity
Not Before: Oct 18 21:31:53 2023 GMT
Not After : Oct 15 21:31:53 2033 GMT
Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = CN-FQDN, emailAddress = mail@example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:9f:22:91:cc:14:b3:75:ad:44:cc:0d:96:0d:19:
04:c4:20:79:fd:b7:f5:65:64:dd:65:a1:f0:b8:2d:
e4:02:75:62:41:03:7c:6e:2f:48:e4:11:38:dc:ed:
08:1f:4c:fa:5f:2f:a2:f5:5d:51:2e:8e:de:de:70:
33:aa:e5:3b:95
ASN1 OID: prime256v1
NIST CURVE: P-256
Signature Algorithm: ecdsa-with-SHA256
30:64:02:30:0d:ff:b1:dc:8b:a6:c6:9c:ad:65:8a:7c:01:41:
9f:91:ca:24:4c:0b:28:5a:5c:f6:35:2a:b2:d7:58:ca:39:da:
6c:bf:cf:8b:23:20:ce:11:45:13:61:36:e2:23:4a:e9:02:30:
54:dc:45:ed:ef:21:58:ea:c7:b6:63:db:a2:d9:71:fe:3d:b3:
d6:1e:15:82:7b:c8:e8:08:33:d5:2f:d5:f2:8f:3b:41:ea:53:
1e:2d:a9:1e:9e:25:9c:fb:a7:12:f9:ec
|
1
| openssl req -in 'client.csr' -text
|
Где:
-in 'client.csr'
- файл с запросом на подпись клиентского сертификата.
Команда выдаст информацию по запросу на подпись клиентского сертификата (пример ниже).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| openssl req -in 'client.csr' -text
Certificate Request:
Data:
Version: 1 (0x0)
Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = CN-FQDN, emailAddress = mail@example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:9f:22:91:cc:14:b3:75:ad:44:cc:0d:96:0d:19:
04:c4:20:79:fd:b7:f5:65:64:dd:65:a1:f0:b8:2d:
e4:02:75:62:41:03:7c:6e:2f:48:e4:11:38:dc:ed:
08:1f:4c:fa:5f:2f:a2:f5:5d:51:2e:8e:de:de:70:
33:aa:e5:3b:95
ASN1 OID: prime256v1
NIST CURVE: P-256
Attributes:
a0:00
Requested Extensions:
Signature Algorithm: ecdsa-with-SHA256
30:45:02:20:51:9c:a0:b9:b2:61:7f:fa:64:ea:34:1f:65:15:
37:ae:f4:89:30:47:11:89:db:c4:1b:1d:9b:82:b9:64:ea:c7:
02:21:00:f8:00:70:4e:e6:db:99:78:cf:25:22:c0:8d:c6:b1:
f3:f0:d8:68:3b:c2:51:21:a5:b3:fa:97:f9:85:c6:9c:49
|
Команд много. Поэтому я как обычно, решил всё автоматизировать и загнать в скрипт.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
| #!/usr/bin/env -S bash -e
#
# OpenSSL certificate generator with CA.
#
# @package Bash
# @author Kai Kimera <mail@kai.kim>
# @copyright 2023 Library Online
# @license MIT
# @version 0.1.2
# @link https://lib.onl/ru/2023/10/6733cb51-62a0-5ed9-b421-8f08c4e0cb18/
# -------------------------------------------------------------------------------------------------------------------- #
(( EUID == 0 )) && { echo >&2 'This script should not be run as root!'; exit 1; }
# -------------------------------------------------------------------------------------------------------------------- #
# CONFIGURATION.
# -------------------------------------------------------------------------------------------------------------------- #
# Get 'cat' command.
cat="$( command -v cat )"
# Get 'date' command.
date="$( command -v date )"
# Get 'mkdir' command.
mkdir="$( command -v mkdir )"
# Get 'openssl' command.
openssl="$( command -v openssl )"
# Get 'shuf' command.
shuf="$( command -v shuf )"
# CA file names.
ca='_CA'
# Specifies the number of days to make a certificate valid for.
# Default is 10 years.
days='3650'
# The two-letter country code where your company is legally located.
country='RU'
# The state/province where your company is legally located.
state='Russia'
# The city where your company is legally located.
city='Moscow'
# Your company's legally registered name (e.g., YourCompany, Inc.).
org='YourCompany'
# Timestamp.
ts="$( ${date} -u '+%s' )"
# Suffix.
sfx=$( ${shuf} -i '1000-9999' -n 1 --random-source='/dev/random' )
# -------------------------------------------------------------------------------------------------------------------- #
# -----------------------------------------------------< SCRIPT >----------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #
# CERTIFICATE AUTHORITY.
# -------------------------------------------------------------------------------------------------------------------- #
ca() {
! [[ -x "${openssl}" ]] && { echo >&2 "'openssl' is not installed!"; exit 1; }
local name="${1:-example.com}"
local email="${2:-mail@example.com}"
local v3ext_ca='_CA.v3ext'
cat > "${v3ext_ca}" <<EOF
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:TRUE
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
EOF
echo '' && echo "--- [SSL-CA] CREATING A CA CERTIFICATE" && echo ''
${openssl} ecparam -genkey -name 'secp384r1' | ${openssl} ec -aes256 -out "${ca}.key" \
&& ${openssl} req -new -sha384 -key "${ca}.key" -out "${ca}.csr" \
-subj "/C=${country}/ST=${state}/L=${city}/O=${org}/emailAddress=${email}/CN=${name}" \
&& ${openssl} x509 -req -extfile "${v3ext_ca}" -sha384 -days ${days} -key "${ca}.key" -in "${ca}.csr" -out "${ca}.crt"
_info "${ca}.crt"
}
# -------------------------------------------------------------------------------------------------------------------- #
# CLIENT CERTIFICATE.
# -------------------------------------------------------------------------------------------------------------------- #
cert() {
for i in "${mkdir}" "${openssl}" "${shuf}"; do
! [[ -x "${i}" ]] && { echo >&2 "'${i}' is not installed!"; exit 1; }
done
local name; name="${1:-example.com}"
local email; email="${2:-mail@example.com}"
local type; type="${3:-server}"; [[ "${name}" = *' '* ]] && type='client'
local dir; dir="${type}/${name// /_}"; ${mkdir} -p "${dir}"
local file; file="${dir}/${email}.${ts}.${sfx}"
local v3ext
if [[ "${type}" = 'client' ]]; then
v3ext=$( _v3ext_client "${file}.v3ext" )
else
v3ext=$( _v3ext_server "${file}.v3ext" "${name}" )
fi
echo '' && echo "--- [SSL] CREATING A ${type^^} CERTIFICATE" && echo ''
${openssl} ecparam -genkey -name 'prime256v1' | ${openssl} ec -out "${file}.key" \
&& ${openssl} req -new -key "${file}.key" -out "${file}.csr" \
-subj "/C=${country}/ST=${state}/L=${city}/O=${org}/emailAddress=${email}/CN=${name}" \
&& ${openssl} x509 -req -extfile "${v3ext}" -days ${days} -in "${file}.csr" \
-CA "${ca}.crt" -CAkey "${ca}.key" -CAcreateserial -CAserial "${file}.srl" -out "${file}.crt" \
&& ${cat} "${file}.key" "${file}.crt" "${ca}.crt" > "${file}.crt.chain"
_verify "${ca}.crt" "${file}.crt" && _info "${file}.crt" && _export "${file}.key" "${file}.crt" "${file}.p12"
}
_verify() {
echo '' && echo "--- [SSL] CERTIFICATE VERIFICATION" && echo ''
for i in "${1}" "${2}"; do [[ ! -f "${i}" ]] && { echo >&2 "'${i}' not found!"; exit 1; }; done
${openssl} verify -CAfile "${1}" "${2}"
}
_info() {
echo '' && echo "--- [SSL] CERTIFICATE DETAILS" && echo ''
[[ ! -f "${1}" ]] && { echo >&2 "'${i}' not found!"; exit 1; }
${openssl} x509 -in "${1}" -text -noout
}
_export() {
echo '' && echo "--- [SSL] EXPORTING A CERTIFICATE" && echo ''
for i in "${1}" "${2}"; do [[ ! -f "${i}" ]] && { echo >&2 "'${i}' not found!"; exit 1; }; done
${openssl} pkcs12 -export -inkey "${1}" -in "${2}" -out "${3}"
}
_v3ext_client() {
cat > "${1}" <<EOF
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
keyUsage = critical, digitalSignature, keyEncipherment, nonRepudiation
extendedKeyUsage = clientAuth, emailProtection
EOF
echo -n "${1}"
}
_v3ext_server() {
cat > "${1}" <<EOF
authorityKeyIdentifier = keyid,issuer:always
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${2}
DNS.2 = *.${2}
IP.1 = 127.0.0.1
EOF
echo -n "${1}"
}
# -------------------------------------------------------------------------------------------------------------------- #
# -------------------------------------------------< RUNNING SCRIPT >------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #
"$@"
|
Скрипт содержит две функции, вызывать которых можно по отдельности. Функция ca()
позволяет сгенерировать ключ и сертификат центра сертификации, а функция cert()
, соответственно, генерирует клиентские сертификаты.
- Создать ключ и сертификат центра сертификации:
1
| bash bash.openssl.ca.sh ca
|
- Создать клиентские ключи и сертификаты:
1
| bash bash.openssl.ca.sh cert
|
Немного расскажу про алгоритм генерации сертификатов.
- У сертификата есть поле
Serial Number
, которое содержит серийный номер сертификата. Серийный номер сертификата это уникальный номер, выданный центром сертификации. В моём скрипте при генерации клиентского сертификата указаны опции -CAcreateserial
и -CAserial "${srl}"
, которые позволяют OpenSSL создавать рядом с клиентскими сертификатами файлы формата .slr
, содержащие в себе серийные номера сгенерированных сертификатов. Таким образом можно создавать небольшую локальную базу данных выданных сертификатов. - Файл каждого клиентского сертификата генерируется с названием вида
[TS].[SFX].[EXE]
, где [TS]
- это временная метка в микросекундах, [SFX]
- числовой суффикс, а [EXE]
- расширение файлов.
На этом всё. Не исключаю факта присутствия ошибок или неточностей. Если что, жду предложения в электронную почту. 😄
Для быстрого создания само-подписанного сертификата, я написал небольшой скрипт. Настройки скрипта необходимо адаптировать под себя.
Генератор корректных SSL-сертификатов. Перед генерацией сертификата, необходимо откорректировать переменные в скрипте под проект.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| #!/usr/bin/env -S bash -e
# -------------------------------------------------------------------------------------------------------------------- #
# OPENSSL SELF SIGNED CERTIFICATE GENERATOR
#
# @package Bash
# @author Kai Kimera <mail@kai.kim>
# @copyright 2024 Library Online
# @license MIT
# @version 0.1.0
# @link https://lib.onl/ru/2023/10/6733cb51-62a0-5ed9-b421-8f08c4e0cb18/
# -------------------------------------------------------------------------------------------------------------------- #
(( EUID == 0 )) && { echo >&2 'This script should not be run as root!'; exit 1; }
# -------------------------------------------------------------------------------------------------------------------- #
# CONFIGURATION
# -------------------------------------------------------------------------------------------------------------------- #
# Specifies the number of days to make a certificate valid for.
# Default is 10 years.
days='3650'
# The two-letter country code where your company is legally located.
country='RU'
# The state/province where your company is legally located.
state='Russia'
# The city where your company is legally located.
city='Moscow'
# Your company's legally registered name (e.g., YourCompany, Inc.).
org='LocalHost'
# Your company's organizational unit.
ou='IT Department'
# Common name (CN). The fully-qualified domain name (FQDN) (e.g., www.example.com).
cn="${1:?}"
# Your email address.
email='mail@localhost'
# -------------------------------------------------------------------------------------------------------------------- #
# -----------------------------------------------------< SCRIPT >----------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #
! [[ -x "$( command -v 'openssl' )" ]] && { echo >&2 "'openssl' is not installed!"; exit 1; }
echo '' && echo "--- [SSL] SELF SIGNED CERTIFICATE: '${cn}'" && echo ''
openssl ecparam -genkey -name 'prime256v1' | openssl ec -out "${cn}.key" \
&& openssl req -new -sha256 \
-key "${cn}.key" \
-out "${cn}.csr" \
-subj "/C=${country}/ST=${state}/L=${city}/O=${org}/OU=${ou}/CN=${cn}/emailAddress=${email}" \
-addext "basicConstraints = critical, CA:${3:-FALSE}" \
-addext 'nsCertType = server' \
-addext 'nsComment = OpenSSL Generated Server Certificate' \
-addext 'keyUsage = critical, digitalSignature, keyEncipherment' \
-addext 'extendedKeyUsage = serverAuth, clientAuth' \
-addext "subjectAltName = ${2:?}" \
&& openssl x509 -req -sha256 -days ${days} -copy_extensions 'copyall' \
-key "${cn}.key" \
-in "${cn}.csr" \
-out "${cn}.crt" \
&& openssl x509 -in "${cn}.crt" -text -noout
exit 0
|
Скрипт можно удалённо запросить из репозитория или запустить локально на хосте. Скрипт принимает следующие параметры согласно очерёдности:
CN
- CN (Common Name).subjectAltName
- subjectAltName (Alternative Name).[TRUE|FALSE]
- является ли сертификат сертификатом центра сертификации и можно ли при помощи него заверять другие сертификаты. Параметр принимает значение TRUE
или FALSE
. По умолчанию FALSE
.
В терминале выполнить команду, подставив свои значения:
1
| curl -sL 'https://lib.onl/ru/2023/10/6733cb51-62a0-5ed9-b421-8f08c4e0cb18/bash.openssl.ssc.sh' | bash -s -- '<CN>' '<subjectAltName>'
|
1
| wget -qO - 'https://lib.onl/ru/2023/10/6733cb51-62a0-5ed9-b421-8f08c4e0cb18/bash.openssl.ssc.sh' | bash -s -- '<CN>' '<subjectAltName>'
|
Например:
1
| curl -sL 'https://lib.onl/ru/2023/10/6733cb51-62a0-5ed9-b421-8f08c4e0cb18/bash.openssl.ssc.sh' | bash -s -- 'example.com' 'DNS:localhost, DNS:*.localhost, DNS:example.com, DNS:*.example.com, IP:127.0.0.1, IP:192.168.1.2'
|
1
| wget -qO - 'https://lib.onl/ru/2023/10/6733cb51-62a0-5ed9-b421-8f08c4e0cb18/bash.openssl.ssc.sh' | bash -s -- 'example.com' 'DNS:localhost, DNS:*.localhost, DNS:example.com, DNS:*.example.com, IP:127.0.0.1, IP:192.168.1.2'
|
Таким образом, сертификат будет сгенерирован с заданными параметрами и заверен собственной подписью.
Чтобы создать PFX-файл, необходимо выполнить следующую команду:
1
| f='example.com'; openssl pkcs12 -export -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES -nomac -inkey "${f}.key" -in "${f}.crt" -out "${f}.pfx"
|
Где:
f
- переменная, содержащая общее название файлов.