Simple LDAP authentication using Java - java

I am using LDAP authentication using Java . I am using the following code but its throwing error
Exception in thread "main" javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09044E, comment: AcceptSecurityContext error, data 52e, v2580
Java Code i am using
package test;
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;
public class LoginLDAP {
public static void main(String[] args) throws Exception {
Map<String,String> params = createParams(args);
// firstname.lastname#mydomain.com
// String domainName = params.get("domain"); // mydomain.com or empty
String url="ldap://ip here:389";
String principalName="username here";
String domainName ="domain name";
if (domainName==null || "".equals(domainName)) {
int delim = principalName.indexOf('#');
domainName = principalName.substring(delim+1);
}
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
props.put(Context.PROVIDER_URL, url);
props.put(Context.SECURITY_PRINCIPAL, principalName);
props.put(Context.SECURITY_CREDENTIALS, "password here");
if (url.toUpperCase().startsWith("LDAPS://")) {
props.put(Context.SECURITY_PROTOCOL, "ssl");
props.put(Context.SECURITY_AUTHENTICATION, "simple");
props.put("java.naming.ldap.factory.socket", "test.DummySSLSocketFactory");
}
InitialDirContext context = new InitialDirContext(props);
try {
SearchControls ctrls = new SearchControls();
ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration<SearchResult> results = context.search(toDC(domainName),"(& (userPrincipalName="+principalName+")(objectClass=user))", ctrls);
if(!results.hasMore())
throw new AuthenticationException("Principal name not found");
SearchResult result = results.next();
System.out.println("distinguisedName: " + result.getNameInNamespace() ); // CN=Firstname Lastname,OU=Mycity,DC=mydomain,DC=com
Attribute memberOf = result.getAttributes().get("memberOf");
if(memberOf!=null) {
for(int idx=0; idx<memberOf.size(); idx++) {
System.out.println("memberOf: " + memberOf.get(idx).toString() ); // CN=Mygroup,CN=Users,DC=mydomain,DC=com
//Attribute att = context.getAttributes(memberOf.get(idx).toString(), new String[]{"CN"}).get("CN");
//System.out.println( att.get().toString() ); // CN part of groupname
}
}
} finally {
try { context.close(); } catch(Exception ex) { }
}
}
/**
* Create "DC=sub,DC=mydomain,DC=com" string
* #param domainName sub.mydomain.com
* #return
*/
private static String toDC(String domainName) {
StringBuilder buf = new StringBuilder();
for (String token : domainName.split("\\.")) {
if(token.length()==0) continue;
if(buf.length()>0) buf.append(",");
buf.append("DC=").append(token);
}
return buf.toString();
}
private static Map<String,String> createParams(String[] args) {
Map<String,String> params = new HashMap<String,String>();
for(String str : args) {
int delim = str.indexOf('=');
if (delim>0) params.put(str.substring(0, delim).trim(), str.substring(delim+1).trim());
else if (delim==0) params.put("", str.substring(1).trim());
else params.put(str, null);
}
return params;
}
package test;
import java.io.*;
import java.net.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.*;
import javax.net.ssl.*;
public class DummySSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory socketFactory;
public DummySSLSocketFactory() {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[]{ new DummyTrustManager()}, new SecureRandom());
socketFactory = ctx.getSocketFactory();
} catch ( Exception ex ){ throw new IllegalArgumentException(ex); }
}
public static SocketFactory getDefault() { return new DummySSLSocketFactory(); }
#Override public String[] getDefaultCipherSuites() { return socketFactory.getDefaultCipherSuites(); }
#Override public String[] getSupportedCipherSuites() { return socketFactory.getSupportedCipherSuites(); }
#Override public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException {
return socketFactory.createSocket(socket, string, i, bln);
}
#Override public Socket createSocket(String string, int i) throws IOException, UnknownHostException {
return socketFactory.createSocket(string, i);
}
#Override public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException, UnknownHostException {
return socketFactory.createSocket(string, i, ia, i1);
}
#Override public Socket createSocket(InetAddress ia, int i) throws IOException {
return socketFactory.createSocket(ia, i);
}
#Override public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException {
return socketFactory.createSocket(ia, i, ia1, i1);
}
}
class DummyTrustManager implements X509TrustManager {
#Override public void checkClientTrusted(X509Certificate[] xcs, String str) {
// do nothing
}
#Override public void checkServerTrusted(X509Certificate[] xcs, String str) {
/*System.out.println("checkServerTrusted for authType: " + str); // RSA
for(int idx=0; idx<xcs.length; idx++) {
X509Certificate cert = xcs[idx];
System.out.println("X500Principal: " + cert.getSubjectX500Principal().getName());
}*/
}
#Override public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[0];
}
}

data 52e - Returns when username is valid but password/credential is invalid.
I am guessing that you are passing the password to Microsoft Active Directory in plain text which usually will not work.
We have a "updateUserPassword" method in our "JNDI Examples" repository that should help.

Related

java.lang.AssertionError at org.java_websocket.client.WebSocketClient.run(WebSocketClient.java:190)

