diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml index b779b53..56756c6 100644 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ b/.github/workflows/keyfactor-starter-workflow.yml @@ -1,33 +1,29 @@ -name: Starter Workflow -on: [workflow_dispatch, push, pull_request] +name: Keyfactor Bootstrap Workflow -jobs: - call-create-github-release-workflow: - uses: Keyfactor/actions/.github/workflows/github-release.yml@main - - call-assign-from-json-workflow: - uses: Keyfactor/actions/.github/workflows/assign-env-from-json.yml@main +on: + workflow_dispatch: + pull_request: + types: [opened, closed, synchronize, edited, reopened] + push: + create: + branches: + - 'release-*.*' - call-dotnet-build-and-release-workflow: - needs: [call-create-github-release-workflow, call-assign-from-json-workflow] - uses: Keyfactor/actions/.github/workflows/dotnet-build-and-release.yml@main +jobs: + call-starter-workflow: + uses: keyfactor/actions/.github/workflows/starter.yml@v4 + permissions: + contents: write # Explicitly grant write permission with: - release_version: ${{ needs.call-create-github-release-workflow.outputs.release_version }} - release_url: ${{ needs.call-create-github-release-workflow.outputs.release_url }} - release_dir: ${{ needs.call-assign-from-json-workflow.outputs.release_dir }} - - secrets: - token: ${{ secrets.PRIVATE_PACKAGE_ACCESS }} - - call-generate-readme-workflow: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - uses: Keyfactor/actions/.github/workflows/generate-readme.yml@main + command_token_url: ${{ vars.COMMAND_TOKEN_URL }} + command_hostname: ${{ vars.COMMAND_HOSTNAME }} + command_base_api_path: ${{ vars.COMMAND_API_PATH }} secrets: - token: ${{ secrets.APPROVE_README_PUSH }} - - call-update-catalog-workflow: - needs: call-assign-from-json-workflow - if: needs.call-assign-from-json-workflow.outputs.update_catalog == 'True' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') - uses: Keyfactor/actions/.github/workflows/update-catalog.yml@main - secrets: - token: ${{ secrets.SDK_SYNC_PAT }} + token: ${{ secrets.V2BUILDTOKEN}} + gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} + gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} + scan_token: ${{ secrets.SAST_TOKEN }} + entra_username: ${{ secrets.DOCTOOL_ENTRA_USERNAME }} + entra_password: ${{ secrets.DOCTOOL_ENTRA_PASSWD }} + command_client_id: ${{ secrets.COMMAND_CLIENT_ID }} + command_client_secret: ${{ secrets.COMMAND_CLIENT_SECRET }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index dfcfd56..75b7cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,350 +1,353 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ +/digicert-metadata-sync/digicert-metadata-sync/App - Copy.config +/digicert-metadata-sync/digicert-metadata-sync/App - Blank.config +/digicert-metadata-sync/digicert-metadata-sync/manualfields - Copy.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 6804b4e..cbbfb6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,35 @@ -Version 2.1.0 - - Added a system that gathers all non-Keyfactor friendly characters and allows the user to configure an alternative. - Added pagination based batch processing, memory consumption has been drastically reduced. - -Version 2.0.3 - - Added a setting to enable or disable syncing deactivated custom fields from DigiCert. - -Version 2.0.2 - - Fixed issue with additional_emails field not syncing. - Added independent logging via NLog. - -Version 2.0.1 - - Fixed issue with no input for either custom or manual fields leading to a crash. - Fixed issue with data for imported DigiCert fields renamed with a replacement character not syncing back to DigiCert. - Fixed possible crash caused by importing DigiCert custom fields with "Anything" data type. - -Version 2.0.0 - - Added ability to sync custom fields from Keyfactor to DigiCert. - Tool now requires command line argument to specify sync direction: "dctokf" for DigiCert to Keyfactor and "kftodc" for Keyfactor to DigiCert. - New DigiCert API Key with restrictions set to "None" in DigiCert config required to perform sync from Keyfactor to Digicert. - -Version 1.0 - - Initial Release +Version 2.2.0 + Fixed a bug that prevented tool from correctly recognizing command line args. + Added Sync Reissue to the config file options. + Improved documentation. + Adjust manualfields.json to work with modern versions of Keyfactor. + +Version 2.1.0 + + Added a system that gathers all non-Keyfactor friendly characters and allows the user to configure an alternative. + Added pagination based batch processing, memory consumption has been drastically reduced. + +Version 2.0.3 + + Added a setting to enable or disable syncing deactivated custom fields from DigiCert. + +Version 2.0.2 + + Fixed issue with additional_emails field not syncing. + Added independent logging via NLog. + +Version 2.0.1 + + Fixed issue with no input for either custom or manual fields leading to a crash. + Fixed issue with data for imported DigiCert fields renamed with a replacement character not syncing back to DigiCert. + Fixed possible crash caused by importing DigiCert custom fields with "Anything" data type. + +Version 2.0.0 + + Added ability to sync custom fields from Keyfactor to DigiCert. + Tool now requires command line argument to specify sync direction: "dctokf" for DigiCert to Keyfactor and "kftodc" for Keyfactor to DigiCert. + New DigiCert API Key with restrictions set to "None" in DigiCert config required to perform sync from Keyfactor to Digicert. + +Version 1.0 + + Initial Release \ No newline at end of file diff --git a/DigicertMetadataSync.sln b/DigicertMetadataSync.sln index bd6ebc8..6352447 100644 --- a/DigicertMetadataSync.sln +++ b/DigicertMetadataSync.sln @@ -1,9 +1,19 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.2.32616.157 +VisualStudioVersion = 17.14.36414.22 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigicertMetadataSync", "digicert-metadata-sync\DigicertMetadataSync.csproj", "{AF8D8189-8B56-4F4A-947E-3DD9066B3227}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigicertMetadataSync", "digicert-metadata-sync\DigicertMetadataSync.csproj", "{A3E989D8-5262-9630-0136-1FD5D33641CD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" + ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md + docsource\content.md = docsource\content.md + integration-manifest.json = integration-manifest.json + .github\workflows\keyfactor-starter-workflow.yml = .github\workflows\keyfactor-starter-workflow.yml + manualfields.json = manualfields.json + README.md = README.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,15 +21,15 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AF8D8189-8B56-4F4A-947E-3DD9066B3227}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF8D8189-8B56-4F4A-947E-3DD9066B3227}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF8D8189-8B56-4F4A-947E-3DD9066B3227}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF8D8189-8B56-4F4A-947E-3DD9066B3227}.Release|Any CPU.Build.0 = Release|Any CPU + {A3E989D8-5262-9630-0136-1FD5D33641CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3E989D8-5262-9630-0136-1FD5D33641CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3E989D8-5262-9630-0136-1FD5D33641CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3E989D8-5262-9630-0136-1FD5D33641CD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {88551ADC-3044-4BCF-9AA2-4A919D6D9574} + SolutionGuid = {A235DCC4-4D23-45CD-BA8E-BF5052AA1203} EndGlobalSection EndGlobal diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md index 72f6b2a..a1fe803 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,221 @@ -# Digicert Metadata Sync - -A tool to automatically synchronize metadata fields and their content from DigiCert to Keyfactor. This utility is indented to be used in conjunction with the Digicert AnyGateway and adds to the information already synchronized by the gateway. - -#### Integration status: Production - Ready for use in production environments. - - - -## Support for Digicert Metadata Sync - -Digicert Metadata Sync is open source and there is **no SLA** for this tool/library/client. Keyfactor will address issues as resources become available. Keyfactor customers may request escalation by opening up a support ticket through their Keyfactor representative. - -###### To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab. +

+ DigiCert Metadata Sync +

+ +

+ +Integration Status: production +Release +Issues +GitHub Downloads (all assets, all releases) +

+ +

+ + + Support + + · + + License + + · + + Related Integrations + +

