Что мешает Java проверять подписанные банки с несколькими сигнатурными алгоритмами
Быстрый исход: Мы публикуем приложение webstart, в которое входят наши собственные банки приложений и многочисленные сторонние банки. Webstart требует, чтобы все распределенные банки, на которые ссылается файл jnlp, подписывались одним сертификатом. Поэтому мы подписываем все банки (наши банки и сторонние банки) с использованием самозаверяющего сертификата. Некоторые сторонние банки уже подписаны стороной, которая их создала, но мы просто подписываем их снова, и это работает нормально. До сих пор.
Проблема: Недавно мы перешли с Java 6 на Java 7, и вдруг webstart отказывается загружать некоторые баночки, жалуясь: "Недопустимый дайджест файла подписи SHA1". Это происходит только для некоторых банок, а не для других, и среди тех банок, которые не работают, появляется общий поток.
После поиска по S.O. и Интернет, похоже, что алгоритм подписи по умолчанию для Java jarsigner изменился между Java 6 и Java 7, от SHA1 до SHA256, и различные люди рекомендуют использовать "jarsigner -digestalg SHA1" для решения проблем проверки. Я пробовал это, и, безусловно, наши многократные подписанные банки теперь проверяют. Таким образом, это, как представляется, является обходным путем для нашей проблемы.
Из того, что я могу собрать, похоже, что сигнатура сторонней стороны является сигнатурой SHA1, и мы подписывались с по умолчанию - SHA256, что приводило к смешению подписей. Когда я нажимаю SHA1 с помощью переключателя '-digestalg', у нас есть две подписи того же типа, и проверка теперь работает. Похоже, проблема связана с наличием нескольких подписей с разными алгоритмами? Или есть какой-то другой фактор, который мне не хватает.
Вопросы:
- Почему это не удается проверить с помощью SHA1 + SHA256, но проверяет с помощью SHA1 + SHA1? Есть ли техническая причина? Причина политики безопасности? Почему он не может проверить правильность обеих подписей?
- Есть ли у нас недостаток использования (продолжение использования) SHA1 вместо текущего SHA256 по умолчанию?
Ответы
Ответ 1
Вместо того, чтобы повторно подписывать сторонние банки самостоятельно, вы можете создать отдельный JNLP файл для каждого стороннего подписчика, который ссылается на соответствующие файлы jar, а затем ваш основной JNLP зависит от них с помощью элемента <extension>
. Ограничение того, что все файлы JAR должны быть подписаны одним и тем же подписывающим агентом, применяется только в одном JNLP, каждое расширение может иметь другой подписант.
В противном случае вы можете лишить сторонние подписи перед добавлением собственных (путем их переупаковки без META-INF/*.{SF,DSA,RSA}
)
Ответ 2
Я знаю, что это немного поздно, но мы сейчас это сделаем. Наша проблема заключалась в выпуске подписи "MD2withRSA". Я решил проблему в пару шагов:
1) Работала с Verisign для удаления "старого" алгоритма из нашего сертификата - поэтому алгоритм MD2withRSA больше не использовался для подписи наших банок.
2) У нас также есть куча сторонних банках, и мы просто переписываем их без нашего сертификата. Мы встретили "не все банки, подписанные с тем же сертификатом", когда оба алгоритма SHA1 и SHA-256 были перечислены в MANIFEST.MF. Это было всего лишь небольшое подмножество банок - поэтому для них мы удалили нижнюю половину файла MANIFEST.MF; эта часть с классом Name: и спецификацией алгоритма. Эти данные повторно генерируются в последней части нашего процесса. Мы разархивируем, исключаем старую подпись и повторную банку. Последний шаг заключается в повторной подписке на банки. Мы обнаружили, что в некоторых случаях, если старая запись Name: с записью SHA1 была в MANIFEST.MF, что подпись не заменила ее SHA-256, поэтому мы вручную обрабатываем эти банки (на данный момент). Работая над обновлением наших задач Ant, чтобы справиться с этим.
Извините - не могу сказать, почему веб-старт не обрабатывает/не разрешает его - просто понял, как заставить его работать!
Удачи!
Ответ 3
Похоже на ошибку в JRE. Лично я предполагаю, что старый алгоритм подписи по умолчанию (DSA с SHA1 digest) менее безопасен, чем новый (RSA с SHA256 digest), поэтому лучше не использовать параметр "-digestalg SHA1".
Я решил эту проблему, используя специальную задачу Ant в моей сборке script для "unsign" моих банок перед их подписанием. Таким образом, для каждой банки есть только одна подпись.
Здесь моя задача Ant:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.resources.FileProvider;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.ResourceUtils;
public class UnsignJar extends Task {
protected List<FileSet> filesets = new ArrayList<FileSet>();
protected File todir;
public void addFileset(final FileSet set) {
filesets.add(set);
}
public void setTodir(File todir) {
this.todir = todir;
}
@Override
public void execute() throws BuildException {
if (todir == null) {
throw new BuildException("todir attribute not specified");
}
if (filesets.isEmpty()) {
throw new BuildException("no fileset specified");
}
Path path = new Path(getProject());
for (FileSet fset : filesets) {
path.addFileset(fset);
}
for (Resource r : path) {
FileResource from = ResourceUtils.asFileResource(r
.as(FileProvider.class));
File destFile = new File(todir, from.getName());
File fromFile = from.getFile();
if (!isUpToDate(destFile, fromFile)) {
unsign(destFile, fromFile);
}
}
}
private void unsign(File destFile, File fromFile) {
log("Unsigning " + fromFile);
try {
ZipInputStream zin = new ZipInputStream(
new FileInputStream(fromFile));
ZipOutputStream zout = new ZipOutputStream(
new FileOutputStream(destFile));
ZipEntry entry = zin.getNextEntry();
while (entry != null) {
if (!entry.getName().startsWith("META-INF")) {
copyEntry(zin, zout, entry);
}
zin.closeEntry();
entry = zin.getNextEntry();
}
zin.close();
zout.close();
} catch (IOException e) {
throw new BuildException(e);
}
}
private void copyEntry(ZipInputStream zin, ZipOutputStream zout,
ZipEntry entry) throws IOException {
zout.putNextEntry(entry);
byte[] buffer = new byte[1024 * 16];
int byteCount = zin.read(buffer);
while (byteCount != -1) {
zout.write(buffer, 0, byteCount);
byteCount = zin.read(buffer);
}
zout.closeEntry();
}
private boolean isUpToDate(File destFile, File fromFile) {
return FileUtils.getFileUtils().isUpToDate(fromFile, destFile);
}
}