Будем использовать apache CXF 2.7.8 для работы с веб-сервисом. Для подписи будем использовать OpenSSL, предварительно сконвертировав ключи в pkcs12 формат, как описано в предыдущей статье.
и немного кода:
logger.info("---------------WS-URL---------------");
logger.info(config.getString("ws.service.url"));
logger.info("-------------------------------------");
QName SERVICE_NAME = new QName("http://tempuri.org", "PaymentGateService");
QName PORT_NAME = new QName("http://tempuri.org", "PaymentGatePort");
Service paymnetService = Service.create(SERVICE_NAME);
paymnetService.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, Config.getWsAddress());
PaymentGate iface = paymnetService.getPort(PORT_NAME, PaymentGate.class);
SignatureInterceptor transformOutInterceptor = new SignatureInterceptor();
Client client = ClientProxy.getClient(iface);
HTTPConduit http = (HTTPConduit) client.getConduit();
final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
TLSClientParameters parameters = new TLSClientParameters();
parameters.setTrustManagers(trustAllCerts);
parameters.setDisableCNCheck(true);
http.setTlsClientParameters(parameters);
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(10000);
httpClientPolicy.setAllowChunking(false);
httpClientPolicy.setReceiveTimeout(40000);
http.setClient(httpClientPolicy);
client.getOutInterceptors().add(new LoggingOutInterceptor());
client.getOutInterceptors().add(transformOutInterceptor);
client.getInInterceptors().add(new SignatureVerificationInterseptor());
System.setProperty("com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump", String.valueOf(Config.isHttpDump()));
Для формирования подписи нам нужно получить сообщение целиком в том виде, в котором оно будет отправлено. Каноникализация производится неявным образом, путем удаления всего форматирования xml документа. используем следующий код, частично скопированный из исходников CXF. Из тонких моментов - инвертация полученной подписи. Видимо есть различия в том, как CryptoPro и OpenSSL возвращают результат подписи, кто-то в Big-endian, кто-то в Little-endian
public class SignatureInterceptor extends AbstractOutDatabindingInterceptor {
private final static Logger logger = LoggerFactory.getLogger(SignatureInterceptor.class);
public SignatureInterceptor() {
super(Phase.PRE_LOGICAL);
getBefore().add(HolderOutInterceptor.class.getName());
}
public void handleMessage(Message message) throws Fault {
try {
OutputStream out = new ByteArrayOutputStream();
Exchange exchange = message.getExchange();
Service service = exchange.getService();
XMLOutputFactory instance = XMLOutputFactory.newInstance();
XMLStreamWriter xmlWriter = instance.createXMLStreamWriter(out);
DataWriter<XMLStreamWriter> dataWriter = getDataWriter(message, service, XMLStreamWriter.class);
OperationInfo op = exchange.getBindingOperationInfo().getOperationInfo();
List<MessagePartInfo> parts = op.getInput().getMessageParts();
MessageContentsList objs = MessageContentsList.getContentsList(message);
for (MessagePartInfo part : parts) {
if (objs.hasValue(part)) {
Object o = objs.get(part);
if (!part.getConcreteName().getLocalPart().equals("digitalSignature")) {
dataWriter.write(o, part, xmlWriter);
}
}
}
xmlWriter.flush();
logger.debug("SIGNATURE BEGIN");
logger.debug(out.toString());
String signature = sign(out.toString());
logger.debug(signature);
logger.debug("signature length = " + signature.length());
logger.debug("SIGNATURE END");
xmlWriter.close();
for (MessagePartInfo part : parts) {
if (part.getConcreteName().getLocalPart().equals("digitalSignature")) {
DigitalSignature ds = new DigitalSignature();
ds.setSignature(signature);
objs.put(part, ds);
}
}
} catch (Exception ex) {
throw new Fault(ex);
}
}
private static String sign(String content) throws SignatureException{
File f = null;
File fOut = null;
try {
f = File.createTempFile("input", "data");
FileOutputStream foutStream = new FileOutputStream(f);
IOUtils.write(content, foutStream);
foutStream.flush();
IOUtils.closeQuietly(foutStream);
fOut = File.createTempFile("input", "sng");
ProcessBuilder pb = new ProcessBuilder(Config.getInstance().getString("crypto.command"),
"dgst",
"-engine",
"gost",
"-binary",
"-md_gost94",
"-out",
fOut.getAbsolutePath(),
"-sign",
Config.getInstance().getString("crypto.private.key"),
f.getAbsolutePath());
logger.debug(StringUtils.join(pb.command(), " "));
Process p = pb.start();
InputStream is = p.getErrorStream();
p.waitFor();
if (p.exitValue() != 0) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copy(is, out);
logger.error(out.toString("UTF-8"));
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(out);
throw new SignatureException("OpenSSL return error code: " + p.exitValue());
}
IOUtils.closeQuietly(is);
FileInputStream fis = new FileInputStream(fOut);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(fis, bos);
IOUtils.closeQuietly(fis);
byte[] res = bos.toByteArray();
IOUtils.closeQuietly(bos);
// Инвертируем полученную подпись - иначе она считается невалидной.
byte[] inverted = new byte[res.length];
for(int i = 0; i< res.length; i++){
inverted[res.length-i-1] = res[i];
}
return new String(Base64.encodeBase64(inverted, false));
} catch (IOException ex) {
throw new SignatureException(ex.getMessage(), ex);
} catch (InterruptedException e) {
logger.error("Signature process was interrupted, returning null...");
return null;
} finally {
FileUtils.deleteQuietly(f);
FileUtils.deleteQuietly(fOut);
}
}
}
И проверка подписи:public class SignatureVerificationInterseptor extends LoggingInInterceptor { private final static Logger logger = LoggerFactory.getLogger(SignatureVerificationInterseptor.class); public SignatureVerificationInterseptor() { super(); } @Override protected String formatLoggingMessage(LoggingMessage loggingMessage) { String soapXmlPayload = loggingMessage.getPayload().toString(); try { String content = soapXmlPayload.substring(soapXmlPayload.indexOf("<soapenv:Body>") + "<soapenv:Body>".length(), soapXmlPayload.indexOf("</soapenv:Body>")); String signature = soapXmlPayload.substring(soapXmlPayload.indexOf("<signature>") + "<signature>".length(), soapXmlPayload.indexOf("</signature>")); logger.debug("signature verifying"); logger.debug(content); logger.debug(signature); if (!verify(content, signature)) { logger.debug(super.formatLoggingMessage(loggingMessage)); throw new SignatureVerificationException("Signature verification error"); } }catch (SignatureVerificationException ex){ throw ex; }catch (Exception ex){ logger.debug(super.formatLoggingMessage(loggingMessage)); throw new RuntimeException("Signature not found in soap envelope"); } return super.formatLoggingMessage(loggingMessage); } private static boolean verify(String content, String signature){ File signatureFile = null; File datafile = null; try { signatureFile = File.createTempFile("output", "sng"); FileOutputStream signatureStream = new FileOutputStream(signatureFile); byte[] sig = Base64.decodeBase64(signature.getBytes()); // Инвертируем полученную подпись - иначе она считается невалидной. byte[] inverted = new byte[sig.length]; for(int i = 0; i< sig.length; i++){ inverted[sig.length-i-1] = sig[i]; } IOUtils.write(inverted, signatureStream); signatureStream.flush(); IOUtils.closeQuietly(signatureStream); datafile = File.createTempFile("output", "data"); FileOutputStream dataStream = new FileOutputStream(datafile); IOUtils.write(content, dataStream); dataStream.flush(); IOUtils.closeQuietly(dataStream); ProcessBuilder pb = new ProcessBuilder(Config.getInstance().getString("crypto.command"), "dgst", "-engine", "gost", "-binary", "-verify", Config.getInstance().getString("crypto.public.cert"), "-signature", signatureFile.getAbsolutePath(), datafile.getAbsolutePath()); logger.debug(StringUtils.join(pb.command(), " ")); Process p = pb.start(); InputStream is = p.getErrorStream(); p.waitFor(); if (p.exitValue() != 0) { ByteArrayOutputStream out = new ByteArrayOutputStream(); IOUtils.copy(is, out); logger.error(out.toString("UTF-8")); IOUtils.closeQuietly(is); IOUtils.closeQuietly(out); return false; } return true; }catch (Exception ex){ logger.error(ex.getClass().toString() + " message:" + ex.getMessage(), ex); return false; } finally { FileUtils.deleteQuietly(signatureFile); FileUtils.deleteQuietly(datafile); } }