TCP communication based on Kerberos authentication

1 Introduction

Kerberos is a security protocol, which involves three parts: KDC, codes of Server and Client. There are many introductions to the entire authentication process of Kerberos. But as for how to perform kerberos authentication in an existing TCP program, I have never known how to use it. Some time ago, in the oracle file, I found an example of server and client. Combined with this use case, I rewritten it for easy understanding.
Pre-instructions:

  1. KDC needs to ensure normal operation
  2. There are corresponding principals, as in our example, we need to ensure that the two principals of admin and lch must exist

2 code introduction

2.1 Server Code Description

package com.test.gssapi.sample1;

import org.ietf.jgss.*;

import java.io.*;
import java.net.Socket;
import java.net.ServerSocket;


public class TestServer {
    
    

    private int localPort;
    private ServerSocket ss;
    private boolean iskerberos;
    private Socket socket = null;
    private GSSContext context = null;


    public TestServer(int port) {
        this.localPort = port;
        this.iskerberos = false;
    }
    public TestServer(int port, boolean iskerberos) {
        this.localPort = port;
        this.iskerberos = iskerberos;
    }

    public void receive() throws IOException, GSSException {
        this.ss =  new ServerSocket(localPort);
        while(true) {
            socket = ss.accept();
            DataInputStream inStream =  new DataInputStream(socket.getInputStream());
            DataOutputStream outStream =  new DataOutputStream(socket.getOutputStream());
            this.initKerberos(inStream, outStream);

            int length = inStream.readInt();
            byte[] token = new byte[length];

            token = new byte[length];
            System.out.println("Will read token of size " + token.length);
            inStream.readFully(token);
            String s = new String(token);
            System.out.println(s);
            //1. 发送回去的消息
            byte[] token1 = "Receive Client Message".getBytes();
            outStream.writeInt(token1.length);
            outStream.write(token1);
            outStream.flush();      

            this.destroy();
        }
    }


    private void initKerberos( DataInputStream inStream, DataOutputStream outStream) throws GSSException, IOException {
        if(!this.iskerberos) {
            return;
        }       
        GSSManager manager = GSSManager.getInstance();
        context = manager.createContext((GSSCredential) null);
        byte[] token = null;

        while (!context.isEstablished()) {

            token = new byte[inStream.readInt()];
            System.out.println(
                    "Will read input token of size " + token.length + " for processing by acceptSecContext");
            inStream.readFully(token);


            token = context.acceptSecContext(token, 0, token.length);

            // Send a token to the peer if one was generated by
            // acceptSecContext
            if (token != null) {
                System.out.println("Will send token of size " + token.length + " from acceptSecContext.");
                outStream.writeInt(token.length);
                outStream.write(token);
                outStream.flush();
            }
        }

        System.out.print("Context Established! ");
        System.out.println("Client is " + context.getSrcName());
        System.out.println("Server is " + context.getTargName());
    }

