RADIUS AND EAP calculating the Message-Authenticator - c#

I have been reading through RFC 3579 as I am implementing a RADIUS service that supports EAP-MD5 authentication. Unfortunately I am a little unsure how to interpret the RFC, particularly when trying to calculate the Message-Authenticator.
I basically create an HMAC-MD5 object (I am using C#) use the shared secret of the NAS for the key and concatenate Type (one byte) + Identifier (one byte) + Length (two bytes) + Request Authenticator (16 bytes) + All Attributes (Except the Message-Authenticator in the Access-Request) but the calculated value does not match the value in the packet.
Following the RFC this seems correct. Am I interpreting the RFC correctly?
Here is the code:
RadiusPacket packet = Objects.Packet;
byte[] toHMAC;
toHMAC = new byte[1] { (byte)packet.Code };
toHMAC = ByteArray.Combine(toHMAC, new byte[1] { packet.Identifier });
// reversed to match endian of packet
toHMAC = ByteArray.Combine(toHMAC, ByteArray.Reverse(packet.LengthAsBytes));
toHMAC = ByteArray.Combine(toHMAC, packet.Authenticator);
for (int i = 0; i < packet.Attributes.Length; i++)
{
if (packet.Attributes[i].Type != RadiusAttributeType.MessageAuthenticator)
{
toHMAC = ByteArray.Combine(toHMAC, packet.Attributes[i].RawData);
}
}
HMACMD5 md5 = new HMACMD5(Encoding.ASCII.GetBytes(Objects.NAS.SharedSecret));
// this DOES NOT match what is in the received packet...
byte[] hmac = md5.ComputeHash(toHMAC);
Any help would be much appreciated.

I found the answer by a combination of re-reading the RFC and looking at some source code in JQuery. Here is what I found for anybody else that has the same is
The RFC (3579) reads:
'When the message integrity check is calculated the signature string should be considered to be sixteen octets of zero.'
Upon recieiving the Access-Request packet I replaced the existing Message-Authenticator with 16 zero bytes then HMAC-MD5 the entire packet and compare the calculated value with the Message-Authenticator in the packet.
The code is much simpler (I created a test packet from a wireshark capture):
// a radius-eap packet captured from wireshark
RadiusPacket packet = new RadiusPacket(ByteArray.FromHex("017600ad375be8f596e90bcffc5e32929d14275b04060a3e01ee05060000c3513d060000000f011f686f73742f64727377696e377472616379702e6472736c2e636f2e756b1e1330302d31322d30302d45332d34312d43311f1342342d39392d42412d46322d38412d44360606000000020c06000005dc4f240200002201686f73742f64727377696e377472616379702e6472736c2e636f2e756b5012c93ef628690a578b31709b0bbccade41"));
// identical packet that I can zero out MA for testing
RadiusPacket radiusPacketCopy = new RadiusPacket(ByteArray.FromHex("017600ad375be8f596e90bcffc5e32929d14275b04060a3e01ee05060000c3513d060000000f011f686f73742f64727377696e377472616379702e6472736c2e636f2e756b1e1330302d31322d30302d45332d34312d43311f1342342d39392d42412d46322d38412d44360606000000020c06000005dc4f240200002201686f73742f64727377696e377472616379702e6472736c2e636f2e756b5012c93ef628690a578b31709b0bbccade41"));
// zero out MA
radiusPacketCopy.ZeroMessageAuthenticator();
// hash it up
HMACMD5 md5 = new HMACMD5(Encoding.ASCII.GetBytes("mykey"));
byte[] hmac = md5.ComputeHash(radiusPacketCopy.RawPacket);
// the message authenticator MUST be correct
if (!ByteArray.AreEqual(hmac, packet.MessageAuthenticator))
{
// etc

Your code is close, but not quite there. You are stripping out the Message-Authenticator attribute completely.
Instead, it should remain in its original position within the packet, but the 16-byte value field of that attribute should be over-written with zeros.

Related

C# BitConverter.GetBytes() padding is incorrect?

I am working on writing my own DNS server in .net core. I'm at the stage where I am encoding the response payload to send back, and the schema shows that most of the numbers are encoded as 16 bit numbers. C#'s ints are 32 bit numbers. Not a big deal, I'm just dropping off the remaining 16 bits from the front of the number I have no problem with that.
I was doing this by hand until I discovered the System.BitConverter class. I tried using it, however, and the results I came up with were reversed of what it came up with.
For example:
using System;
var myInt = 15;
byte[] data = new byte[2];
data[0] = (byte)(myInt >> 8);
data[1] = (byte)(myInt & 255);
var myIntStr = "";
foreach(var b in data)
{
myIntStr += System.Convert.ToHexString(new byte[]{ b });
myIntStr += " ";
}
Console.WriteLine(myIntStr);
var myShort = System.Convert.ToInt16(myInt);
byte[] data2 = System.BitConverter.GetBytes(myShort);
myIntStr = "";
foreach(var b in data2)
{
myIntStr += System.Convert.ToHexString(new byte[]{ b });
myIntStr += " ";
}
Console.WriteLine(myIntStr);
This code produces the following result:
00 0F
0F 00
It's my understanding that 000F is 15 where as 0F00 is 3840. Am I not understanding bit shifting correctly? I literally just started working with actual bits last night lol.
Thanks for reading this and thanks in advance for your help!
As per the comments on the Question, the answer resides in Endianness.
Network byte order sent from the dig command I am using to test with uses Big Endian order. However, my CPU architecture is Small Endian.
Dotnet behind the scenes in their UDPClient class reverses the bytes if your system is Small Endian when sending bytes, and vice verse when receiving bytes. But because I was creating the bytes by hand using bit shifting in the Big Endian format, they were then reversed to be in Non-Network Byte order while everything else was in Network Byte order.
The solution here is to either have conditional logic to test if your system is IsLittleEndian According to the Microsoft dotnet docs, or let the System.BitConverter class handle it for you.
For instance: in my above example I was trying to convert a 32 bit int into a 16 bit unsigned bit. I ended up replacing the above code with:
public static byte[] IntTo16Bit(int input)
{
ushort input16;
if (!UInt16.TryParse(input.ToString(), out input16))
{
throw new Exception($"Input was {input}");
}
if (BitConverter.IsLittleEndian)
{
return BitConverter.GetBytes(input16).Reverse().ToArray();
}
return BitConverter.GetBytes(input16);
}
and plan on better handling when the i32 cannot be converted into a u16.

Roundtrip Unicode conversion returns different Byte[] array

I'm tinkering with RSA signing of data.
I'm using a plaintext string, which i convert to byte array. i then generate private certificate, sign the byte array and then generate public key.
next i'm using the same byte array to verify the signature.
but i want to convert signature, in between steps, to the string - idea is to append it later on to the file that's being signed.
static void TestSigning(string privateKey)
{
string data = "TEST_TEST-TEST+test+TEst";
Console.WriteLine("==MESSAGE==");
Console.WriteLine(data);
byte[] dataByte = Encoding.Unicode.GetBytes(data);
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(privateKey);
var publicKey = rsa.ToXmlString(false);
byte[] signature = rsa.SignData(dataByte, CryptoConfig.MapNameToOID("SHA512"));
string signatureString = Encoding.Unicode.GetString(signature);
byte[] roundtripSignature = Encoding.Unicode.GetBytes(signatureString);
Console.WriteLine("==TEST==");
Console.WriteLine(signature.Length.ToString());
Console.WriteLine(roundtripSignature.Length.ToString());
using (var checkRSA = new RSACryptoServiceProvider())
{
checkRSA.FromXmlString(publicKey);
bool verification = checkRSA.VerifyData(
dataByte,
CryptoConfig.MapNameToOID("SHA512"),
roundtripSignature);
Console.WriteLine("==Verification==");
Console.WriteLine(verification.ToString());
Console.ReadKey();
}
}
}
now here's the fun part
if i use UTF8 encoding i get byte arrays of different length
256 is the original size
484 is the roundtrip
UTF7 returns different sizes too
256 vs 679
both ASCII and Unicode return proper sizes 256 vs 256.
i've tried using
var sb = new StringBuilder();
for (int i = 0; i < signature.Length; i++)
{
sb.Append(signature[i].ToString("x2"));
}
to get the string. I'm then using Encoding.UTF8.GetBytes() method
this time i get the sizes of:
256 vs 512
if i remove the format from toString() i get:
256 vs 670
signature verification alwayas failed.
it works fine if i use 'signature' instead of roundtripSignature.
my question: Why, despite using same encoding type i get different byte arrays and strings? shouldn't this conversion be lossless?
Unicode isn't a good choice because, at minimum, \0, CR, LF, <delete>, <backspace> (and the rest of the control codes) can mess things up. (See an answer about this for Encrypt/Decrypt for more).
As #JamesKPolk said, you need to use a suitable binary-to-text encoding. Base64 and hex/Base16 are the most common, but there are plenty of other viable choices.

Identifying socket messages

I have a code snippet below that process a socket message, and I would like to know what should be the message sent in order not to result in a return.
Where SocketPacket is a class which stores the received socket, and DataLength would be the length of the received message, dataBuffer stores the message.
int num3;
byte num6 = 0;
SocketPacket workSocket;
int DataLength;
if (workSocket.dataBuffer[0] == 0x33)
{
if (DataLength < 0xbb)
{
return false;
}
for (num3 = 0; num3 < 0xba; num3++)
{
num6 = (byte) (num6 + workSocket.dataBuffer[num3]);
}
// how to get pass this if condition??
if (num6 != workSocket.dataBuffer[0xba])
{
return false;
}
}
So,
What would be the message to send to the server such to get pass the last if condition? (According to my understanding, the message should be at least 187 in length and the first digit should be "3:.........................")
What are the 0xba, 0x33, 0xbb etc....? Hexadecimals? How should I re-construct the input message? Convert these to ASCII? or.... dec? Doesn't make any sense to me.......
I tried to convert workSocket.dataBuffer[0 or 1 or any int] to a readable string. Convert.ToChar(workSocket.dataBuffer[0]) and workSocket.dataBuffer[0].toString() gives different results. Why is that?
Well, what you have there is a fixed-length message (a 187 bytes message). The first byte is a mark to identify the begining of the message then if the first byte is not 0x33 then your code doesn't process the bytes in the buffer.
Next, in the For statement you have a checksum. It is adding all the first 186 bytes in order to compare the result with the last byte (the precalculated checksum). It is to verify the message is okay (and it is useless by the way because protocols warranty the stream/datagram is okey).
So, about your questions:
What would be the message to send to the server such to get pass the last if condition?
Well, you need to send 187-bytes-length message (simply a byte[187]): the first one has to be 0x33, next the content and the last one has to be the checksum (you should calculate in the same way your snippet shows)
[0x33 | THE CONTENT | CHKSUM]
0 1 185 186
For example: the following buffer has a valid message (one that will pass the if condition). It simply begins with the mark byte (0x33) and the next 185 bytes are zero (I didn't assign values) then, the checksum is 0x33 + 0 + 0 + 0 + 0 ... 0 ... = 0x33
var buffer = new byte[187];
buffer[0] = 0x33;
buffer[186] = 0x33;
What are the 0xba, 0x33, 0xbb etc....? Hexadecimals?
Yes, they are just numbers in hexadecimal.
I tried to convert (sic) gives different results. Why is that?
Sockets send/receive bytes (just numbers) but the real question is: why do you assume they have to be text? Probably they are text, yes but who knows. That is part of the agreements (the protocol) that both endpoints agreed and that allows them to exchange data. So, you have to know what those 185 bytes (187 - 1 byte for mark - 1 byte checksum) mean in order to be able to process them.
Now, what you are doing is a reverse engineering of a protocol and that is because it is clear you don't know the message format and I guess you don't know what the content meaning is, and even when you are right and the content is just text, you ignore the encoding used. Those are the things you need to focus on.
I hope this helps you.

received byte never over 127 in serial port

I have a program that sends a stream bytes to eother pc.
The values range from 0 to 255. I set up my serialport like this
sp.BaudRate = 115200;
sp.PortName = "COM53";
sp.DataBits = 8;
sp.StopBits = System.IO.Ports.StopBits.One;
sp.Parity = System.IO.Ports.Parity.None;
sp.ReadTimeout = 0;
sp.Open();
sp.DataReceived += new
System.IO.Ports.SerialDataReceivedEventHandler(sp_ DataReceived);
and then I have this
void sp_DataReceived(object sender,
System.IO.Ports.SerialDataReceivedEventArgs e)
{
string Mystring = sp.ReadExisting();
byte testbyte = 254;
// Gather all the bytes until 102 is reached
foreach (byte c in Mystring)
{
if(pixelcount<102)
pixel[pixelcount] = c;
pixelcount++;
if (c 126)
Console.WriteLine("big number {0}", c);// biggest number ever printed is 127
}
//got all the bytes, now draw them
if (pixelcount == 102)
{
Console.WriteLine("testbyte = {0}", testbyte);
oldx = 0;
pixelcount = 0;
pictureBox_rawData.Invalidate();
}
}
My problem is that "c" is never over 127.
What am I missing here?
i've test all encoding but i can not solve this problem. please help.
thanks
int91h
If you want to get the raw bytes, you should be using SerialPort.Read to read it into a byte array. Using SerialPort.ReadExisting to read the data into a string is going to force a conversion of some kind (i.e. encoding will convert bytes to chars).
In the documentation for SerialPort.Write (Remarks section):
By default, SerialPort uses ASCIIEncoding to encode the characters. ASCIIEncoding encodes all characters greater then 127 as (char)63 or '?'. To support additional characters in that range, set Encoding to UTF8Encoding, UTF32Encoding, or UnicodeEncoding.
Maybe ReadExisting behaves similar and converts every byte greater then 127 to 63.
You are not reading bytes, you are reading text. Which is produced by converting the bytes that the port receives according to the SerialPort.Encoding property value. Which defaults to Encoding.ASCII, an encoding that only has characters for byte values 0 through 127. Byte values out of that range are replaced by the "?" character.
Which explains what you see. Choosing another Encoding is an unlikely solution in your case, use SerialPort.Read() instead. The equivalent of ReadExisting is calling Read() with a sufficiently large count argument. You'll get back whatever fits, the actual number of bytes copied into the buffer is the method return value. It blocks when the input buffer is empty. Which can only happen in the DataReceived event handler when e.EventType is not equal to SerialData.Chars. Not usually a problem.
Beware that your call to pictureBox_rawData.Invalidate() is invalid. DataReceived runs on a threadpool thread. You can only touch control members on the UI thread. You'll need to use Control.BeginInvoke().
Just as what Hans Passant said, you need to use SerialPort.Read().
Something like this would work
'retrieve number of bytes in the buffer
Dim bytes1 As Integer = ComPort.BytesToRead
'create a byte array to hold the awaiting data
Dim comBuffer As Byte() = New Byte(bytes1 - 1) {}
'read the data and store it to comBuffer
ComPort.Read(comBuffer, 0, bytes1)

HMAC-based one time password in C# (RFC 4226 - HOTP)

I am attempting to wrap my brain around generating a 6 digit/character non case sensitive expiring one-time password.
My source is https://www.rfc-editor.org/rfc/rfc4226#section-5
First the definition of the parameters
C 8-byte counter value, the moving factor. This counter
MUST be synchronized between the HOTP generator (client)
and the HOTP validator (server).
K shared secret between client and server; each HOTP
generator has a different and unique secret K.
T throttling parameter: the server will refuse connections
from a user after T unsuccessful authentication attempts.
Then we have the algorithm to generate the HOTP
As the output of the HMAC-SHA-1 calculation is 160 bits, we must
truncate this value to something that can be easily entered by a
user.
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
Then, we have Truncate defined as
String = String[0]...String[19]
Let OffsetBits be the low-order 4 bits of String[19]
Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15
Let P = String[OffSet]...String[OffSet+3]
Return the Last 31 bits of P
And then an example is offered for a 6 digit HOTP
The following code example describes the extraction of a dynamic
binary code given that hmac_result is a byte array with the HMAC-
SHA-1 result:
int offset = hmac_result[19] & 0xf ;
int bin_code = (hmac_result[offset] & 0x7f) << 24
| (hmac_result[offset+1] & 0xff) << 16
| (hmac_result[offset+2] & 0xff) << 8
| (hmac_result[offset+3] & 0xff) ;
I am rather at a loss in attempting to convert this into useful C# code for generating one time passwords. I already have code for creating an expiring HMAC as follows:
byte[] hashBytes = alg.ComputeHash(Encoding.UTF8.GetBytes(input));
byte[] result = new byte[8 + hashBytes.Length];
hashBytes.CopyTo(result, 8);
BitConverter.GetBytes(expireDate.Ticks).CopyTo(result, 0);
I'm just not sure how to go from that, to 6 digits as proposed in the above algorithms.
You have two issues here:
If you are generating alpha-numeric, you are not conforming to the RFC - at this point, you can simply take any N bytes and turn them to a hex string and get alpha-numeric. Or, convert them to base 36 if you want a-z and 0-9. Section 5.4 of the RFC is giving you the standard HOTP calc for a set Digit parameter (notice that Digit is a parameter along with C, K, and T). If you are choosing to ignore this section, then you don't need to convert the code - just use what you want.
Your "result" byte array has the expiration time simply stuffed in the first 8 bytes after hashing. If your truncation to 6-digit alphanumeric does not collect these along with parts of the hash, it may as well not be calculated at all. It is also very easy to "fake" or replay - hash the secret once, then put whatever ticks you want in front of it - not really a one time password. Note that parameter C in the RFC is meant to fulfill the expiring window and should be added to the input prior to computing the hash code.
For anyone interested, I did figure out a way to build expiration into my one time password. The approach is to use the created time down to the minute (ignoring seconds, milliseconds, etc). Once you have that value, use the ticks of the DateTime as your counter, or variable C.
otpLifespan is my HOTP lifespan in minutes.
DateTime current = new DateTime(DateTime.Now.Year, DateTime.Now.Month,
DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 0);
for (int x = 0; x <= otpLifespan; x++)
{
var result = NumericHOTP.Validate(hotp, key,
current.AddMinutes(-1 * x).Ticks);
//return valid state if validation succeeded
//return invalid state if the passed in value is invalid
// (length, non-numeric, checksum invalid)
}
//return expired state
My expiring HOTP extends from my numeric HOTP which has a static validation method that checks the length, ensures it is numeric, validates the checksum if it is used, and finally compares the hotp passed in with a generated one.
The only downside to this is that each time you validate an expiring hotp, your worse case scenario is to check n + 1 HOTP values where n is the lifespan in minutes.
The java code example in the document outlining RFC 4226 was a very straightforward move into C#. The only piece I really had to put any effort into rewriting was the hashing method.
private static byte[] HashHMACSHA1(byte[] keyBytes, byte[] text)
{
HMAC alg = new HMACSHA1(keyBytes);
return alg.ComputeHash(text);
}
I hope this helps anyone else attempting to generate one time passwords.
This snippet should do what you are asking for:
public class UniqueId
{
public static string GetUniqueKey()
{
int maxSize = 6; // whatever length you want
char[] chars = new char[62];
string a;
a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
char[] chars = new char[a.Length];
chars = a.ToCharArray();
int size = maxSize;
byte[] data = new byte[1];
RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
crypto.GetNonZeroBytes(data);
size = maxSize;
data = new byte[size];
crypto.GetNonZeroBytes(data);
StringBuilder result = new StringBuilder(size);
foreach (byte b in data)
{ result.Append(chars[b % (chars.Length - 1)]); }
return result.ToString();
}
}

Categories

Resources