I am using WebSocketClient class to get the data from websocket in my android app but when anything change in Internet connection ( Disconnect from Internet ) it gives the following error in WebSocketClient.java file
USER_COMMENT=null
ANDROID_VERSION=10
APP_VERSION_NAME=0.0.1
BRAND=google
PHONE_MODEL=Android SDK built for x86_64
CUSTOM_DATA=
STACK_TRACE=java.lang.AssertionError
at org.java_websocket.client.WebSocketClient.run(WebSocketClient.java:190)
at java.lang.Thread.run(Thread.java:919)
But the WebSocketClient.java is read only file it is coming from java-websocket-1.3.0.jar file
WebSocketClient.java
package org.java_websocket.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import org.java_websocket.SocketChannelIOHelper;
import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketAdapter;
import org.java_websocket.WebSocketFactory;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.WrappedByteChannel;
import org.java_websocket.WebSocket.READYSTATE;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_10;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.handshake.HandshakeImpl1Client;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
public abstract class WebSocketClient extends WebSocketAdapter implements Runnable {
protected URI uri;
private WebSocketImpl conn;
private SocketChannel channel;
private ByteChannel wrappedchannel;
private Thread writethread;
private Thread readthread;
private Draft draft;
private Map<String, String> headers;
private CountDownLatch connectLatch;
private CountDownLatch closeLatch;
private int timeout;
private WebSocketClient.WebSocketClientFactory wsfactory;
private InetSocketAddress proxyAddress;
public WebSocketClient(URI serverURI) {
this(serverURI, new Draft_10());
}
public WebSocketClient(URI serverUri, Draft draft) {
this(serverUri, draft, (Map)null, 0);
}
public WebSocketClient(URI serverUri, Draft draft, Map<String, String> headers, int connecttimeout) {
this.uri = null;
this.conn = null;
this.channel = null;
this.wrappedchannel = null;
this.connectLatch = new CountDownLatch(1);
this.closeLatch = new CountDownLatch(1);
this.timeout = 0;
this.wsfactory = new DefaultWebSocketClientFactory(this);
this.proxyAddress = null;
if (serverUri == null) {
throw new IllegalArgumentException();
} else if (draft == null) {
throw new IllegalArgumentException("null as draft is permitted for `WebSocketServer` only!");
} else {
this.uri = serverUri;
this.draft = draft;
this.headers = headers;
this.timeout = connecttimeout;
try {
this.channel = SelectorProvider.provider().openSocketChannel();
this.channel.configureBlocking(true);
} catch (IOException var6) {
this.channel = null;
this.onWebsocketError((WebSocket)null, var6);
}
if (this.channel == null) {
this.conn = (WebSocketImpl)this.wsfactory.createWebSocket(this, draft, (Socket)null);
this.conn.close(-1, "Failed to create or configure SocketChannel.");
} else {
this.conn = (WebSocketImpl)this.wsfactory.createWebSocket(this, draft, this.channel.socket());
}
}
}
public URI getURI() {
return this.uri;
}
public Draft getDraft() {
return this.draft;
}
public void connect() {
if (this.writethread != null) {
throw new IllegalStateException("WebSocketClient objects are not reuseable");
} else {
this.writethread = new Thread(this);
this.writethread.start();
}
}
public boolean connectBlocking() throws InterruptedException {
this.connect();
this.connectLatch.await();
return this.conn.isOpen();
}
public void close() {
if (this.writethread != null) {
this.conn.close(1000);
}
}
public void closeBlocking() throws InterruptedException {
this.close();
this.closeLatch.await();
}
public void send(String text) throws NotYetConnectedException {
this.conn.send(text);
}
public void send(byte[] data) throws NotYetConnectedException {
this.conn.send(data);
}
public void run() {
if (this.writethread == null) {
this.writethread = Thread.currentThread();
}
this.interruptableRun();
assert !this.channel.isOpen();
}
private final void interruptableRun() {
if (this.channel != null) {
try {
String host;
int port;
if (this.proxyAddress != null) {
host = this.proxyAddress.getHostName();
port = this.proxyAddress.getPort();
} else {
host = this.uri.getHost();
port = this.getPort();
}
this.channel.connect(new InetSocketAddress(host, port));
this.conn.channel = this.wrappedchannel = this.createProxyChannel(this.wsfactory.wrapChannel(this.channel, (SelectionKey)null, host, port));
this.timeout = 0;
this.sendHandshake();
this.readthread = new Thread(new WebSocketClient.WebsocketWriteThread());
this.readthread.start();
} catch (ClosedByInterruptException var3) {
this.onWebsocketError((WebSocket)null, var3);
return;
} catch (Exception var4) {
this.onWebsocketError(this.conn, var4);
this.conn.closeConnection(-1, var4.getMessage());
return;
}
ByteBuffer buff = ByteBuffer.allocate(WebSocketImpl.RCVBUF);
try {
while(this.channel.isOpen()) {
if (SocketChannelIOHelper.read(buff, this.conn, this.wrappedchannel)) {
this.conn.decode(buff);
} else {
this.conn.eot();
}
if (this.wrappedchannel instanceof WrappedByteChannel) {
WrappedByteChannel w = (WrappedByteChannel)this.wrappedchannel;
if (w.isNeedRead()) {
while(SocketChannelIOHelper.readMore(buff, this.conn, w)) {
this.conn.decode(buff);
}
this.conn.decode(buff);
}
}
}
} catch (CancelledKeyException var5) {
this.conn.eot();
} catch (IOException var6) {
this.conn.eot();
} catch (RuntimeException var7) {
this.onError(var7);
this.conn.closeConnection(1006, var7.getMessage());
}
}
}
private int getPort() {
int port = this.uri.getPort();
if (port == -1) {
String scheme = this.uri.getScheme();
if (scheme.equals("wss")) {
return 443;
} else if (scheme.equals("ws")) {
return 80;
} else {
throw new RuntimeException("unkonow scheme" + scheme);
}
} else {
return port;
}
}
private void sendHandshake() throws InvalidHandshakeException {
String part1 = this.uri.getPath();
String part2 = this.uri.getQuery();
String path;
if (part1 != null && part1.length() != 0) {
path = part1;
} else {
path = "/";
}
if (part2 != null) {
path = path + "?" + part2;
}
int port = this.getPort();
String host = this.uri.getHost() + (port != 80 ? ":" + port : "");
HandshakeImpl1Client handshake = new HandshakeImpl1Client();
handshake.setResourceDescriptor(path);
handshake.put("Host", host);
if (this.headers != null) {
Iterator i$ = this.headers.entrySet().iterator();
while(i$.hasNext()) {
Entry<String, String> kv = (Entry)i$.next();
handshake.put((String)kv.getKey(), (String)kv.getValue());
}
}
this.conn.startHandshake(handshake);
}
public READYSTATE getReadyState() {
return this.conn.getReadyState();
}
public final void onWebsocketMessage(WebSocket conn, String message) {
this.onMessage(message);
}
public final void onWebsocketMessage(WebSocket conn, ByteBuffer blob) {
this.onMessage(blob);
}
public final void onWebsocketOpen(WebSocket conn, Handshakedata handshake) {
this.connectLatch.countDown();
this.onOpen((ServerHandshake)handshake);
}
public final void onWebsocketClose(WebSocket conn, int code, String reason, boolean remote) {
this.connectLatch.countDown();
this.closeLatch.countDown();
if (this.readthread != null) {
this.readthread.interrupt();
}
this.onClose(code, reason, remote);
}
public final void onWebsocketError(WebSocket conn, Exception ex) {
this.onError(ex);
}
public final void onWriteDemand(WebSocket conn) {
}
public void onWebsocketCloseInitiated(WebSocket conn, int code, String reason) {
this.onCloseInitiated(code, reason);
}
public void onWebsocketClosing(WebSocket conn, int code, String reason, boolean remote) {
this.onClosing(code, reason, remote);
}
public void onCloseInitiated(int code, String reason) {
}
public void onClosing(int code, String reason, boolean remote) {
}
public WebSocket getConnection() {
return this.conn;
}
public final void setWebSocketFactory(WebSocketClient.WebSocketClientFactory wsf) {
this.wsfactory = wsf;
}
public final WebSocketFactory getWebSocketFactory() {
return this.wsfactory;
}
public InetSocketAddress getLocalSocketAddress(WebSocket conn) {
return this.channel != null ? (InetSocketAddress)this.channel.socket().getLocalSocketAddress() : null;
}
public InetSocketAddress getRemoteSocketAddress(WebSocket conn) {
return this.channel != null ? (InetSocketAddress)this.channel.socket().getLocalSocketAddress() : null;
}
public abstract void onOpen(ServerHandshake var1);
public abstract void onMessage(String var1);
public abstract void onClose(int var1, String var2, boolean var3);
public abstract void onError(Exception var1);
public void onMessage(ByteBuffer bytes) {
}
public ByteChannel createProxyChannel(ByteChannel towrap) {
return (ByteChannel)(this.proxyAddress != null ? new WebSocketClient.DefaultClientProxyChannel(towrap) : towrap);
}
public void setProxy(InetSocketAddress proxyaddress) {
this.proxyAddress = proxyaddress;
}
private class WebsocketWriteThread implements Runnable {
private WebsocketWriteThread() {
}
public void run() {
Thread.currentThread().setName("WebsocketWriteThread");
try {
while(!Thread.interrupted()) {
SocketChannelIOHelper.writeBlocking(WebSocketClient.this.conn, WebSocketClient.this.wrappedchannel);
}
} catch (IOException var2) {
WebSocketClient.this.conn.eot();
} catch (InterruptedException var3) {
}
}
}
public interface WebSocketClientFactory extends WebSocketFactory {
ByteChannel wrapChannel(SocketChannel var1, SelectionKey var2, String var3, int var4) throws IOException;
}
public class DefaultClientProxyChannel extends AbstractClientProxyChannel {
public DefaultClientProxyChannel(ByteChannel towrap) {
super(towrap);
}
public String buildHandShake() {
StringBuilder b = new StringBuilder();
String host = WebSocketClient.this.uri.getHost();
b.append("CONNECT ");
b.append(host);
b.append(":");
b.append(WebSocketClient.this.getPort());
b.append(" HTTP/1.1\n");
b.append("Host: ");
b.append(host);
b.append("\n");
return b.toString();
}
}
}
The third-party library has a description. You can write a class to inherit the WebSocketClient class and rewrite the code after network disconnection and reconnection. The code example of the third-party library is provided for reference.

