DIRKRB-750 (#53)

* DIRKRB-750: Add get_principal cmd for remote admin tool
diff --git a/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/XdrDataType.java b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/XdrDataType.java
index 34bc014..1ef4d7d 100644
--- a/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/XdrDataType.java
+++ b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/XdrDataType.java
@@ -32,9 +32,10 @@
     STRING              (0X04),
     ENUM                (0x05),
     OPAQUE              (0x06),
-    UNSIGNED_INTEGER   (0x07),
-    STRUCT               (0x08),
-    UNION                (0x09);
+    UNSIGNED_INTEGER    (0x07),
+    STRUCT              (0x08),
+    UNION               (0x09),
+    LONG                (0x0A);
 
     /** The dataType value */
     private int value;
diff --git a/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrLong.java b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrLong.java
new file mode 100644
index 0000000..6d601bc
--- /dev/null
+++ b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrLong.java
@@ -0,0 +1,82 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *
+ */
+package org.apache.kerby.xdr.type;
+
+import org.apache.kerby.xdr.XdrDataType;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * From RFC 4506 :
+ *
+ *         (MSB)                                                   (LSB)
+ *       +-------+-------+-------+-------+-------+-------+-------+-------+
+ *       |byte 0 |byte 1 |byte 2 |byte 3 |byte 4 |byte 5 |byte 6 |byte 7 |
+ *       +-------+-------+-------+-------+-------+-------+-------+-------+
+ *       <----------------------------64 bits---------------------------->
+ *                                                  HYPER INTEGER
+ *                                                  UNSIGNED HYPER INTEGER
+ */
+public class XdrLong extends XdrSimple<Long> {
+    public XdrLong() {
+        this((Long) null);
+    }
+
+    public XdrLong(Long value) {
+        super(XdrDataType.LONG, value);
+    }
+
+    /**
+     * The length of a signed long is 8.
+     * @return Length of a signed long type.
+     */
+    @Override
+    protected int encodingBodyLength() throws IOException {
+        return 8;
+    }
+
+    /**
+     * Encode Long type to bytes.
+     * Cannot only use toByteArray() because of fixed 4 bytes length.
+     */
+    @Override
+    protected void toBytes() throws IOException {
+        long value = getValue().longValue();
+        ByteBuffer buffer = ByteBuffer.allocate(8);
+        buffer.putLong(value);
+        buffer.flip();
+        setBytes(buffer.array());
+    }
+
+    /**
+     * Decode bytes to Long value.
+     */
+    @Override
+    protected void toValue() {
+        if (getBytes().length != 8) {
+            byte[] longBytes = ByteBuffer.allocate(8).put(getBytes(), 0, 8).array();
+            /**reset bytes in case the enum type is in a struct or union*/
+            setBytes(longBytes);
+        }
+        ByteBuffer buffer = ByteBuffer.wrap(getBytes());
+        setValue(buffer.getLong());
+    }
+}
\ No newline at end of file
diff --git a/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrSimple.java b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrSimple.java
index b5f2a9a..9c7b8c9 100644
--- a/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrSimple.java
+++ b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrSimple.java
@@ -129,6 +129,7 @@
             case UNSIGNED_INTEGER:
             case ENUM:
             case STRING:
+            case LONG:
                 return true;
             default:
                 return false;
diff --git a/kerby-common/kerby-xdr/src/test/java/org/apache/kerby/xdr/XdrLongTest.java b/kerby-common/kerby-xdr/src/test/java/org/apache/kerby/xdr/XdrLongTest.java
new file mode 100644
index 0000000..6fa0499
--- /dev/null
+++ b/kerby-common/kerby-xdr/src/test/java/org/apache/kerby/xdr/XdrLongTest.java
@@ -0,0 +1,80 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.kerby.xdr;
+
+import org.apache.kerby.xdr.type.XdrLong;
+import org.apache.kerby.xdr.util.HexUtil;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class XdrLongTest {
+    @Test
+    public void testEncoding() throws IOException {
+        testEncodingWith(0L, "0x00 00 00 00 00 00 00 00");
+        testEncodingWith(1L, "0x00 00 00 00 00 00 00 01");
+        testEncodingWith(2L, "0x00 00 00 00 00 00 00 02");
+        testEncodingWith(127L, "0x00 00 00 00 00 00 00 7F");
+        testEncodingWith(128L, "0x00 00 00 00 00 00 00 80");
+        testEncodingWith(-1L, "0xFF FF FF FF FF FF FF FF");
+        testEncodingWith(-127L, "0xFF FF FF FF FF FF FF 81");
+        testEncodingWith(-255L, "0xFF FF FF FF FF FF FF 01");
+        testEncodingWith(-32768L, "0xFF FF FF FF FF FF 80 00");
+        testEncodingWith(1234567890L, "0x00 00 00 00 49 96 02 D2");
+        testEncodingWith(9223372036854775807L, "0x7F FF FF FF FF FF FF FF");
+        testEncodingWith(-9223372036854775807L, "0x80 00 00 00 00 00 00 01");
+        testEncodingWith(-9223372036854775808L, "0x80 00 00 00 00 00 00 00");
+    }
+
+    private void testEncodingWith(long value, String expectedEncoding) throws IOException {
+        byte[] expected = HexUtil.hex2bytesFriendly(expectedEncoding);
+        XdrLong aValue = new XdrLong(value);
+
+        byte[] encodingBytes = aValue.encode();
+        assertThat(encodingBytes).isEqualTo(expected);
+    }
+
+
+    @Test
+    public void testDecoding() throws IOException {
+        testDecodingWith(0L, "0x00 00 00 00 00 00 00 00");
+        testDecodingWith(1L, "0x00 00 00 00 00 00 00 01");
+        testDecodingWith(2L, "0x00 00 00 00 00 00 00 02");
+        testDecodingWith(127L, "0x00 00 00 00 00 00 00 7F");
+        testDecodingWith(128L, "0x00 00 00 00 00 00 00 80");
+        testDecodingWith(-1L, "0xFF FF FF FF FF FF FF FF");
+        testDecodingWith(-127L, "0xFF FF FF FF FF FF FF 81");
+        testDecodingWith(-255L, "0xFF FF FF FF FF FF FF 01");
+        testDecodingWith(-32768L, "0xFF FF FF FF FF FF 80 00");
+        testDecodingWith(1234567890L, "0x00 00 00 00 49 96 02 D2");
+        testDecodingWith(9223372036854775807L, "0x7F FF FF FF FF FF FF FF");
+        testDecodingWith(-9223372036854775807L, "0x80 00 00 00 00 00 00 01");
+        testDecodingWith(-9223372036854775808L, "0x80 00 00 00 00 00 00 00");
+    }
+
+    private void testDecodingWith(long expectedValue, String content) throws IOException {
+        XdrLong decoded = new XdrLong();
+
+        decoded.decode(HexUtil.hex2bytesFriendly(content));
+        assertThat(decoded.getValue().longValue()).isEqualTo(expectedValue);
+    }
+}
diff --git a/kerby-kerb/kerb-admin-server/src/main/java/org/apache/kerby/kerberos/kerb/admin/server/kadmin/AdminServerHandler.java b/kerby-kerb/kerb-admin-server/src/main/java/org/apache/kerby/kerberos/kerb/admin/server/kadmin/AdminServerHandler.java
index 5ffbbf4..e5a4b08 100644
--- a/kerby-kerb/kerb-admin-server/src/main/java/org/apache/kerby/kerberos/kerb/admin/server/kadmin/AdminServerHandler.java
+++ b/kerby-kerb/kerb-admin-server/src/main/java/org/apache/kerby/kerberos/kerb/admin/server/kadmin/AdminServerHandler.java
@@ -33,6 +33,11 @@
 import org.apache.kerby.kerberos.kerb.admin.message.KeytabMessageCode;
 import org.apache.kerby.kerberos.kerb.admin.message.ExportKeytabRep;
 import org.apache.kerby.kerberos.kerb.admin.message.ChangePasswordRep;
+import org.apache.kerby.kerberos.kerb.admin.message.IdentityInfoCode;
+import org.apache.kerby.kerberos.kerb.admin.message.GetPrincipalRep;
+import org.apache.kerby.kerberos.kerb.request.KrbIdentity;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionType;
 import org.apache.kerby.xdr.XdrDataType;
 import org.apache.kerby.xdr.XdrFieldInfo;
 import org.apache.kerby.xdr.type.XdrStructType;
@@ -46,6 +51,8 @@
 import java.nio.file.Files;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * admin server handler to process client acmin requests.
@@ -108,6 +115,10 @@
                 System.out.println("message type: change password req");
                 responseMessage = handleChangePwdReq(localKadmin, fieldInfos);
                 break;
+            case GET_PRINCIPAL_REQ:
+                System.out.println("message type: get principal req");
+                responseMessage = handleGetPrincipalRep(localKadmin, fieldInfos);
+                break;
             default:
                 throw new KrbException("AdminMessageType error, can not handle the type: " + type);
         }
