001/* 002 * (C) Copyright 2011-2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Wojciech Sulejman 018 */ 019package org.nuxeo.ecm.platform.signature.core.user; 020 021import java.io.ByteArrayInputStream; 022import java.io.ByteArrayOutputStream; 023import java.security.KeyStore; 024import java.security.cert.X509Certificate; 025import java.util.HashMap; 026import java.util.Map; 027 028import org.apache.commons.codec.binary.Base64; 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.nuxeo.ecm.core.api.DocumentModel; 032import org.nuxeo.ecm.directory.DirectoryException; 033import org.nuxeo.ecm.directory.Session; 034import org.nuxeo.ecm.directory.api.DirectoryService; 035import org.nuxeo.ecm.platform.signature.api.exception.CertException; 036import org.nuxeo.ecm.platform.signature.api.pki.CertService; 037import org.nuxeo.ecm.platform.signature.api.pki.RootService; 038import org.nuxeo.ecm.platform.signature.api.user.AliasType; 039import org.nuxeo.ecm.platform.signature.api.user.AliasWrapper; 040import org.nuxeo.ecm.platform.signature.api.user.CNField; 041import org.nuxeo.ecm.platform.signature.api.user.CUserService; 042import org.nuxeo.ecm.platform.signature.api.user.UserInfo; 043import org.nuxeo.runtime.api.Framework; 044import org.nuxeo.runtime.model.ComponentInstance; 045import org.nuxeo.runtime.model.DefaultComponent; 046 047/** 048 * Base implementation of the user certificate service. 049 * 050 * @author <a href="mailto:ws@nuxeo.com">Wojciech Sulejman</a> 051 */ 052public class CUserServiceImpl extends DefaultComponent implements CUserService { 053 054 private static final Log LOG = LogFactory.getLog(CUserServiceImpl.class); 055 056 private static final String CERTIFICATE_DIRECTORY_NAME = "certificate"; 057 058 protected RootService rootService; 059 060 protected CertService certService; 061 062 /** 063 * Configurable country code 064 */ 065 protected String countryCode; 066 067 /** 068 * Configurable organization name 069 */ 070 protected String organization; 071 072 /** 073 * Configurable organizational unit name 074 */ 075 protected String organizationalUnit; 076 077 @Override 078 public UserInfo getUserInfo(DocumentModel userModel) throws CertException { 079 UserInfo userInfo; 080 String userID = (String) userModel.getPropertyValue("user:username"); 081 String firstName = (String) userModel.getPropertyValue("user:firstName"); 082 String lastName = (String) userModel.getPropertyValue("user:lastName"); 083 String email = (String) userModel.getPropertyValue("user:email"); 084 085 Map<CNField, String> userFields = new HashMap<>(); 086 087 userFields.put(CNField.C, countryCode); 088 userFields.put(CNField.O, organization); 089 userFields.put(CNField.OU, organizationalUnit); 090 091 userFields.put(CNField.CN, firstName + " " + lastName); 092 userFields.put(CNField.Email, email); 093 userFields.put(CNField.UserID, userID); 094 userInfo = new UserInfo(userFields); 095 return userInfo; 096 } 097 098 @Override 099 public KeyStore getUserKeystore(String userID, String userKeystorePassword) throws CertException { 100 String keystore64Encoded = Framework.doPrivileged(() -> { 101 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 102 DocumentModel entry = session.getEntry(userID); 103 if (entry != null) { 104 return (String) entry.getPropertyValue("cert:keystore"); 105 } else { 106 throw new CertException("No directory entry for " + userID); 107 } 108 } 109 }); 110 byte[] keystoreBytes = Base64.decodeBase64(keystore64Encoded); 111 ByteArrayInputStream byteIS = new ByteArrayInputStream(keystoreBytes); 112 return getCertService().getKeyStore(byteIS, userKeystorePassword); 113 } 114 115 @Override 116 public DocumentModel createCertificate(DocumentModel user, String userKeyPassword) throws CertException { 117 return Framework.doPrivileged(() -> { 118 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 119 DocumentModel certificate = null; 120 121 // create an entry in the directory 122 String userID = (String) user.getPropertyValue("user:username"); 123 124 // make sure that no certificates are associated with the 125 // current userid 126 boolean certificateExists = session.hasEntry(userID); 127 if (certificateExists) { 128 throw new CertException(userID + " already has a certificate"); 129 } 130 131 LOG.info("Starting certificate generation for: " + userID); 132 Map<String, Object> map = new HashMap<>(); 133 map.put("userid", userID); 134 135 // add a keystore to a directory entry 136 KeyStore keystore = getCertService().initializeUser(getUserInfo(user), userKeyPassword); 137 ByteArrayOutputStream byteOS = new ByteArrayOutputStream(); 138 getCertService().storeCertificate(keystore, byteOS, userKeyPassword); 139 String keystore64Encoded = Base64.encodeBase64String(byteOS.toByteArray()); 140 map.put("keystore", keystore64Encoded); 141 map.put("certificate", getUserCertInfo(keystore, user)); 142 map.put("keypassword", userKeyPassword); 143 certificate = session.createEntry(map); 144 return certificate; 145 } catch (DirectoryException e) { 146 LOG.error(e); 147 throw new CertException(e); 148 } 149 }); 150 } 151 152 protected static DirectoryService getDirectoryService() { 153 return Framework.getService(DirectoryService.class); 154 } 155 156 @Override 157 public String getUserCertInfo(DocumentModel user, String userKeyPassword) throws CertException { 158 String userID = (String) user.getPropertyValue("user:username"); 159 KeyStore keystore = getUserKeystore(userID, userKeyPassword); 160 return getUserCertInfo(keystore, user); 161 } 162 163 private String getUserCertInfo(KeyStore keystore, DocumentModel user) throws CertException { 164 String userCertInfo = null; 165 if (null != keystore) { 166 String userID = (String) user.getPropertyValue("user:username"); 167 AliasWrapper alias = new AliasWrapper(userID); 168 X509Certificate certificate = getCertService().getCertificate(keystore, alias.getId(AliasType.CERT)); 169 userCertInfo = certificate.getSubjectDN() + " valid till: " + certificate.getNotAfter(); 170 } 171 return userCertInfo; 172 } 173 174 @Override 175 public DocumentModel getCertificate(String userID) { 176 return Framework.doPrivileged(() -> { 177 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 178 DocumentModel certificate = session.getEntry(userID); 179 return certificate; 180 } 181 }); 182 } 183 184 @Override 185 public byte[] getRootCertificateData() { 186 byte[] certificateData = getRootService().getRootPublicCertificate(); 187 return certificateData; 188 } 189 190 @SuppressWarnings("boxing") 191 @Override 192 public boolean hasCertificate(String userID) throws CertException { 193 return Framework.doPrivileged(() -> { 194 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 195 return session.getEntry(userID) != null; 196 } 197 }); 198 } 199 200 @Override 201 public void deleteCertificate(String userID) throws CertException { 202 Framework.doPrivileged(() -> { 203 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 204 DocumentModel certEntry = session.getEntry(userID); 205 session.deleteEntry(certEntry); 206 assert (null == session.getEntry(userID)); 207 } 208 }); 209 } 210 211 @Override 212 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 213 if (contribution instanceof CUserDescriptor) { 214 CUserDescriptor desc = (CUserDescriptor) contribution; 215 countryCode = desc.getCountryCode(); 216 organization = desc.getOrganization(); 217 organizationalUnit = desc.getOrganizationalUnit(); 218 } 219 } 220 221 protected CertService getCertService() { 222 if (certService == null) { 223 certService = Framework.getService(CertService.class); 224 } 225 return certService; 226 } 227 228 protected RootService getRootService() { 229 if (rootService == null) { 230 rootService = Framework.getService(RootService.class); 231 } 232 return rootService; 233 } 234}