I am using the Java class org.apache.hadoop.security.
authentication.server.AuthenticationFilter from Apache
Hadoop 2.5.0 as a filter in front of a Tomcat 6 Servlet we
wish to add Kerberos authentication to.
I am attempting to write some test cases against this filter
so that we have a better understanding of how it
works and what it does.
In order for the filter to authenticate a user, it is reading the
'Authorization' header of the HTTP request,
expecting the value to contain 'Negotiate '
My understanding of how Kerberos works leads me to believe that I
should be able to write code while creating my
HTTP request that looks something like this:
// normally the server principal keytab is not available from the client side,
// but for the purpose of making test cases I see no problem with sharing the keytab
// between the client side and the server side
javax.security.auth.kerberos.Keytab kt = KeyTab.getInstance("keytab");
KerberosKey keys[] = kt.getKeys("HTTP/voltage-pp-0000.albert.int#ALBERTS.INT");
SomeTokenType token = new SomeTokenType();
<code to set token parameters>
// my understanding of Kerberos is that the only cyphertext key
// needed on this token
// is one of the server principal's keys from the Keytab file
// (which does contain ~5
// keys of different sizes and types, I've checked)
EncryptedTokenType etoken = <encrypt token with a key from keys>
byte[] array = etoken.getBytes();
httprequest.addHeader("Authorization","Negotiate " + new Base64(0).encode(array));
So, questions here:
What is the Java Class that embodies the Kerberos Auth Token sent
in "Authorization Negotiate"?
What fields of that auth token have to be set to what values?
What is the encryption algorithm used to encrypt the auth token
against the keytab key?
What is the best keytab key to use?
What is the mechanism for byte-serializing the auth token, once
encrypted?
You are correct in that it is possible to "forge" a ticket in this manner. However, I know of no standard kerberos API that would do this.
You'll essentially need to reverse engineer the entire kerberos protocol to
create a service ticket based on the keytab. The service ticket format is
documented here
https://www.ietf.org/rfc/rfc4120.txt
You can use any of the keys in the keytab to encyrpt the service ticket. Once
you have the service ticket, you'll need to implement this RFC to create the
negotiation header.
https://www.rfc-editor.org/rfc/rfc4559
Generally, it's a lot simpler to just get a keytab for a client principal and use that and kinit to get a service ticket for testing. Your approach could work
and there is probably hacker code out there somewhere to implement it, but it's
an extremely non-standard way to do testing in a kerberos environment.
Related
Team,
I have an requirement like i have to support to my partner (third party) portal to call us directly by making api call with credentials from their browser.
e.g.) Partner portal browser makes AJAX Call with below:
URL ---> https://example.com/request
HEADER ---> user_id : foo
HEADER ---> password : mypasswd
payload ---> {
"request_time" : 2232876435,
"request_name" : "get_user_info",
...
...
}
And their browser/portal is accessible/used by untrusted users of theirs. So now problem is since the call is from the front end; end user easily can inspect the browser to see the network api calls along with credential we have given to our partner to authorise at our side.
So i am planning to give suggestion to partner by asking them to encrypt the payload and headers in their portal backend server and render the encrypted information in the portal like below.
Encrypt (payload) using mypasswd.
Encrypt (password) using request_time <NOW OPTIONAL TO PASS>
So now,
e.g.) URL ---> https://example.com/request
HEADER ---> user_name : foo
HEADER ---> password : ENCRYPTED<mypasswd> <-- OPTIONAL
payload ---> ENCRYPTED<
{
"request_time" : 2232876435,
"request_name" : "get_user_info",
...
...
}
>
So in our system we will decrypt payload with mypasswd retrieved for user_id foo. so if decryption is successful, then the request is from valid resource.
Now the end portal user cannot understand the request from browser inspection.
NOTES:
I can't suggest my partner to call from their backend.
From the request payload i can identify repeated same request through unique transaction id, so they can't resubmit the same request. Hence avoiding replay attack.
Questions:
Q1) Any flaw or suggestion on this solution?
Q2) Is it possible to identify the decryption using passphrase is success or not in java? I am new to encryption, so could you please share any code or link to achieve this?
yours thoughts much valuable to me.
TLDR:
References:
Basic encryption details
https://blog.storagecraft.com/5-common-encryption-algorithms/
https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography
https://gooroo.io/GoorooTHINK/Article/13023/The-difference-between-encryption-hashing-and-salting/2085#.W2L_KdgzZD0
Java Encryption
How to encrypt and decrypt String with my passphrase in Java (Pc not mobile platform)?
Java Security: Illegal key size or default parameters?
Identifying decryption is successful through this exception:
Given final block not properly padded
EDIT: I misunderstood the question. If the information is encrypted by the third party before it reaches the end-user then this approach is generally safe. Replay attacks are the main thing to look out for. If the request being made is idempotent then you don't really need to worry, but otherwise you might need to implement a short-lived database for used tokens along with an expiry time or something similar.
You are solving this problem the wrong way. Having the end user make this request to you on behalf of the third party is silly - if the request comes from their browser then by definition they control the information they are sending and the way it is sent. Encryption does nothing to solve this since the encryption logic is also client side.
The solution to this problem is to eliminate the end-user. The request should come directly from the third party to you. This might be from the end-user making a request to the third party API or it might not - it doesn't matter.
This is in regards to version 0.2 of the Kubernetes Java client. I'm guessing the way to use basic authentication in the Java API is to do this
ApiClient client = Config.fromUserPassword( "https://....:6443", "user", "password", false );
Configuration.setDefaultApiClient( client );
CoreV1Api api = new CoreV1Api();
// Make api call like
api.listNode(...)
However the above code always returns 403 Forbidden. From the response message, it doesn't look like the user/pass is being used in the request.
{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"nodes is forbidden: User \"system:anonymous\" cannot list nodes at the cluster scope","reason":"Forbidden","details":{"kind":"nodes"},"code":403}
I also debugged through the code a bit and I may be answering my own question but it looks like in CoreV1Api's methods, it never add basic auth as an authentication method and only uses BearerToken. Is basic auth supported or should I be using another API class?
Many kubernetes clusters do not set up basic auth, only bearer token auth. Are you sure your cluster configured basic authentication?
https://kubernetes.io/docs/admin/authentication/#static-password-file
Answering my own question but it doesn't look like the current version of the client actually executes the user/pass authentication. BearerToken is working however.
The java client ignores the HttpBasicAuth object, but if you use the ApiKeyAuth object and set the key prefix to "Basic" and the API key to the base64 encoded credentials, it will work.
For example:
String credentials= new String(Base64.getEncoder().encode("user:password".getBytes()));
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath("https://256.256.256.256");
ApiKeyAuth fakeBearerToken = (ApiKeyAuth) defaultClient.getAuthentication("BearerToken");
fakeBearerToken.setApiKey(credentials);
fakeBearerToken.setApiKeyPrefix("Basic");
This works because the kubernetes client will simply concatenate the API key prefix with the prefix, and put the result in the "Authorization" header.
I have Java client running on Windows machine that calls remote EJB
on JBoss EAP/Wildfly running on Linux machine.
I use Kerberos to achieve SSO. Java client verifies the user against Windows domain
and pass his identity within EJB call to the JBoss server.
I started with JAAS and the builtin com.sun.security.auth.module.Krb5LoginModule.
It works correctly except one thing: user has to type his username and password
again. So, it is not a real SSO.
The problem is that Windows prohibits to export kerberos session key from its LSA credential cache.
It can be fixed by setting a specific Windows registry key on each client machine - but this is not acceptable for the customer.
Therefore I am trying to find an alternative solution.
I learned that Windows provides SSPI that shall be interoperable with GSSAPI used by Java. I use Waffle library to access SSPI from Java on the client. On the server I keep using JAAS, because it runs on Linux so I cannot use Waffle there.
I also learned that I don't need to implement LoginModule, rather I need SASL client.
So, I had a look how com.sun.security.sasl.gsskerb.GssKrb5Client works and I am trying to reimplement it using Waffle.
First step seems to work correctly - I obtain SSPI security context from Waffle,
then get the initial token and send it to the server.
The server accepts the token and respond with its own token.
And now the problem comes. In the original SASL client the 'unwrap' operation is
used to extract data from the server token, and 'wrap' operation is used to create
reply token to be sent to server.
GSSAPI wrap / unwrap operations shall correspond to SSPI EncryptMessage / DecryptMessage
operations according to Microsoft doc. This two methods are not available in Waflle, but are available
in NetAccountClient library.
However, I am not able to use them correctly. If I use a single SECBUFFER_STREAM then the DecryptMessage
is succesfull, however the data part of the token is not extracted and I don't know how to determine
the offset where it begins.
If I use SECBUFFER_STREAM and SECBUFFER_DATA as suggested by Microsoft docs, then I get an error:
com.sun.jna.platform.win32.Win32Exception: The message or signature supplied for verification has been altered
I also tried other combinations of SECBUFFER types as suggested elsewhere, but without success.
Any idea what am I doing wrong ?
To source code of unwrap method:
public byte[] unwrap(byte[] wrapper) throws LoginException {
Sspi.SecBuffer.ByReference inBuffer = new Sspi.SecBuffer.ByReference(Secur32Ext.SECBUFFER_STREAM, wrapper);
Sspi.SecBuffer.ByReference buffer = new Sspi.SecBuffer.ByReference();
buffer.BufferType = Sspi.SECBUFFER_DATA;
Secur32Ext.SecBufferDesc2 buffers = new Secur32Ext.SecBufferDesc2(inBuffer, buffer);
NativeLongByReference pfQOP = new NativeLongByReference();
int responseCode = Secur32Ext.INSTANCE.DecryptMessage(secCtx.getHandle(), buffers, new NativeLong(1), pfQOP);
if (responseCode != W32Errors.SEC_E_OK) {
throw handleError(responseCode);
}
byte[] data = buffer.getBytes();
return data;
}
I confused with basic http authorization. It is needed to send every request to server with Authorization header or just first one and after that browser rember auth tokens like session id?
You have to send the Authorization header on each request. But for example Chrome remembers the auth tokens and sends it automatically on each request.
Using basic authentication, every request needs to have an Authorization HTTP header in the format:
Authorization: Basic <base64(username:password)>
where the username and password are concatenated using a colon (':') and the resulting string is base64 encoded.
If the Authorization header is not part of the request, or the credentials inside are not valid, the server should respond with an HTTP 401 Unauthorized response and include a HTTP header like:
WWW-Authenticate: Basic realm="myRealm"
Basic authentication is an implicit authentication scheme, so after the user enters valid credential, the browser will send them along with each page request.
For AJAX requests you'll need to attach this header from code. However, you really should not use basic authentication to protect an API, for a number of reasons:
You'd force the client to hold those credentials in code, where they can easily be stolen.
You must use HTTPS with basic authentication as base64 encoding gives no protection of the credentials at all.
Username/password combinations are usually valid much longer than an access token, thereby increasing the risk if they get stolen.
Password validation should be a slow process to mitigate brute force attacks, where token validation is just verifying a digital signature.
Having to send the username/password over the wire every time increases the attack surface for someone trying to break the encryption.
Better alternatives to protect web APIs are token based authentication schemes like OAuth2 or HMAC based authentication schemes like Hawk or AWS
Ya that's correct , so for first time when user logs in , his credentials are verified against some data , if correct , a auth token is generated.
Auth token is pretty much a self contained entity (which stores some data signed with a key)
this token gets stores at client side(usually along with a refresh token)
for all subsequent requests , this token is kept in Authorization header(Bearer+token)
When server receives this token , it decrypts it with the key , which it used earlier to sign that token. And uses that stored data
If that auth token is expired , refresh token comes into play.
some links to begin with
On a high level, how does OAuth 2 work?
and jwt.io to get the feel of tokens
This question is in some way related to the below linked question. However, I need a little more clarity on some aspects and some additional information. Refer:
REST Web Service authentication token implementation
Background:
I need to implement security for a REST Web Service using token
The webservice is intended for use with Java client. Hence, form
authentication and popups for credentials are not useful.
I'm new to REST security and encryption
This is what I have understood till now:
For first request:
User establishes https connection (or container ensures https using
301)
User POSTs username and password to login service
If credentials are valid we:
Generate a random temporary token
Store the random token on server mapping it to actual username
Encrypt the token using a symmetric key only known to server
Hash the encrypted token
Send the encrypted token and the hash to the client
For subsequent requests:
Client sends this encrypted token and hash combination (using
username field of basic?)
We make sure the encrypted token is not tampered using the hash and
then decrypt it
We check the decrypted token in the session-tracking-table for a
not-expired entry and get the actual username (expiry to be managed
by code?)
If the username is found, based on allowed roles, allowed operations
are configured
More details:
Since client is a java client, the first request can be a POST
containing the credentials. However, this looks like it may expose
the credentials before the https gets established. Hence should
there be a dummy GET to a secured resource so that https is
established first?
Assuming above is required, the second request is a LoginAction POST
with credentials. This request is handled manually (not using
container's authorisation). Is this right?
The above LoginAction returns the user the combination of encrypted
token + hash
User sets it to the header that is used by BASIC authentication
mechanism (field username)
We implement a JAASRealm to decrypt and validate the token, and find
the roles allowed
The rest of authorisation process is taken care of by the container
with the WebResourceCollection defined in the web.xml
Is this the correct approach?
Why not simplify it to the following?
For first request:
User establishes HTTPS connection to server (service does not listen on any
other ports) and POSTs credentials to login service.
Server replies with HSTS header to ensure all further communication
is HTTPS.
If credentials are valid we:
Generate a random temporary token which is securely generated using a CSPRNG. Make this long enough to be secure (128 bit).
Store the random token on server mapping it to actual username.
Send the random token to the client
For subsequent requests:
Client sends token in a custom HTTP header over HTTPS.
Token is located in the DB and mapped to the username. If found access is configured based on allowed roles and allowed operations.
If not found user is considered unauthenticated and will have to authenticate with the login service again to get a new token.
On the server side the token will be stored with an expiry date. On each access to the service this date will be updated to create a sliding expiration. There will be a job that will run every few minutes to delete expired tokens and the query that checks the token for a valid session will only check those that have not deemed to have expired (to prevent permanent sessions if the scheduled job fails for any reason).
There is no need to hash and encrypt the tokens within the database - it adds no real value apart from a touch of security through obscurity. You could just hash though. This would prevent an attacker that managed to get at the session data table from hijacking existing user sessions.
The approach looks ok. Not very secure.
Let me highlight some of the attacks possible with the request.
Man-In-the-middle attack in a POST request, the user can tamper with the request and server does not have any way to ensure the data is not tampered.
Replay attack: In this, the attacker does not tamper with the request. The attacker taps the request and sends it to the server multiple times in a short duration, though it is a valid request, the server processes the request multiple times, which is not needed
Please read about Nonce.
In the first step, the user sends his credentials i.e username and password to the login service and if you have a web based application that also uses the same password it might be dangerous. If in case password in compromised, API and web everything is exposed, please use a different PIN for API access. Also, ensure decrypted token as specified by you, expires after a certain time.
Ensure the service (application server) tomcat. jboss never returns a server page in case of internal error, this gives the attacker extra information of the server where the app is deployed.
-- MODIFIED BASED ON SECOND POST --
Yes your correct if your using mutual SSL, but in case its a one way access you don't have the client certificates. It would be good if you just double ensured everything in the request, just like signed (signature) SOAP, one of the strong data transfer mechanism. But replay attack is a possibility with HTTPS, just handle that. Rest use tokens encryption is good. And why not ask the client to decrypt the token with the password and return the output of the decryption by this you can validate the output, if it is present in your database ? This approach the user does not send the password over the wire even if it is HTTPS ?