How to create a Rollback log which generates automatically after each day using java.util.logging.Logger?

I am using the following code to create a custom log as per my requirements.
But unable to get the rollback feature as java.util.logging.Logger doesn't support it.
What are the possible options for me to implement?
Is it possible to generate rollback logs automatically using the same library?
Code :
private static class MyCustomFormatterforUpdate extends java.util.logging.Formatter {
#Override
public String format(LogRecord record) {
StringBuffer sb = new StringBuffer();
sb.append("update ApiStatistics set RespDateTime =");
sb.append(record.getMessage());
sb.append(";");
sb.append("\n");
return sb.toString();
}
}
java.util.logging.Logger updatefile = java.util.logging.Logger.getLogger("Update Script");
boolean appendu = false;
FileHandler fhu;
{
try {
fhu = new FileHandler("src/main/resources/updatescript.log",appendu);
updatefile.addHandler(fhu);
fhu.setFormatter(new MyCustomFormatterforUpdate());
} catch (IOException e) {
e.printStackTrace();
}
}
Building upon my previous answer you just need to build out the proxy implementation inferred in that answer. The idea is to install a proxy handler which allows you to open and close the FileHandler object. This enabled the rotation you need. Below is working example that will rotate when the date changes. The test case is included and the output will appear in the home folder by default.
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Calendar;
import java.util.logging.ErrorManager;
import java.util.logging.FileHandler;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;
import java.util.logging.XMLFormatter;
public class DailyFileHandler extends Handler {
public static void main(String[] args) throws IOException {
DailyFileHandler dfh = new DailyFileHandler();
try {
dfh.setFormatter(new SimpleFormatter());
LogRecord r1 = new LogRecord(Level.SEVERE, "test 1");
LogRecord r2 = new LogRecord(Level.SEVERE, "test 2");
r2.setMillis(r1.getMillis() + (24L * 60L * 60L * 1000L));
dfh.publish(r1);
dfh.publish(r2);
} finally {
dfh.close();
}
}
private Calendar current = Calendar.getInstance();
private FileHandler target;
public DailyFileHandler() throws IOException {
target = new FileHandler(pattern(), limit(), count(), false);
init();
}
public DailyFileHandler(String pattern, int limit, int count)
throws IOException {
target = new FileHandler(pattern, limit, count, false);
init();
}
private void init() {
super.setLevel(level());
super.setFormatter(formatter());
super.setFilter(filter());
try {
super.setEncoding(encoding());
} catch (UnsupportedEncodingException impossible) {
throw new AssertionError(impossible);
}
initTarget();
}
private void initTarget() {
target.setErrorManager(super.getErrorManager());
target.setLevel(super.getLevel());
target.setFormatter(super.getFormatter());
target.setFilter(super.getFilter());
try {
target.setEncoding(super.getEncoding());
} catch (UnsupportedEncodingException impossible) {
throw new AssertionError(impossible);
}
}
private void rotate() {
String pattern = pattern();
int count = count();
int limit = limit();
try {
super.setErrorManager(target.getErrorManager());
target.close();
FileHandler rotate = new FileHandler(pattern, 0, count, false);
rotate.setFormatter(new SimpleFormatter()); //empty tail.
rotate.close();
current = Calendar.getInstance();
target = new FileHandler(pattern, limit, count, true);
initTarget();
} catch (RuntimeException | IOException e) {
this.reportError("", e, ErrorManager.OPEN_FAILURE);
}
}
private boolean shouldRotate(long millis) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(millis);
return cal.get(Calendar.DAY_OF_YEAR) != current.get(Calendar.DAY_OF_YEAR);
}
#Override
public synchronized void publish(LogRecord record) {
if (shouldRotate(record.getMillis())) {
rotate();
}
target.publish(record);
}
#Override
public synchronized void close() throws SecurityException {
target.close();
}
#Override
public synchronized void setEncoding(String encoding) throws SecurityException, UnsupportedEncodingException {
target.setEncoding(encoding);
super.setEncoding(encoding);
}
#Override
public synchronized boolean isLoggable(LogRecord record) {
return target.isLoggable(record);
}
#Override
public synchronized void flush() {
target.flush();
}
#Override
public synchronized void setFormatter(Formatter newFormatter) {
target.setFormatter(newFormatter);
super.setFormatter(newFormatter);
}
#Override
public synchronized Formatter getFormatter() {
return target.getFormatter();
}
#Override
public synchronized String getEncoding() {
return target.getEncoding();
}
#Override
public synchronized void setFilter(Filter newFilter) throws SecurityException {
target.setFilter(newFilter);
super.setFilter(newFilter);
}
#Override
public synchronized Filter getFilter() {
return target.getFilter();
}
#Override
public synchronized void setErrorManager(ErrorManager em) {
target.setErrorManager(em);
super.setErrorManager(em);
}
#Override
public synchronized ErrorManager getErrorManager() {
return target.getErrorManager();
}
#Override
public synchronized void setLevel(Level newLevel) throws SecurityException {
target.setLevel(newLevel);
super.setLevel(newLevel);
}
#Override
public synchronized Level getLevel() {
return target.getLevel();
}
private String pattern() {
LogManager m = LogManager.getLogManager();
String p = getClass().getName();
String pattern = m.getProperty(p + ".pattern");
if (pattern == null) {
pattern = "%h/java%u.log";
}
return pattern;
}
private int limit() {
LogManager m = LogManager.getLogManager();
String p = getClass().getName();
String v = m.getProperty(p + ".limit");
int limit = v == null ? Integer.MAX_VALUE : Integer.parseInt(v);
return limit;
}
private int count() {
LogManager m = LogManager.getLogManager();
String p = getClass().getName();
String v = m.getProperty(p + ".count");
int limit = v == null ? 7 : Integer.parseInt(v);
return limit;
}
private Level level() {
LogManager m = LogManager.getLogManager();
String p = getClass().getName();
String v = m.getProperty(p + ".level");
if (v != null) {
try {
return Level.parse(v);
} catch (Exception e) {
this.reportError(v, e, ErrorManager.OPEN_FAILURE);
}
}
return Level.ALL;
}
private Formatter formatter() {
LogManager m = LogManager.getLogManager();
String p = getClass().getName();
String v = m.getProperty(p + ".formatter");
if (v != null) {
try {
return Formatter.class.cast(Class.forName(v).newInstance());
} catch (Exception e) {
this.reportError("", e, ErrorManager.OPEN_FAILURE);
}
}
return new XMLFormatter();
}
private Filter filter() {
LogManager m = LogManager.getLogManager();
String p = getClass().getName();
String v = m.getProperty(p + ".filter");
if (v != null) {
try {
return Filter.class.cast(Class.forName(v).newInstance());
} catch (Exception e) {
this.reportError("", e, ErrorManager.OPEN_FAILURE);
}
}
return null;
}
private String encoding() {
LogManager m = LogManager.getLogManager();
String p = getClass().getName();
String v = m.getProperty(p + ".encoding");
if (v != null) {
try {
return Charset.forName(v).name();
} catch (Exception e) {
this.reportError(v, e, ErrorManager.OPEN_FAILURE);
}
}
return null;
}
}
A suggestion would be please use other logging frameworks which has many features in them
instead of java.util.logging.Logger
Useful links
configure-log4j-for-creating-daily-rolling-log-files
log4j-formatting-examples
a-guide-to-logging-in-java

