Втеча з Крипто Про. Режисерська версія, СМЕВ-edition

Ця стаття присвячена тому, як перестати використовувати Крипто Про і перейти на Bouncy Castle в девелоперському/тестовому оточенні.
На початку статті буде більше про СМЕВ і його клієнт, в кінці — більше про конвертування ключів з готової копипастой, щоб можна було почати прямо зараз.
Картинка для привернення уваги:
image

І відразу ж відповідь:
image

Disclamer
Звичайно, втекти з Крипто Про неможливо, тому що це оплот російської криптографії. Він удовлетовряет вимогам компетентних органів, він прописаний у договори і контраты, йому довіряє вся країна, і так далі. Тому всі нижчеописані відноситься девелоперського або тестового оточення, де ми самі собі господарі.
Нещодавно мені потрібно було розібратися, як написати сервіс, що працює з Системою Міжвідомчого Електронного Взаємодії.
Все написане являє собою просто результат невеликого дослідження, максимально абстрагированный від виконаної роботи. І навіть приблизно вгадати щось про реально прийнятих рішеннях неможливо, я перевіряв.
Обґрунтування потрібності готового клієнта
На технологічному порталі СМЭВ3 лежать исходники клієнта, але ось невдача — вони цвяхами прибиті до КриптоПро. Вордівському файл з інструкцією, що додається до исходниками, стверджує це самим прямим чином. Та й все одно, вихідні ключі у нас теж у форматі КриптоПро. Виходячи з production це нормально, а от для тестового оточення страшенно незручно. Хотілося б від цього позбутися.
На сайті є дві версії — "актуальна" і "рекомендована". Чому вони так, і чому актуальна версія не рекомендується, а рекомендована не актуальна — якась дилема копірайтера :) Далі йдеться про те клієнті який "актуальний".
Строго кажучи, використовувати його не можна, тому що в архіві исходников немає тексту ліцензії, і тому незрозуміло, під якою ліцензією повинна поширюватися похідна робота. Я подзвонив по телефону підтримки, написаному на порталі, написав на пошту, пару тижнів спостерігав як моя заявка літає між рівнями підтримки та виконують організаціями, і в результаті віз і нині там:
image
Задача не виконана, але виконані та закрита, дивовижно. Гаразд, чорт з ними...
Незважаючи на неможливість використовувати його у себе безпосередньо в коді, це відмінний тестовий приклад. Справа в тому, що в методичних рекомендаціях СМЭВа без півлітри не розібратися, і готовий живий код дає відмінний буст до розуміння.
Основна претензія до документації — це канцеляризми і убоге опис в інтернеті.
Пам'ятайте мем про копірайтера, який з абзацу зробив одну пропозицію в кілька слів? Для документації СМЭВа це має місце бути, наприклад ось ланцюжок рефакторинов для однієї произовльно взятої фрази:
"2. ІВ споживача направляє в СМЕВ міжвідомчий запит;"
"2. ІВ споживача направляє в СМЕВ запит;"
"2. ІВ споживача направляє запит;"
"2. споживач направляє запит;"
"2. запит споживача;"
Коротше, наявність готової реалізації — це добро.
Чому Крипто Про JCP ласкаво
  • Він є і працює
  • Судячи з исходниками BoncyCastle, товариші з КриптоПро взяли участь у його розвитку, додавши алгоритми за ГОСТ
