Avoid "Accept unknown certificate" warning in android while using xmpp - android

I am trying to search on XMPP. I got the code from here. It works fine and I am able to connect to the server. But its showing the alert window like this
and If I click "Always" or "Once" it is accepting and I am able to show the contacts and chat messages....
Is there any way to stop this alert and can I connect directly to the server?

This message is displayed by MemorizingTrustManager (MTM), an Android library aimed to improve the security/usability trade-off for "private cloud" SSL installations.
MTM issues this warning whenever you connect to a server with a certificate not issued by one of the Android OS trusted Root CAs, like a self-signed certificate or one by CACert.
If the message appears again after you clicked "Always", this is a bug in MTM (probably due to a mismatching SSL server name), and should be reported via github.
Edit: if you are making an app that only communicates with one server, and you know the server's certificate in advance, you should replace MTM with AndroidPinning, which ensures that nobody can make man-in-the-middle attacks on your connection.
Disclaimer: I am the author of MTM and the mainainer of yaxim.

Get the certificate signed by a certificate authority. Forget all coding solutions.

Is there any way to stop this alert and can I connect directly to the server?
Its not clear to me if you wrote this app that connects to kluebook.com. I think you did, but its not explicit.
Assuming you wrote the app and know the server you are connecting to (kluebook.com), you should provide a custom TrustManager to handle this. You can find code for a custom TrustManger that works with the expected server certificate in OWASP's Certificate and Public Key Pinning example. Its OK to pin because you know what the certificate or public key is, and there's no need to trust someone else like a CA.
If you have no a priori knowledge, then you trust on first use and follow with a key continuity strategy looking for abrupt changes in the certificate or public key. In this case, the trust on first use should include the customary X509 checks.
Pinning is part of an overall security diversification strategy. A great book on the subject is Peter Gutmann's Engineering Security.
What you are seeing with the prompt is one leg of the strategy - namely, the Trust-On-First-Use (TOFU) strategy. The prompt has marginal value because user's don't know how to respond to it, so they just click yes to dismiss the box so they can continue what they are doing. Peter Gutmann has a great write-up on user psychology (complete with Security UI studies) in Engineering Security.
From section 6.1 of OWASP's Certificate and Public Key Pinning:
public final class PubKeyManager implements X509TrustManager
{
private static String PUB_KEY = "30820122300d06092a864886f70d0101...";
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
if (chain == null) {
throw new IllegalArgumentException("checkServerTrusted: X509Certificate array is null");
}
if (!(chain.length > 0)) {
throw new IllegalArgumentException("checkServerTrusted: X509Certificate is empty");
}
if (!(null != authType && authType.equalsIgnoreCase("RSA"))) {
throw new CertificateException("checkServerTrusted: AuthType is not RSA");
}
// Perform customary SSL/TLS checks
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init((KeyStore) null);
for (TrustManager trustManager : tmf.getTrustManagers()) {
((X509TrustManager) trustManager).checkServerTrusted(chain, authType);
}
} catch (Exception e) {
throw new CertificateException(e);
}
// Hack ahead: BigInteger and toString(). We know a DER encoded Public Key begins
// with 0x30 (ASN.1 SEQUENCE and CONSTRUCTED), so there is no leading 0x00 to drop.
RSAPublicKey pubkey = (RSAPublicKey) chain[0].getPublicKey();
String encoded = new BigInteger(1 /* positive */, pubkey.getEncoded()).toString(16);
// Pin it!
final boolean expected = PUB_KEY.equalsIgnoreCase(encoded);
if (!expected) {
throw new CertificateException("checkServerTrusted: Expected public key: "
+ PUB_KEY + ", got public key:" + encoded);
}
}
}
}
You can get the expected public key from OpenSSL's s_client, but you have to know the port. I can't get a response from the well known SSL ports like 5223, and 5222 has no security services:
$ openssl s_client -connect kluebook.com:5222
CONNECTED(00000003)
140735088755164:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:s23_clnt.c:766:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 322 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
---
Once you get a public key, plug it back into the TrustManager at PUB_KEY.

Related

