You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2018/01/12 14:37:21 UTC
[sling-whiteboard] branch master updated: SLING-6585 - Switch to
the ASF code signing service
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
The following commit(s) were added to refs/heads/master by this push:
new 2379b99 SLING-6585 - Switch to the ASF code signing service
2379b99 is described below
commit 2379b998b11b75b7a89f9a3016f4a41c27b7cc59
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Mon Apr 3 19:55:47 2017 +0300
SLING-6585 - Switch to the ASF code signing service
Initial, untested, version of the code signing Maven plugin. Based
mostly on the Tomcat Ant task for codesigning.
---
codesign/pom.xml | 111 ++++++
.../org/apache/tomcat/buildutil/SignCodeMojo.java | 435 +++++++++++++++++++++
2 files changed, 546 insertions(+)
diff --git a/codesign/pom.xml b/codesign/pom.xml
new file mode 100644
index 0000000..206c3ad
--- /dev/null
+++ b/codesign/pom.xml
@@ -0,0 +1,111 @@
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>28</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>codesign-maven-plugin</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>maven-plugin</packaging>
+
+ <name>Apache Codesigning Maven Plugin</name>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.version>3.3.3</maven.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-plugin-api</artifactId>
+ <version>${maven.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-artifact</artifactId>
+ <version>${maven.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <version>${maven.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin-tools</groupId>
+ <artifactId>maven-plugin-annotations</artifactId>
+ <version>3.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-utils</artifactId>
+ <version>3.0.24</version>
+ </dependency>
+ <dependency>
+ <groupId>org.sonatype.plexus</groupId>
+ <artifactId>plexus-build-api</artifactId>
+ <version>0.0.7</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-plugin-plugin</artifactId>
+ <version>3.4</version>
+ <executions>
+ <execution>
+ <id>mojo-descriptor</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>descriptor</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>generated-helpmojo</id>
+ <goals>
+ <goal>helpmojo</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-component-metadata</artifactId>
+ <version>1.6</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>generate-metadata</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/codesign/src/main/java/org/apache/tomcat/buildutil/SignCodeMojo.java b/codesign/src/main/java/org/apache/tomcat/buildutil/SignCodeMojo.java
new file mode 100644
index 0000000..79dfcfd
--- /dev/null
+++ b/codesign/src/main/java/org/apache/tomcat/buildutil/SignCodeMojo.java
@@ -0,0 +1,435 @@
+/*
+* 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.tomcat.buildutil;
+
+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.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+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.maven.model.FileSet;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.Base64;
+import org.codehaus.plexus.util.Scanner;
+import org.codehaus.plexus.util.StringUtils;
+import org.sonatype.plexus.build.incremental.BuildContext;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+// file copied and adapted from http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/buildutil/SignCode.java?revision=1789744&view=co
+@Mojo(
+ name = "sign",
+ defaultPhase = LifecyclePhase.PACKAGE,
+ threadSafe = true
+)
+public class SignCodeMojo extends AbstractMojo {
+
+ private static final URL SIGNING_SERVICE_URL;
+
+ private static final String NS = "cod";
+
+ private static final MessageFactory SOAP_MSG_FACTORY;
+
+ 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);
+ }
+ }
+
+ @Component
+ private BuildContext buildContext;
+
+ @Parameter(defaultValue = "${project}", readonly = true, required = true)
+ protected MavenProject project;
+
+ /**
+ * The username of the API user
+ */
+ @Parameter(required = true)
+ private String userName;
+
+ /**
+ * The password of the API user
+ */
+ @Parameter(required = true)
+ private String password;
+
+ /**
+ * The partner code, initially sent via an email to you titled 'Your Secure App Service API username'
+ */
+ @Parameter(required = true)
+ private String partnerCode;
+
+ @Parameter(defaultValue = "${project.name}")
+ private String applicationName;
+
+ @Parameter(defaultValue = "${project.version}")
+ private String applicationVersion;
+
+ @Parameter(required = true)
+ private String keyStorePassword;
+
+ @Parameter(required = true)
+ private String keyStore;
+
+ @Parameter
+ private FileSet[] artifactSets;
+
+ @Parameter(property="codesign.sslDebug")
+ private boolean sslDebug;
+
+ @Parameter
+ private boolean includeProjectArtifact;
+
+ /**
+ * Use <tt>Java TEST signing Sha256</tt> for testing and <tt>Java signing Sha256</tt> for prod
+ */
+ @Parameter(required = true)
+ private String signingService;
+
+
+ @Override
+ public void execute() throws MojoExecutionException {
+
+ // TODO - do we want configurable files to sign?
+ List<File> filesToSign = new ArrayList<>();
+
+ if ( includeProjectArtifact )
+ filesToSign.add(project.getArtifact().getFile());
+
+ if ( artifactSets != null ) {
+ for ( FileSet artifactSet : artifactSets ) {
+ File base = new File(project.getBasedir(), artifactSet.getDirectory());
+ Scanner scanner = buildContext.newScanner(base);
+ scanner.setIncludes(artifactSet.getIncludes().toArray(new String[0]));
+ scanner.setExcludes(artifactSet.getExcludes().toArray(new String[0]));
+ scanner.scan();
+ for ( String file : scanner.getIncludedFiles() ) {
+ filesToSign.add(new File(base, file));
+ }
+ }
+ }
+
+ if ( filesToSign.isEmpty() )
+ getLog().info("No files to sign, skipping");
+
+ for ( File toSign : filesToSign )
+ getLog().info("Would sign " + toSign);
+
+ // Set up the TLS client
+ System.setProperty("javax.net.ssl.keyStore", keyStore);
+ System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
+ if ( sslDebug ) {
+ System.setProperty("javax.net.debug","all");
+ }
+
+ try {
+ String signingSetID = makeSigningRequest(filesToSign);
+ downloadSignedFiles(filesToSign, signingSetID);
+ } catch (SOAPException | IOException e) {
+ throw new MojoExecutionException("Signing failed : " + e.getMessage(), e);
+ }
+ }
+
+
+ private String makeSigningRequest(List<File> filesToSign) throws SOAPException, IOException, MojoExecutionException {
+ 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(StringUtils.join(fileNames.iterator(), ","));
+
+ 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 ( getLog().isDebugEnabled()) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(2 * 1024);
+ response.writeTo(baos);
+ getLog().debug(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;
+ }
+
+ // pass-through method to make it easier to copy/paste code from tomcat's ant mojos
+ private void log(String msg) {
+ getLog().info(msg);
+ }
+
+ public class BuildException extends MojoExecutionException {
+
+ private static final long serialVersionUID = 1L;
+
+ public BuildException(String message) {
+ super(message);
+ }
+
+ }
+
+ private void downloadSignedFiles(List<File> filesToSign, String id)
+ throws SOAPException, IOException, BuildException {
+
+ 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.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(16 * 1024 * 1024);
+ try (ZipOutputStream zos = new ZipOutputStream(baos)) {
+ 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(Base64.encodeBase64(baos.toByteArray()), "UTF-8");
+ }
+
+
+ /**
+ * 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 {
+ ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decodeBase64(data.getBytes("UTF-8")));
+ try (ZipInputStream zis = new ZipInputStream(bais)) {
+ 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);
+ }
+ }
+ }
+ }
+ }
+}
--
To stop receiving notification emails like this one, please contact
['"commits@sling.apache.org" <co...@sling.apache.org>'].