Get AD Groups with kerberos ticket in Java - java

I am obtaining a kerberos ticket with the following code:
String client = "com.sun.security.jgss.krb5.initiate";
LoginContext lc = new LoginContext(client, new CallbackHandler() {
#Override
public void handle(Callback[] arg0) throws IOException, UnsupportedCallbackException {
System.out.println("CB: " + arg0);
}
});
lc.login();
System.out.println("SUBJ: " + lc.getSubject());
This code works fine, I get a subject that shows my user ID. The problem I'm having is now I need to know whether the user belongs to a certain group in AD. Is there a way to do this from here?
I've seen code to get user groups using LDAP but it requires logging in with a user/password, I need to do it the SSO way.

You cannot actually do this with the kind of ticket you get at login. The problem is that the Windows PAC (which contains the group membership information) is in the encrypted part of the ticket. Only the domain controller knows how to decrypt that initial ticket.
It is possible to do with a service ticket.
So, you could set up a keytab, use jgss to authenticate to yourself and then decrypt the ticket, find the PAC, decode the PAC and then process the SIDs. I wasn't able to find code for most of that in Java, although it is available in C. Take a look at this for how to decrypt the ticket.
Now, at this point you're talking about writing or finding an NDR decoder, reading all the specs about how the PAC and sids are put together, or porting the C code to Java.
My recommendation would be to take a different approach.
Instead, use Kerberos to sign into LDAP. Find an LDAP library that supports Java SASL and you should be able to use a Kerberos ticket to log in.
If your application wants to know the groups the user belongs to in order to populate menus and stuff like that, you can just log in as the user.
However, if you're going to decide what access the user has, don't log in as the user to gain access to LDAP. The problem is that with Kerberos, an attacker can cooperate with the user to impersonate the entire infrastructure to your application unless you confirm that your ticket comes from the infrastructure.
That is, because the user knows their password, and because that's the only secret your application knows about, the user can cooperate with someone to pretend to be the LDAP server and claim to have any access they want.
Instead, your application should have its own account to use when accessing LDAP. If you do that, you can just look up the group list.
I do realize this is all kind of complex.

Related

Microsoft exchange - IMAP OAuth2 With client credentials

I am writing application that need to read mailbox using IMAP, but as daemon, without user interaction. I need to use OAuth2 to get access.
Because I need it without user interaction, I need to use client credentials flow. This was added this June.
I have done everything from official documentation. Registered application, added permissions, added mailbox permission using PowerShell.
When I get request access token with scope https://outlook.office365.com/.default, the one that I receive has role IMAP.AccessAsApp, so I believe that is correct. I used https://jwt.ms/ to parse JWT.
The problem is when I try to authenticate using this access token in Java, for example
Properties props = new Properties();
props.put("mail.imap.ssl.enable", "true");
props.put("mail.imap.auth.mechanisms", "XOAUTH2");
props.put("mail.debug", "true");
Session session = Session.getInstance(props);
Store store = session.getStore("imap");
store.connect("outlook.office365.com", 993, "testing#mydomain.com", "accessToken");
I receive AUTHENTICATE failed. I tried same code with access token received using authorization code flow, which requires user interaction. Using that access code I was able to connect to mailbox. So the code is correct.
I even tried using client id and service id instead of email address as username, but without success.
I am not sure where I made the mistake and if I am using correct username. Any help is appreciated.
I wrote same answer here, so I am coping it here.
I think I made some progress.
I read documentation few times, tried few times from the start with same error. I even have tried using client and object ids instead of email as username, in lack of better ideas.
So this is where I think I have made mistake previous times.
On the part where it is needed to register service principal, I needed to execute
New-ServicePrincipal -AppId <APPLICATION_ID> -ServiceId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]
Here I have put enterprise application object id as ServiceId argument. And that is ok.
But on
Add-MailboxPermission -Identity "email address removed for privacy reasons" -User
<SERVICE_PRINCIPAL_ID> -AccessRights FullAccess
I have put my registered application object id as User argument. I also tried setting object id of enterprise application, but it did not have success.
When I executed
Get-ServicePrincipal -Organization <ORGANIZATION_ID> | fl
I did not pay attention to ServiceId property, even with documentation specifying it and saying it will be different.
Now I cleared everything and started fresh.
I have executed all the steps again, but on the step for creating new service principal I used data from enterprise application view. When I need to add mail permission, I list service principals, and then use ServiceId value from the output, as argument for user.
With that, I was able to authorise.
Thanks everyone for sharing your experience. This has proved to be a little confusing. :)
To sum everything up, to access a mailbox with IMAPS and OAuth2 (as opposed to using Graph API which is another method Microsoft recommends):
Create an Azure App Registration
Add API permission Office 365 Exchange Online - IMAP.AccessAsApp and grant admin consent
Create a service principal, which will be used to grant mailbox permissions to in Exchange Online
Connect-AzureAD
Connect-ExchangeOnline
$azapp = Get-AzureADApplication -SearchString 'App Registration Name'
$azsp = Get-AzureADServicePrincipal -SearchString $azapp.DisplayName
# GOTCHA: You need the ObjectId from 'Enterprise applications' (Get-AzureADServicePrincipal), not 'Application registrations' (Get-AzureADApplication) for ServiceId (thanks #[jamie][1])
$sp = New-ServicePrincipal -AppId $azapp.AppId -ServiceId $azsp.ObjectId -DisplayName "EXO Service Principal for $($azapp.DisplayName)"
Grant access rights to mailboxes for the service principal
$mbxs = 'mymbx1#yourdomain.tld',`
'mymbx2#yourdomain.tld',`
'mymbx3#yourdomain.tld'
$mbxs | %{ Add-MailboxPermission -Identity $_ -User $sp.ServiceId -AccessRights FullAccess } | fl *
Get-MailboxPermission $mbxs[-1] | ft -a
You can use Get-IMAPAccessToken.ps1 to test your setup
.\Get-IMAPAccessToken.ps1 -TenantID $TenantId -ClientId $ClientId -ClientSecret $ClientSecret -TargetMailbox $TargetMailbox
Other parameters you may need:
Authority: https://login.microsoftonline.com/<YourTenantId>/
Scope: https://outlook.office365.com/.default

