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

Decode objectGUI from binary string. #60

Merged
merged 16 commits into from
Jan 5, 2025

Conversation

fengtan
Copy link
Owner

@fengtan fengtan commented Nov 29, 2024

No description provided.

@fengtan fengtan linked an issue Nov 29, 2024 that may be closed by this pull request
@petarov
Copy link
Contributor

petarov commented Dec 1, 2024

I had a look on this, not a Typescript developer, but I managed to come up with something:

const testPairs = [
    ["5M3HPmBsh0SbNM61wk8yhw==", "e4cdc73e-606c-8744-9b34-ceb5c24f3287"],
    ["TSHX64TMpUe0LxqVJ8Z+2w==", "4d21d7eb-84cc-a547-b42f-1a9527c67edb"],
    ["/fKTMip56kKvF9C+tAnrTg==", "fdf29332-2a79-ea42-af17-d0beb409eb4e"],
];

function decodeBase64ToBinary(base64String: string): Uint8Array {
    const binaryString = atob(base64String);
    const binaryData = new Uint8Array(binaryString.length);
    for (let i = 0; i < binaryString.length; i++) {
        binaryData[i] = binaryString.charCodeAt(i);
    }
    return binaryData;
}

function objectGUIDToUUID(objectGUID: Uint8Array) {
    const dashPos = [4, 6, 8, 10];
    let guid = "";

    for (let i = 0; i < objectGUID.length; i++) {
        if (dashPos.includes(i)) {
            guid += "-";
        }

        guid += objectGUID[i].toString(16).padStart(2, "0");
    }

    return guid;
}

for (let i = 0; i < testPairs.length; i++) {
    const pair = testPairs[i];
    console.log(`Value: ${pair[1]}  Expected: ${objectGUIDToUUID(decodeBase64ToBinary(pair[0]))}`)
}

So this works and the values I used as Base64-encoded binary data are real-life GUIDs from an Entra system on my side.

However, looking at the code on your side I saw that the function accepts a string parameter:

function objectGUIDToUUID(objectGUID: string)

Digging further, I saw that this actually comes from how ldapjs works: https://github.com/ldapjs/attribute/blob/d1cba4723fb6953a932a71b53a8d842eeab564c7/index.js#L88

Then I also see that the value should already be a Base64-encoded string, or at least this is how I understand it: https://github.com/ldapjs/attribute/blob/d1cba4723fb6953a932a71b53a8d842eeab564c7/index.js#L342

To summarize, maybe it's as easy as replacing objectGUIDToUUID(attribute.vals[0]) in your changes with objectGUIDToUUID(decodeBase64ToBinary(attribute.vals[0]))

I realize I should have actually forked the repo instead of writing a novel here. Sorry 😄

@fengtan
Copy link
Owner Author

fengtan commented Dec 16, 2024

@petarov thank you so much for coming up with this implementation! I have incorporated this into the changeset.

Somehow, for me, the objectGUID value shown by Apache Directory Studio is different from that returned by your implementation. However I was able to confirm that the Base64 value matches what ldapsearch returns, and if I convert that Base64 value to a UUID using this tool then I get the same value as your implementation.

Would you be in a position to test the changes? You may find the new .vsix archive here (below is where you can find this URL)

find_vsix_header
find_vsix_body

You may install the .vsix archive locally like this:

install_vsix

By default the attributes below are treated as binary and will be rendered as Base64 (except objectGUID which will be represented as a UUID). If you know about others please let me know:

  • caCertificate
  • jpegPhoto
  • krbExtraData
  • msExchArchiveGUID
  • msExchBlockedSendersHash
  • msExchMailboxGuid
  • msExchSafeSendersHash
  • networkAddress
  • objectGUID
  • objectSid
  • userCertificate
  • userSMIMECertificate

Thanks!

@fengtan fengtan marked this pull request as ready for review December 16, 2024 03:15
@petarov
Copy link
Contributor

petarov commented Dec 17, 2024

@fengtan Looks great! I tested with 2 different Windows AD servers, checking about 5 different users and 3 different groups, and the objectGUID for both users and groups shows as expected on my side. Sadly, I was unable to run ApacheDirectoryStudio to verify your observations. It breaks on my M-Mac and I have not been able to resolve that yet.

Just an understanding question: does it make sense for objectGUID to be in the ldap-explorer.binary-attributes list, since it will always get decoded? I mean if I remove it, there is no behavior difference, as far as I could tell.

Thanks a lot for working on this. Really appreciate it.

@fengtan
Copy link
Owner Author

fengtan commented Dec 18, 2024

I tested with 2 different Windows AD servers, checking about 5 different users and 3 different groups, and the objectGUID for both users and groups shows as expected on my side. Sadly, I was unable to run ApacheDirectoryStudio to verify your observations. It breaks on my M-Mac and I have not been able to resolve that yet.

Thanks for testing! 👍

@gwythan, since you also showed interest in this feature in #33, do you happen to be in a position to test and confirm whether the decoded objectGUID's are correct on your side?

