Karl Pauls | 3640732 | 2008-03-07 00:37:30 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Licensed to the Apache Software Foundation (ASF) under one |
| 3 | * or more contributor license agreements. See the NOTICE file |
| 4 | * distributed with this work for additional information |
| 5 | * regarding copyright ownership. The ASF licenses this file |
| 6 | * to you under the Apache License, Version 2.0 (the |
| 7 | * "License"); you may not use this file except in compliance |
| 8 | * with the License. You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, |
| 13 | * software distributed under the License is distributed on an |
| 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | * KIND, either express or implied. See the License for the |
| 16 | * specific language governing permissions and limitations |
| 17 | * under the License. |
| 18 | */ |
| 19 | package org.apache.felix.framework; |
| 20 | |
| 21 | import java.io.File; |
| 22 | import java.io.IOException; |
| 23 | import java.util.HashMap; |
| 24 | import java.util.Iterator; |
| 25 | import java.util.Map; |
| 26 | import java.util.StringTokenizer; |
| 27 | import java.util.Map.Entry; |
| 28 | |
| 29 | import org.apache.felix.framework.security.SecurityConstants; |
| 30 | import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl; |
| 31 | import org.apache.felix.framework.security.permissionadmin.PermissionAdminImpl; |
| 32 | import org.apache.felix.framework.security.util.Conditions; |
| 33 | import org.apache.felix.framework.security.util.LocalPermissions; |
| 34 | import org.apache.felix.framework.security.util.Permissions; |
| 35 | import org.apache.felix.framework.security.util.PropertiesCache; |
| 36 | import org.apache.felix.framework.security.verifier.BundleDNParser; |
| 37 | import org.apache.felix.framework.util.SecureAction; |
| 38 | import org.osgi.framework.Bundle; |
| 39 | import org.osgi.framework.BundleActivator; |
| 40 | import org.osgi.framework.BundleContext; |
| 41 | import org.osgi.framework.BundleException; |
| 42 | import org.osgi.service.condpermadmin.ConditionalPermissionAdmin; |
| 43 | import org.osgi.service.permissionadmin.PermissionAdmin; |
| 44 | |
| 45 | /** |
| 46 | * <p>This Felix specific activator installs a security provider with the Felix |
| 47 | * framework. The security settings can be changed via the |
| 48 | * {@link PermissionAdmin} and/or the |
| 49 | * {@link ConditionalPermissionAdmin} services that may be published by |
| 50 | * this class. |
| 51 | * </p> |
| 52 | * <p> |
| 53 | * Permission informations as well as caching data will be stored in several |
| 54 | * files in a directory called <tt>security</tt> obtained by a call to |
| 55 | * {@link BundleContext#getDataFile(String))}. |
| 56 | * </p> |
| 57 | * <p> |
| 58 | * The following properties are recognized: |
| 59 | * <p> |
| 60 | * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_PROP} - Whether or not |
| 61 | * (<tt>true</tt>|<tt>false</tt>) to |
| 62 | * publish a{@link ConditionalPermissionAdmin} service. The default is |
| 63 | * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_VALUE}. |
| 64 | * </p> |
| 65 | * <p> |
| 66 | * {@link SecurityConstants#ENABLE_CONDPERMADMIN_PROP} - Whether or not |
| 67 | * (<tt>true</tt>|<tt>false</tt>) to |
| 68 | * publish a{@link ConditionalPermissionAdmin} service. The default is |
| 69 | * {@link SecurityConstants#ENABLE_CONDPERMADMIN_VALUE}. |
| 70 | * </p> |
| 71 | * <p> |
| 72 | * {@link SecurityConstants#KEYSTORE_FILE_PROP} - The keystore URL(s) to use as |
| 73 | * trusted CA stores. The urls must be separated by a guard (i.e., <tt>|</tt>). |
| 74 | * The default is |
| 75 | * {@link SecurityConstants#KEYSTORE_FILE_VALUE}. |
| 76 | * </p> |
| 77 | * <p> |
| 78 | * {@link SecurityConstants#KEYSTORE_PASS_PROP} - The keystore password(s) to |
| 79 | * use for the given keystores. The passwords must be separated by a guard |
| 80 | * (i.e., <tt>|</tt>).The default is |
| 81 | * {@link SecurityConstants#KEYSTORE_PASS_VALUE}. |
| 82 | * </p> |
| 83 | * <p> |
| 84 | * {@link SecurityConstants#KEYSTORE_TYPE_PROP} - The keystore type(s) to use |
| 85 | * for the given keystores. The types must be separated by a guard |
| 86 | * (i.e., <tt>|</tt>).The default is |
| 87 | * {@link SecurityConstants#KEYSTORE_TYPE_VALUE}. |
| 88 | * </p> |
| 89 | * <p> |
| 90 | * {@link SecurityConstants#CRL_FILE_PROP} - The CRL URL(s) to use for revoked |
| 91 | * certificates. The urls must be separated by a guard (i.e., <tt>|</tt>). |
| 92 | * The default is {@link SecurityConstants#CRL_FILE_VALUE}. |
| 93 | * </p> |
| 94 | * </p> |
| 95 | */ |
| 96 | /* |
| 97 | * TODO: using a string for passwords is bad. We need to investigate |
| 98 | * alternatives. |
| 99 | * |
| 100 | * TODO: we might want to allow for the recognized properties to |
| 101 | * change without a restart. This is trick because we can not publish a managed |
| 102 | * service due to not being able to import as we are an extension bundle. |
| 103 | */ |
| 104 | public final class SecurityActivator implements BundleActivator |
| 105 | { |
| 106 | private SecurityProviderImpl m_provider = null; |
| 107 | private PropertiesCache m_dnsCache = null; |
| 108 | private PropertiesCache m_localCache = null; |
| 109 | private LocalPermissions m_localPermissions = null; |
| 110 | |
| 111 | public synchronized void start(BundleContext context) throws Exception |
| 112 | { |
| 113 | PermissionAdminImpl pai = null; |
| 114 | |
| 115 | SecureAction action = new SecureAction(); |
| 116 | |
| 117 | Permissions permissions = new Permissions(context, action); |
| 118 | |
| 119 | File tmp = context.getDataFile("security" + File.separator + "tmp"); |
| 120 | if ((tmp == null) || (!tmp.isDirectory() && !tmp.mkdirs())) |
| 121 | { |
| 122 | throw new IOException("Can't create tmp dir."); |
| 123 | } |
| 124 | // TODO: log something if we can not clean-up the tmp dir |
| 125 | File[] old = tmp.listFiles(); |
| 126 | if (old != null) |
| 127 | { |
| 128 | for (int i = 0; i < old.length; i++) |
| 129 | { |
| 130 | old[i].delete(); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | if ("TRUE".equalsIgnoreCase(getProperty(context, |
| 135 | SecurityConstants.ENABLE_PERMISSIONADMIN_PROP, |
| 136 | SecurityConstants.ENABLE_PERMISSIONADMIN_VALUE))) |
| 137 | { |
| 138 | File cache = |
| 139 | context.getDataFile("security" + File.separator + "pa.txt"); |
| 140 | if ((cache == null) || (!cache.isFile() && !cache.createNewFile())) |
| 141 | { |
| 142 | throw new IOException("Can't create cache file"); |
| 143 | } |
| 144 | pai = |
| 145 | new PermissionAdminImpl(permissions, new PropertiesCache(cache, |
| 146 | tmp, action)); |
| 147 | } |
| 148 | |
| 149 | ConditionalPermissionAdminImpl cpai = null; |
| 150 | |
| 151 | if ("TRUE".equalsIgnoreCase(getProperty(context, |
| 152 | SecurityConstants.ENABLE_CONDPERMADMIN_PROP, |
| 153 | SecurityConstants.ENABLE_CONDPERMADMIN_VALUE))) |
| 154 | { |
| 155 | File cpaCache = |
| 156 | context.getDataFile("security" + File.separator + "cpa.txt"); |
| 157 | if ((cpaCache == null) || (!cpaCache.isFile() && !cpaCache.createNewFile())) |
| 158 | { |
| 159 | throw new IOException("Can't create cache file"); |
| 160 | } |
| 161 | File localCache = |
| 162 | context.getDataFile("security" + File.separator + "local.txt"); |
| 163 | if ((localCache == null) || (!localCache.isFile() && !localCache.createNewFile())) |
| 164 | { |
| 165 | throw new IOException("Can't create cache file"); |
| 166 | } |
| 167 | |
| 168 | m_localCache = new PropertiesCache(localCache, tmp, action); |
| 169 | m_localPermissions = new LocalPermissions(permissions, m_localCache); |
| 170 | |
| 171 | cpai = |
| 172 | new ConditionalPermissionAdminImpl(permissions, new Conditions( |
| 173 | action), m_localPermissions, |
| 174 | new PropertiesCache(cpaCache, tmp, action)); |
| 175 | } |
| 176 | |
| 177 | if ((pai != null) || (cpai != null)) |
| 178 | { |
| 179 | String crlList = |
| 180 | getProperty(context, SecurityConstants.CRL_FILE_PROP, |
| 181 | SecurityConstants.CRL_FILE_VALUE); |
| 182 | String storeList = |
| 183 | getProperty(context, SecurityConstants.KEYSTORE_FILE_PROP, |
| 184 | SecurityConstants.KEYSTORE_FILE_VALUE); |
| 185 | String passwdList = |
| 186 | getProperty(context, SecurityConstants.KEYSTORE_PASS_PROP, |
| 187 | SecurityConstants.KEYSTORE_PASS_VALUE); |
| 188 | String typeList = |
| 189 | getProperty(context, SecurityConstants.KEYSTORE_TYPE_PROP, |
| 190 | SecurityConstants.KEYSTORE_TYPE_VALUE); |
| 191 | |
| 192 | StringTokenizer storeTok = new StringTokenizer(storeList, "|"); |
| 193 | StringTokenizer passwdTok = new StringTokenizer(passwdList, "|"); |
| 194 | StringTokenizer typeTok = new StringTokenizer(typeList, "|"); |
| 195 | |
| 196 | if ((storeTok.countTokens() != passwdTok.countTokens()) |
| 197 | || (passwdTok.countTokens() != typeTok.countTokens())) |
| 198 | { |
| 199 | throw new BundleException( |
| 200 | "Each CACerts keystore must have one type and one passwd entry and vice versa."); |
| 201 | } |
| 202 | |
| 203 | m_provider = |
| 204 | new SecurityProviderImpl(crlList, typeList, passwdList, |
| 205 | storeList, pai, cpai, action); |
| 206 | |
| 207 | File cache = |
| 208 | context.getDataFile("security" + File.separator + "dns.txt"); |
| 209 | if ((cache == null) || (!cache.isFile() && !cache.createNewFile())) |
| 210 | { |
| 211 | throw new IOException("Can't create cache file"); |
| 212 | } |
| 213 | m_dnsCache = new PropertiesCache(cache, tmp, action); |
| 214 | |
| 215 | Map store = m_dnsCache.read(String[].class); |
| 216 | |
| 217 | if (store != null) |
| 218 | { |
| 219 | BundleDNParser parser = m_provider.getParser(); |
| 220 | |
| 221 | for (Iterator iter = store.entrySet().iterator(); iter |
| 222 | .hasNext();) |
| 223 | { |
| 224 | Entry entry = (Entry) iter.next(); |
| 225 | String[] value = (String[]) entry.getValue(); |
| 226 | if ("none".equals(value[0])) |
| 227 | { |
| 228 | parser.put((String) entry.getKey(), null); |
| 229 | } |
| 230 | else if ("invalid".equals(value[0])) |
| 231 | { |
| 232 | parser.put((String) entry.getKey(), new String[0]); |
| 233 | } |
| 234 | else |
| 235 | { |
| 236 | parser.put((String) entry.getKey(), value); |
| 237 | } |
| 238 | } |
| 239 | } |
| 240 | ((Felix) context.getBundle(0)).setSecurityProvider(m_provider); |
| 241 | } |
| 242 | |
| 243 | if (pai != null) |
| 244 | { |
| 245 | context.registerService(PermissionAdmin.class.getName(), pai, null); |
| 246 | } |
| 247 | |
| 248 | if (cpai != null) |
| 249 | { |
| 250 | context.registerService(ConditionalPermissionAdmin.class.getName(), |
| 251 | cpai, null); |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | public synchronized void stop(BundleContext context) throws Exception |
| 256 | { |
| 257 | if (m_provider != null) |
| 258 | { |
| 259 | m_dnsCache.write(write(m_provider.getParser().getCache(), context)); |
| 260 | } |
| 261 | if (m_localPermissions != null) |
| 262 | { |
| 263 | m_localCache.write(write(m_localPermissions.getStore(), context)); |
| 264 | } |
| 265 | m_provider = null; |
| 266 | m_dnsCache = null; |
| 267 | m_localPermissions = null; |
| 268 | } |
| 269 | |
| 270 | private Map write(Map cache, BundleContext context) |
| 271 | { |
| 272 | // Filter the cached dn chains and only store the latest for each |
| 273 | // bundle. This is ok because the framework will prune old revisions |
| 274 | // after a restart. The format is <id>-<timestamp> |
| 275 | Map store = new HashMap(); |
| 276 | Map index = new HashMap(); |
| 277 | for (Iterator iter = cache.entrySet().iterator(); iter.hasNext();) |
| 278 | { |
| 279 | Entry entry = (Entry) iter.next(); |
| 280 | |
| 281 | String key = (String) entry.getKey(); |
| 282 | String id = key.substring(0, key.indexOf("-")); |
| 283 | String time = key.substring(key.indexOf("-") + 1); |
| 284 | Bundle bundle = context.getBundle(Long.parseLong(id)); |
| 285 | long timeLong = Long.parseLong(time); |
| 286 | if ((bundle == null) || |
Karl Pauls | d093f2d | 2009-11-24 23:23:26 +0000 | [diff] [blame] | 287 | (bundle.getLastModified() > timeLong)) |
Karl Pauls | 3640732 | 2008-03-07 00:37:30 +0000 | [diff] [blame] | 288 | { |
| 289 | continue; |
| 290 | } |
| 291 | String last = (String) index.get(id); |
| 292 | |
| 293 | if ((last == null) |
| 294 | || (Long.parseLong(last) < timeLong)) |
| 295 | { |
| 296 | index.put(id, time); |
| 297 | Object[] dns = (Object[]) entry.getValue(); |
| 298 | store.remove(id + "-" + last); |
| 299 | if ((dns != null) && (dns.length > 0)) |
| 300 | { |
| 301 | store.put(key, dns); |
| 302 | } |
| 303 | else |
| 304 | { |
| 305 | store.put(key, (dns == null) ? new String[] {"none"} : new String[] {"invalid"}); |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | return store; |
| 310 | } |
| 311 | |
| 312 | private String getProperty(BundleContext context, String key, |
| 313 | String defaultValue) |
| 314 | { |
| 315 | String result = context.getProperty(key); |
| 316 | |
| 317 | return ((result != null) && (result.trim().length() > 0)) ? result |
| 318 | : defaultValue; |
| 319 | } |
| 320 | } |