Spring Boot/Security - can I use X509 Certificate as an extra layer in authentication?

I am building an Android App which communicates with my REST API that is protected by Spring Security.
Since the Android App is "public" and no keys etc is secure I want to create diffrent obstacles and make things complicated to protect my API as much as possible.
One way in which I would like to add more security is to make sure that the one calling my API has a certificate. I don't want to create thousands of certificates in my APIs trust-store so I just want to make sure that the caller have one single certificate that I hid away in a keystore in my Android app.
In the examples I have found it seems like a "normal" X509Certificate authentication in Spring Security requires a unique certificate for every user and then this certificate replaces Basic auth or JWT auth. I would like to have individual client JWT tokens but make sure that every call brings my ONE Android App certificate to make (more) sure that someone is calling my API from my Android app.
Is this possible or is it just not what it is for?
When you create a RestTemplate you can configure it with a keystore and trust-store so in that end it should be easy. But as for protecting my REST API it seems more difficult since I want both certificate + JWT token or Basic auth.
I am not using XML configuration for my securityconfig. I instead extend WebSecurityConfigurerAdapter. It would be great if this was configurable in the configure(HttpSecurity http) method, but I'm thinking that maybe I could achieve this in a OncePerRequestFilter somehow? Perhaps configure a filter before my JwtAuthFilter?
Edit:
In all the examples I have found for configuration of spring security they always seems to use the certificate as an authentication. I just want to configure so that when someone call example.com/api/** it checks so that the certificate is approved by my custom trust store (so that I "know" it is probably a call from my app) but if someone call example.com/website it should use the default java trust store.
If someone call example.com/api/** I would like my server to
check certificate and kill the connection if the certificate is not approved in my custom truststore.
If certificate is ok, establish https (or move on if I can't kill the connection before it have already established https-connection) to user auth with Basic-/JWT-authentication.
I think I figured it out. Here is how I configured it and it seems to work.
The "/**" endpoint is the website which should work with any browser without any specific certificate, but it requires Admin authority (you need to login as admin).
The "/api/**" and "/connect/**" endpoints require the correct certificate, the correct API-key and valid Basic- or JWT-token authentification.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/loginForm")
.loginProcessingUrl("/authenticateTheUser")
.permitAll()
.and()
.logout()
.permitAll().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
http.requestMatchers()
.antMatchers("/connect/**","/api/**")
.and()
.addFilterBefore(new APIKeyFilter(null), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtAuthorizationFilter(), BasicAuthenticationFilter.class)
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/connect/**").hasAnyRole("MASTER,APPCALLER,NEGOTIATOR,MEMBER")
.antMatchers("/api/**").hasAnyRole("MASTER,MEMBER,ANONYMOUS");
}
The ApiKeyFilter class is the one that check the api-key and also make sure that the certificate used in the call is approved in my server trust-store. The api-key check is all that I had to configure, the extended X509AuthenticationFilter will automatically check the request certificate. My ApiKeyFilter looks like this:
public class APIKeyFilter extends X509AuthenticationFilter {
private String principalRequestHeader = "x-api-key";
private String apiKey = "XXXX";
public APIKeyFilter(String principalRequestHeader) {
if (principalRequestHeader != null) {
this.principalRequestHeader = principalRequestHeader;
}
setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(authentication.getPrincipal() == null) {
throw new BadCredentialsException("Access Denied.");
}
String rApiKey = (String) authentication.getPrincipal();
if (authentication.getPrincipal() != null && apiKey.equals(rApiKey)) {
return authentication;
} else {
throw new BadCredentialsException("Access Denied.");
}
}
});
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(principalRequestHeader);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
X509Certificate[] certificates = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (certificates != null && certificates.length > 0) {
return certificates[0].getSubjectDN();
}
return super.getPreAuthenticatedCredentials(request);
}
}
Cred goes to these resources that helped me put things together:
Spring Boot - require api key AND x509, but not for all endpoints
spring security http antMatcher with multiple paths

nv-websocket-client self-signed certificate

I am trying to connect to a WSS server on my intranet with a self signed certificate. I have used Volley for HTTPS and TooTallNate library for WSS and I have been able to set SSLContext to accept all certificates. I am currently switching to nv-websocket-client so that I can add custom headers but, for the love of god, cant seem to bypass SSL certificate verification. I continue to run into the error message "The certificate of the peer...does not match the expected hostname". The code is exactly what is in the docs? is something different in v2.2? Here is the code I am using,
SSLContext context = NaiveSSLContext.getInstance("TLS");
ws = new WebSocketFactory().setSSLContext(context).setConnectionTimeout(5000)
.createSocket("wss://192.168.1.164/chat/")
.addListener(new WebSocketAdapter() {
#Override
public void onTextMessage(WebSocket websocket, String message) {
// Received a text message.
}
#Override
public void onConnectError(WebSocket websocket, WebSocketException e){
mTextView.setText(e.getMessage());
}
});
ws.connectAsynchronously();`
Can somebody help me. Thank you!
The author for the package has addressed it as an issue with a new feature in v2.3
WebSocketFactory.setVerifyHostname(false)
https://github.com/TakahikoKawasaki/nv-websocket-client/issues/116

Xamarin Android Web Service over HTTPS

I'm having some difficulties consuming a web service that is available only over https.
I have read posts from several other people who is having issues with achieving this as well, but none of the answers I have seen so far has fixed the problem for me, so I will try to explain my issue here and hope some of you know how to get past this obstacle.
I'm using Xamarin Studio 6.1.1 developing for Android specifically.
I have set the "HttpClient Implementation" under "Android Build" for the project to "AndroidClientHandler" (which appears to be the latest implementation and should support TLS 1.2).
I have added a web reference (not as WCF) to the web service and supplied the login information when prompted... So far everything is going as expected.
Note: I have tested the web service from a console application in Visual Studio and it works as expected.
However, when I attempt to call one of the methods of the web service I get the same error that I can see so many others have encountered before me which is this "Error: TrustFailure (The authentication or decryption has failed.)".
I have tried several of the previous posted solutions but nothing seems to help.
1.A) providing the callback function for ServicePointManager:
ServicePointManager.ServerCertificateValidationCallback += CertificateValidationCallBack;
1.B) the callback function:
private static bool CertificateValidationCallBack(
object sender,
System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true.
if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
{
return true;
}
// If there are errors in the certificate chain, look at each error to determine the cause.
if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
if (chain != null && chain.ChainStatus != null)
{
foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus)
{
if ((certificate.Subject == certificate.Issuer) &&
(status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot))
{
// Self-signed certificates with an untrusted root are valid.
continue;
}
else
{
if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError)
{
// If there are any other errors in the certificate chain, the certificate is invalid,
// so the method returns false.
return false;
}
}
}
}
// When processing reaches this line, the only errors in the certificate chain are
// untrusted root errors for self-signed certificates. These certificates are valid
// for default Exchange server installations, so return true.
return true;
}
else
{
// In all other cases, return false.
return false;
}
}
2) Creating an instance of the AesCryptoServiceProvider:
System.Security.Cryptography.AesCryptoServiceProvider b = new System.Security.Cryptography.AesCryptoServiceProvider();
If anyone can has a solution this the apparently pretty common problem, please don't hesitate to let me know, I only have so much hair...
kind regards,
Aidal
Possible known bug. Search this here for "https": https://releases.xamarin.com
[Mono], [Xamarin.iOS], [Xamarin.Android], [Xamarin.Mac] – 43566 –
“TrustFailure (The authentication or decryption has failed.) … Invalid
certificate received from server.” with “Error code: 0x5” or “Error
code: 0xffffffff800b010f” when attempting to access HTTPS servers on
ports other than 443
Bug reference: https://bugzilla.xamarin.com/show_bug.cgi?id=44708

Public Key pinning with X509TrustManagerExtensions checkServerTrusted

Public key pinning in for a HTTPS TLS connection.
There is an issue with Android API, below 17, that enables MITM (Man in the Middle) attack incase of public key pinning. This has been explained in the link below.
https://www.cigital.com/blog/ineffective-certificate-pinning-implementations/
So in Android minimum sdk below 17, ie, below Android version 4.2, we need to initialise the X509TrustManager with Android Keystore which has only the server root certificates (instead of the default keystore; which would have all certificates installed in the device). This helps in cleaning the leaf certificates received from the server before performing public key pinning.
From Android API 17 onwards, Android has introduced X509TrustManagerExtensions which performs this root cleaning at OS level.
https://developer.android.com/reference/android/net/http/X509TrustManagerExtensions.html
My question:
I would be glad if anyone could please provide an example on how to implement the following method provided by the X509TrustManagerExtensions for root cleaning.
List<X509Certificate> checkServerTrusted (X509Certificate[] chain,
String authType,
String host)
I am confused with the following.
host; should it be the domain URL? with https or without? or should it be the full url (domain + relative path)
How to create instant of a X509TrustManagerExtensions?
The constructor for X509TrustManagerExtensions takes X509TrustManager as input. Do we create this X509TrustManager with the android default keystore?
Code snippet (Not working):
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(KeyStore.getInstance(KeyStore.getDefaultType()));
for (TrustManager trustManager : tmf.getTrustManagers()) {
X509TrustManagerExtensions tme = new X509TrustManagerExtensions((X509TrustManager) trustManager);
tme.checkServerTrusted(chain, authType, <<String https://www.example.com>>);
}
Exception:
Trust anchor for certification path not found
Possible security risk:
Using KeyStore.getDefaultType()
Any help would be greatly appreciated.
Firstly you need to get hold of the trust manager by using the TrustManagerFactory. When initialising this you pass in null for it to use the default Keystore and it will return the default trust managers. With this you can then create the X509TrustManagerExtensions using the first X509TrustManager.
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
// Find first X509TrustManager in the TrustManagerFactory
X509TrustManager x509TrustManager = null;
for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
x509TrustManager = (X509TrustManager) trustManager;
break;
}
}
X509TrustManagerExtensions x509TrustManagerExtensions =
new X509TrustManagerExtensions(trustManager());
Then to execute this the host I've successfully used just the domain part:
List<X509Certificate> trustedCerts = x509TrustManagerExtensions
.checkServerTrusted(untrustedCerts, "RSA", "appmattus.com");
For those using HttpUrlConnection the the untrusted certs is determined with:
Certificate[] serverCerts = ((HttpsUrlConnection)conn).getServerCertificates();
X509Certificate[] untrustedCerts = Arrays.copyOf(serverCerts,
serverCerts.length,
X509Certificate[].class);
If you are using OkHttp then you can just use the built in CertificatePinner that has since been updated to fix the issues mentioned in that article.

Android manual X509 certificate chain validation

I have implemented javax.net.ssl.X509TrustManager in my code so I can validate my self-signed cert, which my software accesses. However, I still need to validate some other "standard" website SSL certificates. I am using CertPathValidator.validate() to do that, but I just realized that one cert chain I am being passed (for maps.googleapis.com) doesn't actually contain the complete chain - it contains the whole chain but the Root CA (Equifax), which does exist on the phone, but validate() still fails because (apparently) it's not explicitly in the chain. What am I missing here, so that the validation succeeds? Thanks for any input.
Edit - Relevant code (with exception checking removed):
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// chain is of type X509Certificate[]
CertPath cp = cf.generateCertPath(Arrays.asList(chain));
CertPathValidator cpv = CertPathValidator.getInstance(CertPathValidator.getDefaultType());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream is = new FileInputStream("/system/etc/security/cacerts.bks");
ks.load(is, null);
PKIXParameters params = new PKIXParameters(ks);
CertPathValidatorResult cpvr = cpv.validate(cp, params);
Implementing your own TrustManager is generally a bad idea. The better way is to add your certificates to a keystore, and add it as a trust store. See this for an example of how to do it.
You probably need to add the default trust store to your validator. Also, Android does not do revocation (CRL) checking by default, so if you enabled it, you need to get the related CRL's manually. Post your code.

Categories

Resources