From e9f5d7245c67921675710bd6f5166a04e009d58d Mon Sep 17 00:00:00 2001 From: iadgovuser62 <145499407+iadgovuser62@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:18:01 -0500 Subject: [PATCH] Xfer HIRS_Provisioner.NET to main (#663) * Moving HIRS_Provisioner.NET and dotnet_provisioner_unit_tests.yml into main branch * Adding fixed dotnet provisioner workflow * Updated files to match updated master branch, including hirs.sln, Directory.Build.targets, HIRS_Provisioner.NET.csproj, and hirsTest.csproj --- .../dotnet_provisioner_unit_tests.yml | 136 +++++ HIRS_Provisioner.NET/.editorconfig | 201 +++++++ HIRS_Provisioner.NET/hirs.sln | 158 +++++ .../hirs/Directory.Build.targets | 44 ++ .../hirs/HIRS_Provisioner.NET.csproj | 102 ++++ .../hirs/Resources/Product.wxs | 35 ++ .../hirs/Resources/ProvisionerTpm2.proto | 100 ++++ HIRS_Provisioner.NET/hirs/appsettings.json | 38 ++ HIRS_Provisioner.NET/hirs/src/Program.cs | 69 +++ .../hirs/src/client/Client.cs | 130 +++++ .../hirs/src/client/IHirsAcaClient.cs | 53 ++ .../hirs/src/client/TbsWrapper.cs | 179 ++++++ HIRS_Provisioner.NET/hirs/src/config/CLI.cs | 39 ++ .../hirs/src/config/ClientExitCodes.cs | 18 + .../hirs/src/config/Settings.cs | 547 ++++++++++++++++++ .../deviceInfo/ClassicDeviceInfoCollector.cs | 250 ++++++++ .../deviceInfo/IHirsDeviceInfoCollector.cs | 10 + .../hirs/src/provisioner/IHirsProvisioner.cs | 15 + .../hirs/src/provisioner/Provisioner.cs | 296 ++++++++++ .../hirs/src/tpm/CommandTpm.cs | 497 ++++++++++++++++ .../hirs/src/tpm/CommandTpmQuoteResponse.cs | 30 + .../hirs/src/tpm/IHirsAcaTpm.cs | 18 + .../test/settings_test/appsettings.json | 37 ++ .../test/settings_test/component_list | 1 + .../efi/boot/tcg/cert/Mfg.Serial.BASE.cer | 1 + .../cert/platform/Mfg.Serial.ver2.base.cer | 1 + .../boot/tcg/manifest/rim/Mfg.Model.1.rimel | 1 + .../boot/tcg/manifest/rim/Mfg.Model.1.rimpcr | 1 + .../tcg/manifest/swidtag/Mfg.Model.1.swidtag | 1 + .../efi/boot/tcg/other/notread.base.pem | 0 .../efi/boot/tcg/other/notread.delta.cer | 0 .../efi/boot/tcg/other/notread.rimel | 0 .../efi/boot/tcg/other/notread.rimpcr | 0 .../efi/boot/tcg/other/notread.swidtag | 0 .../boot/tcg/pccert/Mfg.Model.ver1.delta.pem | 1 + .../Resources/test/settings_test/event_log | 1 + HIRS_Provisioner.NET/hirsTest/hirsTest.csproj | 38 ++ .../hirsTest/src/client/ClientTests.cs | 53 ++ .../hirsTest/src/config/SettingsTests.cs | 59 ++ .../src/provisioner/ProvisionerTests.cs | 123 ++++ .../tools/pcrextend/pcrextend.csproj | 16 + .../tools/pcrextend/src/Program.cs | 42 ++ .../tools/pcrextend/src/config/CLI.cs | 29 + .../tools/pcrextend/src/tool/PcrExtendTool.cs | 55 ++ .../pcrextend/src/tpm/CommandTpmSimulator.cs | 69 +++ 45 files changed, 3494 insertions(+) create mode 100644 .github/workflows/dotnet_provisioner_unit_tests.yml create mode 100644 HIRS_Provisioner.NET/.editorconfig create mode 100644 HIRS_Provisioner.NET/hirs.sln create mode 100644 HIRS_Provisioner.NET/hirs/Directory.Build.targets create mode 100644 HIRS_Provisioner.NET/hirs/HIRS_Provisioner.NET.csproj create mode 100644 HIRS_Provisioner.NET/hirs/Resources/Product.wxs create mode 100644 HIRS_Provisioner.NET/hirs/Resources/ProvisionerTpm2.proto create mode 100644 HIRS_Provisioner.NET/hirs/appsettings.json create mode 100644 HIRS_Provisioner.NET/hirs/src/Program.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/client/Client.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/client/IHirsAcaClient.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/client/TbsWrapper.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/config/CLI.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/config/ClientExitCodes.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/config/Settings.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/deviceInfo/ClassicDeviceInfoCollector.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/deviceInfo/IHirsDeviceInfoCollector.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/provisioner/IHirsProvisioner.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/provisioner/Provisioner.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/tpm/CommandTpm.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/tpm/CommandTpmQuoteResponse.cs create mode 100644 HIRS_Provisioner.NET/hirs/src/tpm/IHirsAcaTpm.cs create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/appsettings.json create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/component_list create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/cert/Mfg.Serial.BASE.cer create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/cert/platform/Mfg.Serial.ver2.base.cer create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/rim/Mfg.Model.1.rimel create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/rim/Mfg.Model.1.rimpcr create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/swidtag/Mfg.Model.1.swidtag create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.base.pem create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.delta.cer create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.rimel create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.rimpcr create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.swidtag create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/pccert/Mfg.Model.ver1.delta.pem create mode 100644 HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/event_log create mode 100644 HIRS_Provisioner.NET/hirsTest/hirsTest.csproj create mode 100644 HIRS_Provisioner.NET/hirsTest/src/client/ClientTests.cs create mode 100644 HIRS_Provisioner.NET/hirsTest/src/config/SettingsTests.cs create mode 100644 HIRS_Provisioner.NET/hirsTest/src/provisioner/ProvisionerTests.cs create mode 100644 HIRS_Provisioner.NET/tools/pcrextend/pcrextend.csproj create mode 100644 HIRS_Provisioner.NET/tools/pcrextend/src/Program.cs create mode 100644 HIRS_Provisioner.NET/tools/pcrextend/src/config/CLI.cs create mode 100644 HIRS_Provisioner.NET/tools/pcrextend/src/tool/PcrExtendTool.cs create mode 100644 HIRS_Provisioner.NET/tools/pcrextend/src/tpm/CommandTpmSimulator.cs diff --git a/.github/workflows/dotnet_provisioner_unit_tests.yml b/.github/workflows/dotnet_provisioner_unit_tests.yml new file mode 100644 index 00000000..0149f7a3 --- /dev/null +++ b/.github/workflows/dotnet_provisioner_unit_tests.yml @@ -0,0 +1,136 @@ +name: Dotnet Provisioner Unit Tests + +on: push +env: + DOTNET_VERSION: '6.0' +jobs: + dotnet_provisioner_unit_tests: + name: Restore and Run Unit Tests + continue-on-error: true + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: windows-2022 + - os: ubuntu-20.04 + # - os: windows-2019 Cannot Target windows-2019 because the .NET 6 SDK won't receive security patches for this image + steps: + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - name: Checkout repo + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Restore Project + working-directory: HIRS_Provisioner.NET + run: | + dotnet restore + + - name: Build on Windows + working-directory: HIRS_Provisioner.NET + if: contains(matrix.os, 'windows') + run: | + cd hirs + dotnet build -r win-x64 --configuration Release --no-restore + cd .. + + - name: Build on Ubuntu + working-directory: HIRS_Provisioner.NET + if: contains(matrix.os, 'ubuntu') + run: | + dotnet build --configuration Release --no-restore + + - name: Run Unit Tests and Save Logs - Windows + id: window_result + if: contains(matrix.os, 'windows') && always() + working-directory: HIRS_Provisioner.NET + run: | + $logs = dotnet test /p:PublishSingleFile=false --no-restore -v m + $results = [string]$logs + $results = $results.Contains("Passed!") + if($results) { $results = "Pass" } else { $results = "Fail"} + echo "::set-output name=result::$results" + $logName = "${{matrix.os}}-unit-tests-" + $results + ".log" + New-Item $logName + Set-Content $logName $logs + Get-Content $logName + + - name: Run Unit Tests Ubuntu + if: contains(matrix.os, 'ubuntu') + working-directory: HIRS_Provisioner.NET + run: | + logName="${{matrix.os}}-unit-tests.log" + dotnet test --no-restore -v m > $logName + + - name: Extract Ubuntu Unit Test Results + id: ubuntu_result + if: contains(matrix.os, 'ubuntu') && always() + working-directory: HIRS_Provisioner.NET + run: | + logName="${{matrix.os}}-unit-tests.log" + if grep -rnw $logName -e "Passed\!" ; + then + result="Pass" + else + result="Fail" + fi + echo "::set-output name=result::$result" + more $logName + + - name: Upload Logs Ubuntu + uses: actions/upload-artifact@v2 + if: contains(matrix.os, 'ubuntu') && always() + with: + name: "${{matrix.os}}-unit-tests-${{steps.ubuntu_result.outputs.result}}.log" + path: HIRS_Provisioner.NET/*.log + + - name: Upload Logs Windows + uses: actions/upload-artifact@v2 + if: contains(matrix.os, 'windows') && always() + with: + name: "${{matrix.os}}-unit-tests-${{steps.window_result.outputs.result}}.log" + path: HIRS_Provisioner.NET/*.log + + Evaluator: + name: Evaluate Tests + needs: [dotnet_provisioner_unit_tests] + runs-on: ubuntu-latest + continue-on-error: false + steps: + + - uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Make artifact directory + run: | + mkdir artifacts + + - uses: actions/download-artifact@v3 + with: + path: artifacts + + - name: Determine if a test failed + working-directory: artifacts + run: | + result="" + suffix="-unit-tests-Fail.log" + msg=" OS did not pass all the unit tests." + + # Generate Annotations and Console Output + for file in *.log; do + if [[ "$file" == *"Fail"* ]]; then + title=${file%"$suffix"} + echo "::error title=$title Unit Tests Failed::The $title $msg" + result="Failed" + fi + done + + if [ -n "$result" ] + then + exit 1 + fi \ No newline at end of file diff --git a/HIRS_Provisioner.NET/.editorconfig b/HIRS_Provisioner.NET/.editorconfig new file mode 100644 index 00000000..13fe4924 --- /dev/null +++ b/HIRS_Provisioner.NET/.editorconfig @@ -0,0 +1,201 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = false +csharp_new_line_before_else = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = none +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = false +csharp_preserve_single_line_statements = false + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/HIRS_Provisioner.NET/hirs.sln b/HIRS_Provisioner.NET/hirs.sln new file mode 100644 index 00000000..4a2c8b2a --- /dev/null +++ b/HIRS_Provisioner.NET/hirs.sln @@ -0,0 +1,158 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32421.90 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hirs", "hirs\HIRS_Provisioner.NET.csproj", "{300FF15E-1E10-4586-843D-D652BA40DEE5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E61D6E28-B993-436D-AA88-165857AAEEC0}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hirsTest", "hirsTest\hirsTest.csproj", "{C6458436-D548-428C-B250-23E3084F74FC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "pcrextend", "tools\pcrextend\pcrextend.csproj", "{2D518622-5C95-4180-87CE-9F730B2714AD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|ARM.ActiveCfg = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|ARM.Build.0 = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|ARM64.Build.0 = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|x64.ActiveCfg = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|x64.Build.0 = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|x86.ActiveCfg = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Debug|x86.Build.0 = Debug|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|Any CPU.Build.0 = Release|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|ARM.ActiveCfg = Release|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|ARM.Build.0 = Release|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|ARM64.ActiveCfg = Release|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|ARM64.Build.0 = Release|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|x64.ActiveCfg = Release|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|x64.Build.0 = Release|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|x86.ActiveCfg = Release|Any CPU + {300FF15E-1E10-4586-843D-D652BA40DEE5}.Release|x86.Build.0 = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|ARM.Build.0 = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|ARM64.Build.0 = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|x64.Build.0 = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Debug|x86.Build.0 = Debug|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|Any CPU.Build.0 = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|ARM.ActiveCfg = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|ARM.Build.0 = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|ARM64.ActiveCfg = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|ARM64.Build.0 = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|x64.ActiveCfg = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|x64.Build.0 = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|x86.ActiveCfg = Release|Any CPU + {C6458436-D548-428C-B250-23E3084F74FC}.Release|x86.Build.0 = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|ARM.ActiveCfg = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|ARM.Build.0 = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|ARM64.Build.0 = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|x64.Build.0 = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|x86.ActiveCfg = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Debug|x86.Build.0 = Debug|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|Any CPU.Build.0 = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|ARM.ActiveCfg = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|ARM.Build.0 = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|ARM64.ActiveCfg = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|ARM64.Build.0 = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|x64.ActiveCfg = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|x64.Build.0 = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|x86.ActiveCfg = Release|Any CPU + {2D518622-5C95-4180-87CE-9F730B2714AD}.Release|x86.Build.0 = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|ARM.ActiveCfg = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|ARM.Build.0 = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|ARM64.Build.0 = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|x64.ActiveCfg = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|x64.Build.0 = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|x86.ActiveCfg = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Debug|x86.Build.0 = Debug|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|Any CPU.Build.0 = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|ARM.ActiveCfg = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|ARM.Build.0 = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|ARM64.ActiveCfg = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|ARM64.Build.0 = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|x64.ActiveCfg = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|x64.Build.0 = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|x86.ActiveCfg = Release|Any CPU + {08A014E3-3E70-4E8B-9870-5000F3429E9F}.Release|x86.Build.0 = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|ARM.ActiveCfg = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|ARM.Build.0 = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|ARM64.Build.0 = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|x64.ActiveCfg = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|x64.Build.0 = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|x86.ActiveCfg = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Debug|x86.Build.0 = Debug|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|Any CPU.Build.0 = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|ARM.ActiveCfg = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|ARM.Build.0 = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|ARM64.ActiveCfg = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|ARM64.Build.0 = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|x64.ActiveCfg = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|x64.Build.0 = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|x86.ActiveCfg = Release|Any CPU + {34463663-1DEF-46FB-99AC-D8FABB71E7F0}.Release|x86.Build.0 = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|Any CPU.Build.0 = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|ARM.ActiveCfg = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|ARM.Build.0 = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|ARM64.Build.0 = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|x64.ActiveCfg = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|x64.Build.0 = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|x86.ActiveCfg = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Debug|x86.Build.0 = Debug|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|Any CPU.ActiveCfg = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|Any CPU.Build.0 = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|ARM.ActiveCfg = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|ARM.Build.0 = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|ARM64.ActiveCfg = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|ARM64.Build.0 = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|x64.ActiveCfg = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|x64.Build.0 = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|x86.ActiveCfg = Release|Any CPU + {540546BE-36E3-4C3A-B84B-70A4F0393546}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6F78678F-F528-4DE8-BC2A-71FA403D68B8} + EndGlobalSection +EndGlobal diff --git a/HIRS_Provisioner.NET/hirs/Directory.Build.targets b/HIRS_Provisioner.NET/hirs/Directory.Build.targets new file mode 100644 index 00000000..6d1615f5 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/Directory.Build.targets @@ -0,0 +1,44 @@ + + + + false + + + + + /usr/share/hirs + /usr/bin/chmod 644 /usr/share/hirs/appsettings.json; /usr/bin/ln -s /usr/share/hirs/tpm_aca_provision /usr/bin/tpm_aca_provision + rm -f /usr/bin/tpm_aca_provision; rm -rf /usr/share/hirs + + + + + + + + + + $(MSBuildThisFileDirectory)\Resources\Product.wxs + $(NuGetPackageRoot)wix\3.11.2\tools\ + $(WixInstallPath)heat.exe + $(WixInstallPath)candle.exe + $(WixInstallPath)light.exe + + + + + + + + + + + + + + + + diff --git a/HIRS_Provisioner.NET/hirs/HIRS_Provisioner.NET.csproj b/HIRS_Provisioner.NET/hirs/HIRS_Provisioner.NET.csproj new file mode 100644 index 00000000..71bbaed8 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/HIRS_Provisioner.NET.csproj @@ -0,0 +1,102 @@ + + + + Exe + net6.0 + linux-x64;win-x64 + hirs.Program + true + enable + enable + 2.2.0 + + + + + DEBUG;TRACE + 0 + + + + TRACE + 0 + + + + + + + all + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + all + + + + + + + + + + PreserveNewest + true + + + + + + $(ProjectDir)Resources + $(ProjectDir)generated + + + $(protoc_linux64) + $(protoc_linux86) + $(protoc_macosx64) + $(protoc_macosx86) + $(protoc_windows64) + $(protoc_windows86) + + + + + + + + + + + + + + + + + + + + + Always + Always + true + + + + diff --git a/HIRS_Provisioner.NET/hirs/Resources/Product.wxs b/HIRS_Provisioner.NET/hirs/Resources/Product.wxs new file mode 100644 index 00000000..49320713 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/Resources/Product.wxs @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HIRS_Provisioner.NET/hirs/Resources/ProvisionerTpm2.proto b/HIRS_Provisioner.NET/hirs/Resources/ProvisionerTpm2.proto new file mode 100644 index 00000000..71996560 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/Resources/ProvisionerTpm2.proto @@ -0,0 +1,100 @@ +syntax = "proto2"; + +package hirs.pb; +option java_package="hirs.attestationca.configuration.provisionerTpm2"; + +message FirmwareInfo { + required string biosVendor = 1; + required string biosVersion = 2; + required string biosReleaseDate = 3; +} + +message HardwareInfo { + required string manufacturer = 1; + required string productName = 2; + required string productVersion = 3; + required string systemSerialNumber = 4; + repeated ComponentInfo chassisInfo = 5; + repeated ComponentInfo baseboardInfo = 6; + repeated ComponentInfo processorInfo = 7; + repeated ComponentInfo biosOrUefiInfo = 8; + repeated ComponentInfo nicInfo = 9; + repeated ComponentInfo hardDriveInfo = 10; + repeated ComponentInfo memoryInfo = 11; +} + +message ComponentInfo { + required string manufacturer = 1; + required string model = 2; + optional string serialNumber = 3; + optional string revision = 4; +} + +message NetworkInfo { + required string hostname = 1; + required string ipAddress = 2; + required string macAddress = 3; +} + +message OsInfo { + required string osName = 1; + required string osVersion = 2; + required string osArch = 3; + required string distribution = 4; + required string distributionRelease = 5; +} + +message TpmInfo { + required string tpmMake = 1; + required string tpmVersionMajor = 2; + required string tpmVersionMinor = 3; + required string tpmRevMajor = 4; + required string tpmRevMinor = 5; +} + +message DeviceInfo { + required FirmwareInfo fw = 1; + required HardwareInfo hw = 2; + required NetworkInfo nw = 3; + required OsInfo os = 4; + optional bytes pcrslist = 5; + repeated bytes logfile = 6; + repeated bytes swidfile = 7; + optional bytes livelog = 8; +} + +message IdentityClaim { + required DeviceInfo dv = 1; + required bytes ak_public_area = 2; + required bytes ek_public_area = 3; + optional bytes endorsement_credential = 4; + repeated bytes platform_credential = 5; + optional string client_version = 6; + optional string paccorOutput = 7; +} + +message TpmQuote { + required string success = 1; +} + +enum ResponseStatus { + PASS = 0; + FAIL = 1; +} + +message IdentityClaimResponse { + optional bytes credential_blob = 1; + optional string pcr_mask = 2; + optional ResponseStatus status = 3 [default = FAIL]; +} + +message CertificateRequest { + required bytes nonce = 1; + optional bytes quote = 2; +} + +message CertificateResponse { + optional bytes certificate = 1; + optional ResponseStatus status = 2 [default = FAIL]; +} + diff --git a/HIRS_Provisioner.NET/hirs/appsettings.json b/HIRS_Provisioner.NET/hirs/appsettings.json new file mode 100644 index 00000000..854d682e --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/appsettings.json @@ -0,0 +1,38 @@ +{ + "auto_detect_tpm": "TRUE", + "aca_address_port": "https://127.0.0.1:8443", + "efi_prefix": "", + "paccor_output_file": "", + "event_log_file": "", + "hardware_manifest_collectors": "paccor_scripts", + + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ], + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "{Message}{NewLine}", + "theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Grayscale, Serilog.Sinks.Console", + "restrictedToMinimumLevel": "Information" + } + }, + { + "Name": "File", + "Args": { + "path": "hirs.log", + "rollingInterval": "Day", + "retainedFileCountLimit": 5 + } + } + ] + } +} \ No newline at end of file diff --git a/HIRS_Provisioner.NET/hirs/src/Program.cs b/HIRS_Provisioner.NET/hirs/src/Program.cs new file mode 100644 index 00000000..c5a67e56 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/Program.cs @@ -0,0 +1,69 @@ +using CommandLine; +using Serilog; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace hirs { + class Program { + public static readonly string VERSION = "17"; + + static async Task Main(string[] args) { + ClientExitCodes result = 0; + try { + Settings settings = Settings.LoadSettingsFromDefaultFile(); + settings.SetUpLog(); + Log.Information("Starting hirs version " + VERSION); + if (!IsRunningAsAdmin()) { + result = ClientExitCodes.NOT_PRIVILEGED; + Log.Warning("The HIRS provisioner is not running as administrator."); + } + settings.CompleteSetUp(); + CLI cli = new(); + Log.Debug("Parsing CLI args."); + ParserResult cliParseResult = + CommandLine.Parser.Default.ParseArguments(args) + .WithParsed(parsed => cli = parsed) + .WithNotParsed(HandleParseError); + + if (cliParseResult.Tag == ParserResultType.NotParsed) { + // Help text requested, or parsing failed. Exit. + Log.Warning("Could not parse command line arguments. Set --tcp --sim, --tcp :, --nix, or --win. See documentation for further assistance."); + } else { + Provisioner p = new(settings, cli); + IHirsAcaTpm tpm = p.ConnectTpm(); + p.UseClassicDeviceInfoCollector(); + result = (ClientExitCodes)await p.Provision(tpm); + Log.Information("----> Provisioning " + (result == 0 ? "successful" : "failed") + "."); + } + } catch (Exception e) { + result = ClientExitCodes.FAIL; + Log.Fatal(e, "Application stopped."); + } + Log.CloseAndFlush(); + + return (int)result; + } + + private static void HandleParseError(IEnumerable errs) { + //handle errors + Log.Error("There was a CLI error: " + errs.ToString()); + } + + private static bool IsRunningAsAdmin() { + bool isAdmin = false; + try { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + WindowsIdentity user = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new(user); + isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator); + } else { + isAdmin = Mono.Unix.Native.Syscall.geteuid() == 0; + } + } catch { } + return isAdmin; + } + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/client/Client.cs b/HIRS_Provisioner.NET/hirs/src/client/Client.cs new file mode 100644 index 00000000..f075ae2c --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/client/Client.cs @@ -0,0 +1,130 @@ +using Google.Protobuf; +using Hirs.Pb; // Imports ProvisionerTpm2.proto (compiled and generated by protobuf) +using Serilog; +using System; +using System.Buffers.Text; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace hirs { + public class Client : IHirsAcaClient { + public static readonly string POST_IDENTITY_CLAIM_PATH = "HIRS_AttestationCA/identity-claim-tpm2/process"; + public static readonly string POST_REQUEST_CERT_TPM2_PATH = "HIRS_AttestationCA/request-certificate-tpm2"; + + private readonly Uri uri; + private static readonly HttpClientHandler handler = new() { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }; // TODO: Overhaul ACA security + private readonly HttpClient client; + + /** + * This method will create an HttpClient that will accept any server certificate. + */ + public Client(string address) { + uri = new(address); + client = new HttpClient(handler); + } + + public Client(string address, HttpClient httpClient) { + uri = new(address); + client = httpClient; + } + + public async Task PostIdentityClaim(IdentityClaim identityClaim) { + MemoryStream stream = new(identityClaim.ToByteArray()); + // serialize to stream + + stream.Seek(0, SeekOrigin.Begin); + Uri full_address = new(uri.AbsoluteUri + POST_IDENTITY_CLAIM_PATH); + // send data via HTTP + StreamContent streamContent = new(stream); + streamContent.Headers.TryAddWithoutValidation("Content-Type", "application/octet-stream"); + streamContent.Headers.TryAddWithoutValidation("Accept", "application/octet-stream, application/json"); + + IdentityClaimResponse icr = null; + try { + Log.Debug("Attempting to send IdentityClaim to " + full_address); + HttpResponseMessage response = await client.PostAsync(full_address, streamContent).ConfigureAwait(continueOnCapturedContext: false); + Log.Debug(response.ToString()); + if (response.StatusCode == HttpStatusCode.OK) { + byte[] contentBytes = await response.Content.ReadAsByteArrayAsync(); + icr = IdentityClaimResponse.Parser.ParseFrom(contentBytes); + Log.Debug("IdentityClaim delivery succeeded."); + } else { + Log.Debug("IdentityClaim delivery failed."); + Log.Debug("Request reason phrase: " + response.ReasonPhrase); + Log.Debug("Request content: " + response.Content); + } + } catch (Exception e) { + Log.Debug(e, "Error during post of the identity claim."); + } + return icr; + } + + public IdentityClaim CreateIdentityClaim(DeviceInfo dv, byte[] akPublicArea, byte[] ekPublicArea, + byte[] endorsementCredential, List platformCredentials, + string paccoroutput) { + IdentityClaim identityClaim = new(); + identityClaim.Dv = dv; + identityClaim.AkPublicArea = ByteString.CopyFrom(akPublicArea); + identityClaim.EkPublicArea = ByteString.CopyFrom(ekPublicArea); + identityClaim.EndorsementCredential = ByteString.CopyFrom(endorsementCredential); + if (platformCredentials != null) { + foreach (byte[] platformCertificate in platformCredentials) { + identityClaim.PlatformCredential.Add(ByteString.CopyFrom(platformCertificate)); + } + } + identityClaim.PaccorOutput = paccoroutput; + + return identityClaim; + } + + public async Task PostCertificateRequest(CertificateRequest certReq) { + MemoryStream stream = new(certReq.ToByteArray()); + // serialize to stream + + stream.Seek(0, SeekOrigin.Begin); + Uri full_address = new(uri.AbsoluteUri + POST_REQUEST_CERT_TPM2_PATH); + // send data via HTTP + StreamContent streamContent = new(stream); + streamContent.Headers.TryAddWithoutValidation("Content-Type", "application/octet-stream"); + streamContent.Headers.TryAddWithoutValidation("Accept", "application/octet-stream, application/json"); + + CertificateResponse cr = null; + try { + Log.Debug("Attempting to send the Certificate Request to " + full_address); + HttpResponseMessage response = await client.PostAsync(full_address, streamContent).ConfigureAwait(continueOnCapturedContext: false); + Log.Debug(response.ToString()); + if (response.StatusCode == HttpStatusCode.OK) { + byte[] contentBytes = await response.Content.ReadAsByteArrayAsync(); + cr = CertificateResponse.Parser.ParseFrom(contentBytes); + Log.Debug("Certificate Response recevied."); + } else { + Log.Debug("Certificate Response failed."); + Log.Debug("Request reason phrase: " + response.ReasonPhrase); + Log.Debug("Request content: " + response.Content); + } + } catch (Exception e) { + Log.Debug(e, "Error during post of the certificate request."); + } + return cr; + } + + public CertificateRequest CreateAkCertificateRequest(byte[] secret, CommandTpmQuoteResponse ctqr) { + CertificateRequest akCertReq = new(); + akCertReq.Nonce = ByteString.CopyFrom(secret); + CommandTpmQuoteResponse.formatQuoteInfoSigForAca(ctqr.quoted, ctqr.signature, out string quoteInfoSigStr); + akCertReq.Quote = ByteString.CopyFromUtf8(quoteInfoSigStr); + //formatPcrValuesForAca(pcrValues, out string pcrValuesStr); + //akCertReq.Pcrslist = ByteString.CopyFromUtf8(pcrValuesStr); + + return akCertReq; + } + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/client/IHirsAcaClient.cs b/HIRS_Provisioner.NET/hirs/src/client/IHirsAcaClient.cs new file mode 100644 index 00000000..837beafe --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/client/IHirsAcaClient.cs @@ -0,0 +1,53 @@ +using Hirs.Pb; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace hirs { + public interface IHirsAcaClient { + /// + /// Send the to the ACA. The claim is delivered + /// asynchronously to the ACA. However, the client will wait for the response. + /// + /// Evidence about the client. + /// The <> from the + /// ACA. The response is wrapped in a Task. + Task PostIdentityClaim(IdentityClaim identityClaim); + /// + /// Send the to the ACA. The request is delivered + /// asynchronously to the ACA. However, the client will wait for the response. + /// + /// The request for a certificate. Should contain evidence from + /// client to enable nonce verification. + /// The <> from the ACA. + /// The response is wrapped in a Task. It will contain a certificate or the reason why + /// the certificate request was rejected. + Task PostCertificateRequest(CertificateRequest certReq); + /// + /// Collect client evidence regarding a Device into an object that can be interpreted by + /// the ACA. + /// + /// Facts about the Device. + /// The public AK retrieved as a TPM2B_PUBLIC. + /// The public EK retrieved as a TPM2B_PUBLIC. + /// The public EK certificate, encoded in DER or + /// PEM. + /// Any platform certificates relevant to the Device, + /// encoded in DER or PEM. + /// Platform Manifest in a JSON format. + /// An object that can be sent to the ACA. + IdentityClaim CreateIdentityClaim(DeviceInfo dv, byte[] akPublicArea, byte[] ekPublicArea, + byte[] endorsementCredential, + List platformCredentials, string paccoroutput); + /// + /// Collect answers to verification requirements regarding a Device into an object that + /// can be interpreted by the ACA. + /// + /// Verification data. + /// TPM Quote data from the client Device. + /// A object that can be sent to the + /// ACA. + CertificateRequest CreateAkCertificateRequest(byte[] secret, CommandTpmQuoteResponse ctqr); + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/client/TbsWrapper.cs b/HIRS_Provisioner.NET/hirs/src/client/TbsWrapper.cs new file mode 100644 index 00000000..5ca41d56 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/client/TbsWrapper.cs @@ -0,0 +1,179 @@ +using Serilog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Text; + +namespace hirs { + class TbsWrapper { + public class NativeMethods { + [DllImport("tbs.dll", CharSet = CharSet.Unicode)] + internal static extern TBS_RESULT + Tbsi_Context_Create( + ref TBS_CONTEXT_PARAMS ContextParams, + ref UIntPtr Context); + + [DllImport("tbs.dll", CharSet = CharSet.Unicode)] + internal static extern TBS_RESULT + Tbsip_Context_Close( + UIntPtr Context); + + [DllImport("tbs.dll", CharSet = CharSet.Unicode)] + internal static extern TBS_RESULT + Tbsi_Get_OwnerAuth( + UIntPtr Context, + [System.Runtime.InteropServices.MarshalAs(UnmanagedType.U4), In] + TBS_OWNERAUTH_TYPE OwnerAuthType, + [System.Runtime.InteropServices.MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3), In, Out] + byte[] OutBuffer, + ref uint OutBufferSize); + + [DllImport("tbs.dll", CharSet = CharSet.Unicode)] + internal static extern TBS_RESULT + Tbsi_Get_TCG_Log( + UIntPtr Context, + [System.Runtime.InteropServices.MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2), In, Out] + byte[] pOutputBuf, + ref uint pOutputBufLen); + } + + public enum TBS_RESULT : uint { + TBS_SUCCESS = 0, + TBS_E_BLOCKED = 0x80280400, + TBS_E_INTERNAL_ERROR = 0x80284001, + TBS_E_BAD_PARAMETER = 0x80284002, + TBS_E_INSUFFICIENT_BUFFER = 0x80284005, + TBS_E_COMMAND_CANCELED = 0x8028400D, + TBS_E_OWNERAUTH_NOT_FOUND = 0x80284015 + } + + public enum TBS_OWNERAUTH_TYPE : uint { + TBS_OWNERAUTH_TYPE_FULL = 1, + TBS_OWNERAUTH_TYPE_ADMIN = 2, + TBS_OWNERAUTH_TYPE_USER = 3, + TBS_OWNERAUTH_TYPE_ENDORSEMENT = 4, + TBS_OWNERAUTH_TYPE_ENDORSEMENT_20 = 12, + TBS_OWNERAUTH_TYPE_STORAGE_20 = 13 + } + + [StructLayout(LayoutKind.Sequential)] + public struct TBS_CONTEXT_PARAMS { + public TBS_CONTEXT_VERSION Version; + public TBS_CONTEXT_CREATE_FLAGS Flags; + } + + public enum TBS_CONTEXT_VERSION : uint { + ONE = 1, + TWO = 2 + } + + public enum TBS_CONTEXT_CREATE_FLAGS : uint { + RequestRaw = 0x00000001, + IncludeTpm12 = 0x00000002, + IncludeTpm20 = 0x00000004, + } + + // This method is only intended to be called from Windows. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")] + public static bool GetOwnerAuthFromOS(out byte[] ownerAuth) { + ownerAuth = Array.Empty(); + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new(identity); + if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) { + Log.Error("GetOwnerAuthFromOS: run the client with Administrator privileges"); + return false; + } + + // open context + TbsWrapper.TBS_CONTEXT_PARAMS contextParams; + UIntPtr tbsContext = UIntPtr.Zero; + contextParams.Version = TbsWrapper.TBS_CONTEXT_VERSION.TWO; + contextParams.Flags = TbsWrapper.TBS_CONTEXT_CREATE_FLAGS.IncludeTpm20; + TbsWrapper.TBS_RESULT result = TbsWrapper.NativeMethods.Tbsi_Context_Create(ref contextParams, ref tbsContext); + + if (result != TbsWrapper.TBS_RESULT.TBS_SUCCESS) { + return false; + } + if (tbsContext == UIntPtr.Zero) { + return false; + } + + // get owner auth size + uint ownerAuthSize = 0; + TbsWrapper.TBS_OWNERAUTH_TYPE ownerType = TbsWrapper.TBS_OWNERAUTH_TYPE.TBS_OWNERAUTH_TYPE_STORAGE_20; + result = TbsWrapper.NativeMethods.Tbsi_Get_OwnerAuth(tbsContext, ownerType, ownerAuth, ref ownerAuthSize); + if (result != TbsWrapper.TBS_RESULT.TBS_SUCCESS && + result != TbsWrapper.TBS_RESULT.TBS_E_INSUFFICIENT_BUFFER) { + ownerType = TbsWrapper.TBS_OWNERAUTH_TYPE.TBS_OWNERAUTH_TYPE_FULL; + result = TbsWrapper.NativeMethods.Tbsi_Get_OwnerAuth(tbsContext, ownerType, ownerAuth, ref ownerAuthSize); + if (result != TbsWrapper.TBS_RESULT.TBS_SUCCESS && + result != TbsWrapper.TBS_RESULT.TBS_E_INSUFFICIENT_BUFFER) { + Log.Debug("Failed to get ownerAuthSize."); + return false; + } + } + // get owner auth itself + ownerAuth = new byte[ownerAuthSize]; + result = TbsWrapper.NativeMethods.Tbsi_Get_OwnerAuth(tbsContext, ownerType, ownerAuth, ref ownerAuthSize); + if (result != TbsWrapper.TBS_RESULT.TBS_SUCCESS) { + Log.Debug("Failed to get ownerAuth."); + return false; + } + + TbsWrapper.NativeMethods.Tbsip_Context_Close(tbsContext); + + return true; + } + + // This method is only intended to be called from Windows. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")] + public static bool GetEventLog(out byte[] eventLog) { + eventLog = Array.Empty(); + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new(identity); + if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) { + Log.Debug("GetEventLog: run the client with Administrator privileges"); + return false; + } + + // open context + TbsWrapper.TBS_CONTEXT_PARAMS contextParams; + UIntPtr tbsContext = UIntPtr.Zero; + contextParams.Version = TbsWrapper.TBS_CONTEXT_VERSION.TWO; + contextParams.Flags = TbsWrapper.TBS_CONTEXT_CREATE_FLAGS.IncludeTpm12 | TbsWrapper.TBS_CONTEXT_CREATE_FLAGS.IncludeTpm20; + TbsWrapper.TBS_RESULT result = TbsWrapper.NativeMethods.Tbsi_Context_Create(ref contextParams, ref tbsContext); + + if (result != TbsWrapper.TBS_RESULT.TBS_SUCCESS) { + return false; + } + if (tbsContext == UIntPtr.Zero) { + return false; + } + + // Two calls needed + // First gets the log size + uint eventLogSize = 0; + Log.Debug("Attempting to get the event log size from Tbsi."); + result = TbsWrapper.NativeMethods.Tbsi_Get_TCG_Log(tbsContext, eventLog, ref eventLogSize); + if (result != TbsWrapper.TBS_RESULT.TBS_SUCCESS && + result != TbsWrapper.TBS_RESULT.TBS_E_INSUFFICIENT_BUFFER) { + Log.Debug("Failed to get eventLogSize."); + return false; + } + // Second gets the log + Log.Debug("Attempting to get the event log from Tbsi."); + eventLog = new byte[eventLogSize]; + result = TbsWrapper.NativeMethods.Tbsi_Get_TCG_Log(tbsContext, eventLog, ref eventLogSize); + if (result != TbsWrapper.TBS_RESULT.TBS_SUCCESS) { + Log.Debug("Failed to get eventLog."); + return false; + } + + TbsWrapper.NativeMethods.Tbsip_Context_Close(tbsContext); + + return true; + } + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/config/CLI.cs b/HIRS_Provisioner.NET/hirs/src/config/CLI.cs new file mode 100644 index 00000000..33168c88 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/config/CLI.cs @@ -0,0 +1,39 @@ +using CommandLine; + +namespace hirs { + public class CLI { + [Option("tcp", SetName="type", Default = false, HelpText = "Connect to the TPM by IP. Use the format ip:port. By default will connect to " + CommandTpm.DefaultSimulatorNamePort + ".")] + public bool Tcp { + get; set; + } + + [Option("win", SetName = "type", Default = false, HelpText = "Connect to a Windows TPM device.")] + public bool Win { + get; set; + } + + [Option("nix", SetName = "type", Default = false, HelpText = "Connect to a Linux TPM device.")] + public bool Nix { + get; set; + } + + [Option("sim", Default = false, HelpText = "Notify the program of intent to connect to a TPM simulator.")] + public bool Sim { + get; set; + } + + [Option("ip", Default = CommandTpm.DefaultSimulatorNamePort, HelpText = "IP of the TPM Device. Use the format ip:port.")] + public string Ip { + get; set; + } + + [Option("replaceAK", Default = false, HelpText = "Clear any existing hirs AK and create a new one.")] + public bool ReplaceAK { + get; set; + } + + public static string[] SplitArgs(string argString) { + return argString.SplitArgs(true); + } + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/config/ClientExitCodes.cs b/HIRS_Provisioner.NET/hirs/src/config/ClientExitCodes.cs new file mode 100644 index 00000000..1e5912e5 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/config/ClientExitCodes.cs @@ -0,0 +1,18 @@ + +namespace hirs { + public enum ClientExitCodes { + SUCCESS = 0, // Full successful program completion + FAIL = 1, // Unknown/Generic failure resulting in exit + USER_ERROR = 20, // Generic user error + MISSING_CONFIG = 21, // Config file missing + ACA_UNREACHABLE = 22, // Nothing found at the address specified + NOT_PRIVILEGED = 23, // Client not run as root + EXTERNAL_APP_ERROR = 40, // Generic external application error + TPM_ERROR = 41, // Encountered error with the TPM, log the TPM Return Code + HW_COLLECTION_ERROR = 42, // Encountered error when gathering hardware details + PROVISIONING_ERROR = 60, // Generic provisioning error | + PASS_1_STATUS_FAIL = 61, + PASS_2_STATUS_FAIL = 62, + MAKE_CREDENTIAL_BLOB_MALFORMED = 63 // The TPM2_MakeCredential blob was not correct + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/config/Settings.cs b/HIRS_Provisioner.NET/hirs/src/config/Settings.cs new file mode 100644 index 00000000..d9bd28dc --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/config/Settings.cs @@ -0,0 +1,547 @@ +using HardwareManifestPlugin; +using HardwareManifestPluginManager; +using Microsoft.Extensions.Configuration; +using Serilog; +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +namespace hirs { + public class Settings { + public enum Options { + paccor_output_file, + aca_address_port, + efi_prefix, + auto_detect_tpm, + event_log_file, + hardware_manifest_collectors, + hardware_manifest_collection_swid_enforced, + linux_bios_vendor_file, + linux_bios_version_file, + linux_bios_date_file, + linux_sys_vendor_file, + linux_product_name_file, + linux_product_version_file, + linux_product_serial_file + } + + private static readonly string DEFAULT_SETTINGS_FILE = "appsettings.json"; + private static readonly string EFI_ARTIFACT_PATH_COMPAT = "/boot/tcg/"; + private static readonly string EFI_ARTIFACT_PATH = "/EFI/tcg/"; + private static readonly string EFI_ARTIFACT_LINUX_PREFIX = "/boot/efi"; + + private readonly string settingsFile; + private readonly IConfiguration configFromSettingsFile; + + // Storage of options collected from the settingsFile, with some default values + public virtual string paccor_output { + get; private set; + } + public virtual Uri aca_address_port { + get; private set; + } + public string efi_prefix { + get; private set; + } + public bool auto_detect_tpm { + get; private set; + } + public virtual byte[] event_log { + get; private set; + } + public virtual string linux_bios_vendor { + get; private set; + } + public virtual string linux_bios_version { + get; private set; + } + public virtual string linux_bios_date { + get; private set; + } + public virtual string linux_sys_vendor { + get; private set; + } + public virtual string linux_product_name { + get; private set; + } + public virtual string linux_product_version { + get; private set; + } + public virtual string linux_product_serial { + get; private set; + } + private List hardwareManifests = new(); + private Dictionary hardware_manifest_collectors_with_args = new(); + private bool hardware_manifest_collection_swid_enforced = false; + + private Settings() : this(Settings.DEFAULT_SETTINGS_FILE) { } + /// + /// + /// The path to the appsettings.json file on the file system. + private Settings(string file) { + settingsFile = file; + configFromSettingsFile = ReadSettingsFile(); + } + + public static Settings LoadSettingsFromDefaultFile() { + return new(DEFAULT_SETTINGS_FILE); + } + /// + /// + /// The path to the settings JSON file on the file system. + public static Settings LoadSettingsFromFile(string path) { + Settings settings = new(path); + return settings; + } + + private static string GetBasePath() { + return AppContext.BaseDirectory; + } + + private IConfiguration ReadSettingsFile() { + string basePath = GetBasePath(); + IConfiguration configuration = new ConfigurationBuilder() + .SetBasePath(basePath) + .AddJsonFile(settingsFile, false, true) + .Build(); + return configuration; + } + + public void SetUpLog() { + Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configFromSettingsFile).CreateLogger(); + Log.Debug("Reading settings file: " + Path.GetFullPath(Path.Combine(GetBasePath(), settingsFile))); + } + + public void CompleteSetUp() { + try { + ConfigureHardwareManifestManagement(); + + IngestPaccorDataFromFile(); + + ParseAcaAddress(); + + CheckAutoDetectTpm(); + + CheckEfiPrefix(); + + IngestEventLogFromFile(); + + StoreCustomDeviceInfoCollectorOptions(); + + } catch (Exception e) { + if (Log.Logger == null) { + Console.WriteLine("Could not set up logging."); + } + Log.Error(e, "Error reading the settings file."); + throw; + } + } + + #region Hardware Manifest + private void ConfigureHardwareManifestManagement() { + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.hardware_manifest_collectors.ToString()])) { + Log.Debug("Configuring Hardware Manifest Plugin Manager"); + string hardware_manifest_collectors = $"{ configFromSettingsFile[Options.hardware_manifest_collectors.ToString()] }"; + hardware_manifest_collectors_with_args = ParseHardwareManifestCollectorsString(hardware_manifest_collectors); + // Collectors are identified by Name. + // Multiple collectors can be identified with a comma delimiter between collector names. + // There is a field in the HardwareManifestPlugin Interface that must match this Name. + // Each Name can be optionally followed by a space and command-line style arguments. + // Those arguments must not break the JSON encoding of the settings file. + // ex: collector1_name -a --b=c,collector2_name,collector3_name + // If SWID enforcement is enabled, Collectors must also pass validation prior to loading. + // Once loaded, the command-line arguments are passed directly to the collector by the Configure method of the Interface. + List names = hardware_manifest_collectors_with_args.Keys.ToList(); + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.hardware_manifest_collection_swid_enforced.ToString()])) { + string hardware_manifest_collection_swid_enforced_str = $"{ configFromSettingsFile[Options.hardware_manifest_collection_swid_enforced.ToString()] }"; + hardware_manifest_collection_swid_enforced = Boolean.Parse(hardware_manifest_collection_swid_enforced_str); + Log.Debug("SWID enforcement of Hardware Manifest Plugins are " + (hardware_manifest_collection_swid_enforced ? "en" : "dis") + "abled in settings."); + } + hardwareManifests = HardwareManifestPluginManagerUtils.LoadPlugins(names, hardware_manifest_collection_swid_enforced); + CleanHardwareManifestCollectors(); + Log.Debug("Finished configuring the Hardware Manifest Plugin Manager."); + } else { + Log.Debug("Hardware Manifest Plugin Manager will not be used. No collectors were identified in settings."); + } + } + + private static Dictionary ParseHardwareManifestCollectorsString(string hardware_manifest_collectors) { + Dictionary dict = new(); + List names = hardware_manifest_collectors.Split(',').Select(s => s.Trim()).ToList(); + foreach (string name in names) { + string[] parts = name.Split(' ', 2); // split on first space + dict.Add(parts[0], parts.Length == 2 ? parts[1] : ""); + } + return dict; + } + + private void CleanHardwareManifestCollectors() { + List names = hardwareManifests.Select(x => x.Name).ToList(); + Dictionary dict = new(); + foreach (string name in names) { + dict.Add(name, hardware_manifest_collectors_with_args[name]); + } + hardware_manifest_collectors_with_args.Clear(); + hardware_manifest_collectors_with_args = dict; + } + + public virtual string RunHardwareManifestCollectors() { + Log.Debug("Gathering data from loaded hardware manifest collectors."); + string manifestJson = ""; + foreach (IHardwareManifest manifest in hardwareManifests) { + try { + Log.Debug(" Configuring " + manifest.Name); + if (hardware_manifest_collectors_with_args.ContainsKey(manifest.Name)) { + manifest.Configure(CLI.SplitArgs(hardware_manifest_collectors_with_args[manifest.Name])); + } + // TODO: Combine JSON Better + // OR Return proto objects + Log.Debug(" Gathering from " + manifest.Name); + manifestJson = string.Join(manifestJson, manifest.GatherHardwareManifestAsJsonString()); + } catch (Exception e) { + Log.Debug($"Problem retrieving hardware manifest from {manifest.Name}.", e.InnerException); + } + } + //TODO: Verify JSON? + return manifestJson; + } + #endregion + + #region Ingest paccor data from file + private void IngestPaccorDataFromFile() { + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.paccor_output_file.ToString()])) { + Log.Debug("Checking location of the paccor output file."); + string paccor_output_path = $"{ configFromSettingsFile[Options.paccor_output_file.ToString()] }"; + if (DoesFileExist(paccor_output_path, out paccor_output_path)) { + if (HasHardwareManifestPlugins()) { + Log.Warning("The settings file specified hardware manifest collectors and a paccor output file. Fresh data is preferred over data from a file. If you want to use the file data, clear the collectors field from the settings file."); + } else { + Log.Debug("Retrieving components from " + Options.paccor_output_file.ToString() + "."); + paccor_output = File.ReadAllText(paccor_output_path); + if (string.IsNullOrWhiteSpace(paccor_output)) { + Log.Warning(Options.paccor_output_file.ToString() + " Paccor output was empty. Cannot perform Platform Attribute validation."); + } else { + Log.Debug("Output file contains:\n" + paccor_output); + } + } + } + } else { + Log.Debug(Options.paccor_output_file.ToString() + " not set in the settings file."); + } + } + #endregion + + #region ACA Address + private void ParseAcaAddress() { + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.aca_address_port.ToString()])) { + Log.Debug("Parsing the ACA Address."); + string aca_address_port_str = $"{ configFromSettingsFile[Options.aca_address_port.ToString()] }"; + if (!string.IsNullOrWhiteSpace(aca_address_port_str)) { + aca_address_port = new Uri(aca_address_port_str); + Log.Debug(" Found " + aca_address_port); + } + } + if (!HasAcaAddress()) { + Log.Error(Options.aca_address_port.ToString() + " not set in the settings file. No HIRS ACA server to talk to. Looking for the format: \"https://:\""); + } + } + #endregion + + #region Auto Detect TPM + private void CheckAutoDetectTpm() { + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.auto_detect_tpm.ToString()])) { + Log.Debug("Checking Auto Detect TPM setting."); + string auto_detect_tpm_str = $"{ configFromSettingsFile[Options.auto_detect_tpm.ToString()] }"; + try { + auto_detect_tpm = Boolean.Parse(auto_detect_tpm_str); + Log.Debug(" Auto Detect TPM is " + (auto_detect_tpm ? "en" : "dis") + "abled."); + } catch (FormatException) { + auto_detect_tpm = false; + Log.Warning(Options.auto_detect_tpm.ToString() + " did not contain a readable true/false setting. Setting to default of false."); + } + } else { + auto_detect_tpm = false; + Log.Debug(Options.auto_detect_tpm.ToString() + " not set in the settings file. Setting to default of false."); + } + } + #endregion + + #region EFI + private void CheckEfiPrefix() { + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.efi_prefix.ToString()])) { + Log.Debug("Checking EFI Prefix setting."); + efi_prefix = $"{ configFromSettingsFile[Options.efi_prefix.ToString()] }"; + if (string.IsNullOrWhiteSpace(efi_prefix)) { // If not explicitly set in appsettings, try to use default EFI location on Linux + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + efi_prefix = EFI_ARTIFACT_LINUX_PREFIX + EFI_ARTIFACT_PATH; + } + } else { + if (!Directory.Exists(efi_prefix)) { + Log.Debug(Options.efi_prefix.ToString() + ": " + efi_prefix + " did not exist."); + efi_prefix = null; + } + } + } + if (efi_prefix == null) { + Log.Warning(Options.efi_prefix.ToString() + " not set in the settings file. Will not attempt to scan for artifacts in EFI."); + } else { + Log.Debug(" Will scan for artifacts in " + efi_prefix); + } + } + + public virtual List gatherPlatformCertificatesFromEFI() { + // According to FIM: EFIPREFIX/boot/tcg/{cert,pccert,platform} + List platformCerts = null; + if (!string.IsNullOrWhiteSpace(efi_prefix)) { + EnumerationOptions enumOpts = new EnumerationOptions(); + enumOpts.MatchCasing = MatchCasing.CaseInsensitive; + enumOpts.RecurseSubdirectories = true; + List files = new List(); + string[] paths = { "cert", "pccert", "platform" }; + foreach (string subdir in paths) { + string path = efi_prefix + EFI_ARTIFACT_PATH + subdir; + if (Directory.Exists(path)) { + files.AddRange(new DirectoryInfo(path).GetFiles("*base*", enumOpts)); + files.AddRange(new DirectoryInfo(path).GetFiles("*delta*", enumOpts)); + } else { + path = efi_prefix + EFI_ARTIFACT_PATH_COMPAT + subdir; + if (Directory.Exists(path)) { + files.AddRange(new DirectoryInfo(path).GetFiles("*base*", enumOpts)); + files.AddRange(new DirectoryInfo(path).GetFiles("*delta*", enumOpts)); + } + } + } + if (files.Count > 0) { // if none found, don't initialize platformCerts + // At least one base platform cert found + platformCerts = new List(); + foreach (FileInfo file in files) { + platformCerts.Add(File.ReadAllBytes(file.FullName)); + Log.Debug("gatherPlatformCertificatesFromEFI: Gathering " + file.FullName); + } + } + } else { + Log.Warning("gatherPlatformCertificatesFromEFI was called without verifying HasEfiPrefix."); + } + Log.Debug("Found " + (platformCerts == null ? 0 : platformCerts.Count) + " platform certs."); + return platformCerts; + } + + public virtual List gatherRIMBasesFromEFI() { + // According to PC Client RIM + List baseRims = null; + + // /boot/tcg/manifest/swidtag Base RIM Files + // + + .swidtag + if (!string.IsNullOrWhiteSpace(efi_prefix)) { + EnumerationOptions enumOpts = new EnumerationOptions(); + enumOpts.MatchCasing = MatchCasing.CaseInsensitive; + enumOpts.RecurseSubdirectories = true; + List files = new List(); + + string[] paths = { "manifest", "swidtag" }; + string ext = "*swidtag"; + foreach (string subdir in paths) { + string path = efi_prefix + EFI_ARTIFACT_PATH + subdir; + if (Directory.Exists(path)) { + files.AddRange(new DirectoryInfo(path).GetFiles(ext, enumOpts)); + } else { + path = efi_prefix + EFI_ARTIFACT_PATH_COMPAT + subdir; + if (Directory.Exists(path)) { + files.AddRange(new DirectoryInfo(path).GetFiles(ext, enumOpts)); + } + } + } + if (files.Count() > 0) { // if none found, don't initialize baseRims + // At least one base platform cert found + baseRims = new List(); + foreach (FileInfo file in files) { + baseRims.Add(File.ReadAllBytes(file.FullName)); + Log.Debug("gatherRIMBasesFromEFI: Gathering " + file.FullName); + } + } + } else { + Log.Warning("gatherRIMBasesFromEFI was called without verifying HasEfiPrefix."); + } + Log.Debug("Found " + (baseRims == null ? 0 : baseRims.Count) + " base RIMs."); + return baseRims; + } + + public virtual List gatherSupportRIMELsFromEFI() { + // According to PC Client RIM + List supportRimELs = null; + // /boot/tcg/manifest/rim Support RIM Files + // + + .rimel + if (!string.IsNullOrWhiteSpace(efi_prefix)) { + EnumerationOptions enumOpts = new EnumerationOptions(); + enumOpts.MatchCasing = MatchCasing.CaseInsensitive; + enumOpts.RecurseSubdirectories = true; + List files = new List(); + + string[] paths = { "manifest", "rim" }; + string ext = "*rimel"; + foreach (string subdir in paths) { + string path = efi_prefix + EFI_ARTIFACT_PATH + subdir; + if (Directory.Exists(path)) { + files.AddRange(new DirectoryInfo(path).GetFiles(ext, enumOpts)); + } else { + path = efi_prefix + EFI_ARTIFACT_PATH_COMPAT + subdir; + if (Directory.Exists(path)) { + files.AddRange(new DirectoryInfo(path).GetFiles(ext, enumOpts)); + } + } + } + if (files.Count() > 0) { // if none found, don't initialize baseRims + // At least one base platform cert found + supportRimELs = new List(); + foreach (FileInfo file in files) { + supportRimELs.Add(File.ReadAllBytes(file.FullName)); + Log.Debug("gatherSupportRIMELsFromEFI: Gathering " + file.FullName); + } + } + } else { + Log.Warning("gatherSupportRIMELsFromEFI was called without verifying HasEfiPrefix."); + } + Log.Debug("Found " + (supportRimELs == null ? 0 : supportRimELs.Count) + " support rimel files."); + return supportRimELs; + } + + public virtual List gatherSupportRIMPCRsFromEFI() { + // According to PC Client RIM + List supportRimPCRs = null; + // /boot/tcg/manifest/rim Support RIM Files + // + + .rimpcr + if (!string.IsNullOrWhiteSpace(efi_prefix)) { + EnumerationOptions enumOpts = new EnumerationOptions(); + enumOpts.MatchCasing = MatchCasing.CaseInsensitive; + enumOpts.RecurseSubdirectories = true; + List files = new List(); + + string[] paths = { "manifest", "rim" }; + string ext = "*rimpcr"; + foreach (string subdir in paths) { + string path = efi_prefix + EFI_ARTIFACT_PATH + subdir; + if (Directory.Exists(path)) { + files.AddRange(new DirectoryInfo(path).GetFiles(ext, enumOpts)); + } else { + path = efi_prefix + EFI_ARTIFACT_PATH_COMPAT + subdir; + if (Directory.Exists(path)) { + files.AddRange(new DirectoryInfo(path).GetFiles(ext, enumOpts)); + } + } + } + if (files.Count() > 0) { // if none found, don't initialize baseRims + // At least one base platform cert found + supportRimPCRs = new List(); + foreach (FileInfo file in files) { + supportRimPCRs.Add(File.ReadAllBytes(file.FullName)); + Log.Debug("gatherSupportRIMPCRsFromEFI: Gathering " + file.FullName); + } + } + } else { + Log.Warning("gatherSupportRIMPCRsFromEFI was called without verifying HasEfiPrefix."); + } + Log.Debug("Found " + (supportRimPCRs == null ? 0 : supportRimPCRs.Count) + " support rimpcr files."); + return supportRimPCRs; + } + #endregion + + #region Ingest Event Log from File + private void IngestEventLogFromFile() { + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.event_log_file.ToString()])) { + Log.Debug("Checking location of the event log."); + string event_log_path = $"{ configFromSettingsFile[Options.event_log_file.ToString()] }"; + if (DoesFileExist(event_log_path, out event_log_path)) { + Log.Debug("Retrieving the Event Log. "); + event_log = File.ReadAllBytes(event_log_path); + if (event_log == null || event_log.Length == 0) { + Log.Warning(Options.event_log_file.ToString() + " The event log was empty."); + } + } + } else { + Log.Debug(Options.event_log_file.ToString() + " not set in the settings file."); + } + + } + #endregion + + #region Store Custom Device Info Collector Options + private void StoreCustomDeviceInfoCollectorOptions() { + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.linux_bios_vendor_file.ToString()])) { + Log.Debug("Custom bios vendor file specified for the Device Info Collector on Linux."); + string path = $"{ configFromSettingsFile[Options.linux_bios_vendor_file.ToString()] }"; + linux_bios_vendor = ReadFileText(path); + } + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.linux_bios_version_file.ToString()])) { + Log.Debug("Custom bios version file specified for the Device Info Collector on Linux."); + string path = $"{ configFromSettingsFile[Options.linux_bios_version_file.ToString()] }"; + linux_bios_version = ReadFileText(path); + } + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.linux_sys_vendor_file.ToString()])) { + Log.Debug("Custom hardware manufacturer file specified for the Device Info Collector on Linux."); + string path = $"{ configFromSettingsFile[Options.linux_sys_vendor_file.ToString()] }"; + linux_sys_vendor = ReadFileText(path); + } + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.linux_product_name_file.ToString()])) { + Log.Debug("Custom hardware product name file specified for the Device Info Collector on Linux."); + string path = $"{ configFromSettingsFile[Options.linux_product_name_file.ToString()] }"; + linux_product_name = ReadFileText(path); + } + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.linux_product_version_file.ToString()])) { + Log.Debug("Custom hardware product version file specified for the Device Info Collector on Linux."); + string path = $"{ configFromSettingsFile[Options.linux_product_version_file.ToString()] }"; + linux_product_version = ReadFileText(path); + } + if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.linux_product_serial_file.ToString()])) { + Log.Debug("Custom hardware product serial file specified for the Device Info Collector on Linux."); + string path = $"{ configFromSettingsFile[Options.linux_product_serial_file.ToString()] }"; + linux_product_serial = ReadFileText(path); + } + } + #endregion + public static bool DoesFileExist(string path, out string out_full_path) { + bool found = false; + out_full_path = ""; + if (!string.IsNullOrWhiteSpace(path)) { + out_full_path = Path.GetFullPath(path); + found = File.Exists(out_full_path); + if (!found) { + Log.Debug(" File identified in settings did not exist: " + out_full_path); + } else { + Log.Debug(" File exists: " + out_full_path); + } + } + return found; + } + + public static string ReadFileText(string path) { + string text = ""; + if (DoesFileExist(path, out string full_path)) { + Log.Debug(" Reading file: " + full_path + "."); + text = File.ReadAllText(full_path); + Log.Debug(" " + (string.IsNullOrWhiteSpace(text) ? "File was empty." : text)); + } + return text; + } + + public bool HasHardwareManifestPlugins() { + return hardwareManifests.Count > 0; + } + public bool HasPaccorOutputFromFile() { + return !string.IsNullOrEmpty(paccor_output); + } + public bool HasAcaAddress() { + return aca_address_port != null; + } + public bool HasEfiPrefix() { + return !string.IsNullOrEmpty(efi_prefix); + } + public bool IsAutoDetectTpmEnabled() { + return auto_detect_tpm; + } + public bool HasEventLogFromFile() { + return event_log != null && event_log.Length > 0; + } + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/deviceInfo/ClassicDeviceInfoCollector.cs b/HIRS_Provisioner.NET/hirs/src/deviceInfo/ClassicDeviceInfoCollector.cs new file mode 100644 index 00000000..27bf6af8 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/deviceInfo/ClassicDeviceInfoCollector.cs @@ -0,0 +1,250 @@ +using Hirs.Pb; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Management; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.IO; +using Serilog; + +namespace hirs { + public class ClassicDeviceInfoCollector : IHirsDeviceInfoCollector { + public static readonly string NOT_SPECIFIED = "Not Specified"; + public static readonly string LINUX_DEFAULT_BIOS_VENDOR_PATH = "/sys/class/dmi/id/bios_vendor"; + public static readonly string LINUX_DEFAULT_BIOS_VERSION_PATH = "/sys/class/dmi/id/bios_version"; + public static readonly string LINUX_DEFAULT_BIOS_DATE_PATH = "/sys/class/dmi/id/bios_date"; + public static readonly string LINUX_DEFAULT_SYS_VENDOR_PATH = "/sys/class/dmi/id/sys_vendor"; + public static readonly string LINUX_DEFAULT_PRODUCT_NAME_PATH = "/sys/class/dmi/id/product_name"; + public static readonly string LINUX_DEFAULT_PRODUCT_VERSION_PATH = "/sys/class/dmi/id/product_version"; + public static readonly string LINUX_DEFAULT_PRODUCT_SERIAL_PATH = "/sys/class/dmi/id/product_serial"; + private readonly Settings? settings; + + public ClassicDeviceInfoCollector() { + settings = null; + } + public ClassicDeviceInfoCollector(Settings settings) { + this.settings = settings; + } + + public static string FileToString(string path, string def) { + string result; + try { + result = File.ReadAllText(path).Trim(); + } catch { + result = def; + } + if (string.IsNullOrWhiteSpace(result)) { + result = def; + } + return result; + } + + public DeviceInfo CollectDeviceInfo(string acaAddress) { + DeviceInfo dv = new(); + dv.Fw = CollectFirmwareInfo(); + dv.Hw = CollectHardwareInfo(); + dv.Nw = CollectNetworkInfo(acaAddress); + dv.Os = CollectOsInfo(); + return dv; + } + + public FirmwareInfo CollectFirmwareInfo() { + FirmwareInfo fw = new(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + ManagementScope myScope = new("root\\CIMV2"); + ManagementObjectSearcher s = new("SELECT * FROM Win32_BIOS"); + fw.BiosVendor = NOT_SPECIFIED; + fw.BiosVersion = NOT_SPECIFIED; + fw.BiosReleaseDate = NOT_SPECIFIED; + foreach (ManagementObject o in s.Get()) { + string manufacturer = (string)o.GetPropertyValue("Manufacturer"); + string version = (string)o.GetPropertyValue("Version"); + string releasedate = (string)o.GetPropertyValue("ReleaseDate"); + fw.BiosVendor = string.IsNullOrEmpty(manufacturer) ? NOT_SPECIFIED : manufacturer; + fw.BiosVersion = string.IsNullOrEmpty(version) ? NOT_SPECIFIED : version; + fw.BiosReleaseDate = string.IsNullOrEmpty(releasedate) ? NOT_SPECIFIED : releasedate; + break; + } + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + if (settings != null) { + if (!string.IsNullOrEmpty(settings.linux_bios_vendor)) { + fw.BiosVendor = settings.linux_bios_vendor.Trim(); + } + if (!string.IsNullOrEmpty(settings.linux_bios_version)) { + fw.BiosVersion = settings.linux_bios_version.Trim(); + } + if (!string.IsNullOrEmpty(settings.linux_bios_date)) { + fw.BiosReleaseDate = settings.linux_bios_date.Trim(); + } + } + if (string.IsNullOrEmpty(fw.BiosVendor)) { + fw.BiosVendor = FileToString(LINUX_DEFAULT_BIOS_VENDOR_PATH, NOT_SPECIFIED); + } + if (string.IsNullOrEmpty(fw.BiosVersion)) { + fw.BiosVersion = FileToString(LINUX_DEFAULT_BIOS_VERSION_PATH, NOT_SPECIFIED); + } + if (string.IsNullOrEmpty(fw.BiosReleaseDate)) { + fw.BiosReleaseDate = FileToString(LINUX_DEFAULT_BIOS_DATE_PATH, NOT_SPECIFIED); + } + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { + // tbd + } else { + // tbd + } + + Log.Debug("Bios Vendor: " + fw.BiosVendor); + Log.Debug("Bios Version: " + fw.BiosVersion); + Log.Debug("Bios Date: " + fw.BiosReleaseDate); + + return fw; + } + + public HardwareInfo CollectHardwareInfo() { + HardwareInfo hw = new(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + ManagementScope myScope = new("root\\CIMV2"); + ManagementObjectSearcher s = new("SELECT * FROM Win32_ComputerSystemProduct"); + hw.Manufacturer = NOT_SPECIFIED; + hw.ProductName = NOT_SPECIFIED; + hw.ProductVersion = NOT_SPECIFIED; + hw.SystemSerialNumber = NOT_SPECIFIED; + foreach (ManagementObject o in s.Get()) { + string vendor = (string)o.GetPropertyValue("Vendor"); + string name = (string)o.GetPropertyValue("Name"); + string version = (string)o.GetPropertyValue("Version"); + string identifyingnumber = (string)o.GetPropertyValue("IdentifyingNumber"); + hw.Manufacturer = string.IsNullOrWhiteSpace(vendor) ? NOT_SPECIFIED : vendor; + hw.ProductName = string.IsNullOrWhiteSpace(name) ? NOT_SPECIFIED : name; + hw.ProductVersion = string.IsNullOrWhiteSpace(version) ? NOT_SPECIFIED : version; + hw.SystemSerialNumber = string.IsNullOrWhiteSpace(identifyingnumber) ? NOT_SPECIFIED : identifyingnumber; + break; + } + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + if (settings != null) { + if (!string.IsNullOrEmpty(settings.linux_sys_vendor)) { + hw.Manufacturer = settings.linux_sys_vendor.Trim(); + } + if (!string.IsNullOrEmpty(settings.linux_product_name)) { + hw.ProductName = settings.linux_product_name.Trim(); + } + if (!string.IsNullOrEmpty(settings.linux_product_version)) { + hw.ProductVersion = settings.linux_product_version.Trim(); + } + if (!string.IsNullOrEmpty(settings.linux_product_serial)) { + hw.SystemSerialNumber = settings.linux_product_serial.Trim(); + } + } + if (string.IsNullOrEmpty(hw.Manufacturer)) { + hw.Manufacturer = FileToString(LINUX_DEFAULT_SYS_VENDOR_PATH, NOT_SPECIFIED); + } + if (string.IsNullOrEmpty(hw.ProductName)) { + hw.ProductName = FileToString(LINUX_DEFAULT_PRODUCT_NAME_PATH, NOT_SPECIFIED); + } + if (string.IsNullOrEmpty(hw.ProductVersion)) { + hw.ProductVersion = FileToString(LINUX_DEFAULT_PRODUCT_VERSION_PATH, NOT_SPECIFIED); + } + if (string.IsNullOrEmpty(hw.SystemSerialNumber)) { + hw.SystemSerialNumber = FileToString(LINUX_DEFAULT_PRODUCT_SERIAL_PATH, NOT_SPECIFIED); + } + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { + // tbd + } else { + // tbd + } + + Log.Debug("System Manufacturer: " + hw.Manufacturer); + Log.Debug("Product Name: " + hw.ProductName); + Log.Debug("Product Version: " + hw.ProductVersion); + Log.Debug("System Serial Number: " + hw.SystemSerialNumber); + + return hw; + } + + public NetworkInfo CollectNetworkInfo(string acaAddress) { + NetworkInfo nw = new(); + + NetworkInterface iface = NetworkInterface + .GetAllNetworkInterfaces() + .Where(nic => nic.OperationalStatus == OperationalStatus.Up && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .FirstOrDefault(); + + nw.MacAddress = iface.GetPhysicalAddress().ToString(); + + nw.ClearIpAddress(); + // First attempt to find local ip by connecting ACA + if (string.IsNullOrWhiteSpace(acaAddress)) { + Uri uri = new(acaAddress); + try { + Socket socket = new(AddressFamily.InterNetwork, SocketType.Dgram, 0); + socket.Connect(uri.Host, uri.Port); + IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint; + nw.IpAddress = endPoint.Address.ToString(); + } catch { + Log.Debug("Not connected to the internet. Trying another search."); + } + } + // Second attempt to find local ip by scanning first interface that is up and not a loopback address + if (!nw.HasIpAddress) { + foreach (UnicastIPAddressInformation ip in iface.GetIPProperties().UnicastAddresses) { + if (ip.Address.AddressFamily == AddressFamily.InterNetwork) { + nw.IpAddress = ip.Address.ToString(); + break; + } + } + } + + // Search for hostname + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + nw.Hostname = Dns.GetHostName().ToLower(); + string domainName = IPGlobalProperties.GetIPGlobalProperties().DomainName; + if (!string.IsNullOrWhiteSpace(domainName)) { + nw.Hostname = nw.Hostname + "." + domainName; + } + } else if (nw.HasIpAddress) { + nw.Hostname = Dns.GetHostEntry(nw.IpAddress).HostName; + } else { + nw.Hostname = Dns.GetHostName(); + } + + + Log.Debug("Network Info IP: " + nw.IpAddress); + Log.Debug("Network Info MAC: " + nw.MacAddress); + Log.Debug("Network Info Hostname: " + nw.Hostname); + + return nw; + } + + public OsInfo CollectOsInfo() { + OsInfo info = new(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + info.OsName = OSPlatform.Windows.ToString(); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + info.OsName = OSPlatform.Linux.ToString(); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { + info.OsName = OSPlatform.OSX.ToString(); + } else { + info.OsName = RuntimeInformation.OSDescription; + } + info.OsVersion = RuntimeInformation.OSDescription; + info.OsArch = RuntimeInformation.OSArchitecture.ToString(); + info.Distribution = RuntimeInformation.FrameworkDescription; + info.DistributionRelease = RuntimeInformation.FrameworkDescription; + + Log.Debug("OS Name: " + info.OsName); + Log.Debug("OS Version: " + info.OsVersion); + Log.Debug("Architecture: " + info.OsArch); + Log.Debug("Distribution: " + info.Distribution); + Log.Debug("Distribution Release: " + info.DistributionRelease); + + return info; + } + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/deviceInfo/IHirsDeviceInfoCollector.cs b/HIRS_Provisioner.NET/hirs/src/deviceInfo/IHirsDeviceInfoCollector.cs new file mode 100644 index 00000000..ad968878 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/deviceInfo/IHirsDeviceInfoCollector.cs @@ -0,0 +1,10 @@ +using Hirs.Pb; +using System; +using System.Collections.Generic; +using System.Text; + +namespace hirs { + public interface IHirsDeviceInfoCollector { + DeviceInfo CollectDeviceInfo(string address); + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/provisioner/IHirsProvisioner.cs b/HIRS_Provisioner.NET/hirs/src/provisioner/IHirsProvisioner.cs new file mode 100644 index 00000000..ae703062 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/provisioner/IHirsProvisioner.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace hirs { + public interface IHirsProvisioner { + void SetSettings(Settings settings); + void SetCLI(CLI cli); + IHirsAcaTpm ConnectTpm(); + void SetClient(IHirsAcaClient clientWithAddress); + void SetDeviceInfoCollector(IHirsDeviceInfoCollector collector); + Task Provision(IHirsAcaTpm tpm); + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/provisioner/Provisioner.cs b/HIRS_Provisioner.NET/hirs/src/provisioner/Provisioner.cs new file mode 100644 index 00000000..0a8f3e40 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/provisioner/Provisioner.cs @@ -0,0 +1,296 @@ +using Google.Protobuf; +using Hirs.Pb; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace hirs { + + public class Provisioner : IHirsProvisioner { + private CLI cli = null; + private Settings settings = null; + private IHirsDeviceInfoCollector deviceInfoCollector = null; + private IHirsAcaClient acaClient = null; + + public Provisioner() { + } + + public Provisioner(Settings settings, CLI cli) { + SetSettings(settings); + SetCLI(cli); + } + + public void SetSettings(Settings settings) { + if (settings == null) { + Log.Error("Unknown error. Settings were supposed to have been parsed."); + } + this.settings = settings!; + } + + public void SetCLI(CLI cli) { + if (cli == null) { + Log.Error("Unknown error. CLI arguments were supposed to have been parsed."); + } + this.cli = cli; + } + + public IHirsAcaTpm ConnectTpm() { + IHirsAcaTpm tpm = null; + // If tpm device type is set on the command line + if (cli.Nix) { + tpm = new CommandTpm(CommandTpm.Devices.NIX); + } else if (cli.Tcp && cli.Ip != null) { + string[] split = cli.Ip.Split(":"); + if (split.Length == 2) { + tpm = new CommandTpm(cli.Sim, split[0], Int32.Parse(split[1])); + Log.Debug("Connected to TPM via TCP at " + cli.Ip); + } else { + Log.Error("ip input should have the format servername:port. The given input was '" + cli.Ip + "'."); + } + } else if (cli.Win) { + tpm = new CommandTpm(CommandTpm.Devices.WIN); + } + + // If command line not set, check if auto detect is enabled + if ((tpm == null) && settings.IsAutoDetectTpmEnabled()) { + Log.Debug("Auto Detect TPM is Enabled. Starting search for the TPM."); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + try { + tpm = new CommandTpm(CommandTpm.Devices.WIN); + Log.Debug("Auto Detect found a WIN TPM Device."); + } catch (Exception) { + Log.Debug("No WIN TPM Device found by auto detect."); + } + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + try { + tpm = new CommandTpm(CommandTpm.Devices.NIX); + Log.Debug("Auto Detect found a Linux TPM Device."); + } catch (Exception) { + Log.Debug("No Linux TPM Device found by auto detect."); + } + } + + // if tpm still null, try set up TcpTpmDevice on sim, catch exception + if (tpm == null) { + try { + string[] split = CommandTpm.DefaultSimulatorNamePort.Split(":"); + tpm = new CommandTpm(true, split[0], Int32.Parse(split[1])); + Log.Debug("Auto Detect found a TPM simulator at " + CommandTpm.DefaultSimulatorNamePort + "."); + } catch (Exception) { + Log.Debug("No TPM simulator found by auto detect."); + } + } + } else if ((tpm != null) && settings.IsAutoDetectTpmEnabled()) { + Log.Debug("Auto detect TPM was enabled in settings, but command line options were also given. Using command line options."); + } + + // If TPM is still not set up, offer help message + if (tpm == null) { + Log.Fatal( + "To connect to a TPM device on Windows, add the command line argument --win\n" + + "To connect to a TPM device on LINUX, add the command line argument --nix\n" + + "To connect to a TPM via TCP, add the command line arguments --tcp
:\n" + + "To connect to a TPM simulator at the default TCP socket of " + CommandTpm.DefaultSimulatorNamePort + ", add the command line arguments --tcp --sim\n" + + "To connect to a TPM simulator at any other socket, add the command line arguments --tcp --sim
:\n"); + } + return tpm; + } + + public void UseBuiltInClient(string addr) { + acaClient = new Client(addr); + } + + public void SetClient(IHirsAcaClient client) { + acaClient = client; + } + + public void UseClassicDeviceInfoCollector() { + deviceInfoCollector = new ClassicDeviceInfoCollector(settings); + } + + public void SetDeviceInfoCollector(IHirsDeviceInfoCollector collector) { + if (collector == null) { + UseClassicDeviceInfoCollector(); + } else { + deviceInfoCollector = collector; + } + } + + public async Task Provision(IHirsAcaTpm tpm) { + ClientExitCodes result = ClientExitCodes.SUCCESS; + if (tpm != null) { + Log.Information("--> Provisioning"); + Log.Information("----> Gathering Endorsement Key Certificate."); + byte[] ekc = tpm.GetCertificateFromNvIndex(CommandTpm.DefaultEkcNvIndex); + if (ekc.Length == 0) { + Log.Information("------> No Endorsement Key Certificate found at the expected index. The ACA may have one uploaded for this TPM."); + } + Log.Debug("Checking EK PUBLIC"); + tpm.CreateEndorsementKey(CommandTpm.DefaultEkHandle); // Will not create key if obj already exists at handle + byte[] ekPublicArea = tpm.ReadPublicArea(CommandTpm.DefaultEkHandle, out byte[] name, out byte[] qualifiedName); + + Log.Information("----> " + (cli.ReplaceAK ? "Creating new" : "Verifying existence of") + " Attestation Key."); + tpm.CreateAttestationKey(CommandTpm.DefaultEkHandle, CommandTpm.DefaultAkHandle, cli.ReplaceAK); + + Log.Debug("Gathering AK PUBLIC."); + byte[] akPublicArea = tpm.ReadPublicArea(CommandTpm.DefaultAkHandle, out name, out qualifiedName); + + List pcs = null, baseRims = null, supportRimELs = null, supportRimPCRs = null; + if (settings.HasEfiPrefix()) { + Log.Information("----> Gathering artifacts from EFI."); + pcs = settings.gatherPlatformCertificatesFromEFI(); + baseRims = settings.gatherRIMBasesFromEFI(); + supportRimELs = settings.gatherSupportRIMELsFromEFI(); + supportRimPCRs = settings.gatherSupportRIMPCRsFromEFI(); + } + + Log.Debug("Setting up the Client."); + Uri acaAddress = settings.aca_address_port; + if (acaClient == null) { + UseBuiltInClient(acaAddress.AbsoluteUri); + } + + Log.Information("----> Collecting device information."); + DeviceInfo dv = deviceInfoCollector.CollectDeviceInfo(acaAddress.AbsoluteUri); + if (baseRims != null) { + foreach (byte[] baseRim in baseRims) { + dv.Swidfile.Add(ByteString.CopyFrom(baseRim)); + } + } + if (supportRimELs != null) { + foreach (byte[] supportRimEL in supportRimELs) { + dv.Logfile.Add(ByteString.CopyFrom(supportRimEL)); + } + } + if (supportRimPCRs != null) { + foreach (byte[] supportRimPCR in supportRimPCRs) { + dv.Logfile.Add(ByteString.CopyFrom(supportRimPCR)); + } + } + + Log.Debug("Gathering hardware component information:"); + string manifest = ""; + if (settings.HasHardwareManifestPlugins()) { + manifest = settings.RunHardwareManifestCollectors(); + } else if (settings.HasPaccorOutputFromFile()) { + manifest = settings.paccor_output; + } else { + Log.Warning("No hardware collectors nor paccor output file were identified."); + } + Log.Debug("Hardware component information that will be sent to the ACA: " + manifest); + + Log.Debug("Gathering the event log."); + byte[] eventLog; + if (settings.HasEventLogFromFile()) { + Log.Debug(" Using the event log identified in settings."); + eventLog = settings.event_log; + } else { + Log.Debug(" Attempting to collect the event log from the system."); + eventLog = tpm.GetEventLog(); + } + + if (eventLog != null) { + Log.Debug("Event log gathered is " + eventLog.Length + " bytes."); + dv.Livelog = ByteString.CopyFrom(eventLog); + } + + Log.Debug("Gathering PCR data from the TPM."); + string pcrsList, pcrsSha1, pcrsSha256; + CommandTpm.FormatPcrValuesForAca(tpm.GetPcrList(Tpm2Lib.TpmAlgId.Sha1), "sha1", out pcrsSha1); + CommandTpm.FormatPcrValuesForAca(tpm.GetPcrList(Tpm2Lib.TpmAlgId.Sha256), "sha256", out pcrsSha256); + pcrsList = pcrsSha1 + pcrsSha256; + Log.Debug("Result of formatting pcr values for the ACA:"); + Log.Debug("\n" + pcrsList); + dv.Pcrslist = ByteString.CopyFromUtf8(pcrsList); + + Log.Debug("Create identity claim"); + IdentityClaim idClaim = acaClient.CreateIdentityClaim(dv, akPublicArea, ekPublicArea, ekc, pcs, manifest); + + Log.Information("----> Sending identity claim to Attestation CA"); + IdentityClaimResponse icr = await acaClient.PostIdentityClaim(idClaim); + Log.Information("----> Received response. Attempting to decrypt nonce"); + if (icr.HasStatus) { + if (icr.Status == ResponseStatus.Pass) { + Log.Debug("The ACA accepted the identity claim."); + } else { + Log.Debug("The ACA did not accept the identity claim. See details on the ACA."); + result = ClientExitCodes.PASS_1_STATUS_FAIL; + return (int)result; + } + } + + byte[] integrityHMAC = null, encIdentity = null, encryptedSecret = null; + if (icr.HasCredentialBlob) { + byte[] credentialBlob = icr.CredentialBlob.ToByteArray(); // look for the nonce + Log.Debug("ACA delivered IdentityClaimResponse credentialBlob " + BitConverter.ToString(credentialBlob)); + int credentialBlobLen = credentialBlob[0] | (credentialBlob[1] << 8); + int integrityHmacLen = (credentialBlob[2] << 8) | credentialBlob[3]; + integrityHMAC = new byte[integrityHmacLen]; + Array.Copy(credentialBlob, 4, integrityHMAC, 0, integrityHmacLen); + int encIdentityLen = credentialBlobLen - integrityHmacLen - 2; + encIdentity = new byte[encIdentityLen]; + Array.Copy(credentialBlob, 4 + integrityHmacLen, encIdentity, 0, encIdentityLen); + // The following offsets are bound tightly to the way makecredential is implemented on the ACA. + int encryptedSecretLen = credentialBlob[134] | (credentialBlob[135] << 8); + encryptedSecret = new byte[encryptedSecretLen]; + Array.Copy(credentialBlob, 136, encryptedSecret, 0, encryptedSecretLen); + Log.Debug("Prepared values to give to activateCredential."); + Log.Debug(" integrityHMAC: " + BitConverter.ToString(integrityHMAC)); + Log.Debug(" encIdentity: " + BitConverter.ToString(encIdentity)); + Log.Debug(" encryptedSecret: " + BitConverter.ToString(encryptedSecret)); + } else { + result = ClientExitCodes.MAKE_CREDENTIAL_BLOB_MALFORMED; + Log.Error("The response from the ACA did not contain a CredentialBlob."); + } + + if (integrityHMAC != null && encIdentity != null && encryptedSecret != null) { + Log.Debug("Executing activateCredential."); + byte[] recoveredSecret = tpm.ActivateCredential(CommandTpm.DefaultAkHandle, CommandTpm.DefaultEkHandle, integrityHMAC, encIdentity, encryptedSecret); + Log.Debug("Gathering quote."); + uint[] selectPcrs = null; + if (icr.HasPcrMask) { + // For now, the ACA will send a comma separated selection of PCRs as a string + try { + selectPcrs = icr.PcrMask.Split(',').Select(uint.Parse).ToList().ToArray(); + } catch (Exception) { + Log.Warning("PcrMask was included in the IdentityClaimResponse, but could not be parsed." + + "Collecting quote over default PCR selection."); + Log.Debug("This PcrMask could not be parsed: " + icr.PcrMask); + } + } + tpm.GetQuote(CommandTpm.DefaultAkHandle, Tpm2Lib.TpmAlgId.Sha256, recoveredSecret, out CommandTpmQuoteResponse ctqr, selectPcrs); + Log.Information("----> Nonce successfully decrypted. Sending attestation certificate request"); + CertificateRequest akCertReq = acaClient.CreateAkCertificateRequest(recoveredSecret, ctqr); + byte[] certificate; + Log.Debug("Communicate certificate request to the ACA."); + CertificateResponse cr = await acaClient.PostCertificateRequest(akCertReq); + Log.Debug("Response received from the ACA regarding the certificate request."); + if (cr.HasStatus) { + if (cr.Status == ResponseStatus.Pass) { + Log.Debug("ACA returned a positive response to the Certificate Request."); + } else { + Log.Debug("The ACA did not return any certificates. See details on the ACA."); + result = ClientExitCodes.PASS_2_STATUS_FAIL; + return (int)result; + } + } + if (cr.HasCertificate) { + certificate = cr.Certificate.ToByteArray(); // contains certificate + Log.Debug("Printing attestation key certificate: " + BitConverter.ToString(certificate)); + } + } else { + result = ClientExitCodes.MAKE_CREDENTIAL_BLOB_MALFORMED; + Log.Error("Credential elements could not be extracted from the ACA's response."); + } + } else { + result = ClientExitCodes.TPM_ERROR; + Log.Error("Could not provision because the TPM object was null."); + } + return (int)result; + } + + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/tpm/CommandTpm.cs b/HIRS_Provisioner.NET/hirs/src/tpm/CommandTpm.cs new file mode 100644 index 00000000..82af26a3 --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/tpm/CommandTpm.cs @@ -0,0 +1,497 @@ +using Serilog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Tpm2Lib; + +namespace hirs { + public class CommandTpm : IHirsAcaTpm { + public enum Devices { + NIX, + TCP, + WIN + } + + /// + /// If using a TCP connection, the default DNS name/IP address for the + /// simulator. + /// + public const string DefaultSimulatorNamePort = "127.0.0.1:2321"; + + public const uint DefaultEkcNvIndex = 0x1c00002; + public const uint DefaultEkHandle = 0x81010001; + public const uint DefaultAkHandle = 0x81010002; + + private readonly Tpm2 tpm; + + private readonly Boolean simulator; + + private List sessionTracking = new List(); + + /** + * For TCP TpmDevices + */ + public CommandTpm(Boolean sim, string ip, int port) { + simulator = sim; + Tpm2Device tpmDevice = new TcpTpmDevice(ip, port); + tpm = TpmSetupByType(tpmDevice); + } + + /** + * For a TPM device on Linux and Windows + */ + public CommandTpm(Devices dev) { + Tpm2Device tpmDevice = null; + switch (dev) { + case Devices.NIX: + // LinuxTpmDevice will first try to connect tpm2-abrmd and second try to connect directly to device + StringWriter writer = new(); + Console.SetOut(writer); + // The LinuxTpmDevice will print to Console/Stdout an error when it cannot find a resource manager + // This will redirect the Console messages + tpmDevice = new LinuxTpmDevice(); + // Reset the Console messages + Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); + break; + case Devices.WIN: + tpmDevice = new TbsDevice(); + break; + default: + Log.Error("Unknown option selected in CommandTpm(Devices) constructor."); + break; + } + tpm = TpmSetupByType(tpmDevice); + } + + public CommandTpm(Tpm2 tpm) { + this.tpm = tpm; + } + + ~CommandTpm() { + if (tpm != null) { + tpm.Dispose(); + } + } + + public byte[] GetCertificateFromNvIndex(uint index) { + Log.Debug("GetCertificateFromNvIndex 0x" + index.ToString("X")); + byte[] certificate = Array.Empty(); + + TpmHandle nvHandle = new(index); + try { + byte[] nvName; // not used for this function. have to collect from NvReadPublic. + NvPublic obj = tpm.NvReadPublic(nvHandle, out nvName); + if (obj != null) { + byte[] indexData = NvBufferedRead(TpmHandle.RhOwner, nvHandle, obj.dataSize, 0); + if (indexData != null) { + certificate = ExtractFirstCertificate(indexData); // the nvIndex could contain random fill around the certificate + if (certificate != null) { + Log.Debug("GetCertificateFromNvIndex: Read: " + BitConverter.ToString(certificate)); + } else { + Log.Debug("GetCertificateFromNvIndex: No certificate found within data at index."); + } + } else { + Log.Debug("GetCertificateFromNvIndex: Could not read any data."); + } + } else { + Log.Debug("GetCertificateFromNvIndex: Nothing found at index: " + DefaultEkcNvIndex); + } + } catch (TpmException e) { + Log.Debug(e, "GetCertificateFromNvIndex TPM error"); + } + return certificate; + } + + private byte[] NvBufferedRead(TpmHandle authHandle, TpmHandle nvIndex, ushort size, ushort offset) { + ushort maxReadSize = 256; + byte[] buffer = new byte[size]; + + ushort ptr = 0; + while (offset < size) { + int q = Math.DivRem(size - offset, maxReadSize, out int r); + ushort sizeToRead = q > 0 ? maxReadSize : (ushort)r; + byte[] block = tpm.NvRead(authHandle, nvIndex, sizeToRead, offset); + Array.Copy(block, 0, buffer, ptr, sizeToRead); + offset += sizeToRead; + ptr += sizeToRead; + } + return buffer; + } + + private static byte[] ExtractFirstCertificate(byte[] data) { + byte[] extracted = null; + + if (data != null) { + // search for first instance of 30 82 + int pos = 0; + bool found = false; + while (pos < (data.Length - 1)) { + if (data[pos] == 0x30) { + if (data[pos + 1] == 0x82) { + found = true; + break; + } + } + pos++; + } + + // find the size of the structure. + // 30 82 means the size will be described in next 2 bytes + // Data from NV should be BIG ENDIAN since 3082 was found in step 1. + int size = 0; + if (found && data.Length > (pos + 3)) { + byte[] sizeBuffer = new byte[2]; + sizeBuffer[0] = data[pos + 3]; + sizeBuffer[1] = data[pos + 2]; + size = 4 + BitConverter.ToInt16(sizeBuffer); // 4 bytes added to final count for pos+3 + } + + // copy the structure to the output buffer + if (size > 0) { + extracted = new byte[size]; + Array.Copy(data, pos, extracted, 0, size); + } + } + + return extracted; + } + + // allows client to access the readpublic function + public TpmPublic ReadPublicArea(uint handleInt, out byte[] name, out byte[] qualifiedName) { + TpmHandle handle = new(handleInt); + TpmPublic obj = null; + name = null; + qualifiedName = null; + try { + obj = tpm.ReadPublic(handle, out byte[] localName, out byte[] localQualifiedName); + name = localName; + qualifiedName = localQualifiedName; + } catch { + // Don't think I need an exception here. Let the calling method throw if needed. + } + return obj; + } + + public static TpmPublic GenerateEKTemplateL1() { + TpmAlgId nameAlg = TpmAlgId.Sha256; + ObjectAttr attributes = ObjectAttr.FixedTPM | ObjectAttr.FixedParent | ObjectAttr.SensitiveDataOrigin | ObjectAttr.AdminWithPolicy | ObjectAttr.Restricted | ObjectAttr.Decrypt; + byte[] auth_policy = { // Template L-1 + 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, + 0xB3, 0xF8, 0x1A, 0x90, 0xCC, 0x8D, + 0x46, 0xA5, 0xD7, 0x24, 0xFD, 0x52, + 0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64, + 0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14, + 0x69, 0xAA + }; + // ASYM: RSA 2048 with NULL scheme, SYM: AES-128 with CFB mode + RsaParms rsa = new(new SymDefObject(TpmAlgId.Aes, 128, TpmAlgId.Cfb), new NullAsymScheme(), 2048, 0); + // unique buffer must be filled with 0 for the EK Template L-1. + byte[] zero256 = new byte[256]; + Array.Fill(zero256, 0x00); + Tpm2bPublicKeyRsa unique = new(zero256); + TpmPublic inPublic = new(nameAlg, attributes, auth_policy, rsa, unique); + return inPublic; + } + + public void CreateEndorsementKey(uint ekHandleInt) { + TpmHandle ekHandle = new(ekHandleInt); + + TpmPublic existingObject; + try { + existingObject = tpm.ReadPublic(ekHandle, out byte[] name, out byte[] qualifiedName); + Log.Debug("EK already exists."); + return; + } catch (TpmException) { + Log.Debug("Verified EK does not exist at expected handle. Creating EK."); + } + + SensitiveCreate inSens = new(); // key password (no params = no key password) + TpmPublic inPublic = CommandTpm.GenerateEKTemplateL1(); + + TpmHandle newTransientEkHandle = tpm.CreatePrimary(TpmRh.Endorsement, inSens, inPublic, + new byte[] { }, new PcrSelection[] { }, out TpmPublic outPublic, + out CreationData creationData, out byte[] creationHash, out TkCreation ticket); + + Log.Debug("New EK Handle: " + BitConverter.ToString(newTransientEkHandle)); + Log.Debug("New EK PUB Name: " + BitConverter.ToString(outPublic.GetName())); + Log.Debug("New EK PUB 2BREP: " + BitConverter.ToString(outPublic.GetTpm2BRepresentation())); + + // Make the object persistent + tpm.EvictControl(TpmRh.Owner, newTransientEkHandle, ekHandle); + Log.Debug("Successfully made the new EK persistent at handle " + BitConverter.ToString(ekHandle) + "."); + + tpm.FlushContext(newTransientEkHandle); + Log.Debug("Flushed the context for the transient EK."); + } + + private static RsaParms AkRsaParms() { + TpmAlgId digestAlg = TpmAlgId.Sha256; + RsaParms parms = new(new SymDefObject(TpmAlgId.Null, 0, TpmAlgId.Null), new SchemeRsassa(digestAlg), 2048, 0); + return parms; + } + + private static ObjectAttr AkAttributes() { + ObjectAttr attrib = ObjectAttr.Restricted | ObjectAttr.Sign | ObjectAttr.FixedParent | ObjectAttr.FixedTPM + | ObjectAttr.SensitiveDataOrigin | ObjectAttr.UserWithAuth; + return attrib; + } + + private static TpmPublic GenerateAKTemplate(TpmAlgId nameAlg) { + RsaParms rsa = AkRsaParms(); + ObjectAttr attributes = AkAttributes(); + TpmPublic inPublic = new(nameAlg, attributes, null, rsa, new Tpm2bPublicKeyRsa()); + return inPublic; + } + + public void CreateAttestationKey(uint ekHandleInt, uint akHandleInt, bool replace) { + TpmHandle ekHandle = new(ekHandleInt); + TpmHandle akHandle = new(akHandleInt); + + TpmPublic existingObject = null; + try { + existingObject = tpm.ReadPublic(akHandle, out byte[] name, out byte[] qualifiedName); + } catch { } + + if (!replace && existingObject != null) { + // Do Nothing + Log.Debug("AK exists at expected handle. Flag to not replace the AK is set in the settings file."); + return; + } else if (replace && existingObject != null) { + // Clear the object and continue + tpm.EvictControl(TpmRh.Owner, akHandle, akHandle); + Log.Debug("Removed previous AK."); + } + + // Create a new key and make it persistent at akHandle + TpmAlgId nameAlg = TpmAlgId.Sha256; + + SensitiveCreate inSens = new(); + TpmPublic inPublic = GenerateAKTemplate(nameAlg); + + var policyEK = new PolicyTree(nameAlg); + policyEK.SetPolicyRoot(new TpmPolicySecret(TpmRh.Endorsement, false, 0, null, null)); + + AuthSession sessEK = tpm.StartAuthSessionEx(TpmSe.Policy, nameAlg); + sessEK.RunPolicy(tpm, policyEK); + + TpmPrivate kAK = tpm[sessEK].Create(ekHandle, inSens, inPublic, null, null, out TpmPublic outPublic, + out CreationData creationData, out byte[] creationHash, out TkCreation ticket); + + Log.Debug("New AK PUB Name: " + BitConverter.ToString(outPublic.GetName())); + Log.Debug("New AK PUB 2BREP: " + BitConverter.ToString(outPublic.GetTpm2BRepresentation())); + Log.Debug("New AK PUB unique: " + BitConverter.ToString((Tpm2bPublicKeyRsa)(outPublic.unique))); + + tpm.FlushContext(sessEK); + + sessEK = tpm.StartAuthSessionEx(TpmSe.Policy, nameAlg); + sessEK.RunPolicy(tpm, policyEK); + + TpmHandle hAK = tpm[sessEK].Load(ekHandle, kAK, outPublic); + + tpm.EvictControl(TpmRh.Owner, hAK, akHandle); + Log.Debug("Created and persisted new AK at handle 0x" + akHandle.handle.ToString("X") + "."); + + tpm.FlushContext(sessEK); + } + + public Tpm2bDigest[] GetPcrList(TpmAlgId pcrBankDigestAlg, uint[] pcrs = null) { + if (pcrs == null) { + pcrs = new uint[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }; + } + Log.Debug("Retrieving PCR LIST for pcrs: " + string.Join(",", pcrs)); + + PcrSelection[] pcrSelection = new PcrSelection[] { + new PcrSelection(pcrBankDigestAlg, pcrs, (uint)pcrs.Length) + }; + Tpm2bDigest[] pcrValues = MultiplePcrRead(pcrSelection[0]); + return pcrValues; + } + + // qualifying data hashed with SHA256, quote set to use RSASSA scheme with SHA256-- TODO: enable usage of ECC and other digest alg + // if no pcrs are requested (parameter pcrs == null), all pcrs wil be returned. The function does not check if any pcr is available before asking for the quote + public void GetQuote(uint akHandleInt, TpmAlgId pcrBankDigestAlg, byte[] nonce, out CommandTpmQuoteResponse ctqr, uint[] pcrs = null) { + TpmHandle akHandle = new(akHandleInt); + + if (pcrs == null) { + pcrs = new uint[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }; + } + Log.Debug("Retrieving TPM quote for pcrs: " + string.Join(",", pcrs)); + + + PcrSelection[] pcrSelection = new PcrSelection[] { + new PcrSelection(pcrBankDigestAlg, pcrs, (uint)pcrs.Length) + }; + + // for test only + TpmHash qualifyingData = TpmHash.FromData(TpmAlgId.Sha256, nonce); + + Attest localQuotedInfo = tpm.Quote(akHandle, qualifyingData, new SchemeRsassa(TpmAlgId.Sha256), pcrSelection, out ISignatureUnion localQuoteSig); + Tpm2bDigest[] localPcrValues = MultiplePcrRead(pcrSelection[0]); + + TpmPublic pub = tpm.ReadPublic(akHandle, out byte[] name, out byte[] qualifiedName); + + bool verified = pub.VerifyQuote(TpmAlgId.Sha256, pcrSelection, localPcrValues, qualifyingData, localQuotedInfo, localQuoteSig, qualifiedName); + Log.Debug("Quote " + (verified ? "was" : "was not") + " verified."); + ctqr = null; + if (verified) { + ctqr = new CommandTpmQuoteResponse(localQuotedInfo, localQuoteSig, localPcrValues); + } + } + + public Tpm2bDigest[] MultiplePcrRead(PcrSelection pcrs) { + if (pcrs == null) { + return Array.Empty(); + } + + List pcrValues = new(); + + const int MAX_NUM_PCRS_PER_READ = 8; // anticipate TPM has a limit on the number of PCRs read at a time + Queue selectedPcrs = new(pcrs.GetSelectedPcrs()); + + while (selectedPcrs.Count() > 0) { + int numPcrsToRead = (selectedPcrs.Count > MAX_NUM_PCRS_PER_READ) ? MAX_NUM_PCRS_PER_READ : selectedPcrs.Count; + + List subset = new(); + for (int i = 0; i < numPcrsToRead; i++) { + subset.Add(selectedPcrs.Dequeue()); + } + PcrSelection selection = new(pcrs.hash, subset.ToArray()); + PcrSelection[] pcrsIn = { // Need to wrap into an array + selection + }; + uint count = tpm.PcrRead(pcrsIn, out PcrSelection[] pcrsOut, out Tpm2bDigest[] pcrValuesRetrieved); // TODO incorporate check on count to handle race condition + if (pcrValuesRetrieved != null && pcrValuesRetrieved.Length > 0) { + pcrValues.AddRange(pcrValuesRetrieved); + } + } + return pcrValues.ToArray(); + } + + public byte[] ActivateCredential(uint akHandleInt, uint ekHandleInt, byte[] integrityHMAC, byte[] encIdentity, byte[] encryptedSecret) { + byte[] recoveredSecret; + + TpmHandle ekHandle = new(ekHandleInt); + TpmHandle akHandle = new(akHandleInt); + + IdObject credentialBlob = new(integrityHMAC, encIdentity); + + TpmAlgId nameAlg = TpmAlgId.Sha256; + var policyEK = new PolicyTree(nameAlg); + policyEK.SetPolicyRoot(new TpmPolicySecret(TpmRh.Endorsement, false, 0, null, null)); + + AuthSession sessEK = tpm.StartAuthSessionEx(TpmSe.Policy, nameAlg); + sessEK.RunPolicy(tpm, policyEK); + + AuthSession sessAK = tpm.StartAuthSessionEx(TpmSe.None, nameAlg); + recoveredSecret = tpm[sessAK, sessEK].ActivateCredential(akHandle, ekHandle, credentialBlob, encryptedSecret); + Log.Debug("encryptedSecret: " + BitConverter.ToString(encryptedSecret)); + + tpm.FlushContext(sessEK); + tpm.FlushContext(sessAK); + + return recoveredSecret; + } + + public byte[] GetEventLog() { + byte[] eventLog = null; + if (tpm._GetUnderlyingDevice().GetType() == typeof(TbsDevice)) { // if windows TPM + if (!TbsWrapper.GetEventLog(out eventLog)) { + eventLog = null; + Log.Debug("Could not retrieve the event log from Tbsi"); + } + } + + if (eventLog == null) { + if (tpm._GetUnderlyingDevice().GetType() == typeof(TcpTpmDevice) && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // if TCP TPM on Windows + // attempt to read from the measuredboot log folder + string windir = System.Environment.GetEnvironmentVariable("windir"); + string path = windir + "\\Logs\\MeasuredBoot\\"; + DirectoryInfo directory = new(path); + FileInfo mostRecent = directory.GetFiles() + .OrderByDescending(f => f.LastWriteTime) + .FirstOrDefault(); + if (mostRecent != null && File.Exists(mostRecent.FullName)) { + eventLog = File.ReadAllBytes(mostRecent.FullName); + } else { + Log.Debug("Windows boot configuration log does not exist at expected location"); + } + } else if (tpm._GetUnderlyingDevice().GetType() == typeof(LinuxTpmDevice) || (tpm._GetUnderlyingDevice().GetType() == typeof(TcpTpmDevice) && RuntimeInformation.IsOSPlatform(OSPlatform.Linux))) { // if Linux TPM or TCP TPM on Linux + // attempt to read from the binary_bios_measurements file created by the kernel + string path = "/sys/kernel/security/tpm0/binary_bios_measurements"; + if (File.Exists(path)) { + eventLog = File.ReadAllBytes(path); + } else { + Log.Debug("Linux bios measurements log does not exist at expected location"); + } + } + } + return eventLog; + } + + private Tpm2 TpmSetupByType(Tpm2Device tpmDevice) { + try { + tpmDevice.Connect(); + } catch (AggregateException e) { + Log.Error(e, "tpmSetupByType: Error connecting to tpmDevice"); + throw e; + } + + Tpm2 tpm = new(tpmDevice); + if (tpmDevice is TcpTpmDevice) { + // + // If we are using the simulator, we have to do a few things the + // firmware would usually do. These actions have to occur after + // the connection has been established. + // + if (simulator) { + uint rc = 0; + try { + rc = tpm.PcrRead(new PcrSelection[] { new PcrSelection(TpmAlgId.Sha1, new uint[] { 0 }) }, out _, out _); + } catch (TpmException e) { + if (e.RawResponse == TpmRc.Initialize) { + Log.Debug("TPM simulator not initialized. Running startup with clear."); + tpmDevice.PowerCycle(); + tpm.Startup(Su.Clear); + } else { + Log.Debug("TPM simulator already initialized. Skipping TPM2_Startup."); + } + } + } + } else if (tpmDevice is TbsDevice) { + /** + * For device TPMs on Windows, we have to use Windows Identity + */ + // ask windows for owner auth + byte[] ownerAuth; + if (TbsWrapper.GetOwnerAuthFromOS(out ownerAuth)) { + // if found, ownerauth will be delivered with the tpm object + tpm.OwnerAuth = ownerAuth; + } else { + Log.Warning("Could not retrieve owner auth from registry. Trying empty auth."); + } + } else if (tpmDevice is LinuxTpmDevice) { + + } + return tpm; + } + + //TODO Fix ACA so that I don't have to re-format data in this way + public static void FormatPcrValuesForAca(Tpm2bDigest[] pcrValues, string algName, out string pcrValuesStr) { + pcrValuesStr = ""; + if (pcrValues != null && pcrValues.Length > 0) { + pcrValuesStr = algName + " :\n"; + } + for (int i = 0; i < pcrValues.Length; i++) { + Tpm2bDigest pcrValue = pcrValues[i]; + pcrValuesStr += " " + i; + pcrValuesStr += ((i > 9) ? " " : " ") + ": "; + pcrValuesStr += BitConverter.ToString(pcrValue.buffer).Replace("-", "").ToLower().Trim() + "\n"; + } + } + } +} + diff --git a/HIRS_Provisioner.NET/hirs/src/tpm/CommandTpmQuoteResponse.cs b/HIRS_Provisioner.NET/hirs/src/tpm/CommandTpmQuoteResponse.cs new file mode 100644 index 00000000..a0556dff --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/tpm/CommandTpmQuoteResponse.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Tpm2Lib; + +namespace hirs { + public class CommandTpmQuoteResponse : Tpm2QuoteResponse { + + private readonly Attest quoted1; + private readonly ISignatureUnion signature1; + private readonly Tpm2bDigest[] pcrValues1; + + public CommandTpmQuoteResponse(Attest quoted, ISignatureUnion signature, Tpm2bDigest[] pcrValues) { + quoted1 = quoted; + signature1 = signature; + pcrValues1 = pcrValues != null ? (Tpm2bDigest[])pcrValues.Clone() : null; + } + public new Attest quoted => quoted1; + public new ISignatureUnion signature => signature1; + public Tpm2bDigest[] pcrValues => pcrValues1; + + //TODO Fix ACA so that I don't have to re-format data in this way + public static void formatQuoteInfoSigForAca(Attest quoteInfo, ISignatureUnion quoteSig, out string quoteInfoSigStr) { + quoteInfoSigStr = "quoted:"; + quoteInfoSigStr += BitConverter.ToString(quoteInfo.GetTpm2BRepresentation()).Replace("-", "").ToLower().Trim(); + quoteInfoSigStr += "signature:"; + quoteInfoSigStr += BitConverter.ToString(((SignatureRsassa)quoteSig).GetTpm2BRepresentation()).Replace("-", "").ToLower().Trim(); + } + } +} diff --git a/HIRS_Provisioner.NET/hirs/src/tpm/IHirsAcaTpm.cs b/HIRS_Provisioner.NET/hirs/src/tpm/IHirsAcaTpm.cs new file mode 100644 index 00000000..9749ee9f --- /dev/null +++ b/HIRS_Provisioner.NET/hirs/src/tpm/IHirsAcaTpm.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Tpm2Lib; + +namespace hirs { + public interface IHirsAcaTpm { + byte[] GetCertificateFromNvIndex(uint index); + TpmPublic ReadPublicArea(uint handleInt, out byte[] name, out byte[] qualifiedName); + void CreateEndorsementKey(uint ekHandleInt); + void CreateAttestationKey(uint ekHandleInt, uint akHandleInt, bool replace); + Tpm2bDigest[] GetPcrList(TpmAlgId pcrBankDigestAlg, uint[] pcrs = null); + void GetQuote(uint akHandleInt, TpmAlgId pcrBankDigestAlg, byte[] nonce, out CommandTpmQuoteResponse ctqr, uint[] pcrs = null); + byte[] ActivateCredential(uint akHandleInt, uint ekHandleInt, byte[] integrityHMAC, byte[] encIdentity, byte[] encryptedSecret); + byte[] GetEventLog(); + + } +} diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/appsettings.json b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/appsettings.json new file mode 100644 index 00000000..ab1f646f --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/appsettings.json @@ -0,0 +1,37 @@ +{ + "auto_detect_tpm": "FALSE", + "aca_address_port": "https://127.0.0.1:8443", + "efi_prefix": "./Resources/test/settings_test/efi/", + "paccor_output_file": "./Resources/test/settings_test/component_list", + "event_log_file": "./Resources/test/settings_test/event_log", + "hardware_manifest_collectors": "", + + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ], + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {SourceContext} [{Level}] {Message}{NewLine}{Exception}", + "theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Grayscale, Serilog.Sinks.Console" + } + }, + { + "Name": "File", + "Args": { + "path": "hirs.log", + "rollingInterval": "Day", + "retainedFileCountLimit": 5 + } + } + ] + } +} \ No newline at end of file diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/component_list b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/component_list new file mode 100644 index 00000000..3ff542ef --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/component_list @@ -0,0 +1 @@ +component list diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/cert/Mfg.Serial.BASE.cer b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/cert/Mfg.Serial.BASE.cer new file mode 100644 index 00000000..06c9e62d --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/cert/Mfg.Serial.BASE.cer @@ -0,0 +1 @@ +Base Platform Cert Serial diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/cert/platform/Mfg.Serial.ver2.base.cer b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/cert/platform/Mfg.Serial.ver2.base.cer new file mode 100644 index 00000000..4a56b5de --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/cert/platform/Mfg.Serial.ver2.base.cer @@ -0,0 +1 @@ +Delta Platform Certificate Serial diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/rim/Mfg.Model.1.rimel b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/rim/Mfg.Model.1.rimel new file mode 100644 index 00000000..93b1ee59 --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/rim/Mfg.Model.1.rimel @@ -0,0 +1 @@ +RIM EL Model 1 diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/rim/Mfg.Model.1.rimpcr b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/rim/Mfg.Model.1.rimpcr new file mode 100644 index 00000000..47f2ae1b --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/rim/Mfg.Model.1.rimpcr @@ -0,0 +1 @@ +RIM PCR Model 1 diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/swidtag/Mfg.Model.1.swidtag b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/swidtag/Mfg.Model.1.swidtag new file mode 100644 index 00000000..6c59c1fc --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/manifest/swidtag/Mfg.Model.1.swidtag @@ -0,0 +1 @@ +RIM Swidtag Model 1 diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.base.pem b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.base.pem new file mode 100644 index 00000000..e69de29b diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.delta.cer b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.delta.cer new file mode 100644 index 00000000..e69de29b diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.rimel b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.rimel new file mode 100644 index 00000000..e69de29b diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.rimpcr b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.rimpcr new file mode 100644 index 00000000..e69de29b diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.swidtag b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/other/notread.swidtag new file mode 100644 index 00000000..e69de29b diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/pccert/Mfg.Model.ver1.delta.pem b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/pccert/Mfg.Model.ver1.delta.pem new file mode 100644 index 00000000..d20243da --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/efi/boot/tcg/pccert/Mfg.Model.ver1.delta.pem @@ -0,0 +1 @@ +Delta Platform Certificiate Model diff --git a/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/event_log b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/event_log new file mode 100644 index 00000000..2c74f866 --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/Resources/test/settings_test/event_log @@ -0,0 +1 @@ +event log diff --git a/HIRS_Provisioner.NET/hirsTest/hirsTest.csproj b/HIRS_Provisioner.NET/hirsTest/hirsTest.csproj new file mode 100644 index 00000000..120dfd60 --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/hirsTest.csproj @@ -0,0 +1,38 @@ + + + + net6.0 + false + enable + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + PreserveNewest + + + + + + + + + + diff --git a/HIRS_Provisioner.NET/hirsTest/src/client/ClientTests.cs b/HIRS_Provisioner.NET/hirsTest/src/client/ClientTests.cs new file mode 100644 index 00000000..9529efde --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/src/client/ClientTests.cs @@ -0,0 +1,53 @@ +using hirs; +using Hirs.Pb; +using NUnit.Framework; +using System.Collections.Generic; +using System.Text; +using Tpm2Lib; + +namespace hirsTest { + public class ClientTests { + public static readonly string localhost = "https://127.0.0.1:8443/"; + + [Test] + public void TestCreateIdentityClaim() { + IHirsAcaClient client = new Client(ClientTests.localhost); + + DeviceInfo dv = new DeviceInfo(); + byte[] akPub = new byte[] { }; + byte[] ekPub = new byte[] { }; + byte[] ekc = new byte[] { }; + List pcs = new List(); + pcs.Add(new byte[] { }); + string componentList= ""; + + IdentityClaim obj = client.CreateIdentityClaim(dv, akPub, ekPub, ekc, pcs, componentList); + Assert.Multiple(() => { + Assert.That(obj.Dv, Is.Not.Null); + Assert.That(obj.HasAkPublicArea, Is.True); + Assert.That(obj.HasEkPublicArea, Is.True); + Assert.That(obj.HasEndorsementCredential, Is.True); + Assert.That(obj.PlatformCredential, Has.Count.EqualTo(1)); + Assert.That(obj.HasPaccorOutput, Is.True); + }); + } + + [Test] + public void TestCreateAkCertificateRequest() { + IHirsAcaClient client = new Client(ClientTests.localhost); + + byte[] secret = Encoding.UTF8.GetBytes("secret"); + Attest quoted = new Attest(Generated.None, Encoding.UTF8.GetBytes("QUALIFIED SIGNER"), Encoding.UTF8.GetBytes("EXTRA DATA"), new ClockInfo(0, 0, 0, 0), 0, new QuoteInfo()); + ISignatureUnion signature = new SignatureRsassa(); + Tpm2bDigest[] pcrValues = new Tpm2bDigest[] { new Tpm2bDigest(Encoding.UTF8.GetBytes("SHA1 DIGEST1")) }; + + CommandTpmQuoteResponse ctqr = new CommandTpmQuoteResponse(quoted, signature, pcrValues); + + CertificateRequest obj = client.CreateAkCertificateRequest(secret, ctqr); + Assert.Multiple(() => { + Assert.That(obj.HasNonce, Is.True); + Assert.That(obj.HasQuote, Is.True); + }); + } + } +} diff --git a/HIRS_Provisioner.NET/hirsTest/src/config/SettingsTests.cs b/HIRS_Provisioner.NET/hirsTest/src/config/SettingsTests.cs new file mode 100644 index 00000000..b0e3411a --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/src/config/SettingsTests.cs @@ -0,0 +1,59 @@ +using hirs; +using NUnit.Framework; +using System.Collections.Generic; +using System.Text; + +namespace hirsTest { + public class SettingsTests { + [Test] + public void TestConstructorWithAppsettings() { + Settings settings = Settings.LoadSettingsFromFile("./Resources/test/settings_test/appsettings.json"); + settings.SetUpLog(); + settings.CompleteSetUp(); + + Assert.Multiple(() => { + Assert.That(settings.IsAutoDetectTpmEnabled(), Is.False); + Assert.That(settings.HasAcaAddress(), Is.True); + Assert.That(settings.aca_address_port.ToString(), Is.EqualTo("https://127.0.0.1:8443/")); + Assert.That(settings.HasEfiPrefix(), Is.True); + Assert.That(settings.HasPaccorOutputFromFile(), Is.True); + Assert.That(settings.HasEventLogFromFile(), Is.True); + }); + + const string baseSerial = "Base Platform Cert Serial\n"; + const string deltaSerial = "Delta Platform Certificate Serial\n"; + const string deltaModel = "Delta Platform Certificiate Model\n"; + const string swidtagModel = "RIM Swidtag Model 1\n"; + const string rimelModel = "RIM EL Model 1\n"; + const string rimpcrModel = "RIM PCR Model 1\n"; + const string eventLogModel = "event log\n"; + const string componentList = "component list\n"; + + string paccorOutput = settings.paccor_output; + List platformCerts = settings.gatherPlatformCertificatesFromEFI(); + List rims = settings.gatherRIMBasesFromEFI(); + List rimELs = settings.gatherSupportRIMELsFromEFI(); + List rimPCRs = settings.gatherSupportRIMPCRsFromEFI(); + byte[] eventLog = settings.event_log; + //Assert.Multiple(() => { + Assert.That(paccorOutput, Is.EqualTo(componentList)); + Assert.That(platformCerts, Has.Count.EqualTo(3)); + + Assert.That(platformCerts[0], Is.EqualTo(Encoding.ASCII.GetBytes(baseSerial))); + Assert.That(platformCerts[1], Is.EqualTo(Encoding.ASCII.GetBytes(deltaSerial))); + Assert.That(platformCerts[2], Is.EqualTo(Encoding.ASCII.GetBytes(deltaModel))); + + Assert.That(rims, Has.Count.EqualTo(1)); + Assert.That(rims[0], Is.EqualTo(Encoding.ASCII.GetBytes(swidtagModel))); + + Assert.That(rimELs, Has.Count.EqualTo(1)); + Assert.That(rimELs[0], Is.EqualTo(Encoding.ASCII.GetBytes(rimelModel))); + + Assert.That(rimPCRs, Has.Count.EqualTo(1)); + Assert.That(rimPCRs[0], Is.EqualTo(Encoding.ASCII.GetBytes(rimpcrModel))); + + Assert.That(Encoding.ASCII.GetString(eventLog), Is.EqualTo(eventLogModel)); + //}); + } + } +} diff --git a/HIRS_Provisioner.NET/hirsTest/src/provisioner/ProvisionerTests.cs b/HIRS_Provisioner.NET/hirsTest/src/provisioner/ProvisionerTests.cs new file mode 100644 index 00000000..4236c62f --- /dev/null +++ b/HIRS_Provisioner.NET/hirsTest/src/provisioner/ProvisionerTests.cs @@ -0,0 +1,123 @@ +using Hirs.Pb; +using hirs; +using FakeItEasy; +using NUnit.Framework; +using System; +using System.Text; +using System.Threading.Tasks; +using Tpm2Lib; + +namespace hirsTest { + public class ProvisionerTests { + [Test] + public async Task TestGoodAsync() { + const string address = "https://127.0.0.1:8443/"; + byte[] ekCert = Encoding.UTF8.GetBytes("EK CERTIFICATE"); + byte[] secret = Encoding.UTF8.GetBytes("AuthCredential Secret"); + byte[] acaIssuedCert = Encoding.UTF8.GetBytes("ACA ISSUED CERTIFICATE"); + byte[] integrityHMAC = Convert.FromBase64String("VAtedc1RlNA1w0XfrtwmhE0ILBlILP6163Tur5HRIo0="); + byte[] encIdentity = Convert.FromBase64String("6e2oGBsK3H9Vzbj667ZsjnVOtvpSpQ=="); + byte[] encryptedSecret = Convert.FromBase64String("NekvnOX8RPRdyd0/cxBI4FTCuNkiu0KAnS28yT7yYJUL5Lwfcv5ctEK6zQA0fq0IsX5TlAYSidGKxrAilOSwALJmJ+m7sMiXwMKrZn1cd4gzXObZEQimQoWgSEQbPO7rfpUn1UfI8K5SzmUFUTxc5X3D8zFonaEBp6QCjtdLegKGgioCDcQFdz20Y0PFAa1Itug7YbZdCFpfit570eQQinmqdVryiNyn6CLQdMgIejuBxoEpoTSWszB5eFKEdn5g/+8wcvhp6RpNBQ0hikF+6688TOVK/j8n3JDwKVltJ/WNHjVO+lxa2aLIMJRgs5ZRuzuz6OSMf10KqJjSWZE04w=="); + byte[] credentialBlob = Convert.FromBase64String("OAAAIFQLXnXNUZTQNcNF367cJoRNCCwZSCz+tet07q+R0SKN6e2oGBsK3H9Vzbj667ZsjnVOtvpSpQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATXpL5zl/ET0XcndP3MQSOBUwrjZIrtCgJ0tvMk+8mCVC+S8H3L+XLRCus0ANH6tCLF+U5QGEonRisawIpTksACyZifpu7DIl8DCq2Z9XHeIM1zm2REIpkKFoEhEGzzu636VJ9VHyPCuUs5lBVE8XOV9w/MxaJ2hAaekAo7XS3oChoIqAg3EBXc9tGNDxQGtSLboO2G2XQhaX4ree9HkEIp5qnVa8ojcp+gi0HTICHo7gcaBKaE0lrMweXhShHZ+YP/vMHL4aekaTQUNIYpBfuuvPEzlSv4/J9yQ8ClZbSf1jR41TvpcWtmiyDCUYLOWUbs7s+jkjH9dCqiY0lmRNOM="); + TpmPublic ekPublic = CommandTpm.GenerateEKTemplateL1(); + TpmPublic akPublic = new(TpmAlgId.Sha256, ObjectAttr.None, System.Text.Encoding.UTF8.GetBytes("AK PUBLIC AUTH POLICY"), new RsaParms(new SymDefObject(TpmAlgId.Null, 0, TpmAlgId.Null), new SchemeRsassa(TpmAlgId.Sha256), 2048, 0), new Tpm2bPublicKeyRsa()); + Tpm2bDigest[] sha1Values = new Tpm2bDigest[] { new Tpm2bDigest(System.Text.Encoding.UTF8.GetBytes("SHA1 DIGEST1")) }; + Tpm2bDigest[] sha256Values = new Tpm2bDigest[] { new Tpm2bDigest(System.Text.Encoding.UTF8.GetBytes("SHA256 DIGEST1")) }; + DeviceInfo dv = new(); + string paccorOutput = "paccor output"; + + CommandTpmQuoteResponse ctqr = null; + IdentityClaimResponse idClaimResp = new(); + idClaimResp.Status = ResponseStatus.Pass; + idClaimResp.CredentialBlob = Google.Protobuf.ByteString.CopyFrom(credentialBlob); + CertificateResponse certResp = new(); + certResp.Status = ResponseStatus.Pass; + certResp.Certificate = Google.Protobuf.ByteString.CopyFrom(acaIssuedCert); + + IHirsAcaTpm tpm = A.Fake(); + byte[] name = null, qualifiedName = null; + A.CallTo(() => tpm.GetCertificateFromNvIndex(CommandTpm.DefaultEkcNvIndex)).Returns(ekCert); + A.CallTo(() => tpm.CreateEndorsementKey(CommandTpm.DefaultEkHandle)).DoesNothing(); + A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultEkHandle, out name, out qualifiedName)).Returns(ekPublic); + A.CallTo(() => tpm.CreateAttestationKey(CommandTpm.DefaultEkHandle, CommandTpm.DefaultAkHandle, false)).DoesNothing(); + A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultAkHandle, out name, out qualifiedName)).Returns(akPublic); + //A.CallTo(() => tpm.getPcrList(TpmAlgId.Sha1, A.Ignored)).Returns(sha1Values); + //A.CallTo(() => tpm.getPcrList(TpmAlgId.Sha256, A.Ignored)).Returns(sha256Values); + A.CallTo(() => tpm.GetQuote(CommandTpm.DefaultAkHandle, TpmAlgId.Sha256, secret, out ctqr, A.Ignored)).DoesNothing(); + + IHirsDeviceInfoCollector collector = A.Fake(); + A.CallTo(() => collector.CollectDeviceInfo(address)).Returns(dv); + + IHirsAcaClient client = A.Fake(); + IdentityClaim idClaim = client.CreateIdentityClaim(dv, akPublic, ekPublic, ekCert, null, paccorOutput); + CertificateRequest certReq = client.CreateAkCertificateRequest(secret, ctqr); + A.CallTo(() => client.PostIdentityClaim(idClaim)).Returns(Task.FromResult(idClaimResp)); + A.CallTo(() => client.PostCertificateRequest(certReq)).Returns(Task.FromResult(certResp)); + + Settings settings = Settings.LoadSettingsFromFile("./Resources/test/settings_test/appsettings.json"); + settings.SetUpLog(); + settings.CompleteSetUp(); + + CLI cli = A.Fake(); + + IHirsProvisioner p = A.Fake(); + p.SetSettings(settings); + p.SetCLI(cli); + p.SetClient(client); + + p.SetDeviceInfoCollector(collector); // Give the provisioner the mocked collector + int result = await p.Provision(tpm); + + A.CallTo(() => tpm.ActivateCredential(CommandTpm.DefaultAkHandle, CommandTpm.DefaultEkHandle, A.That.IsSameSequenceAs(integrityHMAC), A.That.IsSameSequenceAs(encIdentity), A.That.IsSameSequenceAs(encryptedSecret))).MustHaveHappenedOnceExactly(); + Assert.That(result, Is.EqualTo(0)); + } + + [Test] + public async Task TestIssueWithIdentityClaimResponse() { + const string address = "https://127.0.0.1:8443/"; + byte[] ekCert = Encoding.UTF8.GetBytes("EK CERTIFICATE"); + byte[] acaIssuedCert = Encoding.UTF8.GetBytes("ACA ISSUED CERTIFICATE"); + TpmPublic ekPublic = CommandTpm.GenerateEKTemplateL1(); + TpmPublic akPublic = new(TpmAlgId.Sha256, ObjectAttr.None, System.Text.Encoding.UTF8.GetBytes("AK PUBLIC AUTH POLICY"), new RsaParms(new SymDefObject(TpmAlgId.Null, 0, TpmAlgId.Null), new SchemeRsassa(TpmAlgId.Sha256), 2048, 0), new Tpm2bPublicKeyRsa()); + Tpm2bDigest[] sha1Values = new Tpm2bDigest[] { new Tpm2bDigest(System.Text.Encoding.UTF8.GetBytes("SHA1 DIGEST1")) }; + Tpm2bDigest[] sha256Values = new Tpm2bDigest[] { new Tpm2bDigest(System.Text.Encoding.UTF8.GetBytes("SHA256 DIGEST1")) }; + DeviceInfo dv = new(); + string paccorOutput = "paccor output"; + IdentityClaimResponse idClaimResp = new(); + idClaimResp.ClearCredentialBlob(); + + IHirsAcaTpm tpm = A.Fake(); + byte[] name = null, qualifiedName = null; + A.CallTo(() => tpm.GetCertificateFromNvIndex(CommandTpm.DefaultEkcNvIndex)).Returns(ekCert); + A.CallTo(() => tpm.CreateEndorsementKey(CommandTpm.DefaultEkHandle)).DoesNothing(); + A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultEkHandle, out name, out qualifiedName)).Returns(ekPublic); + A.CallTo(() => tpm.CreateAttestationKey(CommandTpm.DefaultEkHandle, CommandTpm.DefaultAkHandle, false)).DoesNothing(); + A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultAkHandle, out name, out qualifiedName)).Returns(ekPublic); + A.CallTo(() => tpm.GetPcrList(TpmAlgId.Sha1, A.Ignored)).Returns(sha1Values); + A.CallTo(() => tpm.GetPcrList(TpmAlgId.Sha256, A.Ignored)).Returns(sha256Values); + + IHirsDeviceInfoCollector collector = A.Fake(); + A.CallTo(() => collector.CollectDeviceInfo(address)).Returns(dv); + + IHirsAcaClient client = A.Fake(); + IdentityClaim idClaim = client.CreateIdentityClaim(dv, akPublic, ekPublic, ekCert, null, paccorOutput); + A.CallTo(() => client.PostIdentityClaim(idClaim)).WithAnyArguments().Returns(Task.FromResult(idClaimResp)); + + Settings settings = Settings.LoadSettingsFromFile("./Resources/test/settings_test/appsettings.json"); + settings.SetUpLog(); + settings.CompleteSetUp(); + + CLI cli = A.Fake(); + + IHirsProvisioner p = A.Fake(); + p.SetSettings(settings); + p.SetCLI(cli); + p.SetClient(client); + + p.SetDeviceInfoCollector(collector); // Give the provisioner the mocked collector + int result = await p.Provision(tpm); + + Assert.That(result, Is.EqualTo((int)ClientExitCodes.MAKE_CREDENTIAL_BLOB_MALFORMED)); + } + } +} diff --git a/HIRS_Provisioner.NET/tools/pcrextend/pcrextend.csproj b/HIRS_Provisioner.NET/tools/pcrextend/pcrextend.csproj new file mode 100644 index 00000000..1ca57b5a --- /dev/null +++ b/HIRS_Provisioner.NET/tools/pcrextend/pcrextend.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + enable + enable + 0.1.0 + + + + + + + + diff --git a/HIRS_Provisioner.NET/tools/pcrextend/src/Program.cs b/HIRS_Provisioner.NET/tools/pcrextend/src/Program.cs new file mode 100644 index 00000000..2d16f0cd --- /dev/null +++ b/HIRS_Provisioner.NET/tools/pcrextend/src/Program.cs @@ -0,0 +1,42 @@ +using CommandLine; + +namespace pcrextend { + class Program { + static void Main(string[] args) { + bool result = false; + try { + CLI cli = new(); + ParserResult cliParseResult = + CommandLine.Parser.Default.ParseArguments(args) + .WithParsed(parsed => cli = parsed) + .WithNotParsed(HandleParseError); + + if (cliParseResult.Tag == ParserResultType.Parsed) { + // filter pci index + int pcr = cli.Pcr; + if (pcr < 0 || pcr > 23) { + Console.WriteLine("Unknown PCR index: " + pcr + ". Should be from 0 to 23."); + return; + } + PcrExtendTool p = new(cli); + CommandTpmSimulator? tpm = p.connectTpm(); + if (tpm != null) { + result = p.Extend(tpm); + } + Console.WriteLine("PCR Extend " + (result ? "" : "un") + "successful."); + } + } catch (Exception e) { + Console.WriteLine("Application stopped."); + Console.WriteLine(e.ToString()); + Console.WriteLine(e.StackTrace); + } + } + + private static void HandleParseError(IEnumerable errs) { + if (!errs.IsHelp() && !errs.IsVersion()) { + //handle errors + Console.WriteLine("There was a CLI error: " + errs.ToString()); + } + } + } +} diff --git a/HIRS_Provisioner.NET/tools/pcrextend/src/config/CLI.cs b/HIRS_Provisioner.NET/tools/pcrextend/src/config/CLI.cs new file mode 100644 index 00000000..092cee59 --- /dev/null +++ b/HIRS_Provisioner.NET/tools/pcrextend/src/config/CLI.cs @@ -0,0 +1,29 @@ +using CommandLine; + +namespace pcrextend { + public class CLI { + // These fields are controlled by the CommandLineParser library. + // CS8618 is not relevant at this time. + // Non-nullable field must contain a non-null value when exiting constructor. +#pragma warning disable CS8618 + [Option('i', "ip", Default = CommandTpmSimulator.DefaultSimulatorNamePort, HelpText = "IP of the TPM Simulator. Use the format ip:port.")] + public string Ip { + + get; set; + } + [Option('p', "pcr", HelpText = "PCR Index 0 thru 23")] + public short Pcr { + get; set; + } + [Option('a', "hashalg", HelpText = "sha1, sha256, sha384, or sha512. OR their ID Values in decimal (4, 11, 12, 13) or in hex (0x4, 0xB, 0xC, 0xD).")] + public string HashAlg { + get; set; + } + + [Option('d', "digests", HelpText = "Comma separated digest values in hex. e.g. AB,34F53,9785")] + public string Digests { + get; set; + } +#pragma warning restore CS8618 + } +} diff --git a/HIRS_Provisioner.NET/tools/pcrextend/src/tool/PcrExtendTool.cs b/HIRS_Provisioner.NET/tools/pcrextend/src/tool/PcrExtendTool.cs new file mode 100644 index 00000000..49cb2988 --- /dev/null +++ b/HIRS_Provisioner.NET/tools/pcrextend/src/tool/PcrExtendTool.cs @@ -0,0 +1,55 @@ +namespace pcrextend { + public class PcrExtendTool { + private CLI? cli = null; + + public PcrExtendTool(CLI cli) { + SetCLI(cli); + } + + public void SetCLI(CLI cli) { + this.cli = cli; + } + + public CommandTpmSimulator? connectTpm() { + CommandTpmSimulator? tpm = null; + if (cli != null && cli.Ip != null) { + string[] split = cli.Ip.Split(":"); + if (split.Length == 2) { + try { + tpm = new CommandTpmSimulator(split[0], Int32.Parse(split[1])); + } catch (Exception) { + Console.WriteLine("No tpm found at " + cli.Ip); + } + } else { + Console.WriteLine("ip input should have the format servername:port. The given input was '" + cli.Ip + "'."); + } + } + + return tpm; + } + + public bool Extend(CommandTpmSimulator tpm) { + bool result = false; + if (tpm != null && cli != null) { + // parse hashAlg + string hashAlg = cli.HashAlg; + if (hashAlg.StartsWith("0x", StringComparison.OrdinalIgnoreCase) || hashAlg.EndsWith("h")) { + hashAlg = hashAlg.Replace("0x", ""); + hashAlg = hashAlg.TrimEnd('h'); + hashAlg = "" + Convert.ToInt64(hashAlg, 16); + } + + string digestCSV = cli.Digests; + digestCSV = digestCSV.ToUpper().Replace("0X", "").Replace("H", ""); + string[] digestArray = digestCSV.Split(","); + List digestList = new(); + foreach (string digest in digestArray) { + digestList.Add(Convert.FromHexString(digest)); + } + tpm.pcrextend(cli.Pcr, hashAlg, digestList); + result = true; + } + return result; + } + } +} diff --git a/HIRS_Provisioner.NET/tools/pcrextend/src/tpm/CommandTpmSimulator.cs b/HIRS_Provisioner.NET/tools/pcrextend/src/tpm/CommandTpmSimulator.cs new file mode 100644 index 00000000..d0a1a09f --- /dev/null +++ b/HIRS_Provisioner.NET/tools/pcrextend/src/tpm/CommandTpmSimulator.cs @@ -0,0 +1,69 @@ +using Tpm2Lib; + +namespace pcrextend { + public class CommandTpmSimulator { + /// + /// If using a TCP connection, the default DNS name/IP address for the + /// simulator. + /// + public const string DefaultSimulatorNamePort = "127.0.0.1:2321"; + + private readonly Tpm2 tpm; + + private readonly Boolean simulator; + + /** + * For TCP TpmDevices + */ + public CommandTpmSimulator(string ip, int port) { + simulator = true; + Tpm2Device tpmDevice = new TcpTpmDevice(ip, port); + Console.WriteLine("Connecting to TPM at " + ip + ":" + port); + tpm = tpmSetupByType(tpmDevice); + } + + ~CommandTpmSimulator() { + if (tpm != null) { + tpm.Dispose(); + } + } + + public void pcrextend(int pcr, string hashAlgStr, List digestList) { + TpmHandle pcrHandle = TpmHandle.Pcr(pcr); + TpmAlgId hashAlg = Enum.Parse(hashAlgStr, true); + List digests = new(); + foreach (byte[] digest in digestList) { + digests.Add(new TpmHash(hashAlg, digest)); + } + tpm.PcrExtend(pcrHandle, digests.ToArray()); + } + + private Tpm2 tpmSetupByType(Tpm2Device tpmDevice) { + tpmDevice.Connect(); + + Tpm2 tpm = new Tpm2(tpmDevice); + if (tpmDevice is TcpTpmDevice) { + // + // If we are using the simulator, we have to do a few things the + // firmware would usually do. These actions have to occur after + // the connection has been established. + // + if (simulator) { + uint rc = 0; + try { + rc = tpm.PcrRead(new PcrSelection[] { new PcrSelection(TpmAlgId.Sha1, new uint[] { 0 }) }, out _, out _); + } catch (TpmException e) { + if (e.RawResponse == TpmRc.Initialize) { + Console.WriteLine("TPM simulator not initialized. Running startup with clear."); + tpmDevice.PowerCycle(); + tpm.Startup(Su.Clear); + } else { + Console.WriteLine("TPM simulator already initialized. Skipping TPM2_Startup."); + } + } + } + } + return tpm; + } + } +}