    private void destroy() {
        if(this.socket != null) {
            try {
                this.socket.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            this.socket = null;
        }
        if(this.context != null) {
            try {
                this.context.dispose();
            } catch (GSSException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            this.context = null;

        }
    }


    public static void main(String[] args) throws IOException, GSSException {

        int localPort = Integer.parseInt(args[0]);
        boolean iskerberos = false;
        if(args.length >= 2) {
            iskerberos  = Boolean.parseBoolean(args[1]);
        }
        TestServer server = new TestServer(localPort, iskerberos);
        server.receive();
    }


}

2.2 Client code

package com.test.gssapi.sample1;


import org.ietf.jgss.*;

import java.net.Socket;
import java.net.UnknownHostException;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;


public class TestClient {
    
    

    private String srvPrincal;
    private String srvIP;
    private int srvPort;
    private boolean isKerberos;
    private Socket socket;
    private DataInputStream inStream;
    private DataOutputStream outStream;
    private GSSContext context;

    public TestClient(String srvPrincal, String srvIp, int srvPort, boolean iskerberos) {
        this.srvPrincal = srvPrincal;
        this.srvIP = srvIp;
        this.srvPort = srvPort;
        this.context = null;
        this.isKerberos = iskerberos;
        try {
            this.initSocket();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public TestClient(String srvPrincal, String srvIp, int srvPort) {
        this.srvPrincal = srvPrincal;
        this.srvIP = srvIp;
        this.srvPort = srvPort;
        this.context = null;
        this.isKerberos = false;
        try {
            this.initSocket();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void destroy() throws IOException, GSSException {
        if (this.inStream != null) {
            this.inStream.close();
        }
        if (this.outStream != null) {
            this.outStream.close();
        }
        if (this.socket != null) {
            this.socket.close();
        }
        if (this.context != null) {
            this.context.dispose();
        }
    }

    private void initSocket() throws UnknownHostException, IOException {
        this.socket = new Socket(srvIP, srvPort);
        this.inStream = new DataInputStream(socket.getInputStream());
        this.outStream = new DataOutputStream(socket.getOutputStream());
        System.out.println("Connected to server: " + this.socket.getInetAddress());
    }

    private void initKerberos() throws GSSException, IOException {
        if (!isKerberos) {
            return;
        }

        /**
         * Oid用来表示GSS-API这种机制。 这个值是协议中定义的(RFC 1964),这一个部分是固定的
         */
        Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");

        GSSManager manager = GSSManager.getInstance();

        /*
         * Create a GSSName out of the server's name. The null indicates that this
         * application does not wish to make any claims about the syntax of this name
         * and that the underlying mechanism should try to parse it as per whatever
         * default syntax it chooses.
         */
        /**
         * 创建一个GSSName,需要注意的是第一个参数srvPrincal,服务器收到消息后,会与自己的princal进行比较,
         * 如果不正确,就会抛出异常.
         */
        GSSName serverName = manager.createName(srvPrincal, null);

        /*
         * Create a GSSContext for mutual authentication with the server. - serverName
         * is the GSSName that represents the server. - krb5Oid is the Oid that
         * represents the mechanism to use. The client chooses the mechanism to use. -
         * null is passed in for client credentials - DEFAULT_LIFETIME lets the
         * mechanism decide how long the context can remain valid. Note: Passing in null
         * for the credentials asks GSS-API to use the default credentials. This means
         * that the mechanism will look among the credentials stored in the current
         * Subject to find the right kind of credentials that it needs.
         */
        System.out.println("init kerberos .. 1");
        GSSContext context = manager.createContext(serverName, krb5Oid, null, GSSContext.DEFAULT_LIFETIME);

        // Set the desired optional features on the context. The client
        // chooses these options.

        context.requestMutualAuth(true); // Mutual authentication
        context.requestConf(true); // Will use confidentiality later
        context.requestInteg(true); // Will use integrity later
        System.out.println("init kerberos .. 2");

        // Do the context eastablishment loop

        byte[] token = new byte[0];

        while (!context.isEstablished()) {
            // token is ignored on the first call
            // 这里进行kerberos认证,即调用JaaS的代码进行认证。认证完成后,返回
            // 这里我们可以把它GSS-api将JaaS的代码集成了。kerberos信息的确认是在此函数中进行的
            token = context.initSecContext(token, 0, token.length);

            // Send a token to the server if one was generated by
            // initSecContext
            if (token != null) {

                System.out.println("Will send token of size " + token.length + " from initSecContext.");
                outStream.writeInt(token.length);
                outStream.write(token);
                outStream.flush();
            }

            // If the client is done with context establishment
            // then there will be no more tokens to read in this loop
            if (!context.isEstablished()) {
                token = new byte[inStream.readInt()];
                System.out
                        .println("Will read input token of size " + token.length + " for processing by initSecContext");
                inStream.readFully(token);
            }
        }

        System.out.println("Context Established! ");
        System.out.println("Client is " + context.getSrcName());
        System.out.println("Server is " + context.getTargName());

    }

    public void sendMessage() throws UnknownHostException, IOException, GSSException {
        // Obtain the command-line arguments and parse the port number
        System.setProperty("java.security.krb5.conf", "/etc/krb5.conf");

        //判断是不是需要初始化kerberos
        this.initKerberos();

        //发送消息
        String msg = "Hello Server ";
        byte[] messageBytes = msg.getBytes();
        outStream.writeInt(messageBytes.length);
        outStream.write(messageBytes);
        outStream.flush();


        //收到服务端传回来的消息
        byte[] token = new byte[0];
        token = new byte[inStream.readInt()];
        System.out.println("Will read token of size " + token.length);
        inStream.readFully(token);

        String s = new String(token);
        System.out.println(s);

        System.out.println("Exiting... ");

        this.destroy();
    }

    public static void main(String[] args) throws IOException, GSSException {
        String princal = args[0];
        String ip = args[1];
        int port = Integer.parseInt(args[2]);
        boolean iskerberos =  false;
        if(args.length >= 4) {
             iskerberos = Boolean.parseBoolean(args[3]);
        }
        TestClient client =  new TestClient(princal, ip, port, iskerberos);
        client.sendMessage();
    }
}

2.3 Configuration file description

Regardless of whether the server or the client needs to use the jaas configuration file, this configuration file will be explained in conjunction with the execution of the program below

2.4 Execution of the program

It needs to be pointed out that for the client/server, it is necessary to ensure that the KDC is normally available and contains normal principal information. The following describes its execution process

2.4.1 Communication without Kerberos authentication, that is, our normal connection method

The server executes the command:

java -classpath kerberosTest.jar    com.test.gssapi.sample1.TestServer 9111 

The client executes the command:

java  -classpath kerberosTest.jar   com.test.gssapi.sample1.TestClient admin 192.168.1.56 9111 

At this point, it is an ordinary TCP communication protocol

2.4.2 Add Kerberos authentication

Server command:

[root@freeipa56 testKerberos]# java -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM  -Djava.security.krb5.kdc=freeipa56.example.com -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=server.conf   com.test.gssapi.sample1.TestServer 9111 true

Parameter description: The server has two parameters in total, 9111 indicates the port number, true indicates that kerberos is enabled, if not entered, it indicates that kerberos is not enabled

Client command:

java  -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM  -Djava.security.krb5.kdc=freeipa56.example.com    -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=client.conf  com.test.gssapi.sample1.TestClient admin 192.168.1.56 9111 true

Parameter description:
The client parameter has four parameters in total,
admin: indicates that the principal corresponding to the server is admin, and the principal authenticated by the server must be this
192.168.1.56: indicates the corresponding IP of the server
9111: is the port that the server monitors
true: Indicates that kerberos is enabled

Kerberos provides a variety of authentication methods, which are set through the configuration file corresponding to java.security.auth.login.config. Several situations are described below.

2.4.2.1 By entering the user name and password

Server configuration files (ie server.conf and client.conf):

[root@freeipa56 testKerberos]# cat server.conf 
com.sun.security.jgss.accept  {
  com.sun.security.auth.module.Krb5LoginModule required storeKey=true; 
};

Client configuration file client.conf

[root@freeipa56 testKerberos]# cat client.conf 
com.sun.security.jgss.initiate  {
  com.sun.security.auth.module.Krb5LoginModule required;
};

At this time, the screen information of the client is as follows:

[lch@freeipa56 testKerberos]$  java  -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM  -Djava.security.krb5.kdc=freeipa56.example.com    -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=client.conf  com.test.gssapi.sample1.TestClient admin 192.168.1.56 9111 true
Connected to server: /192.168.1.56
Kerberos username [lch]: lch   <------ 提示输入princal
Kerberos password for lch:     <-----  提示输入相应的密码
Will send token of size 558 from initSecContext.
Will read input token of size 108 for processing by initSecContext
Context Established! 
Client is lch@EXAMPLE.COM
Server is admin
Will read token of size 22
Receive Client Message
Exiting... 

And the server side:

[root@freeipa56 testKerberos]# java -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM  -Djava.security.krb5.kdc=freeipa56.example.com -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=bcsLogin.conf   com.test.gssapi.sample1.TestServer 9111 true
Will read input token of size 579 for processing by acceptSecContext
Kerberos username [root]: admin  <---- 提示输入服务端的princal名称 
Kerberos password for admin:     <--- 提示输入相应的密码
Will send token of size 108 from acceptSecContext.
Context Established! Client is [email protected]
Server is [email protected]
Will read token of size 13
Hello Server 

2.4.2.2 Through TGT

The configuration of the server is the same as 2.4.2.1, and the client authenticates by obtaining a ticket
. The configuration file of the server:

[root@freeipa56 testKerberos]# cat server.conf 
com.sun.security.jgss.accept  {
  com.sun.security.auth.module.Krb5LoginModule required storeKey=true; 
};

Client configuration file:

[lch@freeipa56 testKerberos]$ cat client.conf 
com.sun.security.jgss.initiate  {
  com.sun.security.auth.module.Krb5LoginModule required
  useKeyTab=false
  useTicketCache=true;

};

View the kerberos information of the current environment,

[lch@freeipa56 testKerberos]$ klist
Ticket cache: FILE:/tmp/krb5cc_500
Default principal: lch@EXAMPLE.COM

Valid starting     Expires            Service principal
11/15/17 11:08:47  11/16/17 11:08:46  krbtgt/EXAMPLE.COM@EXAMPLE.COM

Execute the client command:

[lch@freeipa56 testKerberos]$  java  -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM  -Djava.security.krb5.kdc=freeipa56.example.com    -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=client.conf  com.test.gssapi.sample1.TestClient admin 192.168.1.56 9111 true
Connected to server: /192.168.1.56
Will send token of size 579 from initSecContext.
Will read input token of size 108 for processing by initSecContext
Context Established! 
Client is lch@EXAMPLE.COM
Server is admin
Will read token of size 22
Receive Client Message
Exiting... 

The client program does not need to enter the username and password (server and)

2.4.2.3 The server passes the keytab file

server configuration file

[root@freeipa56 testKerberos]# cat server.conf 
com.sun.security.jgss.accept  {
  com.sun.security.auth.module.Krb5LoginModule required 
  useKeyTab=true
  storeKey=true
  useTicketCache=false
  keyTab="/root/admin.headless.keytab"
  principal="[email protected]";

};

Client configuration file:

[lch@freeipa56 testKerberos]$ cat client.conf 
com.sun.security.jgss.initiate  {
  com.sun.security.auth.module.Krb5LoginModule required
  useKeyTab=false
  useTicketCache=true;

};

Executing the server program and the client program will find that the server no longer needs to input authentication information.

3. Summary

Kerberos provides multiple authentication methods, and we can authenticate through the configuration parameters corresponding to java.security.auth.login.config

Guess you like

Origin blog.csdn.net/eyoulc123/article/details/78542761