Security Guide : Creating custom plugins

Creating custom plugins
There are various components of VisiSecure that allow for custom plug-ins. They are:
Trust Providers via the SPI
LoginModules
LoginModule describes the interface implemented by authentication technology providers. LoginModules are plugged under applications to provide a particular type of authentication.
While applications write to the LoginContext API, authentication technology providers implement the LoginModule interface. A Configuration specifies the LoginModule(s) to be used with a particular login application. Therefore different LoginModules can be plugged in under the application without requiring any modifications to the application itself.
You can implement your own LoginModules by extending vbsec::LoginModule.
LoginModule serves as the parent of all login modules. User plugin login modules must extend this class. Login modules are configured in the authentication configuration file and called during the login process. Login modules are responsible for authenticating the given subject and associating relevant Principals and Credentials with the subject. They are also responsible for removing and disposing of such security information during logout.
To use the LoginModule, you need to set it in the authentication configuration file, just like any other LoginModule. During runtime, the new customized module will need to be loaded by the secured application.
The syntax of the authentication configuration is as follows:
<realm-name> {
<class-name-of-customized-LoginModule> <required|optional>;
}
Note:
There is implicit replacement of the character “.” to “::” by VisiSecure. Hence, com.borland.security.provider.authn.HostLoginModule is equivalent to com::borland::security::provider::authn::HostLoginModule.
For more information, see “vbsec::LoginModule”.
The first thing you need to do is to determine whether or not your LoginModule will require some form of user interaction (retrieving a user name and password, for example). If so, you will need to become familiar with the CallbackHandler interfaces readily available. (Alternatively, you can create your own Callback implementations.)
The LoginModule will invoke the CallbackHandler specified by the application itself and passed to the LoginModule's initialize method. The LoginModule passes the CallbackHandler which is an array of appropriate Callbacks.
If the LoginModule implementations have no end-user interactions, the LoginModules would not need to access the callback package.
You must also determine what configuration options you want to make available to the user, who specifies configuration information in whatever form the current Configuration implementation expects (for example, in files). For each option, decide the option name and possible values.
For example, if a LoginModule may be configured to consult a particular authentication server host, decide on the option's key name ("auth_server", for example), as well as the possible server hostnames valid for that option.
To implement the login module, you first have to decide on the proper package and class name for your LoginModule.
The LoginModule interface specifies five abstract methods that require implementations: initialize, login, commit, abort, logout.
For more information on implementing login modules, see the Login Module Developer’s Guide in the Oracle JDK - JAAS Documentation.
In addition to these methods, a LoginModule implementation must provide a public constructor with no arguments. This allows for its proper instantiation by a LoginContext.
Note:
If no such constructor is provided in your LoginModule implementation, a default no-argument constructor is automatically inherited from the Object class.
The LoginContext is responsible for reading the configuration and instantiating the appropriate LoginModules. Each LoginModule is initialized with a subject, a CallbackHandler, shared LoginModule state, and LoginModule-specific options. The subject represents the subject currently being authenticated and is updated with relevant credentials if authentication succeeds.
The LoginModule-specific options represent the options configured for this LoginModule by an administrator or user in the login configuration. The options are defined by the LoginModule itself and control the behavior within it.
The calling application sees the authentication process as a single operation. However, the authentication process within the LoginModule proceeds in two distinct phases.
In the first phase, the LoginModule's login method gets invoked by the LoginContext's login method. The login method for the LoginModule then performs the actual authentication (prompt for and verify a password for example) and saves its authentication status as private state information. Once finished, the LoginModule's login method either returns true (if it succeeded) or false (if it should be ignored), or throws a LoginException to specify a failure. In the failure case, the LoginModule must not retry the authentication or introduce delays. The responsibility of such tasks belongs to the application. If the application attempts to retry the authentication, the LoginModule's login method will be called again.
In the second phase, if the LoginContext's overall authentication succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules succeeded), then the commit method for the LoginModule gets invoked. The commit method for a LoginModule checks its privately saved state to see if its own authentication succeeded. If the overall LoginContext authentication succeeded and the LoginModule's own authentication succeeded, then the commit method associates the relevant principals (authenticated identities) and credentials (authentication data such as cryptographic keys) with the subject located within the LoginModule.
If the LoginContext's overall authentication failed (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules did not succeed), then the abort method for each LoginModule gets invoked. In this case, the LoginModule removes/destroys any authentication state originally saved.
Logging out a subject involves only one phase. The LoginContext invokes the LoginModule's logout method. The logout method for the LoginModule then performs the logout procedures, such as removing principals or credentials from the subject or logging session information.
A LoginModule implementation must have a constructor with no arguments. This allows classes which load the LoginModule to instantiate it.
CallbackHandlers
CallbackHandler is the mechanism that produces any necessary user callbacks for authentication credentials and other information. Callbacks are an array of callback objects which contain the information requested by an underlying security service that has the ability to interact with a calling application to retrieve specific authentication data such as usernames and passwords, or to display certain information, such as errors and warning messages.
The CallbackHandler may be used to prompt for usernames and passwords, for example. Note that the CallbackHandler may be null. LoginModules which absolutely require a CallbackHandler to authenticate the subject may throw a LoginException. LoginModules optionally use the shared state to share information or data among themselves.
Underlying security services make requests for different types of information by passing individual callbacks to the CallbackHandler. The CallbackHandler implementation decides how to retrieve and display information depending on the callbacks passed to it.
For example, if the underlying service needs a username and password to authenticate a user, it uses a NameCallback and PasswordCallback. The CallbackHandler can then choose to prompt for a username and password serially, or to prompt for both in a single window.
There are seven types of callbacks provided. There is a default handler that handles all callbacks in interactive text mode.
You can implement your own callback by extending vbsec::CallBackHandler.
To use the callback, you need to set the property vbroker.security.authentication. callbackHandler=<custom-handler-class-name> in the security property file, just like any other callback handler. This property specifies the callback handler that is used by login modules for interacting with users for credentials. You can specify one of the callback handlers provided, or your own custom callback handler. For information about this property, see vbroker.security.authentication. callbackHandler for Java, or vbroker.security. authentication. callbackHandler for C++.
See “VisiSecure for C++ APIs” for more details.
At runtime, the new customized module will need to be loaded by the secured application.
Implementations of the callback interface are passed to a CallbackHandler, allowing underlying security services that have the ability to interact with a calling application to retrieve specific authentication data such as usernames and passwords, or to display certain information, such as error and warning messages.
Callback implementations do not retrieve or display the information requested by underlying security services. Callback implementations simply provide the means to pass such requests to applications, and for applications, if appropriate, to return requested information to the underlying security services.
Authorization Service Providers
Authorization is the process of making access control decisions on behalf of certain resources based on security attributes or privileges. VisiSecure uses the notion of Permission in authorization. The class RolePermission is defined to represent a “role” as a permission. Authorization Services Providers in turn provide the implementation on the homogeneous collection of role permissions that associate privileges with particular resources.
The implementer of the Authorization Service provides the collection of permission objects that are granted access to certain resources. Whenever an access decision is going to be made, the AuthorizationServicesProvider is consulted. The Authorization Service is closely associated with the Authorization domain concept. One Authorization Service is installed for each Authorization domain implementation, and functions only for that particular Authorization domain.
The AuthorizationServicesProvider is initialized during the construction of its corresponding Authorization domain.
Use the following property to set the implementing class for the AuthorizationServicesProvider:
vbroker.security.domain.<domain-name>.provider
At runtime, this property is loaded by way of Java reflection.
Another important functionality of the Authorization Service is to return the run-as alias for a particular role given. The security service is configured with a set of identities, identified by aliases. When resources request to “run-as” a given role the AuthorizationServices is again consulted to return the alias that must be used to “run-as” in the context of the rules specified for this authorization domain.
Authorization service providers are tightly connected with Authorization Domains. Each domain has exactly one authorization service provider implementation. During the initialization of the ORB, the authorization domains defined by vbroker.security.authDomains is constructed, while the Authorization Service Provider implementation is instantiated during the construction of the domain itself.
To plugin an authorization service, you need to set these properties:
vbroker.security.auth.domains=MyDomain
vbroker.security.domain.MyDomain.provider=MyProvider
vbroker.security.domain.MyDomain.property1=xxx
vbroker.security.domain.MyDomain.property2=xxx

