You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ma...@apache.org on 2018/04/30 20:30:22 UTC
[commons-signing] 02/05: Initial code import Code taken from Apache
Tomcat and modified as follows: - change package - in-line
join(Collection) - replace Tomcat's Base64 handling with Java 8's
This code is untested
This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-signing.git
commit 64d724a4e371e38ea440091c8b8c5a0db101d2f5
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Mon Apr 30 21:28:20 2018 +0100
Initial code import
Code taken from Apache Tomcat and modified as follows:
- change package
- in-line join(Collection<String>)
- replace Tomcat's Base64 handling with Java 8's
This code is untested
---
src/main/java/org/apache/commons/SignCode.java | 442 +++++++++++++++++++++++++
1 file changed, 442 insertions(+)
diff --git a/src/main/java/org/apache/commons/SignCode.java b/src/main/java/org/apache/commons/SignCode.java
new file mode 100644
index 0000000..0eeea96
--- /dev/null
+++ b/src/main/java/org/apache/commons/SignCode.java
@@ -0,0 +1,442 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You 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.
+*/
+package org.apache.commons;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Base64.Decoder;
+import java.util.Base64.Encoder;
+import java.util.Collection;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import javax.xml.soap.MessageFactory;
+import javax.xml.soap.SOAPBody;
+import javax.xml.soap.SOAPConnection;
+import javax.xml.soap.SOAPConnectionFactory;
+import javax.xml.soap.SOAPConstants;
+import javax.xml.soap.SOAPElement;
+import javax.xml.soap.SOAPEnvelope;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPMessage;
+import javax.xml.soap.SOAPPart;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Ant task that submits a file to the Symantec code-signing service.
+ */
+public class SignCode extends Task {
+
+ private static final URL SIGNING_SERVICE_URL;
+
+ private static final String NS = "cod";
+
+ private static final MessageFactory SOAP_MSG_FACTORY;
+
+ private static final String EMPTY_STRING = "";
+
+ static {
+ try {
+ SIGNING_SERVICE_URL = new URL(
+ "https://api-appsec-cws.ws.symantec.com/webtrust/SigningService");
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException(e);
+ }
+ try {
+ SOAP_MSG_FACTORY = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
+ } catch (SOAPException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private final List<FileSet> filesets = new ArrayList<>();
+ private String userName;
+ private String password;
+ private String partnerCode;
+ private String keyStore;
+ private String keyStorePassword;
+ private String applicationName;
+ private String applicationVersion;
+ private String signingService;
+ private boolean debug;
+
+ public void addFileset(FileSet fileset) {
+ filesets.add(fileset);
+ }
+
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+
+ public void setPartnerCode(String partnerCode) {
+ this.partnerCode = partnerCode;
+ }
+
+
+ public void setKeyStore(String keyStore) {
+ this.keyStore = keyStore;
+ }
+
+
+ public void setKeyStorePassword(String keyStorePassword) {
+ this.keyStorePassword = keyStorePassword;
+ }
+
+
+ public void setApplicationName(String applicationName) {
+ this.applicationName = applicationName;
+ }
+
+
+ public void setApplicationVersion(String applicationVersion) {
+ this.applicationVersion = applicationVersion;
+ }
+
+
+ public void setSigningService(String signingService) {
+ this.signingService = signingService;
+ }
+
+
+ public void setDebug(String debug) {
+ this.debug = Boolean.parseBoolean(debug);
+ }
+
+
+ @Override
+ public void execute() throws BuildException {
+
+ List<File> filesToSign = new ArrayList<>();
+
+ // Process the filesets and populate the list of files that need to be
+ // signed.
+ for (FileSet fileset : filesets) {
+ DirectoryScanner ds = fileset.getDirectoryScanner(getProject());
+ File basedir = ds.getBasedir();
+ String[] files = ds.getIncludedFiles();
+ if (files.length > 0) {
+ for (int i = 0; i < files.length; i++) {
+ File file = new File(basedir, files[i]);
+ filesToSign.add(file);
+ }
+ }
+ }
+
+ // Set up the TLS client
+ System.setProperty("javax.net.ssl.keyStore", keyStore);
+ System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
+
+ try {
+ String signingSetID = makeSigningRequest(filesToSign);
+ downloadSignedFiles(filesToSign, signingSetID);
+ } catch (SOAPException | IOException e) {
+ throw new BuildException(e);
+ }
+ }
+
+
+ private String makeSigningRequest(List<File> filesToSign) throws SOAPException, IOException {
+ log("Constructing the code signing request");
+
+ SOAPMessage message = SOAP_MSG_FACTORY.createMessage();
+ SOAPBody body = populateEnvelope(message, NS);
+
+ SOAPElement requestSigning = body.addChildElement("requestSigning", NS);
+ SOAPElement requestSigningRequest =
+ requestSigning.addChildElement("requestSigningRequest", NS);
+
+ addCredentials(requestSigningRequest, this.userName, this.password, this.partnerCode);
+
+ SOAPElement applicationName =
+ requestSigningRequest.addChildElement("applicationName", NS);
+ applicationName.addTextNode(this.applicationName);
+
+ SOAPElement applicationVersion =
+ requestSigningRequest.addChildElement("applicationVersion", NS);
+ applicationVersion.addTextNode(this.applicationVersion);
+
+ SOAPElement signingServiceName =
+ requestSigningRequest.addChildElement("signingServiceName", NS);
+ signingServiceName.addTextNode(this.signingService);
+
+ List<String> fileNames = getFileNames(filesToSign);
+
+ SOAPElement commaDelimitedFileNames =
+ requestSigningRequest.addChildElement("commaDelimitedFileNames", NS);
+ commaDelimitedFileNames.addTextNode(join(fileNames));
+
+ SOAPElement application =
+ requestSigningRequest.addChildElement("application", NS);
+ application.addTextNode(getApplicationString(fileNames, filesToSign));
+
+ // Send the message
+ SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
+ SOAPConnection connection = soapConnectionFactory.createConnection();
+
+ log("Sending singing request to server and waiting for response");
+ SOAPMessage response = connection.call(message, SIGNING_SERVICE_URL);
+
+ if (debug) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(2 * 1024);
+ response.writeTo(baos);
+ log(baos.toString("UTF-8"));
+ }
+
+ log("Processing response");
+ SOAPElement responseBody = response.getSOAPBody();
+
+ // Should come back signed
+ NodeList bodyNodes = responseBody.getChildNodes();
+ NodeList requestSigningResponseNodes = bodyNodes.item(0).getChildNodes();
+ NodeList returnNodes = requestSigningResponseNodes.item(0).getChildNodes();
+
+ String signingSetID = null;
+ String signingSetStatus = null;
+
+ for (int i = 0; i < returnNodes.getLength(); i++) {
+ Node returnNode = returnNodes.item(i);
+ if (returnNode.getLocalName().equals("signingSetID")) {
+ signingSetID = returnNode.getTextContent();
+ } else if (returnNode.getLocalName().equals("signingSetStatus")) {
+ signingSetStatus = returnNode.getTextContent();
+ }
+ }
+
+ if (!signingService.contains("TEST") && !"SIGNED".equals(signingSetStatus) ||
+ signingService.contains("TEST") && !"INITIALIZED".equals(signingSetStatus) ) {
+ throw new BuildException("Signing failed. Status was: " + signingSetStatus);
+ }
+
+ return signingSetID;
+ }
+
+
+ private void downloadSignedFiles(List<File> filesToSign, String id)
+ throws SOAPException, IOException {
+
+ log("Downloading signed files. The signing set ID is: " + id);
+
+ SOAPMessage message = SOAP_MSG_FACTORY.createMessage();
+ SOAPBody body = populateEnvelope(message, NS);
+
+ SOAPElement getSigningSetDetails = body.addChildElement("getSigningSetDetails", NS);
+ SOAPElement getSigningSetDetailsRequest =
+ getSigningSetDetails.addChildElement("getSigningSetDetailsRequest", NS);
+
+ addCredentials(getSigningSetDetailsRequest, this.userName, this.password, this.partnerCode);
+
+ SOAPElement signingSetID =
+ getSigningSetDetailsRequest.addChildElement("signingSetID", NS);
+ signingSetID.addTextNode(id);
+
+ SOAPElement returnApplication =
+ getSigningSetDetailsRequest.addChildElement("returnApplication", NS);
+ returnApplication.addTextNode("true");
+
+ // Send the message
+ SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
+ SOAPConnection connection = soapConnectionFactory.createConnection();
+
+ log("Requesting signed files from server and waiting for response");
+ SOAPMessage response = connection.call(message, SIGNING_SERVICE_URL);
+
+ log("Processing response");
+ SOAPElement responseBody = response.getSOAPBody();
+
+ // Check for success
+
+ // Extract the signed file(s) from the ZIP
+ NodeList bodyNodes = responseBody.getChildNodes();
+ NodeList getSigningSetDetailsResponseNodes = bodyNodes.item(0).getChildNodes();
+ NodeList returnNodes = getSigningSetDetailsResponseNodes.item(0).getChildNodes();
+
+ String result = null;
+ String data = null;
+
+ for (int i = 0; i < returnNodes.getLength(); i++) {
+ Node returnNode = returnNodes.item(i);
+ if (returnNode.getLocalName().equals("result")) {
+ result = returnNode.getChildNodes().item(0).getTextContent();
+ } else if (returnNode.getLocalName().equals("signingSet")) {
+ data = returnNode.getChildNodes().item(1).getTextContent();
+ }
+ }
+
+ if (!"0".equals(result)) {
+ throw new BuildException("Download failed. Result code was: " + result);
+ }
+
+ extractFilesFromApplicationString(data, filesToSign);
+ }
+
+
+ private static SOAPBody populateEnvelope(SOAPMessage message, String namespace)
+ throws SOAPException {
+ SOAPPart soapPart = message.getSOAPPart();
+ SOAPEnvelope envelope = soapPart.getEnvelope();
+ envelope.addNamespaceDeclaration(
+ "soapenv","http://schemas.xmlsoap.org/soap/envelope/");
+ envelope.addNamespaceDeclaration(
+ namespace,"http://api.ws.symantec.com/webtrust/codesigningservice");
+ return envelope.getBody();
+ }
+
+
+ private static void addCredentials(SOAPElement requestSigningRequest,
+ String user, String pwd, String code) throws SOAPException {
+ SOAPElement authToken = requestSigningRequest.addChildElement("authToken", NS);
+ SOAPElement userName = authToken.addChildElement("userName", NS);
+ userName.addTextNode(user);
+ SOAPElement password = authToken.addChildElement("password", NS);
+ password.addTextNode(pwd);
+ SOAPElement partnerCode = authToken.addChildElement("partnerCode", NS);
+ partnerCode.addTextNode(code);
+ }
+
+
+ /**
+ * Signing service requires unique files names. Since files will be returned
+ * in order, use dummy names that we know are unique but retain the file
+ * extension since the signing service appears to use it to figure out what
+ * to sign and how to sign it.
+ */
+ private static List<String> getFileNames(List<File> filesToSign) {
+ List<String> result = new ArrayList<>(filesToSign.size());
+
+ for (int i = 0; i < filesToSign.size(); i++) {
+ File f = filesToSign.get(i);
+ String fileName = f.getName();
+ int extIndex = fileName.lastIndexOf('.');
+ String newName;
+ if (extIndex < 0) {
+ newName = Integer.toString(i);
+ } else {
+ newName = Integer.toString(i) + fileName.substring(extIndex);
+ }
+ result.add(newName);
+ }
+ return result;
+ }
+
+
+ /**
+ * Zips the files, base 64 encodes the resulting zip and then returns the
+ * string. It would be far more efficient to stream this directly to the
+ * signing server but the files that need to be signed are relatively small
+ * and this simpler to write.
+ *
+ * @param fileNames Modified names of files
+ * @param files Files to be signed
+ */
+ private static String getApplicationString(List<String> fileNames, List<File> files)
+ throws IOException {
+ // 16 MB should be more than enough for Tomcat
+ // TODO: Refactoring this entire class so it uses streaming rather than
+ // buffering the entire set of files in memory would make it more
+ // widely useful.
+ Encoder encoder = Base64.getEncoder();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(16 * 1024 * 1024);
+ OutputStream os = encoder.wrap(baos);
+ try (ZipOutputStream zos = new ZipOutputStream(os)) {
+ byte[] buf = new byte[32 * 1024];
+ for (int i = 0; i < files.size(); i++) {
+ try (FileInputStream fis = new FileInputStream(files.get(i))) {
+ ZipEntry zipEntry = new ZipEntry(fileNames.get(i));
+ zos.putNextEntry(zipEntry);
+ int numRead;
+ while ( (numRead = fis.read(buf)) >= 0) {
+ zos.write(buf, 0, numRead);
+ }
+ }
+ }
+ }
+
+ return new String(baos.toByteArray(), StandardCharsets.ISO_8859_1);
+ }
+
+
+ /**
+ * Removes base64 encoding, unzips the files and writes the new files over
+ * the top of the old ones.
+ */
+ private static void extractFilesFromApplicationString(String data, List<File> files)
+ throws IOException {
+ Decoder decoder = Base64.getMimeDecoder();
+ ByteArrayInputStream bais = new ByteArrayInputStream(data.getBytes());
+ InputStream is = decoder.wrap(bais);
+ try (ZipInputStream zis = new ZipInputStream(is)) {
+ byte[] buf = new byte[32 * 1024];
+ for (int i = 0; i < files.size(); i ++) {
+ try (FileOutputStream fos = new FileOutputStream(files.get(i))) {
+ zis.getNextEntry();
+ int numRead;
+ while ( (numRead = zis.read(buf)) >= 0) {
+ fos.write(buf, 0 , numRead);
+ }
+ }
+ }
+ }
+ }
+
+
+ public static String join(Collection<String> collection) {
+ // Shortcut
+ if (collection == null || collection.isEmpty()) {
+ return EMPTY_STRING;
+ }
+
+ StringBuilder result = new StringBuilder();
+
+ boolean first = true;
+ for (String value : collection) {
+ if (first) {
+ first = false;
+ } else {
+ result.append(',');
+ }
+ result.append(value);
+ }
+
+ return result.toString();
+ }
+}
--
To stop receiving notification emails like this one, please contact
markt@apache.org.