i can not user ip addr to connect ldap server

i want use java and ldaps to connect ldapService. but its wrong.
the problem is :
nested exception is javax.naming.CommunicationException: 192.168.174.145:636 [Root exception is javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names present]
if i use hostname:636 it is success . i do not know why .can you help me ? very thanks
public class SslLdapContextSource extends LdapContextSource {
#Override
protected Hashtable<String, Object> getAnonymousEnv() {
Hashtable<String, Object> anonymousEnv = super.getAnonymousEnv();
anonymousEnv.put("java.naming.security.protocol", "ssl");
anonymousEnv.put("java.naming.ldap.factory.socket", CustomSSLSocketFactory.class.getName());
anonymousEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
return anonymousEnv;
}
}
public class CustomSslSocketFactory extends SSLSocketFactory {
private SSLSocketFactory socketFactory;
public CustomSslSocketFactory() {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[]{new DummyTrustmanager()}, new SecureRandom());
socketFactory = ctx.getSocketFactory();
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
public static SocketFactory getDefault() {
return new CustomSslSocketFactory();
}
#Override
public String[] getDefaultCipherSuites() {
return socketFactory.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return socketFactory.getSupportedCipherSuites();
}
#Override
public Socket createSocket(Socket socket, Senter code heretring string, int num, boolean bool) throws IOException {
return socketFactory.createSocket(socket, string, num, bool);
}
#Override
public Socket createSocket(String string, int num) throws IOException, UnknownHostException {
return socketFactory.createSocket(string, num);
}
#Override
public Socket createSocket(String string, int num, InetAddress netAdd, int i) throws IOException, UnknownHostException {
return socketFactory.createSocket(string, num, netAdd, i);
}
#Override
public Socket createSocket(InetAddress netAdd, int num) throws IOException {
return socketFactory.createSocket(netAdd, num);
}
#Override
public Socket createSocket(InetAddress netAdd1, int num, InetAddress netAdd2, int i) throws IOException {
return socketFactory.createSocket(netAdd1, num, netAdd2, i);
}
public static class DummyTrustmanager implements X509TrustManager {
#Override
public void checkClientTrusted(X509Certificate[] cert, String string) throws CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] cert, String string) throws CertificateException {
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[0];
}
}
}
#Bean
public LdapTemplate ldapTemplate() {
return new LdapTemplate(contextSourceTarget());
}
#Bean
public LdapContextSource contextSourceTarget() {
if(!useSSL){
String urls = "ldap://"+url+":"+port;
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setUrl(urls);
//ldapContextSource.setBase(base);
ldapContextSource.setUserDn(username);
ldapContextSource.setPassword(password);
ldapContextSource.setReferral(referral);
ldapContextSource.afterPropertiesSet();
return ldapContextSource;
}else{
String urls = "ldaps://"+url+":"+port;
SslLdapContextSource contextSource = new SslLdapContextSource();
contextSource.setUrl(urls);
contextSource.setUserDn(username);
contextSource.setPassword(password);
contextSource.setPooled(false);
contextSource.afterPropertiesSet();
return contextSource;
}
}
i want use ldaps://192.168.174.145:636 to connect ldapService.but now i only can use ldaps://test:636 to connect ldapService.
192.168.174.145 and test is same computer
When an SSL/TLS connection is established various checks are performed as part of the server authentication.
I.e.
Is the server certificate valid?
Is the server certificate issued by a trusted certificate authority?
Does the certificate belong to the server the client is connecting too?
For the last check either the Subject CN of the Subject DN of the server cert or values of the Subject Alternative Name (IP / DNS) extension of the server cert are checked. Please also see RFC5280

