Подтвердить что ты не робот

Подпись XML не проверяется при явном добавлении элемента в документ (JAVA)?

Я создаю следующий XML-документ, используя JAXB данного XSD, который предоставляется третьей стороной. Третий сторонний запрос подписать документ и добавить к нему дополнительный элемент, который содержит подпись. Использование JDK 1.7.

Ниже приведен пример кода для сортировки:

JAXBContext jaxbContext = JAXBContext.newInstance(DataPDU.class);
DataPDU myDataPDU = new DataPDU();
myDataPDU.setRevision("2.0.6");

// marshall the file
Marshaller marshaller = jaxbContext.createMarshaller();

DOMResult domResult = new DOMResult();
marshaller.marshal(myDataPDU, domResult);

// get the document list
Document document = (Document) domResult.getNode();

Затем я создаю элемент (LAU), как показано ниже, и подписываю документ с использованием алгоритма HMAC-SHA256 и JAVA API JSR105 (я не собираюсь включать весь код подписи для уменьшения детализации, я использую стандартное поведение класс XMLSignature, а затем преобразование документа в выходной поток файла из DOMSource с использованием XML-трансформаторов):

Element LAUElement = document.createElementNS("urn:swift:saa:xsd:saa.2.0", "Saa:LAU");
Element rootElement = document.getDocumentElement();
rootElement.appendChild(LAUElement);

// sign the document
XMLSignatureUtil.sign(document, secret, LAUElement, "ds");
// create the output file
TransformerUtil.transformDocumentToFile(document, "resultingFile.xml");

XML правильно подписывается, но при проверке вычисляемое значение дайджест отличается от значения дайджеста.

Я заметил, что при изменении значения пространства имен при создании элемента LAU дайджест никогда не меняется, как если бы документ подписывался и пренебрегал пространством имен элемента LAU, и я думаю, что это причина его неудачи, Любые другие изменения в документе в целом или изменение префикса элемента LAU напрямую влияют на вычисленный дайджест полезной нагрузки.

Если я добавляю подпись к корневому элементу напрямую, а не создавая элемент LAU, проверка корректно работает.

Элемент LAU существует в XSD и может быть создан с использованием JAXB, но проблема в том, что я не могу найти способ назначить префикс (только для него в документе) того же пространства имен, что и корневой элемент.

Вопросы:

Является ли пространство имен фактически опущено из дайджест полезной нагрузки вычисление при добавлении элемента в документ с помощью createElementNS и appendChild?

Есть ли способ предоставить через JAXB префикс для того же корня пространство имен только для одного элемента?

Как я могу найти фактическую строку XML, подписанную API, я попробовал прочитать исходный поток ввода после включения javax.xml.crypto.dsig.cacheReference, но это не сработало при подписании, оно работало только при проверке?

Ниже приведен пример XML:

<DataPDU xmlns="urn:swift:saa:xsd:saa.2.0">
  <Revision>2.0.6</Revision>
  <Saa:LAU xmlns:Saa="urn:swift:saa:xsd:saa.2.0"> Signature lies here </Saa:LAU>
</DataPDU>

Обновление - полный процесс подписи XML

JAXBContext jaxbContext = JAXBContext.newInstance(DataPDU.class);
DataPDU myDataPDU = new DataPDU();
myDataPDU.setRevision("2.0.6");

// marshall the file
Marshaller marshaller = jaxbContext.createMarshaller();

DOMResult domResult = new DOMResult();
marshaller.marshal(myDataPDU, domResult);

// get the document list
Document document = (Document) domResult.getNode();

// signing process
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");

SignatureMethod signatureMethod =
    factory.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256", null);

CanonicalizationMethod canonicalizationMethod =
    factory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (XMLStructure) null);

List<Transform> transforms = new ArrayList<Transform>();
transforms.add(factory.newTransform(Transform.ENVELOPED, (XMLStructure) null));
transforms.add(factory.newTransform("http://www.w3.org/2001/10/xml-exc-c14n#", (XMLStructure) null));

DigestMethod digestMethod = factory.newDigestMethod("http://www.w3.org/2001/04/xmlenc#sha256", null);

Reference reference = factory.newReference("", digestMethod, transforms, null, null);

SignedInfo signedInfo =
    factory.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(reference));

String secretKey = "Abcd1234abcd1234Abcd1234abcd1234";
SecretKeySpec secret_key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");

Element LAUElement = document.createElementNS("urn:swift:saa:xsd:saa.2.0", "Saa:LAU");
Element rootElement = document.getDocumentElement();
rootElement.appendChild(LAUElement);

