1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package cz.hobrasoft.pdfmu.operation.signature;
18
19 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_KEYSTORE_ALIASES;
20 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_KEYSTORE_ALIAS_EXCEPTION;
21 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_KEYSTORE_ALIAS_KEY_EXCEPTION;
22 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_KEYSTORE_ALIAS_MISSING;
23 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_KEYSTORE_ALIAS_NOT_KEY;
24 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_KEYSTORE_CERTIFICATE_CHAIN;
25 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_KEYSTORE_EMPTY;
26 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_KEYSTORE_PRIVATE_KEY;
27 import cz.hobrasoft.pdfmu.operation.OperationException;
28 import cz.hobrasoft.pdfmu.operation.args.ArgsConfiguration;
29 import cz.hobrasoft.pdfmu.operation.args.PasswordArgs;
30 import java.nio.charset.StandardCharsets;
31 import java.security.KeyStore;
32 import java.security.KeyStoreException;
33 import java.security.NoSuchAlgorithmException;
34 import java.security.PrivateKey;
35 import java.security.UnrecoverableKeyException;
36 import java.security.cert.Certificate;
37 import java.util.AbstractMap.SimpleEntry;
38 import java.util.Enumeration;
39 import java.util.logging.Logger;
40 import net.sourceforge.argparse4j.inf.Argument;
41 import net.sourceforge.argparse4j.inf.ArgumentParser;
42 import net.sourceforge.argparse4j.inf.Namespace;
43
44
45
46
47
48 class KeyParameters implements ArgsConfiguration {
49
50 public String alias = null;
51 public char[] password = null;
52
53
54 private static final Logger logger = Logger.getLogger(KeyParameters.class.getName());
55
56 public PasswordArgs passwordArgs = new PasswordArgs("key password");
57
58 private Argument keyAliasArgument;
59
60 @Override
61 public void addArguments(ArgumentParser parser) {
62 keyAliasArgument = parser.addArgument("--key-alias")
63 .help("key keystore entry alias (default: <first entry in the keystore>)")
64 .type(String.class);
65
66 passwordArgs.passwordArgument = parser.addArgument("--key-password")
67 .help("key password (default: <empty>)");
68 passwordArgs.environmentVariableArgument = parser.addArgument("--key-password-envvar")
69 .help("key password environment variable")
70 .setDefault("PDFMU_KEYPASS");
71 passwordArgs.finalizeArguments();
72 }
73
74 @Override
75 public void setFromNamespace(Namespace namespace) {
76 assert keyAliasArgument != null;
77 alias = namespace.getString(keyAliasArgument.getDest());
78
79
80 assert passwordArgs != null;
81 passwordArgs.setFromNamespace(namespace);
82 password = passwordArgs.getPasswordCharArray();
83
84 }
85
86
87
88
89
90
91
92 private void fixAliasWcs(KeyStore ks) throws OperationException {
93 assert "Windows-MY".equals(ks.getType());
94 if (alias != null) {
95 logger.info(String.format("WCS alias correction will be applied. Original alias: %s", alias));
96 alias = new String(alias.getBytes(), StandardCharsets.ISO_8859_1);
97
98 Enumeration<String> aliases = null;
99 try {
100 aliases = ks.aliases();
101 } catch (KeyStoreException ex) {
102 throw new OperationException(SIGNATURE_ADD_KEYSTORE_ALIASES, ex);
103 }
104 String aliasBest = null;
105 while (aliases.hasMoreElements()) {
106 String aliasCandidate = aliases.nextElement();
107 if (alias.startsWith(aliasCandidate)
108 && (aliasBest == null
109 || aliasCandidate.length() > aliasBest.length())) {
110 aliasBest = aliasCandidate;
111 }
112 }
113 if (aliasBest != null) {
114 alias = aliasBest;
115 }
116 }
117 }
118
119 public void fixAlias(KeyStore ks) throws OperationException {
120 if (alias == null) {
121
122 logger.info("Keystore entry alias not set. Using the first entry in the keystore.");
123 Enumeration<String> aliases;
124 try {
125 aliases = ks.aliases();
126 } catch (KeyStoreException ex) {
127 throw new OperationException(SIGNATURE_ADD_KEYSTORE_ALIASES, ex);
128 }
129 if (!aliases.hasMoreElements()) {
130 throw new OperationException(SIGNATURE_ADD_KEYSTORE_EMPTY);
131 }
132 alias = aliases.nextElement();
133 assert alias != null;
134 } else if ("Windows-MY".equals(ks.getType())) {
135 fixAliasWcs(ks);
136 }
137 logger.info(String.format("Keystore entry alias: %s", alias));
138
139
140 try {
141 if (!ks.containsAlias(alias)) {
142 throw new OperationException(SIGNATURE_ADD_KEYSTORE_ALIAS_MISSING,
143 new SimpleEntry<String, Object>("alias", alias));
144 }
145 } catch (KeyStoreException ex) {
146 throw new OperationException(SIGNATURE_ADD_KEYSTORE_ALIAS_EXCEPTION, ex,
147 new SimpleEntry<String, Object>("alias", alias));
148 }
149
150
151 try {
152 if (!ks.isKeyEntry(alias)) {
153 throw new OperationException(SIGNATURE_ADD_KEYSTORE_ALIAS_NOT_KEY,
154 new SimpleEntry<String, Object>("alias", alias));
155 }
156 } catch (KeyStoreException ex) {
157 throw new OperationException(SIGNATURE_ADD_KEYSTORE_ALIAS_KEY_EXCEPTION, ex,
158 new SimpleEntry<String, Object>("alias", alias));
159 }
160 }
161
162 public void fixPassword(KeyStore ks, String ksPassword) {
163 switch (ks.getType()) {
164 case "Windows-MY":
165 if (password != null) {
166 logger.info("The keystore Windows-MY does not accept key password.");
167 password = null;
168 }
169 break;
170 case "pkcs12":
171 if (ksPassword != null) {
172 if (password != null && password != ksPassword.toCharArray()) {
173 logger.warning("In PKCS12 keystores, key password should not differ from the keystore password.");
174 }
175 if (password == null) {
176 logger.info("Key password not set. Using the keystore password.");
177 password = ksPassword.toCharArray();
178 }
179 } else if (password == null) {
180 logger.info("Key password not set. Using an empty password.");
181 password = "".toCharArray();
182 }
183 break;
184 default:
185
186 if (password == null) {
187 logger.info("Key password not set. Using an empty password.");
188 password = "".toCharArray();
189 }
190 }
191 }
192
193 public void fix(KeyStore ks, String ksPassword) throws OperationException {
194 fixAlias(ks);
195 fixPassword(ks, ksPassword);
196 }
197
198 private int countAliasOccurrences(KeyStore ks, String alias) throws KeyStoreException {
199 if (!ks.containsAlias(alias)) {
200 return 0;
201 }
202 Enumeration<String> aliases = ks.aliases();
203 int count = 0;
204 while (aliases.hasMoreElements()) {
205 if (alias.equals(aliases.nextElement())) {
206 ++count;
207 }
208 }
209 assert (count > 0);
210 return count;
211 }
212
213 private boolean isAliasDuplicit(KeyStore ks, String alias) throws KeyStoreException {
214 return countAliasOccurrences(ks, alias) > 1;
215 }
216
217 public PrivateKey getPrivateKey(KeyStore ks) throws OperationException {
218
219 PrivateKey pk;
220 try {
221 if (isAliasDuplicit(ks, alias)) {
222 logger.warning(String.format("The key alias \"%1$s\" occurs multiple times in the keystore.", alias));
223 }
224
225 pk = (PrivateKey) ks.getKey(alias, password);
226 } catch (KeyStoreException ex) {
227 throw new OperationException(SIGNATURE_ADD_KEYSTORE_PRIVATE_KEY, ex,
228 new SimpleEntry<String, Object>("alias", alias));
229 } catch (NoSuchAlgorithmException ex) {
230 throw new OperationException(SIGNATURE_ADD_KEYSTORE_PRIVATE_KEY, ex,
231 new SimpleEntry<String, Object>("alias", alias));
232 } catch (UnrecoverableKeyException ex) {
233
234 throw new OperationException(SIGNATURE_ADD_KEYSTORE_PRIVATE_KEY, ex,
235 new SimpleEntry<String, Object>("alias", alias));
236 }
237 if (pk == null) {
238
239 throw new OperationException(SIGNATURE_ADD_KEYSTORE_PRIVATE_KEY,
240 new SimpleEntry<String, Object>("alias", alias));
241 }
242 return pk;
243 }
244
245 public Certificate[] getCertificateChain(KeyStore ks) throws OperationException {
246 Certificate[] chain;
247 try {
248 chain = ks.getCertificateChain(alias);
249 } catch (KeyStoreException ex) {
250 throw new OperationException(SIGNATURE_ADD_KEYSTORE_CERTIFICATE_CHAIN, ex,
251 new SimpleEntry<String, Object>("alias", alias));
252 }
253 return chain;
254 }
255
256 }