Проверка цифровой подписи с помощью OpenSSL

Как я могу проверить сообщения CMS/PKCS # 7 с помощью OpenSSL в Ruby?
Сообщение PKCS # 7 используется в качестве цифровой подписи для пользовательских сообщений, поэтому мне нужно подписать новое сообщение пользователя и проверить входящий. Я не нашел ничего полезного в документации и google. Я нашел несколько примеров кода для подписи, но ничего не проверял:

signed = OpenSSL::PKCS7::sign(crt, key, data, [], OpenSSL::PKCS7::DETACHED)

Ответ 1

Короткий ответ

Предполагая, что все определено как указано в вашем фрагменте, с отдельной подписью, без цепочки сертификатов для доверенного корня, сертификата crt, подписи signed и данных data, следующее должно делать то, что вы хотите

store = OpenSSL::X509::Store.new
p7 = OpenSSL::PKCS7.new(signed.to_der)
verified = p7.verify([crt], store, data, 
                     OpenSSL::PKCS7::DETACHED || OpenSSL::PKCS7::NOVERIFY)

(Я не тестировал это, YMMV)

Полная история

Вот полная история того, как я нашел это, со ссылками на все источники, которые я использовал, поэтому, если вам нужна дополнительная информация, вам нужно куда-то посмотреть.

Взглянув на документацию OpenSSL:: PKCS7, мы находим этот кусочек мудрости:

PKCS7.new = > pkcs7
PKCS7.new(строка) = > pkcs7
Многие методы в этом классе arent задокументированы.

И быстрый Google тоже ничего не изменит. Это говорит о том, что нам придется принимать более экстремальные меры. Позвольте сделать поиск кода Google для тех, кто использует OpenSSL:: PKCS7 для проверки подписи.

Хм. Мы находим несколько тестовых примеров. Это хорошо; по крайней мере, он имеет модульные тесты, которые могут помочь показать, что функциональность действительно работает, и продемонстрировать, как это работает.

store = OpenSSL::X509::Store.new
store.add_cert(@ca_cert)
ca_certs = [@ca_cert]

data = "aaaaa\r\nbbbbb\r\nccccc\r\n"
tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs)
p7 = OpenSSL::PKCS7::PKCS7.new(tmp.to_der)
certs = p7.certificates
signers = p7.signers
assert(p7.verify([], store))
assert_equal(data, p7.data)

Это не так уж плохо. Храните хранилище сертификатов. Подпишите свои данные, затем создайте новый объект OpenSSL:: PKCS7 из подписанных данных. Затем вы можете вызвать certificates на нем, чтобы извлечь цепочку сертификатов, с которой она была подписана, signers для извлечения подписчиков, а verify можно вызвать, чтобы убедиться, что подпись действительна. Похоже, вы передаете хранилище сертификатов, содержащих ваши доверенные сертификаты ЦС, в качестве второго аргумента для проверки. И вы можете извлечь данные, вызвав data на нем.

Но что означает первый аргумент? Никто в наших тестовых случаях, кажется, не передает ничего, кроме пустого списка для первого аргумента. Хм. Загадка. Мы вернемся к этому.

Третий, необязательный, аргумент verify выглядит так, как он использовался для проверки отдельной записи:

data = "aaaaa\nbbbbb\nccccc\n"
flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED
tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag)
p7 = OpenSSL::PKCS7::PKCS7.new(tmp.to_der)
a1 = OpenSSL::ASN1.decode(p7)

certs = p7.certificates
signers = p7.signers
assert(!p7.verify([], store))
assert(p7.verify([], store, data))

Возврат к первому аргументу. Мы нашли больше, чем просто тестовые примеры, когда мы выполнили наш поиск кода; мы также нашли несколько других применений. Фактически, второй, похоже, использует первый аргумент:

# 'true' if signature was created using given cert, 'false' otherwise
def match?(cert)
  @p7.verify([cert.raw_cert], @store, nil, OpenSSL::PKCS7::NOVERIFY)
end

А, ОК. Это список сертификатов для проверки. И теперь есть четвертый параметр, который, как представляется, состоит из флагов. Проверяя документы OpenSSL, мы видим, что это неинтуитивное имя (с флагом NOVERIFY?) Означает, что вы должны проверять подпись только на прошедших сертификатах и сертификаты, встроенные в подпись, а не пытаться проверить всю цепочку сертификатов на вашем доверенном хранилище CA.

Это вся полезная информация, но есть ли что-то, что нам не хватает? К счастью, Ruby - это программное обеспечение с открытым исходным кодом, поэтому мы можем "Использовать источник, Люк!" После некоторого вовлечения в поиск кода Google мы находим определение ossl_pkcs7_verify. Когда вы пройдете несколько загадочных имен, код достаточно прост для чтения; он в основном просто преобразует свои аргументы в формат, который может понять OpenSSL, и вызывая:

ok = PKCS7_verify(p7, x509s, x509st, in, out, flg);

Итак, это выглядит как что ', где мы действительно хотим найти документацию.

ОПИСАНИЕ

PKCS7_verify() проверяет структуру подписанных PKCS # 7. p7 - это структура PKCS7 для проверки. certs - это набор сертификатов для поиска сертификата подписчика. store - это доверенное хранилище certficate (используется для проверки цепочки). indata​​STRONG > - это подписанные данные, если содержимое отсутствует в p7 (то есть оно отключено). Содержимое записывается в out если он не равен NULL.

flags - необязательный набор флагов, который может использоваться для изменения проверки операция.

PKCS7_get0_signers() извлекает сертификаты подписывающего лица из p7, он делает not проверяют их действительность или действительны ли какие-либо подписи. Сертификаты и flags имеют те же значения, что и в PKCS7_verify().

Подробнее см. полную справочную страницу.

О, и в качестве побочной заметки я нашел это предупреждениепри поиске; это похоже на Ruby 1.9 и, возможно, в некоторых более поздних версиях Ruby 1.8, класс был перемещен из резервной OpenSSL:: PKCS7:: PKCS7 в OpenSSL:: PKCS7.

warn("Warning: OpenSSL::PKCS7::PKCS7 is deprecated after Ruby 1.9; use OpenSSL::PKCS7 instead")