Чому Крипто Про JCP зло
  • Якщо у вас багато розробників і віртуальних машин, купувати ліцензію не дуже хочеться
  • Проприетарщина, тому всі описані нижче баги виправити не можна. Точніше можна (дізассемблер стріляє без промаху), але незаконно і з втратою всіх гарантій — не варіант
  • На Java 8 під OSX завести не вдалося (жодну версію КП JCP). Скоріше всього це виправлять досить скоро, т. к. представники відреагували на мій пост у Фейсбуці
  • Взагалі, на OSX завести не вдалося. Гуй адмінки — полурабочий, сипле помилками, шматки ґуя не працюють.
  • На лінуксі теж є баги в інтерфейсі
  • Колись давно інсталятор Windows писав в консолі крокозябры (не перевіряв на нових версіях — може, пофиксили)
  • Установка патчингом дистрибутива джави. Ящетаю, що установка софта методом патчінга джави — це зло в останній інстанції, за це суд із прав людини повинен призначати шестиразовий розстріл з повешанием
  • Не кожну java можна пропатчити, для з'ясування магічною комбінації потрібно серйозно упороться. Тут важливо, що ми намагаємося розробляти на самих нових версіях джави, з пилу-з жару, і тестуємо на нових версія (на момент написання статті — JDK9), так що обмеження на версію джави — це божевілля, як воно є
  • Способи інсталяції і запуску адмінки — лютий треш (це треба бачити)
В якості альтернативи в тестовому оточенні я предалагю використовувати Bouncy Castle з контейнером PKCS12 або JKS. Це відкрите, вільне і безкоштовне ПЗ.
До честі розробників Крипто Про, схоже, вони брали участь у його розробці.
Що потрібно допилити в готовому клієнта, щоб втекти з Крипто Про
Код написаний досить дружелюбно для розширення, тому можна просто взяти за основу клас KeyStoreWrapperJCP, і аналогічно написати KeyStoreWrapperBouncyCastlePKCS12, KeyStoreWrapperBouncyCastleJKS.
Переписати DigitalSignatureFactory, так, щоб він на вхід почав приймати шлях до криптоконтейнера на файловій системі і пароль від нього (для КриптоПро це просто не потрібно). Там є свіч, який перевіряє тип криптопровайдера, в нього треба дописати додатково два кейса, імен типу BOUNCY_JKS і BOUNCY_PKCS12 і навішати використання відповідних KeyWrapper і виклик initXmlSec.
У initXmlSec потрібно дописати
1) можливість приймати взагалі будь-який провайдер, а не тільки криптопро (це просто зручно)
2) Security.addProvider(new BouncyCastleProvider());
3) для XMLDSIG_SIGN_METHOD зробити свіч: якщо КриптоПро, то алгоритм називається "GOST3411withGOST3410EL", а якщо BouncyCastle алгоритм називається "GOST3411WITHECGOST3410".
Ну ніби і все. Якби була відома ліцензія на цей смев-клієнт, я б доклав конкретний код під Apache License 2, а так це просто список ідей.
Ініціалізація XML-підписи в Santuario
Ах так, тут є один цікавий момент. У мережі безліч порад, що стосуються СМЭВа, які полягають у ручному парсингу шматків XMLек, і іншим заходом сонця вручну, але це не наш метод (і не метод, який використовували творці клієнта)
Заміс в тому, що згенерувати самоподписанные ключі по госту легко (копіпаста на SO шукається за секунди). А ось підписати — ні, бо на думку інтернет-школярів нібито Bouncycastle не підтримує xml-підпис. Конкретніше, при роботі з Apache Santuario, XMLSignature з santuario-xmlsec не розуміє що використовувати для обробки методу "xmldsig-more#gostr34102001-gostr3411" при виклику xmlSignature.sign(privateKey).
Окрема хохма в тому, що IntelliJ IDEA Community глючить при спробі отдебажить xmlsec, кидаючи step in відладчика в невірне місце вірних джерел. Я спробував всі розумні версії Ідеї, тому розуміти як це працює треба наосліп, написуя тактичні листи в Спортлото. Це не в докір Ідеї, не існує ідеальних інструментів, просто фактор вплинув на швидкість розуміння питання.
Щоб це запрацювало, потрібно:
1) Заимплементить реалізацію SignatureAlgorithmSpi з Apache Santuario. У GetEngineUri повернути рядок: "http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411" (або що там у вас). Це ключова магія. Абсолютно нічого розумного цей клас робити не повинен.
Ініціалізація раз за все життя додатки (н-р у сінглтон-біне спринга):
2) Завантажити провайдер, щоб не патчити JDK:
Security.addProvider(new BouncyCastleProvider());

