Компания, в которой я работаю, распространяет часть своего программного обеспечения в виде подписанных файлов jar Java. Существует требование, что файлы jar должны быть «проверяемыми» на Java 6. Существует механизм, который считывает записи jar и дает сбой, когда подпись неверна или отсутствует для какой-либо записи. Код (упрощенный):
import java.io.*;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.jar.*;
public final class JarCheckSimple {
public static void main(String[] args) throws IOException {
check(new File("SuperAwesome.jar"));
}
private static void check(File file) throws IOException {
JarFile jar = new JarFile(file);
Manifest manifest = jar.getManifest();
if (manifest == null) {
throw new SecurityException("jar not signed " + jar);
}
byte[] buffer = new byte[8096];
Enumeration entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry e = (JarEntry) entries.nextElement();
if (!e.isDirectory() && !e.getName().startsWith("META-INF")) {
InputStream is = jar.getInputStream(e);
while (is.read(buffer, 0, buffer.length) != -1) { /* to be verified by VerifierStream */ }
is.close();
Certificate[] certs = e.getCertificates();
if (certs == null || certs.length == 0) { /* Works on Java 8, fails on Java 6: (certs == null) is true there */
throw new SecurityException("unsigned entry " + e.getName());
}
}
}
}
}
Недавно наш отдел разработки релизов попытался изменить процесс подписи, чтобы включить поддержку меток времени подписи (эта вещь: https://docs.oracle.com/javase/7/docs/technotes/guides/security/time-of-signing.html),, и с тех пор проверка выше стала терпеть неудачу на Java 6 (только) - мой вопрос в конечном счете) будет поэтому, но сначала я должен дать некоторые подробности. Release Engineering утверждает, что они используют все те же сертификаты и все, единственное изменение - они добавили "-tsa http://sha256timestamp.ws.symantec.com/sha256/timestamp" к команде jarsigner, которую они имеют.
Я установил параметр -Djava.security.debug=jar
для приведенного выше кода и получил эту разницу в выводе (для файлов JAR, подписанных с отметкой времени и без нее, проверено в Java 6 и Java 8):
Java 6, без отметки времени (CERTIFICATE.RSA здесь меньше 4 КБ):
jar: beginEntry META-INF/MANIFEST.MF
jar: done with meta!
jar: nothing to verify!
jar: beginEntry META-INF/MANIFEST.MF
jar: beginEntry META-INF/CERTIFICATE.SF
jar: processEntry: processing block
jar: beginEntry META-INF/CERTIFICATE.RSA
jar: processEntry: processing block
jar: Signature Block Certificate: [
[
Version: V3
Subject: CN=SuperCompany Ltd, OU=IT, O=SuperCompany Ltd, L=Rwanda, C=CH
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 2048 bits
modulus: ...
public exponent: 65537
Validity: [From: Tue May 29 02:00:00 CEST 2018,
To: Sun Jun 28 01:59:59 CEST 2020]
Issuer: CN=thawte SHA256 Code Signing CA, O="thawte, Inc.", C=US
SerialNumber: [ ...]
Certificate Extensions: 8
...
Похоже, сертификат найден и проанализирован.
Java 8, с отметкой времени (CERTIFICATE.RSA здесь превышает 7 КБ):
jar: beginEntry META-INF/MANIFEST.MF
jar: done with meta!
jar: nothing to verify!
jar: beginEntry META-INF/MANIFEST.MF
jar: beginEntry META-INF/CERTIFICATE.SF
jar: processEntry: processing block
jar: beginEntry META-INF/CERTIFICATE.RSA
jar: processEntry: processing block
jar: Unsupported signer attribute: 1.2.840.113549.1.9.16.2.47
jar:
jar: Detected signature timestamp (#885515941755655847907684100465949331954643356622) generated on Tue Jun 26 14:11:42 CEST 2018
jar:
jar: Signature Block Certificate: [
[
Version: V3
Subject: CN=SuperCompany Ltd, OU=IT, O=SuperCompany Ltd, L=Rwanda, C=CH
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 2048 bits
modulus: ...
public exponent: 65537
Validity: [From: Tue May 29 02:00:00 CEST 2018,
To: Sun Jun 28 01:59:59 CEST 2020]
Issuer: CN=thawte SHA256 Code Signing CA, O="thawte, Inc.", C=US
SerialNumber: [ ...]
Certificate Extensions: 8
...
Обратите внимание на «предупреждение» о «Неподдерживаемый атрибут подписавшего: 1.2.840.113549.1.9.16.2.47»; но все равно сертификат разбирается нормально.
Теперь проблема: Java 6 с меткой времени (CERTIFICATE.RSA здесь превышает 7 КБ):
jar: beginEntry META-INF/MANIFEST.MF
jar: beginEntry META-INF/CERTIFICATE.SF
jar: processEntry: processing block
jar: beginEntry META-INF/CERTIFICATE.RSA
jar: processEntry: processing block
jar: ignoring unsupported signer attribute: 1.2.840.113549.1.9.16.2.47
jar: processEntry caught: sun.security.pkcs.ParsingException: Unable to parse the encoded bytes
jar: done with meta!
jar: nothing to verify!
...
Итак, Java не может проанализировать какой-то блок, поэтому он обрабатывает весь jar как беззнаковый, поэтому наша проверка (см. Выше) завершается неудачно: JarEntry :: getCertificates () возвращает null.
Для полноты: запуск "jarsigner -verify -verbose -certs SuperAwesome.jar" печатает все нормально для jarsigner из JDK 8 и печатает для каждой записи jar, что она просто "упоминалась" (не подписана) для JDK 6.
Отладка с некоторым приложенным исходным кодом OpenJDK, я обнаружил, что именно поэтому Java 6 терпит неудачу (sun.security.pkcs.PKCS9Attribute#PKCS9Attribute(sun.security.util.DerValue)
):
...
// get the oid
ObjectIdentifier oid = val[0].getOID();
index = indexOf(oid, PKCS9_OIDS, 1);
if (index == -1) {
if (debug != null) {
debug.println("ignoring unsupported signer attribute: " + oid);
}
throw new ParsingException("Unsupported PKCS9 attribute: " + oid);
}
...
Пока Java 8 проходит нормально:
...
// get the oid
oid = val[0].getOID();
byte[] content = val[1].toByteArray();
DerValue[] elems = new DerInputStream(content).getSet(1);
index = indexOf(oid, PKCS9_OIDS, 1);
if (index == -1) {
if (debug != null) {
debug.println("Unsupported signer attribute: " + oid);
}
value = content;
return;
}
...
Кроме того, вся логика в sun.security.pkcs.PKCS9Attribute
готова для this.index == -1, в отличие от версии Java 6. Я даже могу изменить этот класс, чтобы он больше походил на JDK 8, и тогда тест проходит успешно; проблема в том, что я не могу контролировать JDK пользователя.
Похоже, что моя проблема могла быть решена, если бы в сертификате не было этого oid "1.2.840.113549.1.9.16.2.47" (единственная информация, которую я мог найти на нем: id-aa- signingCertificateV2 ). Должен признаться, я ничего не знаю обо всем процессе подписания (как в принципе, так и о том, как это делается нашей Release Engineering - кажется, они намеренно скрывают это), сертификатах и т. Д.
Теперь мои вопросы:
- Прав ли я, что самый простой способ исправить это - избавиться от этого атрибута "1.2.840.113549.1.9.16.2.47"?
- Можно ли сказать, имея предоставленную мною информацию, что вводит ее в итоговый сертификат, который попадает в файл jar? (Это CERTIFICATE.RSA)
- Если нет, не могли бы вы намекнуть, какими могут быть мои дальнейшие шаги в попытке отследить или устранить проблему? Я застрял.
Я в Windows 7, использую обновление Oracle JDK 6 45 и обновление Oracle JDK 8 20.
Заранее спасибо.