Simultaneously accessing two dynamodb tables belonging to different accounts using assume role does not work

I have a java application running in ECS in which I want to read data from table in account 1 (source_table) and write it to a table in account 2 (destination_table). I created two dynamodb clients with different credential providers - for source_table client I'm using an STSAssumeRoleSessionCredentialsProvider with the arn of a role in account 1; for destination client I'm using DefaultAWSCredentialsProviderChain.
The assume role bit works and I'm able to read using the source client but using the destination client does not work - it still tries to use the assumed role credentials when trying to write to destination_table and fails with unauthorized error (assumed-role is not authorized to perform Put Item).
I tried using EC2ContainerCredentialsProviderWrapper on the destination client but same error.
Should this work? Or are the credentials shared under the hood which makes it impossible to have two different AWSCredentialProviders running simultaneously like this?
I noticed this answer which uses static credentials and apparently works, so I'm at a loss why this doesn't work.
I figured it out with some help from AWS support. It was a problem with my IAM configuration on the role in account 2. I was misled by the error message which said 'assumed-role is not authorized to perform Put Item' when in fact my original account 2 role itself was unable to do so.

Storing verifaction code

I am building a web app which runs react on the front end and Java/Spring on the back end.
I am using a RESTful API to communicate with my client (client will communicate with an external API too).
I am facing a problem. When a user registers, I want to send an email verification code. My question is about practice.
Is it ok to send an email with a verification code, store the code in the database and then check if the code is correct?
Or is it better to create some static bean which would hold the code for a while and then check if it's correct?
I am not sure which is better in terms of back-end logic.
I appreciate all help.
It's a very common practice to store this in the DB. Just make sure to cleanup the expired tokens once in a while (maybe on timer, maybe when inserting new ones).
You can also store the tokens in an in-memory cache (EhCahe comes to mind) and set the expiration time accordingly, but this way you lose the cache if the application shuts down. Yes, you can make the cache persistent, but why not go the DB route then?
With that said, think about not storing the token anywhere and instead send the link with the email as an URL parameter and a salted hash as an additional parameter. Once the link is clicked, you can check if the hash matches and if it does, the email parameter wasn't tempered with, so you can mark it as validated. Maybe not Pentagon-level secure, but probably enough for email validation and makes everything easier.
In pseudo-code:
public String hash(String email) {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
digester.update(email.getBytes(StandardCharsets.UTF_8)));
digester.update("RanDOmComplCatEdSalt647826583745".getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(digester.digest());
}
String email = "dude#example.com";
sendEmailWithLink("/verify?email=" + email + "&hash=" + hash(email));
On the way in, you just do the exact same thing to calculate and check if the hash matched:
String email = httpRequest.getParameter("email");
String receivedHash = httpRequest.getParameter("hash");
if (hash(email).equals(receivedHash)) {
//the user didn't do anything funny, mark email as valid
}
Or, taking this idea further but with more security, you can generate something like a JWT that contains the email and is properly encrypted.

Share Current User Data Between Subdomains on Google App Engine for Java

