1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.generator.gnupg.loaders;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.io.BufferedReader;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.net.SocketException;
29 import java.net.StandardProtocolFamily;
30 import java.net.UnixDomainSocketAddress;
31 import java.nio.channels.Channels;
32 import java.nio.channels.SocketChannel;
33 import java.nio.file.Path;
34 import java.nio.file.Paths;
35 import java.util.Arrays;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.stream.Collectors;
39
40 import org.bouncycastle.util.encoders.Hex;
41 import org.eclipse.aether.ConfigurationProperties;
42 import org.eclipse.aether.RepositorySystemSession;
43 import org.eclipse.aether.generator.gnupg.GnupgSignatureArtifactGeneratorFactory;
44 import org.eclipse.aether.util.ConfigUtils;
45 import org.eclipse.sisu.Priority;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import static org.eclipse.aether.generator.gnupg.GnupgConfigurationKeys.CONFIG_PROP_AGENT_SOCKET_LOCATIONS;
50 import static org.eclipse.aether.generator.gnupg.GnupgConfigurationKeys.CONFIG_PROP_USE_AGENT;
51 import static org.eclipse.aether.generator.gnupg.GnupgConfigurationKeys.DEFAULT_AGENT_SOCKET_LOCATIONS;
52 import static org.eclipse.aether.generator.gnupg.GnupgConfigurationKeys.DEFAULT_USE_AGENT;
53
54
55
56
57 @Singleton
58 @Named(GpgAgentPasswordLoader.NAME)
59 @Priority(10)
60 @SuppressWarnings("checkstyle:magicnumber")
61 public final class GpgAgentPasswordLoader implements GnupgSignatureArtifactGeneratorFactory.Loader {
62 public static final String NAME = "agent";
63 private final Logger logger = LoggerFactory.getLogger(getClass());
64
65 @Override
66 public char[] loadPassword(RepositorySystemSession session, byte[] fingerprint) throws IOException {
67 if (!ConfigUtils.getBoolean(session, DEFAULT_USE_AGENT, CONFIG_PROP_USE_AGENT)) {
68 return null;
69 }
70 String socketLocationsStr =
71 ConfigUtils.getString(session, DEFAULT_AGENT_SOCKET_LOCATIONS, CONFIG_PROP_AGENT_SOCKET_LOCATIONS);
72 boolean interactive = ConfigUtils.getBoolean(
73 session, ConfigurationProperties.DEFAULT_INTERACTIVE, ConfigurationProperties.INTERACTIVE);
74 List<String> socketLocations = Arrays.stream(socketLocationsStr.split(","))
75 .filter(s -> s != null && !s.isEmpty())
76 .collect(Collectors.toList());
77 for (String socketLocation : socketLocations) {
78 try {
79 Path socketLocationPath = Paths.get(socketLocation);
80 if (!socketLocationPath.isAbsolute()) {
81 socketLocationPath = Paths.get(System.getProperty("user.home"))
82 .resolve(socketLocationPath)
83 .toAbsolutePath();
84 }
85 return load(fingerprint, socketLocationPath, interactive);
86 } catch (SocketException e) {
87
88 logger.debug("Problem communicating with agent on socket: {}", socketLocation, e);
89 }
90 }
91 logger.warn("Could not connect to agent on any of the configured sockets: {}", socketLocations);
92 return null;
93 }
94
95 private char[] load(byte[] fingerprint, Path socketPath, boolean interactive) throws IOException {
96 try (SocketChannel sock = SocketChannel.open(StandardProtocolFamily.UNIX)) {
97 sock.connect(UnixDomainSocketAddress.of(socketPath));
98 try (BufferedReader in = new BufferedReader(new InputStreamReader(Channels.newInputStream(sock)));
99 OutputStream os = Channels.newOutputStream(sock)) {
100
101 expectOK(in);
102 String display = System.getenv("DISPLAY");
103 if (display != null) {
104 os.write(("OPTION display=" + display + "\n").getBytes());
105 os.flush();
106 expectOK(in);
107 }
108 String term = System.getenv("TERM");
109 if (term != null) {
110 os.write(("OPTION ttytype=" + term + "\n").getBytes());
111 os.flush();
112 expectOK(in);
113 }
114 String hexKeyFingerprint = Hex.toHexString(fingerprint);
115 String displayFingerprint = hexKeyFingerprint.toUpperCase(Locale.ROOT);
116
117 String instruction = "GET_PASSPHRASE "
118 + (!interactive ? "--no-ask " : "")
119 + hexKeyFingerprint
120 + " "
121 + "X "
122 + "GnuPG+Passphrase "
123 + "Please+enter+the+passphrase+to+unlock+the+OpenPGP+secret+key+with+fingerprint:+"
124 + displayFingerprint
125 + "+to+use+it+for+signing+Maven+Artifacts\n";
126 os.write((instruction).getBytes());
127 os.flush();
128 return mayExpectOK(in);
129 }
130 }
131 }
132
133 private void expectOK(BufferedReader in) throws IOException {
134 String response = in.readLine();
135 if (!response.startsWith("OK")) {
136 throw new IOException("Expected OK but got this instead: " + response);
137 }
138 }
139
140 private char[] mayExpectOK(BufferedReader in) throws IOException {
141 String response = in.readLine();
142 if (response.startsWith("ERR")) {
143 return null;
144 } else if (!response.startsWith("OK")) {
145 throw new IOException("Expected OK/ERR but got this instead: " + response);
146 }
147 return new String(Hex.decode(
148 response.substring(Math.min(response.length(), 3)).trim()))
149 .toCharArray();
150 }
151 }