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