1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.security;
19
20import java.io.BufferedInputStream;
21import java.io.InputStream;
22import java.util.ArrayList;
23import java.util.Enumeration;
24import java.util.HashMap;
25import java.util.HashSet;
26import java.util.Iterator;
27import java.util.List;
28import java.util.Map;
29import java.util.Map.Entry;
30import java.util.Properties;
31import java.util.Set;
32import org.apache.harmony.security.fortress.Engine;
33import org.apache.harmony.security.fortress.SecurityAccess;
34import org.apache.harmony.security.fortress.Services;
35
36/**
37 * {@code Security} is the central class in the Java Security API. It manages
38 * the list of security {@code Provider} that have been installed into this
39 * runtime environment.
40 */
41public final class Security {
42
43    // Security properties
44    private static final Properties secprops = new Properties();
45
46    // static initialization
47    // - load security properties files
48    // - load statically registered providers
49    // - if no provider description file found then load default providers
50    static {
51        boolean loaded = false;
52        try {
53            InputStream configStream = Security.class.getResourceAsStream("security.properties");
54            InputStream input = new BufferedInputStream(configStream);
55            secprops.load(input);
56            loaded = true;
57            configStream.close();
58        } catch (Exception ex) {
59            System.logE("Could not load 'security.properties'", ex);
60        }
61        if (!loaded) {
62            registerDefaultProviders();
63        }
64        Engine.door = new SecurityDoor();
65    }
66
67    /**
68     * This class can't be instantiated.
69     */
70    private Security() {
71    }
72
73    // Register default providers
74    private static void registerDefaultProviders() {
75        secprops.put("security.provider.1", "com.android.org.conscrypt.OpenSSLProvider");
76        secprops.put("security.provider.2", "com.android.org.bouncycastle.jce.provider.BouncyCastleProvider");
77        secprops.put("security.provider.3", "org.apache.harmony.security.provider.crypto.CryptoProvider");
78        secprops.put("security.provider.4", "com.android.org.conscrypt.JSSEProvider");
79    }
80
81    /**
82     * Returns value for the specified algorithm with the specified name.
83     *
84     * @param algName
85     *            the name of the algorithm.
86     * @param propName
87     *            the name of the property.
88     * @return value of the property.
89     * @deprecated Use {@link AlgorithmParameters} and {@link KeyFactory} instead.
90     */
91    @Deprecated
92    public static String getAlgorithmProperty(String algName, String propName) {
93        if (algName == null || propName == null) {
94            return null;
95        }
96        String prop = "Alg." + propName + "." + algName;
97        Provider[] providers = getProviders();
98        for (Provider provider : providers) {
99            for (Enumeration<?> e = provider.propertyNames(); e.hasMoreElements();) {
100                String propertyName = (String) e.nextElement();
101                if (propertyName.equalsIgnoreCase(prop)) {
102                    return provider.getProperty(propertyName);
103                }
104            }
105        }
106        return null;
107    }
108
109    /**
110     * Insert the given {@code Provider} at the specified {@code position}. The
111     * positions define the preference order in which providers are searched for
112     * requested algorithms.
113     *
114     * @param provider
115     *            the provider to insert.
116     * @param position
117     *            the position (starting from 1).
118     * @return the actual position or {@code -1} if the given {@code provider}
119     *         was already in the list. The actual position may be different
120     *         from the desired position.
121     */
122    public static synchronized int insertProviderAt(Provider provider, int position) {
123        // check that provider is not already
124        // installed, else return -1; if (position <1) or (position > max
125        // position) position = max position + 1; insert provider, shift up
126        // one position for next providers; Note: The position is 1-based
127        if (getProvider(provider.getName()) != null) {
128            return -1;
129        }
130        int result = Services.insertProviderAt(provider, position);
131        renumProviders();
132        return result;
133    }
134
135    /**
136     * Adds the given {@code provider} to the collection of providers at the
137     * next available position.
138     *
139     * @param provider
140     *            the provider to be added.
141     * @return the actual position or {@code -1} if the given {@code provider}
142     *         was already in the list.
143     */
144    public static int addProvider(Provider provider) {
145        return insertProviderAt(provider, 0);
146    }
147
148    /**
149     * Removes the {@code Provider} with the specified name form the collection
150     * of providers. If the the {@code Provider} with the specified name is
151     * removed, all provider at a greater position are shifted down one
152     * position.
153     *
154     * <p>Returns silently if {@code name} is {@code null} or no provider with the
155     * specified name is installed.
156     *
157     * @param name
158     *            the name of the provider to remove.
159     */
160    public static synchronized void removeProvider(String name) {
161        // It is not clear from spec.:
162        // 1. if name is null, should we checkSecurityAccess or not?
163        //    throw SecurityException or not?
164        // 2. as 1 but provider is not installed
165        // 3. behavior if name is empty string?
166
167        Provider p;
168        if ((name == null) || (name.length() == 0)) {
169            return;
170        }
171        p = getProvider(name);
172        if (p == null) {
173            return;
174        }
175        Services.removeProvider(p.getProviderNumber());
176        renumProviders();
177        p.setProviderNumber(-1);
178    }
179
180    /**
181     * Returns an array containing all installed providers. The providers are
182     * ordered according their preference order.
183     *
184     * @return an array containing all installed providers.
185     */
186    public static synchronized Provider[] getProviders() {
187        ArrayList<Provider> providers = Services.getProviders();
188        return providers.toArray(new Provider[providers.size()]);
189    }
190
191    /**
192     * Returns the {@code Provider} with the specified name. Returns {@code
193     * null} if name is {@code null} or no provider with the specified name is
194     * installed.
195     *
196     * @param name
197     *            the name of the requested provider.
198     * @return the provider with the specified name, maybe {@code null}.
199     */
200    public static synchronized Provider getProvider(String name) {
201        return Services.getProvider(name);
202    }
203
204    /**
205     * Returns the array of providers which meet the user supplied string
206     * filter. The specified filter must be supplied in one of two formats:
207     * <nl>
208     * <li> CRYPTO_SERVICE_NAME.ALGORITHM_OR_TYPE
209     * <p>
210     * (for example: "MessageDigest.SHA")
211     * <li> CRYPTO_SERVICE_NAME.ALGORITHM_OR_TYPE
212     * ATTR_NAME:ATTR_VALUE
213     * <p>
214     * (for example: "Signature.MD2withRSA KeySize:512")
215     * </nl>
216     *
217     * @param filter
218     *            case-insensitive filter.
219     * @return the providers which meet the user supplied string filter {@code
220     *         filter}. A {@code null} value signifies that none of the
221     *         installed providers meets the filter specification.
222     * @throws InvalidParameterException
223     *             if an unusable filter is supplied.
224     * @throws NullPointerException
225     *             if {@code filter} is {@code null}.
226     */
227    public static Provider[] getProviders(String filter) {
228        if (filter == null) {
229            throw new NullPointerException("filter == null");
230        }
231        if (filter.length() == 0) {
232            throw new InvalidParameterException();
233        }
234        HashMap<String, String> hm = new HashMap<String, String>();
235        int i = filter.indexOf(':');
236        if ((i == filter.length() - 1) || (i == 0)) {
237            throw new InvalidParameterException();
238        }
239        if (i < 1) {
240            hm.put(filter, "");
241        } else {
242            hm.put(filter.substring(0, i), filter.substring(i + 1));
243        }
244        return getProviders(hm);
245    }
246
247    /**
248     * Returns the array of providers which meet the user supplied set of
249     * filters. The filter must be supplied in one of two formats:
250     * <nl>
251     * <li> CRYPTO_SERVICE_NAME.ALGORITHM_OR_TYPE
252     * <p>
253     * for example: "MessageDigest.SHA" The value associated with the key must
254     * be an empty string. <li> CRYPTO_SERVICE_NAME.ALGORITHM_OR_TYPE
255     * ATTR_NAME:ATTR_VALUE
256     * <p>
257     * for example: "Signature.MD2withRSA KeySize:512" where "KeySize:512" is
258     * the value of the filter map entry.
259     * </nl>
260     *
261     * @param filter
262     *            case-insensitive filter.
263     * @return the providers which meet the user supplied string filter {@code
264     *         filter}. A {@code null} value signifies that none of the
265     *         installed providers meets the filter specification.
266     * @throws InvalidParameterException
267     *             if an unusable filter is supplied.
268     * @throws NullPointerException
269     *             if {@code filter} is {@code null}.
270     */
271    public static synchronized Provider[] getProviders(Map<String,String> filter) {
272        if (filter == null) {
273            throw new NullPointerException("filter == null");
274        }
275        if (filter.isEmpty()) {
276            return null;
277        }
278        ArrayList<Provider> result = new ArrayList<Provider>(Services.getProviders());
279        Set<Entry<String, String>> keys = filter.entrySet();
280        Map.Entry<String, String> entry;
281        for (Iterator<Entry<String, String>> it = keys.iterator(); it.hasNext();) {
282            entry = it.next();
283            String key = entry.getKey();
284            String val = entry.getValue();
285            String attribute = null;
286            int i = key.indexOf(' ');
287            int j = key.indexOf('.');
288            if (j == -1) {
289                throw new InvalidParameterException();
290            }
291            if (i == -1) { // <crypto_service>.<algorithm_or_type>
292                if (val.length() != 0) {
293                    throw new InvalidParameterException();
294                }
295            } else { // <crypto_service>.<algorithm_or_type> <attribute_name>
296                if (val.length() == 0) {
297                    throw new InvalidParameterException();
298                }
299                attribute = key.substring(i + 1);
300                if (attribute.trim().length() == 0) {
301                    throw new InvalidParameterException();
302                }
303                key = key.substring(0, i);
304            }
305            String serv = key.substring(0, j);
306            String alg = key.substring(j + 1);
307            if (serv.length() == 0 || alg.length() == 0) {
308                throw new InvalidParameterException();
309            }
310            filterProviders(result, serv, alg, attribute, val);
311        }
312        if (result.size() > 0) {
313            return result.toArray(new Provider[result.size()]);
314        }
315        return null;
316    }
317
318    private static void filterProviders(ArrayList<Provider> providers, String service,
319            String algorithm, String attribute, String attrValue) {
320        Iterator<Provider> it = providers.iterator();
321        while (it.hasNext()) {
322            Provider p = it.next();
323            if (!p.implementsAlg(service, algorithm, attribute, attrValue)) {
324                it.remove();
325            }
326        }
327    }
328
329    /**
330     * Returns the value of the security property named by the argument.
331     *
332     * @param key
333     *            the name of the requested security property.
334     * @return the value of the security property.
335     */
336    public static String getProperty(String key) {
337        if (key == null) {
338            throw new NullPointerException("key == null");
339        }
340        String property = secprops.getProperty(key);
341        if (property != null) {
342            property = property.trim();
343        }
344        return property;
345    }
346
347    /**
348     * Sets the value of the specified security property.
349     */
350    public static void setProperty(String key, String value) {
351        Services.setNeedRefresh();
352        secprops.put(key, value);
353    }
354
355    /**
356     * Returns a {@code Set} of all registered algorithms for the specified
357     * cryptographic service. {@code "Signature"}, {@code "Cipher"} and {@code
358     * "KeyStore"} are examples for such kind of services.
359     *
360     * @param serviceName
361     *            the case-insensitive name of the service.
362     * @return a {@code Set} of all registered algorithms for the specified
363     *         cryptographic service, or an empty {@code Set} if {@code
364     *         serviceName} is {@code null} or if no registered provider
365     *         provides the requested service.
366     */
367    public static Set<String> getAlgorithms(String serviceName) {
368        Set<String> result = new HashSet<String>();
369        // compatibility with RI
370        if (serviceName == null) {
371            return result;
372        }
373        for (Provider provider : getProviders()) {
374            for (Provider.Service service: provider.getServices()) {
375                if (service.getType().equalsIgnoreCase(serviceName)) {
376                    result.add(service.getAlgorithm());
377                }
378            }
379        }
380        return result;
381    }
382
383    /**
384     *
385     * Update sequence numbers of all providers.
386     *
387     */
388    private static void renumProviders() {
389        ArrayList<Provider> providers = Services.getProviders();
390        for (int i = 0; i < providers.size(); i++) {
391            providers.get(i).setProviderNumber(i + 1);
392        }
393    }
394
395    private static class SecurityDoor implements SecurityAccess {
396        // Access to Security.renumProviders()
397        public void renumProviders() {
398            Security.renumProviders();
399        }
400
401        //  Access to Security.getAliases()
402        public List<String> getAliases(Provider.Service s) {
403            return s.getAliases();
404        }
405
406        // Access to Provider.getService()
407        public Provider.Service getService(Provider p, String type) {
408            return p.getService(type);
409        }
410    }
411}
412