Just an understanding question: does it make sense for objectGUID to be in the ldap-explorer.binary-attributes list, since it will always get decoded? I mean if I remove it, there is no behavior difference, as far as I could tell.

If was not sure how to handle this myself, but, indeed, if ldap-explorer.binary-decode is set to true, then this will take precedence and objectGUID will be rendered as a UUID regardless if whether it is listed in ldap-explorer.binary-attributes or not. If ldap-explorer.binary-decode is set to false, however, it will be rendered as a Base64 string if it is listed in ldap-explorer.binary-attributes (otherwise it will be rendered as a gibberish binary string). I have pushed 5716edb to clarify. I am not sure if there is a better way to manage this.

@petarov
Copy link
Contributor

petarov commented Dec 20, 2024

If was not sure how to handle this myself, but, indeed, if ldap-explorer.binary-decode is set to true, then this will take precedence and objectGUID will be rendered as a UUID regardless if whether it is listed in ldap-explorer.binary-attributes or not. If ldap-explorer.binary-decode is set to false, however, it will be rendered as a Base64 string if it is listed in ldap-explorer.binary-attributes (otherwise it will be rendered as a gibberish binary string).

I forgot to uncheck the ldap-explorer.binary-decode, so I think that's why I was confused. All clear now. 👍

@petarov
Copy link
Contributor

petarov commented Dec 20, 2024

Somehow, for me, the objectGUID value shown by Apache Directory Studio is different from that returned by your implementation. However I was able to confirm that the Base64 value matches what ldapsearch returns, and if I convert that Base64 value to a UUID using this tool then I get the same value as your implementation.

@fengtan I need to come back on this, because I finally managed to run and test with ADS and you are right that the guid values differ. I read some more about this and now I think I know why.

Apparently the objectGUID can be stored either in little-endian or big-endian order on the directory server. Microsoft AD stores it in little-endian order and OpenLDAP for example in big-endian order. Most LDAP clients including ADS display the text representation as UUID strings in big-endian order.

So this is where the discrepancy comes from. My implementation does not take this into account and displays the UUID string in little-endian order instead of big-endian like ADS. On my side it looked ok, because I used an internal tool that does not convert to big-endian order and I also just got Microsoft AD servers here, so I never noticed this.

The implementation needs to be adjusted, but I still wonder how to determine the original order of the objectGUID value, maybe from the Buffer type? 🤔 I mean if it's an OpenLDAP server I guess bigEndian needs to be left to false below and for the common case of Microsoft AD it needs to be set to true.

function objectGUIDToUUID(objectGUID: Uint8Array, bigEndian = false): string {
    const dashPos = [4, 6, 8, 10];
    let guid = "";

    const objectGUIDOrdered = bigEndian ? [
        ...Array.from(objectGUID.slice(0, 4).reverse()),
        ...Array.from(objectGUID.slice(4, 6).reverse()),
        ...Array.from(objectGUID.slice(6, 8).reverse()),
        ...Array.from(objectGUID.slice(8, 16)),
    ] : objectGUID;

    for (let i = 0; i < objectGUIDOrdered.length; i++) {
        if (dashPos.includes(i)) {
            guid += "-";
        }

        guid += objectGUIDOrdered[i].toString(16).padStart(2, "0");
    }

    return guid;
}

@fengtan
Copy link
Owner Author

fengtan commented Dec 23, 2024

Wow amazing, thank you so much for finding out about this @petarov!

Indeed if I reorder the bytes using the algorithm you suggested (ecd3992) then the values are identical to those of Apache Directory Studio (I use Active Directory myself).

I don't see a way to automatically detect whether an LDAP server runs on Microsoft, or which type of endianness a Buffer object is using. That being said objectGUID attributes seem to be used only in Active Directory (https://ldapwiki.com/wiki/Wiki.jsp?page=ObjectGUID) so I wonder if it is worth supporting implementations that may store it in big-endian (?)

@petarov
Copy link
Contributor

petarov commented Dec 23, 2024

You're right, objectGUID should be just a Microsoft AD specific property. Can't believe I somehow forgot about that, heh. Ok, in this case I think it's safe to assume it's always little-endian needed to be convert to big-endian before it gets shown as a guid string.

That being said I just remembered that I had to also deal with Google AD earlier this year and they do use the by-the-spex EntryUUID. I think no conversion is needed there, but will need to double check.

Actually, authenticating via a JKS file instead of bind user/pass would be a nice feature to add, since that's what Google prefer, but let's leave that for another issue.

@fengtan fengtan merged commit fc78dd3 into master Jan 5, 2025
3 checks passed
@fengtan fengtan deleted the 33-feature-decode-active-directory-server-guids branch January 5, 2025 02:22
@fengtan
Copy link
Owner Author

fengtan commented Jan 5, 2025

Merged - thanks again @petarov!

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

Successfully merging this pull request may close these issues.

FEATURE: Decode Active Directory server GUIDs
2 participants