vbroker.security.identity.attributeCodecs=MyCodec
vbroker.security.adapter.MyCodec.property1=xxx
vbroker.security.adapter.MyCodec.property2=xxx
The properties specified will be passed to the user plugin following the same mechanism as above.
Example
You can write a custom authorization module using both user names and groups. Use a HostLoginModule, since that is the only supported login module for C++ VisiBroker security applications. HostLoginModule must show the configurations required and the code required for CORBA components to use the authorization framework. The client needs to have these authorizations in order to access these components in the server.
The roles must be hardcoded into the authorization provider code. The groups for a user can also be obtained from a different source programmatically and the subject can be populated with groups as privileges added to the public credentials of the subject in question, at runtime, for use by VisiSecure authorization mechanism.
You can match the user/group with roles obtained from an external source (for example, a legacy system) other than the Micro Focus rolemap mechanism.
USE_STD_NS is a definition set up by VisiBroker to use the std namespace:
USE_STD_NS
typedef pair<std::string, std::string> String_String_Pair;
typedef pair< std::string, std::set<std::string> > String_Set_Pair;
const std::string USE_CALLER_IDENTITY = (const char*)"use-caller-identity";
const std::string RUNAS = (const char*)"runas.";
void CustomProviderImpl::initialize (const std::string& name, vbsec::InitOptions& opts)
{
To store the name of the module:
_name = name;
cout << "Custom authorization service Provider : " << name << endl;
custProvider = this;
To print out the options given to the custom authorization service provider:
std::map<std::string, std::string> t_options;
std::map<std::string, std::string>::iterator itr;
std::basic_string <char>::size_type index1;
static const std::basic_string <char>::size_type npos = -1;
std::basic_string<char> t_key, ts;
 
t_options = *(opts.options);
for ( itr = t_options.begin(); itr!=t_options.end(); itr++ ) {
cout << "Options key :" << itr->first << ", value : " << itr->second << endl;
t_key = (itr->first).substr(0,RUNAS.size()-1);
if ( t_key == RUNAS ) {
cout << "runas property found :" << itr->first << endl;;
ts = itr->first;
index1 = ts.find_last_of(".", ts.size()-1);
if ( index1 != npos )
ts = ts.substr( index1+1, ts.size()-1 );
else
ts = "";
cout << "runas role : " << ts << endl;
if ( itr->second == USE_CALLER_IDENTITY )
_callerRunAsRoles.insert(ts);
else
_runAsMap.insert(String_String_Pair(ts, itr->second) );
}
}
To store the logger reference:
_logger = opts.logger;
To store logLevel:
_logLevel = opts.logLevel;
You can also use an in-memory role table. To create the in-memory DB that holds the users and groups, do the following:
createInMemoryDB();
return;
}
vbsec::PermissionCollection* CustomProviderImpl::getPermissions(const vbsec::Resource* res, const vbsec::Privileges* prv)
{
CustomProviderImpl::CustomPermissionCollectionImpl* perm_coll = new CustomProviderImpl::CustomPermissionCollectionImpl();
perm_coll->init( (vbsec::Privileges*)prv );
return ( (vbsec::PermissionCollection*) perm_coll );
}
std::string CustomProviderImpl::getRunAsAlias(const std::string& s)
{
std::string s1;
std::map<std::string, std::string>::iterator it;
std::map< std::string, std::set<std::string> >::iterator it2;
std::set<std::string>::iterator it3;
 
it = _runAsMap.find(s);
if ( it == _runAsMap.end() ) {
it2 = _inMemoryDB.find(s);
if ( it2 == _inMemoryDB.end() )
throw CORBA::NO_PERMISSION((CORBA::ULong)0x56422501, CORBA::CompletionStatus::COMPLETED_NO, (const char*)"The RunAs Role specified does not exist");
it3 = _callerRunAsRoles.find(s);
if ( it3 != _callerRunAsRoles.end() )
s1 = USE_CALLER_IDENTITY;
else
s1 = (const char*)"";
}
else
s1 = it->second;
return s1;
}
void CustomProviderImpl::createInMemoryDB()
{
For example, if the authorization requirement for the BankManager object is that the clients should be members of the "Manager" role and for the Account object, it is the "Customer" or "Teller" role:
1
_role1_ug.insert("jjagadeesan"); // user "jjagadeesan"
_role1_ug.insert("FI.PSO"); // group "FI.PSO"
_inMemoryDB.insert(String_Set_Pair("Manager", _role1_ug));
2
_role2_ug.insert("admin"); // user "admin"
_inMemoryDB.insert(String_Set_Pair("Customer", _role2_ug));
3
_role3_ug.insert("admin"); //user "admin"
_role3_ug.insert("user"); // group "user"
_inMemoryDB.insert(String_Set_Pair("Teller", _role3_ug));
}
std::set<std::string>* CustomProviderImpl::getRoleEntries( std::string& role )
{
std::set<std::string> * roleEntries;
std::map< std::string, std::set<std::string> >::iterator it;
 
it = _inMemoryDB.find( role );
if ( it == _inMemoryDB.end() ) {
roleEntries = new std::set<std::string>();
roleEntries->clear();
}
else {
roleEntries = new std::set<std::string>(it->second);
}
return roleEntries;
}
4
Implementation of the functions of the CustomPermissionCollectionImpl class:
void CustomProviderImpl::CustomPermissionCollectionImpl::init( vbsec::Privileges *prv )
{
_privileges = prv;
_provider = CustomProviderImpl::custProvider;
}
bool CustomProviderImpl::CustomPermissionCollectionImpl::implies (const ::vbsec::Permission& p) const
{
bool matchedRole = false;
std::string userName;
std::string groupName;
string s = p.getName();
// if(_logLevel >= 5 )
// _logger.notice( null, "Permission: " + s );
cout << "In CustomAuthorizationProvider::implies: Permission role: " << s << endl;
vbsec::Privileges *privileges = _privileges;
vbsec::Subject& subject = privileges->getSubject();
std::set<vbsec::Principal *> principals = subject.getPrincipals();
std::multimap<std::string, std::string> groupMap = privileges->getAttributes();
std::multimap<std::string, std::string>::iterator it_groups;
std::set<std::string> groups;
it_groups = groupMap.find("group");
while ( it_groups != groupMap.end() ) {
if ( it_groups->first == "group" ) {
groups.insert( it_groups->second );
break;
}
++it_groups;
}
std::set<std::string> * roleEntities = _provider->getRoleEntries(s );
5
if ( !roleEntities )
{
cout << "In CustomAuthorizationProvider::implies: Role: " << s << " not found in roles table" << endl;
return false;
}
if( roleEntities->empty() )
{
cout << "In CustomAuthorizationProvider::implies: Role: " << s << " not found in roles table" << endl;
delete roleEntities;
return false;
}
6
if ( principals.empty() )
{
delete roleEntities;
return false;
}
std::set<vbsec::Principal *>::iterator i;
std::set<std::string>::iterator i_set_str, i_set_str2;
for ( i = principals.begin(); i != principals.end(); i++ ) {
vbsec::UserPrincipal *up = dynamic_cast<vbsec::UserPrincipal*>( *i );
userName = up->getUserName();
cout << "In CustomAuthorizationProvider::implies: Checking for username match: " << userName << endl;
i_set_str = roleEntities->find( userName );
if ( i_set_str != roleEntities->end() ) {
cout << "In CustomAuthorizationProvider::implies: Found role entry for username:" << userName << endl;
delete roleEntities;
return true;
}
}
7
if ( groups.empty() )
{
delete roleEntities;
return false;
}
for ( i_set_str = groups.begin(); i_set_str != groups.end(); i_set_str++ ) {
groupName = (*i_set_str );
cout << "In CustomAuthorizationProvider::implies: Checking for groupname match: " << groupName << endl;
i_set_str2 = roleEntities->find( groupName );
if ( i_set_str2 != roleEntities->end() ) {
cout << "In CustomAuthorizationProvider::implies: Found role entry for groupname:" << groupName << endl;
delete roleEntities;
return true;
}
}
delete roleEntities;
return false; // all match failed
}
****************************
#ifndef _CUSTOMPROVIDER_H_
#define _CUSTOMPROVIDER_H_
#include "vbauthz.h"
#include <map>
#include <set>
#include <hash_map>
#include <string>
#include <iostream>
// typedef pair<std::string, std::string> String_String_Pair;
// typedef pair<std::string, std::set> String_Set_Pair;
// USE_STD_NS is a define setup by VisiBroker to use the std namespace
USE_STD_NS
class CustomProviderImpl : public vbsec::AuthorizationServiceProvider
{
class CustomPermissionCollectionImpl : public vbsec::PermissionCollection
{
public:
CustomPermissionCollectionImpl() {}
void init( vbsec::Privileges* prv );
virtual bool implies (const vbsec::Permission& p) const;
virtual ~CustomPermissionCollectionImpl () {}
private:
vbsec::Privileges* _privileges;
CustomProviderImpl* _provider;
};
public:
CustomProviderImpl() : _logLevel((int)0), _name(""), _logger((vbsec::SimpleLogger*)NULL)
{}
virtual std::string getName() const
{
return _name;
}
virtual void initialize (const std::string& name, vbsec::InitOptions& opts);
vbsec::PermissionCollection* getPermissions(const vbsec::Resource* res, const vbsec::Privileges* prv);
std::string getRunAsAlias(const std::string& s);
void createInMemoryDB();
std::set<std::string>* getRoleEntries( std::string& role );
static CustomProviderImpl * custProvider;
private:
CORBA::ULong _logLevel;
::vbsec::SimpleLogger* _logger;
std::map<std::string, std::string> _runAsMap;
std::set<std::string> _callerRunAsRoles;
std::string _name;
std::map<std::string, std::set<std::string> > _inMemoryDB; // contains the known roles
std::set<std::string> _role1_ug, _role2_ug, _role3_ug; // contains the users and/or groups for the roles in _inMemoryDB
};
REGISTER_CLASS(CustomProviderImpl)
CustomProviderImpl * CustomProviderImpl::custProvider;
#endif
You can secure your application using VisiBroker properties and the JAAS configuration file. The example client and server uses username/password authentication of the client on the server and also for the server's self-authentication.
Look at the different properties files (server.properties, client.properties) and config files (server.config and client.config) in the <install_dir>\examples\vbroker\security/corbaauthz folder.
The server configuration file is the JASS configuration file which defines the Hostlogin modules.
myrealm {
com.borland.security.provider.authn.HostLoginModule required debug=true;
};
To enable security, you must set up the following properties in the server or client properties file:
The default value is false. If set to true, disables all security services.
If this property is set to true, during initialization, this property tries to log on to all the realms listed by the property vbroker.security.login.realms.
When set to none, Authentication is not required. During handshake, no certificate request will be sent to the peer. Regardless of whether the peer has certificates, a connection will be accepted. There will be no transport identity for the peer.
Trust Providers
You can also plugin the assertion trust mechanism. Assertion can happen in a multi-hop scenario, or can be explicitly called through the assertion API. The server can have rules to determine whether the peer is trusted to make the assertion or not. The default implementation uses property settings to configure trusted peers on the server side. During run-time, peers must pass authentication and authorization in order to be trusted for making assertions. There can be only one Trust Provider for the entire security service.
To plugin the assertion trust mechanism, you will need to set the following properties:
vbroker.security.trust.trustProvider=MyProvider
vbroker.security.trust.trustProvider.MyProvider.property1=xxx
vbroker.security.trust.trustProvider.MyProvider.property2=xxx
The properties specified will be passed to the user plugin following the same mechanism as above.