001/**
002 * Copyright 2017 Emmanuel Bourg
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package net.jsign;
018
019import java.io.File;
020
021import org.apache.maven.plugin.AbstractMojo;
022import org.apache.maven.plugin.MojoExecutionException;
023import org.apache.maven.plugin.MojoFailureException;
024import org.apache.maven.plugins.annotations.Component;
025import org.apache.maven.plugins.annotations.LifecyclePhase;
026import org.apache.maven.plugins.annotations.Mojo;
027import org.apache.maven.plugins.annotations.Parameter;
028import org.apache.maven.settings.Proxy;
029import org.apache.maven.settings.Settings;
030import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
031import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
032
033/**
034 * Maven plugin for signing files with Authenticode.
035 * 
036 * @author Emmanuel Bourg
037 * @since 2.0
038 */
039@Mojo(name = "sign", defaultPhase = LifecyclePhase.PACKAGE)
040public class JsignMojo extends AbstractMojo {
041
042    /** The file to be signed. */
043    @Parameter(required = true)
044    private File file;
045
046    /** The program name embedded in the signature. */
047    @Parameter( property = "jsign.name" )
048    private String name;
049
050    /** The program URL embedded in the signature. */
051    @Parameter( property = "jsign.url" )
052    private String url;
053
054    /** The digest algorithm to use for the signature (SHA-1, SHA-256, SHA-384 or SHA-512). */
055    @Parameter( property = "jsign.algorithm", defaultValue = "SHA-256" )
056    private String algorithm;
057
058    /** The keystore file. Required, unless certfile and keyfile are specified. */
059    @Parameter( property = "jsign.keystore" )
060    private File keystore;
061
062    /** The password for the keystore. */
063    @Parameter( property = "jsign.storepass" )
064    private String storepass;
065
066    /** The type of the keystore (JKS or PKCS12). */
067    @Parameter( property = "jsign.storetype", defaultValue = "JKS" )
068    private String storetype;
069
070    /** The alias of the certificate in the keystore. Required if a keystore is specified. */
071    @Parameter( property = "jsign.alias" )
072    private String alias;
073
074    /** The file containing the PKCS#7 certificate chain (.p7b or .spc files). */
075    @Parameter( property = "jsign.certfile" )
076    private File certfile;
077
078    /** The file containing the private key (PEM or PVK format) */
079    @Parameter( property = "jsign.keyfile" )
080    private File keyfile;
081
082    /** The password for the key in the store (if different from the keystore password) or in the keyfile. */
083    @Parameter( property = "jsign.keypass" )
084    private String keypass;
085
086    /** The URL of the timestamping authority. */
087    @Parameter( property = "jsign.tsaurl" )
088    private String tsaurl;
089
090    /** The protocol used for the timestamping (RFC3161 or Authenticode) */
091    @Parameter( property = "jsign.tsmode" )
092    private String tsmode;
093
094    /** The number of retries for timestamping */
095    @Parameter( property = "jsign.tsretries" )
096    private int tsretries = -1;
097
098    /** The number of seconds to wait between timestamping retries */
099    @Parameter( property = "jsign.tsretrywait" )
100    private int tsretrywait = -1;
101
102    /** Tells if previous signatures should be replaced */
103    @Parameter( property = "jsign.replace", defaultValue = "false")
104    private boolean replace;
105
106    /** The encoding of the script to be signed (UTF-8 by default). */
107    @Parameter( property = "jsign.encoding", defaultValue = "UTF-8")
108    private String encoding = "UTF-8";
109
110    @Parameter(defaultValue = "${settings}", readonly = true)
111    private Settings settings;
112
113    @Parameter( property = "jsign.proxyId" )
114    private String proxyId;
115
116    @Component(hint = "mng-4384")
117    private SecDispatcher securityDispatcher;
118
119    @Override
120    public void execute() throws MojoExecutionException, MojoFailureException {
121        SignerHelper helper = new SignerHelper(new MavenConsole(getLog()), "element");
122        
123        helper.name(name);
124        helper.url(url);
125        helper.alg(algorithm);
126        helper.keystore(keystore);
127        helper.storepass(decrypt(storepass));
128        helper.storetype(storetype);
129        helper.alias(alias);
130        helper.certfile(certfile);
131        helper.keyfile(keyfile);
132        helper.keypass(decrypt(keypass));
133        helper.tsaurl(tsaurl);
134        helper.tsmode(tsmode);
135        helper.tsretries(tsretries);
136        helper.tsretrywait(tsretrywait);
137        helper.replace(replace);
138        helper.encoding(encoding);
139
140        Proxy proxy = getProxyFromSettings();
141        if (proxy != null) {
142            helper.proxyUrl(proxy.getProtocol() + "://" + proxy.getHost() + ":" + proxy.getPort());
143            helper.proxyUser(proxy.getUsername());
144            helper.proxyPass(proxy.getPassword());
145        }
146
147        try {
148            helper.sign(file);
149        } catch (SignerException e) {
150            throw new MojoFailureException(e.getMessage(), e);
151        }
152    }
153
154    private Proxy getProxyFromSettings() throws MojoExecutionException {
155        if (settings == null) {
156            return null;
157        }
158
159        if (proxyId != null) {
160            for (Proxy proxy : settings.getProxies()) {
161                if (proxyId.equals(proxy.getId())) {
162                    return proxy;
163                }
164            }
165            throw new MojoExecutionException("Configured proxy with id=" + proxyId + " not found");
166        }
167
168        // Get active http/https proxy
169        for (Proxy proxy : settings.getProxies()) {
170            if (proxy.isActive() && ("http".equalsIgnoreCase(proxy.getProtocol()) || "https".equalsIgnoreCase(proxy.getProtocol()))) {
171                return proxy;
172            }
173        }
174
175        return null;
176    }
177
178    private String decrypt(String encoded) throws MojoExecutionException {
179        if (encoded == null) {
180            return null;
181        }
182
183        try {
184            return securityDispatcher.decrypt(encoded);
185        } catch (SecDispatcherException e) {
186            getLog().error("error using security dispatcher: " + e.getMessage(), e);
187            throw new MojoExecutionException("error using security dispatcher: " + e.getMessage(), e);
188        }
189    }
190}