Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Order of the EAP AKA' attributes #592

Open
Z0827 opened this issue Dec 28, 2022 · 5 comments
Open

Order of the EAP AKA' attributes #592

Z0827 opened this issue Dec 28, 2022 · 5 comments

Comments

@Z0827
Copy link

Z0827 commented Dec 28, 2022

I am using free5gc with EAP-AKA'. The UERANSIM can not verify the AT_MAC, so I debugged it.

It seems like the class EapAttributes is represented by a structure std::array<std::optional<OctetString>, 256> attributes{};, where the index denotes the attribute's type. With this data structure, let us go through the EncodeEapPdu.

void eap::EncodeEapPdu(OctetString &stream, const eap::Eap &pdu)
{
    int initialLength = stream.length();

    stream.appendOctet((int)pdu.code);
    stream.appendOctet(pdu.id);

    if (pdu.eapType == EEapType::NO_TYPE)
    {
        stream.appendOctet2(4);
    }
    else
    {
        stream.appendOctet2(0);
        stream.appendOctet((int)pdu.eapType);

        if (pdu.eapType == EEapType::EAP_AKA_PRIME)
        {
            auto &akaPrime = (const EapAkaPrime &)pdu;
            stream.appendOctet(static_cast<int>(akaPrime.subType));
            stream.appendOctet2(0);

            akaPrime.attributes.forEachEntry([&stream](EAttributeType key, const OctetString &value) {
                stream.appendOctet((int)key);
                stream.appendOctet((value.length() + 2) / 4);
                stream.append(value);
            });
        }
        else if (pdu.eapType == EEapType::NOTIFICATION)
            stream.append(((const EapNotification &)pdu).rawData);
        else if (pdu.eapType == EEapType::IDENTITY)
            stream.append(((const EapIdentity &)pdu).rawData);

        octet2 realLength = octet2{stream.length() - initialLength};
        stream.data()[initialLength + 2] = realLength[0];
        stream.data()[initialLength + 3] = realLength[1];
    }
}

In this function, the EAP object is encoded into OctetString. However, the order of the attributes is encoded in a way that ordered by their attribute indexes. For example, if the EAP object possesses AT_RAND(1), AT_AUTN(2), AT_KDF(24), AT_KDF_INPUT(23), and AT_MAC(11), they must be ordered as AT_RAND(1), AT_AUTN(2), AT_MAC(11), AT_KDF_INPUT(23), AT_KDF(24).

We then look at the function which calculates the expected AT_MAC

OctetString CalculateMacForEapAkaPrime(const OctetString &kaut, const eap::EapAkaPrime &message)
{
    // Create a copy of the EAP message
    eap::EapAkaPrime copy{message.code, message.id, message.subType};

    // Deep copy each attribute
    message.attributes.forEachEntry(
        [&copy](eap::EAttributeType attr, const OctetString &v) { copy.attributes.putRawAttribute(attr, v.copy()); });

    // Except the MAC field
    copy.attributes.putMac(OctetString::FromSpare(16));

    OctetString input{};
    eap::EncodeEapPdu(input, copy);
    

    auto sha = crypto::HmacSha256(kaut, input);
    return sha.subCopy(0, 16);
}

In this manner, the OctetString input is assembled with ordered attributes, and thus the result of SHA is the result of an ordered OctetString.

However, stated by RFC 4187, this may cause issues.

Unless otherwise specified, the order of the attributes in an EAP-AKA message is insignificant, and an EAP-AKA implementation should not assume a certain order will be used.

In fact, it did cause issues for me. The free5gc would not encode attributes in an ordered way, and thus the AT_MAC check failed.

@Z0827
Copy link
Author

Z0827 commented Dec 29, 2022

Without considering the performance, I debugged the code with a fast and simple method. I added a vector<EAttributes> to record the order of the incoming EAP message and made some other corresponding changes to eap.cpp, eap.hpp, keys.cpp, and auth.cpp. It works pretty well. But the AT_MAC check still don't work. Thus I debugged more.

With limited debugging info from free5gc, it took me a long time to find out the issue. The issue is with the OctetString CalculateMk(const OctetString &ckPrime, const OctetString &ikPrime, const Supi &supiIdentity) method within the keys.cpp.

OctetString CalculateMk(const OctetString &ckPrime, const OctetString &ikPrime, const Supi &supiIdentity)
{
    OctetString key = OctetString::Concat(ikPrime, ckPrime);
    OctetString input = OctetString::FromAscii("EAP-AKA'" + supiIdentity.type + "-" + supiIdentity.value);

    // Calculating the 208-octet output
    return crypto::CalculatePrfPrime(key, input, 208);
}

Take a look on OctetString input = OctetString::FromAscii("EAP-AKA'" + supiIdentity.type + "-" + supiIdentity.value);. As defined by RFC, this input should be in the form of:

MK = PRF'(IK'|CK',"EAP-AKA'"|Identity)

Which is slightly different from the code. As a result, the MK and also the K_AUT will be wrong. Since MAC is calculated by K_AUT, the MAC will not be the same as the expected correct one. After changing it to OctetString input = OctetString::FromAscii("EAP-AKA'" /*+ supiIdentity.type + "-" */+ supiIdentity.value);, the issue is solved.

However, there is still other problems regarding the EAP challenge response. The Wireshark turned out that it had no actual contents in it. I am still working on the src code. Will keep updating.

@Z0827
Copy link
Author

