Using GSSManager to validate a Kerberos ticket - java

I have the following code:
public static void main(String args[]){
try {
//String ticket = "Negotiate YIGCBg...==";
//byte[] kerberosTicket = ticket.getBytes();
byte[] kerberosTicket = Base64.decode("YIGCBg...==");
GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
String user = context.getSrcName().toString();
context.dispose();
} catch (GSSException e) {
e.printStackTrace();
} catch (Base64DecodingException e) {
e.printStackTrace();
}
}
Of course it fails. Here's the exception:
GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
I don't know what I'm supposed to do to solve this. Honestly, I don't really understand Kerberos.
I got this ticket by sending a 401 with the appropriate header WWW-Authenticate with 'Negotiate' as the value. The browser immediately issued the same request again with an authorization header containing this ticket.
I was hoping I could validate the ticket and determine who the user is.
Do I need a keytab file? If so, what credentials would I run this under? I'm trying to use the Kerberos ticket for auth for a web-site. Would the credentials be the credentials from IIS?
What am I missing?
Update 1
From Michael-O's reply, I did a bit more googling and found this article, which led me to this article.
On table 3, I found 1.3.6.1.5.5.2 SPNEGO.
I have now added that to my credentials following the example from the first article. Here's my code:
public static void main(String args[]){
try {
Oid mechOid = new Oid("1.3.6.1.5.5.2");
GSSManager manager = GSSManager.getInstance();
GSSCredential myCred = manager.createCredential(null,
GSSCredential.DEFAULT_LIFETIME,
mechOid,
GSSCredential.ACCEPT_ONLY);
GSSContext context = manager.createContext(myCred);
byte[] ticket = Base64.decode("YIGCBg...==");
context.acceptSecContext(ticket, 0, ticket.length);
String user = context.getSrcName().toString();
context.dispose();
} catch (GSSException e) {
e.printStackTrace();
} catch (Base64DecodingException e) {
e.printStackTrace();
}
}
But now the code is failing on createCredential with this error:
GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos credentails)
Here's the entire ticket: YIGCBgYrBgEFBQKgeDB2oDAwLgYKKwYBBAGCNwICCgYJKoZIgvcSAQICBgkqhkiG9xIBAgIGCisGAQQBgjcCAh6iQgRATlRMTVNTUAABAAAAl7II4g4ADgAyAAAACgAKACgAAAAGAbEdAAAAD0xBUFRPUC0yNDVMSUZFQUNDT1VOVExMQw==

Validating an SPNEGO ticket from Java is a somewhat convoluted process. Here's a brief overview but bear in mind that the process can have tons of pitfalls. You really need to understand how Active Directory, Kerberos, SPNEGO, and JAAS all operate to successfully diagnose problems.
Before you start, make sure you know your kerberos realm name for your windows domain. For the purposes of this answer I'll assume it's MYDOMAIN. You can obtain the realm name by running echo %userdnsdomain% from a cmd window. Note that kerberos is case sensitive and the realm is almost always ALL CAPS.
Step 1 - Obtain a Kerberos Keytab
In order for a kerberos client to access a service, it requests a ticket for the Service Principal Name [SPN] that represents that service. SPNs are generally derived from the machine name and the type of service being accessed (e.g. HTTP/www.my-domain.com). In order to validate a kerberos ticket for a particular SPN, you must have a keytab file that contains a shared secret known to both the Kerberos Domain Controller [KDC] Ticket Granting Ticket [TGT] service and the service provider (you).
In terms of Active Directory, the KDC is the Domain Controller, and the shared secret is just the plain text password of the account that owns the SPN. A SPN may be owned by either a Computer or a User object within the AD.
The easiest way to setup a SPN in AD if you are defining a service is to setup a user-based SPN like so:
Create an unpriviledged service account in AD whose password doesn't expire e.g. SVC_HTTP_MYSERVER with password ReallyLongRandomPass
Bind the service SPN to the account using the windows setspn utility. Best practice is to define multiple SPNs for both the short name and the FQDN of the host:
setspn -U -S HTTP/myserver#MYDOMAIN SVC_HTTP_MYSERVER
setspn -U -S HTTP/myserver.my-domain.com#MYDOMAIN SVC_HTTP_MYSERVER
Generate a keytab for the account using Java's ktab utility.
ktab -k FILE:http_myserver.ktab -a HTTP/myserver#MYDOMAIN ReallyLongRandomPass
ktab -k FILE:http_myserver.ktab -a HTTP/myserver.my-domain.com#MYDOMAIN ReallyLongRandomPass
If you are trying to authenticate a pre-existing SPN that is bound to a Computer account or to a User account you do not control, the above will not work. You will need to extract the keytab from ActiveDirectory itself. The Wireshark Kerberos Page has some good pointers for this.
Step 2 - Setup your krb5.conf
In %JAVA_HOME%/jre/lib/security create a krb5.conf that describes your domain. Make sure the realm you define here matches what you setup for your SPN. If you don't put the file in the JVM directory, you can point to it by setting -Djava.security.krb5.conf=C:\path\to\krb5.conf on the command line.
Example:
[libdefaults]
default_realm = MYDOMAIN
[realms]
MYDOMAIN = {
kdc = dc1.my-domain.com
default_domain = my-domain.com
}
[domain_realm]
.my-domain.com = MYDOMAIN
my-domain.com = MYDOMAIN
Step 3 - Setup JAAS login.conf
Your JAAS login.conf should define a login configuration that sets up the Krb5LoginModule as a acceptor. Here's an example that assumes that the keytab we created above is in C:\http_myserver.ktab. Point to the JASS config file by setting -Djava.security.auth.login.config=C:\path\to\login.conf on the command line.
http_myserver_mydomain {
com.sun.security.auth.module.Krb5LoginModule required
principal="HTTP/myserver.my-domain.com#MYDOMAIN"
doNotPrompt="true"
useKeyTab="true"
keyTab="C:/http_myserver.ktab"
storeKey="true"
isInitiator="false";
};
Alternatively, you can generate a JAAS config at runtime like so:
public static Configuration getJaasKrb5TicketCfg(
final String principal, final String realm, final File keytab) {
return new Configuration() {
#Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
Map<String, String> options = new HashMap<String, String>();
options.put("principal", principal);
options.put("keyTab", keytab.getAbsolutePath());
options.put("doNotPrompt", "true");
options.put("useKeyTab", "true");
options.put("storeKey", "true");
options.put("isInitiator", "false");
return new AppConfigurationEntry[] {
new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
LoginModuleControlFlag.REQUIRED, options)
};
}
};
}
You would create a LoginContext for this configuration like so:
LoginContext ctx = new LoginContext("doesn't matter", subject, null,
getJaasKrbValidationCfg("HTTP/myserver.my-domain.com#MYDOMAIN", "MYDOMAIN",
new File("C:/path/to/my.ktab")));
Step 4 - Accepting the ticket
This is a little off-the-cuff, but the general idea is to define a PriviledgedAction that performs the SPNEGO protocol using the ticket. Note that this example does not check that SPNEGO protocol is complete. For example if the client requested server authentication, you would need to return the token generated by acceptSecContext() in the authentication header in the HTTP response.
public class Krb5TicketValidateAction implements PrivilegedExceptionAction<String> {
public Krb5TicketValidateAction(byte[] ticket, String spn) {
this.ticket = ticket;
this.spn = spn;
}
#Override
public String run() throws Exception {
final Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
GSSManager gssmgr = GSSManager.getInstance();
// tell the GSSManager the Kerberos name of the service
GSSName serviceName = gssmgr.createName(this.spn, GSSName.NT_USER_NAME);
// get the service's credentials. note that this run() method was called by Subject.doAs(),
// so the service's credentials (Service Principal Name and password) are already
// available in the Subject
GSSCredential serviceCredentials = gssmgr.createCredential(serviceName,
GSSCredential.INDEFINITE_LIFETIME, spnegoOid, GSSCredential.ACCEPT_ONLY);
// create a security context for decrypting the service ticket
GSSContext gssContext = gssmgr.createContext(serviceCredentials);
// decrypt the service ticket
System.out.println("Entering accpetSecContext...");
gssContext.acceptSecContext(this.ticket, 0, this.ticket.length);
// get the client name from the decrypted service ticket
// note that Active Directory created the service ticket, so we can trust it
String clientName = gssContext.getSrcName().toString();
// clean up the context
gssContext.dispose();
// return the authenticated client name
return clientName;
}
private final byte[] ticket;
private final String spn;
}
Then to authenticate the ticket, you would do something like the following. Assume that ticket contains the already-base-64-decoded ticket from the authentication header. The spn should be derived from the Host header in the HTTP request if the format of HTTP/<HOST>#<REALM>. E.g. if the Host header was myserver.my-domain.com then spn should be HTTP/myserver.my-domain.com#MYDOMAIN.
public boolean isTicketValid(String spn, byte[] ticket) {
LoginContext ctx = null;
try {
// this is the name from login.conf. This could also be a parameter
String ctxName = "http_myserver_mydomain";
// define the principal who will validate the ticket
Principal principal = new KerberosPrincipal(spn, KerberosPrincipal.KRB_NT_SRV_INST);
Set<Principal> principals = new HashSet<Principal>();
principals.add(principal);
// define the subject to execute our secure action as
Subject subject = new Subject(false, principals, new HashSet<Object>(),
new HashSet<Object>());
// login the subject
ctx = new LoginContext("http_myserver_mydomain", subject);
ctx.login();
// create a validator for the ticket and execute it
Krb5TicketValidateAction validateAction = new Krb5TicketValidateAction(ticket, spn);
String username = Subject.doAs(subject, validateAction);
System.out.println("Validated service ticket for user " + username
+ " to access service " + spn );
return true;
} catch(PriviledgedActionException e ) {
System.out.println("Invalid ticket for " + spn + ": " + e);
} catch(LoginException e) {
System.out.println("Error creating validation LoginContext for "
+ spn + ": " + e);
} finally {
try {
if(ctx!=null) { ctx.logout(); }
} catch(LoginException e) { /* noop */ }
}
return false;
}

