Подпись HTTP сообщений
В качестве дополнительной защиты от подмены сообщения в TLS-канале отправляемое сообщение может быть подписано.
Формирование подписи
Клиент формирует подпись отправляемого сообщения следующим образом.
- Создаётся случайный ключ
K
. - На основе отправляемого HTTP сообщения формируется строка
M
. - Вычисляется подпись
S
на ключеK
от строкиM
:BASE64(HMAC(K, UTF8.GetBytes(M))
. - Формируется заголовок
CP-Signature
, содержащийS
иK
, и добавляется к HTTP запросу.
Проверка подписи
Сервер проверяет подпись следующим образом.
- Из заголовка
CP-Signature
извлекается ключK
и подписьS
. - На основе полученного HTTP сообщения формируется строка
M
. - Вычисляется подпись
S'
на ключеK
от строкиM
:BASE64(HMAC(K, UTF8.GetBytes(M))
. - Подпись
S'
сравнивается сS
. Если значения совпадают, то подпись верна, иначе в сообщении что-то было изменено.
Формирование строки M
Строка M
формируется на основе заголовков HTTP сообщения и его содержимого.
Целевой URL считается псевдозаголвком (request-target)
.
Строка M
представляет собой конкатенацию двух строк: headers
и body
.
Формирование строки headers
Для каждого подписываемого заголовка (какие заголовки будут подписаны
определяется клиентом) вычисляются строки header-name: header-value
,
где header-name
- это название заголовка в нижнем регистре, а header-value
значение заголовка без лидирующих и конечных пробелов.
Если заголовком является (request-target), то его значение вычисляется как
method URL
, где method
- название HTTP метода в нижнем регистре, а URL -
целевой URL запроса (без изменений).
Все сформированные строки конкатенируются через символ новой строки \n
.
После последней строки символа \n
нет.
Пример
Для HTTP запроса
POST /SignServer/rest/api/transactions HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5qdWFKbEtHMDlwblF1cDVyUUdxLW1TTkVsUSJ9.eyJ1bmlxdWVfbmFtZSI6InNnYSIsIm5hbWVpZCI6ImMxMDNhYjhlLTIzNDUtNDk3OS1hODg1LWQxNmIwZjNjYWQ0MiIsImRzc19pc3MiOiJyZWFsc3RzIiwiZHNzX3V1aWQiOiI0OXBEWmZ4VXdGamVIaFlTc3JTWDJMdmRTeEE9IiwiZW1haWwiOiJnLmEuc2Fkb2ZpZXZAZ21haWwucnUiLCJyb2xlIjoiVXNlcnMiLCJodHRwOi8vZHNzLmNyeXB0b3Byby5ydS9pZGVudGl0eS9jbGFpbXMvYWN0aW9uL2lzc3VlIjoidGZhOmZhbHNlIiwiaHR0cDovL2Rzcy5jcnlwdG9wcm8ucnUvaWRlbnRpdHkvY2xhaW1zL2FjdGlvbi9zaWduZG9jdW1lbnQiOiJ0ZmE6dHJ1ZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vc2lnbmRvY3VtZW50cyI6InRmYTpmYWxzZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vZGVjcnlwdGRvY3VtZW50IjoidGZhOmZhbHNlIiwiaHR0cDovL2Rzcy5jcnlwdG9wcm8ucnUvaWRlbnRpdHkvY2xhaW1zL2FjdGlvbi9jcmVhdGVyZXF1ZXN0IjoidGZhOnRydWUiLCJodHRwOi8vZHNzLmNyeXB0b3Byby5ydS9pZGVudGl0eS9jbGFpbXMvYWN0aW9uL2NoYW5nZXBpbiI6InRmYTpmYWxzZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vcmVuZXdjZXJ0aWZpY2F0ZSI6InRmYTpmYWxzZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vcmV2b2tlY2VydGlmaWNhdGUiOiJ0ZmE6ZmFsc2UiLCJodHRwOi8vZHNzLmNyeXB0b3Byby5ydS9pZGVudGl0eS9jbGFpbXMvYWN0aW9uL2hvbGRjZXJ0aWZpY2F0ZSI6InRmYTpmYWxzZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vdW5ob2xkY2VydGlmaWNhdGUiOiJ0ZmE6ZmFsc2UiLCJodHRwOi8vZHNzLmNyeXB0b3Byby5ydS9pZGVudGl0eS9jbGFpbXMvYWN0aW9uL2RlbGV0ZWNlcnRpZmljYXRlIjoidGZhOmZhbHNlIiwiaHR0cDovL2Rzcy5jcnlwdG9wcm8ucnUvaWRlbnRpdHkvY2xhaW1zL2FjdGlvbi9wcml2YXRla2V5YWNjZXNzIjoidGZhOmZhbHNlIiwiZHNzX2dyb3VwIjoiRGVmYXVsdCIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY2Nlc3Nwb2xpY3kiOiIwIiwiZHNzX3NodHRwIjoiKHJlcXVlc3QtdGFyZ2V0KSBjb250ZW50LXR5cGUgY29udGVudC1sZW5ndGggYXV0aG9yaXphdGlvbiAocmVxdWVzdC1ib2R5KSIsImlzcyI6InJlYWxzdHMiLCJleHAiOjE1MzM1NDE2NjQsIm5iZiI6MTUzMzU0MTM2NH0.N4N4e3re_lZM9k1Kyeijhnm6mBlV9KpNxexv7pHnpIAf3dT3n9qA1HCtmmwipzwEm04F5SE4cJwFG7nleozfG2R_RMm-voG7YCmjCeNyAR9SMaHqwVwNkVN3_162JF8lzQ4h6ZJW2tKE7whSZRkdfxI8NAEyiYaFSezulmSg8_Ks8MKkkYipsOuKlkh6tAcLiMP1Xn0sgeXO-dDyz8F5hfBFAEmTMsGH1_Va-a7pI4X-SzA6ZYMFvvBnCw9DZ1CQDl42RIirqi_axXzllP9TTJKxVNXeWThC8bHHi-t42IrQXXYf-w8zABlMop6DdemWJXql5MqopcJCAVSJW_6H64BRA85lTqOX3iOjimyZRPwFBBlYn8jqgQBQaBDBxnETT8jCHRQMkwYSrFUMkQWDeKsRGRVEJ2WJoAE2DqeRVkC2ENS4SBFxLITTFmDEi6-PjCUNGqU30IjGRCOvdBadnHfgqHeQK0MVU03pqzHrk-aIBhp0_EdmLY1l348gQFzqy1kJlTviZdFq7OIWlDB98y1ovKA5lE-AGnqcU32EMFqAMCWMPV-4VTzFxvBbVbDliNRv2zavBiNrq5lJlUfrs-EerJqFCAIsA3bJaTz_Sqo4r_dJmBdhICtl5slfXLpYfdR4zxT20-dx_-VvyCdGYBspoigCLo5zaCaW2hsuqoE
Content-Type: application/json; charset=utf-8
Content-Length: 179
{"OperationCode":"SignDocument","Document":"AQI=","Parameters":[{"Name":"SignatureType","Value":"CMS"},{"Name":"CertificateID","Value":"0"},{"Name":"DocumentInfo","Value":"aaa"}]}
Будет сформирована следующая строка headers
:
(request-target): post /SignServer/rest/api/transactions
content-length: 179
content-type: application/json; charset=utf-8
authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5qdWFKbEtHMDlwblF1cDVyUUdxLW1TTkVsUSJ9.eyJ1bmlxdWVfbmFtZSI6InNnYSIsIm5hbWVpZCI6ImMxMDNhYjhlLTIzNDUtNDk3OS1hODg1LWQxNmIwZjNjYWQ0MiIsImRzc19pc3MiOiJyZWFsc3RzIiwiZHNzX3V1aWQiOiI0OXBEWmZ4VXdGamVIaFlTc3JTWDJMdmRTeEE9IiwiZW1haWwiOiJnLmEuc2Fkb2ZpZXZAZ21haWwucnUiLCJyb2xlIjoiVXNlcnMiLCJodHRwOi8vZHNzLmNyeXB0b3Byby5ydS9pZGVudGl0eS9jbGFpbXMvYWN0aW9uL2lzc3VlIjoidGZhOmZhbHNlIiwiaHR0cDovL2Rzcy5jcnlwdG9wcm8ucnUvaWRlbnRpdHkvY2xhaW1zL2FjdGlvbi9zaWduZG9jdW1lbnQiOiJ0ZmE6dHJ1ZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vc2lnbmRvY3VtZW50cyI6InRmYTpmYWxzZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vZGVjcnlwdGRvY3VtZW50IjoidGZhOmZhbHNlIiwiaHR0cDovL2Rzcy5jcnlwdG9wcm8ucnUvaWRlbnRpdHkvY2xhaW1zL2FjdGlvbi9jcmVhdGVyZXF1ZXN0IjoidGZhOnRydWUiLCJodHRwOi8vZHNzLmNyeXB0b3Byby5ydS9pZGVudGl0eS9jbGFpbXMvYWN0aW9uL2NoYW5nZXBpbiI6InRmYTpmYWxzZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vcmVuZXdjZXJ0aWZpY2F0ZSI6InRmYTpmYWxzZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vcmV2b2tlY2VydGlmaWNhdGUiOiJ0ZmE6ZmFsc2UiLCJodHRwOi8vZHNzLmNyeXB0b3Byby5ydS9pZGVudGl0eS9jbGFpbXMvYWN0aW9uL2hvbGRjZXJ0aWZpY2F0ZSI6InRmYTpmYWxzZSIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY3Rpb24vdW5ob2xkY2VydGlmaWNhdGUiOiJ0ZmE6ZmFsc2UiLCJodHRwOi8vZHNzLmNyeXB0b3Byby5ydS9pZGVudGl0eS9jbGFpbXMvYWN0aW9uL2RlbGV0ZWNlcnRpZmljYXRlIjoidGZhOmZhbHNlIiwiaHR0cDovL2Rzcy5jcnlwdG9wcm8ucnUvaWRlbnRpdHkvY2xhaW1zL2FjdGlvbi9wcml2YXRla2V5YWNjZXNzIjoidGZhOmZhbHNlIiwiZHNzX2dyb3VwIjoiRGVmYXVsdCIsImh0dHA6Ly9kc3MuY3J5cHRvcHJvLnJ1L2lkZW50aXR5L2NsYWltcy9hY2Nlc3Nwb2xpY3kiOiIwIiwiZHNzX3NodHRwIjoiKHJlcXVlc3QtdGFyZ2V0KSBjb250ZW50LXR5cGUgY29udGVudC1sZW5ndGggYXV0aG9yaXphdGlvbiAocmVxdWVzdC1ib2R5KSIsImlzcyI6InJlYWxzdHMiLCJleHAiOjE1MzM1NDE2NjQsIm5iZiI6MTUzMzU0MTM2NH0.N4N4e3re_lZM9k1Kyeijhnm6mBlV9KpNxexv7pHnpIAf3dT3n9qA1HCtmmwipzwEm04F5SE4cJwFG7nleozfG2R_RMm-voG7YCmjCeNyAR9SMaHqwVwNkVN3_162JF8lzQ4h6ZJW2tKE7whSZRkdfxI8NAEyiYaFSezulmSg8_Ks8MKkkYipsOuKlkh6tAcLiMP1Xn0sgeXO-dDyz8F5hfBFAEmTMsGH1_Va-a7pI4X-SzA6ZYMFvvBnCw9DZ1CQDl42RIirqi_axXzllP9TTJKxVNXeWThC8bHHi-t42IrQXXYf-w8zABlMop6DdemWJXql5MqopcJCAVSJW_6H64BRA85lTqOX3iOjimyZRPwFBBlYn8jqgQBQaBDBxnETT8jCHRQMkwYSrFUMkQWDeKsRGRVEJ2WJoAE2DqeRVkC2ENS4SBFxLITTFmDEi6-PjCUNGqU30IjGRCOvdBadnHfgqHeQK0MVU03pqzHrk-aIBhp0_EdmLY1l348gQFzqy1kJlTviZdFq7OIWlDB98y1ovKA5lE-AGnqcU32EMFqAMCWMPV-4VTzFxvBbVbDliNRv2zavBiNrq5lJlUfrs-EerJqFCAIsA3bJaTz_Sqo4r_dJmBdhICtl5slfXLpYfdR4zxT20-dx_-VvyCdGYBspoigCLo5zaCaW2hsuqoE
Формирование строки body
Строка body представляет собой содержимое HTTP запроса (без каких-либо изменений).
Формирование заголовка CP-Signature
Заголовок CP-Signature
имеет вид (для предыдущего примера):
Здесь переносы строк добавлены только для наглядности.
CP-Signature: headers="(request-target) content-length content-type authorization",
key="QH5xtLRVDncY50KC82RprzX9AIhaDNioX4wzb9PlGn8=",
signature="8z8qEydpfZdnzOwRn8eVvxSmIUwx3akScU6azm3SNUc="
В параметре key
передаётся значение ключа K
в BASE64, в параметре headers
передаётся перечень всех подписываемых заголовков, в параметре signature
- значение
подписи S
в BASE64.