@@ -273,6 +284,21 @@
         return responseMessage;
     }
 
+    private ByteBuffer handleGetPrincipalRep(LocalKadmin localKadmin, XdrFieldInfo[] fieldInfos) throws IOException {
+        String principal = fixPrincipal((String) fieldInfos[2].getValue());
+
+        try {
+            KrbIdentity identity = localKadmin.getPrincipal(principal);
+
+            ByteBuffer responseMessage = infoPackageTool(identity, "getPrincipal");
+            return responseMessage;
+        } catch (KrbException e) {
+            String error = String.format("Failed to get principal %s. ", principal) + e.toString();
+            ByteBuffer responseError = infoPackageTool(error, "getPrincipal");
+            return responseError;
+        }
+    }
+
     private ByteBuffer infoPackageTool(String message, String dealType) throws IOException {
         AdminMessage adminMessage = null;
         XdrFieldInfo[] xdrFieldInfos = new XdrFieldInfo[3];
@@ -320,6 +346,33 @@
         return KadminCode.encodeMessage(adminMessage);
     }
 
+    private ByteBuffer infoPackageTool(KrbIdentity identity, String dealType) throws IOException {
+        AdminMessage adminMessage = null;
+        XdrFieldInfo[] xdrFieldInfos = new XdrFieldInfo[9];
+        if ("getPrincipal".equals(dealType)) {
+            adminMessage = new GetPrincipalRep();
+            xdrFieldInfos[0] = new XdrFieldInfo(0, XdrDataType.ENUM, AdminMessageType.GET_PRINCIPAL_REP);
+        }
+
+        Map<EncryptionType, EncryptionKey> key = identity.getKeys();
+        // Join key EncryptionType with comma delimiter
+        String keySet = key.keySet().stream().map(EncryptionType::getName).collect(Collectors.joining(","));
+
+        xdrFieldInfos[1] = new XdrFieldInfo(1, XdrDataType.INTEGER, 7);
+        xdrFieldInfos[2] = new XdrFieldInfo(2, XdrDataType.STRING, identity.getPrincipalName());
+        xdrFieldInfos[3] = new XdrFieldInfo(3, XdrDataType.LONG, identity.getExpireTime().getTime());
+        xdrFieldInfos[4] = new XdrFieldInfo(4, XdrDataType.LONG, identity.getCreatedTime().getTime());
+        xdrFieldInfos[5] = new XdrFieldInfo(5, XdrDataType.INTEGER, identity.getKdcFlags());
+        xdrFieldInfos[6] = new XdrFieldInfo(6, XdrDataType.INTEGER, identity.getKeyVersion());
+        xdrFieldInfos[7] = new XdrFieldInfo(7, XdrDataType.INTEGER, key.size());
+        xdrFieldInfos[8] = new XdrFieldInfo(8, XdrDataType.STRING, keySet.toString());
+
+        IdentityInfoCode value = new IdentityInfoCode(xdrFieldInfos);
+        adminMessage.setMessageBuffer(ByteBuffer.wrap(value.encode()));
+
+        return KadminCode.encodeMessage(adminMessage);
+    }
+
     private String listToString(List<String> list) {
         if (list.isEmpty()) {
             return null;
diff --git a/kerby-kerb/kerb-admin-server/src/test/java/org/apache/kerby/kerberos/kerb/admin/server/RemoteKadminTest.java b/kerby-kerb/kerb-admin-server/src/test/java/org/apache/kerby/kerberos/kerb/admin/server/RemoteKadminTest.java
index 23670db..1d53c70 100644
--- a/kerby-kerb/kerb-admin-server/src/test/java/org/apache/kerby/kerberos/kerb/admin/server/RemoteKadminTest.java
+++ b/kerby-kerb/kerb-admin-server/src/test/java/org/apache/kerby/kerberos/kerb/admin/server/RemoteKadminTest.java
@@ -29,6 +29,7 @@
 import org.apache.kerby.kerberos.kerb.admin.server.kadmin.AdminServerConfig;
 import org.apache.kerby.kerberos.kerb.identity.backend.BackendConfig;
 import org.apache.kerby.kerberos.kerb.keytab.Keytab;
+import org.apache.kerby.kerberos.kerb.request.KrbIdentity;
 import org.apache.kerby.kerberos.kerb.server.KdcConfig;
 import org.apache.kerby.kerberos.kerb.server.KdcServer;
 import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
@@ -58,6 +59,7 @@
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
 
 public class RemoteKadminTest {
     private static final Logger LOG = LoggerFactory.getLogger(RemoteKadminTest.class);
@@ -207,6 +209,14 @@
             Assert.fail("Login using new password failed: " + e.toString());
         }
     }
+    
+    @Test
+    public void remoteGetPrincipalTest() throws Exception {
+        AdminClient adminClient = buildKadminRemoteClient();
+        KrbIdentity identity = adminClient.requestGetPrincipal("kadmin/EXAMPLE.COM");
+        KrbIdentity expectIdentity = new KrbIdentity("kadmin/EXAMPLE.COM@EXAMPLE.COM");
+        assertEquals(identity, expectIdentity);
+    }
 
     private void doSaslHandShake(AdminClient adminClient, AdminConfig config) throws Exception {
         TransportPair tpair = AdminUtil.getTransportPair(adminClient.getSetting());
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/RemoteAdminClientTool.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/RemoteAdminClientTool.java
index 676ca60..78cf4af 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/RemoteAdminClientTool.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/RemoteAdminClientTool.java
@@ -27,9 +27,10 @@
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteAddPrincipalCommand;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteDeletePrincipalCommand;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteRenamePrincipalCommand;
-import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteGetprincsCommand;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteListPrincsCommand;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteKeytabAddCommand;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteChangePasswordCommand;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteGetPrincipalCommand;
 import org.apache.kerby.kerberos.kerb.common.KrbUtil;
 import org.apache.kerby.kerberos.kerb.server.KdcConfig;
 import org.apache.kerby.kerberos.kerb.server.KdcUtil;
@@ -90,7 +91,9 @@
         + "ktadd, xst\n"
         + "                         Add entry(s) to a keytab\n"
         + "change_password, cpw\n"
-        + "                         Change password\n";
+        + "                         Change password\n"
+        + "get_principal, getprinc\n"
+        + "                         Get principal\n";
 
     public static void main(String[] args) throws Exception {
         AdminClient adminClient;
@@ -218,7 +221,7 @@
         System.out.println("enter \"command\" to see legal commands.");
 
         Completer completer = new StringsCompleter("add_principal", "delete_principal", "rename_principal",
-                "list_principals", "ktadd", "change_password");
+                "list_principals", "ktadd", "change_password", "get_principal");
 
         Terminal terminal = null;
         try {
@@ -282,13 +285,16 @@
             executor = new RemoteRenamePrincipalCommand(adminClient);
         } else if (input.startsWith("list_principals")
             || input.startsWith("listprincs")) {
-            executor = new RemoteGetprincsCommand(adminClient);
+            executor = new RemoteListPrincsCommand(adminClient);
         } else if (input.startsWith("ktadd")
             || input.startsWith("xst")) {
             executor = new RemoteKeytabAddCommand(adminClient);
         } else if (input.startsWith("change_password")
                 || input.startsWith("cpw")) {
             executor = new RemoteChangePasswordCommand(adminClient);
+        } else if (input.startsWith("get_principal")
+                || input.startsWith("getprinc")) {
+            executor = new RemoteGetPrincipalCommand(adminClient);
         } else {
             System.out.println(LEGAL_COMMANDS);
             return;
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminClient.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminClient.java
index 1415ef9..1937c19 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminClient.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminClient.java
@@ -24,6 +24,7 @@
 import org.apache.kerby.kerberos.kerb.admin.kadmin.Kadmin;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.impl.DefaultInternalAdminClient;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.impl.InternalAdminClient;
+import org.apache.kerby.kerberos.kerb.request.KrbIdentity;
 
 import java.io.File;
 import java.util.List;
@@ -192,14 +193,12 @@
 
     public List<String> requestGetprincs() throws KrbException {
         Kadmin remote = new RemoteKadminImpl(innerClient);
-        List<String> principalLists = remote.getPrincipals();
-        return principalLists;
+        return remote.getPrincipals();
     }
 
     public List<String> requestGetprincsWithExp(String exp) throws KrbException {
         Kadmin remote = new RemoteKadminImpl(innerClient);
-        List<String> principalLists = remote.getPrincipals(exp);
-        return principalLists;
+        return remote.getPrincipals(exp);
     }
     
     public void requestExportKeytab(File keytabFile, String principal) throws KrbException {
@@ -216,4 +215,9 @@
         Kadmin remote = new RemoteKadminImpl(innerClient);
         remote.changePassword(principal, newPassword);
     }
+
+    public KrbIdentity requestGetPrincipal(String principal) throws KrbException {
+        RemoteKadminImpl remote = new RemoteKadminImpl(innerClient);
+        return remote.getPrincipal(principal);
+    }
 }
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminHandler.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminHandler.java
index e412551..37f7980 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminHandler.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminHandler.java
@@ -26,6 +26,11 @@
 import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageCode;
 import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageType;
 import org.apache.kerby.kerberos.kerb.admin.message.KeytabMessageCode;
+import org.apache.kerby.kerberos.kerb.admin.message.IdentityInfoCode;
+import org.apache.kerby.kerberos.kerb.request.KrbIdentity;
+import org.apache.kerby.kerberos.kerb.type.KerberosTime;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionType;
 import org.apache.kerby.xdr.XdrFieldInfo;
 import org.apache.kerby.xdr.type.XdrStructType;
 
@@ -187,6 +192,44 @@
         return keytabFileBytes;
     }
 
+    public KrbIdentity onResponseMessageForIdentity(AdminRequest adminRequest,
+                                                    ByteBuffer responseMessage) throws KrbException {
+        KrbIdentity identity = null;
+
+        IdentityInfoCode decoded = new IdentityInfoCode();
+        try {
+            decoded.decode(responseMessage);
+        } catch (IOException e) {
+            throw new KrbException("On response message failed.", e);
+        }
+        XdrFieldInfo[] fieldInfos = decoded.getValue().getXdrFieldInfos();
+        AdminMessageType type = (AdminMessageType) fieldInfos[0].getValue();
+
+        switch (type) {
+            case GET_PRINCIPAL_REP:
+                if (adminRequest.getAdminReq().getAdminMessageType() == AdminMessageType.GET_PRINCIPAL_REQ) {
+                    identity = new KrbIdentity((String) fieldInfos[2].getValue());
+
+                    identity.setExpireTime(new KerberosTime((long) fieldInfos[3].getValue()));
+                    identity.setCreatedTime(new KerberosTime((long) fieldInfos[4].getValue()));
+                    identity.setKdcFlags((int) fieldInfos[5].getValue());
+                    identity.setKeyVersion((int) fieldInfos[6].getValue());
+                    int keySize = (int) fieldInfos[7].getValue();
+                    String[] keySet = ((String) fieldInfos[8].getValue()).split(",");
+                    for (int i = 0; i < keySize; i++) {
+                        EncryptionKey key = new EncryptionKey();
+                        key.setKeyType(EncryptionType.fromName(keySet[i]));
+                        identity.addKey(key);
+                    }
+
+                }
+                break;
+            default:
+                throw new KrbException("Response message type error: " + type);
+        }
+        return identity;
+    }
+
     /**
      * Send message to kdc.
      *
@@ -200,4 +243,6 @@
     protected abstract List<String> handleRequestForList(AdminRequest adminRequest) throws KrbException;
     
     protected abstract byte[] handleRequestForBytes(AdminRequest adminRequest) throws KrbException;
+
+    protected abstract KrbIdentity handleRequestForIdentity(AdminRequest adminRequest) throws KrbException;
 }
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/RemoteKadminImpl.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/RemoteKadminImpl.java
index fba40bd..e12a913 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/RemoteKadminImpl.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/RemoteKadminImpl.java
@@ -32,8 +32,10 @@
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.RenamePrincipalRequest;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.GetprincsRequest;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.ChangePasswordRequest;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.GetPrincipalRequest;
 import org.apache.kerby.kerberos.kerb.common.KrbUtil;
 import org.apache.kerby.kerberos.kerb.keytab.Keytab;
+import org.apache.kerby.kerberos.kerb.request.KrbIdentity;
 import org.apache.kerby.kerberos.kerb.transport.KrbNetwork;
 import org.apache.kerby.kerberos.kerb.transport.KrbTransport;
 import org.apache.kerby.kerberos.kerb.transport.TransportPair;
@@ -197,18 +199,18 @@
 
     @Override
     public List<String> getPrincipals() throws KrbException {
-        AdminRequest grtPrincsRequest = new GetprincsRequest();
-        grtPrincsRequest.setTransport(transport);
+        AdminRequest getPrincsRequest = new GetprincsRequest();
+        getPrincsRequest.setTransport(transport);
         AdminHandler adminHandler = new DefaultAdminHandler();
-        return adminHandler.handleRequestForList(grtPrincsRequest);
+        return adminHandler.handleRequestForList(getPrincsRequest);
     }
 
     @Override
     public List<String> getPrincipals(String globString) throws KrbException {
-        AdminRequest grtPrincsRequest = new GetprincsRequest(globString);
-        grtPrincsRequest.setTransport(transport);
+        AdminRequest getPrincsRequest = new GetprincsRequest(globString);
+        getPrincsRequest.setTransport(transport);
         AdminHandler adminHandler = new DefaultAdminHandler();
-        return adminHandler.handleRequestForList(grtPrincsRequest);
+        return adminHandler.handleRequestForList(getPrincsRequest);
     }
 
     @Override
@@ -229,6 +231,13 @@
     public void release() throws KrbException {
 
     }
+    
+    public KrbIdentity getPrincipal(String principalName) throws KrbException {
+        AdminRequest getPrincipalRequest = new GetPrincipalRequest(principalName);
+        getPrincipalRequest.setTransport(transport);
+        AdminHandler adminHandler = new DefaultAdminHandler();
+        return adminHandler.handleRequestForIdentity(getPrincipalRequest);
+    }
 
     private String listToString(List<String> list) {
         if (list.isEmpty()) {
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteGetPrincipalCommand.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteGetPrincipalCommand.java
new file mode 100644
index 0000000..d6f6c96
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteGetPrincipalCommand.java
@@ -0,0 +1,69 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command;
+
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminClient;
+import org.apache.kerby.kerberos.kerb.request.KrbIdentity;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionType;
+
+public class RemoteGetPrincipalCommand extends RemoteCommand {
+    private static final String USAGE = "Usage: getprinc principalName\n"
+            + "\tExample:\n"
+            + "\t\tgetprinc hello@TEST.COM\n";
+
+    public RemoteGetPrincipalCommand(AdminClient adminClient) {
+        super(adminClient);
+    }
+
+    @Override
+    public void execute(String input) throws KrbException {
+        String[] items = input.split("\\s+");
+        if (items.length != 2) {
+            System.err.println(USAGE);
+            return;
+        }
+
+        String clientPrincipalName = items[items.length - 1];
+        KrbIdentity identity = null;
+        try {
+            identity = adminClient.requestGetPrincipal(clientPrincipalName);
+        } catch (KrbException e) {
+            System.err.println("Failed to get principal: " + clientPrincipalName + ". " + e.toString());
+        }
+        if (identity == null) {
+            return;
+        } else {
+            System.out.println("Principal is listed:");
+            System.out.println(
+                    "Principal: " + identity.getPrincipalName() + "\n"
+                            + "Expiration date: " + identity.getExpireTime() + "\n"
+                            + "Created time: " + identity.getCreatedTime() + "\n"
+                            + "KDC flags: " + identity.getKdcFlags() + "\n"
+                            + "Key version: " + identity.getKeyVersion() + "\n"
+                            + "Number of keys: " + identity.getKeys().size()
+            );
+
+            for (EncryptionType keyType: identity.getKeys().keySet()) {
+                System.out.println("key: " + keyType);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteGetprincsCommand.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteListPrincsCommand.java
similarity index 94%
rename from kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteGetprincsCommand.java
rename to kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteListPrincsCommand.java
index 2e15281..b808c91 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteGetprincsCommand.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteListPrincsCommand.java
@@ -23,13 +23,13 @@
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminClient;
 import java.util.List;
 
-public class RemoteGetprincsCommand extends RemoteCommand {
+public class RemoteListPrincsCommand extends RemoteCommand {
     private static final String USAGE = "Usage: list_principals [expression]\n"
             + "\t'expression' is a shell-style glob expression that can contain the wild-card characters ?, *, and []."
             + "\tExample:\n"
             + "\t\tlist_principals [expression]\n";
 
-    public RemoteGetprincsCommand(AdminClient adminClient) {
+    public RemoteListPrincsCommand(AdminClient adminClient) {
         super(adminClient);
     }
 
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/impl/DefaultAdminHandler.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/impl/DefaultAdminHandler.java
index 41615a7..03e3e93 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/impl/DefaultAdminHandler.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/impl/DefaultAdminHandler.java
@@ -22,6 +22,7 @@
 import org.apache.kerby.kerberos.kerb.KrbException;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminHandler;
 import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.AdminRequest;
+import org.apache.kerby.kerberos.kerb.request.KrbIdentity;
 import org.apache.kerby.kerberos.kerb.transport.KrbTransport;
 
 import java.io.IOException;
@@ -92,4 +93,20 @@
         }
         return keytabFileBytes;
     }
+
+    @Override
+    protected KrbIdentity handleRequestForIdentity(AdminRequest adminRequest) throws KrbException {
+        super.handleRequest(adminRequest);
+
+        KrbTransport transport = adminRequest.getTransport();
+        ByteBuffer receiveMessage = null;
+        KrbIdentity identity;
+        try {
+            receiveMessage = transport.receiveMessage();
+            identity = super.onResponseMessageForIdentity(adminRequest, receiveMessage);
+        } catch (IOException e) {
+            throw new KrbException("Admin receives response message failed", e);
+        }
+        return identity;
+    }
 }
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/request/GetPrincipalRequest.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/request/GetPrincipalRequest.java
new file mode 100644
index 0000000..0413a22
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/request/GetPrincipalRequest.java
@@ -0,0 +1,60 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request;
+
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageCode;
+import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageType;
+import org.apache.kerby.kerberos.kerb.admin.message.GetPrincipalReq;
+import org.apache.kerby.xdr.XdrDataType;
+import org.apache.kerby.xdr.XdrFieldInfo;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class GetPrincipalRequest extends AdminRequest {
+
+    public GetPrincipalRequest(String principal) {
+        super(principal);
+    }
+
+    @Override
+    public void process() throws KrbException {
+        super.process();
+
+        GetPrincipalReq getPrincipalReq = new GetPrincipalReq();
+        XdrFieldInfo[] xdrFieldInfos = new XdrFieldInfo[3];
+        xdrFieldInfos[0] = new XdrFieldInfo(0, XdrDataType.ENUM, AdminMessageType.GET_PRINCIPAL_REQ);
+        xdrFieldInfos[1] = new XdrFieldInfo(1, XdrDataType.INTEGER, 1);
+        xdrFieldInfos[2] = new XdrFieldInfo(2, XdrDataType.STRING, getPrincipal());
+
+        AdminMessageCode value = new AdminMessageCode(xdrFieldInfos);
+        byte[] encodeBytes;
+        try {
+            encodeBytes = value.encode();
+        } catch (IOException e) {
+            throw new KrbException("Xdr encode error when generate get principal request.", e);
+        }
+        ByteBuffer messageBuffer = ByteBuffer.wrap(encodeBytes);
+        getPrincipalReq.setMessageBuffer(messageBuffer);
+
+        setAdminReq(getPrincipalReq);
+    }
+}
\ No newline at end of file
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/AdminMessageType.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/AdminMessageType.java
index 7996355..d36c64c 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/AdminMessageType.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/AdminMessageType.java
@@ -46,7 +46,9 @@
     EXPORT_KEYTAB_REQ(8),
     EXPORT_KEYTAB_REP(9),
     CHANGE_PWD_REQ(10),
-    CHANGE_PWD_REP(11);
+    CHANGE_PWD_REP(11),
+    GET_PRINCIPAL_REQ(12),
+    GET_PRINCIPAL_REP(13);
 
     private int value;
 
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/GetPrincipalRep.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/GetPrincipalRep.java
new file mode 100644
index 0000000..fbe0bea
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/GetPrincipalRep.java
@@ -0,0 +1,26 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.kerby.kerberos.kerb.admin.message;
+
+public class GetPrincipalRep extends AdminRep {
+    public GetPrincipalRep() {
+        super(AdminMessageType.GET_PRINCIPAL_REP);
+    }
+}
\ No newline at end of file
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/GetPrincipalReq.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/GetPrincipalReq.java
new file mode 100644
index 0000000..d21ebd3
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/GetPrincipalReq.java
@@ -0,0 +1,26 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.kerby.kerberos.kerb.admin.message;
+
+public class GetPrincipalReq extends AdminReq {
+    public GetPrincipalReq() {
+        super(AdminMessageType.GET_PRINCIPAL_REQ);
+    }
+}
\ No newline at end of file
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/IdentityInfoCode.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/IdentityInfoCode.java
new file mode 100644
index 0000000..2dc5001
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/IdentityInfoCode.java
@@ -0,0 +1,105 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.kerby.kerberos.kerb.admin.message;
+
+import org.apache.kerby.xdr.XdrDataType;
+import org.apache.kerby.xdr.XdrFieldInfo;
+import org.apache.kerby.xdr.type.AbstractXdrType;
+import org.apache.kerby.xdr.type.XdrInteger;
+import org.apache.kerby.xdr.type.XdrLong;
+import org.apache.kerby.xdr.type.XdrString;
+import org.apache.kerby.xdr.type.XdrStructType;
+import org.apache.kerby.xdr.type.XdrType;
+
+/**
+ * An extend XdrStructType to encode and decode IdentityInfo.
+ * fields[0]: reply message type
+ * fields[1]: message size except the first two elements
+ * fields[2]: identity principal name
+ * fields[3]: identity expire time
+ * fields[4]: identity created time
+ * fields[5]: identity kdc flags
+ * fields[6]: identity key version
+ * fields[7]: the size of identity encryption keys
+ * fields[8]: identity encryption type joined with coma delimiter
+ */
+public class IdentityInfoCode extends XdrStructType {
+    public IdentityInfoCode() {
+        super(XdrDataType.STRUCT);
+    }
+
+    public IdentityInfoCode(XdrFieldInfo[] fieldInfos) {
+        super(XdrDataType.STRUCT, fieldInfos);
+    }
+
+    @Override
+    protected void getStructTypeInstance(XdrType[] fields, XdrFieldInfo[] fieldInfos) {
+        for (int i = 0; i < fieldInfos.length; i++) {
+            switch (fieldInfos[i].getDataType()) {
+                case INTEGER:
+                    fields[i] = new XdrInteger((Integer) fieldInfos[i].getValue());
+                    break;
+                case ENUM:
+                    fields[i] = new AdminMessageEnum((AdminMessageType) fieldInfos[i].getValue());
+                    break;
+                case STRING:
+                    fields[i] = new XdrString((String) fieldInfos[i].getValue());
+                    break;
+                case LONG:
+                    fields[i] = new XdrLong((Long) fieldInfos[i].getValue());
+                    break;
+                default:
+                    fields[i] = null;
+            }
+        }
+    }
+
+    @Override
+    protected XdrStructType fieldsToValues(AbstractXdrType[] fields) {
+        int paramNum = (int) fields[1].getValue();
+        XdrFieldInfo[] xdrFieldInfos = new XdrFieldInfo[paramNum + 2];
+        xdrFieldInfos[0] = new XdrFieldInfo(0, XdrDataType.ENUM, fields[0].getValue());
+        xdrFieldInfos[1] = new XdrFieldInfo(1, XdrDataType.INTEGER, fields[1].getValue());
+        xdrFieldInfos[2] = new XdrFieldInfo(2, XdrDataType.STRING, fields[2].getValue());
+        xdrFieldInfos[3] = new XdrFieldInfo(3, XdrDataType.LONG, fields[3].getValue());
+        xdrFieldInfos[4] = new XdrFieldInfo(4, XdrDataType.LONG, fields[4].getValue());
+        xdrFieldInfos[5] = new XdrFieldInfo(5, XdrDataType.INTEGER, fields[5].getValue());
+        xdrFieldInfos[6] = new XdrFieldInfo(6, XdrDataType.INTEGER, fields[6].getValue());
+        xdrFieldInfos[7] = new XdrFieldInfo(7, XdrDataType.INTEGER, fields[7].getValue());
+        xdrFieldInfos[8] = new XdrFieldInfo(8, XdrDataType.STRING, fields[8].getValue());
+
+        return new IdentityInfoCode(xdrFieldInfos);
+    }
+
+    @Override
+    protected AbstractXdrType[] getAllFields() {
+        AbstractXdrType[] fields = new AbstractXdrType[9];
+        fields[0] = new AdminMessageEnum();
+        fields[1] = new XdrInteger();
+        fields[2] = new XdrString();
+        fields[3] = new XdrLong();
+        fields[4] = new XdrLong();
+        fields[5] = new XdrInteger();
+        fields[6] = new XdrInteger();
+        fields[7] = new XdrInteger();
+        fields[8] = new XdrString();
+        return fields;
+    }
+}
\ No newline at end of file