Read SubjectAlternativeNames from Certificate using Bouncy-Castle Library

I am using bouncy-castle library to make a TLS-Handshake with an Web-Server and grab the public certificate. Below is my code
private org.bouncycastle.asn1.x509.Certificate[] certificateList;
public static void main(String... args) {
new BCMain().testBCTLS();
}
private void testBCTLS() {
try {
Socket s = new Socket(InetAddress.getByName(WEB_SERVER), WEB_SERVER_PORT);
//TlsProtocolHandler tlsHandler = new TlsProtocolHandler(s.getInputStream(), s.getOutputStream());
TlsClientProtocol protocol = new TlsClientProtocol(s.getInputStream(), s.getOutputStream(), new SecureRandom());
TlsClient client = new DefaultTlsClient() {
private Boolean connectionStatus = Boolean.FALSE;
#Override
public TlsAuthentication getAuthentication() throws IOException {
return new ServerOnlyTlsAuthentication() {
public void notifyServerCertificate(Certificate serverCertificate)
throws IOException {
certificateList = serverCertificate.getCertificateList();
}
};
}
#Override
public Hashtable getClientExtensions() throws IOException {
Hashtable clientExtensions = super.getClientExtensions();
clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(clientExtensions);
Vector<ServerName> serverNames = new Vector(1);
serverNames.add(new ServerName(NameType.host_name, SNI_HOST_NAME));
TlsExtensionsUtils.addServerNameExtension(clientExtensions, new ServerNameList(serverNames));
return clientExtensions;
}
public Boolean getConnectionStatus() {
return connectionStatus;
}
};
protocol.connect(client);
if (this.certificateList!=null) {
org.bouncycastle.asn1.x509.Certificate certificate = certificateList[0];
System.out.println(certificate.getSubject());
}
InputStream is = protocol.getInputStream();
System.out.println(is);
} catch (Exception e) {
e.printStackTrace();
}
}
I wanted to extract Subject Alternative Names from that Public certificate
The X509Certificate of JDK has method to extract SubjectAlternativeNames .. But I want to get the same from the bouncy-castle certificate.
Can some one help on this please ?
I was able to extract Subject-Alternative-Names using X509CertificateHolder and JcaX509CertificateConverter classes from BouncyCastle Library .. In continuation to the above code
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
if (this.certificateList!=null) {
org.bouncycastle.asn1.x509.Certificate certificate = certificateList[0];
X509CertificateHolder holder = new X509CertificateHolder(certificate.getEncoded());
X509Certificate x509Certificate = new JcaX509CertificateConverter().getCertificate(holder);
Collection<List<?>> sanCollections = x509Certificate.getSubjectAlternativeNames();
}