I am Using Google App Engine for Java and I want to be able to share session data between subdomains:
www.myapp.com
user1.myapp.com
user2.myapp.com
The reason I need this is that I need to be able to detect if the user was logged in on www.myapp.com when trying to access user1.myapp.com. I want to do this to give them admin abilities on their own subdomains as well as allow them to seamlessly switch between subdomains without having to login again.
I am willing to share all cookie data between the subdomains and this is possible using Tomcat as seen here: Share session data between 2 subdomains
Is this possible with App Engine in Java?
Update 1
I got a good tip that I could share information using a cookie with the domain set to ".myapp.com". This allows me to set something like the "current_user" to "4" and have access to that on all subdomains. Then my server code can be responsible for checking cookies if the user does not have an active session.
This still doesn't allow me to get access to the original session (which seems like it might not be possible).
My concern now is security. Should I allow a user to be authenticated purely on the fact that the cookie ("current_user" == user_id)? This seems very un-secure and I certainly hope I'm missing something.
Shared cookie is most optimal way for your case. But you cannot use it to share a session on appengine. Except the case when you have a 3rd party service to store sessions, like Redis deployed to Cloud Instances.
You also need to add some authentication to your cookie. In cryptography there is a special thing called Message Authentication Code (MAC), or most usually HMAC.
Basically you need to store user id + hash of this id and a secret key (known to both servers, but not to the user). So each time you could check if user have provided valid id, like:
String cookie = "6168165_4aee8fb290d94bf4ba382dc01873b5a6";
String[] pair = cookie.split('_');
assert pair.length == 2
String id = pair[0];
String sign = pair[1];
assert DigestUtils.md5Hex(id + "_mysecretkey").equals(sign);
Take a look also at TokenBasedRememberMeServices from Spring Security, you can use it as an example.

Session management between thick client and server?

My application is a Eclipse Rich Client and I would like to add authentication and authorization features to. My Users and roles are stored in a database and my application also has a web based admin console which lets me manage users and roles. I am leveraging Spring security on this admin console.
So here's my requirement:
I would like my thick client to provide users with a login dialog box. The authentication would need to be performed on the server side (it could be a webservice) and the roles have to flow in to the thick client. I would also like to manage sessions on the server side, somehow.
I really can't think of any easy way to doing this. I know that if I were to use Spring Rich Client, it would integrate pretty well with Spring Security on the server side.
But, that is not an option for me at this point.
Please share your thoughts on how to acheive this. Appreciate your help.
Since you're leaning toward web services (it sounds like you are) I'd think about taking the user information from your rich client (I assume user ID and password), using WS-Security to send the encrypted info to a web service, and having the web service do the auth stuff. Also I'd think about the web service returning any info that you want to go back to the rich client about the user (first/last name, etc).
I developed a similar application recently using the Challenge-Response-authentication. Basically you have three methods in your webservice or on your server
getChallenge(username) : challenge
getSession(username, response) : key
getData(username, action?) : data
getChallenge returns a value (some random value or a timestamp for instance) that the client hashes with his/hers password and sends back to getSession. The server stores the username and the challenge in a map for instance.
In getSession the server calculates the same hash and compares against the response from the client. If correct, a session key is generated, stored, and sent to the client encrypted with the users password. Now every call to getData could encrypt the data with the session key, and since the client is already validated in getSession, s/he doesn't have to "login" again.
The good thing about this is that the password is never sent in plain text, and if someone is listening, since the password is hashed with a random value, the call to getSession will be hard to fake (by replaying a call for instance). Since the key from getSession is sent encrypted with the users password, a perpetrator would have to know the password to decipher it. And last, you only have to validate a user once, since the call to getData would encipher the data with the users session key and then wouldn't have to "care" anymore.
I've a similar requirement I think. In our case:
user provides username and password at login
check this against a USER table (password not in plain text btw)
if valid, we want a session to last, say, 20 minutes; we don't want to check username and password every time the thick client does a retrieve-data or store-data (we could do that, and in fact it wouldn't be the end of the world, but it's an extra DB op that's unnecessary)
In our case we have many privileges to consider, not just a boolean "has or has not got access". What I am thinking of doing is generating a globally unique session token/key (e.g. a java.util.UUID) that the thick client retains in a local ThickClientSession object of some sort.
Every time the thick client initiates an operation, e.g. calls getLatestDataFromServer(), this session key gets passed to the server.
The app server (e.g. a Java webapp running under Tomcat) is essentially stateless, except for the record of this session key. If I log in at 10am, then the app server records the session key as being valid until 10:20am. If I request data at 10:05am, the session key validity extends to 10:25am. The various privilege levels accompanying the session are held in state as well. This could be done via a simple Map collection keyed on the UUID.
As to how to make these calls: I recommend Spring HTTP Invoker. It's great. You don't need a full blown Spring Rich Client infrastructure, it can be very readily integrated into any Java client technology; I'm using Swing to do so for example. This can be combined with SSL for security purposes.
Anyway that's roughly how I plan to tackle it. Hope this is of some use!
Perhaps this will help you out:
http://prajapatinilesh.wordpress.com/2009/01/14/manually-set-php-session-timeout-php-session/
Notice especially this (for forcing garbage collection):
ini_set(’session.gc_maxlifetime’,30);
ini_set(’session.gc_probability’,1);
ini_set(’session.gc_divisor’,1);
There is also another variable called session.cookie_lifetime which you may have to alter as well.
IIRC, there are at least 2, possibly more, variables that you have to set. I can't remember for the life of me what they were, but I do remember there was more than 1.

Categories