thanks in advance for the help.
I developed a suite of api tests in Java TestNG against an api deployed to a non-secure internal QA environment. Recently this application was re-deployed to a new, secure environment. When this happened I began to see the following error on every api request, both GET and POST:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
My first move was to install the appropriate certificate, even though it is signed by DigiCert, so it should be approved by default.
keytool -import -alias ca -file qa4cert.crt -keystore cacerts -storepass changeit
And that had no effect. I also tried adding the certificate via the IDE (Intellij). Again, no effect, still seeing the same error.
Since I was unable to make progress here, and I am still working against an internal QA environment with no sensitive data, I was comfortable dropping the certificate validation and install an all-trusting cert manager. My simple implementation is below:
package test_utils;
import javax.net.ssl.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class SSLTool {
private static boolean isTrustAllCertsInitialized = false;
public static void disableCertificateValidation() {
if (isTrustAllCertsInitialized) {
return;
}
isTrustAllCertsInitialized = true;
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLContext.setDefault(ctx);
System.out.println("new trust manager should be set");
} catch (Exception e) {
e.printStackTrace();
}
return ctx;
}
}
I ran the above code as part of my --before-- implementation in the test suite and verified we reached the end of the try block, so the new All_trusting TrustManager should be set. Unfortunately this also had no effect, I am still seeing the error.
To try and isolate the issue, I processed the same POST request through both Postman and curl, both returned the desired result, no errors.
For sanity's sake I also hit a few public urls both secure and insecure and got the desired results as well.
At this point I'm stumped. Since Postman and curl work, the error must have something to do with my implementation, but I don't understand why it would work with our old environment but not in the new one. A debugging version of my code that makes a failing get request is below. This fails both when run through the ide, and through the command line with maven.
Test File
public class DebugTests extends BaseTest {
#Test
public void debug() {
BaseApi api = new BaseApi();
api.debugGet("<<MYURL>>");
}
BaseTest
#Listeners(Listener.class)
public class BaseTest {
#BeforeMethod
public void before() {
// the below function is used to disable certificate validation. It is ONLY meant to be used in testing environments
// if used in production it exposes our test suite to MITM attacks.
SSLTool.disableCertificateValidation();
}
#AfterMethod
public void after() {
}
}
Relevant Code from the API object
public BaseApi() {
SSLContext ctx = SSLTool.disableCertificateValidation();
client = HttpClients.custom().setSSLContext(ctx).build();
System.out.println("trust manager set");
}
public void debugGet(String endpoint) {
try {
client.execute(this.buildGetConnection(endpoint, false));
} catch (Exception e) {
e.printStackTrace();
}
}
protected HttpGet buildGetConnection(String endpoint) {
return this.buildGetConnection(endpoint, true);
}
protected HttpGet buildGetConnection(String endpoint, boolean auth) {
//TODO build a switch to change testing environments based off command line
HttpGet get = new HttpGet(rootUrl + endpoint);
if(auth) {
StsAuthApi authApi = new StsAuthApi();
get.setHeader("Authorization", "Bearer " + authApi.getToken());
}
get.setHeader("accept","application/json");
get.setHeader("Content-Type","application/xml");
System.out.println("making GET request to " + rootUrl + endpoint);
return get;
}
In my debugging implementation rooturl is an empty string so the url matches the string supplied via the test case.
HttpClients.createDefault() will not use your null trust manager. It internally creates and initialises an SSLContext like this:
final SSLContext sslContext = SSLContext.getInstance(SSLContextBuilder.TLS);
sslContext.init(null, null, null);
You can create the HttpClient like this instead:
HttpClients.custom()
.setSSLContext(ctx)
.build();
Where ctx is the one you created in the disableCertificateValidation() method.
The question about why it didn't work when you added your certificate to cacerts is still open. If you did that correctly then the default client should have used it. You can set the system property javax.net.debug=all if you want to debug that one further (it will print out your truststore when it's first initialised into a context).
Related
I'm trying to run google map on android application I have used Debug certificate fingerprint and my package name to get API KEY when I run the application on emulator I got this error
Google Maps Android API: Failed to load map. Error contacting Google servers. This is
probably an authentication issue (but could be due to network errors).
while getting bank screen in the emulator attached below, I'm sure is everything is correct from my side
Activate Maps SDK for Android
Get Debug certificate fingerprint using
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey - storepass android -keypass android
Add the API key into res/value/google_maps_api.xml
could you please help in that
I have found the issue I was using handleSSLHandShake in android app class it's working fine after removing it.
public static void handleSSLHandshake() {
try {
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
#Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
#Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier((arg0, arg1) -> {
Log.e("verify_url","is => "+arg0);
if(arg0.equals("") ){
return true;
}else{
return false;
}
});
} catch (Exception ignored) {
}
}
This might sound silly but I am encountering this behavior. I am using JNDI for LDAP authentication. I have a demo program setup, where the authentication fails upon providing incorrect credentials, but the same seems to go through in a Spring controller method(I'm making a post call from a react app).
Plain Java implementation
import java.util.Properties;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
public class Demo {
public static void main(String[] args) {
Properties environment = new Properties();
String userDomain = "#region.company.net";
environment.setProperty(DirContext.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.setProperty(DirContext.PROVIDER_URL, "ldap://region.company.net:3268");
environment.setProperty(DirContext.SECURITY_AUTHENTICATION, "simple");
environment.setProperty(DirContext.SECURITY_PRINCIPAL, "userId"+userDomain);
environment.setProperty(DirContext.SECURITY_CREDENTIALS, "wrongPassword");
try {
DirContext context = new InitialDirContext(environment);
System.out.println("Authentication Successful !!!\n\n");
} catch (NamingException e) {
System.out.println("Authentication Failed !!!\n\n");
e.printStackTrace();
}
}
}
Controller implementation
#PostMapping("/authenticateUser")
public String authenticateUser(#RequestBody HashMap<String, String> user) {
Properties environment = new Properties();
String userDomain = "#region.company.net";
environment.setProperty(DirContext.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.setProperty(DirContext.PROVIDER_URL, "ldap://region.company.net:3268");
environment.setProperty(DirContext.SECURITY_AUTHENTICATION, "simple");
// environment.setProperty(DirContext.SECURITY_PRINCIPAL, "userId"+userDomain);
environment.setProperty(DirContext.SECURITY_CREDENTIALS, "wrongPassword");
try {
DirContext context = new InitialDirContext(environment);
System.out.println("Authentication Successful !!!\n\n");
} catch (NamingException e) {
System.out.println("Authentication Failed !!!\n\n");
e.printStackTrace();
}
return "Method executed successfully";
}
After providing incorrect password, if I execute this code then it prints Authentication Failed !!! which is expected, but when I insert this same code in a controller's method, it prints Authentication Successful !!!.
Shouldn't both behave in the same way? I find this behavior pretty weird. Perhaps, I am overlooking something?
EDIT 1 : START
It appears the line that sets the SECURITY_PRINCIPAL was commented out in case of the Spring Controller. I have commented out that part of code.
This now gives rise to another question as to why it never threw any exception ?
Not sure if I should ask this in a separate post.
Is it a proper way to authenticate by passing Username(like abc#xyz.com) as SECURRITY_PRINCIPAL or one should pass the entry path ?
EDIT 1 : END
I want to connect my Eclipse plug-in to an HTTPS URL, but have a problem because the user would need to accept the certificate. Of course there are a couple of tutorials for how to do this in plain Java, but that might be hard to do inside an Eclipse plug-in and I think I'd reinvent the wheel that way.
Because Eclipse has some built in tooling to connect to sites with different network protocols. An example would be the "Install new Software..." action. The tooling even has a preference page that lists HTTPS separately.
According to the Eclipse Help, the KeyStore is used "as a repository for Certificates used for trust decisions [...] when making SSL connections". Yet I couldn't figure out how to use it.
So my question is: How do I use the Eclipse's build in facilities to connect to my HTTPS site?
Based on this answer here I build my own plug-in which loads just the one certificate I need (lucky me) in its EarlyStartup:
public class EarlyStartup implements IStartup {
private static final String ALIAS = "ACME";
#Override
public void earlyStartup() {
final char[] passphrase = "changeit".toCharArray();
final char separator = File.separatorChar;
final File dir = new File(System.getProperty("java.home") + separator + "lib" + separator + "security");
final File file = new File(dir, "cacerts");
try (InputStream certIn = getClass().getResourceAsStream("acme.org.crt");
final InputStream localCertIn = new FileInputStream(file);) {
final KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(localCertIn, passphrase);
if (keystore.containsAlias(ALIAS)) {
return;
}
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
final Certificate cert = cf.generateCertificate(certIn);
keystore.setCertificateEntry(ALIAS, cert);
try (OutputStream out = new FileOutputStream(file)) {
keystore.store(out, passphrase);
}
} catch (final Exception e) {
e.printStackTrace();
}
}
}
I'm trying to create a XAdES-BES signature for a given blob. For this signature, I'd need to add two transforms on the content before it is signed: Base64 (http://www.w3.org/2000/09/xmldsig#base64) & a custom one (called optional-deflate).
The problems lies with that optional transform. I'm trying to figure out how to implement a custom Transform, register it, and finally have Xades4J use it.
So far I figured a lot (thanks Google and a lot of time), so I got roughly to this: I've got a Provider-class that, in the constructor, puts the new TransformService; In my main code I add my Provider to the Security instance; then, I try to add the transform to my actual to-be-signed object.
Unfortunately, I always get the same error:
Exception in thread "main" xades4j.UnsupportedAlgorithmException: Unsupported transform on XML Signature provider (urn:xml:sig:transform:optional-deflate)
at xades4j.production.DataObjectDescsProcessor.processTransforms(DataObjectDescsProcessor.java:194)
at xades4j.production.DataObjectDescsProcessor.process(DataObjectDescsProcessor.java:87)
at xades4j.production.SignerBES.sign(SignerBES.java:173)
at xades4j.production.SignerBES.sign(SignerBES.java:122)
at com.mycompany.Test.createXades(Test.java:199)
at com.mycompany.Test.main(Test.java:47)
Caused by: org.apache.xml.security.transforms.TransformationException: Unknown transformation. No handler installed for URI urn:xml:sig:transform:optional-deflate
Original Exception was org.apache.xml.security.transforms.InvalidTransformException: Unknown transformation. No handler installed for URI urn:xml:sig:transform:optional-deflate
at org.apache.xml.security.transforms.Transforms.addTransform(Unknown Source)
at xades4j.production.DataObjectDescsProcessor.processTransforms(DataObjectDescsProcessor.java:185)
... 5 more
So, my code looks like this (abbreviated to what I think is necessary here):
TransformService class:
package com.mycompany.security;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.spec.AlgorithmParameterSpec;
import javax.xml.crypto.Data;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.TransformService;
import javax.xml.crypto.dsig.TransformException;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
public class OptionalDeflateTransform extends TransformService {
public AlgorithmParameterSpec getParameterSpec() {
return null;
}
public Data transform(Data data, XMLCryptoContext context) throws TransformException {
return null;
}
public Data transform(Data data, XMLCryptoContext context, OutputStream os) throws TransformException {
return null;
}
public boolean isFeatureSupported(String feature) {
return false;
}
public void init(TransformParameterSpec params) throws InvalidAlgorithmParameterException {}
public void marshalParams(XMLStructure parent, XMLCryptoContext context) throws MarshalException {}
public void init(XMLStructure parent, XMLCryptoContext context) throws InvalidAlgorithmParameterException {}
}
Provider subclass:
package com.mycompany.security;
import java.security.Provider;
public final class OptionalDeflateProvider extends Provider {
private static final long serialVersionUID = 8849833178389029123L;
public OptionalDeflateProvider() {
super("OptionalDeflate", 1.0, "OptionalDeflate provider 1.0 implementing the OptionalDeflate transform algorithm.");
put("TransformService.urn:xml:sig:transform:optional-deflate", "com.mycompany.security.OptionalDeflateTransform");
}
}
And finally, my main Test class, which contains the actual signing. Without that transform, it works (but well, doesn't add the transform, which is necessary). So Base64 works.
protected static void createXades(String content) throws Exception {
/*Get certificate & private key*/
Certificates c = new Certificates();
c.initSession(); //some helper class where I can get my certificate & private key for signing
/*Create a document*/
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
Element objectElement = doc.createElement("object");
doc.appendChild(objectElement);
Element requestElement = doc.createElement("request");
requestElement.appendChild(doc.createTextNode(content));
requestElement.setAttribute("ID", UUID.randomUUID().toString());
objectElement.appendChild(requestElement);
/*Key provider, signing profile & signer itself*/
KeyingDataProvider kp = new CustomKeyingDataProvider(c.getCertificate(), c.getPrivateKey());
XadesSigningProfile p = new XadesBesSigningProfile(kp);
p.withAlgorithmsProviderEx(new ProviderEx());
XadesSigner signer = p.newSigner();
/*Add the optional deflate provider*/
Security.addProvider(new OptionalDeflateProvider());
System.out.println("--- installed providers ---");
for (Provider pr : Security.getProviders())
System.out.println(pr.getName());
System.out.println("---");
/*Test if we can get the transformservice-instance*/
TransformService ts = TransformService.getInstance("urn:xml:sig:transform:optional-deflate", "DOM");
System.out.println(ts.getAlgorithm());
System.out.println("---");
/*Signed data*/
DataObjectDesc flatFile = new DataObjectReference("#" + requestElement.getAttribute("ID"))
.withTransform(new GenericAlgorithm("http://www.w3.org/2000/09/xmldsig#base64"))
.withTransform(new GenericAlgorithm("urn:xml:sig:transform:optional-deflate"));
SignedDataObjects dataObjs = new SignedDataObjects(flatFile);
/*Actual signing*/
signer.sign(dataObjs, objectElement);
log(objectElement.getLastChild());
}
As you can see I print some things out. I for instance logged that the installation works fine and I also logged the installed providers. I get this as output:
--- installed providers ---
SUN
SunRsaSign
SunEC
SunJSSE
SunJCE
SunJGSS
SunSASL
XMLDSig
SunPCSC
SunMSCAPI
OptionalDeflate
---
urn:xml:sig:transform:optional-deflate
---
So as far as I can see, the provider has succesfully been registered, the transformservice can be loaded without a problem, ... So I don't really see what's going on?
I've checked the source code of Xades4j as well, and what's happening internally on the line .withTransform(new GenericAlgorithm("urn:xml:sig:transform:optional-deflate")) is pretty straight forward:
import org.apache.xml.security.transforms.Transforms;
...
private Transforms processTransforms(DataObjectDesc dataObjDesc, Document document) throws UnsupportedAlgorithmException {
Collection<Algorithm> dObjTransfs = dataObjDesc.getTransforms();
if (dObjTransfs.isEmpty()) {
return null;
}
Transforms transforms = new Transforms(document);
for (Algorithm dObjTransf : dObjTransfs) {
try {
List<Node> transfParams = this.algorithmsParametersMarshaller.marshalParameters(dObjTransf, document);
if (null == transfParams) {
transforms.addTransform(dObjTransf.getUri());
} else {
transforms.addTransform(dObjTransf.getUri(), DOMHelper.nodeList(transfParams));
}
} catch (TransformationException ex) {
throw new UnsupportedAlgorithmException("Unsupported transform on XML Signature provider", dObjTransf.getUri(), ex);
}
}
return transforms;
}
The exact line throwing up the error is transforms.addTransform(dObjTransf.getUri()). This transforms object is a 'standard' apache object (org.apache.xml.security.transforms.Transforms object). So I'd guess it should be able to get the same TransformService as I do in code litteraly two lines up higher? But it isn't?
Anyone that can point me out what I'm missing? I'll be eternally grateful.
Apparently, Apache Santuario loads transforms from a internal map. There is a register
method that you probably can use to register your custom transform.
i use custom DummySocketFactory and DummyTrustMAnager to connect to smtp over TLS.
DummySocketFactory:
package XMailMessenger;
public class DummySSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory factory;
public DummySSLSocketFactory() {
try {
SSLContext sslcontext = SSLContext.getInstance("TLS");
//Security.removeProvider("SunJSSE");
sslcontext.init(null,
new TrustManager[] { new DummyTrustManager()},
null );
factory = (SSLSocketFactory)sslcontext.getSocketFactory();
} catch(Exception ex) {
System.out.println(ex.toString());
}
}
public static SocketFactory getDefault() {
SocketFactory a = new DummySSLSocketFactory();
if ( a == null ) { System.out.println("1"); }
return a;
}
...
DummyTrustManager:
public class DummyTrustManager implements X509TrustManager{
public void checkClientTrusted(X509Certificate[] cert, String authType) {
// everything is trusted
}
public void checkServerTrusted(X509Certificate[] cert, String authType) {
// everything is trusted
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
//return null;
}
}
in sending e-mail i receive exception as in subject, this exception goes from function sslcontext.init in DummySSLSocketFactory. I debug it and noticed , that in code:
private X509TrustManager chooseTrustManager(TrustManager[] tm)
throws KeyManagementException {
// We only use the first instance of X509TrustManager passed to us.
for (int i = 0; tm != null && i < tm.length; i++) {
if (tm[i] instanceof X509TrustManager) {
if (SunJSSE.isFIPS() &&
!(tm[i] instanceof X509TrustManagerImpl)) {
throw new KeyManagementException
("FIPS mode: only SunJSSE TrustManagers may be used");
}
if (tm[i] instanceof X509ExtendedTrustManager) {
return (X509TrustManager)tm[i];
} else {
return new AbstractTrustManagerWrapper(
(X509TrustManager)tm[i]);
}
}
}
// nothing found, return a dummy X509TrustManager.
return DummyX509TrustManager.INSTANCE;
}
exception occures in if (SunJSSE.isFIPS() &&
!(tm[i] instanceof X509TrustManagerImpl)) expression.
I suppose that tm[i] contains my DummyTrustManager , it can not be extended from X509TrustManagerImpl so my question is : How to disable Fips in SunJSSE ?
SunJSSE can be configured to run on FIPS-140 compliant mode as long as it uses a FIPS-140 certified cryptographic hardware or software provider that implements all cryptographic algorithms required by JSSE (ex. Network Security Services – NSS, Sun Cryptographic Accelerator 6000, nCipher, etc).
To enable FIPS mode, edit the file ${java.home}/lib/security/java.security and modify the line that lists com.sun.net.ssl.internal.ssl.Provider and associate the name of the FIPS-140 cryptographic provider (ex. SunPKCS11-NSS). The name of the provider is a string that concatenates the prefix SunPKCS11- with the name of the specified PKCS#11 provider in its configuration file.
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
SunPKCS11-NSS
In case of using NSS as cryptographic software token (Make use of NSS 3.1.1. or above), assuming the libraries are located under the /opt/nss/lib directory and its key database files (with the suffix .db) are under the /opt/nss/fipsdb directory, the sample configuration for representing NSS will be as follows:
# Use NSS as a FIPS-140 compliant cryptographic token
# SunPKCS11-NSS
name = NSS
nssLibraryDirectory = /opt/nss/lib
nssSecmodDirectory = /opt/nss/fipsdb
nssModule = fips
In FIPS mode, SunJSSE will perform SSL/TLS 1.0 based communication and cryptographic operations including symmetric and asymmetric encryption, signature generation and verification, message digests and message authentication codes, key generation and key derivation, random number generation, etc.
To anyone having a giant headache when you need to install a tomcat webapp on a third party server, I lost 1 hour trying to bypass this damn thing...
I solved in this way, without touching anything in the webapp.
Add this java parameter:
-Djava.security.disableSystemPropertiesFile=true
Source:
https://access.redhat.com/documentation/en-us/openjdk/8/pdf/configuring_openjdk_8_on_rhel_with_fips/OpenJDK-8-Configuring_OpenJDK_8_on_RHEL_with_FIPS-en-US.pdf
Also, if the app needs to connect to a Windows Server, you might want to disable FIPS there too:
In Control Panel, click Administrative Tools -> Local Security Policy.
In Security Settings -> Local Policies -> Security Options.
Under Policy in the right pane, double-click System cryptography: Use FIPS compliant algorithms for encryption, hashing, and signing, and then click Disabled.
Reboot the server
(bonus)
If you want to uninstall FIPS from the server, follow this giude (I didn't test it):
https://www.bggofurther.com/2021/02/disable-fips-mode-on-centos-7/