TLS 1.2 + Java 1.6 + BouncyCastle

For supporting HTTPS connections through a Java 1.6 API to remote hosts using TLS 1.2, we have developed a customized TLS SocketConnection factory based on Bouncy Castle Libraries (v. 1.53)
It's very easy to use, just:
String httpsURL = xxxxxxxxxx
URL myurl = new URL(httpsURL);
HttpsURLConnection con = (HttpsURLConnection )myurl.openConnection();
con.setSSLSocketFactory(new TSLSocketConnectionFactory());
InputStream ins = con.getInputStream();
During testing, I connect different web and remote hosts exposed into SSLabs Tests
90% of the time this works fine! But there are some cases in which we get an annoying error: "Internal TLS error, this could be an attack" . It has been checked that there is no attack. That's a common error based on the treatment of internal BouncyCastle exceptions. I'm trying to find a common pattern to those remote host that fails with little luck.
Updated:
Updating some code for extra information, we get this:
org.bouncycastle.crypto.tls.TlsFatalAlert: illegal_parameter(47)
at org.bouncycastle.crypto.tls.AbstractTlsClient.checkForUnexpectedServerExtension(AbstractTlsClient.java:56)
at org.bouncycastle.crypto.tls.AbstractTlsClient.processServerExtensions(AbstractTlsClient.java:207)
at org.bouncycastle.crypto.tls.TlsClientProtocol.receiveServerHelloMessage(TlsClientProtocol.java:773)
The extension type i get is this:
ExtensionType:11
ExtensionData:
Acording to ExtensionType class, "ec_point_formats". This causes "UnexpectedServerExtension" --> The "UnexpectedServerExtension" causes a --> TlsFatalAlert: illegal_parameter , and at last this a "Internal TLS error, this could be an attack"
Any advise to log or trace this strange TLS Errors....???? As i say, this code works 90%...but with some remote host i get this errof
The trick consists in overriding startHandShake to use Bouncy's TLSClientProtocol:
Override ClientExtensions to include "host" ExtensionType. Just ExtensionType.server_name ( maybe any more Extension to include?)
Create a TlsAuthentication to include remoteCerts on Socket's
peerCertificate .Also optionally check if remote certs are in
default keystore (cacerts,etc..)
I share the code of TLSSocketConnectionFactory:
public class TLSSocketConnectionFactory extends SSLSocketFactory {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Adding Custom BouncyCastleProvider
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//SECURE RANDOM
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
private SecureRandom _secureRandom = new SecureRandom();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Adding Custom BouncyCastleProvider
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#Override
public Socket createSocket(Socket socket, final String host, int port, boolean arg3)
throws IOException {
if (socket == null) {
socket = new Socket();
}
if (!socket.isConnected()) {
socket.connect(new InetSocketAddress(host, port));
}
final TlsClientProtocol tlsClientProtocol = new TlsClientProtocol(socket.getInputStream(), socket.getOutputStream(), _secureRandom);
return _createSSLSocket(host, tlsClientProtocol);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SOCKET FACTORY METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#Override
public String[] getDefaultCipherSuites() {
return null;
}
#Override
public String[] getSupportedCipherSuites(){
return null;
}
#Override
public Socket createSocket( String host,
int port) throws IOException,UnknownHostException{
return null;
}
#Override
public Socket createSocket( InetAddress host,
int port) throws IOException {
return null;
}
#Override
public Socket createSocket( String host,
int port,
InetAddress localHost,
int localPort) throws IOException, UnknownHostException {
return null;
}
#Override
public Socket createSocket( InetAddress address,
int port,
InetAddress localAddress,
int localPort) throws IOException{
return null;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//SOCKET CREATION
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private SSLSocket _createSSLSocket(final String host , final TlsClientProtocol tlsClientProtocol) {
return new SSLSocket() {
private java.security.cert.Certificate[] peertCerts;
#Override
public InputStream getInputStream() throws IOException {
return tlsClientProtocol.getInputStream();
}
#Override
public OutputStream getOutputStream() throws IOException {
return tlsClientProtocol.getOutputStream();
}
#Override
public synchronized void close() throws IOException {
Log.to("util").info("\\\n::::::Close Socket");
tlsClientProtocol.close();
}
#Override
public void addHandshakeCompletedListener(HandshakeCompletedListener arg0) {
}
#Override
public boolean getEnableSessionCreation() {
return false;
}
#Override
public String[] getEnabledCipherSuites() {
return null;
}
#Override
public String[] getEnabledProtocols() {
return null;
}
#Override
public boolean getNeedClientAuth(){
return false;
}
#Override
public SSLSession getSession() {
return new SSLSession() {
#Override
public int getApplicationBufferSize() {
return 0;
}
#Override
public String getCipherSuite() {
throw new UnsupportedOperationException();
}
#Override
public long getCreationTime() {
throw new UnsupportedOperationException();
}
#Override
public byte[] getId() {
throw new UnsupportedOperationException();
}
#Override
public long getLastAccessedTime() {
throw new UnsupportedOperationException();
}
#Override
public java.security.cert.Certificate[] getLocalCertificates() {
throw new UnsupportedOperationException();
}
#Override
public Principal getLocalPrincipal() {
throw new UnsupportedOperationException();
}
#Override
public int getPacketBufferSize() {
throw new UnsupportedOperationException();
}
#Override
public X509Certificate[] getPeerCertificateChain()
throws SSLPeerUnverifiedException {
return null;
}
#Override
public java.security.cert.Certificate[] getPeerCertificates()throws SSLPeerUnverifiedException {
return peertCerts;
}
#Override
public String getPeerHost() {
throw new UnsupportedOperationException();
}
#Override
public int getPeerPort() {
return 0;
}
#Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
return null;
//throw new UnsupportedOperationException();
}
#Override
public String getProtocol() {
throw new UnsupportedOperationException();
}
#Override
public SSLSessionContext getSessionContext() {
throw new UnsupportedOperationException();
}
#Override
public Object getValue(String arg0) {
throw new UnsupportedOperationException();
}
#Override
public String[] getValueNames() {
throw new UnsupportedOperationException();
}
#Override
public void invalidate() {
throw new UnsupportedOperationException();
}
#Override
public boolean isValid() {
throw new UnsupportedOperationException();
}
#Override
public void putValue(String arg0, Object arg1) {
throw new UnsupportedOperationException();
}
#Override
public void removeValue(String arg0) {
throw new UnsupportedOperationException();
}
};
}
#Override
public String[] getSupportedProtocols() {
return null;
}
#Override
public boolean getUseClientMode() {
return false;
}
#Override
public boolean getWantClientAuth() {
return false;
}
#Override
public void removeHandshakeCompletedListener(HandshakeCompletedListener arg0) {
}
#Override
public void setEnableSessionCreation(boolean arg0) {
}
#Override
public void setEnabledCipherSuites(String[] arg0) {
}
#Override
public void setEnabledProtocols(String[] arg0) {
}
#Override
public void setNeedClientAuth(boolean arg0) {
}
#Override
public void setUseClientMode(boolean arg0) {
}
#Override
public void setWantClientAuth(boolean arg0) {
}
#Override
public String[] getSupportedCipherSuites() {
return null;
}
#Override
public void startHandshake() throws IOException {
Log.to("util").info("TSLSocketConnectionFactory:startHandshake()");
tlsClientProtocol.connect(new DefaultTlsClient() {
#SuppressWarnings("unchecked")
#Override
public Hashtable<Integer, byte[]> getClientExtensions() throws IOException {
Hashtable<Integer, byte[]> clientExtensions = super.getClientExtensions();
if (clientExtensions == null) {
clientExtensions = new Hashtable<Integer, byte[]>();
}
//Add host_name
byte[] host_name = host.getBytes();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream dos = new DataOutputStream(baos);
dos.writeShort(host_name.length + 3);
dos.writeByte(0); //
dos.writeShort(host_name.length);
dos.write(host_name);
dos.close();
clientExtensions.put(ExtensionType.server_name, baos.toByteArray());
return clientExtensions;
}
#Override
public TlsAuthentication getAuthentication()
throws IOException {
return new TlsAuthentication() {
#Override
public void notifyServerCertificate(Certificate serverCertificate) throws IOException {
try {
KeyStore ks = _loadKeyStore();
Log.to("util").info(">>>>>>>> KeyStore : "+ks.size());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<java.security.cert.Certificate> certs = new LinkedList<java.security.cert.Certificate>();
boolean trustedCertificate = false;
for ( org.bouncycastle.asn1.x509.Certificate c : serverCertificate.getCertificateList()) {
java.security.cert.Certificate cert = cf.generateCertificate(new ByteArrayInputStream(c.getEncoded()));
certs.add(cert);
String alias = ks.getCertificateAlias(cert);
if(alias != null) {
Log.to("util").info(">>> Trusted cert\n" + c.getSubject().toString());
if (cert instanceof java.security.cert.X509Certificate) {
try {
( (java.security.cert.X509Certificate) cert).checkValidity();
trustedCertificate = true;
Log.to("util").info("Certificate is active for current date\n"+cert);
} catch(CertificateExpiredException cee) {
R01FLog.to("r01f.util").info("Certificate is expired...");
}
}
} else {
Log.to("util").info(">>> Unknown cert " + c.getSubject().toString());
Log.to("util").fine(""+cert);
}
}
if (!trustedCertificate) {
throw new CertificateException("Unknown cert " + serverCertificate);
}
peertCerts = certs.toArray(new java.security.cert.Certificate[0]);
} catch (Exception ex) {
ex.printStackTrace();
throw new IOException(ex);
}
}
#Override
public TlsCredentials getClientCredentials(CertificateRequest arg0)
throws IOException {
return null;
}
/**
* Private method to load keyStore with system or default properties.
* #return
* #throws Exception
*/
private KeyStore _loadKeyStore() throws Exception {
FileInputStream trustStoreFis = null;
try {
String sysTrustStore = null;
File trustStoreFile = null;
KeyStore localKeyStore = null;
sysTrustStore = System.getProperty("javax.net.ssl.trustStore");
String javaHome;
if (!"NONE".equals(sysTrustStore)) {
if (sysTrustStore != null) {
trustStoreFile = new File(sysTrustStore);
trustStoreFis = _getFileInputStream(trustStoreFile);
} else {
javaHome = System.getProperty("java.home");
trustStoreFile = new File(javaHome + File.separator + "lib" + File.separator + "security" + File.separator + "jssecacerts");
if ((trustStoreFis = _getFileInputStream(trustStoreFile)) == null) {
trustStoreFile = new File(javaHome + File.separator + "lib" + File.separator + "security" + File.separator + "cacerts");
trustStoreFis = _getFileInputStream(trustStoreFile);
}
}
if (trustStoreFis != null) {
sysTrustStore = trustStoreFile.getPath();
} else {
sysTrustStore = "No File Available, using empty keystore.";
}
}
String trustStoreType = System.getProperty("javax.net.ssl.trustStoreType")!=null?System.getProperty("javax.net.ssl.trustStoreType"):KeyStore.getDefaultType();
String trustStoreProvider = System.getProperty("javax.net.ssl.trustStoreProvider")!=null?System.getProperty("javax.net.ssl.trustStoreProvider"):"";
if (trustStoreType.length() != 0) {
if (trustStoreProvider.length() == 0) {
localKeyStore = KeyStore.getInstance(trustStoreType);
} else {
localKeyStore = KeyStore.getInstance(trustStoreType, trustStoreProvider);
}
char[] keyStorePass = null;
String str5 = System.getProperty("javax.net.ssl.trustStorePassword")!=null?System.getProperty("javax.net.ssl.trustStorePassword"):"";
if (str5.length() != 0) {
keyStorePass = str5.toCharArray();
}
localKeyStore.load(trustStoreFis, (char[]) keyStorePass);
if (keyStorePass != null) {
for (int i = 0; i < keyStorePass.length; i++) {
keyStorePass[i] = 0;
}
}
}
return (KeyStore)localKeyStore;
} finally {
if (trustStoreFis != null) {
trustStoreFis.close();
}
}
}
private FileInputStream _getFileInputStream(File paramFile) throws Exception {
if (paramFile.exists()) {
return new FileInputStream(paramFile);
}
return null;
}
};
}
});
}
};//Socket
}
}
If you look at RFC 4492 5.2, you'll see that the server CAN send the "ec_point_formats" extension, but is only supposed to do so "when negotiating an ECC cipher suite". If you want TLSClient to just ignore the extra extension instead of raising an exception, I suggest overriding TlsClient.allowUnexpectedServerExtension(...) to allow ec_point_formats in the same way the default implementation allows elliptic_curves:
protected boolean allowUnexpectedServerExtension(Integer extensionType, byte[] extensionData)
throws IOException
{
switch (extensionType.intValue())
{
case ExtensionType.ec_point_formats:
/*
* Exception added based on field reports that some servers send Supported
* Point Format Extension even when not negotiating an ECC cipher suite.
* If present, we still require that it is a valid ECPointFormatList.
*/
TlsECCUtils.readSupportedPointFormatsExtension(extensionData);
return true;
default:
return super.allowUnexpectedServerExtension(extensionType, extensionData);
}
}
If this is a widespread problem, we might consider adding this case to the default implementation.
For logging, there are the (TLSPeer) methods notifyAlertRaised and notifyAlertReceived that you can override on your TLSClient implementation.
i am posting this so maybe some people might find this helpful - i was also getting invalid_parameter(47) exception from my BC client code.
this error started to happen when we upgraded BC from version 1.62 (bcprov- and bctls-jdk15on-162 JARs) to 1.71 (bcprov-, bctls-, bcpkix-, bcutil-jdk15to18-171).
my stacktrace was different from the above problem, though:
org.bouncycastle.tls.TlsFatalAlert: illegal_parameter(47)
at org.bouncycastle.tls.TlsClientProtocol.processServerHello(Unknown Source)
at org.bouncycastle.tls.TlsClientProtocol.handleHandshakeMessage(Unknown Source)
...
I had some trouble figuring out how to debug the internal BC TLS code. Тhe provided JARs are compiled without the debug info - I had to download the source files from Maven Central and build from them, creating a new project in Netbeans with existing sources - the only Java version it can be built with is 8, but the end result could be referenced by my Java 6 client code.
Having compiled bctls with debug info, and referencing it from my client, I could actually land in the code that was throwing the exception:
protected void processServerHello(ServerHello serverHello)
throws IOException
{
...
if (sessionServerExtensions != null && !sessionServerExtensions.isEmpty())
{
{
/*
* RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client
* and then selects a stream or Authenticated Encryption with Associated Data (AEAD)
* ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the
* client.
*/
boolean serverSentEncryptThenMAC = TlsExtensionsUtils.hasEncryptThenMACExtension(sessionServerExtensions);
if (serverSentEncryptThenMAC && !TlsUtils.isBlockCipherSuite(securityParameters.getCipherSuite()))
{
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
}
securityParameters.encryptThenMAC = serverSentEncryptThenMAC;
}
...
}
I don't really know what encrypt-then-MAC and MAC-then-encrypt is, and I didn't want to get deep into their meaning. After some searching, I figured out that possibly I had to limit the list of the cipher suites used for the communication - because possibly some cipher suite that is chosen by default forces the server into some condition where it behaves differently from the RFC specification - and yes, this at least worked:
TrustManagerFactory tmf = ...
SSLContext sc = SSLContext.getInstance("TLSv1.2", BouncyCastleJsseProvider.PROVIDER_NAME);
sc.init(null, tmf.getTrustManagers(), new SecureRandom());
SSLConnectionSocketFactory ret = new SSLConnectionSocketFactory(sc,
new String[] { "TLSv1.2" },
new String[] { "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" },
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
// the created SSLConnectionSocketFactory is then used to create socketFactoryRegistry
// which is then passed to constructor of Apache HttpComponents' PoolingHttpClientConnectionManager
// which is used to create a new instance of Apache HttpClient
Other useful stuff that I learned is
there are sites, e.g. https://www.ssllabs.com/ssltest that analyze the available protocols and cipher suites that a server supports.
the list of the cipher suites above is not exactly arbitrary - I took it from this stackoverflow q&a.

Categories

Resources