+ +## Support +The DigiCert Metadata Sync is open source and there is **no SLA**. Keyfactor will address issues as resources become available. Keyfactor customers may request escalation by opening up a support ticket through their Keyfactor representative. + +> To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab. +## Overview +This tool can: +1. **Create/align metadata fields in Keyfactor** to match: + - DigiCert **Custom Metadata Fields** (aka “custom fields”), and + - Useful **non‑custom fields** from DigiCert order/certificate data (e.g., DigiCert Order ID, organization contact details). These are called **manual fields** in this project. +2. **Sync field values** from DigiCert back into the matching Keyfactor metadata fields. +Before running, configure **`DigicertMetadataSync.dll.config`** and **`manualfields.json`** (both described below). -## Overview -This tool primarily sets up metadata fields in Keyfactor for the custom metadata fields in DigiCert, which are named as such, but can also setup metadata fields in Keyfactor for non-custom fields available in DigiCert and unavailable in Keyfactor by default, such as the Digicert Cert ID and the Organization contact. These fields are referred to as manual fields in the context of this tool. After setting up these fields, the tool proceeds to update the contents of these fields. This tool only adds metadata to certificates that have already been imported into Keyfactor. Additionally, this tool requires a properly installed and functioning AnyGateway configured to work with Keyfactor and Digicert. The latest update allows for syncronization of custom field contents from Keyfactor to DigiCert. New fields are created in Keyfactor and DigiCert to accomodate for this. +--- -## Installation and Usage -The tool comes as a Windows executable. The tool performs synchronization each time its run. For the tool to run automatically, it needs to be added as a scheduled process using Windows. The advised interval for running it is once per week. The files DigicertMetadataSync.dll.config and manualfields.json need to be present in the same directory as the tool for it to run correctly. The specific location from which the tool is ran does not matter, but it needs to have access to both the Keyfactor API endpoint as well as Digicert, and appropriate permissions for access to the configuration files. -An explanation for the settings found in these files is given below. +## Settings -## Command Line Arguments +### Command Line Arguments One of these two arguments needs to be used for the tool to run. -- "kftodc" -Syncronizes the contents of custom fields listed in manualfields.json from Keyfactor to DigiCert. If the fields in manualfields.json do not exist in Keyfactor or DigiCert, they are created first. Example: ```.\DigicertMetadataSync.exe kftodc``` -- "dctokf" -Syncronizes the contents of both custom and non-custom fields from DigiCert to Keyfactor. The fields are listed in manualfields.json, and are created if necessary. -Example: ```.\DigicertMetadataSync.exe dctokf``` - -## Settings -The settings currently present in these files are shown as an example and need to be configured for your specific situation. -### DigicertMetadataSync.dll.config settings -- DigicertAPIKey -Standard DigiCert API access key. -- DigicertAPIKeyTopPerm -DigiCert API access key with restrictions set to "None" - required for sync from Keyfactor to DigiCert. -- KeyfactorDomainAndUser -Same credential as used when logging into Keyfactor Command. A different set of credentials can be used provided they have adequate access permissions. -- KeyfactorPassword -Password for the account used in the KeyfactorDomainAndUser field. -- KeyfactorCertSearchReturnLimit -This specifies the number of certs the tool will expect to receive from Keyfactor Command. Can be set to an arbitrarily large number for unlimited or to a smaller number for testing. -- KeyfactorAPIEndpoint -This should include the Keyfactor API endpoint, of the format https://domain.com/keyfactorapi/ -- KeyfactorDigicertIssuedCertQueryTerm -This should include the common prefix all DigiCert certs have in your Keyfactor instance. For example, "DigiCert" -- ImportAllCustomDigicertFields -This setting enables the tool to import all of the custom metadata fields included in DigiCert and sync all of their data. - -During the first run, the tool will scan the custom fields it will be importing for characters that are not supported in Keyfactor Metadata field names. -Each unsupported character will be shown in a file named "replacechar.json" and its replacement can be selected. If the values in the file are not populated, the tool will not run a second time. -- ImportDataForDeactivatedDigiCertFields -If this is enabled, custom metadata fields that were deactivated in DigiCert will also be synced, and the data stored in these fields in certificates will be too. - -### replacechar.json settings -This file is populated during the first run of the tool if the ImportAllCustomDigicertFields setting is toggled. -The only text that needs replacing is shown as "null", and can be filled with any alphanumeric string. The "_" and "-" characters are also supported. - - -### manualfields.json settings -This file is used to specify which metadata fields should be synced up. - -The "ManualFields" section is used to specify the non custom fields to import into Keyfactor. - -The "CustomFields" section is used to specify which of the custom metadata fields in DigiCert should be imported into Keyfactor. - -- DigicertFieldName -For "ManualFields", this should specify the location and name of the field in the json returned from the DigiCert API following a certificate order query. If the field is not at the top level, the input should be delimited using a "." character: "organization_contact.email". The structure of the json the API returns can be viewed here: https://dev.digicert.com/services-api/orders/order-info/ -For "CustomFields", this should be the label of the custom metadata field as listed in DigiCert. - -- KeyfactorMetadataFieldName -This is the string that will be used as the field name in Keyfactor. -For "ManualFields", this needs to be configured. -For "CustomFields", if left blank, will use the same name as the same string as the DigicertFieldName, provided it has no spaces. - -- KeyfactorDescription -This is the string that will be setup as the field description in Keyfactor. - -- KeyfactorDataType -The datatype the field will use in Keyfactor. Currently accepted types are Int and String. - -- KeyfactorDataType -String to be input into Keyfactor as the metadata field hint. - -- KeyfactorAllowAPI -Allows API management of this metadata field in Keyfactor. Should be set to true for continuous synchronization with this tool. - -### Logging -Logging functionality can be configured via entering either "Debug" or "Trace" into the value of `` in NLog.config. +- **"kftodc"** +Syncronizes the contents of custom fields listed in manualfields.json from Keyfactor to DigiCert. If the fields in manualfields.json do not exist in Keyfactor or DigiCert, they are created first. Example: `.\DigicertMetadataSync.exe kftodc` + +- **"dctokf"** +Syncronizes the contents of both custom and non-custom fields from DigiCert to Keyfactor. The fields are listed in manualfields.json, and are created if necessary. Example: `.\DigicertMetadataSync.exe dctokf` + +### `DigicertMetadataSync.dll.config` keys +- **`DigicertAPIKey`** + Your DigiCert API Key with **API key restrictions** set to **Orders, Domains and Organizations". + +- **`DigicertAPIKeyTopPerm`** + A second DigiCert API key with **top-level permissions** (needed to read all custom fields).In DigiCert, to obtain one, you must keep the API key’s **API key restrictions** at **None** when creating it. + +- **`KeyfactorDomainAndUser`** + A Keyfactor login in the format `DOMAIN\username` with permissions to create metadata fields and update certificates. + +- **`KeyfactorPassword`** + Password for the `KeyfactorDomainAndUser` account. + +- **`KeyfactorCertSearchReturnLimit`** + Maximum number of certificates the tool will request from Keyfactor when searching (use a number that exceeds the total number of certs you are trying sync when running in prod). + +- **`KeyfactorAPIEndpoint`** + Your Keyfactor API base URL, e.g. `https://example.com/KeyfactorAPI/` + +- **`KeyfactorDigicertIssuedCertQueryTerm`** + A substring present in the **Issuer DN** of DigiCert‑issued certs in your Keyfactor instance (e.g., `"DigiCert"`). Used to scope the Keyfactor query to DigiCert certs only. + +- **`ImportAllCustomDigicertFields`** (boolean) + If `true`, import **all** custom fields from DigiCert automatically (labels are auto‑converted to valid Keyfactor names). If `false`, only the custom fields listed in the **`CustomFields`** section of `manualfields.json` are imported. + +- **`ReplaceDigicertWhiteSpaceCharacterInName`** + When `ImportAllCustomDigicertFields=true`, DigiCert labels that contain spaces will be converted to Keyfactor‑safe names using this string. Example: set to `"_"` to turn `"Requester Email"` into `"Requester_Email"`. + +- **`SyncReissue`** (boolean) + When `true`, the Keyfactor lookup includes **revoked** and **expired** certificates (`pq.includeRevoked=true&pq.includeExpired=true`). + +### Example `DigicertMetadataSync.dll.config` +File is available within the repository named as App.config (**should be renamed to DigicertMetadataSync.dll.config for actual use**). +```xml + + + + + + + + + + + + + + + + +``` +--- + +## `manualfields.json` + +This file tells the tool **which fields** to create in Keyfactor and **how to map** DigiCert values into them. It has two top‑level arrays: + +```jsonc +{ + "ManualFields": [ /* non-custom DigiCert fields you want to map */ ], + "CustomFields": [ /* DigiCert Custom Metadata Fields you want to map */ ] +} +``` + +### Common properties (apply to entries in **both** arrays) + +| Property | Type | Required | Description | +|---|---|---:|---| +| `DigicertFieldName` | string | yes | For **ManualFields**: a **dot path** into the DigiCert order/cert object (e.g., `organization_contact.email`). For **CustomFields**: the **label** of the DigiCert custom field _exactly as it appears_ in DigiCert. | +| `KeyfactorMetadataFieldName` | string | no | The Keyfactor field name to create/use. If omitted for **CustomFields**, the tool will derive a safe name from `DigicertFieldName` (label), replacing spaces with `ReplaceDigicertWhiteSpaceCharacterInName`. | +| `KeyfactorDescription` | string | no | Description to show in Keyfactor for this field. | +| `KeyfactorDataType` | string | yes | One of the **Keyfactor data types** listed below (e.g., `"String"`, `"Integer"`…). | +| `KeyfactorHint` | string | no | Hint to display in Keyfactor’s UI (e.g., sample format). | +| `KeyfactorAllowAPI` | bool/string | no | Defaults to `true`. Leave `true` to allow this tool to manage the field via API. | + +#### New/advanced properties +These map to Keyfactor’s Metadata Field schema and are optional unless your use case requires them. + +| Property | Type | Required | Notes | +|---|---|---:|---| +| `KeyfactorValidation` | string | no | Regex for **String** fields only. If set, `KeyfactorMessage` should describe the validation failure. | +| `KeyfactorMessage` | string | no | Validation error message shown if regex fails. | +| `KeyfactorEnrollment` | string | no | One of: `"Optional"` (default), `"Required"`, `"Hidden"`. | +| `KeyfactorOptions` | array\ | no | Choices for **Multiple Choice** fields. You may list them as an **array** in config; the tool serializes them to a **comma‑separated string** when posting to Keyfactor. | +| `KeyfactorDefaultValue` | string | no | Default field value. | + +### Example `manualfields.json` +File is available within the repository. +```json +{ + "ManualFields": [ + { + "DigicertFieldName": "id", + "KeyfactorMetadataFieldName": "DigicertID", + "KeyfactorDescription": "Digicert Assigned Cert ID", + "KeyfactorDataType": "Integer", + "KeyfactorHint": "", + "KeyfactorAllowAPI": "True", + "KeyfactorValidation": "", + "KeyfactorMessage": "", + "KeyfactorEnrollment": "Optional", + "KeyfactorOptions": [], + "KeyfactorDefaultValue": "" + } + ], + "CustomFields": [ + { + "DigicertFieldName": "Field2", + "KeyfactorMetadataFieldName": "Field2", + "KeyfactorDescription": "Test", + "KeyfactorDataType": "String", + "KeyfactorHint": "Pick one or more", + "KeyfactorAllowAPI": "True", + "KeyfactorValidation": "", + "KeyfactorMessage": "", + "KeyfactorEnrollment": "Optional", + "KeyfactorOptions": "", + "KeyfactorDefaultValue": "Test" + } + ] +} +``` + +> **Notes** +> • For **ManualFields**, `DigicertFieldName` must match the **flattened path** into the DigiCert order object returned by the Order Info API (e.g., `organization_contact.email`). +> • For **CustomFields**, `DigicertFieldName` must match the **label** in DigiCert (the tool will convert it to a Keyfactor‑safe name if you omit `KeyfactorMetadataFieldName`). +> • If `ImportAllCustomDigicertFields=true`, you can leave `CustomFields` empty—every custom field in DigiCert will be created/mapped automatically. + +--- + +## Keyfactor Metadata Field Data Types + +Use the **names** below in `KeyfactorDataType` (the tool maps them to the corresponding numeric codes internally when calling the Keyfactor API): + +| Name | Code | Typical uses | +|---|---:|---| +| `String` | 1 | Free text / short text | +| `Integer` | 2 | Numeric values | +| `Date` | 3 | Dates | +| `Boolean` | 4 | True/False | +| `Multiple Choice` | 5 | Fixed list of values (uses `KeyfactorOptions`) | +| `Big Text` | 6 | Long multi‑line text | + +--- + +## Sync flow (high level) +1. **Field alignment:** Creates/updates metadata fields in Keyfactor as defined in `manualfields.json` (plus, optionally, all DigiCert custom fields). +1a. If a field is detected as having invalid characters that Keyfactor does not accept, the tool exits with an appropriate message and replacechar.json is populated. +2. **Value sync:** Depending on whether you set the tool to `kftodc` or `dctokf`, the values are synced either from Keyfactor to DigiCert or from DigiCert to Keyfactor. + + +--- + +## Troubleshooting tips +- Validation only applies to **String** fields—omit `KeyfactorValidation`/`KeyfactorMessage` for other types. + + + +## License + +Apache License 2.0, see [LICENSE](LICENSE). + +## Related Integrations + +See all [Keyfactor integrations](https://github.com/topics/keyfactor-integration). \ No newline at end of file diff --git a/digicert-metadata-sync/AddFieldsToKeyfactor.cs b/digicert-metadata-sync/AddFieldsToKeyfactor.cs index 8425582..dbe96e0 100644 --- a/digicert-metadata-sync/AddFieldsToKeyfactor.cs +++ b/digicert-metadata-sync/AddFieldsToKeyfactor.cs @@ -1,100 +1,138 @@ -// Copyright 2021 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Newtonsoft.Json; -using RestSharp; -using RestSharp.Authenticators; - -namespace DigicertMetadataSync; - -// This fuction adds the fields to keyfactor. -// It will only add new fields. -internal partial class DigicertSync -{ - public static Tuple> AddFieldsToKeyfactor(List inputlist, - List existingmetadatalist, bool noexistingfields, string keyfactorusername, - string keyfactorpassword, string keyfactorapilocation) - { - var addfieldstokeyfactorurl = keyfactorapilocation + "MetadataFields"; - var addfieldsclient = new RestClient(); - addfieldsclient.Authenticator = new HttpBasicAuthenticator(keyfactorusername, keyfactorpassword); - var totalnumberadded = 0; - var newfields = new List(); - if (inputlist.Count != 0) - foreach (var metadatainstance in inputlist) - if (noexistingfields == false) - { - var fieldquery = from existingmetadatainstance in existingmetadatalist - where existingmetadatainstance.Name == metadatainstance.Name - select existingmetadatainstance; - // If field does not exist in Keyfactor, add it. - if (!fieldquery.Any()) - { - var addfieldrequest = new RestRequest(addfieldstokeyfactorurl); - addfieldrequest.AddHeader("Content-Type", "application/json"); - addfieldrequest.AddHeader("Accept", "application/json"); - addfieldrequest.AddHeader("x-keyfactor-api-version", "1"); - addfieldrequest.AddHeader("x-keyfactor-requested-with", "APIClient"); - var serializedfield = JsonConvert.SerializeObject(metadatainstance); - addfieldrequest.AddParameter("application/json", serializedfield, ParameterType.RequestBody); - var metadataresponse = new RestResponse(); - try - { - metadataresponse = addfieldsclient.Post(addfieldrequest); - newfields.Add(metadatainstance.Name); - ++totalnumberadded; - } - catch (HttpRequestException e) - { - Console.WriteLine(metadataresponse.Content); - throw e; - } - } - else - { - if (fieldquery.FirstOrDefault().DataType != metadatainstance.DataType) - { - //Throw error if datatype included in keyfactor does not match the digicert one. - var mismatchedtypes = new NotSupportedException(); - throw mismatchedtypes; - } - } - } - else - { - var addfieldrequest = new RestRequest(addfieldstokeyfactorurl); - addfieldrequest.AddHeader("Content-Type", "application/json"); - addfieldrequest.AddHeader("Accept", "application/json"); - addfieldrequest.AddHeader("x-keyfactor-api-version", "1"); - addfieldrequest.AddHeader("x-keyfactor-requested-with", "APIClient"); - var serializedfield = JsonConvert.SerializeObject(metadatainstance); - addfieldrequest.AddParameter("application/json", serializedfield, ParameterType.RequestBody); - var metadataresponse = new RestResponse(); - try - { - metadataresponse = addfieldsclient.Post(addfieldrequest); - ++totalnumberadded; - } - catch (HttpRequestException e) - { - Console.WriteLine(metadataresponse.Content); - throw e; - } - } - - var returnvals = new Tuple>(totalnumberadded, newfields); - - return returnvals; - } +// Copyright 2021 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Newtonsoft.Json; +using RestSharp; + +namespace DigicertMetadataSync; + +// This fuction adds the fields to keyfactor. +// It will only add new fields. +internal partial class DigicertSync +{ + public static Tuple> AddFieldsToKeyfactor(List inputlist, + List existingmetadatalist, bool noexistingfields, string keyfactorusername, + string keyfactorpassword, string keyfactorapilocation, RestClient kfClient) + { + var addfieldstokeyfactorurl = keyfactorapilocation + "MetadataFields"; + var totalnumberadded = 0; + var newfields = new List(); + if (inputlist.Count != 0) + foreach (var metadatainstance in inputlist) + if (noexistingfields == false) + { + var fieldquery = from existingmetadatainstance in existingmetadatalist + where existingmetadatainstance.Name == metadatainstance.Name + select existingmetadatainstance; + // If field does not exist in Keyfactor, add it. + if (!fieldquery.Any()) + { + // Ensure DisplayOrder is set and Options are in the expected format per latest API. + try + { + // API expects array for multiple choice; make sure it's not null. + + // Ensure Options is a string (always present) + if (metadatainstance.Options == null) + metadatainstance.Options = string.Empty; + } + catch + { + /* non-fatal; continue with best effort */ + } + + var addfieldrequest = new RestRequest(addfieldstokeyfactorurl); + addfieldrequest.AddHeader("Content-Type", "application/json"); + addfieldrequest.AddHeader("Accept", "application/json"); + addfieldrequest.AddHeader("x-keyfactor-api-version", "1"); + addfieldrequest.AddHeader("x-keyfactor-requested-with", "APIClient"); + var serializedfield = JsonConvert.SerializeObject(metadatainstance); + addfieldrequest.AddParameter("application/json", serializedfield, ParameterType.RequestBody); + var metadataresponse = new RestResponse(); + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try + { + metadataresponse = kfClient.Post(addfieldrequest); + ; + if (!metadataresponse.IsSuccessful) + { + var msg = "Something went wrong while adding field " + metadatainstance.Name + + " to Keyfactor."; + _logger.Error(msg); + throw new CustomException(msg, new Exception("Request failed.")); + } + } + catch (Exception ex) + { + _logger.Error($"Unexpected error: {ex}"); + throw; + } + + _logger.Trace("Added field {0} to Keyfactor.", metadatainstance.Name); + }); + newfields.Add(metadatainstance.Name); + ++totalnumberadded; + } + else + { + if (fieldquery.FirstOrDefault().DataType != metadatainstance.DataType) + { + //Throw error if datatype included in keyfactor does not match the digicert one. + var mismatchedtypes = new NotSupportedException(); + throw mismatchedtypes; + } + } + } + else + { + var addfieldrequest = new RestRequest(addfieldstokeyfactorurl); + addfieldrequest.AddHeader("Content-Type", "application/json"); + addfieldrequest.AddHeader("Accept", "application/json"); + addfieldrequest.AddHeader("x-keyfactor-api-version", "1"); + addfieldrequest.AddHeader("x-keyfactor-requested-with", "APIClient"); + var serializedfield = JsonConvert.SerializeObject(metadatainstance); + addfieldrequest.AddParameter("application/json", serializedfield, ParameterType.RequestBody); + var metadataresponse = new RestResponse(); + + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try + { + metadataresponse = kfClient.Post(addfieldrequest); + ; + if (!metadataresponse.IsSuccessful) + { + var msg = "Something went wrong while adding field " + metadatainstance.Name + + " to Keyfactor."; + _logger.Error(msg); + throw new CustomException(msg, new Exception("Request failed.")); + } + } + catch (Exception ex) + { + _logger.Error($"Unexpected error: {ex}"); + throw; + } + + _logger.Trace("Added field {0} to Keyfactor.", metadatainstance.Name); + }); + ++totalnumberadded; + } + + var returnvals = new Tuple>(totalnumberadded, newfields); + + return returnvals; + } } \ No newline at end of file diff --git a/digicert-metadata-sync/App.config b/digicert-metadata-sync/App.config index 4cf5c9f..94f77d0 100644 --- a/digicert-metadata-sync/App.config +++ b/digicert-metadata-sync/App.config @@ -1,4 +1,5 @@ - + + @@ -11,5 +12,6 @@ + \ No newline at end of file diff --git a/digicert-metadata-sync/BannedCharacters.cs b/digicert-metadata-sync/BannedCharacters.cs index 74e33fe..954bfde 100644 --- a/digicert-metadata-sync/BannedCharacters.cs +++ b/digicert-metadata-sync/BannedCharacters.cs @@ -13,7 +13,6 @@ // limitations under the License. using System.Text.RegularExpressions; -using Newtonsoft.Json.Linq; namespace DigicertMetadataSync; @@ -21,42 +20,37 @@ internal partial class DigicertSync { public static List BannedCharacterParse(string input) { - string pattern = "[a-zA-Z0-9-_]"; + var pattern = "[a-zA-Z0-9-_]"; - List bannedChars = new List(); + var bannedChars = new List(); - foreach (char c in input) - { + foreach (var c in input) if (!Regex.IsMatch(c.ToString(), pattern)) { - CharDBItem localitem = new CharDBItem(); + var localitem = new CharDBItem(); localitem.character = c.ToString(); localitem.replacementcharacter = "null"; bannedChars.Add(localitem); } - } if (bannedChars.Count > 0) - { Console.WriteLine("The field name " + input + " contains the following invalid characters: " + string.Join("", bannedChars.Select(item => item.character))); - } else - { Console.WriteLine("The field name " + input + " is valid."); - } return bannedChars; } - public static void CheckForChars(List input, List allBannedChars, bool restartandconfigrequired) + public static void CheckForChars(List input, List allBannedChars, + bool restartandconfigrequired) { foreach (var dgfield in input) { - List newChars = BannedCharacterParse(dgfield.DigicertFieldName); + var newChars = BannedCharacterParse(dgfield.DigicertFieldName); foreach (var newchar in newChars) { - bool exists = allBannedChars.Any(allcharchar => allcharchar.character == newchar.character); + var exists = allBannedChars.Any(allcharchar => allcharchar.character == newchar.character); if (!exists) { allBannedChars.Add(newchar); @@ -65,4 +59,4 @@ public static void CheckForChars(List input, List - - Exe - net6.0 - DigicertMetadataSync - enable - enable - + + Exe + net9.0 + DigicertMetadataSync + enable + enable + - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - Always - - + + + + + + + + + + + + Never + + + Never + + + Always + + + Never + + + PreserveNewest + + + PreserveNewest + + + Always + + + PreserveNewest + + - + \ No newline at end of file diff --git a/digicert-metadata-sync/GrabCustomFieldsFromDigiCert.cs b/digicert-metadata-sync/GrabCustomFieldsFromDigiCert.cs index 8795602..8883159 100644 --- a/digicert-metadata-sync/GrabCustomFieldsFromDigiCert.cs +++ b/digicert-metadata-sync/GrabCustomFieldsFromDigiCert.cs @@ -12,38 +12,63 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Keyfactor.Logging; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using NLog.Time; +using Newtonsoft.Json.Linq; using RestSharp; -using RestSharp.Authenticators; namespace DigicertMetadataSync; // This fuction adds the fields to keyfactor. // It will only add new fields. -partial class DigicertSync +internal partial class DigicertSync { - public static List GrabCustomFieldsFromDigiCert(string apikey, bool importdeactivated) + public static List GrabCustomFieldsFromDigiCert( + string apikey, bool importdeactivated, RestClient digicertClient) { - ILogger logger = LogHandler.GetClassLogger(); - var digicertclient = new RestClient(); - var customfieldsretrieval = "https://www.digicert.com/services/v2/account/metadata"; - var digicertrequest = new RestRequest(customfieldsretrieval); - digicertrequest.AddHeader("Accept", "application/json"); - digicertrequest.AddHeader("X-DC-DEVKEY", apikey); - var digicertresponse = digicertclient.Execute(digicertrequest); - var trimmeddigicertresponse = digicertresponse.Content.Remove(0, 12); - int lengthofresponse = trimmeddigicertresponse.Length; - trimmeddigicertresponse = trimmeddigicertresponse.Remove(lengthofresponse - 1, 1); - var fieldlist = JsonConvert.DeserializeObject>(trimmeddigicertresponse); - if (importdeactivated == false) + const string url = "https://www.digicert.com/services/v2/account/metadata"; + + var req = new RestRequest(url); + req.AddHeader("Accept", "application/json"); + req.AddHeader("Content-Type", "application/json"); // matches DigiCert examples + req.AddHeader("X-DC-DEVKEY", apikey); + + var resp = new RestResponse(); + GlobalRetryPolicy.RetryPolicy.Execute(() => { - fieldlist.RemoveAll(unit => unit.is_active == false); + try + { + resp = digicertClient.Execute(req); + if (!resp.IsSuccessful) + { + var msg = "Something went wrong while retrieving custom fields from DigiCert."; + _logger.Error(msg + $" HTTP {(int)resp.StatusCode} {resp.StatusDescription}. Body: {resp.Content}"); + throw new CustomException(msg, new Exception("Request failed.")); + } + } + catch (Exception ex) + { + _logger.Error($"Unexpected error: {ex}"); + throw; + } + + _logger.Debug("Obtained custom fields from DigiCert."); + }); + + // Robust JSON handling per spec: + // { "metadata": [ ... ] } OR {} when none + var fieldlist = new List(); + if (!string.IsNullOrWhiteSpace(resp.Content)) + { + var root = JObject.Parse(resp.Content); + var metaToken = root["metadata"]; + if (metaToken != null && metaToken.Type == JTokenType.Array) + fieldlist = metaToken.ToObject>() ?? + new List(); + // else {} --> leave fieldlist empty } + + if (!importdeactivated) fieldlist.RemoveAll(f => f.is_active == false); + Console.WriteLine("Obtained custom fields from DigiCert."); - logger.LogDebug("Obtained custom fields from DigiCert."); return fieldlist; } } \ No newline at end of file diff --git a/digicert-metadata-sync/Helpers.cs b/digicert-metadata-sync/Helpers.cs index 0246749..7f5d371 100644 --- a/digicert-metadata-sync/Helpers.cs +++ b/digicert-metadata-sync/Helpers.cs @@ -1,4 +1,4 @@ -// Copyright 2021 Keyfactor +// Copyright 2021 Keyfactor // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,23 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. - -using System.Collections.Generic; - -using System.Text.RegularExpressions; +using System.Configuration; using Newtonsoft.Json.Linq; namespace DigicertMetadataSync; internal partial class DigicertSync { - public static int TypeMatcher(string digicerttype) + public static int TypeMatcher(string keyfactorType) { - if (digicerttype.Contains("int") || digicerttype.Contains("Int")) - // 2 matches the keyfactor int type metadata field - return 2; - //1 matches the keyfactor string type - return 1; + if (string.IsNullOrWhiteSpace(keyfactorType)) return 1; + var t = keyfactorType.Trim().ToLowerInvariant(); + + // canonical names + common aliases + return t switch + { + "string" or "str" or "text" => 1, + "int" or "integer" => 2, + "date" or "datetime" => 3, + "bool" or "boolean" => 4, + "multiple choice" or "multiplechoice" or "choice" or "dropdown" => 5, + "big text" or "bigtext" or "multiline" => 6, + _ => 1 + }; } } @@ -57,12 +63,9 @@ public static Dictionary ClassConverter(object obj) return null; } - public static string ReplaceAllBannedCharacters(string input, ListallBannedChars) + public static string ReplaceAllBannedCharacters(string input, List allBannedChars) { - foreach (CharDBItem item in allBannedChars) - { - input = input.Replace(item.character, item.replacementcharacter); - } + foreach (var item in allBannedChars) input = input.Replace(item.character, item.replacementcharacter); return input; } @@ -72,32 +75,70 @@ public static bool CheckMode(string mode) return false; } - private static List convertlisttokf(List inputlist, List allBannedChars, bool importallcustomfields) + private static List convertlisttokf( + List inputlist, + List allBannedChars, + bool importallcustomfields) { var formattedlist = new List(); - if (inputlist.Count != 0) - foreach (var input in inputlist) - { - var formatinstance = new KeyfactorMetadataInstanceSendoff(); + if (inputlist == null || inputlist.Count == 0) return formattedlist; - if (input.KeyfactorMetadataFieldName == null || input.KeyfactorMetadataFieldName == "" || input.FieldType == "Custom") - //If name is empty, clean up the characters. - formatinstance.Name = ReplaceAllBannedCharacters(input.DigicertFieldName, allBannedChars); + foreach (var input in inputlist) + { + var formatinstance = new KeyfactorMetadataInstanceSendoff(); + + // Name: prefer user override; else clean DigiCert name + formatinstance.Name = string.IsNullOrWhiteSpace(input.KeyfactorMetadataFieldName) || + input.FieldType == "Custom" + ? ReplaceAllBannedCharacters(input.DigicertFieldName, allBannedChars) + : input.KeyfactorMetadataFieldName; + + formatinstance.AllowAPI = SafeToBool(input.KeyfactorAllowAPI, true); + formatinstance.Hint = input.KeyfactorHint ?? ""; + formatinstance.DataType = TypeMatcher(input.KeyfactorDataType); + formatinstance.Description = input.KeyfactorDescription ?? ""; + + // ALWAYS build a CSV string (even for non-Multiple Choice types) + var optionsList = input.KeyfactorOptions ?? new List(); + formatinstance.Options = string.Join(",", + optionsList.Where(static s => !string.IsNullOrWhiteSpace(s)) + .Select(s => s.Trim()) + .Distinct()); + + // Optional extras (only if you use them) + formatinstance.Validation = input.KeyfactorValidation ?? ""; + formatinstance.Message = input.KeyfactorMessage ?? ""; + formatinstance.DefaultValue = input.KeyfactorDefaultValue ?? ""; + formatinstance.Enrollment = EnrollmentMatcher(input.KeyfactorEnrollment); + + formattedlist.Add(formatinstance); + } - else - //Use user input preferred name. - formatinstance.Name = input.KeyfactorMetadataFieldName; - - formatinstance.AllowAPI = Convert.ToBoolean(input.KeyfactorAllowAPI); - formatinstance.Hint = input.KeyfactorHint; - formatinstance.DataType = TypeMatcher(input.KeyfactorDataType); - formatinstance.Description = input.KeyfactorDescription; - formattedlist.Add(formatinstance); - } + return formattedlist; + static bool SafeToBool(string s, bool defVal) + { + if (string.IsNullOrWhiteSpace(s)) return defVal; + if (bool.TryParse(s, out var b)) return b; + s = s.Trim().ToLowerInvariant(); + return s is "1" or "y" or "yes" or "true" ? true : + s is "0" or "n" or "no" or "false" ? false : defVal; + } + static int EnrollmentMatcher(string s) + { + if (string.IsNullOrWhiteSpace(s)) return 0; + s = s.Trim().ToLowerInvariant(); + return s switch { "required" => 1, "hidden" => 2, "1" => 1, "2" => 2, _ => 0 }; + } + } + private static int? ReadMaxItemsLimit() + { + var s = ConfigurationManager.AppSettings["KeyfactorSearchLimit"] + ?? ConfigurationManager.AppSettings["KeyfactorCertSearchReturnLimit"]; - return formattedlist; + if (int.TryParse(s, out var v) && v > 0) return v; // enforce only if > 0 + return null; // unlimited } public static JObject Flatten(JObject jObject, string parentName = "") diff --git a/digicert-metadata-sync/MetadataSync.cs b/digicert-metadata-sync/MetadataSync.cs index ce31580..8b1d149 100644 --- a/digicert-metadata-sync/MetadataSync.cs +++ b/digicert-metadata-sync/MetadataSync.cs @@ -1,21 +1,18 @@ -// Copyright 2021 Keyfactor +// Copyright 2021 Keyfactor // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions // and limitations under the License. -using System.Collections.Generic; -using System.IO; -using System.Reflection.Metadata.Ecma335; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using NLog; +using Polly; using RestSharp; using RestSharp.Authenticators; using ConfigurationManager = System.Configuration.ConfigurationManager; -//using Keyfactor.Logging; - namespace DigicertMetadataSync; internal partial class DigicertSync @@ -30,11 +27,12 @@ public static void Main(string[] args) var digicertapikeytopperm = ConfigurationManager.AppSettings.Get("DigicertAPIKeyTopPerm"); var keyfactorusername = ConfigurationManager.AppSettings.Get("KeyfactorDomainAndUser"); var keyfactorpassword = ConfigurationManager.AppSettings.Get("KeyfactorPassword"); - var importdeactivated = Convert.ToBoolean(ConfigurationManager.AppSettings.Get("ImportDataForDeactivatedDigiCertFields")); - int batchsize = 200; + var importdeactivated = + Convert.ToBoolean(ConfigurationManager.AppSettings.Get("ImportDataForDeactivatedDigiCertFields")); + var batchsize = 200; var importallcustomdigicertfields = Convert.ToBoolean(ConfigurationManager.AppSettings.Get("ImportAllCustomDigicertFields")); - _logger.Debug("Settings: importallcustomdigicertfields={0}, replacementcharacter={1}", + _logger.Debug("Settings: importallcustomdigicertfields={0}", importallcustomdigicertfields); var config_mode = args[0]; if (CheckMode(config_mode) == false) @@ -46,31 +44,59 @@ public static void Main(string[] args) var digicertIssuerQueryterm = ConfigurationManager.AppSettings.Get("KeyfactorDigicertIssuedCertQueryTerm"); var returnlimit = ConfigurationManager.AppSettings.Get("KeyfactorCertSearchReturnLimit"); var keyfactorapilocation = ConfigurationManager.AppSettings.Get("KeyfactorAPIEndpoint"); - int returnlimitint = Int32.Parse(returnlimit); - int numberOfBatches = (int)Math.Ceiling((double)returnlimitint / batchsize); + var syncreissue = Convert.ToBoolean(ConfigurationManager.AppSettings.Get("SyncReissue")); + if (syncreissue) + Console.WriteLine("Reissued and revoked cert data will be synced"); + else + Console.WriteLine("Reissued and revoked cert data will not be synced"); + var returnlimitint = int.Parse(returnlimit); + var numberOfBatches = (int)Math.Ceiling((double)returnlimitint / batchsize); _logger.Debug("Loaded config. Starting metadata field name processing."); - // Initializing net client - var client = new RestClient(); - client.Authenticator = new HttpBasicAuthenticator(keyfactorusername, keyfactorpassword); + // Initializing Keyfactor net client + var kfoptions = new RestClientOptions(); + kfoptions.Authenticator = new HttpBasicAuthenticator(keyfactorusername, keyfactorpassword); + var kfclient = new RestClient(kfoptions); + + //Initializing DigiCert net client + var digicertClient = new RestClient(); //Getting list of custom metadata fields from Keyfactor var getmetadalistkf = keyfactorapilocation + "MetadataFields"; - var getmetadatakfclient = new RestClient(); - getmetadatakfclient.Authenticator = new HttpBasicAuthenticator(keyfactorusername, keyfactorpassword); var metadatakfrequest = new RestRequest(getmetadalistkf); metadatakfrequest.AddHeader("Accept", "application/json"); metadatakfrequest.AddHeader("x-keyfactor-api-version", "1"); metadatakfrequest.AddHeader("x-keyfactor-requested-with", "APIClient"); - var metadatakfresponse = client.Execute(metadatakfrequest); - var metadatakfrawresponse = metadatakfresponse.Content; - var kfmetadatafields = JsonConvert.DeserializeObject>(metadatakfrawresponse); + var metadatakfresponse = new RestResponse(); + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try + { + metadatakfresponse = kfclient.Execute(metadatakfrequest); + ; + if (!metadatakfresponse.IsSuccessful) + { + var msg = "Something went wrong while retrieving list of custom metadata fields from Keyfactor."; + _logger.Error(msg); + throw new CustomException(msg, new Exception("Request failed.")); + } + } + catch (Exception ex) + { + _logger.Error($"Unexpected error: {ex}"); + throw; + } + + _logger.Debug("Got list of custom fields from Keyfactor."); + }); + //var metadatakfrawresponse = metadatakfresponse.Content; + //var kfmetadatafields = JsonConvert.DeserializeObject>(metadatakfrawresponse); Console.WriteLine("Got list of custom fields from Keyfactor."); - _logger.Debug("Got list of custom fields from Keyfactor."); //Getting list of custom metadata fields on DigiCert - var customdigicertmetadatafieldlist = GrabCustomFieldsFromDigiCert(digicertapikey, importdeactivated); + var customdigicertmetadatafieldlist = + GrabCustomFieldsFromDigiCert(digicertapikey, importdeactivated, digicertClient); //Convert DigiCert custom fields to Keyfactor appropriate ones //This depends on whether the setting to import all fields was enabled or not @@ -130,10 +156,8 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. if (kfcustomfields == null) kfcustomfields = new List(); _logger.Debug("Loading custom fields using json, no autofill/conversion"); } - foreach (var item in kfcustomfields) - { - item.FieldType = "Custom"; - } + + foreach (var item in kfcustomfields) item.FieldType = "Custom"; //Adding metadata fields for the ID and the email of the requester from DigiCert. @@ -141,91 +165,110 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. var manualfieldslist = "ManualFields"; kfmanualfields = config.GetSection(manualfieldslist).Get>(); if (kfmanualfields == null) kfmanualfields = new List(); - foreach (var item in kfmanualfields) - { - item.FieldType = "Manual"; - } + foreach (var item in kfmanualfields) item.FieldType = "Manual"; _logger.Debug("Performed field conversion."); //Pulling list of existing metadata fields from Keyfactor for later comparison. var noexistingfields = true; var existingmetadataurl = keyfactorapilocation + "MetadataFields"; - var existingmetadataclient = new RestClient(); - existingmetadataclient.Authenticator = new HttpBasicAuthenticator(keyfactorusername, keyfactorpassword); var existingmetadatareq = new RestRequest(existingmetadataurl); existingmetadatareq.AddHeader("Accept", "application/json"); existingmetadatareq.AddHeader("x-keyfactor-api-version", "1"); existingmetadatareq.AddHeader("x-keyfactor-requested-with", "APIClient"); - var existingmetadataresponse = existingmetadataclient.Execute(existingmetadatareq); + var existingmetadataresponse = new RestResponse(); + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try + { + existingmetadataresponse = kfclient.Execute(existingmetadatareq); + if (!existingmetadataresponse.IsSuccessful) + { + var msg = "Failed to retrieve list of existing metadata fields from Keyfactor."; + _logger.Error(msg); + throw new CustomException(msg, new Exception("Request failed.")); + } + } + catch (Exception ex) + { + _logger.Error($"Unexpected error: {ex}"); + throw; + } + + _logger.Debug("Pulled existing metadata fields from Keyfactor."); + }); var existingmetadatalist = new List(); if (existingmetadataresponse != null) { - //Fields exist + var settings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore + // Converter already attached via attribute; you could also add it here if preferred: + // Converters = { new StringOrArrayToListConverter() } + }; + existingmetadatalist = - JsonConvert.DeserializeObject>(existingmetadataresponse.Content); + JsonConvert.DeserializeObject>( + existingmetadataresponse.Content, settings); + noexistingfields = false; } Console.WriteLine("Pulled existing metadata fields from keyfactor."); - _logger.Debug("Pulled existing metadata fields from Keyfactor."); - - + // Carrying out the persistent banned character database check // Loading up the character database - string currentDirectory = Directory.GetCurrentDirectory(); + var currentDirectory = Directory.GetCurrentDirectory(); - string filePath = Path.Combine(currentDirectory, "replacechar.json"); + var filePath = Path.Combine(currentDirectory, "replacechar.json"); - bool restartandconfigrequired = false; + var restartandconfigrequired = false; - List allBannedChars = JsonConvert.DeserializeObject>(File.ReadAllText(filePath)); + var allBannedChars = JsonConvert.DeserializeObject>(File.ReadAllText(filePath)); if (importallcustomdigicertfields) { CheckForChars(kfmanualfields, allBannedChars, restartandconfigrequired); CheckForChars(kfcustomfields, allBannedChars, restartandconfigrequired); - string formattedjsonchars = JsonConvert.SerializeObject(allBannedChars, Formatting.Indented); + var formattedjsonchars = JsonConvert.SerializeObject(allBannedChars, Formatting.Indented); File.WriteAllText(filePath, formattedjsonchars); foreach (var badchar in allBannedChars) - { if (badchar.replacementcharacter == "null") { restartandconfigrequired = true; break; } - } if (restartandconfigrequired) { - _logger.Trace("Please replace \"null\" with your desired replacement characters in replacechar.json and re-run the tool! Only alphanumerics, \"-\" and \"_\" are allowed"); - Console.WriteLine("Please replace \"null\" with your desired replacement characters in replacechar.json and re-run the tool! Only alphanumerics, \"-\" and \"_\" are allowed"); + _logger.Error( + "Please replace \"null\" with your desired replacement characters in replacechar.json and re-run the tool! Only alphanumerics, \"-\" and \"_\" are allowed"); + Console.WriteLine( + "Please replace \"null\" with your desired replacement characters in replacechar.json and re-run the tool! Only alphanumerics, \"-\" and \"_\" are allowed"); Environment.Exit(0); } - - } - // Converting the read in fields into sendable lists + // Converting the read in fields into sendable lists var convertedmanualfields = convertlisttokf(kfmanualfields, allBannedChars, importallcustomdigicertfields); var convertedcustomfields = convertlisttokf(kfcustomfields, allBannedChars, importallcustomdigicertfields); - _logger.Trace("Sending following manual fields to KF: {0}", JsonConvert.SerializeObject(convertedmanualfields)); var totalfieldsadded = 0; //If all the fields are absent from Keyfactor, the fields are added. var manualresult = AddFieldsToKeyfactor(convertedmanualfields, existingmetadatalist, noexistingfields, - keyfactorusername, keyfactorpassword, keyfactorapilocation); + keyfactorusername, keyfactorpassword, keyfactorapilocation, kfclient); _logger.Trace("Sending following custom fields to KF: {0}", JsonConvert.SerializeObject(convertedcustomfields)); var customresult = AddFieldsToKeyfactor(convertedcustomfields, existingmetadatalist, noexistingfields, - keyfactorusername, keyfactorpassword, keyfactorapilocation); + keyfactorusername, keyfactorpassword, keyfactorapilocation, kfclient); totalfieldsadded += manualresult.Item1; totalfieldsadded += customresult.Item1; @@ -242,11 +285,11 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. if (config_mode == "kftodc") { // Initialize variable to keep track of items downloaded so far - int certsdownloaded = 0; + var certsdownloaded = 0; var certcounttracker = 0; var totalcertsprocessed = 0; var numcertsdatauploaded = 0; - for (int batchnum = 0; batchnum < numberOfBatches; batchnum++) + for (var batchnum = 0; batchnum < numberOfBatches; batchnum++) { // Check if reaching the arbitrary limit if (certsdownloaded + batchsize > returnlimitint) @@ -262,18 +305,40 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. // Download the items in this batch var digicertlookup = keyfactorapilocation + "Certificates?pq.queryString=IssuerDN%20-contains%20%22" - + digicertIssuerQueryterm + "%22&pq.returnLimit=" + batchsize.ToString() + - "&includeMetadata=true" + "&pq.pageReturned=" + batchnum.ToString(); + + digicertIssuerQueryterm + "%22&pq.returnLimit=" + + batchsize + + "&includeMetadata=true" + "&pq.pageReturned=" + batchnum; + if (syncreissue) digicertlookup += "&pq.includeRevoked=true&pq.includeExpired=true"; var request = new RestRequest(digicertlookup); request.AddHeader("Accept", "application/json"); request.AddHeader("x-keyfactor-api-version", "1"); request.AddHeader("x-keyfactor-requested-with", "APIClient"); - var response = client.Execute(request); - var rawresponse = response.Content; + var kflookupresponse = new RestResponse(); + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try + { + kflookupresponse = kfclient.Execute(request); + if (!kflookupresponse.IsSuccessful) + { + var msg = + "Something went wrong while retrieving batch of DigiCert issued certs from Keyfactor."; + _logger.Error(msg); + throw new CustomException(msg, new Exception("Request failed.")); + } + } + catch (Exception ex) + { + _logger.Error($"Unexpected error: {ex}"); + throw; + } + + _logger.Debug("Got DigiCert issued certs from keyfactor"); + }); + var rawresponse = kflookupresponse.Content; var certlist = JsonConvert.DeserializeObject>(rawresponse, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); Console.WriteLine("Got DigiCert issued certs from keyfactor"); - _logger.Debug("Got DigiCert issued certs from keyfactor"); // Rebuild the list of metadata field names as they are on DigiCerts side. @@ -336,7 +401,8 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. // Grabbing the list again from digicert, populating ids for new ones //Getting list of custom metadata fields on DigiCert - var updatedmetadatafieldlist = GrabCustomFieldsFromDigiCert(digicertapikey, importdeactivated); + var updatedmetadatafieldlist = + GrabCustomFieldsFromDigiCert(digicertapikey, importdeactivated, digicertClient); foreach (var subitem in updatedmetadatafieldlist) foreach (var fulllistitem in fullcustomdgfieldlist) if (subitem.label == fulllistitem.label) @@ -369,7 +435,31 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. var lookupnewrequest = new RestRequest(digicertnewlookupurl); lookupnewrequest.AddHeader("Content-Type", "application/json"); lookupnewrequest.AddHeader("X-DC-DEVKEY", digicertapikey); - var digicertnewlookupresponse = client.Execute(lookupnewrequest); + + var digicertnewlookupresponse = new RestResponse(); + + + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try + { + digicertnewlookupresponse = digicertClient.Execute(lookupnewrequest); + if (!digicertnewlookupresponse.IsSuccessful) + { + string msg = "Something went wrong while retrieving data for cert with serial" + + kfserialnumber + " from DigiCert."; + _logger.Error(msg); + throw new CustomException(msg, new Exception("Request failed.")); + } + } + catch (Exception ex) + { + _logger.Error($"Unexpected error: {ex}"); + throw; + } + + _logger.Trace("Retrieved data for cert with serial {0} from DigiCert.", kfserialnumber); + }); var newparseddigicertresponse = JsonConvert.DeserializeObject(digicertnewlookupresponse.Content); @@ -403,8 +493,35 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. var newserializedsyncfield = JsonConvert.SerializeObject(metadatapayload); digicertnewfieldsrequest.AddParameter("application/json", newserializedsyncfield, ParameterType.RequestBody); - var digicertresponsenewfields = - digicertnewfieldsclient.Post(digicertnewfieldsrequest); + + var digicertresponsenewfields = new RestResponse(); + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try + { + digicertresponsenewfields = + digicertnewfieldsclient.Post(digicertnewfieldsrequest); + if (!digicertresponsenewfields.IsSuccessful) + { + string msg = + "Something went wrong while updating metadata for cert" + + cert["SerialNumber"].ToString() + " in DigiCert."; + _logger.Error(msg); + throw new CustomException(msg, + new Exception("Request failed.")); + } + } + catch (Exception ex) + { + _logger.Error($"Unexpected error: {ex}"); + throw; + } + + _logger.Trace("Updated metadata for cert {0} in DigiCert.", + cert["SerialNumber"].ToString()); + }); + + datauploaded = true; } } @@ -429,7 +546,8 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. Console.WriteLine($"Certs that had their metadata synced: {numcertsdatauploaded.ToString()}"); _logger.Debug( $"Metadata sync from Keyfactor to DigiCert complete. Number of certs processed: {totalcertsprocessed.ToString()}"); - _logger.Debug($"Certs that had their metadata synced: {numcertsdatauploaded.ToString()}"); ; + _logger.Debug($"Certs that had their metadata synced: {numcertsdatauploaded.ToString()}"); + ; break; } @@ -440,9 +558,9 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. if (config_mode == "dctokf") { // Initialize variable to keep track of items downloaded so far - int certsdownloaded = 0; + var certsdownloaded = 0; var certcounttracker = 0; - for (int batchnum = 0; batchnum < numberOfBatches; batchnum++) + for (var batchnum = 0; batchnum < numberOfBatches; batchnum++) { // Check if reaching the arbitrary limit if (certsdownloaded + batchsize > returnlimitint) @@ -457,114 +575,182 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. var digicertlookup = keyfactorapilocation + "Certificates?pq.queryString=IssuerDN%20-contains%20%22" - + digicertIssuerQueryterm + "%22&pq.returnLimit=" + batchsize.ToString() + - "&includeMetadata=true" + "&pq.pageReturned=" + batchnum.ToString(); + + digicertIssuerQueryterm + "%22&pq.returnLimit=" + + batchsize + + "&includeMetadata=true" + "&pq.pageReturned=" + batchnum; + if (syncreissue) digicertlookup += "&pq.includeRevoked=true&pq.includeExpired=true"; var request = new RestRequest(digicertlookup); request.AddHeader("Accept", "application/json"); request.AddHeader("x-keyfactor-api-version", "1"); request.AddHeader("x-keyfactor-requested-with", "APIClient"); - var response = client.Execute(request); - var rawresponse = response.Content; - var certlist = JsonConvert.DeserializeObject>(rawresponse, - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); - Console.WriteLine("Got DigiCert issued certs from keyfactor"); - _logger.Debug("Got DigiCert issued certs from keyfactor"); + var keyfactorlookupResponse = new RestResponse(); - //Each cert that is DigiCert in origin in Keyfactor is looked up on DigiCert via serial number, - //and the metadata contents from those fields are stored. - var digicertlookupclient = new RestClient(); - var digicertcertificates = new List(); - foreach (var certinstance in certlist) + + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try + { + keyfactorlookupResponse = kfclient.Execute(request); + if (!keyfactorlookupResponse.IsSuccessful) + { + var msg = + "Something went wrong while retrieving list of DigiCert issued certs from Keyfactor."; + _logger.Error(msg); + throw new CustomException(msg, new Exception("Request failed.")); + } + } + catch (Exception ex) { - var digicertlookupurl = "https://www.digicert.com/services/v2/order/certificate/"; + _logger.Error($"Unexpected error: {ex}"); + throw; + } - var bodytemplate = new RootDigicertLookup(); - var searchcriterioninstance = new SearchCriterion(); - bodytemplate.searchCriteriaList.Add(searchcriterioninstance); + _logger.Debug("Got DigiCert issued certs from keyfactor"); + }); - digicertlookupurl = digicertlookupurl + certinstance.SerialNumber; - var lookuprequest = new RestRequest(digicertlookupurl); - lookuprequest.AddHeader("Content-Type", "application/json"); - lookuprequest.AddHeader("X-DC-DEVKEY", digicertapikey); - var digicertlookupresponse = client.Execute(lookuprequest); - var certcontent = JsonConvert.DeserializeObject(digicertlookupresponse.Content, - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + var rawresponse = keyfactorlookupResponse.Content; + var certlist = JsonConvert.DeserializeObject>(rawresponse, + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + Console.WriteLine("Got DigiCert issued certs from keyfactor"); - if (certcontent["certificate"] != null) + //Each cert that is DigiCert in origin in Keyfactor is looked up on DigiCert via serial number, + //and the metadata contents from those fields are stored. + var digicertlookupclient = new RestClient(); + var digicertcertificates = new List(); + foreach (var certinstance in certlist) + { + // Use Order info endpoint; you can pass the primary cert serial number in the path. + // NOTE: This only works for the primary certificate on the order, not duplicates. + // Docs: GET /services/v2/order/certificate/{order_id|serial} + var digicertlookupurl = + $"https://www.digicert.com/services/v2/order/certificate/{certinstance.SerialNumber}"; + var lookuprequest = new RestRequest(digicertlookupurl); + lookuprequest.AddHeader("Accept", "application/json"); + lookuprequest.AddHeader("X-DC-DEVKEY", digicertapikey); + + var digicertlookupresponse = new RestResponse(); + + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try { - digicertcertificates.Add(certcontent); - _logger.Trace("Pulled and storing following cert from digicert: {0}", - digicertlookupresponse.Content); + digicertlookupresponse = digicertClient.Execute(lookuprequest); + if (!digicertlookupresponse.IsSuccessful) + { + var msg = $"DigiCert order lookup failed for serial {certinstance.SerialNumber}."; + _logger.Error(msg + + $" HTTP {(int)digicertlookupresponse.StatusCode} {digicertlookupresponse.StatusDescription}. Body: {digicertlookupresponse.Content}"); + throw new CustomException(msg, new Exception("Request failed.")); + } } - else + catch (Exception ex) { - _logger.Trace("Failed to retrieve cert {0} from Digicert", digicertlookupresponse.Content); + _logger.Error($"Unexpected error: {ex}"); + throw; } - } - Console.WriteLine("Pulled DigiCert matching DigiCert cert data."); - _logger.Debug("Pulled DigiCert matching DigiCert cert data."); + _logger.Trace("Located order for cert serial {0} in DigiCert.", certinstance.SerialNumber); + }); - foreach (var digicertcertinstance in digicertcertificates) + // Parse as an order object (NOT just the 'certificate' object) + var order = JObject.Parse(digicertlookupresponse.Content); + + // Serial number for matching (order info includes it) + var serial = order["certificate"]?["serial_number"]?.ToString()?.ToUpperInvariant(); + if (string.IsNullOrEmpty(serial)) { - var finalsyncclient = new RestClient(); - finalsyncclient.Authenticator = new HttpBasicAuthenticator(keyfactorusername, keyfactorpassword); - var finalsyncurl = keyfactorapilocation + "Certificates/Metadata"; - //Find matching certificate via Keyfactor ID - var test = digicertcertinstance["certificate"]["serial_number"].ToString().ToUpper(); - var query = from kfcertlocal in certlist - where kfcertlocal.SerialNumber == - digicertcertinstance["certificate"]["serial_number"].ToString().ToUpper() - select kfcertlocal; - var certificateid = query.FirstOrDefault().Id; - - - var payloadforkf = new KeyfactorMetadataQuery(); - payloadforkf.Id = certificateid; - - if (digicertcertinstance["custom_fields"] != null) - // Getting custom metadata field values - foreach (var metadatafieldinstance in digicertcertinstance["custom_fields"]) - if (importallcustomdigicertfields) - { - // Using autoimport and thus using autorename - var metadatanamefield = ReplaceAllBannedCharacters(metadatafieldinstance["label"].ToString(), - allBannedChars); - payloadforkf.Metadata[metadatanamefield] = metadatafieldinstance["value"]; - } - else - { - //Using custom names - var metadatanamequery = from customfieldinstance in kfcustomfields - where customfieldinstance.DigicertFieldName == - metadatafieldinstance["label"] - select customfieldinstance; - if (metadatanamequery.FirstOrDefault() != null) - payloadforkf.Metadata[metadatanamequery.FirstOrDefault().DigicertFieldName] = - metadatafieldinstance["value"]; - } + _logger.Trace("Order response missing certificate.serial_number for serial {0}", + certinstance.SerialNumber); + continue; + } + + // Keep the whole order object; custom_fields are at the order level + digicertcertificates.Add(order); + } + - var flattenedcert = Flatten(digicertcertinstance); - //Getting manually selected metadata field values (not custom in DigiCert) - foreach (var manualinstance in kfmanualfields) - if (flattenedcert[manualinstance.DigicertFieldName] != null) - payloadforkf.Metadata[manualinstance.KeyfactorMetadataFieldName] = - flattenedcert[manualinstance.DigicertFieldName].ToString(); - //Sending the payload off to Keyfactor for the update - var finalsyncreq = new RestRequest(finalsyncurl); - finalsyncreq.AddHeader("Content-Type", "application/json"); - finalsyncreq.AddHeader("x-keyfactor-api-version", "1"); - finalsyncreq.AddHeader("x-keyfactor-requested-with", "APIClient"); - var serializedsyncfield = JsonConvert.SerializeObject(payloadforkf); - _logger.Trace("Sending Metadata update to KF for cert ID {0}, metadata update: {1}", - payloadforkf.Id.ToString(), serializedsyncfield); - - finalsyncreq.AddParameter("application/json", serializedsyncfield, ParameterType.RequestBody); - finalsyncclient.Put(finalsyncreq); - ++certcounttracker; + Console.WriteLine("Pulled DigiCert matching DigiCert cert data."); + _logger.Debug("Pulled DigiCert matching DigiCert cert data."); + var finalsyncurl = keyfactorapilocation + "Certificates/Metadata"; + foreach (var digicertcertinstance in digicertcertificates) + { + // Match Keyfactor cert + var test = digicertcertinstance["certificate"]?["serial_number"]?.ToString()?.ToUpperInvariant(); + var certificateid = certlist.FirstOrDefault(k => k.SerialNumber == test)?.Id ?? 0; + if (certificateid == 0) + { + _logger.Trace("KF match not found for serial {0}", test); + continue; } - + var payloadforkf = new KeyfactorMetadataQuery { Id = certificateid }; + + // --- CUSTOM FIELDS --- + var customFields = digicertcertinstance["custom_fields"] as JArray; + if (customFields != null) + foreach (var cf in customFields) + { + var label = cf["label"]?.ToString(); + var value = cf["value"]; // keep JToken: could be string/number/null + + if (importallcustomdigicertfields) + { + // Auto-import: sanitize label for KF key + var metadatanamefield = ReplaceAllBannedCharacters(label ?? "", allBannedChars); + payloadforkf.Metadata[metadatanamefield] = value; + } + else + { + // Mapped import: use your mapping table + var mapping = kfcustomfields.FirstOrDefault(x => x.DigicertFieldName == label); + if (mapping != null) + // BUGFIX: target should be the Keyfactor field name, not the DigiCert label + payloadforkf.Metadata[mapping.KeyfactorMetadataFieldName] = value; + } + } + + var flattenedcert = Flatten(digicertcertinstance); + //Getting manually selected metadata field values (not custom in DigiCert) + foreach (var manualinstance in kfmanualfields) + if (flattenedcert[manualinstance.DigicertFieldName] != null) + payloadforkf.Metadata[manualinstance.KeyfactorMetadataFieldName] = + flattenedcert[manualinstance.DigicertFieldName].ToString(); + //Sending the payload off to Keyfactor for the update + var finalsyncreq = new RestRequest(finalsyncurl); + finalsyncreq.AddHeader("Content-Type", "application/json"); + finalsyncreq.AddHeader("x-keyfactor-api-version", "1"); + finalsyncreq.AddHeader("x-keyfactor-requested-with", "APIClient"); + var serializedsyncfield = JsonConvert.SerializeObject(payloadforkf); + _logger.Trace("Sending Metadata update to KF for cert ID {0}, metadata update: {1}", + payloadforkf.Id.ToString(), serializedsyncfield); + + finalsyncreq.AddParameter("application/json", serializedsyncfield, ParameterType.RequestBody); + var finalresponse = new RestResponse(); + + GlobalRetryPolicy.RetryPolicy.Execute(() => + { + try + { + finalresponse = kfclient.Put(finalsyncreq); + if (!finalresponse.IsSuccessful) + { + string msg = "Something went wrong while submitting metadata update for cert " + + digicertcertinstance["serial_number"] + " to Keyfactor."; + _logger.Error(msg); + throw new CustomException(msg, new Exception("Request failed.")); + } + } + catch (Exception ex) + { + _logger.Error($"Unexpected error: {ex}"); + throw; + } + + string serial = digicertcertinstance["serial_number"]?.ToString() ?? ""; + _logger.Trace("Submitted metadata update for cert {0} to Keyfactor.", serial); // OK + }); + ++certcounttracker; + } // Update the count of items downloaded so far @@ -585,4 +771,33 @@ CURRENTLY REPLACING WITH "_-_" AS STAND IN FOR SPACE CHARACTER. Environment.Exit(0); } -} + + public static class GlobalRetryPolicy + { + static GlobalRetryPolicy() + { + RetryPolicy = Policy + .Handle() // Handle all exceptions + .Retry(5, (exception, retryCount, context) => + { + // Check if the exception is a CustomException + if (exception is CustomException customEx) + // Log the custom message from CustomException + _logger.Error($"Retry {retryCount} due to: {customEx.Message}"); + else + // Log the message for other exceptions + _logger.Error($"Retry {retryCount} due to: {exception.Message}"); + }); + } + + public static Policy RetryPolicy { get; } + } + + public class CustomException : Exception + { + public CustomException(string customMessage, Exception innerException = null) + : base($"{customMessage} Original error: {innerException?.Message}", innerException) + { + } + } +} \ No newline at end of file diff --git a/digicert-metadata-sync/Models/CharDBItem.cs b/digicert-metadata-sync/Models/CharDBItem.cs index 63e12c9..64935b8 100644 --- a/digicert-metadata-sync/Models/CharDBItem.cs +++ b/digicert-metadata-sync/Models/CharDBItem.cs @@ -14,13 +14,11 @@ namespace DigicertMetadataSync; -partial class DigicertSync +internal partial class DigicertSync { - public class CharDBItem { public string character; public string replacementcharacter; } - } \ No newline at end of file diff --git a/digicert-metadata-sync/Models/DigiCertClientFields.cs b/digicert-metadata-sync/Models/DigiCertClientFields.cs new file mode 100644 index 0000000..f24184b --- /dev/null +++ b/digicert-metadata-sync/Models/DigiCertClientFields.cs @@ -0,0 +1,38 @@ +// in Models/DigicertMetadataFieldCreate.cs + +using Newtonsoft.Json; + +namespace DigicertMetadataSync.Models; + +public sealed class DigicertMetadataFieldCreate +{ + [JsonProperty("label")] public string Label { get; set; } = ""; + [JsonProperty("data_type")] public string DataType { get; set; } = "text"; // adjust as needed + [JsonProperty("is_required")] public bool IsRequired { get; set; } + [JsonProperty("is_active")] public bool IsActive { get; set; } = true; +} + +public sealed class DigicertMetadataFieldUpdate +{ + [JsonProperty("label")] public string? Label { get; set; } + [JsonProperty("data_type")] public string? DataType { get; set; } + [JsonProperty("is_required")] public bool? IsRequired { get; set; } + [JsonProperty("is_active")] public bool? IsActive { get; set; } +} + +public sealed class CustomDigicertMetadataInstance +{ + [JsonProperty("id")] public int Id { get; set; } + [JsonProperty("label")] public string Label { get; set; } = ""; + [JsonProperty("is_required")] public bool IsRequired { get; set; } + [JsonProperty("is_active")] public bool IsActive { get; set; } + + [JsonProperty("data_type")] public string DataType { get; set; } = ""; + // Add any extra properties you use (e.g., kf_field_name) with proper JsonProperty +} + +public sealed class DigicertMetadataUpdateInstance +{ + [JsonProperty("metadata_id")] public int MetadataId { get; set; } + [JsonProperty("value")] public string Value { get; set; } = ""; +} \ No newline at end of file diff --git a/digicert-metadata-sync/Models/InternalClasses.cs b/digicert-metadata-sync/Models/InternalClasses.cs index 5c0d3eb..237f27e 100644 --- a/digicert-metadata-sync/Models/InternalClasses.cs +++ b/digicert-metadata-sync/Models/InternalClasses.cs @@ -26,26 +26,50 @@ public class CustomDigicertMetadataInstance public string data_type { get; set; } } + + // Add or replace with this fuller version public class ReadInMetadataField { public string DigicertFieldName { get; set; } = "local_test_nullx0"; public string KeyfactorMetadataFieldName { get; set; } = "test_name_nullx0"; public string KeyfactorDescription { get; set; } = "None."; - public string KeyfactorDataType { get; set; } = "string"; - public string KeyfactorHint { get; set; } = "None."; + + public string KeyfactorDataType { get; set; } = + "String"; // String, Integer, Date, Boolean, Multiple Choice, Big Text + + public string KeyfactorHint { get; set; } = ""; public string KeyfactorAllowAPI { get; set; } = "True"; - public string FieldType { get; set; } = "manual/custom"; + + // NEW: keep config explicit and self-contained + public string KeyfactorValidation { get; set; } = ""; // regex (String fields only) + public string KeyfactorMessage { get; set; } = ""; // message for failed regex + public string KeyfactorEnrollment { get; set; } = "Optional"; // Optional | Required | Hidden + public List KeyfactorOptions { get; set; } = new(); // Multiple Choice values + public string KeyfactorDefaultValue { get; set; } = ""; // default value + public int? KeyfactorDisplayOrder { get; set; } = null; // if null, we’ll set later + public string FieldType { get; set; } = "manual/custom"; // existing behavior } + // using Newtonsoft.Json; // already present in your project + public class KeyfactorMetadataInstanceSendoff { public string Name { get; set; } = ""; - public string Description { get; set; } = "No description provided."; - - //Default field type is set to 1 for Keyfactor - string - public int DataType { get; set; } = 1; + public int DataType { get; set; } = 1; // 1=String, 2=Integer, 3=Date, 4=Boolean, 5=Multiple Choice, 6=Big Text public string Hint { get; set; } = ""; + + // (keep these if you use them) + public string Validation { get; set; } = ""; + public int Enrollment { get; set; } = 0; // 0 Optional, 1 Required, 2 Hidden + public string Message { get; set; } = ""; + + // IMPORTANT: Options is always a CSV string (never an array) + public string Options { get; set; } = ""; + + public string DefaultValue { get; set; } = ""; + + // Deprecated but harmless if present public bool AllowAPI { get; set; } = true; } } \ No newline at end of file diff --git a/digicert-metadata-sync/Models/KeyfactorCertInstance.cs b/digicert-metadata-sync/Models/KeyfactorCertInstance.cs index 4188851..73c24e4 100644 --- a/digicert-metadata-sync/Models/KeyfactorCertInstance.cs +++ b/digicert-metadata-sync/Models/KeyfactorCertInstance.cs @@ -66,7 +66,6 @@ public class KeyfactorCert public class Metadata { - } public class Detailedkeyusage diff --git a/digicert-metadata-sync/Models/KeyfactorMetadataInstance.cs b/digicert-metadata-sync/Models/KeyfactorMetadataInstance.cs index 043c9ca..a3501fe 100644 --- a/digicert-metadata-sync/Models/KeyfactorMetadataInstance.cs +++ b/digicert-metadata-sync/Models/KeyfactorMetadataInstance.cs @@ -1,36 +1,38 @@ -// Copyright 2021 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace DigicertMetadataSync; - -internal partial class DigicertSync -{ - //This stores all of the data keyfactor API returns when asked for metadata field details. - public class KeyfactorMetadataInstance - { - public int Id { get; set; } - public string Name { get; set; } - public string Description { get; set; } - public int DataType { get; set; } - public string Hint { get; set; } - public string Validation { get; set; } - public int Enrollment { get; set; } - public string Message { get; set; } - public string Options { get; set; } - public string DefaultValue { get; set; } - public bool AllowAPI { get; set; } - public bool ExplicitUpdate { get; set; } - public int DisplayOrder { get; set; } - } +// Copyright 2021 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace DigicertMetadataSync; + +// using Newtonsoft.Json; // already present in your project + +public class KeyfactorMetadataInstance +{ + public string Name { get; set; } = ""; + public string Description { get; set; } = "No description provided."; + public int DataType { get; set; } = 1; // 1=String, 2=Integer, 3=Date, 4=Boolean, 5=Multiple Choice, 6=Big Text + public string Hint { get; set; } = ""; + + // (keep these if you use them) + public string Validation { get; set; } = ""; + public int Enrollment { get; set; } = 0; // 0 Optional, 1 Required, 2 Hidden + public string Message { get; set; } = ""; + + // IMPORTANT: Options is always a CSV string (never an array) + public string Options { get; set; } = ""; + + public string DefaultValue { get; set; } = ""; + + // Deprecated but harmless if present + public bool AllowAPI { get; set; } = true; } \ No newline at end of file diff --git a/digicert-metadata-sync/NLog.config b/digicert-metadata-sync/NLog.config index 1ff0000..af12c75 100644 --- a/digicert-metadata-sync/NLog.config +++ b/digicert-metadata-sync/NLog.config @@ -1,4 +1,5 @@ - + + - - + + diff --git a/digicert-metadata-sync/Properties/launchSettings.json b/digicert-metadata-sync/Properties/launchSettings.json new file mode 100644 index 0000000..144a3c8 --- /dev/null +++ b/digicert-metadata-sync/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "DigicertMetadataSync": { + "commandName": "Project", + "commandLineArgs": "kftodc" + } + } +} \ No newline at end of file diff --git a/digicert-metadata-sync/digicertsync.Designer.cs b/digicert-metadata-sync/digicertsync.Designer.cs deleted file mode 100644 index 38e6af5..0000000 --- a/digicert-metadata-sync/digicertsync.Designer.cs +++ /dev/null @@ -1,38 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -// Copyright 2021 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -namespace digicert_metadata_sync { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.2.0.0")] - internal sealed partial class digicertsync : global::System.Configuration.ApplicationSettingsBase { - - private static digicertsync defaultInstance = ((digicertsync)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new digicertsync()))); - - public static digicertsync Default { - get { - return defaultInstance; - } - } - } -} diff --git a/digicert-metadata-sync/manualfields.json b/digicert-metadata-sync/manualfields.json index 099416c..7ab402f 100644 --- a/digicert-metadata-sync/manualfields.json +++ b/digicert-metadata-sync/manualfields.json @@ -6,33 +6,53 @@ "KeyfactorDescription": "Digicert Assigned Cert ID", "KeyfactorDataType": "Integer", "KeyfactorHint": "", - "KeyfactorAllowAPI": "True" + "KeyfactorAllowAPI": "True", + "KeyfactorValidation": "", + "KeyfactorMessage": "", + "KeyfactorEnrollment": "Optional", + "KeyfactorOptions": [], + "KeyfactorDefaultValue": "" }, { "DigicertFieldName": "organization_contact.email", "KeyfactorMetadataFieldName": "digicertorgemail", "KeyfactorDescription": "Digicert Requester Email", "KeyfactorDataType": "String", - "KeyfactorHint": "", - "KeyfactorAllowAPI": "True" + "KeyfactorHint": "Requester email (keyexample.com)", + "KeyfactorAllowAPI": "True", + "KeyfactorValidation": "", + "KeyfactorMessage": "Use a @keyexample.org or @keyexample.com email.", + "KeyfactorEnrollment": "Optional", + "KeyfactorOptions": [], + "KeyfactorDefaultValue": "" + }, + { + "DigicertFieldName": "additional_emails", + "KeyfactorMetadataFieldName": "additional_emails", + "KeyfactorDescription": "Email notifications", + "KeyfactorDataType": "String", + "KeyfactorHint": "Pick one or more", + "KeyfactorAllowAPI": "True", + "KeyfactorValidation": "", + "KeyfactorMessage": "", + "KeyfactorEnrollment": "Optional", + "KeyfactorOptions": "", + "KeyfactorDefaultValue": "" } ], "CustomFields": [ { - "DigicertFieldName": "test", - "KeyfactorMetadataFieldName": "", - "KeyfactorDescription": "Just an empty testfield.", + "DigicertFieldName": "Field2", + "KeyfactorMetadataFieldName": "Field2", + "KeyfactorDescription": "Test", "KeyfactorDataType": "String", - "KeyfactorHint": "", - "KeyfactorAllowAPI": "True" - }, - { - "DigicertFieldName": "IntegerField", - "KeyfactorMetadataFieldName": "", - "KeyfactorDescription": "Another test field but with an int.", - "KeyfactorDataType": "Integer", - "KeyfactorHint": "", - "KeyfactorAllowAPI": "True" + "KeyfactorHint": "Pick one or more", + "KeyfactorAllowAPI": "True", + "KeyfactorValidation": "", + "KeyfactorMessage": "", + "KeyfactorEnrollment": "Optional", + "KeyfactorOptions": "", + "KeyfactorDefaultValue": "Test" } ] } diff --git a/docsource/content.md b/docsource/content.md new file mode 100644 index 0000000..2b4b53f --- /dev/null +++ b/docsource/content.md @@ -0,0 +1,187 @@ +# digicert-metadata-sync +A tool to automatically synchronize metadata fields and their content between **DigiCert** and **Keyfactor Command**. +> **NOTE (Upgrade to 2.2.0+)** +> - The structure of `manualfields.json` has changed and now supports additional fields (e.g., `KeyfactorValidation`, `KeyfactorMessage`, `KeyfactorEnrollment`, `KeyfactorOptions`, `KeyfactorDefaultValue`, `KeyfactorDisplayOrder`). Review the updated examples and tables below. +> - `DigicertMetadataSync.dll.config` now includes a **Sync Reissue** setting (`SyncReissue`) that, when enabled, includes revoked and expired certificates in Keyfactor lookups to support reissue/historical synchronization. +> - If you are upgrading from **any version prior to 2.2.0**, you must review and update your configuration to match the new schema and settings. See the **manualfields.json** and **app.config / DigicertMetadataSync.dll.config** sections for details. +--- + +## Overview +This tool can: + +1. **Create/align metadata fields in Keyfactor** to match: + - DigiCert **Custom Metadata Fields** (aka “custom fields”), and + - Useful **non‑custom fields** from DigiCert order/certificate data (e.g., DigiCert Order ID, organization contact details). These are called **manual fields** in this project. +2. **Sync field values** from DigiCert back into the matching Keyfactor metadata fields. + +Before running, configure **`DigicertMetadataSync.dll.config`** and **`manualfields.json`** (both described below). + +--- + +## Settings + +### Command Line Arguments +One of these two arguments needs to be used for the tool to run. + +- **"kftodc"** +Syncronizes the contents of custom fields listed in manualfields.json from Keyfactor to DigiCert. If the fields in manualfields.json do not exist in Keyfactor or DigiCert, they are created first. Example: `.\DigicertMetadataSync.exe kftodc` + +- **"dctokf"** +Syncronizes the contents of both custom and non-custom fields from DigiCert to Keyfactor. The fields are listed in manualfields.json, and are created if necessary. Example: `.\DigicertMetadataSync.exe dctokf` + +### `DigicertMetadataSync.dll.config` keys +- **`DigicertAPIKey`** + Your DigiCert API Key with **API key restrictions** set to **Orders, Domains and Organizations". + +- **`DigicertAPIKeyTopPerm`** + A second DigiCert API key with **top-level permissions** (needed to read all custom fields).In DigiCert, to obtain one, you must keep the API key’s **API key restrictions** at **None** when creating it. + +- **`KeyfactorDomainAndUser`** + A Keyfactor login in the format `DOMAIN\username` with permissions to create metadata fields and update certificates. + +- **`KeyfactorPassword`** + Password for the `KeyfactorDomainAndUser` account. + +- **`KeyfactorCertSearchReturnLimit`** + Maximum number of certificates the tool will request from Keyfactor when searching (use a number that exceeds the total number of certs you are trying sync when running in prod). + +- **`KeyfactorAPIEndpoint`** + Your Keyfactor API base URL, e.g. `https://example.com/KeyfactorAPI/` + +- **`KeyfactorDigicertIssuedCertQueryTerm`** + A substring present in the **Issuer DN** of DigiCert‑issued certs in your Keyfactor instance (e.g., `"DigiCert"`). Used to scope the Keyfactor query to DigiCert certs only. + +- **`ImportAllCustomDigicertFields`** (boolean) + If `true`, import **all** custom fields from DigiCert automatically (labels are auto‑converted to valid Keyfactor names). If `false`, only the custom fields listed in the **`CustomFields`** section of `manualfields.json` are imported. + +- **`ReplaceDigicertWhiteSpaceCharacterInName`** + When `ImportAllCustomDigicertFields=true`, DigiCert labels that contain spaces will be converted to Keyfactor‑safe names using this string. Example: set to `"_"` to turn `"Requester Email"` into `"Requester_Email"`. + +- **`SyncReissue`** (boolean) + When `true`, the Keyfactor lookup includes **revoked** and **expired** certificates (`pq.includeRevoked=true&pq.includeExpired=true`). + +### Example `DigicertMetadataSync.dll.config` +File is available within the repository named as App.config (**should be renamed to DigicertMetadataSync.dll.config for actual use**). +```xml + + + + + + + + + + + + + + + + +``` +--- + +## `manualfields.json` + +This file tells the tool **which fields** to create in Keyfactor and **how to map** DigiCert values into them. It has two top‑level arrays: + +```jsonc +{ + "ManualFields": [ /* non-custom DigiCert fields you want to map */ ], + "CustomFields": [ /* DigiCert Custom Metadata Fields you want to map */ ] +} +``` + +### Common properties (apply to entries in **both** arrays) + +| Property | Type | Required | Description | +|---|---|---:|---| +| `DigicertFieldName` | string | yes | For **ManualFields**: a **dot path** into the DigiCert order/cert object (e.g., `organization_contact.email`). For **CustomFields**: the **label** of the DigiCert custom field _exactly as it appears_ in DigiCert. | +| `KeyfactorMetadataFieldName` | string | no | The Keyfactor field name to create/use. If omitted for **CustomFields**, the tool will derive a safe name from `DigicertFieldName` (label), replacing spaces with `ReplaceDigicertWhiteSpaceCharacterInName`. | +| `KeyfactorDescription` | string | no | Description to show in Keyfactor for this field. | +| `KeyfactorDataType` | string | yes | One of the **Keyfactor data types** listed below (e.g., `"String"`, `"Integer"`…). | +| `KeyfactorHint` | string | no | Hint to display in Keyfactor’s UI (e.g., sample format). | +| `KeyfactorAllowAPI` | bool/string | no | Defaults to `true`. Leave `true` to allow this tool to manage the field via API. | + +#### New/advanced properties +These map to Keyfactor’s Metadata Field schema and are optional unless your use case requires them. + +| Property | Type | Required | Notes | +|---|---|---:|---| +| `KeyfactorValidation` | string | no | Regex for **String** fields only. If set, `KeyfactorMessage` should describe the validation failure. | +| `KeyfactorMessage` | string | no | Validation error message shown if regex fails. | +| `KeyfactorEnrollment` | string | no | One of: `"Optional"` (default), `"Required"`, `"Hidden"`. | +| `KeyfactorOptions` | array\ | no | Choices for **Multiple Choice** fields. You may list them as an **array** in config; the tool serializes them to a **comma‑separated string** when posting to Keyfactor. | +| `KeyfactorDefaultValue` | string | no | Default field value. | + +### Example `manualfields.json` +File is available within the repository. +```json +{ + "ManualFields": [ + { + "DigicertFieldName": "id", + "KeyfactorMetadataFieldName": "DigicertID", + "KeyfactorDescription": "Digicert Assigned Cert ID", + "KeyfactorDataType": "Integer", + "KeyfactorHint": "", + "KeyfactorAllowAPI": "True", + "KeyfactorValidation": "", + "KeyfactorMessage": "", + "KeyfactorEnrollment": "Optional", + "KeyfactorOptions": [], + "KeyfactorDefaultValue": "" + } + ], + "CustomFields": [ + { + "DigicertFieldName": "Field2", + "KeyfactorMetadataFieldName": "Field2", + "KeyfactorDescription": "Test", + "KeyfactorDataType": "String", + "KeyfactorHint": "Pick one or more", + "KeyfactorAllowAPI": "True", + "KeyfactorValidation": "", + "KeyfactorMessage": "", + "KeyfactorEnrollment": "Optional", + "KeyfactorOptions": "", + "KeyfactorDefaultValue": "Test" + } + ] +} +``` + +> **Notes** +> • For **ManualFields**, `DigicertFieldName` must match the **flattened path** into the DigiCert order object returned by the Order Info API (e.g., `organization_contact.email`). +> • For **CustomFields**, `DigicertFieldName` must match the **label** in DigiCert (the tool will convert it to a Keyfactor‑safe name if you omit `KeyfactorMetadataFieldName`). +> • If `ImportAllCustomDigicertFields=true`, you can leave `CustomFields` empty—every custom field in DigiCert will be created/mapped automatically. + +--- + +## Keyfactor Metadata Field Data Types + +Use the **names** below in `KeyfactorDataType` (the tool maps them to the corresponding numeric codes internally when calling the Keyfactor API): + +| Name | Code | Typical uses | +|---|---:|---| +| `String` | 1 | Free text / short text | +| `Integer` | 2 | Numeric values | +| `Date` | 3 | Dates | +| `Boolean` | 4 | True/False | +| `Multiple Choice` | 5 | Fixed list of values (uses `KeyfactorOptions`) | +| `Big Text` | 6 | Long multi‑line text | + +--- + +## Sync flow (high level) +1. **Field alignment:** Creates/updates metadata fields in Keyfactor as defined in `manualfields.json` (plus, optionally, all DigiCert custom fields). +1a. If a field is detected as having invalid characters that Keyfactor does not accept, the tool exits with an appropriate message and replacechar.json is populated. +2. **Value sync:** Depending on whether you set the tool to `kftodc` or `dctokf`, the values are synced either from Keyfactor to DigiCert or from DigiCert to Keyfactor. + + +--- + +## Troubleshooting tips +- Validation only applies to **String** fields—omit `KeyfactorValidation`/`KeyfactorMessage` for other types. + diff --git a/integration-manifest.json b/integration-manifest.json index 89601cd..7dd3278 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -1,11 +1,12 @@ { - "$schema": "https://keyfactor.github.io/integration-manifest-schema.json", + "$schema": "https://keyfactor.github.io/v2/integration-manifest-schema.json", "integration_type": "api-client", - "name": "Digicert Metadata Sync", + "name": "DigiCert Metadata Sync", "status": "production", - "description": "A tool to automatically synchronize metadata fields and their content from DigiCert to Keyfactor. This utility is indented to be used in conjunction with the Digicert AnyGateway and adds to the information already synchronized by the gateway.", - "link_github": true, - "update_catalog": true, + "description": "Digicert Metadata Sync Application", + "link_github": false, + "update_catalog": false, "support_level": "kf-community", - "release_dir": "digicert-metadata-sync\\bin\\Release\\net6.0 " -} + "release_project": "digicert-metadata-sync/DigicertMetadataSync.csproj", + "release_dir": "digicert-metadata-sync/bin/Release" +} \ No newline at end of file diff --git a/readme_source.md b/readme_source.md deleted file mode 100644 index e7d257b..0000000 --- a/readme_source.md +++ /dev/null @@ -1,77 +0,0 @@ - - -## Overview -This tool primarily sets up metadata fields in Keyfactor for the custom metadata fields in DigiCert, which are named as such, but can also setup metadata fields in Keyfactor for non-custom fields available in DigiCert and unavailable in Keyfactor by default, such as the Digicert Cert ID and the Organization contact. These fields are referred to as manual fields in the context of this tool. After setting up these fields, the tool proceeds to update the contents of these fields. This tool only adds metadata to certificates that have already been imported into Keyfactor. Additionally, this tool requires a properly installed and functioning AnyGateway configured to work with Keyfactor and Digicert. The latest update allows for syncronization of custom field contents from Keyfactor to DigiCert. New fields are created in Keyfactor and DigiCert to accomodate for this. - -## Installation and Usage -The tool comes as a Windows executable. The tool performs synchronization each time its run. For the tool to run automatically, it needs to be added as a scheduled process using Windows. The advised interval for running it is once per week. The files DigicertMetadataSync.dll.config and manualfields.json need to be present in the same directory as the tool for it to run correctly. The specific location from which the tool is ran does not matter, but it needs to have access to both the Keyfactor API endpoint as well as Digicert, and appropriate permissions for access to the configuration files. -An explanation for the settings found in these files is given below. - -## Command Line Arguments -One of these two arguments needs to be used for the tool to run. -- "kftodc" -Syncronizes the contents of custom fields listed in manualfields.json from Keyfactor to DigiCert. If the fields in manualfields.json do not exist in Keyfactor or DigiCert, they are created first. Example: ```.\DigicertMetadataSync.exe kftodc``` -- "dctokf" -Syncronizes the contents of both custom and non-custom fields from DigiCert to Keyfactor. The fields are listed in manualfields.json, and are created if necessary. -Example: ```.\DigicertMetadataSync.exe dctokf``` - -## Settings -The settings currently present in these files are shown as an example and need to be configured for your specific situation. -### DigicertMetadataSync.dll.config settings -- DigicertAPIKey -Standard DigiCert API access key. -- DigicertAPIKeyTopPerm -DigiCert API access key with restrictions set to "None" - required for sync from Keyfactor to DigiCert. -- KeyfactorDomainAndUser -Same credential as used when logging into Keyfactor Command. A different set of credentials can be used provided they have adequate access permissions. -- KeyfactorPassword -Password for the account used in the KeyfactorDomainAndUser field. -- KeyfactorCertSearchReturnLimit -This specifies the number of certs the tool will expect to receive from Keyfactor Command. Can be set to an arbitrarily large number for unlimited or to a smaller number for testing. -- KeyfactorAPIEndpoint -This should include the Keyfactor API endpoint, of the format https://domain.com/keyfactorapi/ -- KeyfactorDigicertIssuedCertQueryTerm -This should include the common prefix all DigiCert certs have in your Keyfactor instance. For example, "DigiCert" -- ImportAllCustomDigicertFields -This setting enables the tool to import all of the custom metadata fields included in DigiCert and sync all of their data. - -During the first run, the tool will scan the custom fields it will be importing for characters that are not supported in Keyfactor Metadata field names. -Each unsupported character will be shown in a file named "replacechar.json" and its replacement can be selected. If the values in the file are not populated, the tool will not run a second time. -- ImportDataForDeactivatedDigiCertFields -If this is enabled, custom metadata fields that were deactivated in DigiCert will also be synced, and the data stored in these fields in certificates will be too. - -### replacechar.json settings -This file is populated during the first run of the tool if the ImportAllCustomDigicertFields setting is toggled. -The only text that needs replacing is shown as "null", and can be filled with any alphanumeric string. The "_" and "-" characters are also supported. - - -### manualfields.json settings -This file is used to specify which metadata fields should be synced up. - -The "ManualFields" section is used to specify the non custom fields to import into Keyfactor. - -The "CustomFields" section is used to specify which of the custom metadata fields in DigiCert should be imported into Keyfactor. - -- DigicertFieldName -For "ManualFields", this should specify the location and name of the field in the json returned from the DigiCert API following a certificate order query. If the field is not at the top level, the input should be delimited using a "." character: "organization_contact.email". The structure of the json the API returns can be viewed here: https://dev.digicert.com/services-api/orders/order-info/ -For "CustomFields", this should be the label of the custom metadata field as listed in DigiCert. - -- KeyfactorMetadataFieldName -This is the string that will be used as the field name in Keyfactor. -For "ManualFields", this needs to be configured. -For "CustomFields", if left blank, will use the same name as the same string as the DigicertFieldName, provided it has no spaces. - -- KeyfactorDescription -This is the string that will be setup as the field description in Keyfactor. - -- KeyfactorDataType -The datatype the field will use in Keyfactor. Currently accepted types are Int and String. - -- KeyfactorDataType -String to be input into Keyfactor as the metadata field hint. - -- KeyfactorAllowAPI -Allows API management of this metadata field in Keyfactor. Should be set to true for continuous synchronization with this tool. - -### Logging -Logging functionality can be configured via entering either "Debug" or "Trace" into the value of `` in NLog.config.