DOMSignContext domSignContext = new DOMSignContext(secret_key, LAUElement);
domSignContext.setDefaultNamespacePrefix("ds");

XMLSignature signature = factory.newXMLSignature(signedInfo, null);
signature.sign(domSignContext);
4b9b3361

Ответ 1

Как я могу найти фактическую строку XML, подписанную API, я попытался прочитать поток входных данных после включения javax.xml.crypto.dsig.cacheReference, но это не сработало при подписании, оно работало только при проверке?

К счастью, реализация по умолчанию, предполагая, что используется, предлагает некоторые возможности отладки. Здесь приведен пример настроек для входа в консоль с использованием детализации FINEST, хотя FINE кажется достаточно.

handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = FINER
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
org.jcp.xml.dsig.internal.level = FINER
com.sun.org.apache.xml.internal.security.level = FINER

Вы можете сохранить это как файл logging.properties и указать свой путь через опцию командной строки -Djava.util.logging.config.file, хотя это, возможно, также возможно сделать программным путем.

Таким образом, на самом деле будет отображаться подписанный канонизированный XML-код, который отвечает на это:

Является ли пространство имен фактически опущено из расчета дайджеста полезной нагрузки при добавлении элемента в документ с помощью createElementNS и appendChild?

В самом деле, это так. Запустив код, я увидел это в протоколе как канонизированный XML для подписания.

<DataPDU xmlns="urn:swift:saa:xsd:saa.2.0"><Revision>2.0.6</Revision><Saa:LAU></Saa:LAU></DataPDU>

Обратите внимание, что существует префикс Saa, но декларация пространства имен отсутствует. Тем не менее, информация находится там, потому что после преобразования в результате появляется объявление с префиксом. Это объясняет, почему изменения в URI пространства имен не приводят к другому дайджесту, но изменение префикса. Не знаю, почему это происходит. Похоже, что этого не должно быть, но правила для того, что включено в каноническую версию, настолько запутывают в спецификациях, что либо я что-то упускаю, либо это ошибка в реализации. Обход для этого заключается в добавлении этой строки после создания LAUElement:

 LAUElement.setAttribute("xmlns:Saa", "urn:swift:saa:xsd:saa.2.0");

Я подумал, что DOMResult использовал создатель документа, который не был настроен на использование пространства имен, но я пробовал это без разницы:

DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = domFactory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
DOMResult domResult = new DOMResult(doc);
marshaller.marshal(myDataPDU, domResult);

Это может быть ошибка самого DOMResult. Обходной путь немного испорчен, но он выполняет эту работу. Это фактически меняет дайджест. Я пробовал это, предоставив как LAUElement, так и корень документа как вход для DOMSignContext, который в обоих случаях приводит к одной и той же сигнатуре (дайджесту и значению), хотя в последнем случае подпись добавляется к root, а не элемент.

Это также учит нас, что весь документ используется как вход для подписания, даже когда вы поставляете элемент и используете метод исключительной канонизации. Он изменяется только там, где размещена подпись. Решение URI находит предков до корневого элемента и всего под ним. Это немного меня удивило.

Выполняя вышеуказанное, мне удалось правильно проверить подпись, используя приведенный ниже код:

XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");

NodeList sigList = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (sigList.getLength() == 0) {
    throw new Exception("Cannot find Signature element");
}
String secretKey = "Abcd1234abcd1234Abcd1234abcd1234";
SecretKeySpec secret_key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
DOMValidateContext valContext = new DOMValidateContext(secret_key, sigList.item(0));
XMLSignature signature = factory.unmarshalXMLSignature(valContext);

System.out.println("Core validity: " + signature.validate(valContext));

Кажется, что проблема заключалась в том, что при отмене объявления пространства имен сгенерированный дайджест отличался от того, который был сгенерирован при проверке.

Есть ли способ предоставить через JAXB префикс для одного и того же корневого пространства имен только для одного элемента?

Короткий ответ: нет тривиального способа сделать это. Длинный ответ можно найти здесь: fooobar.com/info/419715/...

EDIT:

Дополнительная заметка. Будьте осторожны с трансформатором для вывода подписанного документа в файл. Если он установил для отступов вывод, в результате файл фактически имел бы другой дайджест. Там пробелы, которые можно игнорировать (например, в <empty /> vs <empty/>), но внутри элементов, которые обычно рассматриваются как текстовые узлы и часть канонизированной версии, по которой вычисляется дайджест.