3) Впердолить в рантайм тільки що написаний клас:
String algorithmClassName = "fully qualified name класу реалізує SignatureAlgorithmSpi";
Class.forName(algorithmClassName);
SignatureAlgorithm.providerInit();
SignatureAlgorithm.register("http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411", algorithmClassName);

4) Достукатися до маппингов алгоритмів JCE:
String ns = "http://www.xmlsecurity.org/NS/#configuration";
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element rootElement = document.createElementNS(ns, "JCEAlgorithmMappings");
Element algorithms = document.createElementNS(ns, "Algorithms");

5) Замапить метод алгоритм:
Element aElem = document.createElementNS(NameSpace, "Algorithm");
aElem.setAttribute("URI", "http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411");
aElem.setAttribute("Description", "GOST R 34102001 Digital Signature Algorithm with GOST R 3411 Digest");
aElem.setAttribute("AlgorithmClass", "Signature");
aElem.setAttribute("RequirementLevel", "OPTIONAL");
aElem.setAttribute("JCEName", "GOST3411WITHECGOST3410");
algorithms.appendChild(aElem);

6) Застосувати маппинги:
org.apache.xml.security.Init.init();
JCEMapper.init(rootElement);

6) PROFIT!
Після цього XMLSignature різко починає розуміти цей метод, і почне робити xmlSignature.sign.
Підготовка OpenSSL для роботи з ГОСТ
Якщо у вас на початку статті на стіні висить OpenSSL, коли-небудь він точно вистрілить.
Так що так, це важливий момент, необхідний для здійснення подальшого тексту.
  • Так як у нас Крипто Про, і на Маці воно не злітає, нам знадобиться віртуальна машина з Windows (можна навіть Windows XP), та встановленої Криптою
  • Встановити як можна більш актуальну версію OpenSSL (так, щоб в ній вже була підтримка ГОСТ):
    https://wiki.openssl.org/index.php/Binaries
    https://slproweb.com/products/Win32OpenSSL.html
  • Перевірити, що у встановленій версії є файл gost.dll
  • В установленому OpenSSL знайти файл openssl.cfg
  • В початок файлу додати рядок:
    openssl_conf = openssl_def
  • В кінець файлу додати рядки:
    [openssl_def]
    engines = engine_section
    
    [engine_section]
    gost = gost_section
    
    [gost_section]
    engine_id = gost
    dynamic_path = ./gost.dll
    default_algorithms = ALL
    CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet

  • PROFIT
