001/* 002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Wojciech Sulejman 016 */ 017package org.nuxeo.ecm.platform.signature.core.user; 018 019import java.io.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.security.KeyStore; 022import java.security.cert.X509Certificate; 023import java.util.HashMap; 024import java.util.Map; 025 026import javax.security.auth.login.LoginContext; 027import javax.security.auth.login.LoginException; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.nuxeo.common.utils.Base64; 032import org.nuxeo.ecm.core.api.DocumentModel; 033import org.nuxeo.ecm.core.api.NuxeoException; 034import org.nuxeo.ecm.directory.DirectoryException; 035import org.nuxeo.ecm.directory.Session; 036import org.nuxeo.ecm.directory.api.DirectoryService; 037import org.nuxeo.ecm.platform.signature.api.exception.CertException; 038import org.nuxeo.ecm.platform.signature.api.pki.CertService; 039import org.nuxeo.ecm.platform.signature.api.pki.RootService; 040import org.nuxeo.ecm.platform.signature.api.user.AliasType; 041import org.nuxeo.ecm.platform.signature.api.user.AliasWrapper; 042import org.nuxeo.ecm.platform.signature.api.user.CNField; 043import org.nuxeo.ecm.platform.signature.api.user.CUserService; 044import org.nuxeo.ecm.platform.signature.api.user.UserInfo; 045import org.nuxeo.runtime.api.Framework; 046import org.nuxeo.runtime.model.ComponentInstance; 047import org.nuxeo.runtime.model.DefaultComponent; 048 049/** 050 * Base implementation of the user certificate service. 051 * 052 * @author <a href="mailto:ws@nuxeo.com">Wojciech Sulejman</a> 053 */ 054public class CUserServiceImpl extends DefaultComponent implements CUserService { 055 056 private static final Log LOG = LogFactory.getLog(CUserServiceImpl.class); 057 058 private static final String CERTIFICATE_DIRECTORY_NAME = "certificate"; 059 060 protected RootService rootService; 061 062 protected CertService certService; 063 064 /** 065 * Configurable country code 066 */ 067 protected String countryCode; 068 069 /** 070 * Configurable organization name 071 */ 072 protected String organization; 073 074 /** 075 * Configurable organizational unit name 076 */ 077 protected String organizationalUnit; 078 079 @Override 080 public UserInfo getUserInfo(DocumentModel userModel) throws CertException { 081 UserInfo userInfo = null; 082 String userID = (String) userModel.getPropertyValue("user:username"); 083 String firstName = (String) userModel.getPropertyValue("user:firstName"); 084 String lastName = (String) userModel.getPropertyValue("user:lastName"); 085 String email = (String) userModel.getPropertyValue("user:email"); 086 087 Map<CNField, String> userFields = new HashMap<CNField, String>(); 088 089 userFields.put(CNField.C, countryCode); 090 userFields.put(CNField.O, organization); 091 userFields.put(CNField.OU, organizationalUnit); 092 093 userFields.put(CNField.CN, firstName + " " + lastName); 094 userFields.put(CNField.Email, email); 095 userFields.put(CNField.UserID, userID); 096 userInfo = new UserInfo(userFields); 097 return userInfo; 098 } 099 100 @Override 101 public KeyStore getUserKeystore(String userID, String userKeystorePassword) throws CertException { 102 // Log in as system user 103 LoginContext lc; 104 try { 105 lc = Framework.login(); 106 } catch (LoginException e) { 107 throw new NuxeoException("Cannot log in as system user", e); 108 } 109 try { 110 // Open directory session 111 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 112 KeyStore keystore = null; 113 DocumentModel entry = session.getEntry(userID); 114 if (entry != null) { 115 String keystore64Encoded = (String) entry.getPropertyValue("cert:keystore"); 116 byte[] keystoreBytes = Base64.decode(keystore64Encoded); 117 ByteArrayInputStream byteIS = new ByteArrayInputStream(keystoreBytes); 118 keystore = getCertService().getKeyStore(byteIS, userKeystorePassword); 119 } else { 120 throw new CertException("No directory entry for " + userID); 121 } 122 return keystore; 123 } 124 } finally { 125 try { 126 // Login context may be null in tests 127 if (lc != null) { 128 lc.logout(); 129 } 130 } catch (LoginException e) { 131 throw new NuxeoException("Cannot log out system user", e); 132 } 133 } 134 } 135 136 @Override 137 public DocumentModel createCertificate(DocumentModel user, String userKeyPassword) throws CertException { 138 // Log in as system user 139 LoginContext lc; 140 try { 141 lc = Framework.login(); 142 } catch (LoginException e) { 143 throw new NuxeoException("Cannot log in as system user", e); 144 } 145 try { 146 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 147 String userKeystorePassword = userKeyPassword; 148 DocumentModel certificate = null; 149 150 // create an entry in the directory 151 String userID = (String) user.getPropertyValue("user:username"); 152 153 // make sure that no certificates are associated with the 154 // current userid 155 boolean certificateExists = session.hasEntry(userID); 156 if (certificateExists) { 157 throw new CertException(userID + " already has a certificate"); 158 } 159 160 LOG.info("Starting certificate generation for: " + userID); 161 Map<String, Object> map = new HashMap<String, Object>(); 162 map.put("userid", userID); 163 164 // add a keystore to a directory entry 165 KeyStore keystore = getCertService().initializeUser(getUserInfo(user), userKeyPassword); 166 ByteArrayOutputStream byteOS = new ByteArrayOutputStream(); 167 getCertService().storeCertificate(keystore, byteOS, userKeystorePassword); 168 String keystore64Encoded = Base64.encodeBytes(byteOS.toByteArray()); 169 map.put("keystore", keystore64Encoded); 170 map.put("certificate", getUserCertInfo(keystore, user)); 171 map.put("keypassword", userKeyPassword); 172 certificate = session.createEntry(map); 173 return certificate; 174 } catch (DirectoryException e) { 175 LOG.error(e); 176 throw new CertException(e); 177 } 178 } finally { 179 try { 180 // Login context may be null in tests 181 if (lc != null) { 182 lc.logout(); 183 } 184 } catch (LoginException e) { 185 throw new NuxeoException("Cannot log out system user", e); 186 } 187 } 188 } 189 190 protected static DirectoryService getDirectoryService() { 191 return Framework.getService(DirectoryService.class); 192 } 193 194 @Override 195 public String getUserCertInfo(DocumentModel user, String userKeyPassword) throws CertException { 196 String userKeystorePassword = userKeyPassword; 197 String userID = (String) user.getPropertyValue("user:username"); 198 KeyStore keystore = getUserKeystore(userID, userKeystorePassword); 199 return getUserCertInfo(keystore, user); 200 } 201 202 private String getUserCertInfo(KeyStore keystore, DocumentModel user) throws CertException { 203 String userCertInfo = null; 204 if (null != keystore) { 205 String userID = (String) user.getPropertyValue("user:username"); 206 AliasWrapper alias = new AliasWrapper(userID); 207 X509Certificate certificate = getCertService().getCertificate(keystore, alias.getId(AliasType.CERT)); 208 userCertInfo = certificate.getSubjectDN() + " valid till: " + certificate.getNotAfter(); 209 } 210 return userCertInfo; 211 } 212 213 @Override 214 public DocumentModel getCertificate(String userID) { 215 // Log in as system user 216 LoginContext lc; 217 try { 218 lc = Framework.login(); 219 } catch (LoginException e) { 220 throw new NuxeoException("Cannot log in as system user", e); 221 } 222 try { 223 // Open directory session 224 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 225 DocumentModel certificate = session.getEntry(userID); 226 return certificate; 227 } 228 } finally { 229 try { 230 // Login context may be null in tests 231 if (lc != null) { 232 lc.logout(); 233 } 234 } catch (LoginException e) { 235 throw new NuxeoException("Cannot log out system user", e); 236 } 237 } 238 } 239 240 @Override 241 public byte[] getRootCertificateData() { 242 byte[] certificateData = getRootService().getRootPublicCertificate(); 243 return certificateData; 244 } 245 246 @Override 247 public boolean hasCertificate(String userID) throws CertException { 248 // Log in as system user 249 LoginContext lc; 250 try { 251 lc = Framework.login(); 252 } catch (LoginException e) { 253 throw new NuxeoException("Cannot log in as system user", e); 254 } 255 try { 256 // Open directory session 257 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 258 return session.getEntry(userID) != null; 259 } 260 } finally { 261 try { 262 // Login context may be null in tests 263 if (lc != null) { 264 lc.logout(); 265 } 266 } catch (LoginException e) { 267 throw new NuxeoException("Cannot log out system user", e); 268 } 269 } 270 } 271 272 @Override 273 public void deleteCertificate(String userID) throws CertException { 274 // Log in as system user 275 LoginContext lc; 276 try { 277 lc = Framework.login(); 278 } catch (LoginException e) { 279 throw new NuxeoException("Cannot log in as system user", e); 280 } 281 try { 282 // Open directory session 283 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 284 DocumentModel certEntry = session.getEntry(userID); 285 session.deleteEntry(certEntry); 286 assert (null == session.getEntry(userID)); 287 } 288 } finally { 289 try { 290 // Login context may be null in tests 291 if (lc != null) { 292 lc.logout(); 293 } 294 } catch (LoginException e) { 295 throw new NuxeoException("Cannot log out system user", e); 296 } 297 } 298 } 299 300 @Override 301 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 302 if (contribution instanceof CUserDescriptor) { 303 CUserDescriptor desc = (CUserDescriptor) contribution; 304 countryCode = desc.getCountryCode(); 305 organization = desc.getOrganization(); 306 organizationalUnit = desc.getOrganizationalUnit(); 307 } 308 } 309 310 protected CertService getCertService() { 311 if (certService == null) { 312 certService = Framework.getService(CertService.class); 313 } 314 return certService; 315 } 316 317 protected RootService getRootService() { 318 if (rootService == null) { 319 rootService = Framework.getService(RootService.class); 320 } 321 return rootService; 322 } 323}