Цифровая подпись jar файлов

С 2014 г. для запуска приложения Java в веб-браузере необходимо получить цифровой сертификат и «подписать» код программы. Это относится к размещаемым на web-странице приложениям типа апплет, выполненным в виде jar файла. Но, если можно подписать апплет, то, следовательно, можно подписать и обычное десктопное (desktop) Java-приложение, выполненное в виде jar. И не обязательно, чтобы этот jar был исполняемым; он может выполнять функции java-библиотеки.

Поскольку java-приложения и java-библиотеки могут распространяться в виде готовых jar-модулей, то, естественно, некоторые разработчики заинтересованы, чтобы их код не был модифицирован. Те разработчики, которые разрешают вносить изменения в свой код, распространяют приложения с открытым кодом под определенной лицензией. Но, вот закрытый код в виде jar-файла следует каким-либо образом защитить; это можно сделать с использованием цифровой подписи. Конечно, цифровая подпись не защитит код от взлома – файл можно декомпилировать, внести изменения и снова собрать. Но после таких махинаций цифровая подпись станет недействительной, и автор может либо предъявить претензии, либо откреститься от непредусмотренных кодом действий. То есть, автор не будет нести ответственность перед пользователем за модифицированное приложение.

В данной статье будет рассмотрен вопрос цифровой подписи jar файлов.

Самоподписанный сертификат

Для подписи jar файла будем использовать утилиту jarsigner, которая входит в комплект поставки JDK (Java Development Kit). Первое, что нам нужно – это получить цифровой сертификат. Мудрить долго не будем и воспользуемся самоподписанным сертификатом, который сами же и создадим с помощью утилиты keytool. О том, как создать самоподписанный сертификат, а также опции утилиты keytool подробно описано здесь. Не будем повторяться и выполним следующую команду :


C:\Program Files\Java\jdk1.8.0_161\bin>keytool \
   -v -genkey -dname "CN=java-online.ru, OU=Developers, \
   O=IT Systems Inc., L=Moscow, C=RF" -alias codesigner \
   -storetype jks -keystore keystore.jks -validity 365 \
   -keyalg RSA -keysize 2048 -storepass mystorepass \
   -keypass mykeypass

Generating 2 048 bit RSA key pair and self-signed \
certificate (SHA256withRSA) with a validity of 365 days \
for: CN=java-online.ru, OU=Developers, O=IT Systems Inc., \
L=Moscow, C=RF
[Storing keystore.jks]

Warning:
The JKS keystore uses a proprietary format. It is 
recommended to migrate to PKCS12 which is an industry 
standard format using 
"keytool -importkeystore -srckeystore keystore.jks 
    -destkeystore keystore.jks -deststoretype pkcs12".
 

При выполнении данной команды будет создано хранилище keystore.jks с сертификатом, алиас которого 'codesigner'. Если хранилище keystore.jks было создано ранее, то в него будет добавлен новый сертификат. Для доступа к хранилищу и сертификату определены соответствующие пароли : mystorepass и mykeypass.

Проект приложения

В качестве примера для тестирования подписи jar файла создадим проект certificate-reader в IDE Eclipse, представленный на следующем скриншоте. В проект включим класс CertificateReader, позволяющий выполнять чтение сертификатов хранилища. Описание данного класса приведено здесь. Поэтому, на описании и функциональных возможностях класса останавливаться не будем, а сразу же создадим исполняемый модуль certificate-reader.jar, который будем «подписывать».

В проект включены три командных bat-файла, которые можно использовать для старта самого приложения из командной строки, подписи файла и проверки цифровой подписи файла, а также хранилище keystore.jks с самоподписанным сертификатом.

Файл MANIFEST.MF до подписи

Первое, на что следует обратить внимание в jar файле, так это на содержимое манифеста META-INF/MANIFEST.MF, который включает только наименование основного класса, имеющего метод main для старта приложения :

Manifest-Version: 1.0
Class-Path: .
Main-Class: com.labir.CertificateReader

Примечание : jar-файл – это архив, который можно открыть любым из архиваторов, например 7-zip. Кроме этого содержимое архива включает директорию META-INF с файлом манифеста MANIFEST.MF.

Утилита формирования подписи, jarsigner

После того, как jar файл подготовлен, переходим к подписи файла с использованием утилиты jarsigner, входящей в комплект JDK. Утилита jarsigner является инструментом для решения двух задач :

  • подпись архива java (файл jar);
  • проверка подписи файлов jar.

Цифровая подпись представляет строку битов, которая вычисляется из некоторых данных ("подписываемые" данные) с помощью закрытого ключа. Отличительные характеристики цифровой подписи :

  • подлинность цифровой подписи может быть проверена вычислением, которое использует открытый ключ, соответствующий закрытому ключу, используемому для генерирования подписи;
  • цифровая подпись не может быть подделана; предполагается, что закрытый ключ хранится в надежном месте;
  • подписанные данные не могут быть изменены; если это произойдет, то подпись не будет действительной.

