ru.i_novus.common.sign.soap.GostSoapSignature Maven / Gradle / Ivy
The newest version!
package ru.i_novus.common.sign.soap;
/*-
* -----------------------------------------------------------------
* common-sign-gost
* -----------------------------------------------------------------
* Copyright (C) 2018 - 2019 I-Novus LLC
* -----------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* -----------------------------------------------------------------
*/
import jakarta.xml.soap.SOAPElement;
import jakarta.xml.soap.SOAPException;
import jakarta.xml.soap.SOAPHeaderElement;
import jakarta.xml.soap.SOAPMessage;
import org.apache.commons.lang3.StringUtils;
import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import ru.i_novus.common.sign.api.SignAlgorithmType;
import ru.i_novus.common.sign.soap.dto.SecurityElementInfo;
import ru.i_novus.common.sign.util.CryptoFormatConverter;
import ru.i_novus.common.sign.util.CryptoUtil;
import ru.i_novus.common.sign.util.DomUtil;
import javax.xml.namespace.QName;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import static ru.i_novus.common.sign.util.Base64Util.getBase64EncodedString;
public class GostSoapSignature {
public static final String WSSE_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
public static final String WSU_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
public static final String DS_NS = "http://www.w3.org/2000/09/xmldsig#";
public static final String BASE64_ENCODING = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary";
public static final String X509_V3_TYPE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3";
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_INSTANT;
public static final String BODY_REFERENCE_ID = "body";
public static final String DIGEST_VALUE_LOCAL_NAME = "DigestValue";
public static final String CERT_ID_LOCAL_NAME = "CertId";
public static final String REFERENCE_LIST_XPATH = "//*[@wsu:Id[namespace-uri()='" + WSU_NS + "'] and not(local-name()='BinarySecurityToken') and not(local-name()='RelatesTo')]";
private GostSoapSignature() {
// не позволяет создать экземпляр класса, класс утилитный
}
public static void addSecurityElement(SecurityElementInfo securityElemInfo) throws SOAPException {
final String actor = securityElemInfo.getActor();
final SOAPMessage message = securityElemInfo.getMessage();
final SignAlgorithmType signAlgorithmType = securityElemInfo.getSignAlgorithmType();
// Добавляем элемент Security
SOAPElement securityElem;
if (StringUtils.isBlank(actor)) {
securityElem = message.getSOAPHeader().addChildElement("Security", "wsse");
} else {
securityElem = message.getSOAPHeader().addHeaderElement(new QName(WSSE_NS, "Security", "wsse"));
((SOAPHeaderElement) securityElem).setActor(actor);
}
final String x509ReferenceId ="X509-"+ UUID.randomUUID().toString();
final String encodedCertificate = CryptoFormatConverter.getInstance().getPEMEncodedCertificate(securityElemInfo.getCertificate());
// Добавляем элемент BinarySecurityToken
addBinarySecurityTokenElement(securityElem, x509ReferenceId, encodedCertificate);
// Добавляем элемент Signature
SOAPElement signature = securityElem.addChildElement("Signature", "ds");
signature.setAttribute("Id", "SIG-" + UUID.randomUUID().toString());
// Добавляем элемент SignedInfo
SOAPElement signedInfo = signature.addChildElement("SignedInfo", "ds");
// Добавляем элемент CanonicalizationMethod
signedInfo.addChildElement("CanonicalizationMethod", "ds")
.setAttribute("Algorithm", Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
// Добавляем элемент SignatureMethod
signedInfo.addChildElement("SignatureMethod", "ds")
.setAttribute("Algorithm", securityElemInfo.getSignAlgorithmType().getSignUri());
// Добавляем элемент Reference для //Body
addReferenceElement(signAlgorithmType, signedInfo, securityElemInfo.getBodyReferenceId());
final String timestampReferenceId = "TS-" + UUID.randomUUID().toString();
// Добавляем элемент Reference для //Timestamp
addReferenceElement(signAlgorithmType, signedInfo, timestampReferenceId);
// Добавляем элемент Reference для //MessageId
addReferenceElement(signAlgorithmType, signedInfo, securityElemInfo.getMessageIdReferenceId());
// Добавляем элемент Reference для //ReplyTo
addReferenceElement(signAlgorithmType, signedInfo, securityElemInfo.getReplyToReferenceId());
// Добавляем элемент Reference для //To
addReferenceElement(signAlgorithmType, signedInfo, securityElemInfo.getToReferenceId());
// Добавляем элемент Reference для //Action
addReferenceElement(signAlgorithmType, signedInfo, securityElemInfo.getActionReferenceId());
// Добавляем элемент SignatureValue (значение ЭЦП считаем позже)
signature.addChildElement("SignatureValue", "ds");
// Добавляем элементы KeyInfo, SecurityTokenReference и Reference
addKeyInfoElementWithId(signature, x509ReferenceId);
//Добавляем элемент Timestamp
addTimestampElement(securityElem, securityElemInfo.getExpireDateTime(), timestampReferenceId);
}
public static void addSecurityElement(SOAPMessage message, String encodedCertificate, String actor, SignAlgorithmType signAlgorithmType) throws SOAPException {
// Добавляем элемент Security
SOAPElement security;
if (StringUtils.isBlank(actor)) {
security = message.getSOAPHeader().addChildElement("Security", "wsse");
} else {
security = message.getSOAPHeader().addHeaderElement(new QName(WSSE_NS, "Security", "wsse"));
((SOAPHeaderElement) security).setActor(actor);
}
// Добавляем элемент Signature
SOAPElement signature = security.addChildElement("Signature", "ds");
// Добавляем элемент SignedInfo
SOAPElement signedInfo = signature.addChildElement("SignedInfo", "ds");
// Добавляем элемент CanonicalizationMethod
signedInfo.addChildElement("CanonicalizationMethod", "ds")
.setAttribute("Algorithm", Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
// Добавляем элемент SignatureMethod
signedInfo.addChildElement("SignatureMethod", "ds")
.setAttribute("Algorithm", signAlgorithmType.getSignUri());
// Добавляем элемент Reference
addReferenceElement(signAlgorithmType, signedInfo, BODY_REFERENCE_ID);
// Добавляем элемент SignatureValue (значение ЭЦП считаем позже)
signature.addChildElement("SignatureValue", "ds");
// Добавляем элементы KeyInfo, SecurityTokenReference и Reference
addKeyInfoElement(signature, CERT_ID_LOCAL_NAME);
// Добавляем элемент BinarySecurityToken
addBinarySecurityTokenElement(security, CERT_ID_LOCAL_NAME, encodedCertificate);
}
public static void addSecurityElement(SOAPMessage message, X509Certificate certificate, String actor)
throws SOAPException {
addSecurityElement(message, CryptoFormatConverter.getInstance().getPEMEncodedCertificate(certificate), actor, SignAlgorithmType.findByCertificate(certificate));
}
public static void sign(SOAPMessage message, String encodedPrivateKey, SignAlgorithmType signAlgorithmType) throws IOException,
SOAPException, TransformerException, InvalidCanonicalizerException, CanonicalizationException, GeneralSecurityException {
PrivateKey privateKey = CryptoFormatConverter.getInstance().getPKFromPEMEncoded(signAlgorithmType, encodedPrivateKey);
sign(message, privateKey, signAlgorithmType);
}
public static void sign(SOAPMessage message, PrivateKey privateKey, SignAlgorithmType signAlgorithmType) throws IOException,
SOAPException, TransformerException, InvalidCanonicalizerException, CanonicalizationException, GeneralSecurityException {
// Сохраняем изменения
message.saveChanges();
// Делаем такое преобразование, чтобы не поломался в последующем хэш для Body
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
message.writeTo(outputStream);
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray())) {
message.getSOAPPart().setContent(new StreamSource(inputStream));
}
}
NodeList referenceNodeList = XPathAPI.selectNodeList(message.getSOAPHeader(), REFERENCE_LIST_XPATH);
try (ByteArrayOutputStream tempBuffer = new ByteArrayOutputStream()) {
for (Node node : DomUtil.iterable(referenceNodeList)) {
addDigestValue(message, signAlgorithmType, tempBuffer, node);
}
tempBuffer.reset();
signSignedInfo(message, privateKey, signAlgorithmType, tempBuffer);
}
}
private static void signSignedInfo(SOAPMessage message, PrivateKey privateKey, SignAlgorithmType signAlgorithmType, ByteArrayOutputStream tempBuffer) throws CanonicalizationException, InvalidCanonicalizerException, TransformerException, SOAPException, GeneralSecurityException {
Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS)
.canonicalizeSubtree(XPathAPI.selectSingleNode(message.getSOAPHeader(),
"//*[local-name()='SignedInfo']"), tempBuffer);
byte[] signature = CryptoUtil.getSignature(tempBuffer.toByteArray(), privateKey, signAlgorithmType);
((SOAPElement) XPathAPI.selectSingleNode(message.getSOAPHeader(), "//*[local-name()='SignatureValue']"))
.addTextNode(getBase64EncodedString(signature));
}
private static void addDigestValue(SOAPMessage message, SignAlgorithmType signAlgorithmType, ByteArrayOutputStream tempBuffer, Node node) throws TransformerException, SOAPException, CanonicalizationException, InvalidCanonicalizerException {
final String id = node.getAttributes().getNamedItem("wsu:Id").getNodeValue();
Node referenceNode = XPathAPI.selectSingleNode(message.getSOAPHeader(), "//*[local-name()='Reference' and @URI='#" + id + "']");
if (referenceNode != null) {
Node digestValueNode = referenceNode.getLastChild();
if (digestValueNode != null && DIGEST_VALUE_LOCAL_NAME.equals(digestValueNode.getLocalName())) {
tempBuffer.reset();
Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS).canonicalizeSubtree(node, tempBuffer);
final String digestValue = CryptoUtil.getBase64Digest(new String(tempBuffer.toByteArray()), signAlgorithmType);
((SOAPElement) digestValueNode).addTextNode(digestValue);
}
}
}
private static void addReferenceElement(SignAlgorithmType signAlgorithmType, SOAPElement signedInfo, final String referenceURI) throws SOAPException {
// Добавляем элемент Reference для body
SOAPElement referenceElem = signedInfo.addChildElement("Reference", "ds")
.addAttribute(new QName("URI"), "#" + referenceURI);
// Добавляем элементы Transforms и Transform
referenceElem.addChildElement("Transforms", "ds")
.addChildElement("Transform", "ds")
.setAttribute("Algorithm", Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
// Добавляем элемент DigestMethod
referenceElem.addChildElement("DigestMethod", "ds")
.setAttribute("Algorithm", signAlgorithmType.getDigestUri());
// Добавляем элемент DigestValue (значение хэша считаем позже)
referenceElem.addChildElement("DigestValue", "ds");
}
private static void addTimestampElement(SOAPElement securityElem, final ZonedDateTime expireDateTime, final String timestampReferenceId) throws SOAPException {
SOAPElement timestampElem = securityElem.addChildElement(new QName(WSU_NS, "Timestamp", "wsu"));
timestampElem.setAttribute("wsu:Id", timestampReferenceId);
timestampElem.addChildElement(new QName(WSU_NS, "Created", "wsu"))
.setTextContent(ZonedDateTime.now().format(DATE_TIME_FORMATTER));
timestampElem.addChildElement(new QName(WSU_NS, "Expires", "wsu"))
.setTextContent(expireDateTime.format(DATE_TIME_FORMATTER));
}
private static void addBinarySecurityTokenElement(SOAPElement security, final String x509ReferenceId, final String encodedCertificate) throws SOAPException {
security.addChildElement("BinarySecurityToken", "wsse")
.addAttribute(new QName("EncodingType"), BASE64_ENCODING)
.addAttribute(new QName("ValueType"), X509_V3_TYPE)
.addAttribute(new QName("wsu:Id"), x509ReferenceId)
.addTextNode(encodedCertificate);
}
private static void addKeyInfoElement(SOAPElement signature, final String x509ReferenceId) throws SOAPException {
signature.addChildElement("KeyInfo", "ds")
.addChildElement("SecurityTokenReference", "wsse")
.addChildElement("Reference", "wsse")
.addAttribute(new QName("URI"), "#" + x509ReferenceId)
.addAttribute(new QName("ValueType"), X509_V3_TYPE);
}
private static void addKeyInfoElementWithId(SOAPElement signature, final String x509ReferenceId) throws SOAPException {
SOAPElement keyInfoElem = signature.addChildElement("KeyInfo", "ds");
keyInfoElem.setAttribute("Id", "KI-" + UUID.randomUUID().toString());
SOAPElement securityTokenReferenceElem = keyInfoElem.addChildElement("SecurityTokenReference", "wsse");
securityTokenReferenceElem.setAttribute("wsu:Id", "STR-" + UUID.randomUUID().toString());
securityTokenReferenceElem.addChildElement("Reference", "wsse")
.addAttribute(new QName("URI"), "#" + x509ReferenceId)
.addAttribute(new QName("ValueType"), X509_V3_TYPE);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy