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 public final class GpgAgentPasswordLoader implements GnupgSignatureArtifactGeneratorFactory.Loader {
61 public static final String NAME = "agent";
62 private final Logger logger = LoggerFactory.getLogger(getClass());
63
64 @Override
65 public char[] loadPassword(RepositorySystemSession session, byte[] fingerprint) throws IOException {
66 if (!ConfigUtils.getBoolean(session, DEFAULT_USE_AGENT, CONFIG_PROP_USE_AGENT)) {
67 return null;
68 }
69 String socketLocationsStr =
70 ConfigUtils.getString(session, DEFAULT_AGENT_SOCKET_LOCATIONS, CONFIG_PROP_AGENT_SOCKET_LOCATIONS);
71 boolean interactive = ConfigUtils.getBoolean(
72 session, ConfigurationProperties.DEFAULT_INTERACTIVE, ConfigurationProperties.INTERACTIVE);
73 List<String> socketLocations = Arrays.stream(socketLocationsStr.split(","))
74 .filter(s -> s != null && !s.isEmpty())
75 .collect(Collectors.toList());
76 for (String socketLocation : socketLocations) {
77 try {
78 Path socketLocationPath = Paths.get(socketLocation);
79 if (!socketLocationPath.isAbsolute()) {
80 socketLocationPath = Paths.get(System.getProperty("user.home"))
81 .resolve(socketLocationPath)
82 .toAbsolutePath();
83 }
84 return load(fingerprint, socketLocationPath, interactive);
85 } catch (SocketException e) {
86
87 logger.debug("Problem communicating with agent on socket: {}", socketLocation, e);
88 }
89 }
90 logger.warn("Could not connect to agent on any of the configured sockets: {}", socketLocations);
91 return null;
92 }
93
94 private char[] load(byte[] fingerprint, Path socketPath, boolean interactive) throws IOException {
95 try (SocketChannel sock = SocketChannel.open(StandardProtocolFamily.UNIX)) {
96 sock.connect(UnixDomainSocketAddress.of(socketPath));
97 try (BufferedReader in = new BufferedReader(new InputStreamReader(Channels.newInputStream(sock)));
98 OutputStream os = Channels.newOutputStream(sock)) {
99
100 expectOK(in);
101 String display = System.getenv("DISPLAY");
102 if (display != null) {
103 os.write(("OPTION display=" + display + "\n").getBytes());
104 os.flush();
105 expectOK(in);
106 }
107 String term = System.getenv("TERM");
108 if (term != null) {
109 os.write(("OPTION ttytype=" + term + "\n").getBytes());
110 os.flush();
111 expectOK(in);
112 }
113 String hexKeyFingerprint = Hex.toHexString(fingerprint);
114 String displayFingerprint = hexKeyFingerprint.toUpperCase(Locale.ROOT);
115
116 String instruction = "GET_PASSPHRASE "
117 + (!interactive ? "--no-ask " : "")
118 + hexKeyFingerprint
119 + " "
120 + "X "
121 + "GnuPG+Passphrase "
122 + "Please+enter+the+passphrase+to+unlock+the+OpenPGP+secret+key+with+fingerprint:+"
123 + displayFingerprint
124 + "+to+use+it+for+signing+Maven+Artifacts\n";
125 os.write((instruction).getBytes());
126 os.flush();
127 return mayExpectOK(in);
128 }
129 }
130 }
131
132 private void expectOK(BufferedReader in) throws IOException {
133 String response = in.readLine();
134 if (!response.startsWith("OK")) {
135 throw new IOException("Expected OK but got this instead: " + response);
136 }
137 }
138
139 private char[] mayExpectOK(BufferedReader in) throws IOException {
140 String response = in.readLine();
141 if (response.startsWith("ERR")) {
142 return null;
143 } else if (!response.startsWith("OK")) {
144 throw new IOException("Expected OK/ERR but got this instead: " + response);
145 }
146 return new String(Hex.decode(
147 response.substring(Math.min(response.length(), 3)).trim()))
148 .toCharArray();
149 }
150 }