package com.intel.mtwilson.security.jpa;
import com.intel.mtwilson.datatypes.Role;
import com.intel.mtwilson.ms.controller.ApiClientX509JpaController;
import com.intel.mtwilson.ms.data.ApiClientX509;
import com.intel.mtwilson.ms.data.ApiRoleX509;
import com.intel.mtwilson.security.core.PublicKeyUserFinder;
import com.intel.mtwilson.security.core.PublicKeyUserInfo;
import com.intel.mtwilson.security.core.X509UserFinder;
import com.intel.mtwilson.security.core.X509UserInfo;
import java.io.ByteArrayInputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.EntityManagerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* It was labeled business logic but it's very closely tied to the JPA layer.
*
* Secret key lookup provider for the authentication filter that secures
* the REST API.
*
* @since 0.5.1
* @author jbuhacoff
*/
public class ApiClientX509BO implements X509UserFinder, PublicKeyUserFinder {
private static Logger log = LoggerFactory.getLogger(ApiClientX509BO.class);
private ApiClientX509JpaController controller;
public ApiClientX509BO(EntityManagerFactory factory) {
controller = new ApiClientX509JpaController(factory);
}
private ApiClientX509 getApiClientByFingerprint(byte[] fingerprint) {
ApiClientX509 apiClient = controller.findEnabledApiClientX509ByFingerprint(fingerprint);
return apiClient;
}
private Certificate getCertificate(byte[] certificateBytes) {
if( certificateBytes != null ) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(certificateBytes));
return cert;
}
catch (CertificateException ex) {
log.error("Cannot load certificate", ex);
return null;
}
}
return null;
}
// commenting out unused function (6/11 1.2)
/*
private PublicKey getPublicKey(byte[] publicKeyBytes) {
if( publicKeyBytes != null ) {
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
PublicKey publicKey = (PublicKey)kf.generatePublic(keySpec);
return publicKey;
} catch (InvalidKeySpecException ex) {
log.error("Cannot load public key from database", ex);
return null;
}
catch (NoSuchAlgorithmException ex) {
log.error("Cannot load public key from database", ex);
return null;
}
}
return null;
}
*/
@Override
public X509UserInfo getUserForX509Identity(byte[] fingerprint) {
ApiClientX509 apiClient = getApiClientByFingerprint(fingerprint);
if( apiClient == null ) {
return null;
}
X509UserInfo userInfo = new X509UserInfo();
userInfo.certificate = getCertificate(apiClient.getCertificate());
// Let us add the display name of the user too
String tempUserName = apiClient.getName();
// Since we need to ignore the comma character before the OU, the to index is being subtracted by 2
userInfo.loginName = tempUserName.substring(tempUserName.indexOf("CN=") + 3, tempUserName.indexOf("OU=")-1);
// userInfo.publicKey = userInfo.certificate.getPublicKey(); // apiClient.getCertificate().getPublicKey();
userInfo.fingerprint = fingerprint;
Collection<ApiRoleX509> roles = apiClient.getApiRoleX509Collection();
ArrayList<Role> allowedRoles = new ArrayList<Role>();
for( ApiRoleX509 role : roles ) {
String roleName = role.getApiRoleX509PK().getRole();
try {
allowedRoles.add(Role.valueOf(roleName));
}
catch(IllegalArgumentException e) {
// we ignore unsupported roles, but log the error
log.error("Unsupported role name ("+roleName+") assigned to "+apiClient.getName(), e);
}
}
userInfo.roles = allowedRoles.toArray(new Role[0]);
return userInfo;
}
/**
* For now we are using the same X509 table for both "X509" and "PublicKey"
* authentication schemes. The "PublicKey" scheme just needs the public key,
* the "X509" needs the entire certificate. To be normal they should be
* in separate tables.
* @param fingerprint
* @return
*/
@Override
public PublicKeyUserInfo getUserForIdentity(byte[] fingerprint) {
X509UserInfo x509 = getUserForX509Identity(fingerprint);
if( x509 == null ) {
return null;
}
PublicKeyUserInfo userInfo = new PublicKeyUserInfo();
userInfo.fingerprint = fingerprint;
if( x509.certificate != null ) { userInfo.publicKey = x509.certificate.getPublicKey(); }
userInfo.roles = x509.roles;
return userInfo;
}
}