Як ми можемо попросити Крипто Про віддати ключі (насправді, немає)
Якщо у нас вже є справжні (не самоподписанные) ключі, то абсолютно некисло було б перевірити їх у дії. Так, ми говоримо про тестових цілях, але таки довіряй — але перевіряй!
Якщо поставити вінду у віртуальну машину, накотити туди Крипто Про, встановити ключі і спробувати їх експортувати, то виявляємо дивовижну річ: в експортері не працює експорт в PKCS12, а всі інші напрями в експортері заблоковані (англ. "grayed out").
Гуглю помилку, і що ж ми бачимо на офіційному форумі?
https://www.cryptopro.ru/forum2/default.aspx?g=posts&t=2425
"Від Олексія Писинина було отримано відповідь:
Добрий день. PKCS12 не відповідає вимогам безпеки ФСБ в частині зберігання закритих ключів. В теорії, закриті ключі повинні зберігатися на так званих "знімних" носіях. Власне, з цієї причини і не працює експорт."
Я правильно це читаю як, що у них гуй для експорту є, але бізнес-логіки до нього нема?!
Ппоэтому яких галочок ні нащелкай — завжди буде випадати помилка на останньому кроці гуевого майстра?!
Який же сором.
Dear God,
Please kill them all.
Love, Greg.
image
Як ми можемо грубо змусити Крипто Про віддати ключі
Можна дуже довго мучитися, намагаючись засукавши C++ віднімати ключ з контейнера за допомогою OpenSSL і такий-то матері. Я чесно намагався, і розбився об завдання як корабель об скелі (принаймні, це задачка більше ніж на 1 день для людини, яка давно таким не займався). На просторі інтернетів ми не єдині, хто розбився об ті скелі: http://gigamir.net/techno/pub903517
Ось тут настає момент "це радість зі сльозами на очах". Якась контора під назвою Ліссі Софт, всього за 2 тисячі рублів віддає нам геніальну утиліту P12FromGostCSP. Її творці таки перемогли ту проблему, яку не подужав співтовариство, і вона витягає ключі в PFX. Радість — бо вона працює.
Зі сльозами — тому що це проприетарщина, і вона фіг знає як працює.
На картинці Річард Столлман як би здивовано питає: "невже ви боретеся з проприетарщиной з допомогою іншої проприетарщины?"
image
Так що цілком інструкція по перегону ключів виглядає якось так:
  • Всі дії проводити на Windows (підійде віртуальна машина) з встановленим Крипто Про CSP;
  • Підготувати OpenSSL з ГОСТом по інструкції (є в цій статті).
  • Купити P12FromGostCSP. Поплакати.
  • Встановити вихідний ключ в КриптоПро (це заслуговує окремої інструкції, але вона гуглится).
  • Запустити P12FromGostCSP (перед цим заховати ікону Річард Столман під стіл, щоб тебе не прокляв він за запуск проприетарщины)
  • Вибрати встановлений сертифікат, вказати пароль сертифіката КриптоПро
  • Вказати довільний новий пароль (парольну фразу для ключової пари PFX
  • Вказати місце для збереження файлу. Файл краще називати в форматі p12.pfx (це назва по-замовчуванню, краще не чіпати, кажуть, є баги, якщо перейменувати)
  • Отримати pem файл:
    openssl pkcs12 -in p12.pfx -out private.key.pem -name "alias"
  • (-name "alias" — ця опція поміняє ім'я ключа всередині контейнера. Це потрібно тому, що P12FromGostCSP іменує ключі як попало (насправді, по порядку, цифрами), без збереження вихідного псевдонімів.
  • Отримати pkcs12 файл:
    openssl pkcs12 -export -out private.key.pkcs12 -in private.key.pem -name "alias"
  • Готово
Тестові самоподписанные ключі
Так як ми деламем все це в тестових цілях, тепер ми підходимо до кульмінації і починаємо самі собі видавати ключі.
Як видати ключі у форматі Крипто Про, я особливо не морочився, бо це просто не потрібно в рамках поточної задачі. Але на всяк випадок, існує сервіс видачі таких ключів: http://www.cryptopro.ru/certsrv/
  • Всі дії проводити на Windows (підійде віртуальна машина) з встановленим Крипто Про CSP;
  • Відкрити сайт і перейти до видачі ключа;
  • виберіть пункт "Сформувати ключі і відправити запит на сертифікат" і натиснути кнопку "Далі";
  • Клацнути по посиланню "Створити і видати запит до цього ЦС";
  • Заповнити необхідні поля;
  • Натиснутий кнопку "Видати";
  • Встановити сертифікат.
Видача контейнера JKS
Це питання широко представлений в інтернеті, тому можна відразу дивитися Stackoverflow:
http://stackoverflow.com/questions/14580340/generate-gost-34-10-2001-keypair-and-save-it-to-some-keystore
Ідея в тому, що раз вже ми все одно використовуємо Bouncy Castle, то їм же можемо і згенерувати ключ.
Цей код не самий ідеальний, але дає реально працюючу реалізацію (на практиці у мене в результаті вийшло кілька об'ємних класів, щоб зробити зручний інтерфейс)
Security.addProvider( new org.bouncycastle.jce.provider.BouncyCastleProvider() );

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( "ECGOST3410", "BC" );
keyPairGenerator.initialize( new ECGenParameterSpec( "GostR3410-2001-CryptoPro-A" ) );
KeyPair keyPair = keyPairGenerator.generateKeyPair();

org.bouncycastle.asn1.x500.X500Name subject = new org.bouncycastle.asn1.x500.X500Name( "CN=Me" );
org.bouncycastle.asn1.x500.X500Name issuer = subject; // self-signed
BigInteger serial = BigInteger.ONE; // serial number for self-signed does not matter a lot
Date notBefore = new Date();
Date notAfter = new Date( notBefore.getTime() + TimeUnit.DAYS.toMillis( 365 ) );

org.bouncycastle.cert.X509v3CertificateBuilder certificateBuilder = new org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder(
issuer, serial,
notBefore, notAfter,
subject, keyPair.getPublic()
);
org.bouncycastle.cert.X509CertificateHolder certificateHolder = certificateBuilder.build(
new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder( "GOST3411withECGOST3410" )
.build( keyPair.getPrivate() )
);
org.bouncycastle.cert.jcajce.JcaX509CertificateConverter certificateConverter = new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter();
X509Certificate certificate = certificateConverter.getCertificate( certificateHolder );

KeyStore keyStore = KeyStore.getInstance( "JKS" );
keyStore.load( null, null ); // initialize new keystore
keyStore.setEntry(
"alias",
new KeyStore.PrivateKeyEntry(
keyPair.getPrivate(),
new Certificate[] { certificate }
),
new KeyStore.PasswordProtection( "entryPassword".toCharArray() )
);
keyStore.store( new FileOutputStream( "test.jks" ), "keystorePassword".toCharArray()

Видача контейнера PKCS12
В принципі, це не особливо потрібно, тому що у нас вже є простий і зручний спосіб видавати JKS, а JKS для Java це саме що ні на є рідне рішення. Але для повноти картини, нехай буде.
  • Підготувати OpenSSL з ГОСТом по інструкції (є в цій статті).
  • Зробити Cerificate Signing Request + приватний ключ (вписати потрібні дані про ключі!):
    openssl req -newkey gost2001 -pkeyopt paramset:A -passout pass:aofvlgzm -subj "/C=RU/ST=Moscow/L=Moscow/O=foo_bar/OU=foo_bar/CN=developer/emailAddress=olegchiruhin@gmail.com" -keyout private.key.pem -out csr.csr
  • Підписати приватним ключем (на Windows цю операцію потрібно робити з правами Administrator, інакше звалиться з помилкою "unable to write 'random state'"):
    openssl x509 -req -days 365 -in csr.csr -signkey private.key.pem -out crt.crt
  • Отримати публічний ключ:
    openssl x509 -inform pem -in crt.crt -pubkey -noout > public.key.pem
  • GOST2001-md_gost94 hex (якщо треба):
    openssl.exe dgst -hex -sign private.key.pem message.xml 
  • MIME application/x-pkcs7-signature (якщо треба):
    openssl smime -sign -inkey private.key.pem -signer crt.crt -in message.xml
  • Перетворити pem в pkcs12:
    openssl pkcs12 -export -out private.key.pkcs12 -in private.key.pem -name "alias" 
Резюме
В результаті всіх вищеописаних дій ми отримали щодо легій спосіб позбутися від тяжкої ноші Крипто Про.
надалі хотілося б продовжити боротьбу за випив до фінальної перемоги: оформити всі утиліти, генератори ключів, самописні смев-клієнти ітп у вигляді одного репозиторію на Гітхабі. Ще, дуже хотілося б отримати права на модифікацію і поширення під пермиссивной ліцензією офіційного клієнта СМЕВ. Тоді половина цієї статті була б просто не потрібна, і проблема вирішувалася б скачуванням потрібного коду з Гитхаба.

Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.