前提:我需要创建一个使用 WS-Security 标准的 SOAP 服务来执行标题中描述的操作,以便 SOAP 客户端可以使用此服务。
基本上,它涉及在发送/接收的信封的soap:标头中发送证书(.cer 文件)。然而,我在实现这一目标的过程中遇到了一些困难。根据我所看到的,我只有两个选择:
这些方法对我来说都不起作用。
在我的 chat-gpt/gemini 提出许多问题和微调之后,我得到的只是带有虚构方法或不能满足我需要的方法的代码。我阅读了库的文档,这些方法没有解释从哪里检索值或如何计算它们。
因此,我遵循可用信息,进行反复试验。我传递了一个 crypto.properties 文件以及一个 .p12 文件,其中包含我的 .cer 和 .key 以及有效的密码和别名。我也尝试过使用 .jks 文件,但在这两种情况下,我都会收到“无法识别的字符 111”之类的错误,或者库未正确初始化。
总的来说,我使用 p12 和 jks 格式以及 crypto.properties 文件尝试了类中的所有方法,但服务要么没有启动,要么无法签名,而是向我的服务器发送了带有空正文的 202 响应。终点。
结果? 由于上述错误,服务甚至无法启动。
如果您查看我正在使用的两个参考,您会发现需要考虑一些属性,例如 ID,对吧?
结果? 当客户端验证我的响应时,他们会收到“签名无效”异常。我尝试使用自签名证书、CA 颁发的证书和不带特殊字符的证书,但这些变化都没有改变结果。我们还验证了他们这边的 .cer 与我正在使用的匹配。
备注:
尝试根据问题中给出的示例手动创建标签,这应该可以解决问题,但事实并非如此
public class ServerHeaderInterceptor implements SoapEndpointInterceptor {
@Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
SoapMessage soapMessage = (SoapMessage) messageContext.getRequest();
SoapHeader soapHeader = soapMessage.getSoapHeader();
Iterator<SoapHeaderElement> headerElements = soapHeader.examineAllHeaderElements();
while (headerElements.hasNext()) {
SoapHeaderElement headerElement = headerElements.next();
if (headerElement.getName().getLocalPart().equals("Security")) {
Node securityNode = ((DOMSource) headerElement.getSource()).getNode();
Element binarySecurityTokenElement = findBinarySecurityToken(securityNode);
if (binarySecurityTokenElement != null) {
String binarySecurityTokenValue = binarySecurityTokenElement.getTextContent();
RequestContext.put("security.signature.binarytoken", binarySecurityTokenValue);
byte encodedCert[] = Base64.decodeBase64(binarySecurityTokenValue);
ByteArrayInputStream inputStream = new ByteArrayInputStream(encodedCert);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(inputStream);
return isValidClientCertificate(cert);
}
}
}
return false;
}
private Element findNode(Node parentNode, String tag) {
if (parentNode.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) parentNode;
if (element.getLocalName().equals(tag)) {
return element;
}
}
NodeList childNodes = parentNode.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
Element foundElement = findNode(childNode, tag);
if (foundElement != null) {
return foundElement;
}
}
}
return null;
}
private Element findBinarySecurityToken(Node parentNode) {
if (parentNode.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) parentNode;
if (element.getLocalName().equals("BinarySecurityToken")) {
return element;
}
}
NodeList childNodes = parentNode.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
Element foundElement = findBinarySecurityToken(childNode);
if (foundElement != null) {
return foundElement;
}
}
}
return null;
}
private boolean isValidClientCertificate(X509Certificate certificate) {
try {
certificate.checkValidity();
String isuerDN = certificate.getSubjectX500Principal().getName().replace("\\", "");
String expectedIssuerDN = "stuff";
boolean isIssuerDN = isuerDN.equals(expectedIssuerDN);
PublicKey publicKey = certificate.getPublicKey();
// byte[] signature = certificate.getSignature();
Signature verifier = Signature.getInstance(certificate.getSigAlgName());
verifier.initVerify(publicKey);
verifier.update(certificate.getTBSCertificate());
// boolean isSignatureValid = verifier.verify(signature);
boolean[] keyUsage = certificate.getKeyUsage();
if (keyUsage != null) {
if (keyUsage[0]) {
System.out.println("allows.");
}
if (keyUsage[1]) {
System.out.println("no allows.");
}
} else {
System.out.println("no key.");
}
return isIssuerDN; // && isSignatureValid;
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
return false;
}
catch (NoSuchAlgorithmException | InvalidKeyException | CertificateEncodingException | SignatureException e) {
return false;
}
}
@SuppressWarnings("deprecation")
@Override
public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
X509Certificate cert = getCertificate();
String certBase64 = Base64.encodeBase64String(cert.getEncoded());
SoapMessage message = (SoapMessage) messageContext.getResponse();
Element payload = (Element) message.getDocument().getFirstChild().getLastChild().getFirstChild();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.newDocument();
// Create the root element (soap:Envelope)
Element envelope = document.createElementNS("http://schemas.xmlsoap.org/soap/envelope/", "soap:Envelope");
// Add namespaces to the Envelope element
envelope.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
envelope.setAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
envelope.setAttribute("xmlns:wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing");
envelope.setAttribute("xmlns:wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
envelope.setAttribute("xmlns:wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
// Create the Header element
Element header = document.createElementNS("http://schemas.xmlsoap.org/soap/envelope/", "soap:Header");
// Create the Body element
Element body = document.createElementNS("http://schemas.xmlsoap.org/soap/envelope/", "soap:Body");
String bodyId = "Id-" + UUID.randomUUID().toString();
body.setAttribute("wsu:Id", bodyId);
this.stripNamespaces(payload, body, document);
// Append Header and Body to Envelope
envelope.appendChild(header);
envelope.appendChild(body);
// Append Envelope to Document
document.appendChild(envelope);
Element messageHeader = document.createElementNS("http://www.uc-council.org/smp/schemas/eanucc", "messageHeader");
Element messageHeaderTo = document.createElement("to");
Element messageHeaderToGln = document.createElement("gln");
messageHeaderToGln.setTextContent((String) RequestContext.get("to.gln"));
messageHeaderTo.appendChild(messageHeaderToGln);
messageHeader.appendChild(messageHeaderTo);
Element messageHeaderFrom = document.createElement("from");
Element messageHeaderFromGln = document.createElement("gln");
messageHeaderFromGln.setTextContent((String) RequestContext.get("from.gln"));
messageHeaderFrom.appendChild(messageHeaderFromGln);
messageHeader.appendChild(messageHeaderFrom);
Element messageHeaderRepresentingParty = document.createElement("representingParty");
Element messageHeaderRepresentingPartyGln = document.createElement("gln");
messageHeaderRepresentingPartyGln.setTextContent((String) RequestContext.get("representingparty.gln"));
messageHeaderRepresentingParty.appendChild(messageHeaderRepresentingPartyGln);
messageHeader.appendChild(messageHeaderRepresentingParty);
Element action = document.createElement("wsa:Action");
action.setTextContent("http://vesta.com.br/pse/FEServices/" + payload.getLocalName());
Element messageId = document.createElement("wsa:MessageID");
String messageIdValue = UUID.randomUUID().toString();
messageId.setTextContent("urn:uuid:" + messageIdValue);
Element relatesTo = document.createElement("wsa:RelatesTo");
relatesTo.setTextContent((String) RequestContext.get("messageid"));
Element to = document.createElement("wsa:To");
to.setTextContent("http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous");
Element security = document.createElement("wsse:Security");
security.setAttribute("soap:mustUnderstand", "1");
Element securityTimestamp = document.createElement("wsu:Timestamp");
String timestampid = UUID.randomUUID().toString();
securityTimestamp.setAttribute("wsu:Id", "Timestamp-" + timestampid);
Element securityTimestampCreated = document.createElement("wsu:Created");
Element securityTimestampExpires = document.createElement("wsu:Expires");
Date now = new Date();
Date expires = (Date) now.clone();
expires.setHours(expires.getHours() + 1);
DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT.withZone(ZoneOffset.UTC);
Instant instant = now.toInstant();
String createdT = formatter.format(instant);
instant = expires.toInstant();
String expiresT = formatter.format(instant);
securityTimestampCreated.setTextContent(createdT);
securityTimestampExpires.setTextContent(expiresT);
securityTimestamp.appendChild(securityTimestampCreated);
securityTimestamp.appendChild(securityTimestampExpires);
security.appendChild(securityTimestamp);
Element binaryToken = document.createElement("wsse:BinarySecurityToken");
binaryToken.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
binaryToken.setAttribute("ValueType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");
String tokenId = "SecurityToken-" + UUID.randomUUID().toString();
binaryToken.setAttribute("wsu:Id", tokenId);
binaryToken.setTextContent(certBase64);
security.appendChild(binaryToken);
Element securitySignature = document.createElementNS("http://www.w3.org/2000/09/xmldsig#", "Signature");
Element securitySignatureSigned = document.createElement("SignedInfo");
Element securitySignatureSignedCanon = document.createElement("CanonicalizationMethod");
securitySignatureSignedCanon.setAttribute("Algorithm", "http://www.w3.org/2001/10/xml-exc-c14n#");
securitySignatureSignedCanon.setAttribute("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#");
securitySignatureSigned.appendChild(securitySignatureSignedCanon);
Element securitySignatureSignedMethod = document.createElement("SignatureMethod");
securitySignatureSignedMethod.setAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
securitySignatureSigned.appendChild(securitySignatureSignedMethod);
Element securitySignatureSignedReference = document.createElement("Reference");
securitySignatureSignedReference.setAttribute("URI", bodyId);
Element securitySignatureSignedReferenceTransW = document.createElement("Transforms");
Element securitySignatureSignedReferenceTrans = document.createElement("Transform");
securitySignatureSignedReferenceTrans.setAttribute("Algorithm", "http://www.w3.org/2001/10/xml-exc-c14n#");
Element securitySignatureSignedReferenceDigestM = document.createElement("DigestMethod");
securitySignatureSignedReferenceDigestM.setAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
Element securitySignatureSignedReferenceDigestV = document.createElement("DigestValue");
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
DOMSource domSource = new DOMSource(body.getFirstChild());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
StreamResult streamResult = new StreamResult(outputStream);
transformer.transform(domSource, streamResult);
String digestable = outputStream.toString("UTF-8");
MessageDigest digest = MessageDigest.getInstance("SHA-1");
byte[] hashedBody = digest.digest(digestable.getBytes());
String digestValue = Base64.encodeBase64String(hashedBody);
securitySignatureSignedReferenceDigestV.setTextContent(digestValue);
securitySignatureSignedReferenceTransW.appendChild(securitySignatureSignedReferenceTrans);
securitySignatureSignedReference.appendChild(securitySignatureSignedReferenceTransW);
securitySignatureSignedReference.appendChild(securitySignatureSignedReferenceDigestM);
securitySignatureSignedReference.appendChild(securitySignatureSignedReferenceDigestV);
securitySignatureSigned.appendChild(securitySignatureSignedReference);
securitySignature.appendChild(securitySignatureSigned);
Element securitySignatureValue = document.createElement("SignatureValue");
String signatureValue = this.signDigest(securitySignatureSigned, "keystore.p12", "password of jks/p12", "alias of the p12/jks");
securitySignatureValue.setTextContent(signatureValue);
Element securitySignatureKey = document.createElement("KeyInfo");
Element securitySignatureKeyToken = document.createElement("wsse:SecurityTokenReference");
Element securitySignatureKeyTokenKey = document.createElement("wsse:Reference");
securitySignatureKeyTokenKey.setAttribute("ValueType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");
securitySignatureKeyTokenKey.setAttribute("URI", "#" + tokenId);
securitySignatureKeyToken.appendChild(securitySignatureKeyTokenKey);
securitySignatureKey.appendChild(securitySignatureKeyToken);
securitySignature.appendChild(securitySignatureValue);
securitySignature.appendChild(securitySignatureKey);
security.appendChild(securitySignature);
header.appendChild(messageHeader);
header.appendChild(action);
header.appendChild(messageId);
header.appendChild(relatesTo);
header.appendChild(to);
header.appendChild(security);
message.setDocument(document);
return true;
}
public String signDigest(Element element, String keystorePath, String keystorePassword, String keyAlias) throws Exception {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
DOMSource domSource = new DOMSource(element);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
StreamResult streamResult = new StreamResult(outputStream);
transformer.transform(domSource, streamResult);
String signable = outputStream.toString("UTF-8");
// Load the keystore
KeyStore keystore = KeyStore.getInstance("PKCS12");
try (InputStream is = new FileInputStream(keystorePath)) {
keystore.load(is, keystorePassword.toCharArray());
}
PrivateKey privateKey = (PrivateKey) keystore.getKey(keyAlias, keystorePassword.toCharArray());
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
signature.update(signable.getBytes());
// Base64 encode the signature
byte[] signedHash = signature.sign();
return Base64.encodeBase64String(signedHash);
}
private void stripNamespaces(Element originalElement, Element newElement, Document document) {
// Create a new element with no namespace
Element newChildElement = document.createElement(originalElement.getLocalName());
// Copy attributes
for (int i = 0; i < originalElement.getAttributes().getLength(); i++) {
newChildElement.setAttribute(originalElement.getAttributes().item(i).getLocalName(),
originalElement.getAttributes().item(i).getNodeValue());
}
// Process child nodes
for (int i = 0; i < originalElement.getChildNodes().getLength(); i++) {
if (originalElement.getChildNodes().item(i) instanceof Element) {
Element childElement = (Element) originalElement.getChildNodes().item(i);
stripNamespaces(childElement, newChildElement, document);
} else {
newChildElement.appendChild(document.importNode(originalElement.getChildNodes().item(i), true));
}
}
// Append the newly created child to the new element
newElement.appendChild(newChildElement);
}
private X509Certificate getCertificate() {
X509Certificate certificate = null;
try {
FileInputStream certFileInputStream = new FileInputStream("file.cer");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
certificate = (X509Certificate) certFactory.generateCertificate(certFileInputStream);
certFileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return certificate;
}
@Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
return true;
}
@Override
public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception {
}
@Override
public boolean understands(SoapHeaderElement header) {
return true; // without this the server fails to understand the request
}
}