Чтобы генерировать цифровые подписи jar файлов jarsigner использует ключ и сертификат хранилища, который является базой данных закрытых ключей, аутентифицирующих соответствующие открытые ключи. Для создания и администрирования хранилища используется утилита keytool.

Файл jar с цифровой подписью включает копию сертификата от хранилища для открытого ключа, соответствующего закрытому ключу, используемому для подписи файла. Утилита jarsigner проверяет цифровую подпись файла jar, используя размещенный внутри файла сертификат.

Команда подписи файла jar

При подписи файла утилите jarsigner в качестве опций необходимо указать хранилище ключей и сертификатов -keystore, пароль хранилища -storepass и пароль закрытого ключа -keypass, файл jar и псевдоним сертификата. Следующая команда сформирует цифровую подпись файла certificate-reader.jar и разместит ее внутри архива :


>"C:/Program Files/Java/jdk1.8.0_161/bin/"jarsigner.exe \
   -verbose -keystore keystore.jks -storepass mystorepass \
   -keypass mykeypass certificate-reader.jar codesigner

Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
 updating: META-INF/MANIFEST.MF
   adding: META-INF/CODESIGN.SF
   adding: META-INF/CODESIGN.RSA
   adding: com/
   adding: com/labir/
  signing: com/labir/CertificateReader$1.class
  signing: com/labir/CertificateReader$2.class
  signing: com/labir/CertificateReader.class
jar signed.

Warning:
No -tsa or -tsacert is provided and this jar is not timestamped. 
Without a timestamp, users may not be able to validate this jar 
after the signer certificate's expiration date (2019-04-16) or 
after any future revocation date.
 

При формировании цифровой подписи jarsigner вывел в консоль дополнительную информацию, связанную с указанием внесения изменений в файл META-INF/MANIFEST.MF и добавлением двух файлов (META-INF/CODESIGN.SF, META-INF/CODESIGN.RSA). Далее приводятся наименования файлов (классов), для которых сформированы подписи. Обратите внимание, что алиас сертификата 'codesigner' в наименованиях добавленных файлов сокращен до восьми символов CODESIGN.

MANIFEST.MF после подписи

После подписи jar файла содержимое манифеста изменилось добавлением 3-х дайджестов :

Manifest-Version: 1.0
Class-Path: .
Main-Class: com.labir.CertificateReader

Name: com/labir/CertificateReader$2.class
SHA-256-Digest: dMd0CV9qRF4KUznsnNFo1slW0g6U1zwRn3jM4PVjjw8=

Name: com/labir/CertificateReader$1.class
SHA-256-Digest: UhMt98YW190OqDiV1NExH4eCMZtzto0vjzEnPpME9rg=

Name: com/labir/CertificateReader.class
SHA-256-Digest: jtwnwiYnLa+qY3F2N5105qa+oWM/FNonBtBQtal+tx8=

CODESIGN.SF

Файл CODESIGN.SF содержит дайджесты манифеста и файлов архива jar.

Signature-Version: 1.0
SHA-256-Digest-Manifest-Main-Attributes: QjbCmYJbFmnmqyFWabrHW2Jd7kwYt
 GnzIHhXpBF4UWM=
SHA-256-Digest-Manifest: AfgfUrLzD7tMXju3Io/DU0iTeRMoCgybFeV/J4fVGO0=
Created-By: 1.8.0_161 (Oracle Corporation)

Name: com/labir/CertificateReader$2.class
SHA-256-Digest: neEYk0uhwtFOKOk/Vr3pGBDnzGn0qahBg8TupL4M7tI=

Name: com/labir/CertificateReader$1.class
SHA-256-Digest: BqfYq26UnpZjz9thlc+oDbuijR35VSiwnbnlVl+z/qM=

Name: com/labir/CertificateReader.class
SHA-256-Digest: C4vnZjuC2OYkg7qMAPjoMhdsLZJDq6YyHvrqHWWjThA=

CODESIGN.RSA

Файл CODESIGN.RSA содержит цифровой сертификат RSA, используемый в криптографии с открытым ключом.

Проверка подписи файла с использованием jarsigner

Чтобы выполнить проверку цифровой подписи jar файла с помощью утилиты jarsigner необходимо использовать опцию -verify. Следующая команда демонстрирует проверку «действительной» подписи файла certificate-reader.jar.


> "C:/Program Files/Java/jdk1.8.0_161/bin/"jarsigner.exe \
   -verify -verbose -certs certificate-reader.jar


Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)

s       400 Mon Apr 16 12:00:56 MSK 2018 META-INF/MANIFEST.MF

        X.509, CN=java-online.ru, OU=Developers,  \
               O=IT Systems Inc., L=Moscow, C=RF
       [certificate is valid from 16.04.18 12:00 \
                             to 16.04.19 12:00]
       [CertPath not validated: Path does not chain \
                       with any of the trust anchors]

         550 Mon Apr 16 12:00:56 MSK 2018 META-INF/CODESIGN.SF
        1352 Mon Apr 16 12:00:56 MSK 2018 META-INF/CODESIGN.RSA
           0 Mon Apr 16 11:07:32 MSK 2018 com/
           0 Mon Apr 16 11:07:32 MSK 2018 com/labir/

sm      765 Mon Apr 16 11:08:38 MSK 2018 \
                       com/labir/CertificateReader$1.class

      X.509, CN=java-online.ru, OU=Developers, O=IT Systems Inc., \
                                               L=Moscow, C=RF
      [certificate is valid from 16.04.18 12:00 to 16.04.19 12:00]
      [CertPath not validated: Path does not chain with any of the \
                                                      trust anchors]
sm      1424 Mon Apr 16 11:08:38 MSK 2018 \
                        com/labir/CertificateReader$2.class

      X.509, CN=java-online.ru, OU=Developers, O=IT Systems Inc., \
                                               L=Moscow, C=RF
      [certificate is valid from 16.04.18 12:00 to 16.04.19 12:00]
      [CertPath not validated: Path does not chain with any of the \
                                                      trust anchors]
sm      8177 Mon Apr 16 11:08:38 MSK 2018 \
                        com/labir/CertificateReader.class

      X.509, CN=java-online.ru, OU=Developers, O=IT Systems Inc., \
                                               L=Moscow, C=RF
      [certificate is valid from 16.04.18 12:00 to 16.04.19 12:00]
      [CertPath not validated: Path does not chain with any of the \
                                                      trust anchors]


  s = signature was verified
  m = entry is listed in manifest
  k = at least one certificate was found in keystore
  i = at least one certificate was found in identity scope

- Signed by "CN=java-online.ru, OU=Developers, O=IT Systems Inc., \
                                               L=Moscow, C=RF"
    Digest algorithm: SHA-256
    Signature algorithm: SHA256withRSA, 2048-bit key

jar verified.

Warning:
This jar contains entries whose certificate chain is not validated.
This jar contains signatures that does not include a timestamp. 
Without a timestamp, users may not be able to validate this jar 
after the signer certificate's expiration date (2019-04-16) or 
after any future revocation date.
   

В случае, если цифровая подпись окажется «недействительной», то можно увидеть следующее сообщение :


> "C:/Program Files/Java/jdk1.8.0_161/bin/"jarsigner.exe \
   -verify -verbose -certs certificate-reader.jar

Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
jarsigner: java.lang.SecurityException: invalid SHA-256 signature \
  file digest for com/labir/CertificateReader$1.class
   

Проверка «недействительной» подписи заканчивается вызовом исключения типа SecurityException.

Программная проверка подписи

Выполним проверку цифровой подписи файла программным способом. Для этого необходимо только прочитать файлы архива jar. Если один из файлов будет изменен и его подпись будет недействительной, то чтение завершится вызовом исключения SecurityException.

Следующий пример демонстрирует проверку подписанного файла.

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class SignVerify 
{
    private final  String jar_path = "D:/certificate-reader.jar";

    SignVerify () {
        try {
            JarFile jfile = new JarFile(jar_path);
            System.out.println("verify = " + jarVerify(jfile));
        } catch (IOException e) {}
    }
    private static boolean jarVerify(JarFile jar) 
                                    throws IOException {
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            System.out.println("entry.name = " + entry.getName());
            try {
                byte[] buffer = new byte[16384];
                InputStream is = jar.getInputStream(entry);
                while ((is.read(buffer,0,buffer.length)) != -1){
                    // Только чтение, которое может вызвать 
                    // SecurityException, если цифровая подпись
                    // нарушена.
                }
            } catch (SecurityException se) {
                System.err.println("SecurityException : " +
                                    se.getMessage());
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) throws IOException 
    {
        new SignVerify();
        System.exit(0);
    }
}

При чтении архивного файла в консоль выведена следующая информация :


entry.name = com/
entry.name = com/labir/
entry.name = com/labir/CertificateReader$1.class
entry.name = com/labir/CertificateReader$2.class
entry.name = com/labir/CertificateReader.class
entry.name = META-INF/CODESIGN.RSA
entry.name = META-INF/CODESIGN.SF
entry.name = META-INF/MANIFEST.MF
verify = true
 

Если подпись файла окажется недействительной, то в консоли будет выведено сообщение об ошибке. Чтобы убедиться в этом достаточно внести изменение в один из дайджестов манифеста и снова запустить пример.

В примере можно организовать дополнительную проверку, связанную, например, с сертификатом (файлом .RSA). Здесь все зависит от Вашего опыта и желания блокировать выполнение измененного приложения/библиотеки.

Скачать пример

Исходный код рассмотренного на странице примера подписи jar файла с использованием утилиты jarsigner в виде проекта IDE Eclipse можно скачать здесь (21.2 Kб).

  Рейтинг@Mail.ru