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 /** 059 * Configurable country code 060 */ 061 protected String countryCode; 062 063 /** 064 * Configurable organization name 065 */ 066 protected String organization; 067 068 /** 069 * Configurable organizational unit name 070 */ 071 protected String organizationalUnit; 072 073 @Override 074 public UserInfo getUserInfo(DocumentModel userModel) throws CertException { 075 UserInfo userInfo; 076 String userID = (String) userModel.getPropertyValue("user:username"); 077 String firstName = (String) userModel.getPropertyValue("user:firstName"); 078 String lastName = (String) userModel.getPropertyValue("user:lastName"); 079 String email = (String) userModel.getPropertyValue("user:email"); 080 081 Map<CNField, String> userFields = new HashMap<>(); 082 083 userFields.put(CNField.C, countryCode); 084 userFields.put(CNField.O, organization); 085 userFields.put(CNField.OU, organizationalUnit); 086 087 userFields.put(CNField.CN, firstName + " " + lastName); 088 userFields.put(CNField.Email, email); 089 userFields.put(CNField.UserID, userID); 090 userInfo = new UserInfo(userFields); 091 return userInfo; 092 } 093 094 @Override 095 public KeyStore getUserKeystore(String userID, String userKeystorePassword) throws CertException { 096 String keystore64Encoded = Framework.doPrivileged(() -> { 097 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 098 DocumentModel entry = session.getEntry(userID); 099 if (entry != null) { 100 return (String) entry.getPropertyValue("cert:keystore"); 101 } else { 102 throw new CertException("No directory entry for " + userID); 103 } 104 } 105 }); 106 byte[] keystoreBytes = Base64.decodeBase64(keystore64Encoded); 107 ByteArrayInputStream byteIS = new ByteArrayInputStream(keystoreBytes); 108 return getCertService().getKeyStore(byteIS, userKeystorePassword); 109 } 110 111 @Override 112 public DocumentModel createCertificate(DocumentModel user, String userKeyPassword) throws CertException { 113 return Framework.doPrivileged(() -> { 114 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 115 DocumentModel certificate = null; 116 117 // create an entry in the directory 118 String userID = (String) user.getPropertyValue("user:username"); 119 120 // make sure that no certificates are associated with the 121 // current userid 122 boolean certificateExists = session.hasEntry(userID); 123 if (certificateExists) { 124 throw new CertException(userID + " already has a certificate"); 125 } 126 127 LOG.info("Starting certificate generation for: " + userID); 128 Map<String, Object> map = new HashMap<>(); 129 map.put("userid", userID); 130 131 // add a keystore to a directory entry 132 KeyStore keystore = getCertService().initializeUser(getUserInfo(user), userKeyPassword); 133 ByteArrayOutputStream byteOS = new ByteArrayOutputStream(); 134 getCertService().storeCertificate(keystore, byteOS, userKeyPassword); 135 String keystore64Encoded = Base64.encodeBase64String(byteOS.toByteArray()); 136 map.put("keystore", keystore64Encoded); 137 map.put("certificate", getUserCertInfo(keystore, user)); 138 map.put("keypassword", userKeyPassword); 139 certificate = session.createEntry(map); 140 return certificate; 141 } catch (DirectoryException e) { 142 LOG.error(e); 143 throw new CertException(e); 144 } 145 }); 146 } 147 148 protected static DirectoryService getDirectoryService() { 149 return Framework.getService(DirectoryService.class); 150 } 151 152 @Override 153 public String getUserCertInfo(DocumentModel user, String userKeyPassword) throws CertException { 154 String userID = (String) user.getPropertyValue("user:username"); 155 KeyStore keystore = getUserKeystore(userID, userKeyPassword); 156 return getUserCertInfo(keystore, user); 157 } 158 159 private String getUserCertInfo(KeyStore keystore, DocumentModel user) throws CertException { 160 String userCertInfo = null; 161 if (null != keystore) { 162 String userID = (String) user.getPropertyValue("user:username"); 163 AliasWrapper alias = new AliasWrapper(userID); 164 X509Certificate certificate = getCertService().getCertificate(keystore, alias.getId(AliasType.CERT)); 165 userCertInfo = certificate.getSubjectDN() + " valid till: " + certificate.getNotAfter(); 166 } 167 return userCertInfo; 168 } 169 170 @Override 171 public DocumentModel getCertificate(String userID) { 172 return Framework.doPrivileged(() -> { 173 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 174 DocumentModel certificate = session.getEntry(userID); 175 return certificate; 176 } 177 }); 178 } 179 180 @Override 181 public byte[] getRootCertificateData() { 182 byte[] certificateData = getRootService().getRootPublicCertificate(); 183 return certificateData; 184 } 185 186 @SuppressWarnings("boxing") 187 @Override 188 public boolean hasCertificate(String userID) throws CertException { 189 return Framework.doPrivileged(() -> { 190 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 191 return session.getEntry(userID) != null; 192 } 193 }); 194 } 195 196 @Override 197 public void deleteCertificate(String userID) throws CertException { 198 Framework.doPrivileged(() -> { 199 try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) { 200 DocumentModel certEntry = session.getEntry(userID); 201 session.deleteEntry(certEntry); 202 assert (null == session.getEntry(userID)); 203 } 204 }); 205 } 206 207 @Override 208 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 209 if (contribution instanceof CUserDescriptor) { 210 CUserDescriptor desc = (CUserDescriptor) contribution; 211 countryCode = desc.getCountryCode(); 212 organization = desc.getOrganization(); 213 organizationalUnit = desc.getOrganizationalUnit(); 214 } 215 } 216 217 protected CertService getCertService() { 218 return Framework.getService(CertService.class); 219 } 220 221 protected RootService getRootService() { 222 return Framework.getService(RootService.class); 223 } 224}