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
Related
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
Please help, I have the below implementation in MainApplication.java file. I am checking fixed hostnames and then returning true or false accordingly.
Error Insecure hostname verifier
Security Your app is using an unsafe implementation of hostname
verifier. Please see this Google Help Centre article for details,
including the deadline for fixing the vulnerability.
Ld/a/a/a/a/c/e$1; Ld/a/a/a/a/c/f$1; sv:deadline:12/10/2020
#Override
public void onCreate() {
super.onCreate();
hostnameVerifier();
MobileCore.setApplication(this);
SoLoader.init(this, /* native exopackage */ false);
ReactNativeFirebaseApp.setApplicationContext(this);
}
private void hostnameVerifier(){
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
String hostList[] = {"in.appcenter.ms", "graph.facebook.com",
"assets.adobedtm.com", "codepushupdates.azureedge.net", "app-measurement.com",
"forter.com", "dmn1", "dmn2", "dmn3", "quantummetric.com", "urbanairship.com", "demdex.net", "search.unbxd.io",
"monetate.net", "bazaarvoice.com", "google.com", "stylitics.com", "getcandid.com","braintreegateway.com"
};
for (String host : hostList) {
if (host.contains(arg0) || arg0.endsWith(host)) {
return true;
}
}
return false;
}
});
}
What should I change, please help.
The security alert is correct. Your code is putting users at risk and should be rejected from the App Store.
Why do you need this code at all? Remove it and only connect to servers when you get valid certificates back.
On google play console go to Release Management -> Select apk version -> Security tab. There you will see list of security issues with that apk along with class in your code that's causing that security issue where ever possible.
If you do not see a class name and rather see some encoded code in the security warning message, upload another build by disabling whatever code compress tool you maybe using. In my case it was proguard, I disabled it and got the library name. FYI - THe library was Braintree in my case
I have written this code but I'm getting an error. How can I get to work?
But the same token works with postman.
Error:
{"message":"The security token included in the request is invalid."}
Code :
public class test extends AppCompatActivity {
private final AWS4Signer signer = new AWS4Signer();
Request<?> aws;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
AWSCredentials credentials = new BasicAWSCredentials("AccessKey", "SecretKey");
aws = generateBasicRequest();
signer.setServiceName("execute-api");
signer.sign(aws, credentials);
new get_aws().execute();
}
private Request<?> generateBasicRequest() {
Request<?> request = new DefaultRequest<Void>("execute-api");
request.addHeader("Content-type", "application/json");
String securityToken = "Session Token";
request.addHeader("X-Amz-Security-Token", securityToken);
request.addHeader("Host", "********.amazonaws.com");
request.addHeader("x-amz-archive-description", "test test");
request.setResourcePath("/");
request.setEndpoint(URI.create("https://******.execute-api.****.amazonaws.com/data/all"));
return request;
}
private class get_aws extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
BufferedReader in = null;
String data = null;
try {
HttpClient httpclient = new DefaultHttpClient();
HttpGet request = new HttpGet();
request.addHeader("Authorization", aws.getHeaders().get("Authorization"));
request.addHeader("X-Amz-Date",request_aws.getHeaders().get("X-Amz-Date"));
request.addHeader("Content-Type","application/x-www-form-urlencoded");
URI website = new URI("https://********.execute-api.*******.amazonaws.com/data/all");
request.setURI(website);
HttpResponse response = httpclient.execute(request);
in = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
String line = in.readLine();
Log.d("line", line);
} catch (Exception e) {
Log.e("log_tag", "Error in http connection " + e.toString());
}
return null;
}
#Override
protected void onPostExecute(Void result) {
}
#Override
protected void onPreExecute() {
}
#Override
protected void onProgressUpdate(Void... values) {
}
}
}
To answer your immediate question, AWS can generate a Java SDK from your API Gateway for you.
Using the generated SDK, you can then pass an AWSCredentialsProvider object into your SDK.
AWSCredentials credentials = new BasicAWSCredentials("AccessKey", "SecretKey");
ApiClientFactory factory = new ApiClientFactory()
.credentialsProvider(credentials);
But...
You should never ship IAM access keys in a shipped application. These credentials can be retrieved by anyone who has installed your application by opening the .apk file.
Those credentials can then be used to access any other AWS actions the associated IAM User has access to in your account. This means anyone with access to the application apk (ie: anyone who can download the app from the app store) has access to your AWS account.
Depending what problem you're trying to solve will dictate the correct solution to the problem.
My Lambda needs an IAM Role to run
This is a fairly common mistake to make with API gateway when people see the "Invoke with caller credentials" option from API Gateway.
Uncheck this box and the Lambda will run with the IAM Role you defined in Lambda.
If requests fail after doing this, you need to make sure API Gateway has permission to invoke your lambda.
Restrict API to the application itself without users
Your application can't keep a secret and you have no user credentials.
You should disable Authorization completely, this is effectively a public API.
Requiring an API Key (and usage plan) to rate limit your API can be useful, but keep in mind this is not a security measure as, again - your application can't keep that key secret.
You want users to log in first (no existing source of users)
This makes sense if your API call is only designed to be called by registered users.
You'll need to configure Cognito User Pools for this. This shouldn't be confused with Cognito Federated Identities - which focuses on a different part of the problem. You can use it to solve this, but trust me - you'll be happier if you don't go down that path.
To get cracking you'll need to take a few steps:
Create a User Pool (detailed settings explained here).
Configure a Cognito Authorizer on your API Gateway.
Create an App Client for your pool. Don't generate a client secret key when you do this.
Integrate with your Android application. There's a prebuilt Android example available from AWS for getting the client side going: AmazonCognitoYourUserPoolsDemo
You want users to log in first (existing source of users)
If you can use SAML or OAuth2.0 / OpenID Connect to authenticate your users, follow the instructions and then configure federation.
If not, this is possibly the time to consider Cognito Federated Identities, specifically using the Developer Authenticated Identities process. But again, I'd really recommend against it.
API Gateway & Cognito is a massive topic. Hopefully the instructions provided are a great entry point to the relevant parts of the documentation.
Have you tried to look at the examples from AWS https://github.com/awslabs/aws-sdk-android-samples with cognito credentials, I found them easier to use, in case you want to use your AccessKey and SecretKey, you can also use something like this
AWSCredentials credentials = new BasicAWSCredentials("AccessKey", "SecretKey");
AmazonS3Client sS3Client = new AmazonS3Client(credentials,Region.getRegion("Region"));
I have angualrJS web app that recieves alerts from mobile app and marks the location of the mobile app user on google map. Once that mark is clicked info windo opens with name of user etc.
BackEnd is java maven project, and using spring boot.
My question is:
When I add spring boot security, to authenticate the page this stops all communication and no alerts show at all....
Any suggestions:
At the moment this is the application.js
var app = angular.module('sos',['ngRoute','mgcrea.ngStrap']);
app.config(['$httpProvider','$logProvider','$routeProvider',
function($httpProvider,$logProvider,$routeProvider) {
$logProvider.debugEnabled(true);
$routeProvider
.when('/', {
controller: 'MainController',
templateUrl: 'index.html',
controllerAs: 'controller'
})
.when('/download',{
templateUrl: 'download.html'
})
There is also a websocket connection that also stops and shows error 500 when I enable spring security...
This is web socket configuration:
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(geoLocationHandler(),
"/GeoLocationHandler").setAllowedOrigins("*");
}
#Bean
public GeoLocationHandler geoLocationHandler(){
return new GeoLocationHandler();
}
Any suggestions why spring security stops the alerts and markers showing on the map on the web app? And the spring security would make websocket fail giving error 500?
I tried to extend WebSecurityConfigAdapter and add mathcers to home page etc didnt work, I tried cors filters didnt work, I tried csrf filters didnt work as well...Any suggestion would be appreciated....
Can u try this in your
WebSecurityConfig.java file?
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().disable().httpBasic().and().authorizeRequests()
.antMatchers(PUBLIC_MATCHERS).permitAll().anyRequest().authenticated();
(my snippet , try it and then remove what you don't like)
I think that eventually you don't let anyone to GET.
Here I give you also my Global method , in case you need it.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
}
Ok, after research I found that I needed to include http method get to be able to recive alerts on the the web app, so my websecurityConfiguration is like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
//.and()
.httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/*").permitAll()
.antMatchers(HttpMethod.GET, "/api/geolocation/*").permitAll()
.antMatchers(HttpMethod.GET, "/GeoLocationHandler").permitAll()
.antMatchers(HttpMethod.GET, "/api/responser/*").permitAll()
.antMatchers(HttpMethod.GET, "/api/user/").authenticated()
.anyRequest().authenticated();
}
Otherwise the webapp will not accept any alerts....
Hope this helps someone needing it....
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.