I'm sing Java and tring to change the password in AD. I have imported the certificate to the server, but I get an error in the certificate.
Import's valid:
keytool -import -keystore "C:\Program Files\Java\jre6\lib\security\cacerts" -trustcacerts -alias openldap -file "C:\certnew.cer"
List's valid:
keytool -list -keystore "C:\Program Files\Java\jre6\lib\security\cacerts"
My code:
public class PassChange
{
public static void main (String[] args) {
Hashtable env = new Hashtable();
String userName = "CN=optimus,DC=ad,DC=euclid,DC=com";
String oldPassword = "euclid!23";
String newPassword = "kcube!23";
//Could also do this via command line java -Djavax.net.ssl.trustStore....
String keystore = "C:\\Program Files\\Java\\jre6\\lib\\security\\cacerts";
// 1 String keystore = "C:\\Program Files\\Java\\jre6\\lib\\security\\cacerts";
// 2 String keystore = "C:\\Program Files\\Java\\jre6\\lib\\security\\cacerts.jks";
// 3 String keystore = "c:\\";
// 1,2,3 all error
System.setProperty("javax.net.ssl.trustStore", keystore);
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
//set security credentials, note using simple cleartext authentication
env.put(Context.SECURITY_AUTHENTICATION,"simple");
env.put(Context.SECURITY_PRINCIPAL,userName);
env.put(Context.SECURITY_CREDENTIALS,oldPassword);
//specify use of ssl
env.put(Context.SECURITY_PROTOCOL,"ssl");
//connect to my domain controller
String ldapURL = "ldaps://xxx.xxx.xxx.xxx:636";
env.put(Context.PROVIDER_URL,ldapURL);
try {
// Create the initial directory context
LdapContext ctx = new InitialLdapContext(env,null);
//change password is a single ldap modify operation
//that deletes the old password and adds the new password
ModificationItem[] mods = new ModificationItem[2];
String oldQuotedPassword = "\"" + oldPassword + "\"";
byte[] oldUnicodePassword = oldQuotedPassword.getBytes("UTF-16LE");
String newQuotedPassword = "\"" + newPassword + "\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("unicodePwd", oldUnicodePassword));
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));
ctx.modifyAttributes(userName, mods);
System.out.println("Changed Password for: " + userName);
ctx.close();
}
catch (NamingException e) {
System.err.println("Problem changing password: " + e);
}
catch (UnsupportedEncodingException e) {
System.err.println("Problem encoding password: " + e);
}
}
}
Error message:
problem changing password: javax.naming.CommunicationException: simple bind failed: xxx.xxx.xxx.xxx:636 [Root exception is 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]
Which certificate did you import? You do NOT want the servers certificate. Rather you want the public key of the Certificate Authority. Specifically the switch -trustcacerts is meant to indicate this is a CA public key.
Guessing by the name, I wonder if you grabbed the certificate of the server.
Related
I am using a Java Websocket Server (TooTallNate). A Javascript App is connecting securely via a LetsEncrypt certificate. It is renewed automatically via certbot and is serving an Apache on the same machine too. On all tested browsers everything is working fine, for both https and wss.
I wanted to submit my app as a packaged FireTV app. I tested it in the "Web App Tester" app. As soon as the JS tries to connect to the WSS, it raises an SSL error, which reads in adb-logcat
I/X509util: Failed to validate the certificate chain, error: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
but sometimes just
E/chromium(13208): [ERROR:ssl_client_socket_impl.cc(947)] handshake failed; returned -1, SSL error code 1, net_error -202
The relevant Java code filling the SSLContext from TooTallNate is:
private static SSLContext getContext() {
SSLContext context;
String password = "CHANGEIT";
String pathname = "pem";
try {
context = SSLContext.getInstance("TLS");
byte[] certBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "cert.pem")),"-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
byte[] keyBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "privkey.pem")),"-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");
X509Certificate cert = generateCertificateFromDER(certBytes);
RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes);
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null);
keystore.setCertificateEntry("cert-alias", cert);
keystore.setKeyEntry("key-alias", key, password.toCharArray(), new Certificate[]{cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keystore, password.toCharArray());
KeyManager[] km = kmf.getKeyManagers();
context.init(km, null, null);
}
catch (Exception e) {
context = null;
}
return context;
}
It took some reading to get it to work. There are few information on this special problem, so I decided to answer myself:
It was truly a problem of the missing chain in the SSLContext. Couldn't believe that, because it worked on every other chromium based browser.
In my LetsEncrypt folder I have four files: privkey.pem (not at thing), cert.pem, chain.pem and fullchain.pem, where fullchain.pem is just the concatenation of chain.pem and cert.pem. cert.pem is our own certificate and chain.pem is the Digital Signature Trust DST Root CA X3 certificate which you can see yourself using:
cat chain.pem | openssl x509 -text
I compared the certificates served over https and wss using:
openssl s_client -connect myurl:443 | openssl x509 -text
and
openssl s_client -connect myurl:8080 | openssl x509 -text
The latter showed two error at the beginning of the output
verify error:num=20:unable to get local issuer certificate
while the https response from apache showed
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = myurl
verify return:1
Finally the working solution is a lot easier than my solutions I had meanwhile. Its not necessary to download any further chain certificates. Just load the chain.pem you already have in your LetsEncrypt folder like the cert.pem and add both as the chain to the keystore in the setKeyEntry function:
private static SSLContext getContext() {
SSLContext context;
String password = "CHANGEIT";
String pathname = "pem";
try {
context = SSLContext.getInstance("TLS");
byte[] certBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "cert.pem")),"-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
byte[] chainBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "chain.pem")),"-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
byte[] keyBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "privkey.pem")),"-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");
X509Certificate cert = generateCertificateFromDER(certBytes);
X509Certificate chain = generateCertificateFromDER(chainBytes);
RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes);
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null);
keystore.setCertificateEntry("cert-alias", cert);
keystore.setKeyEntry("key-alias", key, password.toCharArray(), new Certificate[]{cert, chain});
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keystore, password.toCharArray());
KeyManager[] km = kmf.getKeyManagers();
context.init(km, null, null);
}
catch (Exception e) {
context = null;
}
return context;
}
My Question:
From a Certificate Signing Request as below:
Example:
-----BEGIN CERTIFICATE REQUEST-----
MIIClDCCAXwCAQAwTzELMAkGA1UEBhMCVk4xEDAOBgNVBAMMB2RldmljZTMxCjAI ...
-----BEGIN CERTIFICATE REQUEST-----
I am using system.security.cryptography.x509certificates, I would load it as a CertificateRequest/X509Certificate2 object to create new certificate from Certificate Signing Request.
Anyone who know how to do?
My Try
Input:
self signed certificate as a CA
certificate signing request
My code
string rootCA = ""; // root certificate
string scrString = ""; // certificate signing request
byte[] rootRawData = Convert.FromBase64String(rootCA);
X509Certificate2 rootCert = new X509Certificate2(rootRawData);
var generator = X509SignatureGenerator.CreateForRSA(rootCert.GetRSAPrivateKey(), RSASignaturePadding.Pkcs1);
byte[] scrRawData = Convert.FromBase64String(scrString);
X509Certificate2 scrDeviceCert = new X509Certificate2(scrRawData); ------> ERROR: : 'Cannot find the requested object.'
CertificateRequest scrDeviceReq = new CertificateRequest(scrDeviceCert.IssuerName, scrDeviceCert.PublicKey, HashAlgorithmName.SHA256);
var deviceCert = scrDeviceReq.Create(rootCert.IssuerName, generator, DateTimeOffset.UtcNow.AddDays(1), DateTimeOffset.UtcNow.AddDays(6), new byte[] { 1, 2, 3, 4 });
There is no built-in functionality to read CSRs in .NET Core. You have to use 3rd party libraries to decode CSRs.
I have logstash-6.5.4 (with ssl), web and scheduler in my local (hostname: webbox) and kafka-2.0 (with ssl) on another (hostname: kafkabox).
I am not able to receive message in kafka topic when message is sent from logstash.
Neither error message is displayed not message is sent to kafka topic. I tried to import logstash.crt into kafka's truststore but it also didn't worked.
Created logstash.crt and logstash.key with below command.
sudo openssl req -x509 -batch -nodes -days 3650 -newkey rsa:2048 -keyout /etc/logstash/logstash.key -out /etc/logstash/logstash.crt
Imported the logstash.crt into kafka's truststore file also and tried.
keytool -import -alias logstash -file logstash.crt -keystore cacerts
Logstash conf file is given below...
input {
tcp {
host=>"0.0.0.0"
port=>5514
type=>"syslogType"
ssl_enable=>true
ssl_cert=>"/etc/logstash/logstash.crt"
ssl_key=>"/etc/logstash/logstash.key"
ssl_verify=>false
}
}
filter {
}
output {
kafka {
bootstrap_servers=>"kafkabox:9093"
codec=>"json_lines"
topic_id=>"a_test"
ssl_keystore_location=>"keystore file"
ssl_keystore_password=>"changeit"
ssl_key_password=>"changeit"
ssl_truststore_location=>"truststore file"
ssl_truststore_password=>"changeit"
security_protocol=>"SSL"
}
}
Expecting message is sent from logstash (with SSL) to kafka (with SSL).
Java Code to connect to logstash which internally failing to send message to kafka topics (in ssl mode).
public class LogstashClient {
private static String message = "<86>Jun 25 14:32:25 webbox sshd[7517]: Failed password for root from 196.165.132.192 port 45691 ssh2";
public static void main(String[] args) throws Exception {
nonSSL();
//SSL();
}
private static void SSL() throws Exception {
// logstash.crt is directly imported into kafka's truststore
// Below <<Client Truststore>> will also have logstash.crt imported for handshaking while connecting
System.setProperty("javax.net.ssl.trustStore", "<<Client Truststore>>");
System.setProperty("javax.net.ssl.trustStorePassword", "test1234");
SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) factory.createSocket("localhost", 5514);
System.out.println("Handshaking...");
socket.startHandshake();
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
boolean checkError = printWriter.checkError();
printWriter.println(message);
}
private static void nonSSL() throws Exception {
Socket socket = new Socket("localhost", 5514);
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.println(message);
}
}
Thanks,
RK,
Extra information at the end after comments from Crypt32 (thank you Crypt32!)
I have to send data to a server. I need mutual authentication: the server needs to be certain it is me, and I need to be certain that the server really is the server that I trust. This needs to be dons in a windows program.
To identify itself, the server will send me a certificate that is issued by certificate authorities that I trust: a root certificate and an intermediate certificate:
Root CA-G2.PEM
Intermediate CA-G2.PEM
To identify me, the organization gave me a certificate and a private key
Root CA-G3.PEM
Intermediate CA-G3.PEM
MyCertificate.CRT (= pem) and MyCertificate.Private.Key (=RSA)
I have imported all root certificates and intermediate certificates into the windows keystore.
To sent the message:
const string url = "https://...//Deliver";
HttpWebRequest webRequest = WebRequest.CreateHttp(url);
// Security:
webRequest.AuthenticationLevel=AuthenticationLevel.MutualAuthRequired;
webRequest.Credentials = CredentialCache.DefaultCredentials;
// Should I add my certificate?
X509Certificate myCertificate = new X509Certificate("MyCertificate.CRT");
// Should I add Certificate authorities?
// only the CA-G2 authorities, so my WebRequest can trust the certificate
// that will be sent by the Server?
// or Should I also add the CA-G3 who issued MyCertificate
// and what about MyCertificate.Private.Key, the RSA file?
// Fill the rest of the WebRequest:
webRequest.Method = "Post";
webRequest.Accept = "text/xml";
webRequest.Headers.Add("SOAP:Action");
webRequest.ContentType = "text/xml;charset=\"utf-8\"";
... etc
// do the call and display the result
using (WebResponse response = webRequest.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
{
string soapResult = reader.ReadToEnd();
Console.WriteLine(soapResult);
}
}
The WebResponse doesn't indicate any error. The returned data is an empty (non-null) string. Yet:
response.StatusCode == NoContent (204)
soapResult == String.Empty
response.IsMutuallyAuthenticated == false
The NoContent and the empty data result are expected. Does the false IsMutuallyAuthenticated indicate that something is wrong with my authentication?
Added information
Crypt32 suggested I should convert MyCertificate.CRT and MyCertificate.Private.Key into one PFX (or P12) file.
For this I use openssl.
I concatenated the CA-G3 files into one TrustG3.Pem and created the P12 file:
openssl.exe pkcs12 -export -name "<some friendly name>"
-certfile TrustG3.Pem
-in MyCertificate.CRT
-inkey MyCertificate.Private.Key
-out MyCertificate.P12
After providing a password a proper Pkcs12 file (PFX) was created. The source code changes slightly:
HttpWebRequest webRequest = WebRequest.CreateHttp(url);
// Security:
webRequest.AuthenticationLevel=AuthenticationLevel.MutualAuthRequired;
webRequest.Credentials = CredentialCache.DefaultCredentials;
var p12Certificate = new X509Certificate("MyCertificate.P12", "my password");
webRequest.ClientCertificates.Add(p12Certificate);
Alas, this didn't help. The webResponse still says:
response.IsMutuallyAuthenticated == false
Does the false IsMutuallyAuthenticated indicate that something is wrong with my authentication?
yes, it does. Because you add only public part of client certificate. There is no associated private key specified. Either, use certificate from certificate store (assuming, cert store contains private key) or import certificate from PFX.
Update:
now your client authentication code looks correct. Next step is to check if your client certificate is trusted by server.
A self signed certificate is one that is not signed by a Certificate Authority(CA). JAVA stores the certificates of most of the CA (here jre/lib/security/cacerts) and so if you want to connect to an https site with certificate signed by a CA there is no special code for it. (the same code for http call would work)
So basically
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(https_url);
httpclient.execute(httpGet)
But if we have a self signed certificate then we would need to configure the client and make it work. Otherwise we would see
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
javax.net.ssl.SSLPeerUnverifiedException: Host name 'localhost' does not match the certificate subject provided by the peer ...
How to configure http client for this case?
httpclient 4.x
Approach 1
Configure the TrustStrore through debug options
-Djavax.net.ssl.trustStore=/Users/amodpandey/.keystore
Java is able to read the truststore even without passing the password (password of the truststore)
But even after this you might face
javax.net.ssl.SSLPeerUnverifiedException: Host name 'localhost' does not match the certificate subject provided by the peer ...
CloseableHttpClient httpClient =
HttpClientBuilder.create()
.setSSLHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
})
.build();
And it works..
Approach 2
Put it all in the code
CloseableHttpClient httpClient =
HttpClientBuilder.create()
.setSslcontext(SSLContexts.custom().loadTrustMaterial(new File(Thread.currentThread().getContextClassLoader().getResource("keystore").getFile())).build())
.setSSLHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
})
.build();
Approach 3
While using a connection manager
The setSslcontext and setSSLHostnameVerifier are ignore when using
.setConnectionManager(connectionManager)
So connection manager should be configured
Files.copy(ClassLoader.getSystemResourceAsStream("keystore"), Paths.get(URI.create("file:/tmp/keystore")),
StandardCopyOption.REPLACE_EXISTING);
Registry<ConnectionSocketFactory> socketFactoryRegistry =
RegistryBuilder
.<ConnectionSocketFactory>create()
.register(
"https",
new SSLConnectionSocketFactory(SSLContextBuilder.create()
.loadTrustMaterial(new File("/tmp/keystore")).build(),
new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})).register("http", PlainConnectionSocketFactory.INSTANCE).build();
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager(socketFactoryRegistry);
CloseableHttpClient httpClient =
HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.build();
Note
-Djavax.net.debug=all debug option is very helpful to see the certificates being used
The SSLContext uses File and if we plan to package the certificate with the code in a Jar then we would need to create a file to pass it to the File object (it does not for files inside the jar)
Files.copy(ClassLoader.getSystemResourceAsStream("keystore"),Paths.get(URI.create("file:/tmp/utskeystore")),StandardCopyOption.REPLACE_EXISTING)