This is not a Kerberos ticket but a SPNEGO ticket. Your context has the wrong mechanism.
Edit: Though, you now have the correct mech, you client is sending you a NTLM token which the GSS-API is not able to process. Take the Base 64 token, decode to raw bytes and display ASCII chars. If it starts with NTLMSSP, it won't work for sure and you have broken Kerberos setup.
Edit 2: This is your ticket:
60 81 82 06 06 2B 06 01 05 05 02 A0 78 30 76 A0 30 30 2E 06 `..+..... x0v 00..
0A 2B 06 01 04 01 82 37 02 02 0A 06 09 2A 86 48 82 F7 12 01 .+....7.....*H÷..
02 02 06 09 2A 86 48 86 F7 12 01 02 02 06 0A 2B 06 01 04 01 ....*H÷......+....
82 37 02 02 1E A2 42 04 40 4E 54 4C 4D 53 53 50 00 01 00 00 7...¢B.#NTLMSSP....
00 97 B2 08 E2 0E 00 0E 00 32 00 00 00 0A 00 0A 00 28 00 00 .².â....2.......(..
00 06 01 B1 1D 00 00 00 0F 4C 41 50 54 4F 50 2D 32 34 35 4C ...±.....LAPTOP-245L
49 46 45 41 43 43 4F 55 4E 54 4C 4C 43 IFEACCOUNTLLC
This is a wrapped NTLM token inside a SPNEGO token. which simply means that Kerberos has failed for some reasons, e.g.,
SPN not registered
Clockskew
Not allowed for Kerberos
Incorrect DNS records
Best option is to use Wireshark on the client to find the root cause.
Please note that Java does not support NTLM as a SPNEGO submechanism. NTLM is only supported by SSPI and Heimdal.

If the server does not have a keytab and associated key registered the KDC, you will never be able use kerberos to validate a ticket.
Getting SPNEGO to work is tricky at best and will be next to impossible without at least a cursory understanding of how kerberos works. Try reading this dialog and see if you can get a better understanding.
http://web.mit.edu/kerberos/dialogue.html
SPNEGO requires an SPN of the form HTTP/server.example.com and you'll need to tell the GSS libraries where that keytab is when you start the server.

Related

How to encode optional fields in spark dataset with java?

I would like to not use null value for field of a class used in dataset. I try to use scala Option and java Optional but it failed:
#AllArgsConstructor // lombok
#NoArgsConstructor // mutable type is required in java :(
#Data // see https://stackoverflow.com/q/59609933/1206998
public static class TestClass {
String id;
Option<Integer> optionalInt;
}
#Test
public void testDatasetWithOptionField(){
Dataset<TestClass> ds = spark.createDataset(Arrays.asList(
new TestClass("item 1", Option.apply(1)),
new TestClass("item .", Option.empty())
), Encoders.bean(TestClass.class));
ds.collectAsList().forEach(x -> System.out.println("Found " + x));
}
Fails, at runtime, with message File 'generated.java', Line 77, Column 47: Cannot instantiate abstract "scala.Option"
Question: Is there a way to encode optional fields without null in a dataset, using java?
Subsidiary question: btw, I didn't use much dataset in scala either, can you validate that it is actually possible in scala to encode a case class containing Option fields?
Note: This is used in an intermediate dataset, i.e something that isn't read nor write (but for spark internal serialization)
This is fairly simple to do in Scala.
Scala Implementation
import org.apache.spark.sql.{Encoders, SparkSession}
object Test {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder
.appName("Stack-scala")
.master("local[2]")
.getOrCreate()
val ds = spark.createDataset(Seq(
TestClass("Item 1", Some(1)),
TestClass("Item 2", None)
))( Encoders.product[TestClass])
ds.collectAsList().forEach(println)
spark.stop()
}
case class TestClass(
id: String,
optionalInt: Option[Int] )
}
Java
There are various Option classes available in Java. However, none of them work out-of-the-box.
java.util.Optional : Not serializable
scala.Option -> Serializable but abstract, so when CodeGenerator generates the following code, it fails!
/* 081 */ // initializejavabean(newInstance(class scala.Option))
/* 082 */ final scala.Option value_9 = false ?
/* 083 */ null : new scala.Option(); // ---> Such initialization is not possible for abstract classes
/* 084 */ scala.Option javaBean_1 = value_9;
org.apache.spark.api.java.Optional -> Spark's implementation of Optional which is serializable but has private constructors. So, it fails with error : No applicable constructor/method found for zero actual parameters. Since this is a final class, it's not possible to extend this.
/* 081 */ // initializejavabean(newInstance(class org.apache.spark.api.java.Optional))
/* 082 */ final org.apache.spark.api.java.Optional value_9 = false ?
/* 083 */ null : new org.apache.spark.api.java.Optional();
/* 084 */ org.apache.spark.api.java.Optional javaBean_1 = value_9;
/* 085 */ if (!false) {
One option is to use normal Java Optionals in the data class and then use Kryo as serializer.
Encoder en = Encoders.kryo(TestClass.class);
Dataset<TestClass> ds = spark.createDataset(Arrays.asList(
new TestClass("item 1", Optional.of(1)),
new TestClass("item .", Optional.empty())
), en);
ds.collectAsList().forEach(x -> System.out.println("Found " + x));
Output:
Found TestClass(id=item 1, optionalInt=Optional[1])
Found TestClass(id=item ., optionalInt=Optional.empty)
There is a downside when using Kryo: this encoder encodes in a binary format:
ds.printSchema();
ds.show(false);
prints
root
|-- value: binary (nullable = true)
+-------------------------------------------------------------------------------------------------------+
|value |
+-------------------------------------------------------------------------------------------------------+
|[01 00 4A 61 76 61 53 74 61 72 74 65 72 24 54 65 73 74 43 6C 61 73 F3 01 01 69 74 65 6D 20 B1 01 02 02]|
|[01 00 4A 61 76 61 53 74 61 72 74 65 72 24 54 65 73 74 43 6C 61 73 F3 01 01 69 74 65 6D 20 AE 01 00] |
+-------------------------------------------------------------------------------------------------------+
An udf-based solution to get the normal output columns of a dataset encoded with Kryo describes this answer.
Maybe a bit off-topic but probably a start to find a long-term solution is to look at the code of JavaTypeInference. The methods serializerFor and deserializerFor are used by ExpressionEncoder.javaBean to create the serializer and deserializer part of the encoder for Java beans.
In this pattern matching block
typeToken.getRawType match {
case c if c == classOf[String] => createSerializerForString(inputObject)
case c if c == classOf[java.time.Instant] => createSerializerForJavaInstant(inputObject)
case c if c == classOf[java.sql.Timestamp] => createSerializerForSqlTimestamp(inputObject)
case c if c == classOf[java.time.LocalDate] => createSerializerForJavaLocalDate(inputObject)
case c if c == classOf[java.sql.Date] => createSerializerForSqlDate(inputObject)
[...]
there is the handling for java.util.Optional missing. It could probably be added here as well as in the corresponding deserialize method. This would allow Java beans to have properties of type Optional.

I am getting "<ip address> was resolved to null" message while using SendArpRequest class from Pcap4j library. How to implement it properly?

I am trying to get MAC address of the device in a local network by using its IP address. To implement this in java I found a library name Pcap4j. I am using its class SendArpRequest to generate ARP request and receive a reply, but it always says "IP-Address was resolved to null".
Here is the java code:
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.pcap4j.core.BpfProgram.BpfCompileMode;
import org.pcap4j.core.NotOpenException;
import org.pcap4j.core.PacketListener;
import org.pcap4j.core.PcapHandle;
import org.pcap4j.core.PcapNativeException;
import org.pcap4j.core.PcapNetworkInterface;
import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode;
import org.pcap4j.core.Pcaps;
import org.pcap4j.packet.ArpPacket;
import org.pcap4j.packet.EthernetPacket;
import org.pcap4j.packet.Packet;
import org.pcap4j.packet.namednumber.ArpHardwareType;
import org.pcap4j.packet.namednumber.ArpOperation;
import org.pcap4j.packet.namednumber.EtherType;
import org.pcap4j.util.ByteArrays;
import org.pcap4j.util.MacAddress;
import org.pcap4j.util.NifSelector;
#SuppressWarnings("javadoc")
public class SendArpRequest {
private static final String COUNT_KEY = SendArpRequest.class.getName() + ".count";
private static final int COUNT = Integer.getInteger(COUNT_KEY, 1);
private static final String READ_TIMEOUT_KEY = SendArpRequest.class.getName() + ".readTimeout";
private static final int READ_TIMEOUT = Integer.getInteger(READ_TIMEOUT_KEY, 10); // [ms]
private static final String SNAPLEN_KEY = SendArpRequest.class.getName() + ".snaplen";
private static final int SNAPLEN =Integer.getInteger(SNAPLEN_KEY, 65536); // [bytes]
private static final MacAddress SRC_MAC_ADDR =MacAddress.getByName("00:db:df:8b:b1:a9");
private static MacAddress resolvedAddr;
private SendArpRequest() {}
public static void main(String[] args) throws PcapNativeException, NotOpenException {
String strSrcIpAddress = "192.168.0.11"; // for InetAddress.getByName()
//String strDstIpAddress = args[0]; // for InetAddress.getByName()
String strDstIpAddress = "192.168.0.3"; // for InetAddress.getByName()
System.out.println(COUNT_KEY + ": " + COUNT);
System.out.println(READ_TIMEOUT_KEY + ": " + READ_TIMEOUT);
System.out.println(SNAPLEN_KEY + ": " + SNAPLEN);
System.out.println("\n");
PcapNetworkInterface nif;
try {
nif = new NifSelector().selectNetworkInterface();
} catch (IOException e){
e.printStackTrace();
return;
}
if (nif == null) {
return;
}
System.out.println(nif.getName() + "(" + nif.getDescription() + ")");
PcapHandle handle = nif.openLive(SNAPLEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
PcapHandle sendHandle = nif.openLive(SNAPLEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
ExecutorService pool = Executors.newSingleThreadExecutor();
try {
handle.setFilter(
"arp and src host "
+ strDstIpAddress
+ " and dst host "
+ strSrcIpAddress
+ " and ether dst "
+ Pcaps.toBpfString(SRC_MAC_ADDR),
BpfCompileMode.OPTIMIZE);
PacketListener listener =
new PacketListener() {
public void gotPacket(Packet packet) {
if (packet.contains(ArpPacket.class)) {
ArpPacket arp = packet.get(ArpPacket.class);
if (arp.getHeader().getOperation().equals(ArpOperation.REPLY)) {
SendArpRequest.resolvedAddr = arp.getHeader().getSrcHardwareAddr();
}
}
System.out.println(packet);
}
};
Task t = new Task(handle, listener);
pool.execute(t);
ArpPacket.Builder arpBuilder = new ArpPacket.Builder();
try {
arpBuilder
.hardwareType(ArpHardwareType.ETHERNET)
.protocolType(EtherType.IPV4)
.hardwareAddrLength((byte) MacAddress.SIZE_IN_BYTES)
.protocolAddrLength((byte) ByteArrays.INET4_ADDRESS_SIZE_IN_BYTES)
.operation(ArpOperation.REQUEST)
.srcHardwareAddr(SRC_MAC_ADDR)
.srcProtocolAddr(InetAddress.getByName(strSrcIpAddress))
.dstHardwareAddr(MacAddress.ETHER_BROADCAST_ADDRESS)
.dstProtocolAddr(InetAddress.getByName(strDstIpAddress));
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
EthernetPacket.Builder etherBuilder = new EthernetPacket.Builder();
etherBuilder
.dstAddr(MacAddress.ETHER_BROADCAST_ADDRESS)
.srcAddr(SRC_MAC_ADDR)
.type(EtherType.ARP)
.payloadBuilder(arpBuilder)
.paddingAtBuild(true);
for (int i = 0; i < COUNT; i++) {
Packet p = etherBuilder.build();
System.out.println(p);
sendHandle.sendPacket(p);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
} finally {
if (handle != null && handle.isOpen()) {
handle.close();
}
if (sendHandle != null && sendHandle.isOpen()) {
sendHandle.close();
}
if (pool != null && !pool.isShutdown()) {
pool.shutdown();
}
System.out.println(strDstIpAddress + " was resolved to " + resolvedAddr);
}
}
private static class Task implements Runnable {
private PcapHandle handle;
private PacketListener listener;
public Task(PcapHandle handle, PacketListener listener) {
this.handle = handle;
this.listener = listener;
}
public void run() {
try {
handle.loop(COUNT, listener);
} catch (PcapNativeException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (NotOpenException e) {
e.printStackTrace();
}
}
}
}
And I got this output by using sourceIP = "192.168.0.11", sourceMAC = "00:db:df:8b:b1:a9" and destinationIP = "192.168.0.3".
com.arpscan.SendArpRequest.count: 1
com.arpscan.SendArpRequest.readTimeout: 10
com.arpscan.SendArpRequest.snaplen: 65536
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
6
6
7
2
2
0
0
0
0
0
NIF[0]: wlp1s0
: link layer address: 00:db:df:8b:b1:a9
: address: /192.168.0.11
: address: /fe80:0:0:0:c090:df9:7448:e0be
NIF[1]: any
: description: Pseudo-device that captures on all interfaces
NIF[2]: lo
: link layer address: 00:00:00:00:00:00
: address: /127.0.0.1
: address: /0:0:0:0:0:0:0:1
NIF[3]: virbr0
: link layer address: 52:54:00:72:e9:70
: address: /192.168.122.1
NIF[4]: enp0s31f6
: link layer address: 50:7b:9d:cc:71:d2
NIF[5]: bluetooth0
: description: Bluetooth adapter number 0
NIF[6]: nflog
: description: Linux netfilter log (NFLOG) interface
NIF[7]: nfqueue
: description: Linux netfilter queue (NFQUEUE) interface
NIF[8]: usbmon1
: description: USB bus number 1
NIF[9]: usbmon2
: description: USB bus number 2
Select a device number to capture packets, or enter 'q' to quit > 0
wlp1s0(null)
[Ethernet Header (14 bytes)]
Destination address: ff:ff:ff:ff:ff:ff
Source address: 00:db:df:8b:b1:a9
Type: 0x0806 (ARP)
[ARP Header (28 bytes)]
Hardware type: 1 (Ethernet (10Mb))
Protocol type: 0x0800 (IPv4)
Hardware address length: 6 [bytes]
Protocol address length: 4 [bytes]
Operation: 1 (REQUEST)
Source hardware address: 00:db:df:8b:b1:a9
Source protocol address: /192.168.0.11
Destination hardware address: ff:ff:ff:ff:ff:ff
Destination protocol address: /192.168.0.3
[Ethernet Pad (18 bytes)]
Hex stream: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[data (42 bytes)]
Hex stream: 00 db df 8b b1 a9 d0 04 01 62 61 38 08 06 00 01 08 00 06 04 00 02 d0 04 01 62 61 38 c0 a8 00 03 00 db df 8b b1 a9 c0 a8 00 0b
192.168.0.3 was resolved to null
Here I got null for destination MAC Address as you can see.
But when I run arp-scan command I got correct MAC address for the given IP Address. Here is screenshot of arp-scan result.
Please suggest me how I can implement it properly.
You need to add a packet factory moudle (e.g. pcap4j-packetfactory-static.jar) to your class path so that Pcap4J can dessect the ARP response.
And, probably you'd better add volatile to private static MacAddress resolvedAddr;.

How to delegate the kerberos client credentials on a linux server running Tomcat?

This is nearly the same question as How to delegate the kerberos client credentials to the server?
But there is no answer. That's why I raise the question again. Hopefully someone can help.
It's possible to get a service ticket for the client (remote user) in the server side in order to use that ticket to authenticate against another backend?
Scenario: User (IE) ==> AppServer (Tomcat, under Linux) ==> Backend (webservice - REST service on Windows)
We have SPNEGO auth running and working in the AppServer
The AD user in the keytab file on in the AppServer has the rights to
do the delegation (hopefully)
What are the preconditions that the GSSManager can create a
credential that can be used for delegation? (´context.getDelegCred()´
should not fail after
´GSSManager.getInstance().createContext(this.serverCredentials)´?
There must be someone who has solved this problem?
Does "Forwardable Ticket true" mean that user from keytab file has delegation rights? Does anyone know this?
Thanks in advance
Output from HelloKDC.java (see extract from bellow)
Client Principal = HTTP/servername.domain.com#CORP1.AD1.COMPANY.NET
Server Principal = krbtgt/CORP1.AD1.COMPANY.NET#CORP1.AD1.COMPANY.NET
Session Key = EncryptionKey: keyType=23 keyBytes (hex dump)=
0000: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx ................
Forwardable Ticket true
Forwarded Ticket false
Proxiable Ticket false
Proxy Ticket false
Postdated Ticket false
Renewable Ticket false
Initial Ticket false
Auth Time = Wed Dec 20 16:52:03 CET 2017
Start Time = Wed Dec 20 16:52:03 CET 2017
End Time = Thu Dec 21 02:52:03 CET 2017
Renew Till = null
Client Addresses Null
Private Credential: /opt/app/tomcat/ssoad1/servername.domain.com.keytab for HTTP/servername.domain.com#CORP1.AD1.COMPANY.NET
Connection test successful.
Extract from HelloKDC.java (also from net.sourceforge.spnego):
// Name of our krb5 config file
final String krbfile = "/opt/app/tomcat/ssoad1/krb5.ini";
// Name of our login config file
final String loginfile = "/opt/app/tomcat/ssoad1/jaas.conf";
// Name of our login module
//final String module = "spnego-client";
final String module = "com.sun.security.jgss.krb5.initiate";
// set some system properties
System.setProperty("java.security.krb5.conf", krbfile);
System.setProperty("java.security.auth.login.config", loginfile);
System.setProperty("sun.security.krb5.debug", "true");
final LoginContext loginContext = new LoginContext(module);
// attempt to login
loginContext.login();
// output some info
System.out.println("Subject=" + loginContext.getSubject());
// logout
loginContext.logout();
System.out.println("Connection test successful.");
jaas.conf:
com.sun.comcurity.jgss.krb5.initiate {
com.sun.comcurity.auth.module.Krb5LoginModule required
doNotPrompt=true
principal="HTTP/servername.domain.com#CORP1.AD1.COMPANY.NET"
useKeyTab=true
keyTab="/opt/app/tomcat/ssoad1/servername.domain.com.keytab"
storeKey=true;
};
com.sun.security.jgss.krb5.accept {
com.sun.security.auth.module.Krb5LoginModule required
doNotPrompt=true
principal="HTTP/servername.domain.com#CORP1.AD1.COMPANY.NET"
useKeyTab=true
keyTab="/opt/app/tomcat/ssoad1/servername.domain.com.keytab"
storeKey=true
useTicketCache=true
isInitiator=true
refreshKrb5Config=true
moduleBanner=true
storePass=true;
};
spnego-client {
com.sun.security.auth.module.Krb5LoginModule required;
};
spnego-server {
com.sun.security.auth.module.Krb5LoginModule required
principal="HTTP/servername.domain.com#CORP1.AD1.COMPANY.NET"
useKeyTab=true
keyTab="/opt/app/tomcat/ssoad1/servername.domain.com.keytab"
storeKey=true
useTicketCache=true
isInitiator=false
refreshKrb5Config=true
moduleBanner=true
;
};
krb5.ini
[libdefaults]
default_realm = CORP1.AD1.COMPANY.NET
default_keytab_name = FILE:/opt/app/tomcat/ssoad1/servername.domain.com.keytab
default_tkt_enctypes = rc4-hmac
default_tgs_enctypes = rc4-hmac
forwardable = true
renewable = true
noaddresses = true
clockskew = 300
udp_preference_limit = 1
[realms]
CORP1.AD1.COMPANY.NET = {
kdc = ndcr001k.corp1.ad1.company.net:88
default_domain = domain.com
}
[domain_realm]
.domain.com = CORP1.AD1.COMPANY.NET
from net.sourceforge.spnego.SpnegoAuthenticator.java
SpnegoAuthenticator.LOCK.lock();
try {
LOGGER.fine("create context");
LOGGER.fine("serverCredentials="+this.serverCredentials.toString());
context = SpnegoAuthenticator.MANAGER.createContext(this.serverCredentials);
context.requestCredDeleg(true);
LOGGER.fine("clientModuleName="+clientModuleName.toString());
LOGGER.fine("context.getCredDelegState()="+context.getCredDelegState());
token = context.acceptSecContext(gss, 0, gss.length); // When I understand right : gss contains the token from the authorized client (IE Windows user)
LOGGER.fine("token="+token);
LOGGER.fine("context.getDelegCred()="+context.getDelegCred());
} finally {
SpnegoAuthenticator.LOCK.unlock();
}
creates the following Exception:
javax.servlet.ServletException: GSSException: No valid credentials provided
net.sourceforge.spnego.SpnegoHttpFilter.doFilter(SpnegoHttpFilter.java:287)
Root Cause
GSSException: No valid credentials provided
sun.security.jgss.krb5.Krb5Context.getDelegCred(Krb5Context.java:511)
sun.security.jgss.GSSContextImpl.getDelegCred(GSSContextImpl.java:614)
sun.security.jgss.spnego.SpNegoContext.getDelegCred(SpNegoContext.java:1064)
sun.security.jgss.GSSContextImpl.getDelegCred(GSSContextImpl.java:614)
net.sourceforge.spnego.SpnegoAuthenticator.doSpnegoAuth(SpnegoAuthenticator.java:503)
As recommended I answer my own question:
First make sure to have delegation allowed in active directory for the user that is listed in the keytab file. For delegation we add a full qualified hostname and a username under which the service is running on the second sever (delegation target server - here windows).
AD Delegation tab
The AD admins should know how to create a keytab file and hand it over to you.
Create a jaas.conf and a krb5.ini file like described in the question.
Use the library from http://spnego.sourceforge.net/.
Add the filter to the web.xml:
<filter>
<filter-name>SpnegoHttpFilter</filter-name>
<filter-class>net.sourceforge.spnego.SpnegoHttpFilter</filter-class>
....
....
<init-param>
<param-name>spnego.allow.delegation</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SpnegoHttpFilter</filter-name>
<url-pattern>/sc2</url-pattern>
</filter-mapping>
with all needed init parameters like described on the library web page and the filter mapping of course.
Start Tomcat with the following options:
CATALINA_OPTS="-Dsun.security.krb5.debug=[true|false]
-Djava.security.auth.login.config=/opt/app/tomcat/ssoad1/jaas.conf
-Djava.security.krb5.conf=/opt/app/tomcat/ssoad1/krb5.ini
-Djavax.security.auth.useSubjectCredsOnly=false"
The last option
-Djavax.security.auth.useSubjectCredsOnly=false
is very important - without that it doesn't work. That is not mentioned on the spnego.sourceforge.net website.
And then the magic really works:
The application acts as a http client with the credentials from the user who called the application from the browser.
private void doServiceCall(HttpServletRequest request, StringBuilder sb) throws GSSException, MalformedURLException, PrivilegedActionException, IOException {
if (request instanceof DelegateServletRequest) {
DelegateServletRequest dsr = (DelegateServletRequest) request;
GSSCredential creds = dsr.getDelegatedCredential();
if (null == creds) {
sb.append("No delegated creds.");
} else {
sb.append(creds.getName().toString());
SpnegoHttpURLConnection spnego =
new SpnegoHttpURLConnection(creds);
HttpURLConnection con = spnego.connect(new URL("https://server.domain.com/ServiceFactory/servicenamexyz/Get?KeyConditionValue=ACTION_OUTPUT"));
sb.append("<br />HTTP Status Code: " + spnego.getResponseCode());
sb.append("<br />HTTP Status Message: " + spnego.getResponseMessage());
String contentType = con.getContentType();
sb.append("<br />HTTP Content Type: " + contentType);
StringBuilder result = new StringBuilder();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
while ((line = reader.readLine()) != null) {
result.append(line);
}
reader.close();
sb.append("<br />HTTP Content: " + result.toString());
spnego.disconnect();
}
} else {
sb.append("Request not a delegate.");
}
br(sb);
}

Why not serialize my protobuf?

my widgets.proto:
option java_package = "example";
option java_outer_classname = "WidgetsProtoc";
message Widget {
required string name = 1;
required int32 id = 2;
}
message WidgetList {
repeated Widget widget = 1;
}
my rest: (Path: /widgets)
#GET
#Produces("application/x-protobuf")
public WidgetsProtoc.WidgetList getAllWidgets() {
Widget widget1 =
Widget.newBuilder().setId(1).setName("testing").build();
Widget widget2 =
Widget.newBuilder().setId(100).setName("widget 2").build();
System.err.println("widgets1: " + widget1);
System.err.println("widgets2: " + widget2);
WidgetsProtoc.WidgetList list = WidgetsProtoc.WidgetList.newBuilder().addWidget(widget1).addWidget(widget2).build();
System.err.println("list: " + list.toByteArray());
return list;
}
And when i use postman i get this response:
(emptyline)
(emptyline)
testing
(emptyline)
widget 2d
This is normal? I think not really...in my messagebodywriter class i override writeto like this:
#Override
public void writeTo(WidgetsProtoc.WidgetList widgetList, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
entityStream.write(widgetList.toByteArray());
}
I thought its good to send a byte array...but it is a little bit weird that not serialize nothing....or just my be the id...Thanks for the help :)
It already is in binary.
Have you noticed that you message consists of name and id but you get only name displayed?
That's what I get displayed when I just save the response as a "test" file from the browser's GET call:
$ cat test
testing
widget 2d%
You can see cat being barely able to display it, but there is more content into it.
Now if I open it in bless (hex editor in ubuntu) I can get more out of it
0A 0B 0A 07 74 65 73 74 69 6E 67 10 01 0A 0C 0A 08 77 69 64 67 65 74 20 32 10 64
In general I wouldn't trust Postman to display binary data. You need to know how to decode it, and apparently Postman doesn't.
And here's the final proof:
$ cat test | protoc --decode_raw
1 {
1: "testing"
2: 1
}
1 {
1: "widget 2"
2: 100
}

unable to read modbus data using jamod 2( wimpi.modbus )

i am new to java serial port programming​,
I have trying to read data from the modbus slave device through modbus RTU over serial port from my java application.
I am using the Jamod java library to read modbus protocal.
In my case my application failed to receive entire modbus response from the device. please find my java coding and error log for your reference.
Any one can suggest me what may be the reason for the error.
<br/>
**ERROR**<br/>
Clear input: 02 c2 c1<br/>
Sent: 01 04 03 e8 00 05 b0 79 <br/>
Last request: 01 04 03 e8 00 05 b0 79<br/>
CRC Error in received frame: 0 bytes: <br/>
Response: 01 84 <br/>
net.wimpi.modbus.ModbusIOException: I/O exception - failed to read<br/>
at net.wimpi.modbus.io.ModbusRTUTransport.readResponse(ModbusRTUTransport.java:163)<br/>
at net.wimpi.modbus.io.ModbusSerialTransaction.execute(ModbusSerialTransaction.java:187)<br/>
at modbusnewapplication.ModbusConnection.main(ModbusConnection.java:8<br/>
Modbus Program <br/>
---------------<br/>
package modbusnewapplication;<br/>
import java.io.;<br/>
import javax.comm.;<br/>
import net.wimpi.modbus.ModbusCoupler;<br/>
import net.wimpi.modbus.io.ModbusSerialTransaction;<br/>
import net.wimpi.modbus.msg.ReadInputRegistersRequest;<br/>
import net.wimpi.modbus.msg.ReadInputRegistersResponse;<br/>
import net.wimpi.modbus.net.SerialConnection;<br/>
import net.wimpi.modbus.util.SerialParameters;<br/>
public class ModbusConnection {<br/>
public static void main(String[] args) {<br/>
// if (args.length < 4) {<br/>
// System.out.println("not enough args");<br/>
// System.exit(1);<br/>
// }else{<br/>
try {<br/>
System.out.println("Serial Port Connection");<br/><br/>
/* The important instances of the classes mentioned before */<br/>
SerialConnection con = null; //the connection<br/>
ModbusSerialTransaction trans = null; //the transaction<br/>
ReadInputRegistersRequest req = null; //the request<br/>
ReadInputRegistersResponse res = null; //the response<br/>
// **1 Variables for storing the parameters** <br/>
String portname= "COM1"; //the name of the serial port to be used<br/>
int unitid = 1; //the unit identifier we will be talking to<br/>
int ref = 1000; //the reference, where to start reading from<br/>
int count = 5; //the count of IR's to read<br/>
int repeat = 1; //a loop for repeating the transaction <br/>
boolean isopen = false;<br/><br/>
**// 2. Set master identifier**
// ModbusCoupler.createModbusCoupler(null);
// ModbusCoupler.getReference().setMaster(master); I added this in
// ModbusCoupler.getReference().setMaster(true);
// ModbusCoupler.getReference().setUnitID(1);
**// 3. Setup serial parameters**<br/>
SerialParameters params = new SerialParameters();<br/>
params.setPortName("COM1");<br/>
params.setBaudRate(9600);<br/>
params.setDatabits(8);<br/>
params.setParity("None");<br/>
params.setStopbits(1);<br/>
params.setEncoding("RTU");<br/>
params.setEcho(false);<br/>
System.setProperty("net.wimpi.modbus.debug", "true");<br/>
**// 4. Open the connection**<br/>
con = new SerialConnection(params);
System.out.println("Connection..." + con.toString());
con.open();
isopen = con.isOpen();<br/>
System.out.println("Serial port status..." + isopen);<br/>
**// 5. Prepare a request<br/>**
req = new ReadInputRegistersRequest(ref, count);<br/>
req.setUnitID(unitid);<br/>
req.setHeadless();<br/>
**// 6. Prepare a transaction<br/>**
trans = new ModbusSerialTransaction(con);<br/>
trans.setRequest(req);<br/>
**// 7. Execute the transaction repeat times<br/>**
int k = 0;<br/>
do { <br/>
trans.execute();<br/>
res = (ReadInputRegistersResponse) trans.getResponse();<br/>
for (int n = 0; n < res.getWordCount(); n++) {<br/>
System.out.println("Word " + n + "=" + res.getRegisterValue(n));<br/>
}<br/>
k++;<br/>
} while (k < repeat);<br/>
**// 8. Close the connection**<br/>
con.close();<br/>
} catch (Exception ex) {<br/>
ex.printStackTrace();<br/>
}<br/>
//}//else<br/>
}//main
}
You should add a method like Thread.sleep(500) between request and response method in jamod library. Maybe this kind of error is usually caused by the length of response data.
jamod library didn't consider the long size of response data. So, we need to request and wait until all data is received from the serial interface. Ff not, because all data is not received, CRC check will fail and cause an error.

Categories

Resources