/
CertFilterManager.java
310 lines (272 loc) · 15.3 KB
/
CertFilterManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/* Copyright (C) 2011 [Gobierno de Espana]
* This file is part of "Cliente @Firma".
* "Cliente @Firma" is free software; you can redistribute it and/or modify it under the terms of:
* - the GNU General Public License as published by the Free Software Foundation;
* either version 2 of the License, or (at your option) any later version.
* - or The European Software License; either version 1.1 or (at your option) any later version.
* You may contact the copyright holder at: soporte.afirma@seap.minhap.es
*/
package es.gob.afirma.keystores.filters;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;
import es.gob.afirma.keystores.CertificateFilter;
import es.gob.afirma.keystores.MultipleCertificateFilter;
import es.gob.afirma.keystores.filters.rfc.KeyUsageFilter;
import es.gob.afirma.keystores.filters.rfc.RFC2254CertificateFilter;
import es.gob.afirma.keystores.filters.rfc.SscdFilter;
/** Identifica y obtiene los filtros de certificados definidos en las propiedades
* de una operación de firma/multifirma electrónica. *
* @author Carlos Gamuci Millán. */
public final class CertFilterManager {
private static final String HEADLESS_PROPERTY = "headless"; //$NON-NLS-1$
private static final String MANDATORY_CERT_SELECTION_PROPERTY = "mandatoryCertSelection"; //$NON-NLS-1$
private static final String FILTER_PREFIX_KEY = "filter"; //$NON-NLS-1$
private static final String FILTERS_PREFIX_KEY = "filters"; //$NON-NLS-1$
private static final String FILTERS_ENUM_SEPARATOR = "."; //$NON-NLS-1$
private static final String FILTERS_SEPARATOR = ";"; //$NON-NLS-1$
/** Filtro especial que indica que no debe permitirse la apertura por parte del usuario de almacenes
* de claves distintos al por defecto. */
private static final String FILTER_TYPE_DISABLE_EXTERNAL_STORES = "disableopeningexternalstores"; //$NON-NLS-1$
private static final String FILTER_TYPE_DNIE = "dnie:"; //$NON-NLS-1$
private static final String FILTER_TYPE_SSL = "ssl:"; //$NON-NLS-1$
private static final String FILTER_TYPE_QUALIFIED = "qualified:"; //$NON-NLS-1$
private static final String FILTER_TYPE_SIGNING_CERTIFICATE = "signingcert:"; //$NON-NLS-1$
private static final String FILTER_TYPE_AUTHENTICATION_CERTIFICATE = "authcert:"; //$NON-NLS-1$
private static final String FILTER_TYPE_NON_EXPIRED = "nonexpired:"; //$NON-NLS-1$
private static final String FILTER_TYPE_SSCD = "sscd:"; //$NON-NLS-1$
private static final String FILTER_TYPE_SUBJECT_RFC2254 = "subject.rfc2254:"; //$NON-NLS-1$
private static final String FILTER_TYPE_SUBJECT_CONTAINS = "subject.contains:"; //$NON-NLS-1$
private static final String FILTER_TYPE_ISSUER_RFC2254 = "issuer.rfc2254:"; //$NON-NLS-1$
private static final String FILTER_TYPE_ISSUER_RFC2254_RECURSE = "issuer.rfc2254.recurse:"; //$NON-NLS-1$
private static final String FILTER_TYPE_ISSUER_CONTAINS = "issuer.contains:"; //$NON-NLS-1$
private static final String FILTER_TYPE_THUMBPRINT = "thumbprint:"; //$NON-NLS-1$
private static final String FILTER_TYPE_POLICY_ID = "policyid:"; //$NON-NLS-1$
private static final String FILTER_TYPE_PSEUDONYM = "pseudonym:"; //$NON-NLS-1$
private static final String FILTER_TYPE_ENCODED_CERT = "encodedcert:"; //$NON-NLS-1$
private static final String FILTER_PREFIX_KEYUSAGE = "keyusage."; //$NON-NLS-1$
private static final String FILTER_TYPE_KEYUSAGE_DIGITAL_SIGNATURE = FILTER_PREFIX_KEYUSAGE + "digitalsignature:"; //$NON-NLS-1$
private static final String FILTER_TYPE_KEYUSAGE_NON_REPUDIATION = FILTER_PREFIX_KEYUSAGE + "nonrepudiation:"; //$NON-NLS-1$
private static final String FILTER_TYPE_KEYUSAGE_KEY_ENCIPHERMENT = FILTER_PREFIX_KEYUSAGE + "keyencipherment:"; //$NON-NLS-1$
private static final String FILTER_TYPE_KEYUSAGE_DATA_ENCIPHERMENT = FILTER_PREFIX_KEYUSAGE + "dataencipherment:"; //$NON-NLS-1$
private static final String FILTER_TYPE_KEYUSAGE_KEY_AGREEMENT = FILTER_PREFIX_KEYUSAGE + "keyagreement:"; //$NON-NLS-1$
private static final String FILTER_TYPE_KEYUSAGE_KEY_CERT_SIGN = FILTER_PREFIX_KEYUSAGE + "keycertsign:"; //$NON-NLS-1$
private static final String FILTER_TYPE_KEYUSAGE_CRL_SIGN = FILTER_PREFIX_KEYUSAGE + "crlsign:"; //$NON-NLS-1$
private static final String FILTER_TYPE_KEYUSAGE_ENCIPHER_ONLY = FILTER_PREFIX_KEYUSAGE + "encipheronly:"; //$NON-NLS-1$
private static final String FILTER_TYPE_KEYUSAGE_DECIPHER_ONLY = FILTER_PREFIX_KEYUSAGE + "decipheronly:"; //$NON-NLS-1$
private static final String[] PATTERN_KEYUSAGES_FILTER = new String[] {
FILTER_TYPE_KEYUSAGE_DIGITAL_SIGNATURE,
FILTER_TYPE_KEYUSAGE_NON_REPUDIATION,
FILTER_TYPE_KEYUSAGE_KEY_ENCIPHERMENT,
FILTER_TYPE_KEYUSAGE_DATA_ENCIPHERMENT,
FILTER_TYPE_KEYUSAGE_KEY_AGREEMENT,
FILTER_TYPE_KEYUSAGE_KEY_CERT_SIGN,
FILTER_TYPE_KEYUSAGE_CRL_SIGN,
FILTER_TYPE_KEYUSAGE_ENCIPHER_ONLY,
FILTER_TYPE_KEYUSAGE_DECIPHER_ONLY
};
private static final Logger LOGGER = Logger.getLogger("es.gob.afirma"); //$NON-NLS-1$
private boolean mandatoryCertificate = false;
private final List<CertificateFilter> filters = new ArrayList<>();
private boolean allowExternalStores = true;
/** Identifica los filtros que deben aplicarse sobre una serie de certificados para
* comprobar cuales de ellos se ajustan a nuestra necesidades.
* @param propertyFilters Listado de propiedades entre las que identificar las que
* establecen los criterios de filtrado. */
public CertFilterManager(final Properties propertyFilters) {
this.mandatoryCertificate = isMandatoryCertificate(propertyFilters);
// Obtenemos los distintos filtros disyuntivos declarados
final List<String> filterValues = getFilterValues(propertyFilters);
// Agregamos el filtro correspondiente a cada uno de los filtros desclarados
for (final String filterValue : filterValues) {
this.filters.add(parseFilter(filterValue));
}
// Siguiendo los criterios de la ETSI TS 119 102-1, un usuario no deberia firmar nunca con
// un certificado caducado, asi que, si no se definio ningun tipo de filtrado, se agregara
// un filtro omitiendo estos certificados. Si se agregaron filtros, se considerara que es
// el integrador estara definiendo sus preferencias concretas de filtrado
if (this.filters.isEmpty()) {
this.filters.add(new ExpiredCertificateFilter(false));
}
}
/**
* Indica si las propiedades establecidas para la operación indican que se debe omitir
* la selección de certificado si es posible.
* @param propertyFilters Propiedades de configuración de la operación.
* @return {@code true} si se debe seleccionar el certificado automáticamente;,
* {@code false} cuando se deba mostrar el diálogo de selección.
*/
private static boolean isMandatoryCertificate(final Properties propertyFilters) {
final boolean headless = propertyFilters != null
&& Boolean.parseBoolean(propertyFilters.getProperty(HEADLESS_PROPERTY));
final boolean omitSelection = propertyFilters != null
&& propertyFilters.containsKey(MANDATORY_CERT_SELECTION_PROPERTY)
&& Boolean.FALSE.toString().equalsIgnoreCase(
propertyFilters.getProperty(MANDATORY_CERT_SELECTION_PROPERTY));
return headless || omitSelection;
}
/** Indica si la apertura de almacenes externos al principal desde el UI del diálogo está
* permitida o prohibida.
* @return <code>true</code> si se permite la apertura de almacenes externos,
* <code>false</code> en caso contrario. */
public boolean isExternalStoresOpeningAllowed() {
return this.allowExternalStores;
}
/** Recoge los distintos filtros declarados en el parámetro de configuración.
* @param config Configuración de la operación.
* @return Listado de filtros disyuntivos para aplicar al listado decertificados. */
private static List<String> getFilterValues(final Properties config) {
final List<String> filterValues = new ArrayList<>();
if (config.containsKey(FILTER_PREFIX_KEY)) {
filterValues.add(config.getProperty(FILTER_PREFIX_KEY));
}
else if (config.containsKey(FILTERS_PREFIX_KEY)) {
filterValues.add(config.getProperty(FILTERS_PREFIX_KEY));
}
else if (config.containsKey(FILTERS_PREFIX_KEY + FILTERS_ENUM_SEPARATOR + 1)) {
int i = 1;
while (config.containsKey(FILTERS_PREFIX_KEY + FILTERS_ENUM_SEPARATOR + i)) {
filterValues.add(config.getProperty(FILTERS_PREFIX_KEY + FILTERS_ENUM_SEPARATOR + i));
i++;
}
}
return filterValues;
}
private CertificateFilter parseFilter(final String filterValue) {
final List<CertificateFilter> filtersList = new ArrayList<>();
final String[] sortedFilterValues = filterValue.split(FILTERS_SEPARATOR);
// Se ordena para que los KeyUsage esten consecutivos
Arrays.sort(sortedFilterValues);
for (int i = 0; i < sortedFilterValues.length; i++) {
final String filter = sortedFilterValues[i];
if (filter.toLowerCase().startsWith(FILTER_TYPE_DNIE)) {
filtersList.add(new SignatureDNIeFilter());
}
else if (filter.toLowerCase().equals(FILTER_TYPE_DISABLE_EXTERNAL_STORES)) {
this.allowExternalStores = false;
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_SSL)) {
filtersList.add(new SSLFilter(filter.substring(FILTER_TYPE_SSL.length())));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_QUALIFIED)) {
filtersList.add(new QualifiedCertificatesFilter(filter.substring(FILTER_TYPE_QUALIFIED.length())));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_SIGNING_CERTIFICATE)) {
filtersList.add(new SigningCertificateFilter());
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_AUTHENTICATION_CERTIFICATE)) {
filtersList.add(new AuthCertificateFilter());
}
else if (filter.toLowerCase().startsWith(FILTER_PREFIX_KEYUSAGE)) {
final Boolean[] kuPattern = generateKeyUsageFiltersPattern(sortedFilterValues, i);
filtersList.add(new KeyUsageFilter(kuPattern));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_NON_EXPIRED)) {
final boolean showExpired = filter.equalsIgnoreCase(FILTER_TYPE_NON_EXPIRED) ?
false : !Boolean.parseBoolean(filter.substring(FILTER_TYPE_NON_EXPIRED.length()));
filtersList.add(new ExpiredCertificateFilter(showExpired));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_SSCD)) {
filtersList.add(new SscdFilter());
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_SUBJECT_RFC2254)) {
filtersList.add(new RFC2254CertificateFilter(filter.substring(FILTER_TYPE_SUBJECT_RFC2254.length()), null));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_SUBJECT_CONTAINS)) {
filtersList.add(new TextContainedCertificateFilter(new String[] { filter.substring(FILTER_TYPE_SUBJECT_CONTAINS.length()) }, null));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_ISSUER_RFC2254)) {
filtersList.add(new RFC2254CertificateFilter(null, filter.substring(FILTER_TYPE_ISSUER_RFC2254.length())));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_ISSUER_RFC2254_RECURSE)) {
filtersList.add(new RFC2254CertificateFilter(null, filter.substring(FILTER_TYPE_ISSUER_RFC2254_RECURSE.length()), true));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_ISSUER_CONTAINS)) {
filtersList.add(new TextContainedCertificateFilter(null, new String[] { filter.substring(FILTER_TYPE_ISSUER_CONTAINS.length()) }));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_THUMBPRINT)) {
final String[] params = filter.substring(FILTER_TYPE_THUMBPRINT.length()).split(":"); //$NON-NLS-1$
if (params.length == 2) {
filtersList.add(new ThumbPrintCertificateFilter(params[0], params.length > 1 ? params[1] : null));
}
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_POLICY_ID)) {
final String oids = filter.substring(FILTER_TYPE_POLICY_ID.length());
if (oids != null && !oids.isEmpty()) {
filtersList.add(new PolicyIdFilter(Arrays.asList(oids.split(",")))); //$NON-NLS-1$
}
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_PSEUDONYM)) {
String value = PseudonymFilter.VALUE_PSEUDONYM_AND_OTHERS;
if (!filter.toLowerCase().equals(FILTER_TYPE_PSEUDONYM)) {
value = filter.substring(FILTER_TYPE_PSEUDONYM.length());
}
filtersList.add(new PseudonymFilter(value));
}
else if (filter.toLowerCase().startsWith(FILTER_TYPE_ENCODED_CERT)) {
filtersList.add(new EncodedCertificateFilter(filter.substring(FILTER_TYPE_ENCODED_CERT.length())));
}
else {
LOGGER.warning("Se omitira el filtro '" + filter + "' por no estar reconocido"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
return filtersList.size() == 1 ?
filtersList.get(0) :
new MultipleCertificateFilter(filtersList.toArray(new CertificateFilter[filtersList.size()]));
}
/** Obtiene el patrón que define el filtro por <i>KeyUsage</i>, que de
* cara al exterior era un conjunto de filtros distintos.
* @param sortedFilterValues Listado de filtros definidos ordenados
* alfabéticamente.
* @param pos Posición del primer identificador de filtro de <i>KeyUsage</i>.
* @return Patrón para definir el filtro de <i>KeyUsages</i>. */
private static Boolean[] generateKeyUsageFiltersPattern(final String[] sortedFilterValues, final int pos) {
int i = pos;
String filter = sortedFilterValues[pos];
final Boolean[] kuPattern = new Boolean[PATTERN_KEYUSAGES_FILTER.length];
do {
processKeyUsageFilterDeclaration(filter, kuPattern);
if (sortedFilterValues.length > i + 1 && sortedFilterValues[i + 1].startsWith(FILTER_PREFIX_KEYUSAGE)) {
filter = sortedFilterValues[++i];
}
else {
break;
}
// Nos valemos de que los filtros se han ordenado alfabeticamente,
// asi que todos los filtros de KeyUsage estaran juntos
} while (filter.toLowerCase().startsWith(FILTER_PREFIX_KEYUSAGE));
return kuPattern;
}
private static void processKeyUsageFilterDeclaration (final String filter, final Boolean[] kuPattern) {
int patternPosition = -1;
for (int i = 0; patternPosition < 0 && i < PATTERN_KEYUSAGES_FILTER.length; i++) {
if (filter.toLowerCase().startsWith(PATTERN_KEYUSAGES_FILTER[i])) {
patternPosition = i;
}
}
if (patternPosition >= 0) {
final String value = filter.substring(filter.indexOf(':') + 1);
kuPattern[patternPosition] = value.equalsIgnoreCase("null") ? null : Boolean.valueOf(value); //$NON-NLS-1$
}
}
/** Devuelve la lista de certificados definidos.
* @return Listado de certificados. */
public List<CertificateFilter> getFilters() {
return new ArrayList<>(this.filters);
}
/** Indica si se debe seleccionar automáticamente un certificado si es el único que
* cumple los filtros.
* @return {@code true} si debe seleccionarse automáticamente el único certificado
* que supera el filtrado, {@code false} en caso contrario. */
public boolean isMandatoryCertificate() {
return this.mandatoryCertificate;
}
public static void main(final String[] args) {
final Properties config = new Properties();
//config.setProperty(HEADLESS_PROPERTY, "true");
config.setProperty(MANDATORY_CERT_SELECTION_PROPERTY, "true");
System.out.println(CertFilterManager.isMandatoryCertificate(config));
}
}