I have a tomcat-hibernate-hsqldb setup and I want to use SSL to secure data transfer between my application and hsqldb. However, I need to pre install a certificate which can be used at any deployment. I do not want to use a new certificate for each new deployment site. For this, if I just use a self-signed certificate issues to any random Common Name and then install the same certificate in the trust store of tomcat, then I get this exception
java.net.UnknownHostException: Certificate Common Name[random name] does not match host name[192.168.100.10]
I need to disable hostname verification in this setup, but all the info I found on web points to the mechanism of disabling it for HttpsURLConnection.
I believe hsqldb has a custom code to do it, in the file
org.hsqldb.serverHsqlSocketFactorySecure
Here is the method, which does this:
protected void verify(String host, SSLSession session) throws Exception {
X509Certificate[] chain;
X509Certificate certificate;
Principal principal;
PublicKey publicKey;
String DN;
String CN;
int start;
int end;
String emsg;
chain = session.getPeerCertificateChain();
certificate = chain[0];
principal = certificate.getSubjectDN();
DN = String.valueOf(principal);
start = DN.indexOf("CN=");
if (start < 0) {
throw new UnknownHostException(
Error.getMessage(ErrorCode.M_SERVER_SECURE_VERIFY_1));
}
start += 3;
end = DN.indexOf(',', start);
CN = DN.substring(start, (end > -1) ? end
: DN.length());
if (CN.length() < 1) {
throw new UnknownHostException(
Error.getMessage(ErrorCode.M_SERVER_SECURE_VERIFY_2));
}
if (!CN.equalsIgnoreCase(host)) {
// TLS_HOSTNAME_MISMATCH
throw new UnknownHostException(
Error.getMessage(
ErrorCode.M_SERVER_SECURE_VERIFY_3, 0,
new Object[] {
CN, host
}));
}
}
Is there a way to somehow bypass this mechanism and disable hostname validation?
Asked the same question on hsqldb forums and got to know that there is no workaround to this. The only thing you could do is to comment out the code which is calling the verify method and then rebuild the jar. I am still puzzled why hsqldb didn't use the HostnameVerifier (http://docs.oracle.com/javase/6/docs/api/javax/net/ssl/HostnameVerifier.html), which would have made it easier to write a custom Hostname Verifier.
Related
I try to request a new certificate via EST protocol from the EST test service URL “https://testrfc7030.com/”. The program uses Bouncy Castle for this.
I have already configured the EST service’s TA and my client certificate obtained from them. I also use the BC JSSE provider to get access to the “tls-unique” channel binding value.
eSTService = new JsseESTServiceBuilder(Config.CredAdmin.caHost, trustManagers)
.withKeyManagers(keyManagers)
.withProvider(BouncyCastleJsseProvider.PROVIDER_NAME)
.withChannelBindingProvider(new ChannelBindingProvider() {
//Use an anonymous binding provider that supports linking
//Identity and POP Information (RFC7030, Section 3.5.), that
//relies on Channel Bindings for TLS (RFC5929) using "tls-unique".
public boolean canAccessChannelBinding(Socket sock) {
boolean ret = sock instanceof BCSSLSocket;
if (!ret)
//should never happen
MyUtils.LambdaLogger.error("Can't get channel binding value, check if BouncyCastleJsseProvider could be loaded.");
return ret;
}
public byte[] getChannelBinding(Socket sock, String binding) {
BCSSLConnection bcon = ((BCSSLSocket)sock).getConnection();
if (bcon == null) {
//should never happen
MyUtils.LambdaLogger.error("Can't get \"%s\" channel binding value, check if BouncyCastleJsseProvider could be loaded.", binding);
return null;
}
byte[] ret = bcon.getChannelBinding(binding);
MyUtils.LambdaLogger.debug("retrieved %d bytes \"%s\" channel binding value", ret.length, binding);
return ret;
}
})
.build();
and
Security.addProvider(new BouncyCastleJsseProvider());
When I configure EST service port 9443 – that requires my client cert but no TLS channel binding – I do get a new certificate:
However, when I configure port 443 – that also needs TLS channel binding – although I get 12 bytes of “tls-unique” from BC JSSE, these won’t get accepted by the EST service testrfc7030.com, so it gives me an HTTP 401 – Unauthorized:
My problem is, I don’t know, what’s wrong:
my code
the BC JSSE implementation of “tls-unique” (RFC 5929)
the EST service’s implementation of “tls-unique” (RFC 5929)?
Does someone have an implementation that works with the EST service “testrfc7030.com:443” art has at least an idea, what's wrong?
---Update 1---
I'm creating the ContentSigner as following:
ContentSigner signer =
new JcaContentSignerBuilder(MyUtils.Crypto.sha256WithRSAEncryption)
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(keyPair.getPrivate());
and the csrBuilder:
PKCS10CertificationRequestBuilder csrBuilder =
new PKCS10CertificationRequestBuilder(
new X500Name(subjectDN),
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate());
with
ExtensionsGenerator extGen = new ExtensionsGenerator();
...
This we then use as following:
EnrollmentResponse resp = eSTService.simpleEnrollPoP(false, cb.csrBuilder, cb.signer, null);
Based on the input by Peter we were able to fix this problem as following:
//just for testrfc7030.com
ESTAuth auth = new JcaHttpAuthBuilder(null, "estuser", "estpwd".toCharArray())
.setNonceGenerator(new SecureRandom())
.setProvider("BC")
.build();
EnrollmentResponse resp = eSTService.simpleEnrollPoP(false, cb.csrBuilder, cb.signer, auth);
It turned out, that testrfc3070 requires the following authentication schemes:
Port 443: requires HTTP user auth + identity POP linking
Port 8443: requires HTTP user auth but no identity POP linking
Port 9443: requires user auth with client certificate (obtained via Port 8443 or Port 443) but no identity POP linking
identity POP linking = TLS channel binding
I'm trying to make a post request to one of our clients with certificate as part of their security.
I tried testing it in postman, just like the usual attached the certificate on postman settings and then able to make a post request to their api.
Now my problem is i'm encountering this error when i'm doing the request on our platform built in java
java.base/sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:664)
java.base/sun.security.util.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:222)
java.base/java.security.KeyStore.load(KeyStore.java:1479)
On the test environment they don't require the password hence null.
try (InputStream inputStream = new ByteArrayInputStream(p12Cert))
{
KeyStore keyStore = KeyStore.getInstance("jks");
keyStore.load(inputStream, null);
I traced the engineLoad method from javakeystore.
private static final int MAGIC = 0xfeedfeed;
private static final int VERSION_1 = 0x01;
private static final int VERSION_2 = 0x02;
if (xMagic!=MAGIC ||
(xVersion!=VERSION_1 && xVersion!=VERSION_2)) {
throw new IOException("Invalid keystore format");
}
Can someone elaborate the above?
Able to fix the problem above, first is the p12 cert i am trying to encode in base64 is in utf-8 encoding which is wrong and should be converted from HEXADECIMAL to base64.
I have a problem listing all available certificates installed in my personal (windows) keystore.
I use following code to get a list of all certificate aliases:
public static void main(String[] args) {
try {
KeyStore ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
Enumeration<String> en = ks.aliases();
while (en.hasMoreElements()) {
String aliasKey = (String) en.nextElement();
System.out.println("---> alias '" + aliasKey + "'");
//TODO GET CERT ETC
}
} catch (Exception ioe) {
System.err.println(ioe.getMessage());
}
}
It works fine, listing 2 of my recently installed certificates.
HOWEVER.
After a computer restart, code lists only one certificate alias, though in system (using certmgr.msc) i still have two certificates active.
Whats more - installed certificates are from smartcards:
- ENCARD (with ENIGMA CAPI)
- UNIZETO CARD (dont know details)
Problem seems to occur, when i use ENCARD with API.
After restart, UNIZETO card still works fine.
Any ideas?
I am having lots of problems with this.
I have the following code
try {
final SSHClient ssh = new SSHClient();
PKCS8KeyFile keyFile = new PKCS8KeyFile();
keyFile.init(new File(Thread.currentThread().getContextClassLoader().getResource("development.pem").toURI()));
ssh.loadKnownHosts();
ssh.addHostKeyVerifier("ec2-XX-XX-XX-XX.compute-1.amazonaws.com", 22, "ff:59:aa:24:42:b1:a0:9f:c9:4c:73:34:fb:95:53:c2:b8:37:a8:f8");
// ssh.addHostKeyVerifier("ec2-XX-XX-XX-XX.compute-1.amazonaws.com", 22, "90:1e:4d:09:42:c4:16:8a:4c:dc:ae:c2:60:14:f9:ea");
ssh.connect("ec2-XX-XX-XX-XX.compute-1.amazonaws.com");
ssh.auth("ec2-user", new AuthPublickey(keyFile));
Session session = ssh.startSession();
Command sudo = session.exec("sudo su -");
System.out.println("sudo=" +sudo.getOutputAsString());
Command whoami = session.exec("whoami");
System.out.println("whoami=" + whoami.getOutputAsString());
} catch (Exception e) {
e.printStackTrace();
}
The first addHostKeyVerifier is using the fingerprint on the AWS console, the commented out one is the one that it keeps telling me it is failing against. Where am i meant to get the correct key from.
If i use the second key it passes verification then fails afterwards.
I am using SSHJ version 0.8.1
This worked for me.
For your PEM file you need to use the OpenSSHKeyFile key provider.
SSHClient ssh = new SSHClient();
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
File file = new File("c:\\full\\path\\to\\keyfile.pem");
keyFile.init(file);
Personally, I just surpressed the host key verification to always return true. But I'm sure your way is more secure (if it works).
ssh.loadKnownHosts();
ssh.addHostKeyVerifier((a, b, c) -> true);
The username for AWS depends on your image. Very often it is "root". In my case, it was "ubuntu".
ssh.connect("ec2-54-165-233-48.compute-1.amazonaws.com");
ssh.auth("ubuntu", new AuthPublickey(keyFile));
Session session = ssh.startSession();
(Note: I'm using version 0.26.0 though.)
I'm developing on an application at the moment which contains quite a lot of personal user information - things like Facebook contacts, etc ... Now, one of the things I want to be able to do (and have done, quite effectively) is open up parts of the application to "3rd Party" applications, using Android's build-in inter-process communication protocol (AIDL). So far so good.
Here's the catch: because we're involved in handling quite a lot of personal information, we have to be quite careful about who can and can't access it; specifically, only "Trusted" applications should be able to do so. So the natural way to do this is to use a custom permission within the AndroidManifest.xml file where we declare the services. My problem is this: I want to be able to enact signature-level protection (similar to the normal "signature" permission level), but with a bit of a catch:
I don't only want application signed with our internal signature to be able to access the services. I'd like to be able to build a list of "trusted signatures" & at runtime (or if there's a better way, then maybe some other time?) be able to check incoming requests against this list of trusted keys.
This would satisfy the security constraints in the same way as the normal "signature" permission level I think - only programs on the "trusted keys list" would be able to access the services, and keys are hard to spoof (if possible at all?) - but with the added bonus that we wouldn't have to sign every application making use of the APIs with our internal team's key.
Is this possible at the moment in Android? And if so, are there any special requirements?
Thanks
I've now found the answer to this question, but I'll leave it for the sake of anyone looking in the future.
I opened up a discussion on android-security-discuss where it was answered. Link: http://groups.google.com/group/android-security-discuss/browse_thread/thread/e01f63c2c024a767
Short answer:
private boolean checkAuthorised(){
PackageManager pm = getPackageManager();
try {
for (Signature sig :
pm.getPackageInfo(pm.getNameForUid(getCallingUid()),
PackageManager.GET_SIGNATURES).signatures){
LogUtils.logD("Signature: " + sig.toCharsString());
if (Security.trustedSignatures.get(sig.toCharsString()) != null) {
return true;
}
}
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
LogUtils.logD("Couldn't find signature in list of trusted keys! Possibilities:");
for(String sigString : Security.trustedSignatures.keySet()){
LogUtils.logD(sigString);
}
/* Crash the calling application if it doesn't catch */
throw new SecurityException();
}
Where Security.trustedSignatures is a Map of the form:
Map<String,String>().put("public key","some description eg. name");
Put this method inside any code that is being called by the external process (ie. within your interface). Note that this will not have the desired effect inside the onBind() method of your RemoteService.
Great info jelford, but I would suggest instead of storing the entire string of the signature, to store/compare the SHA-1 of the certificate as shown in this answer from matreshkin.
This is similar to how Google handles the Maps Android API, and this will match the output shown via keytool.
private boolean checkAuthorized() throws SecurityException {
PackageManager pm = getPackageManager();
try {
PackageInfo packageInfo = pm.getPackageInfo(pm.getNameForUid(getCallingUid()),
PackageManager.GET_SIGNATURES);
Signature[] signatures = packageInfo.signatures;
byte[] certBytes = signatures[0].toByteArray();
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(
new ByteArrayInputStream(certBytes));
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] encodedCert = md.digest(cert.getEncoded());
String hexString = byte2HexFormatted(encodedCert);
Log.d("public certificate SHA-1: " + hexString);
String trustedAppName = trustedCerts.get(hexString);
if (trustedAppName != null) {
Log.d("Found public certificate SHA-1 for " + trustedAppName);
return true;
}
} catch (Exception e) {
Log.e(e, "Unable to get certificate from client");
}
Log.w("Couldn't find signature in list of trusted certs!");
/* Crash the calling application if it doesn't catch */
throw new SecurityException();
}
public static String byte2HexFormatted(byte[] arr) {
StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1) h = "0" + h;
if (l > 2) h = h.substring(l - 2, l);
str.append(h.toUpperCase());
if (i < (arr.length - 1)) str.append(':');
}
return str.toString();
}