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

Support for authentication with client certificates #656

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

D-r-P-3-p-p-3-r
Copy link

Added possibility to authenticate via client certificates (including configuration of CA certificates).

@D-r-P-3-p-p-3-r
Copy link
Author

D-r-P-3-p-p-3-r commented Sep 12, 2024

@microsoft-github-policy-service agree company="Seadex GmbH"

@jw-msft
Copy link
Contributor

jw-msft commented Dec 6, 2024

Using EIS (edge identity service) to retrieve the x509 certs is already a supported way to use them for individual and group DPS enrollments. If I am interpreting correctly, what is the reasoning to support yet another way of doing it directly? Is it because you do not want to use EIS?

You can see here that we request the certificate from EIS unix domain server:

EISErr certResult = RequestCertificateFromEIS(certId, timeoutMS, &certResponseStr);

Specifically, we are using https://azure.github.io/iot-identity-service/ with further details here: https://azure.github.io/iot-identity-service/api/identity-service.html

The reason device update agent uses EIS is for separating and offloading the concerns of securely handling the certicates where EIS can use a TPM or HSM, etc.

@D-r-P-3-p-p-3-r
Copy link
Author

Using EIS (edge identity service) to retrieve the x509 certs is already a supported way to use them for individual and group DPS > enrollments. If I am interpreting correctly, what is the reasoning to support yet another way of doing it directly?

Yes.

Is it because you do not want to use EIS?

Yes, because it is rather heavy-weight for our environment.
Building DUA and all its dependencies for an ARM-based system with a custom Linux (ptxdist-baked) was already very time-consuming. It will be very time-consuming to maintain in the future as well and we'll have to repeat this step in parts for DUA updates. Not only might DUA change, but so might its dependencies.

If we do the same with EIS it's even more binaries to build, configure, and maintain.

Side note:
It wasn't a lot of big issues, but DUA wasn't even 32-bit compatible out of the box.

Also, the system is constrained in memory and flash storage. It's not a tiny embedded controller. But adding a bunch of services just to do certificate-based authentication (which works without them) is not justified.

The reason device update agent uses EIS is for separating and offloading the concerns of securely handling the certicates where > EIS can use a TPM or HSM, etc.

This is already implemented for our device and works just fine with OpenSSL out of the box. You don't point to a key file in the filesystem, but you use a "special" path to the key in the secure storage.

Thus, there is no benefit for us. But a lot of overhead. Especially, because the device software already uses the Azure IoT C SDK and uses the "direct way" to authenticate using the same key material. It works well and it's completely secure.

Copy link
Contributor

@jw-msft jw-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. A couple of minor comments.

if (x509File)
{
if (fseek(x509File, 0, SEEK_END) == 0) {
bufferLength = (size_t)ftell(x509File);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat pedantic but how about handling when ftell returns -1 and Log_Error the errno. I suppose it would handle mostly the off chance when x509File is not a regular file (such as ESPIPE)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

bytesRead = fread(buffer, 1, bufferLength, x509File);
if (bytesRead < bufferLength)
{
Log_Error("Failed to read '%s'!", x509_file_path);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could eof and error be distinguished and also log the return value from bytesRead? Something like this:

if (bytesRead < bufferLength)
{
    if (feof(x509File)) {
        Log_Error("Unexpected end of file while reading '%s', bytesRead: %zu", x509_file_path, bytesRead);
    } else if (ferror(x509File)) {
        Log_Error("Error occurred while reading '%s', bytesRead: %zu", x509_file_path, bytesRead);
    }
    free(buffer);
    buffer = NULL;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@D-r-P-3-p-p-3-r D-r-P-3-p-p-3-r force-pushed the feature/pr_client_certificates branch from 9ed2796 to 41ec48b Compare January 10, 2025 12:44
@D-r-P-3-p-p-3-r
Copy link
Author

D-r-P-3-p-p-3-r commented Jan 10, 2025

Updated the PR with the suggest changes.

Copy link
Contributor

@jw-msft jw-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly style comments, some of which can be fixed by running clang-format.

if (connection_x509_cert_file_path || connection_x509_private_key_file_path || connection_x509_ca_cert_file_path) {
if(connection_x509_cert_file_path && connection_x509_private_key_file_path && connection_x509_ca_cert_file_path) {
agent->x509Cert = ADUC_AgentInfo_Read_X509_File(connection_x509_cert_file_path);
if (!agent->x509Cert)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the code base uses explicit conditions regarding check for NULL values, so please change to:

if (agent->x509Cert == NULL)

The same for lines 221 and 226.

FILE* x509File = NULL;

x509File = fopen(x509_file_path, "rb");
if (x509File)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use explicit checks for NULL / non-NULL:

if (x509File != NULL)

bytesRead = fread(buffer, 1, bufferLength, x509File);
if (bytesRead < bufferLength)
{
if (feof(x509File))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 95 here has an if-statement that is 7 levels deep.
Let's keep the nesting of if-statements shallow by using the following pattern that:

  • guarantees usually just 1-level of nesting, and usually at most 2-3 nesting depth in practice
  • Centralizes all clean-up code after 'done' label that in general avoids doing so in multiple nested error branches (not the case here but in general it lowers the cognitive load), similar in function to Zig or Odin's defer statement (or c++ RAII) but can control exactly when it happens:
bool success = false;
...
if (x509File == NULL)
{
    Log_Error("Failed to open '%s'!", x509_file_path);
    goto done;
}

if (fseek(x509File, 0, SEEK_END) != 0)
{
    Log_Error("Failed to seek end of '%s'!", x509_file_path);
    goto done;
}

...
...
    success = true;
done:
    if (!success)
    {
        free(buffer);
        buffer = NULL;
    }

    if (x509File != NULL)
    {
        fclose(x509File);
    }

    return buffer;
}

x509File = fopen(x509_file_path, "rb");
if (x509File)
{
if (fseek(x509File, 0, SEEK_END) == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style consistency issue: Opening brace should be on next line

Touch every file in the current set of commits, git add them to git staging, and then run scripts/clang-format.sh. see:

if [[ $0 != "${BASH_SOURCE[0]}" ]]; then

Longer-term, we need to add clang-format.sh (and some other things) to the git pre-commit hook. Currently, the existing git pre-commit hook only runs sh-format.sh (

if ! "$GITROOT/scripts/sh-format.sh" -v; then
)

Log_Error("Failed to open '%s'!", x509_file_path);
}

return(buffer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: no parens for return

Log_Error("Failed to seek end of '%s'!", x509_file_path);
}

if(fclose (x509File) != 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: no space between function name and arg list (should be caught by clang-format)

{
Log_Error("Unexpected end of file while reading '%s' (bytesRead: %zu)!", x509_file_path, bytesRead);
}
else if (ferror(x509File)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paren on next line

}

if(fclose (x509File) != 0) {
Log_Warn("Failed to close '%s'!", x509_file_path);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omit. no need to log when fclose fails as it's not really actionable and the log trace might not even be able to flush to disk in that case (things are in a pretty bad state)

Log_Error("Failed to seek end of '%s'!", x509_file_path);
}

if(fclose (x509File) != 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would prefer this to be in centralized cleanup area, such as after 'done:' label (along with free(buffer) if in error condition).

{
Log_Error("Failed to read '%s' (bytesRead: %zu)!", x509_file_path, bytesRead);
}
free(buffer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for future maintainability, this should be done unconditionally at the end, if in "not succeeded" state.

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.

2 participants