├── test ├── CMakeLists.txt ├── test-data │ ├── fulcio-email.pem │ ├── fulcio-github-actions.pem │ ├── containerplat-leaf.pem │ ├── ms-test.pem │ └── ms-code-signing.pem └── unit_tests.cpp ├── .gitignore ├── CODE_OF_CONDUCT.md ├── SUPPORT.md ├── .vscode └── launch.json ├── LICENSE ├── CMakeLists.txt ├── .clang-tidy ├── .github └── workflows │ ├── codeql-analysis.yml │ └── ci.yml ├── README.md ├── SECURITY.md ├── .clang-format └── didx509cpp.h /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 5 | 6 | add_unit_test(unit_tests unit_tests.cpp --data-dir ${CMAKE_CURRENT_SOURCE_DIR}/test-data) 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | .cache 35 | build* -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | For help and questions about using this project, please file issues on 10 | [https://github.com/microsoft/didx509cpp/issues](our issue tracker) or start a 11 | [discussion](https://github.com/microsoft/CCF/discussions). 12 | 13 | ## Microsoft Support Policy 14 | 15 | Support for this project is limited to the resources listed above. 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Unit tests", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/build/test/unit_tests", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}/build", 15 | "environment": [], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "Enable pretty-printing for gdb", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | }, 24 | { 25 | "description": "Set Disassembly Flavor to Intel", 26 | "text": "-gdb-set disassembly-flavor intel", 27 | "ignoreFailures": true 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | cmake_minimum_required(VERSION 3.16) 4 | 5 | project(didx509cpp LANGUAGES CXX C ASM) 6 | 7 | set(CMAKE_CXX_STANDARD 20) 8 | 9 | option(PROFILE "enable profiling" OFF) 10 | option(TESTS "enable testing" ON) 11 | 12 | add_library(didx509cpp INTERFACE) 13 | target_include_directories(didx509cpp INTERFACE .) 14 | 15 | install(TARGETS didx509cpp) 16 | 17 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".so") 18 | 19 | find_package(OpenSSL) 20 | target_compile_definitions(didx509cpp INTERFACE HAVE_OPENSSL) 21 | target_link_libraries(didx509cpp INTERFACE crypto) 22 | 23 | if(TESTS) 24 | enable_testing() 25 | 26 | function(add_unit_test NAME SRC) 27 | add_executable(${NAME} ${SRC}) 28 | target_link_libraries(${NAME} PRIVATE $) 29 | 30 | if(PROFILE) 31 | target_compile_options(${NAME} PRIVATE -g -pg) 32 | target_link_options(${NAME} PRIVATE -g -pg) 33 | endif() 34 | 35 | if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 36 | target_compile_options( 37 | ${NAME} PRIVATE -fsanitize=undefined,address,leak -fno-omit-frame-pointer 38 | ) 39 | target_link_options(${NAME} PRIVATE -fsanitize=undefined,address,leak) 40 | endif() 41 | 42 | add_test(NAME ${NAME} COMMAND ${NAME} ${ARGN}) 43 | endfunction() 44 | 45 | add_subdirectory(test) 46 | endif() 47 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | clang-diagnostic-*, 4 | clang-analyzer-*, 5 | -clang-analyzer-deadcode.DeadStores, 6 | bugprone-*, 7 | cert-*, 8 | -cert-err58-cpp, 9 | concurrency-*, 10 | cppcoreguidelines-*, 11 | -cppcoreguidelines-avoid-do-while, 12 | misc-*, 13 | modernize-*, 14 | -modernize-use-trailing-return-type, 15 | performance-*, 16 | -performance-avoid-endl, 17 | portability-*, 18 | readability-*, 19 | -cert-dcl59-cpp, 20 | -readability-identifier-length, 21 | -bugprone-easily-swappable-parameters, 22 | -bugprone-narrowing-conversions, 23 | -cppcoreguidelines-narrowing-conversions, 24 | -readability-function-cognitive-complexity, 25 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 26 | -cppcoreguidelines-avoid-magic-numbers, 27 | -readability-magic-numbers, 28 | -cppcoreguidelines-pro-type-cstyle-cast, 29 | -cppcoreguidelines-special-member-functions, 30 | -cppcoreguidelines-non-private-member-variables-in-classes, 31 | -misc-non-private-member-variables-in-classes, 32 | -bugprone-exception-escape, 33 | -performance-inefficient-string-concatenation, 34 | -cppcoreguidelines-avoid-c-arrays, 35 | -modernize-avoid-c-arrays, 36 | -cppcoreguidelines-rvalue-reference-param-not-moved, 37 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 38 | -cppcoreguidelines-pro-type-union-access 39 | 40 | WarningsAsErrors: '*' 41 | HeaderFilterRegex: '.*didx509cpp\.h' 42 | FormatStyle: 'file' -------------------------------------------------------------------------------- /test/test-data/fulcio-email.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICEDCCAZagAwIBAgITIK73YV52uJcmxL9ZeKo+wZbm3zAKBggqhkjOPQQDAzAq 3 | MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIy 4 | MDgwOTEyNDcxNFoXDTIyMDgwOTEyNTcxM1owADBZMBMGByqGSM49AgEGCCqGSM49 5 | AwEHA0IABPmQP4xa5TxXg/HkUrw3CUcqmW6F5eEBQSU8tcGMIIzIHnMCVwTa4uoq 6 | ZGgdCN+0Erk+toNwkGG+pS3Qc2EocbejgcQwgcEwDgYDVR0PAQH/BAQDAgeAMBMG 7 | A1UdJQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFJITi/Hz 8 | 4QkD5qz2gKoi4UBYfaRRMB8GA1UdIwQYMBaAFFjAHl+RRaVmqXrMkKGTItAqxcX6 9 | MB4GA1UdEQEB/wQUMBKBEGlnYXJjaWFAc3VzZS5jb20wLAYKKwYBBAGDvzABAQQe 10 | aHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMAoGCCqGSM49BAMDA2gAMGUC 11 | MQDPO3n+JgPlTbXSQy942esSy7KQ6OI4N9Q9MsqN4UR2tkML7tUm5feKTQUkfwTs 12 | 6BsCMADuoj3fJGAiRDMlSphfrZ0tAEIFaVZtJmvKWXpElHQo9y39W0w9bJTEVgTa 13 | 4xvX4w== 14 | -----END CERTIFICATE----- 15 | -----BEGIN CERTIFICATE----- 16 | MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw 17 | KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y 18 | MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl 19 | LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 20 | XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex 21 | X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j 22 | YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY 23 | wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ 24 | KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM 25 | WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 26 | TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ main ] 9 | # schedule: 10 | # - cron: '22 12 * * 1' 11 | 12 | permissions: 13 | security-events: write 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ 'cpp' ] 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v3 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: security-extended 34 | # If you wish to specify custom queries, you can do so here or in a config file. 35 | # By default, queries listed here will override any specified in a config file. 36 | # Prefix the list here with "+" to use these queries and those in the config file. 37 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 38 | 39 | - name: Install packages 40 | run: sudo apt install doctest-dev libcurl4-openssl-dev libssl-dev 41 | 42 | - name: Create Build Environment 43 | run: cmake -E make_directory ${{github.workspace}}/build/${{ matrix.build_type }} 44 | 45 | - name: Configure didx509cpp 46 | working-directory: ${{github.workspace}}/build/${{ matrix.build_type }} 47 | run: cmake -DCMAKE_BUILD_TYPE=Debug -DTESTS=ON $GITHUB_WORKSPACE 48 | 49 | - name: Build didx509cpp tests 50 | working-directory: ${{github.workspace}}/build/${{ matrix.build_type }} 51 | run: cmake --build . --config Debug 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v3 55 | -------------------------------------------------------------------------------- /test/test-data/fulcio-github-actions.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDTzCCAtSgAwIBAgIUAOuDsEYQXN1cbwfqYOy5ADUqqDAwCgYIKoZIzj0EAwMw 3 | KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y 4 | MjA2MDkwMjM4MTJaFw0yMjA2MDkwMjQ4MTFaMAAwWTATBgcqhkjOPQIBBggqhkjO 5 | PQMBBwNCAAR8Qujd0dQ2F7uSANd+0M7VXVkhXlGvFERJc1oPxk+R/ApEantKDVd/ 6 | 5/+e2AOoS1ltjcZkCt1oP1mAZ/+2G3i6o4ICADCCAfwwDgYDVR0PAQH/BAQDAgeA 7 | MBMGA1UdJQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLcK 8 | jVaYdV1BcZimzcrE8foM3xAPMB8GA1UdIwQYMBaAFFjAHl+RRaVmqXrMkKGTItAq 9 | xcX6MIGFBgNVHREBAf8EezB5hndodHRwczovL2dpdGh1Yi5jb20vYnJlbmRhbmNh 10 | c3NlbGxzL21jdy1jb250aW51b3VzLWRlbGl2ZXJ5LWxhYi1maWxlcy8uZ2l0aHVi 11 | L3dvcmtmbG93cy9mYWJyaWthbS13ZWIueW1sQHJlZnMvaGVhZHMvbWFpbjAWBgor 12 | BgEEAYO/MAECBAhzY2hlZHVsZTA/BgorBgEEAYO/MAEFBDFicmVuZGFuY2Fzc2Vs 13 | bHMvbWN3LWNvbnRpbnVvdXMtZGVsaXZlcnktbGFiLWZpbGVzMDkGCisGAQQBg78w 14 | AQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20w 15 | NgYKKwYBBAGDvzABAwQoMTIxMDQ4ZDVkMmViNTc3OTg3NTFlY2Y1N2FjNWNlNTg2 16 | NmVmMWEyZDAUBgorBgEEAYO/MAEEBAZEb2NrZXIwHQYKKwYBBAGDvzABBgQPcmVm 17 | cy9oZWFkcy9tYWluMAoGCCqGSM49BAMDA2kAMGYCMQDfg/L1SnH1EdkPtmPN197q 18 | Y+oc+mdFz1ica3Xlx8/En/JcxHUtBkHx1Lv5w++Wg3sCMQC4YKWGL0AfIIES1XT7 19 | b96WTdxBSnBx2Nvrqg0VuzLvM+wIjaz3dXWf41eKB2sXvwo= 20 | -----END CERTIFICATE----- 21 | -----BEGIN CERTIFICATE----- 22 | MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw 23 | KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y 24 | MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl 25 | LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 26 | XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex 27 | X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j 28 | YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY 29 | wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ 30 | KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM 31 | WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 32 | TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /test/test-data/containerplat-leaf.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGazCCBFOgAwIBAgITMwAAAA6GfbGZe5fD8AAAAAAADjANBgkqhkiG9w0BAQwF 3 | ADBVMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u 4 | MSYwJAYDVQQDEx1NaWNyb3NvZnQgU0NEIFByb2R1Y3RzIFJTQSBDQTAeFw0yMzAx 5 | MDUxOTIyNDdaFw0yNDAxMDMxOTIyNDdaMGwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI 6 | EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv 7 | ZnQgQ29ycG9yYXRpb24xFjAUBgNVBAMTDUNvbnRhaW5lclBsYXQwggGiMA0GCSqG 8 | SIb3DQEBAQUAA4IBjwAwggGKAoIBgQC1zPF0G+N8JW8J6+Ow0Fy2zmp7/L50bVxN 9 | cPwZ7Zc2Q0D3WDCTG9AHY2hAwWZdGS+kfsP/O+F9rUt7XXRh3NIXKQo3h5HCHxRl 10 | 8sewhWj8mMTvPiAcLplfkc41bxjZ6jD1nHlRZvjRIqjKP4swITqyuELLFv/3dFgF 11 | MoRHud210PCGCrQ5C2kVHCO3ROFO1RHNEwoOwB4ahp7H4qflxW5fPcKtfoAHdEEO 12 | cSDsPxAecJGNZZHmGV15kJ8yqZsGNDCBzJ8dXKi2lvzUEI1sC1zQrU2LHkcHyW75 13 | vZfI7y8GISQD+/r8kDTSCD8jUyIX75QHkNhtcZiTY87JAct7zQTQFQOiC+WzNyvR 14 | ZqhGi+LmKUkmGo81hcI0jDQ80rWGS6dICP7gIDhTcDvNeRX2cXsXGkuMNZ3jl+dT 15 | GKVegKZw6rMAs/Q4sohD/bI9VZ2Jw1M3hcVOYTDLaG5YwwgXltHidA2cIBCZ223l 16 | CQn1ZVJnzctBwIrTTKJXnJABGgVDyU0CAwEAAaOCAZswggGXMA4GA1UdDwEB/wQE 17 | AwIHgDAjBgNVHSUEHDAaBgsrBgEEAYI3TDsBAQYLKwYBBAGCN0w7AQIwHQYDVR0O 18 | BBYEFBZGoCKzvZk9Mx5xSX25m/u2+IArMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL 19 | ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTQ3Mjk3Mis1MDAwNDUw 20 | HwYDVR0jBBgwFoAUVc1NhW7NSjXDjj9yAbqqmBmXS6cwXgYDVR0fBFcwVTBToFGg 21 | T4ZNaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 22 | JTIwU0NEJTIwUHJvZHVjdHMlMjBSU0ElMjBDQS5jcmwwawYIKwYBBQUHAQEEXzBd 23 | MFsGCCsGAQUFBzAChk9odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Nl 24 | cnRzL01pY3Jvc29mdCUyMFNDRCUyMFByb2R1Y3RzJTIwUlNBJTIwQ0EuY3J0MAwG 25 | A1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEMBQADggIBAGUC0GBN5viiEbQWT2Mv1BNN 26 | 95rYHcdtEgYbGr9Pkks46FY4wPys4YzDR6m7hB2JiMbXMsyzldgjZdjYQniW5/yv 27 | 4QyH02cXSWrqR50NSq4QKpsx+jVwvlZXB3le6zbqmnNAWz+MsXxS4XKOgiV3ND1B 28 | J0163zt5+fX94gTyno4b39+oND1jY0N20AWupTC9LoeWZcxvXi3/Nf40w5ugANHX 29 | B6WAqQSmv1EudOyB9xzoBDe0voafm0F8Y6r9gj/KL6F5Qi7ZWEfk22z1trYOw2cY 30 | DwnH3uGNW5kev9cvzEP5WrkYZxJcj/00fzTfJ9H6iYRvvxwmQuRsuj9mLjgNVBSp 31 | nbATrdTtuZ7jIc0VQsMgtJFR8I1pbTIOZdD02J/FCiJYyox+Vqq+yuDLy+00q4dH 32 | uQOYoaRskQCOtKoaPBd0Y1RG6DvKxUtcotC2UTSvTWndQjxcnvPaGLr4QGJEiMw7 33 | Rnn4QK+x+8V8jBO8am0cUFr2Qa6xEhwHk+1Pf7pOnBJ6/SjyGzLTfpdGD4L7yQZ4 34 | eQFHono5+7KvmA/hFow+cnl8FPRi0UqZ01UoAuQz8h0XMyXqytE24zJuosJv/kfp 35 | U7g3ohASr7LwgJvbzTyZmwrCe4Lh43cW9z4ADxYSCMptWrKddNA4xy0Hq+uPAzRV 36 | 3BesuHYDHLAmQOHINW9x 37 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # didx509cpp 2 | 3 | A header-only C++ library for verification of [did:x509](https://github.com/microsoft/did-x509) identifiers. 4 | 5 | [![Continuous Integration](https://github.com/microsoft/didx509cpp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/microsoft/didx509cpp/actions/workflows/ci.yml) [![CodeQL](https://github.com/microsoft/didx509cpp/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/microsoft/didx509cpp/actions/workflows/codeql-analysis.yml) 6 | 7 | ## Usage 8 | 9 | ```cpp 10 | #include 11 | 12 | std::string pem_chain = ...; 13 | std::string did = "did:x509:0:sha256:WE4P5dd8DnLHSkyHaIjhp4udlkF9LqoKwCvu9gl38jk::eku:1.3.6.1.4.1.311.10.3.13"; 14 | 15 | try { 16 | std::string doc = resolve(pem_chain, did); 17 | } catch (...) 18 | {...} 19 | 20 | // Or when resolving a historical did, for example for audit purposes 21 | 22 | try { 23 | std::string doc = resolve(pem_chain, did, true /* Ignore time */)); 24 | } catch (...) 25 | {...} 26 | ``` 27 | 28 | ## Contributing 29 | 30 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 31 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 32 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 33 | 34 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 35 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 36 | provided by the bot. You will only need to do this once across all repos using our CLA. 37 | 38 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 39 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 40 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 41 | 42 | ## Trademarks 43 | 44 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 45 | trademarks or logos is subject to and must follow 46 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 47 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 48 | Any use of third-party trademarks or logos are subject to those third-party's policies. 49 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: AlwaysBreak 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: DontAlign 9 | AlignOperands: false 10 | AlignTrailingComments: false 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | AfterExternBlock: true 33 | BeforeCatch: true 34 | BeforeElse: true 35 | IndentBraces: false 36 | SplitEmptyFunction: false 37 | SplitEmptyRecord: false 38 | SplitEmptyNamespace: false 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Custom 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: false 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: AfterColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 80 48 | CommentPragmas: "^ IWYU pragma:" 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 51 | ConstructorInitializerIndentWidth: 2 52 | ContinuationIndentWidth: 2 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: false 58 | ForEachMacros: 59 | - FOREACH 60 | IncludeBlocks: Regroup 61 | IncludeCategories: 62 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 63 | Priority: 2 64 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 65 | Priority: 3 66 | - Regex: ".*" 67 | Priority: 1 68 | IncludeIsMainRegex: "(Test)?$" 69 | IndentCaseLabels: true 70 | IndentPPDirectives: AfterHash 71 | IndentWidth: 2 72 | IndentWrappedFunctionNames: false 73 | JavaScriptQuotes: Leave 74 | JavaScriptWrapImports: true 75 | KeepEmptyLinesAtTheStartOfBlocks: false 76 | MacroBlockBegin: "" 77 | MacroBlockEnd: "" 78 | MaxEmptyLinesToKeep: 1 79 | NamespaceIndentation: All 80 | ObjCBlockIndentWidth: 2 81 | ObjCSpaceAfterProperty: false 82 | ObjCSpaceBeforeProtocolList: true 83 | PenaltyBreakAssignment: 2 84 | PenaltyBreakBeforeFirstCallParameter: 19 85 | PenaltyBreakComment: 300 86 | PenaltyBreakFirstLessLess: 120 87 | PenaltyBreakString: 1000 88 | PenaltyExcessCharacter: 1000000 89 | PenaltyReturnTypeOnItsOwnLine: 600 90 | PointerAlignment: Left 91 | ReflowComments: true 92 | SortIncludes: true 93 | SortUsingDeclarations: true 94 | SpaceAfterCStyleCast: false 95 | SpaceAfterTemplateKeyword: true 96 | SpaceBeforeAssignmentOperators: true 97 | SpaceBeforeParens: ControlStatements 98 | SpaceInEmptyParentheses: false 99 | SpacesBeforeTrailingComments: 1 100 | SpacesInAngles: false 101 | SpacesInContainerLiterals: false 102 | SpacesInCStyleCastParentheses: false 103 | SpacesInParentheses: false 104 | SpacesInSquareBrackets: false 105 | Standard: Cpp11 106 | TabWidth: 2 107 | UseTab: Never 108 | --- 109 | 110 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - "releases/**" 8 | tags: 9 | - "didx509cpp-*" 10 | - "v*" 11 | pull_request: 12 | 13 | jobs: 14 | build-test-ubuntu: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ubuntu-22.04, ubuntu-24.04] 20 | build_type: [Debug, Release] 21 | compiler: [clang++-15, clang++-18] 22 | exclude: 23 | - os: ubuntu-22.04 24 | compiler: clang++-18 25 | - os: ubuntu-24.04 26 | compiler: clang++-15 27 | 28 | steps: 29 | - name: Install packages 30 | run: | 31 | sudo apt update 32 | sudo apt install libcurl4-openssl-dev libssl-dev 33 | 34 | - name: Install clang 15 35 | if: matrix.compiler == 'clang++-15' 36 | run: sudo apt install clang-15 37 | 38 | - name: Install clang 18 39 | if: matrix.compiler == 'clang++-18' 40 | run: sudo apt install clang-18 41 | 42 | - uses: actions/checkout@v4 43 | 44 | - name: Create Build Environment 45 | run: cmake -E make_directory build/${{ matrix.build_type }} 46 | 47 | - name: Configure CMake 48 | shell: bash 49 | working-directory: build/${{ matrix.build_type }} 50 | run: | 51 | cmake $GITHUB_WORKSPACE -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DTESTS=ON 52 | 53 | - name: Build 54 | working-directory: build/${{ matrix.build_type }} 55 | shell: bash 56 | run: cmake --build . --config ${{ matrix.build_type }} 57 | 58 | - name: Test 59 | working-directory: build/${{ matrix.build_type }} 60 | shell: bash 61 | run: ctest -C ${{ matrix.build_type }} -VV 62 | 63 | build-test-azure-linux: 64 | runs-on: ubuntu-latest 65 | container: 66 | image: mcr.microsoft.com/azurelinux/base/core:3.0 67 | 68 | strategy: 69 | fail-fast: false 70 | matrix: 71 | build_type: [Debug, Release] 72 | compiler: [g++, clang++] 73 | 74 | steps: 75 | - name: Checkout dependencies 76 | shell: bash 77 | run: | 78 | set -ex 79 | gpg --import /etc/pki/rpm-gpg/MICROSOFT-RPM-GPG-KEY 80 | tdnf -y update 81 | tdnf -y install ca-certificates git 82 | 83 | - name: Install clang 84 | if: matrix.compiler == 'clang++' 85 | run: tdnf -y install clang 86 | 87 | - uses: actions/checkout@v4 88 | 89 | - name: Build dependencies 90 | shell: bash 91 | run: tdnf -y install build-essential cmake openssl-devel curl-devel clang-tools-extra 92 | 93 | - name: Create Build Environment 94 | run: cmake -E make_directory build/${{ matrix.build_type }} 95 | 96 | - name: Configure CMake 97 | shell: bash 98 | working-directory: build/${{ matrix.build_type }} 99 | run: | 100 | cmake $GITHUB_WORKSPACE -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DTESTS=ON 101 | 102 | - name: Build 103 | working-directory: build/${{ matrix.build_type }} 104 | shell: bash 105 | run: cmake --build . --config ${{ matrix.build_type }} 106 | 107 | - name: Clang-Tidy 108 | working-directory: build/${{ matrix.build_type }} 109 | shell: bash 110 | run: clang-tidy ../../didx509cpp.h 111 | 112 | - name: Test 113 | working-directory: build/${{ matrix.build_type }} 114 | shell: bash 115 | run: ctest -C ${{ matrix.build_type }} -VV 116 | -------------------------------------------------------------------------------- /test/test-data/ms-test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFCzCCA/OgAwIBAgITMwAAB3FVMYJn0pcXPAABAAAHcTANBgkqhkiG9w0BAQsF 3 | ADB5MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH 4 | UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSMwIQYDVQQD 5 | ExpNaWNyb3NvZnQgVGVzdGluZyBQQ0EgMjAxMDAeFw0yNTAzMjAyMDAxMDRaFw0y 6 | NjAzMTgyMDAxMDRaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u 7 | MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp 8 | b24xJjAkBgNVBAMTHUNvZGUgU2lnbiBUZXN0IChETyBOT1QgVFJVU1QpMIIBIjAN 9 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsaHrpPpOvQX3rtyVTkstC6zfBrsF 10 | mOWycvGTFAGfZu8bImj3fDUVHTdfMkh2f3ukn0gZhdEN5yVbglACUytofDIFsekB 11 | phVpAEs9oKU79WwrcdtW9d7UoSqzSYuToNn0scf3VtcXLbcBepoixzKiZb0il016 12 | tGf/MWDR1PNTguqfz9PhgQAgG2IitwLBfkhfwzTwtSh4/xa/0ca8sYVgmUWqzGfx 13 | eaH5dXbcLBJr3QQJBFs78qhT5LmifP+eAtQc1kVRJLdSN80jyGwNKHw7DE0Xukhw 14 | RAj8TwzrkyE6db/avI7xi0D/AYuDe5hwj8+W0vsICsNPzglWAbvI0ur+GQIDAQAB 15 | o4IBhzCCAYMwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0G 16 | A1UdDgQWBBSPOAT2ATbgtFDPpo73famzNoMJITBFBgNVHREEPjA8pDowODEeMBwG 17 | A1UECxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMRYwFAYDVQQFEw0yMzAwNzIrNTA0 18 | MDIyMB8GA1UdIwQYMBaAFL9loqtvdaNORZZXBc85h/TAFRwcMFwGA1UdHwRVMFMw 19 | UaBPoE2GS2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jv 20 | c29mdCUyMFRlc3RpbmclMjBQQ0ElMjAyMDEwKDEpLmNybDBpBggrBgEFBQcBAQRd 21 | MFswWQYIKwYBBQUHMAKGTWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv 22 | Y2VydHMvTWljcm9zb2Z0JTIwVGVzdGluZyUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG 23 | A1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAIsMeYB5p0Pkdg5fJ776+zFr 24 | eOWQL8CtbHOTWXPwOZfu0UJC5yXaKKIaf/ewCm5Si0XtWOrbocsQwqvwpz4Wbz4V 25 | zbBgUCGTH/pfaPDqb0IXe3cPR+nY8m0dg0d+FnsdsJl2NZmhPa2br0xshRivblIj 26 | m7szHj0oivg0eMLqzzVOoVic/Wf2D7DEbHwILrcWESyCgUqLafrg/VQev3UfoNhr 27 | kSzxe9kCfN7kh6RPvsULUfAYHT07JBxRpZqidSxzg5tf3VCDj5znFwlCkX/8DL0j 28 | dLUbOvTKbYJ5cKpCHH7Yx1Jql3rFf5Qg89U0wd4QeUfuuj67v8Yw3pJZ1zziLy4= 29 | -----END CERTIFICATE----- 30 | -----BEGIN CERTIFICATE----- 31 | MIIGkzCCBHugAwIBAgITMwAAAC01ekaIyQdx2AAAAAAALTANBgkqhkiG9w0BAQsF 32 | ADCBkDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT 33 | B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE6MDgGA1UE 34 | AxMxTWljcm9zb2Z0IFRlc3RpbmcgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkg 35 | MjAxMDAeFw0yMDEyMTAyMDQzMjBaFw0zNTA2MTcyMTA0MTFaMHkxCzAJBgNVBAYT 36 | AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD 37 | VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xIzAhBgNVBAMTGk1pY3Jvc29mdCBU 38 | ZXN0aW5nIFBDQSAyMDEwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 39 | vzxggau+7P/XF2PypkLRE2KcsBfOukYaeyIuVXOaVLnG1NHKmP53Rw2OnfBezPhU 40 | 7/LPKtRi8ak0CgTXxQWG8hD1TdOWCGaF2wJ9GNzieiOnmildrnkYzwxj8Br/gamp 41 | Qz+pC7lR8bNIOvxELl8RxVY6/8oOzYgIwf3H1fU+7+pOG3KLI71FN54fcMGnybgg 42 | c+3zbD2LIQXPdxL+odwH6Q1beAlsMlUQR9A3yMf3+nP+RjTkVhaoN2RT1jX7w4C2 43 | jraGkaEQ1sFK9uN61BEKst4unhCX4IGuEl2IAV3MpMQoUpxg8ArmiK9L6VeK7KMP 44 | Nx4p9l0h09faXQ7JTtuNbQIDAQABo4IB+jCCAfYwDgYDVR0PAQH/BAQDAgGGMBIG 45 | CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFOqfXzO20F+erestpsEC 46 | u0A4y+e1MB0GA1UdDgQWBBS/ZaKrb3WjTkWWVwXPOYf0wBUcHDBUBgNVHSAETTBL 47 | MEkGBFUdIAAwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t 48 | L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBkGCSsGAQQBgjcUAgQMHgoAUwB1 49 | AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUowEEfjCIM+u5MZzK 50 | 64V2Z/xltNEwWQYDVR0fBFIwUDBOoEygSoZIaHR0cDovL2NybC5taWNyb3NvZnQu 51 | Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGVzUm9vQ2VyQXV0XzIwMTAtMDYtMTcu 52 | Y3JsMIGNBggrBgEFBQcBAQSBgDB+ME0GCCsGAQUFBzAChkFodHRwOi8vd3d3Lm1p 53 | Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Rlc1Jvb0NlckF1dF8yMDEwLTA2LTE3 54 | LmNydDAtBggrBgEFBQcwAYYhaHR0cDovL29uZW9jc3AubWljcm9zb2Z0LmNvbS9v 55 | Y3NwMA0GCSqGSIb3DQEBCwUAA4ICAQAntNCFsp7MD6QqU3PVbdrXMQDI9v9jyPYB 56 | EbUYktrctPmvJuj8Snm9wWewiAN5Zc81NQVYjuKDBpb1un4SWVCb4PDVPZ0J87tG 57 | zYe9dOJ30EYGeiIaaStkLLmLOYAM6oInIqIwVyIk2SE/q2lGt8OvwcZevNmPkVYj 58 | k6nyJi5EdvS6ciPRmW9bRWRT4pWU8bZIQL938LE4lHOQAixrAQiWes5Szp2U85E0 59 | nLdaDr5w/I28J/Z1+4zW1Nao1prVCOqrosnoNUfVf1kvswfW3FY2l1PiAYp8sGyO 60 | 57GaztXdBoEOBcDLedfcPra9+NLdEF36NkE0g+9dbokFY7KxhUJ8WpMiCmN4yj9L 61 | KFLvQbctGMJJY9EwHFifm2pgaiaafKF1Gyz+NruJzEEgpysMo/f9AVBQ/qCdPQQG 62 | EWp3QDIaef4ts9QTx+RmDKCBDMTFLgFmmhbtUY0JWjLkKn7soz/LIcDUle/p5TiF 63 | D4VhfZnAcvYQHXfuslnyp+yuhWzASnAQNnOIO6fc1JFIwkDkcM+k/TspfAajzHoo 64 | SAwXkrOWrjRDV6wI0YzMVHrEyQ0hZ5NnIXbL3lrTkOPjf3NBu1naSNEaySduStDb 65 | FVjV3TXoENEnZiugJKYSwmhzoYHM1ngipN5rNdqJiK5ukp6E8LDzi3l5/7XctJQY 66 | 3+ZgHDJosg== 67 | -----END CERTIFICATE----- 68 | -----BEGIN CERTIFICATE----- 69 | MIIF/TCCA+WgAwIBAgIQdEXIeE4MyZZKtC+82inhvDANBgkqhkiG9w0BAQsFADCB 70 | kDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl 71 | ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE6MDgGA1UEAxMx 72 | TWljcm9zb2Z0IFRlc3RpbmcgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx 73 | MDAeFw0xMDA2MTcyMDU4MDJaFw0zNTA2MTcyMTA0MTFaMIGQMQswCQYDVQQGEwJV 74 | UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE 75 | ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTowOAYDVQQDEzFNaWNyb3NvZnQgVGVz 76 | dGluZyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMIICIjANBgkqhkiG 77 | 9w0BAQEFAAOCAg8AMIICCgKCAgEAleOowbmcJlSwme8mH6wexzCAu/U/8uS7+P4G 78 | agqmiLy0jEXgcFUZiLQFy7XBofrUfMJCUwecVFaol+CUab4TJO/liimcptArL4qm 79 | 6HlELovqyb64VIZTvgckNFQVIiABe4pG+9KRB5UJsFYRzHay0B9EeVI0KOxPScLL 80 | YdOG3OSjflWen+4Qb8/hPfi3hHmiO40csIF85EQH5M5GsJiDjYeP5fWuQHrx7T2b 81 | mnxK0bnDlAV73Nq4ztwebM/Znjfvw1o2e5CGRdz2Lsrd7t4n2XSaafXZXQktRUHM 82 | t8KC1CqMFiWSlz2UTokzflsDVM2wg6COQbeHjdkFY1L27uZOE51UzUn+44s7UJtI 83 | u7LlktSroMUQrz6xRSE0kNytuff+Ia7uUFh6OuWq2OOC1s9tTckVrJwxF6UWp0L2 84 | 2hJ4p2aQ7PzNAWP/8A664c3w22uaD/YPBAEJvJ/Ot2xRcFcIG/95mlJduqwU5Ttn 85 | zyxS3ieaNANuJUiwGXT8TZjCS4yS4YiuSCqrq80UTbZhDqEJjyzbRa99O4FWCMk7 86 | Qbdkn10uEn+5aSkfUkVKI8avtrI4cp0IM//Qz4m26m6FRJQ+kVnr7569m5waRwNO 87 | oheW+mIL6FO2TuPoKnNZ4hO4+Fp+xuIK3UpDzMN3O3oxBArBhJY6Y24aPgoMJbh+ 88 | tVIMuasCAwEAAaNRME8wCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYD 89 | VR0OBBYEFKMBBH4wiDPruTGcyuuFdmf8ZbTRMBAGCSsGAQQBgjcVAQQDAgEAMA0G 90 | CSqGSIb3DQEBCwUAA4ICAQBJi8H8T+jkLWeSmnYFutG8mOQruh9mX2Yjzxwn67Sq 91 | 3aAXIFVyM7F23sltDTwtCggkLew4lnqD8SdQPIYJ3Q1Bzqpe8496Pj7x8LqLct02 92 | oWkFW3zs53BjjR1uwP06A/EQPpDXe3rc6mDsL1P9GR06oXQIwns84FCsIde23d08 93 | RBv380Q+bJbgwJ/m793dsaZoYWxenvn/mgakas2edUOJm8uF9twMRkqMm6wRpmNF 94 | +/zeIO7OZ5890JPbOfvqXkv81iDxlTYIjLKzoZcbQRmwrP7i1at92SbU3L0fOMDj 95 | ht8k5/U+CcpNoboWw0qx/HKYzw6S+ldF6UhNxqJ8O3JjrE70dOkrV6yrMogLqRBn 96 | U37SYtL6aOidW67N4OXiBpYMNDL2vCWtmPMyYL4U03jREG//MuOePYjaszIKzyBl 97 | R3iqpUuHaoPcGloq33BhrzUy4FmhnwsUeqqrQgtr//s0y52W1yYqEzvj3xHmhn0N 98 | CRGTS6T20gfCzci+9Wf3rgXOFv6QyUqYGyRpeJD5NI436G4d3M9P59JkQB3EMLrV 99 | CIhnSw+45VnpGNgMYGiuf+qRVb7r8aeO2F1QPr/VaVeVj6f/5Ak/CICXMkK4gkOC 100 | b4sLk9oZv2NOX5/tLCK2IF9wRPqJWZOwexIPXmJiURG9ulrQzqG274Ag5nNLEQZW 101 | 4g== 102 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /test/test-data/ms-code-signing.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF/zCCA+egAwIBAgITMwAAAs+gJZDjEwTvFQAAAAACzzANBgkqhkiG9w0BAQsF 3 | ADB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH 4 | UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQD 5 | Ex9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMB4XDTIyMDUxMjIwNDYw 6 | NFoXDTIzMDUxMTIwNDYwNFowdDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp 7 | bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw 8 | b3JhdGlvbjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMIIBIjANBgkq 9 | hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9HduD2rvmO+SGksB4HR+qvSK379St8N 10 | nUZBH8xBiQvt2zONOLUHWQibeBW4NLUfHfzMaOM77RhNlqPNiDRKhChlG1aHqEHS 11 | AaQBGrmr0ULGIzq+1YvqQufMGYBFfq0sc10UdvWqT0RjwkPQTu4bjg37zSYF9OcG 12 | xS9uGnPMdWRM0ThOsYUcDmMoCaJRebsLUBpMmYXkcUYXJrcSGAaUNd0wjhwIpEog 13 | OD+AbWW/7TPZOl+JciMj40a78EEXIc2p06lWHfe5hegQ7uGIlSAPG6zDzjhjNkzE 14 | 63/+GoqJU+6QLazbL5/y27ZDUAEYJokbb305A+dOp930CjTar3BvWQIDAQABo4IB 15 | fjCCAXowHwYDVR0lBBgwFgYKKwYBBAGCNwoDFQYIKwYBBQUHAwMwHQYDVR0OBBYE 16 | FHs4/z9sVQLrJJTk5iEaOQwyHU0iMFAGA1UdEQRJMEekRTBDMSkwJwYDVQQLEyBN 17 | aWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEWMBQGA1UEBRMNMjMwMjE3 18 | KzQ3MDUzMjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8E 19 | TTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9N 20 | aWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBR 21 | BggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0 22 | cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAw 23 | DQYJKoZIhvcNAQELBQADggIBAIBiYcrj5Ph8uwFKZXw0eCS9qv2lk4lZY4Semy2D 24 | 4sfKDNUqKsqP5Q0zJcAq3Z+uEKc9Q8boxkm9/3PPESQKWhTRqLY+LL2XTjbm1S/L 25 | AhtQ09ftHkxwienGU+Xo8ntz6Z7iQV2xCqjTMRWGFysEKgMgdAMPftWPXNa9k1G9 26 | qEJpPcCLeiM6UEJdxnRDHKgDSugW4fYvcEXlOJJXn/VZr4fFJZ+xLGT+US/NwGwb 27 | 8DdoUYls2u5o2250nm0TA0cZkJCzrxzV6Fptv14jbPcTZpRU6D0zGSSLPaM2cA/A 28 | Q3yxRi9FZOpcbrJM+2Rp6aufmyxUgIN6MvG2IH2D++Xq3a4Zy+Gmce9thBRBff1i 29 | IROq6CdGJHbOVbfdivV3L7qBD9pQYqSKitq4fJV95iYEchgMoXGwkJwagXix+f8g 30 | jnOmlSjysSwzAmDwtAxUkX+lNoU5xUJLwf9/4nIXp7drjWptpn9IIiARLPFxLRYg 31 | 7S9digox7quSKM/xXb1bFzp346lwjuvK+QHC8pUOF8OojQ0YAZ+Q0EKKukchQ3wF 32 | 7RiHk/INqYgEFli/xpMzwVM2k91UlArvYylUKLGDGy8QabMosUrZdNQvBCWiePYR 33 | AaJR5t+IR5QeBNdaKEqh2EQ/VzCu7J247Q3UrZrPLUJ9bGp2INwL8jynhVOeZteW 34 | CEKV 35 | -----END CERTIFICATE----- 36 | -----BEGIN CERTIFICATE----- 37 | MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkG 38 | A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx 39 | HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9z 40 | b2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1 41 | OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz 42 | aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv 43 | cnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAy 44 | MDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqC 45 | EE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC 46 | 04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlIm 47 | Ei/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPe 48 | Bw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb 49 | 2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXD 50 | OW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yx 51 | kq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA 52 | 4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uD 53 | jexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ec 54 | XL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4Ta 55 | sIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQD 56 | AgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIE 57 | DB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNV 58 | HSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklo 59 | dHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29D 60 | ZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEF 61 | BQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29D 62 | ZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQB 63 | gjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br 64 | aW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBn 65 | AGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqG 66 | SIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8u 67 | LD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxC 68 | i1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQ 69 | u6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n 70 | 7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVY 71 | ODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38c 72 | bxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3 73 | BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmA 74 | H9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5 75 | GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF6 76 | 70EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzQ== 77 | -----END CERTIFICATE----- 78 | -----BEGIN CERTIFICATE----- 79 | MIIF7TCCA9WgAwIBAgIQP4vItfyfspZDtWnWbELhRDANBgkqhkiG9w0BAQsFADCB 80 | iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl 81 | ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp 82 | TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEw 83 | MzIyMjIwNTI4WhcNMzYwMzIyMjIxMzA0WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV 84 | BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv 85 | c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm 86 | aWNhdGUgQXV0aG9yaXR5IDIwMTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK 87 | AoICAQCygEGqNThNE3IyaCJNuLLx/9VSvGzH9dJKjDbu0cJcfoyKrq8TKG/Ac+M6 88 | ztAlqFo6be+ouFmrEyNozQwph9FvgFyPRH9dkAFSWKxRxV8qh9zc2AodwQO5e7BW 89 | 6KPeZGHCnvjzfLnsDbVU/ky2ZU+I8JxImQxCCwl8MVkXeQZ4KI2JOkwDJb5xalwL 90 | 54RgpJki49KvhKSn+9GY7Qyp3pSJ4Q6g3MDOmT3qCFK7VnnkH4S6Hri0xElcTzFL 91 | h93dBWcmmYDgcRGjuKVB4qRTufcyKYMME782XgSzS0NHL2vikR7TmE/dQgfI6B0S 92 | /Jmpaz6SfsjWaTr8ZL22CZ3K/QwLopt3YEsDlKQwaRLWQi3BQUzK3Kr9j1uDRprZ 93 | /LHR47PJf0h6zSTwQY9cdNCssBAgBkm3xy0hyFfj0IbzA2j70M5xwYmZSmQBbP3s 94 | MJHPQTySx+W6hh1hhMdfgzlirrSSL0fzC/hV66AfWdC7dJse0Hbm8ukG1xDo+mTe 95 | acY1logC8Ea4PyeZb8txiSk190gWAjWP1Xl8TQLPX+uKg09FcYj5qQ1OcunCnAfP 96 | SRtOBA5jUYxe2ADBVSy2xuDCZU7JNDn1nLPEfuhhbhNfFcRf2X7tHc7uROzLLoax 97 | 7Dj2cO2rXBPB2Q8Nx4CyVe0096yb5MPa50c8prWPMd/FS6/r8QIDAQABo1EwTzAL 98 | BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUci06AjGQQ7kU 99 | BU7h6qfHMdEjiTQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQELBQADggIB 100 | AH9yzw+3xRXbm8BJyiZb/p4T5tPw0tuXX/JLP02zrhmu7deXoKzvqTqjwkGw5biR 101 | nhOBJAPmCf0/V0A5ISRW0RAvS0CpNoZLtFNXmvvxfomPEf4YbFGq6O0JlbXlccmh 102 | 6Yd1phV/yX43VF50k8XDZ8wNT2uoFwxtCJJ+i92Bqi1wIcM9BhS7vyRep4TXPw8h 103 | Ir1LAAbblxzYXtTFC1yHblCk6MM4pPvLLMWSZpuFXst6bJN8gClYW1e1QGm6CHmm 104 | ZGIVnYeWRbVmIyADixxzoNOieTPgUFmG2y/lAiXqcyqfABTINseSO+lOAOzYVgm5 105 | M0kS0lQLAausR7aRKX1MtHWAUgHoyoL2n8ysnI8X6i8msKtyrAv+nlEex0NVZ09R 106 | s1fWtuzuUrc66U7h14GIvE+OdbtLqPA1qibUZ2dJsnBMO5PcHd94kIZysjik0dyS 107 | TclY6ysSXNQ7roxrsIPlAT/4CTL2kzU0Iq/dNw13CYArzUgA8YyZGUcFAenRv9FO 108 | 0OYoQzeZpApKCNmacXPSqs0xE2N2oTdvkjgefRI8ZjLny23h/FKJ3crWZgWalmG+ 109 | oijHHKOnNlA8OqTfSm7mhzvO6/DggTedEzxSjr25HTTGHdUKaj2YKXCMiSrRq4IQ 110 | SB/c9O+lxbtVGjhjhE63bK2VVOxlIhBJF7jAHscPrFRH 111 | -----END CERTIFICATE----- 112 | -------------------------------------------------------------------------------- /test/unit_tests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | #include "didx509cpp.h" 5 | 6 | #include 7 | 8 | #define DOCTEST_CONFIG_IMPLEMENT 9 | #include "doctest.h" 10 | #include "json.hpp" 11 | 12 | using namespace didx509; 13 | 14 | static std::string test_data_dir = "../test/test-data"; 15 | 16 | static std::string load_certificate_chain(const std::string& path) 17 | { 18 | std::ifstream t(test_data_dir + "/" + path); 19 | if (!t.good()) 20 | throw std::runtime_error(std::string("could not open ") + path); 21 | std::stringstream ss; 22 | ss << t.rdbuf(); 23 | return ss.str(); 24 | } 25 | 26 | static std::vector split_x509_cert_bundle( 27 | const std::string_view& pem) 28 | { 29 | std::string separator("-----END CERTIFICATE-----"); 30 | std::vector pems; 31 | size_t separator_end = 0; 32 | auto next_separator_start = pem.find(separator); 33 | while (next_separator_start != std::string_view::npos) 34 | { 35 | // Trim whitespace between certificates 36 | while (separator_end < next_separator_start && 37 | (std::isspace(pem[separator_end]) != 0)) 38 | { 39 | ++separator_end; 40 | } 41 | pems.emplace_back(std::string(pem.substr( 42 | separator_end, 43 | (next_separator_start - separator_end) + separator.size()))); 44 | separator_end = next_separator_start + separator.size(); 45 | next_separator_start = pem.find(separator, separator_end); 46 | } 47 | return pems; 48 | } 49 | 50 | void test_resolve_success(const std::string& chain, const std::string& did) 51 | { 52 | std::string did_doc; 53 | REQUIRE_NOTHROW(did_doc = resolve(chain, did, true)); 54 | // Verify that resolved DID document is valid JSON 55 | REQUIRE_NOTHROW(auto _ = nlohmann::json::parse(did_doc)); 56 | 57 | std::string jwk; 58 | const auto split_chain = split_x509_cert_bundle(chain); 59 | REQUIRE_NOTHROW(jwk = resolve_jwk(split_chain, did, true)); 60 | // Verify that resolved JWK is valid JSON 61 | REQUIRE_NOTHROW(auto _ = nlohmann::json::parse(jwk)); 62 | } 63 | 64 | void test_resolve_error( 65 | const std::string& chain, 66 | const std::string& did, 67 | const doctest::String& error_msg) 68 | { 69 | REQUIRE_THROWS_WITH(resolve(chain, did, true), doctest::Contains(error_msg)); 70 | } 71 | 72 | void test_resolve_jwk_error( 73 | const std::vector& chain, 74 | const std::string& did, 75 | const doctest::String& error_msg) 76 | { 77 | REQUIRE_THROWS_WITH(resolve_jwk(chain, did, true), doctest::Contains(error_msg)); 78 | } 79 | 80 | TEST_CASE("Wrong prefix") 81 | { 82 | auto chain = load_certificate_chain("ms-code-signing.pem"); 83 | auto did = "djd:y508:1:abcd::"; 84 | test_resolve_error(chain, did, "unsupported method/prefix"); 85 | } 86 | 87 | TEST_CASE("Empty chain") 88 | { 89 | auto chain = ""; 90 | auto did = "djd:y508:1:abcd::"; 91 | test_resolve_error(chain, did, "no certificate chain"); 92 | } 93 | 94 | TEST_CASE("Chain of one not-a-cert-but-a-chain") 95 | { 96 | auto chain = load_certificate_chain("ms-code-signing.pem"); 97 | auto did = 98 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 99 | "::subject:CN:Microsoft%20Corporation"; 100 | test_resolve_jwk_error({chain}, did, "expected exactly one PEM element"); 101 | } 102 | 103 | TEST_CASE("Invalid input") 104 | { 105 | auto did = 106 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 107 | "::subject:CN:Microsoft%20Corporation"; 108 | test_resolve_jwk_error({"-----BEGIN CERTIFICATE-----"}, did, "bad end line"); 109 | auto chain = load_certificate_chain("ms-code-signing.pem"); 110 | auto split_chain = split_x509_cert_bundle(chain); 111 | split_chain[0][42] += 5; 112 | test_resolve_jwk_error(split_chain, did, "bad base64 decode"); 113 | split_chain[0][42] -= 10; 114 | test_resolve_jwk_error(split_chain, did, "asn1 encoding routines::too long"); 115 | } 116 | 117 | TEST_CASE("TestRootCA") 118 | { 119 | auto chain = load_certificate_chain("ms-code-signing.pem"); 120 | auto did = 121 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 122 | "::subject:CN:Microsoft%20Corporation"; 123 | test_resolve_success(chain, did); 124 | } 125 | 126 | TEST_CASE("TestIntermediateCA") 127 | { 128 | auto chain = load_certificate_chain("ms-code-signing.pem"); 129 | auto did = 130 | "did:x509:0:sha256:VtqHIq_ZQGb_4eRZVHOkhUiSuEOggn1T-32PSu7R4Ys" 131 | "::subject:CN:Microsoft%20Corporation"; 132 | test_resolve_success(chain, did); 133 | } 134 | 135 | TEST_CASE("TestInvalidLeafCA") 136 | { 137 | auto chain = load_certificate_chain("ms-code-signing.pem"); 138 | auto did = "did:x509:0:sha256:h::subject:CN:Microsoft%20Corporation"; 139 | test_resolve_error(chain, did, "invalid certificate fingerprint"); 140 | } 141 | 142 | TEST_CASE("TestInvalidCA") 143 | { 144 | auto chain = load_certificate_chain("ms-code-signing.pem"); 145 | auto did = "did:x509:0:sha256:abc::CN:Microsoft%20Corporation"; 146 | test_resolve_error(chain, did, "invalid certificate fingerprint"); 147 | } 148 | 149 | TEST_CASE("TestMultiplePolicies") 150 | { 151 | auto chain = load_certificate_chain("ms-code-signing.pem"); 152 | auto did = 153 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 154 | "::eku:1.3.6.1.5.5.7.3.3" 155 | "::eku:1.3.6.1.4.1.311.10.3.21"; 156 | test_resolve_success(chain, did); 157 | } 158 | 159 | TEST_CASE("TestSubject") 160 | { 161 | auto chain = load_certificate_chain("ms-code-signing.pem"); 162 | auto did = 163 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 164 | "::subject:CN:Microsoft%20Corporation"; 165 | test_resolve_success(chain, did); 166 | } 167 | 168 | TEST_CASE("TestSubjectWithStateST") 169 | { 170 | auto chain = load_certificate_chain("ms-code-signing.pem"); 171 | auto did = 172 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 173 | "::subject:CN:Microsoft%20Corporation:ST:Washington"; 174 | test_resolve_success(chain, did); 175 | chain = load_certificate_chain("ms-test.pem"); 176 | did = 177 | "did:x509:0:sha256:m9D3z27ZZ1GTkbzUmpWIZ7lVpg8i3luJeEdKL8utgaY" 178 | "::subject:C:US:ST:Washington:L:Redmond:O:Microsoft%20" 179 | "Corporation:CN:Code%20Sign%20Test%20%28DO%20NOT%20TRUST%29"; 180 | test_resolve_success(chain, did); 181 | } 182 | 183 | TEST_CASE("TestSubjectWithStateS") 184 | { 185 | auto chain = load_certificate_chain("ms-code-signing.pem"); 186 | auto did = 187 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 188 | "::subject:CN:Microsoft%20Corporation:S:Washington"; 189 | test_resolve_success(chain, did); 190 | chain = load_certificate_chain("ms-test.pem"); 191 | did = 192 | "did:x509:0:sha256:m9D3z27ZZ1GTkbzUmpWIZ7lVpg8i3luJeEdKL8utgaY" 193 | "::subject:C:US:S:Washington:L:Redmond:O:Microsoft%20" 194 | "Corporation:CN:Code%20Sign%20Test%20%28DO%20NOT%20TRUST%29"; 195 | test_resolve_success(chain, did); 196 | } 197 | 198 | TEST_CASE("TestSubjectWithStateSandST") 199 | { 200 | auto chain = load_certificate_chain("ms-code-signing.pem"); 201 | auto did = 202 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 203 | "::subject:CN:Microsoft%20Corporation:S:Washington:ST:Washington"; 204 | test_resolve_error(chain, did, "duplicate field 'ST'"); 205 | did = 206 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 207 | "::subject:CN:Microsoft%20Corporation:ST:Washington:S:Washington"; 208 | test_resolve_error(chain, did, "duplicate field 'ST'"); 209 | } 210 | 211 | TEST_CASE("TestSubjectInvalidName") 212 | { 213 | auto chain = load_certificate_chain("ms-code-signing.pem"); 214 | auto did = 215 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 216 | "::subject:CN:MicrosoftCorporation"; 217 | test_resolve_error(chain, did, "invalid subject key/value"); 218 | } 219 | 220 | TEST_CASE("TestSubjectDuplicateField") 221 | { 222 | auto chain = load_certificate_chain("ms-code-signing.pem"); 223 | auto did = 224 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 225 | "::subject:CN:Microsoft%20Corporation:CN:Microsoft%20Corporation"; 226 | test_resolve_error(chain, did, "duplicate field"); 227 | } 228 | 229 | TEST_CASE("TestSAN") 230 | { 231 | auto chain = load_certificate_chain("fulcio-email.pem"); 232 | auto did = 233 | "did:x509:0:sha256:O6e2zE6VRp1NM0tJyyV62FNwdvqEsMqH_07P5qVGgME" 234 | "::san:email:igarcia%40suse.com"; 235 | test_resolve_success(chain, did); 236 | } 237 | 238 | TEST_CASE("TestSANInvalidType") 239 | { 240 | auto chain = load_certificate_chain("fulcio-email.pem"); 241 | auto did = 242 | "did:x509:0:sha256:O6e2zE6VRp1NM0tJyyV62FNwdvqEsMqH_07P5qVGgME" 243 | "::san:uri:igarcia%40suse.com"; 244 | test_resolve_error(chain, did, "SAN not found"); 245 | } 246 | 247 | TEST_CASE("TestSANInvalidValue") 248 | { 249 | auto chain = load_certificate_chain("fulcio-email.pem"); 250 | auto did = 251 | "did:x509:0:sha256:O6e2zE6VRp1NM0tJyyV62FNwdvqEsMqH_07P5qVGgME" 252 | "::email:bob%40example.com"; 253 | test_resolve_error(chain, did, "unsupported did:x509 scheme"); 254 | } 255 | 256 | TEST_CASE("TestBadEKU") 257 | { 258 | auto chain = load_certificate_chain("ms-code-signing.pem"); 259 | auto did = 260 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 261 | "::eku:1.3.6.1.5.5.7.3.12"; 262 | test_resolve_error(chain, did, "EKU not found"); 263 | } 264 | 265 | TEST_CASE("TestGoodEKU") 266 | { 267 | auto chain = load_certificate_chain("ms-code-signing.pem"); 268 | auto did = 269 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 270 | "::eku:1.3.6.1.4.1.311.10.3.21"; 271 | test_resolve_success(chain, did); 272 | } 273 | 274 | TEST_CASE("TestEKUInvalidValue") 275 | { 276 | auto chain = load_certificate_chain("ms-code-signing.pem"); 277 | auto did = 278 | "did:x509:0:sha256:hH32p4SXlD8n_HLrk_mmNzIKArVh0KkbCeh6eAftfGE" 279 | "::eku:1.2.3"; 280 | test_resolve_error(chain, did, "EKU not found"); 281 | } 282 | 283 | TEST_CASE("TestFulcioIssuerWithEmailSAN") 284 | { 285 | auto chain = load_certificate_chain("fulcio-email.pem"); 286 | auto did = 287 | "did:x509:0:sha256:O6e2zE6VRp1NM0tJyyV62FNwdvqEsMqH_07P5qVGgME" 288 | "::fulcio-issuer:github.com%2Flogin%2Foauth" 289 | "::san:email:igarcia%40suse.com"; 290 | test_resolve_success(chain, did); 291 | } 292 | 293 | TEST_CASE("TestFulcioIssuerWithURISAN") 294 | { 295 | auto chain = load_certificate_chain("fulcio-github-actions.pem"); 296 | auto did = 297 | "did:x509:0:sha256:O6e2zE6VRp1NM0tJyyV62FNwdvqEsMqH_07P5qVGgME" 298 | "::fulcio-issuer:token.actions.githubusercontent.com" 299 | "::san:uri:https%3A%2F%2Fgithub.com%2Fbrendancassells%2Fmcw-continuous-" 300 | "delivery-lab-files%2F.github%2Fworkflows%2Ffabrikam-web.yml%40refs%" 301 | "2Fheads%2Fmain"; 302 | test_resolve_success(chain, did); 303 | } 304 | 305 | TEST_CASE("TestInvalidLeafOnly") 306 | { 307 | auto chain = load_certificate_chain("containerplat-leaf.pem"); 308 | REQUIRE_THROWS_WITH( 309 | resolve( 310 | chain, 311 | "did:x509:0:sha256:pDI-AL3g4rw3cHMC_dmMKpdzFF8JMFzWvfIzbK9_DbQ" 312 | "::eku:1.3.6.1.4.1.311.76.59.1.2", 313 | true), 314 | doctest::Contains("certificate chain too short")); 315 | } 316 | 317 | int main(int argc, char** argv) 318 | { 319 | doctest::Context ctx; 320 | ctx.applyCommandLine(argc, argv); 321 | for (size_t i = 0; i < argc; i++) 322 | if (strcmp(argv[i], "--data-dir") == 0 && i < argc - 1) 323 | test_data_dir = argv[i + 1]; 324 | return ctx.run(); 325 | } -------------------------------------------------------------------------------- /didx509cpp.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 35 | # include 36 | # include 37 | #endif 38 | 39 | namespace didx509 40 | { 41 | namespace 42 | { 43 | inline std::string error_string(unsigned long ec) 44 | #ifdef _DEBUG 45 | __attribute__((noinline)) 46 | #endif 47 | { 48 | if (ec != 0) 49 | { 50 | return {ERR_error_string(ec, nullptr)}; 51 | } 52 | return "unknown error"; 53 | } 54 | 55 | /// Throws if rc is different from and there is an error 56 | inline void CHECK1(int rc) 57 | #ifdef _DEBUG 58 | __attribute__((noinline)) 59 | #endif 60 | { 61 | const unsigned long ec = ERR_get_error(); 62 | if (rc != 1 && ec != 0) 63 | { 64 | throw std::runtime_error( 65 | std::string("OpenSSL error: ") + error_string(ec)); 66 | } 67 | } 68 | 69 | /// Throws if rc is 0 and there is an error 70 | inline void CHECK0(int rc) 71 | #ifdef _DEBUG 72 | __attribute__((noinline)) 73 | #endif 74 | { 75 | const unsigned long ec = ERR_get_error(); 76 | if (rc == 0 && ec != 0) 77 | { 78 | throw std::runtime_error( 79 | std::string("OpenSSL error: ") + error_string(ec)); 80 | } 81 | } 82 | 83 | /// Throws if ptr is nullptr 84 | inline void CHECKNULL(void* ptr) 85 | #ifdef _DEBUG 86 | __attribute__((noinline)) 87 | #endif 88 | { 89 | if (ptr == nullptr) 90 | { 91 | const unsigned long ec = ERR_get_error(); 92 | throw std::runtime_error( 93 | std::string("OpenSSL error: missing object: ") + error_string(ec)); 94 | } 95 | } 96 | 97 | inline std::string to_base64(const std::vector& bytes) 98 | { 99 | const int r_sz = 4 * ((bytes.size() + 2) / 3); 100 | std::string r(r_sz, 0); 101 | auto out_sz = 102 | EVP_EncodeBlock((unsigned char*)r.data(), bytes.data(), bytes.size()); 103 | if (r_sz != out_sz) 104 | { 105 | throw std::runtime_error("base64 conversion failed"); 106 | } 107 | while (r.back() == '=') 108 | { 109 | r.pop_back(); 110 | } 111 | return r; 112 | } 113 | 114 | inline std::string to_base64url(const std::vector& bytes) 115 | { 116 | auto r = to_base64(bytes); 117 | for (char & i: r) 118 | { 119 | if (i == '+') 120 | { 121 | i = '-'; 122 | } 123 | else if (i == '/') 124 | { 125 | i = '_'; 126 | } 127 | } 128 | return r; 129 | } 130 | 131 | template 132 | class UqSSLOBJECT 133 | { 134 | protected: 135 | std::unique_ptr p; 136 | 137 | public: 138 | UqSSLOBJECT() : p(CTOR(), DTOR) 139 | { 140 | CHECKNULL(p.get()); 141 | } 142 | 143 | UqSSLOBJECT(T* ptr, void (*dtor)(T*), bool check_null = true) : 144 | p(ptr, dtor) 145 | { 146 | if (check_null) 147 | { 148 | CHECKNULL(p.get()); 149 | } 150 | } 151 | 152 | UqSSLOBJECT(const UqSSLOBJECT&) = delete; 153 | UqSSLOBJECT& operator=(const UqSSLOBJECT&) = delete; 154 | 155 | operator T*() 156 | { 157 | return p.get(); 158 | } 159 | 160 | operator T*() const 161 | { 162 | return p.get(); 163 | } 164 | 165 | const T* operator->() const 166 | { 167 | return p.get(); 168 | } 169 | 170 | void reset(T* other) 171 | { 172 | p.reset(other); 173 | } 174 | 175 | T* release() 176 | { 177 | return p.release(); 178 | } 179 | }; 180 | 181 | struct UqBIGNUM : public UqSSLOBJECT 182 | { 183 | UqBIGNUM(const BIGNUM* n) : UqSSLOBJECT(BN_dup(n), BN_free) {} 184 | 185 | UqBIGNUM(UqBIGNUM&& other) noexcept : UqSSLOBJECT(nullptr, BN_free, false) 186 | { 187 | p = std::move(other.p); 188 | } 189 | }; 190 | 191 | struct UqBIO : public UqSSLOBJECT 192 | { 193 | UqBIO() : UqSSLOBJECT(BIO_new(BIO_s_mem()), [](auto x) { BIO_free(x); }) 194 | {} 195 | 196 | UqBIO(const void* buf, int len) : 197 | UqSSLOBJECT(BIO_new_mem_buf(buf, len), [](auto x) { BIO_free(x); }) 198 | {} 199 | 200 | UqBIO(const std::string& s) : 201 | UqSSLOBJECT( 202 | BIO_new_mem_buf(s.data(), s.size()), [](auto x) { BIO_free(x); }) 203 | {} 204 | 205 | UqBIO(const std::vector& d) : 206 | UqSSLOBJECT( 207 | BIO_new_mem_buf(d.data(), d.size()), [](auto x) { BIO_free(x); }) 208 | {} 209 | 210 | UqBIO(UqBIO&& b, UqBIO&& next) : 211 | UqSSLOBJECT(BIO_push(b, next), [](auto x) { BIO_free_all(x); }) 212 | { 213 | b.release(); 214 | next.release(); 215 | } 216 | 217 | [[nodiscard]] std::string to_string() const 218 | { 219 | BUF_MEM* bptr = nullptr; 220 | BIO_get_mem_ptr(p.get(), &bptr); 221 | return {bptr->data, bptr->length}; 222 | } 223 | 224 | [[nodiscard]] std::vector to_vector() const 225 | { 226 | BUF_MEM* bptr = nullptr; 227 | BIO_get_mem_ptr(p.get(), &bptr); 228 | return {bptr->data, bptr->data + bptr->length}; 229 | } 230 | }; 231 | 232 | struct UqASN1_OBJECT 233 | : public UqSSLOBJECT 234 | { 235 | UqASN1_OBJECT(const std::string& oid) : 236 | UqSSLOBJECT(OBJ_txt2obj(oid.c_str(), 1), ASN1_OBJECT_free) 237 | {} 238 | 239 | UqASN1_OBJECT(const ASN1_OBJECT* obj) : 240 | UqSSLOBJECT(OBJ_dup(obj), ASN1_OBJECT_free, true) 241 | {} 242 | 243 | UqASN1_OBJECT(int nid) : 244 | UqSSLOBJECT(OBJ_nid2obj(nid), ASN1_OBJECT_free, true) 245 | {} 246 | 247 | UqASN1_OBJECT(UqASN1_OBJECT&& other) noexcept : 248 | UqSSLOBJECT(nullptr, ASN1_OBJECT_free, false) 249 | { 250 | p = std::move(other.p); 251 | } 252 | 253 | bool operator==(const UqASN1_OBJECT& other) const 254 | { 255 | return OBJ_cmp(*this, other) == 0; 256 | } 257 | 258 | bool operator!=(const UqASN1_OBJECT& other) const 259 | { 260 | return !(*this == other); 261 | } 262 | }; 263 | 264 | struct UqASN1_OCTET_STRING : public UqSSLOBJECT< 265 | ASN1_OCTET_STRING, 266 | ASN1_OCTET_STRING_new, 267 | ASN1_OCTET_STRING_free> 268 | { 269 | UqASN1_OCTET_STRING(const ASN1_OCTET_STRING* str) : 270 | UqSSLOBJECT(ASN1_OCTET_STRING_dup(str), ASN1_OCTET_STRING_free) 271 | {} 272 | 273 | UqASN1_OCTET_STRING(UqASN1_OCTET_STRING&& other) noexcept : 274 | UqSSLOBJECT(nullptr, ASN1_OCTET_STRING_free, false) 275 | { 276 | p = std::move(other.p); 277 | } 278 | 279 | operator std::string() const 280 | { 281 | UqBIO bio; 282 | ASN1_STRING_print(bio, *this); 283 | return bio.to_string(); 284 | } 285 | }; 286 | 287 | struct UqX509_EXTENSION : public UqSSLOBJECT< 288 | X509_EXTENSION, 289 | X509_EXTENSION_new, 290 | X509_EXTENSION_free> 291 | { 292 | UqX509_EXTENSION(X509_EXTENSION* ext) : 293 | UqSSLOBJECT(X509_EXTENSION_dup(ext), X509_EXTENSION_free, true) 294 | {} 295 | 296 | UqX509_EXTENSION(const UqX509_EXTENSION& ext) : 297 | UqSSLOBJECT(X509_EXTENSION_dup(ext), X509_EXTENSION_free, true) 298 | {} 299 | 300 | UqX509_EXTENSION(UqX509_EXTENSION&& ext) noexcept : 301 | UqSSLOBJECT(ext, X509_EXTENSION_free, true) 302 | {} 303 | 304 | [[nodiscard]] UqASN1_OBJECT object() const 305 | { 306 | return X509_EXTENSION_get_object(*this); 307 | } 308 | 309 | [[nodiscard]] UqASN1_OCTET_STRING data() const 310 | { 311 | return X509_EXTENSION_get_data(*this); 312 | } 313 | }; 314 | 315 | struct UqGENERAL_NAME 316 | : public UqSSLOBJECT 317 | { 318 | UqGENERAL_NAME(GENERAL_NAME* n) : 319 | UqSSLOBJECT(GENERAL_NAME_dup(n), GENERAL_NAME_free) 320 | {} 321 | 322 | UqGENERAL_NAME(UqGENERAL_NAME&& other) noexcept : 323 | UqSSLOBJECT(nullptr, GENERAL_NAME_free, false) 324 | { 325 | p = std::move(other.p); 326 | } 327 | }; 328 | 329 | struct UqSUBJECT_ALT_NAME 330 | : public UqSSLOBJECT 331 | { 332 | UqSUBJECT_ALT_NAME() : 333 | UqSSLOBJECT(sk_GENERAL_NAME_new_null(), [](auto x) { 334 | sk_GENERAL_NAME_pop_free(x, GENERAL_NAME_free); 335 | }) 336 | {} 337 | 338 | UqSUBJECT_ALT_NAME(const UqX509_EXTENSION& ext) : 339 | UqSSLOBJECT( 340 | nullptr, 341 | [](auto x) { sk_GENERAL_NAME_pop_free(x, GENERAL_NAME_free); }, 342 | false) 343 | { 344 | const UqASN1_OBJECT ext_obj(X509_EXTENSION_get_object(ext)); 345 | const UqASN1_OBJECT ext_key_obj(NID_subject_alt_name); 346 | if (ext_obj != ext_key_obj) 347 | { 348 | throw std::runtime_error("invalid extension type"); 349 | } 350 | auto * data = static_cast(X509V3_EXT_d2i(ext)); 351 | if (data == nullptr) 352 | { 353 | throw std::runtime_error("SAN extension could not be decoded"); 354 | } 355 | p.reset(data); 356 | } 357 | 358 | UqSUBJECT_ALT_NAME(UqSUBJECT_ALT_NAME&& other) noexcept : 359 | UqSSLOBJECT( 360 | nullptr, [](auto x) { sk_GENERAL_NAME_pop_free(x, GENERAL_NAME_free); }) 361 | { 362 | p = std::move(other.p); 363 | } 364 | 365 | [[nodiscard]] size_t size() const 366 | { 367 | return sk_GENERAL_NAME_num(*this); 368 | } 369 | 370 | [[nodiscard]] UqGENERAL_NAME at(size_t i) const 371 | { 372 | if (i >= size()) 373 | { 374 | throw std::out_of_range("extended key usage index out of range"); 375 | } 376 | return sk_GENERAL_NAME_value(*this, i); 377 | } 378 | }; 379 | 380 | struct UqEXTENDED_KEY_USAGE : public UqSSLOBJECT< 381 | EXTENDED_KEY_USAGE, 382 | EXTENDED_KEY_USAGE_new, 383 | EXTENDED_KEY_USAGE_free> 384 | { 385 | UqEXTENDED_KEY_USAGE(EXTENDED_KEY_USAGE* eku) : 386 | UqSSLOBJECT(sk_ASN1_OBJECT_dup(eku), EXTENDED_KEY_USAGE_free) 387 | {} 388 | 389 | UqEXTENDED_KEY_USAGE(const UqX509_EXTENSION& ext) : 390 | UqSSLOBJECT(nullptr, EXTENDED_KEY_USAGE_free, false) 391 | { 392 | const UqASN1_OBJECT ext_obj = UqASN1_OBJECT(X509_EXTENSION_get_object(ext)); 393 | const UqASN1_OBJECT ext_key_obj(NID_ext_key_usage); 394 | if (ext_obj != ext_key_obj) 395 | { 396 | throw std::runtime_error("invalid extension type"); 397 | } 398 | auto * data = static_cast(X509V3_EXT_d2i(ext)); 399 | if (data == nullptr) 400 | { 401 | throw std::runtime_error("key usage extension could not be decoded"); 402 | } 403 | p.reset(data); 404 | } 405 | 406 | UqEXTENDED_KEY_USAGE(UqEXTENDED_KEY_USAGE&& other) noexcept : 407 | UqSSLOBJECT(nullptr, EXTENDED_KEY_USAGE_free, false) 408 | { 409 | p = std::move(other.p); 410 | } 411 | 412 | [[nodiscard]] size_t size() const 413 | { 414 | return sk_ASN1_OBJECT_num(*this); 415 | } 416 | 417 | [[nodiscard]] UqASN1_OBJECT at(size_t i) const 418 | { 419 | if (i >= size()) 420 | { 421 | throw std::out_of_range("extended key usage index out of range"); 422 | } 423 | return {sk_ASN1_OBJECT_value(*this, i)}; 424 | } 425 | }; 426 | 427 | struct UqX509; 428 | 429 | struct UqEVP_PKEY 430 | : public UqSSLOBJECT 431 | { 432 | UqEVP_PKEY(const UqX509& x509); 433 | 434 | UqEVP_PKEY(const EVP_PKEY* key); 435 | 436 | UqEVP_PKEY(UqEVP_PKEY&& other) noexcept : 437 | UqSSLOBJECT(nullptr, EVP_PKEY_free, false) 438 | { 439 | p = std::move(other.p); 440 | } 441 | 442 | bool operator==(const UqEVP_PKEY& other) const 443 | { 444 | #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 445 | return EVP_PKEY_eq(*this, other) == 1; 446 | #else 447 | return EVP_PKEY_cmp(*this, other) == 1; 448 | #endif 449 | } 450 | 451 | bool operator!=(const UqEVP_PKEY& other) const 452 | { 453 | return !(*this == other); 454 | } 455 | 456 | [[nodiscard]] bool verify_signature( 457 | const std::vector& message, 458 | const std::vector& signature) const; 459 | 460 | #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 461 | UqBIGNUM get_bn_param(const char* key_name) const 462 | { 463 | BIGNUM* bn = nullptr; 464 | CHECK1(EVP_PKEY_get_bn_param(*this, key_name, &bn)); 465 | UqBIGNUM r(bn); 466 | BN_free(bn); 467 | return r; 468 | } 469 | #endif 470 | }; 471 | 472 | struct UqEVP_PKEY_CTX : public UqSSLOBJECT 473 | { 474 | UqEVP_PKEY_CTX(int nid) : 475 | UqSSLOBJECT(EVP_PKEY_CTX_new_id(nid, nullptr), EVP_PKEY_CTX_free) 476 | {} 477 | }; 478 | 479 | struct UqX509 : public UqSSLOBJECT 480 | { 481 | UqX509(const std::string& pem, bool check_null = true) : 482 | UqSSLOBJECT( 483 | PEM_read_bio_X509(UqBIO(pem), nullptr, nullptr, nullptr), 484 | X509_free, 485 | check_null) 486 | {} 487 | 488 | UqX509(UqX509&& other) noexcept : UqSSLOBJECT(nullptr, X509_free, false) 489 | { 490 | X509* ptr = other; 491 | other.release(); 492 | p.reset(ptr); 493 | } 494 | 495 | UqX509(X509* x509) : UqSSLOBJECT(x509, X509_free) 496 | { 497 | X509_up_ref(x509); 498 | } 499 | 500 | UqX509& operator=(const UqX509& other) noexcept 501 | { 502 | if (this != &other) 503 | { 504 | X509_up_ref(other); 505 | p.reset(other.p.get()); 506 | } 507 | return *this; 508 | } 509 | 510 | UqX509& operator=(UqX509&& other) noexcept 511 | { 512 | p = std::move(other.p); 513 | return *this; 514 | } 515 | 516 | [[nodiscard]] bool is_ca() const 517 | { 518 | return X509_check_ca(p.get()) != 0; 519 | } 520 | 521 | [[nodiscard]] int extension_index(const std::string& oid) const 522 | { 523 | return X509_get_ext_by_OBJ(*this, UqASN1_OBJECT(oid), -1); 524 | } 525 | 526 | template 527 | [[nodiscard]] std::vector extensions(const UqASN1_OBJECT& obj) const 528 | { 529 | std::vector r; 530 | auto count = X509_get_ext_count(*this); 531 | int index = -1; 532 | do 533 | { 534 | index = X509_get_ext_by_OBJ(*this, obj, index); 535 | if (index != -1) 536 | { 537 | r.emplace_back(X509_get_ext(*this, index)); 538 | } 539 | } while (index != -1 && index < count); 540 | return r; 541 | } 542 | 543 | template 544 | [[nodiscard]] std::vector extensions(const std::string& oid) const 545 | { 546 | return extensions(UqASN1_OBJECT(oid)); 547 | } 548 | 549 | [[nodiscard]] std::vector subject_alternative_name() const 550 | { 551 | return extensions( 552 | UqASN1_OBJECT(NID_subject_alt_name)); 553 | }; 554 | 555 | [[nodiscard]] std::vector extended_key_usage() const 556 | { 557 | return extensions( 558 | UqASN1_OBJECT(NID_ext_key_usage)); 559 | }; 560 | 561 | [[nodiscard]] bool has_key_usage() const 562 | { 563 | return (X509_get_extension_flags(*this) & EXFLAG_KUSAGE) != 0; 564 | } 565 | 566 | [[nodiscard]] bool has_key_usage_digital_signature() const 567 | { 568 | return has_key_usage() && 569 | (X509_get_key_usage(*this) & KU_DIGITAL_SIGNATURE) != 0; 570 | } 571 | 572 | [[nodiscard]] bool has_key_usage_key_agreement() const 573 | { 574 | return has_key_usage() && 575 | (X509_get_key_usage(*this) & KU_KEY_AGREEMENT) != 0; 576 | } 577 | 578 | [[nodiscard]] bool has_common_name(const std::string& expected_name) const; 579 | 580 | [[nodiscard]] std::map> subject() const 581 | { 582 | std::map> r; 583 | 584 | auto * name = X509_get_subject_name(*this); 585 | CHECKNULL(name); 586 | auto n = X509_NAME_entry_count(name); 587 | for (auto i = 0; i < n; i++) 588 | { 589 | X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, i); 590 | CHECKNULL(entry); 591 | 592 | ASN1_OBJECT* oid = X509_NAME_ENTRY_get_object(entry); 593 | CHECKNULL(oid); 594 | 595 | std::string key; 596 | std::map short_name_map = { 597 | // As specified by the did-x509 spec 598 | {NID_commonName, "CN"}, 599 | {NID_localityName, "L"}, 600 | {NID_stateOrProvinceName, "ST"}, 601 | {NID_organizationName, "O"}, 602 | {NID_organizationalUnitName, "OU"}, 603 | {NID_countryName, "C"}, 604 | {NID_streetAddress, "STREET"}, 605 | }; 606 | 607 | auto snit = short_name_map.find(OBJ_obj2nid(oid)); 608 | if (snit != short_name_map.end()) 609 | { 610 | key = snit->second; 611 | } 612 | else 613 | { 614 | const int sz = OBJ_obj2txt(nullptr, 0, oid, 1); 615 | key.resize(sz + 1, 0); 616 | OBJ_obj2txt((char*)key.data(), key.size(), oid, 1); 617 | } 618 | 619 | ASN1_STRING* val_asn1 = X509_NAME_ENTRY_get_data(entry); 620 | CHECKNULL(val_asn1); 621 | UqBIO value_bio; 622 | ASN1_STRING_print(value_bio, val_asn1); 623 | auto value = value_bio.to_string(); 624 | 625 | r[key].push_back(value); 626 | } 627 | 628 | return r; 629 | } 630 | 631 | [[nodiscard]] bool has_subject_key_id() const 632 | { 633 | return X509_get0_subject_key_id(*this) != nullptr; 634 | } 635 | 636 | [[nodiscard]] std::string subject_key_id() const 637 | { 638 | const ASN1_OCTET_STRING* key_id = X509_get0_subject_key_id(*this); 639 | if (key_id == nullptr) 640 | { 641 | throw std::runtime_error( 642 | "certificate does not contain a subject key id"); 643 | } 644 | const std::unique_ptr c(i2s_ASN1_OCTET_STRING(nullptr, key_id), free); 645 | return {c.get()}; 646 | } 647 | 648 | [[nodiscard]] bool has_authority_key_id() const 649 | { 650 | return X509_get0_authority_key_id(*this) != nullptr; 651 | } 652 | 653 | [[nodiscard]] std::string authority_key_id() const 654 | { 655 | const ASN1_OCTET_STRING* key_id = X509_get0_authority_key_id(*this); 656 | if (key_id == nullptr) 657 | { 658 | throw std::runtime_error( 659 | "certificate does not contain an authority key id"); 660 | } 661 | const std::unique_ptr c(i2s_ASN1_OCTET_STRING(nullptr, key_id), free); 662 | return {c.get()}; 663 | } 664 | 665 | bool has_san(const std::string& san_type, const std::string& value) 666 | { 667 | if (san_type == "dns") 668 | { 669 | if (X509_check_host(*this, value.c_str(), value.size(), 0, nullptr) == 1) 670 | { 671 | return true; 672 | } 673 | } 674 | else if (san_type == "email") 675 | { 676 | if (X509_check_email(*this, value.c_str(), value.size(), 0) == 1) 677 | { 678 | return true; 679 | } 680 | } 681 | else if (san_type == "ipaddress") 682 | { 683 | if ( 684 | X509_check_ip( 685 | *this, (unsigned char*)value.c_str(), value.size(), 0) == 1) 686 | { 687 | return true; 688 | } 689 | } 690 | else if (san_type == "uri") 691 | { 692 | auto san_exts = subject_alternative_name(); 693 | for (const auto& ext : san_exts) 694 | { 695 | for (size_t i = 0; i < ext.size(); i++) 696 | { 697 | const auto& san_i = ext.at(i); 698 | switch (san_i->type) 699 | { 700 | case GEN_URI: { 701 | ASN1_STRING* x = san_i->d.uniformResourceIdentifier; 702 | const std::string gen_uri = (const char*)ASN1_STRING_get0_data(x); 703 | if (gen_uri == value) 704 | { 705 | return true; 706 | } 707 | } 708 | default:; 709 | } 710 | } 711 | } 712 | } 713 | else 714 | { 715 | throw std::runtime_error( 716 | std::string("unknown SAN type: ") + san_type); 717 | } 718 | 719 | return false; 720 | } 721 | 722 | [[nodiscard]] std::vector der() const 723 | { 724 | UqBIO mem; 725 | i2d_X509_bio(mem, *this); 726 | return mem.to_vector(); 727 | } 728 | 729 | [[nodiscard]] UqEVP_PKEY public_key() const 730 | { 731 | return X509_get0_pubkey(*this); 732 | } 733 | 734 | [[nodiscard]] std::string public_jwk() const 735 | { 736 | std::string r = "{"; 737 | 738 | UqEVP_PKEY pk = X509_get0_pubkey(*this); 739 | auto base_id = EVP_PKEY_base_id(pk); 740 | switch (base_id) 741 | { 742 | case EVP_PKEY_RSA: { 743 | r += R"("kty":"RSA",)"; 744 | #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 745 | const UqEVP_PKEY_CTX ek_ctx(EVP_PKEY_RSA); 746 | auto n = pk.get_bn_param(OSSL_PKEY_PARAM_RSA_N); 747 | auto e = pk.get_bn_param(OSSL_PKEY_PARAM_RSA_E); 748 | #else 749 | auto rsa = EVP_PKEY_get0_RSA(pk); 750 | const BIGNUM *n = nullptr, *e = nullptr, *d = nullptr; 751 | RSA_get0_key(rsa, &n, &e, &d); 752 | #endif 753 | auto n_len = BN_num_bytes(n); 754 | auto e_len = BN_num_bytes(e); 755 | std::vector nv(n_len); 756 | std::vector ev(e_len); 757 | BN_bn2bin(n, nv.data()); 758 | BN_bn2bin(e, ev.data()); 759 | r += R"("n":")" + to_base64url(nv) + R"(",)"; 760 | r += R"("e":")" + to_base64url(ev) + R"(")"; 761 | break; 762 | } 763 | case EVP_PKEY_EC: { 764 | r += R"("kty":"EC",)"; 765 | r += R"("crv":")"; 766 | #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 767 | BIGNUM *x = nullptr; 768 | BIGNUM *y = nullptr; 769 | EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_EC_PUB_X, &x); 770 | EVP_PKEY_get_bn_param(pk, OSSL_PKEY_PARAM_EC_PUB_Y, &y); 771 | size_t gname_len = 0; 772 | CHECK1(EVP_PKEY_get_group_name(pk, nullptr, 0, &gname_len)); 773 | std::string gname(gname_len + 1, 0); 774 | CHECK1(EVP_PKEY_get_group_name( 775 | pk, (char*)gname.data(), gname.size(), &gname_len)); 776 | gname.resize(gname_len); 777 | if (gname == SN_X9_62_prime256v1) 778 | { 779 | r += "P-256"; 780 | } 781 | else if (gname == SN_secp384r1) 782 | { 783 | r += "P-384"; 784 | } 785 | else if (gname == SN_secp521r1) 786 | { 787 | r += "P-521"; 788 | } 789 | else 790 | { 791 | throw std::runtime_error("unsupported EC key curve"); 792 | } 793 | #else 794 | auto ec_key = EVP_PKEY_get0_EC_KEY(pk); 795 | const EC_GROUP* grp = EC_KEY_get0_group(ec_key); 796 | int curve_nid = EC_GROUP_get_curve_name(grp); 797 | const EC_POINT* pnt = EC_KEY_get0_public_key(ec_key); 798 | BIGNUM *x = BN_new(), *y = BN_new(); 799 | CHECK1(EC_POINT_get_affine_coordinates(grp, pnt, x, y, nullptr)); 800 | if (curve_nid == NID_X9_62_prime256v1) 801 | { 802 | r += "P-256"; 803 | } 804 | else if (curve_nid == NID_secp384r1) 805 | { 806 | r += "P-384"; 807 | } 808 | else if (curve_nid == NID_secp521r1) 809 | { 810 | r += "P-521"; 811 | } 812 | else 813 | { 814 | throw std::runtime_error("unsupported EC key curve"); 815 | } 816 | #endif 817 | r += R"(",)"; 818 | auto x_len = BN_num_bytes(x); 819 | auto y_len = BN_num_bytes(y); 820 | std::vector xv(x_len); 821 | std::vector yv(y_len); 822 | BN_bn2bin(x, xv.data()); 823 | BN_bn2bin(y, yv.data()); 824 | r += R"("x":")" + to_base64url(xv) + R"(",)"; 825 | r += R"("y":")" + to_base64url(yv) + R"(")"; 826 | BN_free(x); 827 | BN_free(y); 828 | break; 829 | } 830 | default: 831 | throw std::runtime_error("unsupported key base id"); 832 | } 833 | r += "}"; 834 | return r; 835 | } 836 | }; 837 | 838 | UqEVP_PKEY::UqEVP_PKEY(const UqX509& x509) : 839 | UqSSLOBJECT(X509_get_pubkey(x509), EVP_PKEY_free) 840 | {} 841 | 842 | UqEVP_PKEY::UqEVP_PKEY(const EVP_PKEY* key) : 843 | UqSSLOBJECT((EVP_PKEY*)key, EVP_PKEY_free) 844 | { 845 | EVP_PKEY_up_ref((EVP_PKEY*)key); 846 | } 847 | 848 | struct UqX509_NAME 849 | : public UqSSLOBJECT 850 | { 851 | UqX509_NAME(const UqX509& x509) : 852 | UqSSLOBJECT(X509_get_subject_name(x509), X509_NAME_free, true) 853 | {} 854 | }; 855 | 856 | struct UqX509_NAME_ENTRY : public UqSSLOBJECT< 857 | X509_NAME_ENTRY, 858 | X509_NAME_ENTRY_new, 859 | X509_NAME_ENTRY_free> 860 | { 861 | UqX509_NAME_ENTRY(const UqX509_NAME& name, int i) : 862 | UqSSLOBJECT(X509_NAME_get_entry(name, i), X509_NAME_ENTRY_free, true) 863 | {} 864 | }; 865 | 866 | inline bool UqX509::has_common_name(const std::string& expected_name) const 867 | { 868 | UqX509_NAME subject_name(*this); 869 | int cn_i = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1); 870 | while (cn_i != -1) 871 | { 872 | UqX509_NAME_ENTRY entry(subject_name, cn_i); 873 | ASN1_STRING* entry_string = X509_NAME_ENTRY_get_data(entry); 874 | const std::string common_name = (char*)ASN1_STRING_get0_data(entry_string); 875 | if (common_name == expected_name) 876 | { 877 | return true; 878 | } 879 | cn_i = X509_NAME_get_index_by_NID(subject_name, NID_commonName, cn_i); 880 | } 881 | return false; 882 | } 883 | 884 | struct UqEVP_MD_CTX 885 | : public UqSSLOBJECT 886 | { 887 | void init(const EVP_MD* md) 888 | { 889 | md_size = EVP_MD_size(md); 890 | CHECK1(EVP_DigestInit_ex(p.get(), md, nullptr)); 891 | } 892 | 893 | void update(const std::vector& message) 894 | { 895 | CHECK1(EVP_DigestUpdate(p.get(), message.data(), message.size())); 896 | } 897 | 898 | std::vector final() 899 | { 900 | std::vector r(md_size); 901 | unsigned sz = r.size(); 902 | CHECK1(EVP_DigestFinal_ex(p.get(), r.data(), &sz)); 903 | return r; 904 | } 905 | 906 | protected: 907 | size_t md_size = 0; 908 | }; 909 | 910 | struct UqX509_STORE_CTX : public UqSSLOBJECT< 911 | X509_STORE_CTX, 912 | X509_STORE_CTX_new, 913 | X509_STORE_CTX_free> 914 | {}; 915 | 916 | struct UqX509_STORE 917 | : public UqSSLOBJECT 918 | { 919 | void set_flags(int flags) 920 | { 921 | X509_STORE_set_flags(p.get(), flags); 922 | } 923 | 924 | void add(const UqX509& x509) 925 | { 926 | X509_STORE_add_cert(p.get(), x509); 927 | } 928 | 929 | void add(const std::string& pem) 930 | { 931 | add(UqX509(pem)); 932 | } 933 | }; 934 | 935 | struct UqSTACK_OF_X509_INFO 936 | : public UqSSLOBJECT 937 | { 938 | UqSTACK_OF_X509_INFO() : 939 | UqSSLOBJECT(sk_X509_INFO_new_null(), [](auto x) { 940 | sk_X509_INFO_pop_free(x, X509_INFO_free); 941 | }) 942 | {} 943 | 944 | UqSTACK_OF_X509_INFO(UqSTACK_OF_X509_INFO&& other) noexcept : 945 | UqSSLOBJECT(other, [](auto x) { sk_X509_INFO_pop_free(x, X509_INFO_free); }) 946 | { 947 | other.release(); 948 | } 949 | 950 | UqSTACK_OF_X509_INFO(const UqBIO& bio) : 951 | UqSSLOBJECT( 952 | PEM_X509_INFO_read_bio(bio, nullptr, nullptr, nullptr), 953 | [](auto x) { sk_X509_INFO_pop_free(x, X509_INFO_free); }) 954 | { 955 | if (p == nullptr) 956 | { 957 | throw std::runtime_error("could not parse PEM chain"); 958 | } 959 | } 960 | 961 | [[nodiscard]] int size() const 962 | { 963 | return sk_X509_INFO_num(p.get()); 964 | } 965 | }; 966 | 967 | struct UqSTACK_OF_X509 968 | : public UqSSLOBJECT 969 | { 970 | UqSTACK_OF_X509() : 971 | UqSSLOBJECT( 972 | sk_X509_new_null(), [](auto x) { sk_X509_pop_free(x, X509_free); }) 973 | {} 974 | 975 | UqSTACK_OF_X509(const UqX509_STORE_CTX& ctx) : 976 | UqSSLOBJECT(X509_STORE_CTX_get1_chain(ctx), [](auto x) { 977 | sk_X509_pop_free(x, X509_free); 978 | }) 979 | {} 980 | 981 | UqSTACK_OF_X509(UqSTACK_OF_X509&& other) noexcept : 982 | UqSSLOBJECT(other, [](auto x) { sk_X509_pop_free(x, X509_free); }) 983 | { 984 | other.release(); 985 | } 986 | 987 | UqSTACK_OF_X509(const std::string& pem) : 988 | UqSSLOBJECT( 989 | nullptr, [](auto x) { sk_X509_pop_free(x, X509_free); }, false) 990 | { 991 | const UqBIO mem(pem); 992 | UqSTACK_OF_X509_INFO sk_info(mem); 993 | p.reset(sk_X509_new_null()); 994 | for (int i = 0; i < sk_info.size(); i++) 995 | { 996 | auto * sk_i = sk_X509_INFO_value(sk_info, i); 997 | if (sk_i->x509 == nullptr) 998 | { 999 | throw std::runtime_error("invalid PEM element"); 1000 | } 1001 | X509_up_ref(sk_i->x509); 1002 | sk_X509_push(*this, sk_i->x509); 1003 | } 1004 | } 1005 | 1006 | UqSTACK_OF_X509(const std::vector& pem) : 1007 | UqSSLOBJECT( 1008 | nullptr, [](auto x) { sk_X509_pop_free(x, X509_free); }, false) 1009 | { 1010 | p.reset(sk_X509_new_null()); 1011 | for (const auto& pem_elem: pem) 1012 | { 1013 | const UqBIO mem(pem_elem); 1014 | UqSTACK_OF_X509_INFO sk_info(mem); 1015 | if (sk_info.size() != 1) 1016 | { 1017 | throw std::runtime_error("expected exactly one PEM element"); 1018 | } 1019 | auto * sk_0 = sk_X509_INFO_value(sk_info, 0); 1020 | if (sk_0->x509 == nullptr) 1021 | { 1022 | throw std::runtime_error("invalid PEM element"); 1023 | } 1024 | X509_up_ref(sk_0->x509); 1025 | sk_X509_push(*this, sk_0->x509); 1026 | } 1027 | } 1028 | 1029 | UqSTACK_OF_X509& operator=(UqSTACK_OF_X509&& other) noexcept 1030 | { 1031 | p = std::move(other.p); 1032 | return *this; 1033 | } 1034 | 1035 | [[nodiscard]] size_t size() const 1036 | { 1037 | const int r = sk_X509_num(p.get()); 1038 | return r == (-1) ? 0 : r; 1039 | } 1040 | 1041 | [[nodiscard]] bool empty() const 1042 | { 1043 | return size() == 0; 1044 | } 1045 | 1046 | [[nodiscard]] UqX509 at(size_t i) const 1047 | { 1048 | if (i >= size()) 1049 | { 1050 | throw std::out_of_range("index into certificate stack too large"); 1051 | } 1052 | return sk_X509_value(p.get(), i); 1053 | } 1054 | 1055 | void insert(size_t i, UqX509&& x) 1056 | { 1057 | X509_up_ref(x); 1058 | CHECK0(sk_X509_insert(p.get(), x, i)); 1059 | } 1060 | 1061 | void push(UqX509&& x509) 1062 | { 1063 | sk_X509_push(p.get(), x509.release()); 1064 | } 1065 | 1066 | [[nodiscard]] UqX509 front() const 1067 | { 1068 | return (*this).at(0); 1069 | } 1070 | 1071 | [[nodiscard]] UqX509 back() const 1072 | { 1073 | return (*this).at(size() - 1); 1074 | } 1075 | 1076 | [[nodiscard]] std::pair get_validity_range() const 1077 | { 1078 | if (size() == 0) 1079 | { 1080 | throw std::runtime_error( 1081 | "no certificate change to compute validity ranges for"); 1082 | } 1083 | 1084 | const ASN1_TIME *latest_from = nullptr; 1085 | const ASN1_TIME *earliest_to = nullptr; 1086 | for (size_t i = 0; i < size(); i++) 1087 | { 1088 | const auto& c = at(i); 1089 | const ASN1_TIME* not_before = X509_get0_notBefore(c); 1090 | if (latest_from == nullptr || ASN1_TIME_compare(latest_from, not_before) == -1) 1091 | { 1092 | latest_from = not_before; 1093 | } 1094 | const ASN1_TIME* not_after = X509_get0_notAfter(c); 1095 | if (earliest_to == nullptr || ASN1_TIME_compare(earliest_to, not_after) == 1) 1096 | { 1097 | earliest_to = not_after; 1098 | } 1099 | } 1100 | 1101 | std::pair r; 1102 | ASN1_TIME_to_tm(latest_from, &r.first); 1103 | ASN1_TIME_to_tm(earliest_to, &r.second); 1104 | return r; 1105 | } 1106 | 1107 | [[nodiscard]] UqSTACK_OF_X509 verify( 1108 | const std::vector& roots, 1109 | bool ignore_time = false, 1110 | bool no_auth_key_id_ok = true) const 1111 | { 1112 | if (size() <= 1) 1113 | { 1114 | throw std::runtime_error("certificate chain too short"); 1115 | } 1116 | 1117 | UqX509_STORE store; 1118 | 1119 | for (const auto& c : roots) 1120 | { 1121 | CHECK1(X509_STORE_add_cert(store, back())); 1122 | } 1123 | 1124 | auto target = at(0); 1125 | 1126 | UqX509_STORE_CTX store_ctx; 1127 | CHECK1(X509_STORE_CTX_init(store_ctx, store, target, *this)); 1128 | 1129 | X509_VERIFY_PARAM* param = X509_VERIFY_PARAM_new(); 1130 | X509_VERIFY_PARAM_set_depth(param, std::numeric_limits::max()); 1131 | X509_VERIFY_PARAM_set_auth_level(param, 0); 1132 | 1133 | CHECK1(X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_X509_STRICT)); 1134 | CHECK1( 1135 | X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CHECK_SS_SIGNATURE)); 1136 | CHECK1(X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_PARTIAL_CHAIN)); 1137 | 1138 | if (ignore_time) 1139 | { 1140 | CHECK1(X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_NO_CHECK_TIME)); 1141 | } 1142 | 1143 | X509_STORE_CTX_set0_param(store_ctx, param); 1144 | 1145 | #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 1146 | if (no_auth_key_id_ok) 1147 | { 1148 | X509_STORE_CTX_set_verify_cb( 1149 | store_ctx, [](int ok, X509_STORE_CTX* ctx) { 1150 | const int ec = X509_STORE_CTX_get_error(ctx); 1151 | if (ec == X509_V_ERR_MISSING_AUTHORITY_KEY_IDENTIFIER) 1152 | { 1153 | return 1; 1154 | } 1155 | return ok; 1156 | }); 1157 | } 1158 | #endif 1159 | 1160 | const int rc = X509_verify_cert(store_ctx); 1161 | 1162 | if (rc == 1) 1163 | { 1164 | return {store_ctx}; 1165 | } 1166 | 1167 | if (rc == 0) 1168 | { 1169 | const int err_code = X509_STORE_CTX_get_error(store_ctx); 1170 | const int depth = X509_STORE_CTX_get_error_depth(store_ctx); 1171 | const char* err_str = X509_verify_cert_error_string(err_code); 1172 | throw std::runtime_error( 1173 | std::string("certificate chain verification failed: ") + err_str + 1174 | " (depth: " + std::to_string(depth) + ")"); 1175 | throw std::runtime_error("no chain or signature invalid"); 1176 | } 1177 | 1178 | auto msg = std::string(ERR_error_string(ERR_get_error(), nullptr)); 1179 | throw std::runtime_error(std::string("OpenSSL error: ") + msg); 1180 | } 1181 | }; 1182 | 1183 | inline std::vector sha256(const std::vector& message) 1184 | { 1185 | UqEVP_MD_CTX ctx; 1186 | ctx.init(EVP_sha256()); 1187 | ctx.update(message); 1188 | return ctx.final(); 1189 | } 1190 | 1191 | inline std::vector sha384(const std::vector& message) 1192 | { 1193 | UqEVP_MD_CTX ctx; 1194 | ctx.init(EVP_sha384()); 1195 | ctx.update(message); 1196 | return ctx.final(); 1197 | } 1198 | 1199 | inline std::vector sha512(const std::vector& message) 1200 | { 1201 | UqEVP_MD_CTX ctx; 1202 | ctx.init(EVP_sha512()); 1203 | ctx.update(message); 1204 | return ctx.final(); 1205 | } 1206 | 1207 | inline void check_fingerprint( 1208 | const UqSTACK_OF_X509& chain, 1209 | const std::string& fingerprint_alg, 1210 | const std::string& fingerprint) 1211 | { 1212 | const std::unordered_set valid_fingerprints; 1213 | 1214 | for (size_t i = 1; i < chain.size(); i++) 1215 | { 1216 | const auto& cert = chain.at(i).der(); 1217 | 1218 | std::vector hash; 1219 | if (fingerprint_alg == "sha256") 1220 | { 1221 | hash = sha256(cert); 1222 | } 1223 | else if (fingerprint_alg == "sha384") 1224 | { 1225 | hash = sha384(cert); 1226 | } 1227 | else if (fingerprint_alg == "sha512") 1228 | { 1229 | hash = sha512(cert); 1230 | } 1231 | else 1232 | { 1233 | throw std::runtime_error("unsupported fingerprint algorithm"); 1234 | } 1235 | 1236 | auto b64 = to_base64url(hash); 1237 | if (fingerprint == b64) 1238 | { 1239 | return; 1240 | } 1241 | } 1242 | 1243 | throw std::runtime_error("invalid certificate fingerprint"); 1244 | } 1245 | 1246 | inline bool is_hex_digit(char digit) 1247 | { 1248 | return (digit >= 0x30 && digit <= 0x39) || 1249 | (digit >= 0x41 && digit <= 0x46) || (digit >= 0x61 && digit <= 0x66); 1250 | } 1251 | 1252 | // Adapted from curl: 1253 | // https://github.com/curl/curl/blob/e335d778e3eaa41ebbe209e9b8110e8a0d9a72f3/lib/escape.c#L134 1254 | inline std::string url_unescape(const std::string& is) 1255 | { 1256 | std::string r; 1257 | 1258 | const char* string = is.data(); 1259 | for (size_t i = 0; i < is.size(); i++) 1260 | { 1261 | if ( 1262 | is[i] == '%' && i + 2 < is.size() && is_hex_digit(is[i + 1]) && 1263 | is_hex_digit(is[i + 2])) 1264 | { 1265 | /* this is two hexadecimal digits following a '%' */ 1266 | char hexstr[3]; 1267 | char *ptr = nullptr; 1268 | hexstr[0] = is[i + 1]; 1269 | hexstr[1] = is[i + 2]; 1270 | hexstr[2] = 0; 1271 | const char c = (char)strtoul(hexstr, &ptr, 16); 1272 | r.push_back(c); 1273 | i += 2; 1274 | } 1275 | else 1276 | { 1277 | r.push_back(is[i]); 1278 | } 1279 | } 1280 | 1281 | return r; 1282 | } 1283 | 1284 | inline std::vector url_unescape( 1285 | const std::vector& urls) 1286 | { 1287 | std::vector r; 1288 | r.reserve(urls.size()); 1289 | for (const auto& url : urls) 1290 | { 1291 | r.push_back(url_unescape(url)); 1292 | } 1293 | return r; 1294 | } 1295 | 1296 | inline std::vector split( 1297 | const std::string& s, const std::string& delimiter) 1298 | { 1299 | std::vector r; 1300 | size_t start = 0; 1301 | size_t end = 0; 1302 | 1303 | do 1304 | { 1305 | end = s.find(delimiter, start); 1306 | r.push_back(s.substr(start, end - start)); 1307 | start = end + delimiter.size(); 1308 | } while (end != std::string::npos); 1309 | 1310 | return r; 1311 | } 1312 | 1313 | inline void verify(const UqSTACK_OF_X509& chain, const std::string& did) 1314 | { 1315 | auto top_tokens = split(did, "::"); 1316 | 1317 | if (top_tokens.size() <= 1) 1318 | { 1319 | throw std::runtime_error("invalid DID string"); 1320 | } 1321 | 1322 | // Check prefix 1323 | auto prefix = top_tokens[0]; 1324 | auto pretokens = split(prefix, ":"); 1325 | 1326 | if ( 1327 | pretokens.size() < 5 || pretokens[0] != "did" || pretokens[1] != "x509") 1328 | { 1329 | throw std::runtime_error("unsupported method/prefix"); 1330 | } 1331 | 1332 | if (pretokens[2] != "0") 1333 | { 1334 | throw std::runtime_error("unsupported did:x509 version"); 1335 | } 1336 | 1337 | // Check fingerprint 1338 | const auto& ca_fingerprint_alg = pretokens[3]; 1339 | const auto& ca_fingerprint = pretokens[4]; 1340 | 1341 | check_fingerprint(chain, ca_fingerprint_alg, ca_fingerprint); 1342 | 1343 | // Check policies 1344 | for (size_t i = 1; i < top_tokens.size(); i++) 1345 | { 1346 | const auto& policy = top_tokens[i]; 1347 | auto parts = split(policy, ":"); 1348 | 1349 | if (parts.size() < 2) 1350 | { 1351 | throw std::runtime_error("invalid policy"); 1352 | } 1353 | 1354 | auto policy_name = parts[0]; 1355 | auto args = std::vector(parts.begin() + 1, parts.end()); 1356 | 1357 | if (policy_name == "subject") 1358 | { 1359 | if (args.size() % 2 != 0) 1360 | { 1361 | throw std::runtime_error("key-value pairs required"); 1362 | } 1363 | 1364 | if (args.size() < 2) 1365 | { 1366 | throw std::runtime_error("at least one key-value pair is required"); 1367 | } 1368 | 1369 | std::unordered_set seen_fields; 1370 | for (size_t j = 0; j < args.size(); j += 2) 1371 | { 1372 | auto k = args[j]; 1373 | if (k == "S") 1374 | { 1375 | // The correct key for state is ST, see 1376 | // https://www.rfc-editor.org/rfc/rfc4519#section-2.33 1377 | // and https://www.rfc-editor.org/rfc/rfc4514.html#section-3 1378 | // but the same text also says: 1379 | // > Implementations MAY recognize other DN string representations. 1380 | // and S is used instead by some issuers to mean State. DNs that 1381 | // contain both an S and a ST field are accordingly considered 1382 | // to contain a duplicate field, and rejected. 1383 | k = "ST"; 1384 | } 1385 | const auto& v = url_unescape(args[j + 1]); 1386 | 1387 | if (seen_fields.find(k) != seen_fields.end()) 1388 | { 1389 | throw std::runtime_error( 1390 | std::string("duplicate field '") + k + "'"); 1391 | } 1392 | seen_fields.insert(k); 1393 | 1394 | const auto& lc = chain.at(0); 1395 | auto subject = lc.subject(); 1396 | 1397 | auto sit = subject.find(k); 1398 | if (sit == subject.end()) 1399 | { 1400 | throw std::runtime_error( 1401 | std::string("unsupported subject key: '") + k + "'"); 1402 | } 1403 | 1404 | bool found = false; 1405 | for (const auto& fv : sit->second) 1406 | { 1407 | if (fv.find(v) != std::string::npos) 1408 | { 1409 | found = true; 1410 | break; 1411 | } 1412 | } 1413 | if (!found) 1414 | { 1415 | throw std::runtime_error( 1416 | std::string("invalid subject key/value: " + k + "=" + v)); 1417 | } 1418 | } 1419 | } 1420 | else if (policy_name == "san") 1421 | { 1422 | if (args.size() != 2) 1423 | { 1424 | throw std::runtime_error("exactly one SAN type and value required"); 1425 | } 1426 | 1427 | auto san_type = args[0]; 1428 | auto san_value = url_unescape(args[1]); 1429 | 1430 | if (!chain.at(0).has_san(san_type, san_value)) 1431 | { 1432 | throw std::runtime_error( 1433 | std::string("SAN not found: ") + san_value); 1434 | } 1435 | } 1436 | else if (policy_name == "eku") 1437 | { 1438 | if (args.size() != 1) 1439 | { 1440 | throw std::runtime_error("exactly one EKU required"); 1441 | } 1442 | 1443 | const UqASN1_OBJECT oid(args[0]); 1444 | 1445 | bool found_eku = false; 1446 | auto eku_exts = chain.at(0).extended_key_usage(); 1447 | for (size_t k = 0; k < eku_exts.size() && !found_eku; k++) 1448 | { 1449 | const auto& eku_ext_k = eku_exts.at(k); 1450 | for (size_t j = 0; j < eku_ext_k.size() && !found_eku; j++) 1451 | { 1452 | if (eku_ext_k.at(j) == oid) 1453 | { 1454 | found_eku = true; 1455 | } 1456 | } 1457 | } 1458 | if (!found_eku) 1459 | { 1460 | throw std::runtime_error(std::string("EKU not found: ") + args[0]); 1461 | } 1462 | } 1463 | else if (policy_name == "fulcio-issuer") 1464 | { 1465 | if (args.size() != 1) 1466 | { 1467 | throw std::runtime_error("excessive arguments to fulcio-issuer"); 1468 | } 1469 | 1470 | const std::string fulcio_oid("1.3.6.1.4.1.57264.1.1"); 1471 | auto decoded_arg = url_unescape(args[0]); 1472 | auto fulcio_issuer = "https://" + decoded_arg; 1473 | 1474 | bool found = false; 1475 | auto exts = chain.at(0).extensions(fulcio_oid); 1476 | for (const auto& ext : exts) 1477 | { 1478 | if ((std::string)ext.data() == fulcio_issuer) 1479 | { 1480 | found = true; 1481 | break; 1482 | } 1483 | } 1484 | if (!found) 1485 | { 1486 | throw std::runtime_error( 1487 | std::string("invalid fulcio-issuer: ") + fulcio_issuer); 1488 | } 1489 | } 1490 | else 1491 | { 1492 | throw std::runtime_error( 1493 | std::string("unsupported did:x509 scheme '") + policy_name + "'"); 1494 | } 1495 | } 1496 | } 1497 | 1498 | inline std::pair is_agreed_signature_key(const UqX509& cert) 1499 | { 1500 | const bool include_assertion_method = 1501 | !cert.has_key_usage() || cert.has_key_usage_digital_signature(); 1502 | const bool include_key_agreement = 1503 | !cert.has_key_usage() || cert.has_key_usage_key_agreement(); 1504 | if (!include_assertion_method && !include_key_agreement) 1505 | { 1506 | throw std::runtime_error( 1507 | "certificate key usage must include digital signature or key " 1508 | "agreement"); 1509 | } 1510 | 1511 | return {include_assertion_method, include_key_agreement}; 1512 | } 1513 | 1514 | inline std::string create_did_document( 1515 | const std::string& did, const UqSTACK_OF_X509& chain) 1516 | { 1517 | const std::string format = R"({ 1518 | "@context": "https://www.w3.org/ns/did/v1", 1519 | "id": "_DID_", 1520 | "verificationMethod": [{ 1521 | "id": "_DID_#key-1", 1522 | "type": "JsonWebKey2020", 1523 | "controller": "_DID_", 1524 | "publicKeyJwk": _LEAF_JWK_ 1525 | }] 1526 | _ASSERTION_METHOD_ 1527 | _KEY_AGREEMENT_ 1528 | })"; 1529 | 1530 | const auto& leaf = chain.front(); 1531 | const auto& [include_assertion_method, include_key_agreement] = is_agreed_signature_key(leaf); 1532 | 1533 | std::string am; 1534 | std::string ka; 1535 | if (include_assertion_method) 1536 | { 1537 | am = R"(,"assertionMethod": ")" + did + R"(#key-1")"; 1538 | } 1539 | if (include_key_agreement) 1540 | { 1541 | ka = R"(,"keyAgreement": ")" + did + R"(#key-1")"; 1542 | } 1543 | 1544 | const auto& leaf_jwk = leaf.public_jwk(); 1545 | 1546 | auto t = std::regex_replace(format, std::regex("_DID_"), did); 1547 | t = std::regex_replace(t, std::regex("_ASSERTION_METHOD_"), am); 1548 | t = std::regex_replace(t, std::regex("_KEY_AGREEMENT_"), ka); 1549 | t = std::regex_replace(t, std::regex("_LEAF_JWK_"), leaf_jwk); 1550 | 1551 | return t; 1552 | } 1553 | } 1554 | 1555 | inline UqSTACK_OF_X509 resolve_chain( 1556 | const UqSTACK_OF_X509& chain, 1557 | const std::string& did, 1558 | bool ignore_time = false) 1559 | { 1560 | if (chain.empty()) 1561 | { 1562 | throw std::runtime_error("no certificate chain"); 1563 | } 1564 | 1565 | // The last certificate in the chain is assumed to be the trusted root. 1566 | UqX509 root = chain.back(); 1567 | 1568 | std::vector roots; 1569 | roots.emplace_back(std::move(root)); 1570 | 1571 | auto valid_chain = chain.verify(roots, ignore_time); 1572 | verify(valid_chain, did); 1573 | 1574 | return valid_chain; 1575 | } 1576 | 1577 | inline std::string resolve( 1578 | const std::string& chain_pem, 1579 | const std::string& did, 1580 | bool ignore_time = false) 1581 | { 1582 | const UqSTACK_OF_X509 chain(chain_pem); 1583 | 1584 | const auto valid_chain = resolve_chain(chain, did, ignore_time); 1585 | return create_did_document(did, valid_chain); 1586 | } 1587 | 1588 | inline std::string resolve_jwk( 1589 | const std::vector& chain_pem, 1590 | const std::string& did, 1591 | bool ignore_time = false) 1592 | { 1593 | const UqSTACK_OF_X509 chain(chain_pem); 1594 | 1595 | const auto valid_chain = resolve_chain(chain, did, ignore_time); 1596 | const auto& leaf = valid_chain.front(); 1597 | is_agreed_signature_key(leaf); 1598 | 1599 | return leaf.public_jwk(); 1600 | } 1601 | } --------------------------------------------------------------------------------