Z0827 commented Dec 29, 2022

For reference, I put my first debugging issue here.

I am using the latest free5gc against the UERANSIM with EAP-AKA'. There are two issues until now.

  1. When the UERANSIM is checking the length of RAND, AUTN, and MAC, it always gives out a [semantically incorrect message], and thus I debugged it somehow. The raw RAND, AUTN, and MAC are 18 bytes each after UERANSIM get them out from the stream. The first two bytes, according to RFC, should be reserved 0s. Then UERANSIM calls the function get2I() and compare the result with 16(18-2).
OctetString eap::EapAttributes::getMac() const
{
    auto &val = attributes[(int)EAttributeType::AT_MAC];
    if (!val.has_value() || val->length() < 2){
        return {};
    }
    int len = val->get2I(0);//the problem
    /*
    if (len != val->length() - 2){
    	throw std::runtime_error("? MAC is with bad length if get rid of leading 0s ?");
        return {};
    }
    */
    return val->subCopy(2);
}

I guess the intention for this step is to see if there are 16bytes of RAND, AUTN, and MAC without the reserved 0 and get rid of these 0s at the return step, but the logic seems to be strange. The logic of get2I() probably just get first two bytes together into a uint16_t and seems like the result cannot be 16. I command out those checks and there are no more [semantically incorrect message].

@Z0827
Copy link
Author

Z0827 commented Dec 30, 2022

I debugged a new issue today. The UERANSIM can now run perfectly with free5gc.

The new issue is with the AT_RES. Let's see the code of putRes:

void eap::EapAttributes::putRes(const OctetString &value)
{
    attributes[(int)EAttributeType::AT_RES] = OctetString::Concat(OctetString::FromOctet2(value.length()), value);
}

The code concat the length of the AT_RES in bytes and the value of AT_RES as the final AT_RES. However, according to RFC 4187:

The value field of this attribute begins with the 2-byte RES Length, which identifies the exact length of the RES in bits. The RES length is followed by the AKA RES parameter. According to [TS33.105], the length of the AKA RES can vary between 32 and 128 bits. Because the length of the AT_RES attribute must be a multiple of 4 bytes, the sender pads the RES with zero bits where necessary.

The length should be in bit length instead of byte length. Add *8 would solve this issue. Also, the RFC 4187 says the AT_RES should pad with 0s to make its length multiple of 4. This is not implemented by the code. A conditional branch would solve this problem.

@oliveiraleo
Copy link
Contributor

Hello @Z0827, I know it's been a long time since you opened that issue here but I'm willing to try to solve it.

I have a working Free5GC instance and UERANSIM works like a charm with 5G-AKA authentication. However, EAP-AKA' isn't working and while trying to find a solution online I found this issue here.

I read your comments above and made some of the changes suggested (as it's possible to see here), but I couldn't figure it out how to translate the suggestions you made on your first message to the code

In this function, the EAP object is encoded into OctetString. However, the order of the attributes is encoded in a way that ordered by their attribute indexes. For example, if the EAP object possesses AT_RAND(1), AT_AUTN(2), AT_KDF(24), AT_KDF_INPUT(23), and AT_MAC(11), they must be ordered as AT_RAND(1), AT_AUTN(2), AT_MAC(11), AT_KDF_INPUT(23), AT_KDF(24).

In this manner, the OctetString input is assembled with ordered attributes, and thus the result of SHA is the result of an ordered OctetString.

However, stated by RFC 4187, this may cause issues.

Unless otherwise specified, the order of the attributes in an EAP-AKA message is insignificant, and an EAP-AKA implementation should not assume a certain order will be used.

In fact, it did cause issues for me. The free5gc would not encode attributes in an ordered way, and thus the AT_MAC check failed.

More specifically on how to record the parameters on the vector.

Without considering the performance, I debugged the code with a fast and simple method. I added a vector<EAttributes> to record the order of the incoming EAP message and made some other corresponding changes to eap.cpp, eap.hpp, keys.cpp, and auth.cpp. It works pretty well. But the AT_MAC check still don't work. Thus I debugged more.

Could you please help me out?

Thanks in advance,
Leo.

edipascale added a commit to edipascale/UERANSIM that referenced this issue Apr 16, 2024
* remove spurious length check when fetching attributes,
  such as in getRand(), getMac() etc.
* fix the AT_RES length (it's in bits, not bytes)
* fix reordering of attributes and MAC computation issues
* fix SUPI input to the master key (remove the supi type prefix)
* use kAusf computation for eap-aka' rather than the 5g-aka one

Inspired in large part by the issues and solutions reported in aligungr#592
@oliveiraleo
Copy link
Contributor

oliveiraleo commented Aug 17, 2024

Hello,

I didn't get notified that this issue was mentioned, that's why it took so long for me to note that progress was made. I could test the new UERANSIM nightly from commit 85a0fbf on free5GC v3.4.2 and I can confirm that now EAP-AKA' works like a charm. Huge thanks @edipascale for that.

For future reference, if you already have UERANSIM running on a previous nightly version or the stable version, follow the steps below to get EAP fixed:

  1. Enter UERANSIM's folder (the folder where the source code is cloned):
cd UERANSIM
  1. Move the version of your source code to the commit where the fixes were merged:
git checkout 85a0fbf
  1. Rebuild UERANSIM
make

Done, enjoy.

So, @Z0827 or @aligungr IMO this issue should be closed as it was resolved by #700

Regards,
Leo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants