Для цифровой подписи файлов у меня есть PKCS12 со всем необходимым ключевым материалом (например, закрытый ключ + сертификат подписи + цепочка сертификатов).
PKCS12 построен примерно так:
openssl pkcs12 -in cert_chain.pem -inkey private.key -passin "pass:xx" -export -passout "pass:yy" -out signature.pfx
Я намерен импортировать этот материал в Azure KeyVault, чтобы он выполнил фактическое вычисление подписи, при этом мое собственное программное обеспечение не будет иметь фактического доступа к закрытому ключу.
(Конечно, это не настоящая подпись HSM, но цель более или менее та же: мое программное обеспечение будет иметь доступ только к API подписи Azure KeyVault)
Но я не могу этого добиться.
Даже помечая ключ как не подлежащий экспорту, я всегда нахожу способ получить доступ к закрытому ключу, так или иначе, через API-интерфейсы Azure.
Короче говоря, вопрос: как изолировать закрытый ключ, чтобы приложение, имеющее доступ к хранилищу ключей (разрешения политики: получение сертификата, подпись, получение секрета), не могло получить к нему доступ?
Давайте посмотрим, что у меня есть на данный момент:
Насколько я понимаю, это возможно с помощью Политики сертификатов KeyVault, помечая ключ как exportable: false
Я использую azure-cli для операции импорта:
az keyvault certificate import \
--vault-name "someKv" \
--name "someName" \
-f signature.pfx \
--password yy \
--policy "@cert_policy.json"
При такой политике:
{
"keyProperties": {"exportable": false},
"secretProperties": {"contentType": "application/x-pkcs12"}
}
На этом этапе у меня есть что-то странное: моя политика активно не учитывается (например, в выводе я все еще вижу ключ, отмеченный как exportable: true
).
Я знаю, что мой файл JSON учитывается при вызове, потому что, если я его изменю, у меня возникнут ошибки анализа JSON из CLI.
Возможно, в этом суть моей проблемы... Но я упрям. Поэтому я использую необработанный вызов REST API для принудительной настройки:
curl -XPATCH \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer someToken" \
"https://someKv.vault.azure.net/certificates/myCertName/policy?api-version=7.4" \
-d '{"key_props": {"exportable": false}}'
Что удалось (HTTP 200/OK), и после этого вызова я вижу:
> az keyvault certificate show --vault-name "someKv" --name signature
...
"policy": {
"attributes": { ... },
...
"keyProperties": {
...
"exportable": false,
...
},
Так что я чувствую себя нормально.
Например, я использую Java SDK в своем приложении и, используя возможности azure-security-keyvault-jca
, когда я пытаюсь получить доступ к ключу, я получаю не RSAPrivateKey
, а KeyVaultPrivateKey
с его атрибутами доступа к Keyvault, который (если вы хотите следовать источнику ), очевидно, это то, что ожидается для неэкспортируемых ключей.
Я все еще чувствую себя хорошо.
Затем я реализую свою подпись, и мне нужен доступ к цепочке сертификатов (действительно, мой настоящий центр сертификации использует промежуточный сертификат между моим собственным сертификатом и его корнем, поэтому мне приходится встраивать его в подпись).
Оглядываясь вокруг, я не нашел другого способа получить к нему доступ в своей настройке, кроме как использовать API-интерфейс KeyVault Secret API Azure (при импорте сертификата KeyVault Azure вместе с ним создает секрет KeyVault). Содержимое секрета — это (base64-ed) PKCS12.
Я открываю этот PKCS12, он имеет одну запись (как и моя исходная, импортированная), связанную с этим псевдонимом, сертификатом, его цепочкой... а что насчет закрытого ключа:
System.out.println(
ks.getKey(aliases.get(0), "".toCharArray())
)
> SunRsaSign RSA private CRT key, 2048 bits
params: null
modulus: (a real value)
private exponent: (a real value)
Упс... Мое программное обеспечение имеет доступ к закрытому ключу (кстати, любой пользователь Azure с достаточными привилегиями Keyvault также может получить доступ к ключу, что не абсурдно, но и не особо ожидаемо от exportable: false
).
Это не то, что я хотел.
Теперь я мог удалить разрешение Get Secret
для моего приложения в политике KeyVault (ну, на самом деле я не мог этого сделать для других функций приложения, но давайте признаем), но тогда я вообще не мог получить доступ к секрету, что не позволяет мне получить доступ к сертификату. цепочку (которую я, по общему признанию, мог бы включить в свое приложение, но она создает ощущение дублирования информации, что усложняет развертывание и обслуживание).
Отвечая на мой собственный вопрос.
Кажется, тот факт, что
На этом этапе у меня есть что-то странное: моя политика активно не учитывается (например, в выводе я все еще вижу ключ, помеченный как экспортируемый: true).
Это ключевой вопрос здесь. Похоже, что изменение экспортируемой политики после первоначальной загрузки не поддерживается.
В качестве «доказательства» я переключил метод импорта сертификатов с az cli
на Azure REST API:
curl -XPOST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ..." \
"https://someVaule.vault.azure.net/certificates/myCert/import?api-version=7.4" \
--data-raw "{\"value\": \"$(cat sign.pfx | openssl base64 -A)\", \"policy\": {\"key_props\": {\"exportable\": false}, \"secret_props\": {\"contentType\": \"application/x-pkcs12\"}}, \"pwd\": \"somePwd\"}"
Используя эту загрузку, импортированный объект сразу же помечается как exportable: false
, а последующая загрузка связанного секрета выводит PKCS12 без закрытого ключа (что, кстати, довольно странный зверь для работы, по крайней мере, в Java).
Короче говоря, возможное неправильное использование или ошибка Azure CLI заставили меня импортировать сертификат, который изначально был экспортируемым. Изменение экспортируемого характера после операции импорта выглядит так, как будто оно работает, но это не так. Использование REST API позволяет обойти эту проблему.