View Javadoc

1   package org.apache.maven.plugin.gpg;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedReader;
23  import java.io.ByteArrayInputStream;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.project.MavenProject;
31  import org.codehaus.plexus.util.Os;
32  import org.codehaus.plexus.util.StringUtils;
33  import org.codehaus.plexus.util.cli.CommandLineException;
34  import org.codehaus.plexus.util.cli.CommandLineUtils;
35  import org.codehaus.plexus.util.cli.Commandline;
36  import org.codehaus.plexus.util.cli.DefaultConsumer;
37  
38  public class GpgSigner
39  {
40  
41      public static final String SIGNATURE_EXTENSION = ".asc";
42  
43      private String executable;
44   
45      private boolean useAgent;
46  
47      private boolean isInteractive = true;
48  
49      private boolean defaultKeyring = true;
50  
51      private String keyname;
52  
53      private String passphrase;
54  
55      private File outputDir;
56  
57      private File buildDir;
58  
59      private File baseDir;
60  
61      private File homeDir;
62  
63      private String secretKeyring;
64  
65      private String publicKeyring;
66  
67      public void setExecutable( String executable )
68      {
69          this.executable = executable;
70      }
71  
72      public void setInteractive( boolean b )
73      {
74          isInteractive = b;
75      }
76  
77      public void setUseAgent( boolean b )
78      {
79          useAgent = b;
80      }
81  
82      public void setDefaultKeyring( boolean enabled )
83      {
84          defaultKeyring = enabled;
85      }
86  
87      public void setKeyName( String s )
88      {
89          keyname = s;
90      }
91  
92      public void setPassPhrase( String s )
93      {
94          passphrase = s;
95      }
96  
97      public void setOutputDirectory( File out )
98      {
99          outputDir = out;
100     }
101 
102     public void setBuildDirectory( File out )
103     {
104         buildDir = out;
105     }
106 
107     public void setBaseDirectory( File out )
108     {
109         baseDir = out;
110     }
111 
112     public void setHomeDirectory( File homeDirectory )
113     {
114         homeDir = homeDirectory;
115     }
116 
117     public void setSecretKeyring( String path )
118     {
119         secretKeyring = path;
120     }
121 
122     public void setPublicKeyring( String path )
123     {
124         publicKeyring = path;
125     }
126 
127     public File generateSignatureForArtifact( File file )
128         throws MojoExecutionException
129     {
130         File signature = new File( file + SIGNATURE_EXTENSION );
131 
132         boolean isInBuildDir = false;
133         if ( buildDir != null )
134         {
135             File parent = signature.getParentFile();
136             if ( buildDir.equals( parent ) )
137             {
138                 isInBuildDir = true;
139             }
140         }
141         if ( !isInBuildDir && outputDir != null )
142         {
143             String fileDirectory = "";
144             File signatureDirectory = signature;
145 
146             while ( ( signatureDirectory = signatureDirectory.getParentFile() ) != null )
147             {
148                 if ( !signatureDirectory.equals( baseDir ) )
149                 {
150                     fileDirectory = signatureDirectory.getName() + File.separatorChar + fileDirectory;
151                 }
152                 else
153                 {
154                     break;
155                 }
156             }
157             signatureDirectory = new File( outputDir, fileDirectory );
158             if ( !signatureDirectory.exists() )
159             {
160                 signatureDirectory.mkdirs();
161             }
162             signature = new File( signatureDirectory, file.getName() + SIGNATURE_EXTENSION );
163         }
164 
165         if ( signature.exists() )
166         {
167             signature.delete();
168         }
169 
170         Commandline cmd = new Commandline();
171 
172         if ( StringUtils.isNotEmpty( executable ) )
173         {
174             cmd.setExecutable( executable );
175         }
176         else
177         {
178             cmd.setExecutable( "gpg" + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" ) );
179         }
180 
181         if ( homeDir != null )
182         {
183             cmd.createArg().setValue( "--homedir" );
184             cmd.createArg().setFile( homeDir );
185         }
186 
187         if ( useAgent )
188         {
189             cmd.createArg().setValue( "--use-agent" );
190         }
191         else
192         {
193             cmd.createArg().setValue( "--no-use-agent" );
194         }
195 
196         InputStream in = null;
197         if ( null != passphrase )
198         {
199             // make --passphrase-fd effective in gpg2
200             cmd.createArg().setValue( "--batch" );
201 
202             cmd.createArg().setValue( "--passphrase-fd" );
203 
204             cmd.createArg().setValue( "0" );
205 
206             // Prepare the input stream which will be used to pass the passphrase to the executable
207             in = new ByteArrayInputStream( passphrase.getBytes() );
208         }
209 
210         if ( null != keyname )
211         {
212             cmd.createArg().setValue( "--local-user" );
213 
214             cmd.createArg().setValue( keyname );
215         }
216 
217         cmd.createArg().setValue( "--armor" );
218 
219         cmd.createArg().setValue( "--detach-sign" );
220 
221         if ( !isInteractive )
222         {
223             cmd.createArg().setValue( "--no-tty" );
224         }
225 
226         if ( !defaultKeyring )
227         {
228             cmd.createArg().setValue( "--no-default-keyring" );
229         }
230 
231         if ( StringUtils.isNotEmpty( secretKeyring ) )
232         {
233             cmd.createArg().setValue( "--secret-keyring" );
234             cmd.createArg().setValue( secretKeyring );
235         }
236 
237         if ( StringUtils.isNotEmpty( publicKeyring ) )
238         {
239             cmd.createArg().setValue( "--keyring" );
240             cmd.createArg().setValue( publicKeyring );
241         }
242 
243         cmd.createArg().setValue( "--output" );
244         cmd.createArg().setFile( signature );
245 
246         cmd.createArg().setFile( file );
247 
248         try
249         {
250             int exitCode = CommandLineUtils.executeCommandLine( cmd, in, new DefaultConsumer(), new DefaultConsumer() );
251 
252             if ( exitCode != 0 )
253             {
254                 throw new MojoExecutionException( "Exit code: " + exitCode );
255             }
256         }
257         catch ( CommandLineException e )
258         {
259             throw new MojoExecutionException( "Unable to execute gpg command", e );
260         }
261 
262         return signature;
263     }
264 
265     private MavenProject findReactorProject( MavenProject prj )
266     {
267         if ( prj.getParent() != null && prj.getParent().getBasedir() != null && prj.getParent().getBasedir().exists() )
268         {
269             return findReactorProject( prj.getParent() );
270         }
271         return prj;
272     }
273 
274     public String getPassphrase( MavenProject project )
275         throws IOException
276     {
277         String pass = null;
278 
279         if ( project != null )
280         {
281             pass = project.getProperties().getProperty( "gpg.passphrase" );
282             if ( pass == null )
283             {
284                 MavenProject prj2 = findReactorProject( project );
285                 pass = prj2.getProperties().getProperty( "gpg.passphrase" );
286             }
287         }
288         if ( pass == null )
289         {
290             // TODO: with JDK 1.6, we could call System.console().readPassword("GPG Passphrase: ", null);
291 
292             BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) );
293             while ( System.in.available() != 0 )
294             {
295                 // there's some junk already on the input stream, consume it
296                 // so we can get the real passphrase
297                 System.in.read();
298             }
299 
300             System.out.print( "GPG Passphrase:  " );
301             MaskingThread thread = new MaskingThread();
302             thread.start();
303 
304             pass = in.readLine();
305 
306             // stop masking
307             thread.stopMasking();
308         }
309         if ( project != null )
310         {
311             findReactorProject( project ).getProperties().setProperty( "gpg.passphrase", pass );
312         }
313         return pass;
314     }
315 
316     // based on ideas from http://java.sun.com/developer/technicalArticles/Security/pwordmask/
317     class MaskingThread
318         extends Thread
319     {
320         private volatile boolean stop;
321 
322         /**
323          * Begin masking until asked to stop.
324          */
325         public void run()
326         {
327             // this needs to be high priority to make sure the characters don't
328             // really get to the screen.
329 
330             int priority = Thread.currentThread().getPriority();
331             Thread.currentThread().setPriority( Thread.MAX_PRIORITY );
332 
333             try
334             {
335                 stop = false;
336                 while ( !stop )
337                 {
338                     // print a backspace + * to overwrite anything they type
339                     System.out.print( "\010*" );
340                     try
341                     {
342                         // attempt masking at this rate
343                         Thread.sleep( 1 );
344                     }
345                     catch ( InterruptedException iex )
346                     {
347                         Thread.currentThread().interrupt();
348                         return;
349                     }
350                 }
351             }
352             finally
353             {
354                 // restore the original priority
355                 Thread.currentThread().setPriority( priority );
356             }
357         }
358 
359         /**
360          * Instruct the thread to stop masking.
361          */
362         public void stopMasking()
363         {
364             this.stop = true;
365         }
366     }
367 
368 }