diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce58e3057..2dee241df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: lint: runs-on: windows-2022 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install .NET SDK uses: actions/setup-dotnet@v3 @@ -26,7 +26,7 @@ jobs: Roslyn_Static_Analysis: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install .NET SDK uses: actions/setup-dotnet@v3 @@ -46,7 +46,7 @@ jobs: Security_Code_Scan: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install .NET SDK uses: actions/setup-dotnet@v3 @@ -62,7 +62,7 @@ jobs: Coverage_Requirements: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install .NET SDK uses: actions/setup-dotnet@v3 @@ -79,7 +79,7 @@ jobs: if: github.ref == 'refs/heads/master' runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up dotnet tools and dependencies run: make install @@ -97,7 +97,7 @@ jobs: if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install .NET SDK uses: actions/setup-dotnet@v3 @@ -140,7 +140,7 @@ jobs: - name: Net80 framework: net8.0 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true @@ -180,7 +180,7 @@ jobs: Integration_Tests: runs-on: windows-2022 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true @@ -212,7 +212,7 @@ jobs: FSharp_Compatibility_Tests: runs-on: windows-2022 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true @@ -245,7 +245,7 @@ jobs: runs-on: windows-2022 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..811783fd6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,72 @@ +name: Release + +on: + push: + tags: + # ex. "v1.2.3", "v1.2.3-rc1" + - "v[0-9]+.[0-9]+.*" + +jobs: + publish: + name: Publish to NuGet + runs-on: windows-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install .NET SDK + uses: actions/setup-dotnet@v3 + with: + # .NET 5 is deprecated and removed from GitHub Actions, we need to manually install it + dotnet-version: | + 5.x.x + 8.x.x + + - name: Setup Nuget + uses: NuGet/setup-nuget@v1.1.1 + + - name: Restore NuGet Packages + run: make restore + + - name: Set up dotnet tools and dependencies + run: make install + + - name: Set up authenticity certificate + run: | + echo "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 + shell: bash + + - name: Set variables + id: variables + run: | + echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV" + echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV" + echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH + echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH + echo "C:\Program Files\DigiCert\DigiCert Keylocker Tools" >> $GITHUB_PATH + shell: bash + + - name: Setup Keylocker KSP on Windows + run: | + curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o Keylockertools-windows-x64.msi + msiexec /i Keylockertools-windows-x64.msi /quiet /qn + smksp_registrar.exe list + smctl.exe keypair ls + C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user + shell: cmd + + - name: Sync Certificates + run: | + smctl windows certsync + shell: cmd + + - name: Build and Sign NuGet package + # TODO: Need to keep signing_cert.snk in the repo + run: | + call scripts\win\build_release_nuget.bat EasyPost EasyPostNETStrongNameSigning.snk "${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" Release + shell: cmd + + - name: Publish to NuGet + run: make publish key=${{ secrets.NUGET_API_KEY }} diff --git a/EasyPostNETStrongNameSigning.snk b/EasyPostNETStrongNameSigning.snk new file mode 100644 index 000000000..648cfd6ca Binary files /dev/null and b/EasyPostNETStrongNameSigning.snk differ diff --git a/Makefile b/Makefile index 8494a0c20..cba340269 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,6 @@ build-prod: ## clean - Clean the project clean: dotnet clean - rm -rf *.nupkg ## coverage - Generate coverage reports (unit tests, not integration) for the project coverage: @@ -44,10 +43,6 @@ install-tools: dotnet tool install --local dotnet-format || exit 0 dotnet tool install --local docfx --version 2.60.2 || exit 0 -## install-release-tools - Install required tools for release -install-release-tools: - bash scripts/unix/install_osslsigncode.sh - ## install-styleguide - Import style guide (Unix only) install-styleguide: | update-examples-submodule sh examples/symlink_directory_files.sh examples/style_guides/csharp . @@ -70,13 +65,13 @@ lint-fix: lint-scripts: scripts\win\lint_scripts.bat -## prep-release - Build, sign and package the project for distribution, signing with the provided certificate +## publish - Publish the project to NuGet # @parameters: -# sncert= - The strong-name certificate to use for signing the built assets. -# cert= - The authenticity certificate to use for signing the built assets. -# pass= - The password for the authenticity certificate. -prep-release: - bash scripts/unix/build_release_nuget.sh EasyPost ${sncert} ${cert} ${pass} Release +# key= - The NuGet API key to use for publishing. +# ref: https://learn.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-push +publish: + # Verify that no extraneous .nupkg files exist + dotnet nuget push *.nupkg -Source https://api.nuget.org/v3/index.json -k ${key} -SkipDuplicate ## release - Cuts a release for the project on GitHub (requires GitHub CLI) # tag = The associated tag title of the release @@ -135,4 +130,4 @@ fs-compat-test: vb-compat-test: dotnet test EasyPost.Compatibility.VB/EasyPost.Compatibility.VB.vbproj -f ${fw} -restore -.PHONY: help analyze build build-fw build-prod clean coverage coverage-check docs format install-styleguide install-tools install-release-tools install lint lint-scripts prep-release release restore scan setup-win setup-unix test update-examples-submodule unit-test integration-test fs-compat-test vb-compat-test +.PHONY: help analyze build build-fw build-prod clean coverage coverage-check docs format install-styleguide install-tools install lint lint-scripts release restore scan setup-win setup-unix test update-examples-submodule unit-test integration-test fs-compat-test vb-compat-test diff --git a/scripts/unix/build_project.sh b/scripts/unix/build_project.sh deleted file mode 100755 index cb63f2610..000000000 --- a/scripts/unix/build_project.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# This script will restore and build a .NET project in a specified mode and platform. - -# Requirements: -# - dotnet is installed on the machine and is accessible everywhere (added to PATH) - -# Parse command line arguments -BUILD_MODE=$1 - -# Restore dependencies and build solution -echo "Restoring and building project..." -dotnet msbuild -property:Configuration="$BUILD_MODE" -target:Rebuild -restore || exit 1 diff --git a/scripts/unix/build_release_nuget.sh b/scripts/unix/build_release_nuget.sh deleted file mode 100755 index dfee0e74c..000000000 --- a/scripts/unix/build_release_nuget.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# This script will build a .NET project in Release mode, sign the generated DLLs with a provided PFX certificate, -# package the DLLs into a NuGet package, and sign the NuGet package with the provided PFX certificate. -# This script also handles pre-run cleanup (will delete old DLLs and NuGet package files) - -# Requirements: -# - NuGet is installed on the machine and is accessible everywhere (added to PATH) -# - dotnet is installed on the machine and is accessible everywhere (added to PATH) -# - osslsigncode (https://github.com/mtrojnar/osslsigncode) is installed on the machine and is accessible everywhere (added to PATH) - -# Parse command line arguments -PROJECT_NAME=$1 -STRONG_NAME_CERT_FILE=$2 -AUTH_CERT_FILE=$3 -AUTH_CERT_PASSWORD=$4 -BUILD_MODE=$5 - -# Delete old files -bash scripts/unix/delete_old_assemblies.sh - -# Restore dependencies and build solution -bash scripts/unix/build_project.sh "$BUILD_MODE" || exit 1 - -# Strong-name sign the DLLs -bash scripts/unix/strong_name_dlls.sh "$STRONG_NAME_CERT_FILE" || exit 1 - -# Sign the DLLs for authenticity -bash scripts/unix/sign_dlls.sh "$AUTH_CERT_FILE" "$AUTH_CERT_PASSWORD" || exit 1 - -# Package the DLLs into a NuGet package (will fail if DLLs are missing) -bash scripts/unix/pack_nuget.sh "$PROJECT_NAME" || exit 1 - -# Sign the NuGet package for authenticity -bash scripts/unix/sign_nuget.sh "$AUTH_CERT_FILE" "$AUTH_CERT_PASSWORD" || exit 1 - -# Preset final information -NUGET_PACKAGE_FILE=$(find . -name "*.nupkg") -echo "NuGet file $NUGET_PACKAGE_FILE is ready." diff --git a/scripts/unix/delete_old_assemblies.sh b/scripts/unix/delete_old_assemblies.sh deleted file mode 100755 index de420d3df..000000000 --- a/scripts/unix/delete_old_assemblies.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# This script will delete any DLLs and NuGet packages - -# Delete old DLLs -echo "Cleaning old files..." -rm -rf lib -rm -rf "*.nupkg" diff --git a/scripts/unix/install_osslsigncode.sh b/scripts/unix/install_osslsigncode.sh deleted file mode 100644 index f5c6eb389..000000000 --- a/scripts/unix/install_osslsigncode.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# This script will install the osslsigncode dependency (https://github.com/mtrojnar/osslsigncode) required to release this project - -PATH_STORAGE=/usr/local/bin - -# Create a temporary folder -TEMP_FOLDER="temp" -mkdir -p "$TEMP_FOLDER" -cd "$TEMP_FOLDER" || exit - -# Download the latest macOS release -# Courtesy: https://gist.github.com/steinwaywhw/a4cd19cda655b8249d908261a62687f8 -REPO="mtrojnar/osslsigncode" -curl -s https://api.github.com/repos/$REPO/releases/latest \ -| grep "browser_download_url.*macOS.zip" \ -| cut -d : -f 2,3 \ -| tr -d \" \ -| wget -qi - - -# Find the file name of the downloaded file -ZIP_FILE=$(find . -name "*macOS.zip") - -# Unzip the file -unzip "$ZIP_FILE" - -# Find the executable -OSSLSIGNCODE_EXE=$(find . -name "osslsigncode") - -# Make the executable executable -chmod +x "$OSSLSIGNCODE_EXE" - -# Move the executable to the PATH_STORAGE folder -mv "$OSSLSIGNCODE_EXE" "$PATH_STORAGE" - -# Clean up -cd .. -rm -rf "$TEMP_FOLDER" diff --git a/scripts/unix/pack_nuget.sh b/scripts/unix/pack_nuget.sh deleted file mode 100755 index 186efb33b..000000000 --- a/scripts/unix/pack_nuget.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# This script will generate a NuGet package according the the project's .nuspec file. - -# Requirements: -# - NuGet is installed on the machine and is accessible everywhere (added to PATH) - -# Parse command line arguments -PROJECT_NAME=$1 - -# Generate the NuGet package (will fail if assemblies are missing) -echo "Generating NuGet package..." -nuget pack "$PROJECT_NAME.nuspec" || exit 1 diff --git a/scripts/unix/sign_dlls.sh b/scripts/unix/sign_dlls.sh deleted file mode 100755 index c4e2f293c..000000000 --- a/scripts/unix/sign_dlls.sh +++ /dev/null @@ -1,23 +0,0 @@ -# Parse command line arguments -CERT_FILE=$1 -CERT_PASSWORD=$2 - -# Set variables -AUTHOR_NAME="EasyPost" -AUTHOR_URL="http://www.easypost.com" -TIMESTAMP_SERVER="http://timestamp.digicert.com?alg=sha256" -FOLDER="lib" -SUFFIX=".signed" -FILE_PATTERN="*.dll" - -# Sign all DLLs found with our certificate to guarantee authenticity -echo "Signing DLLs with $CERT_FILE for authenticity..." -for file in $(find "$FOLDER" -name "$FILE_PATTERN"); do - echo "Signing $file..." - # Sign the file to a new file with added suffix - osslsigncode sign -pkcs12 "$CERT_FILE" -pass "$CERT_PASSWORD" -n "$AUTHOR_NAME" -i "$AUTHOR_URL" -ts "$TIMESTAMP_SERVER" -in "$file" -out "$file$SUFFIX" - # Delete original file - rm -f "$file" - # Rename signed file to original name - mv "$file$SUFFIX" "$file" -done diff --git a/scripts/unix/sign_nuget.sh b/scripts/unix/sign_nuget.sh deleted file mode 100755 index 87fcd16bf..000000000 --- a/scripts/unix/sign_nuget.sh +++ /dev/null @@ -1,22 +0,0 @@ -# This script will find and sign any NuGet packages with a provided PFX certificate for authenticity - -# Requirements: -# - NuGet is installed on the machine and is accessible everywhere (added to PATH) - -# Parse command line arguments -CERT_FILE=$1 -CERT_PASSWORD=$2 - -# Set variables -TIMESTAMP_SERVER="http://timestamp.digicert.com?alg=sha256" -FOLDER="." -FILE_PATTERN="*.nupkg" - -# Sign all NuGet packages found with our certificate to guarantee authenticity -echo "Signing NuGet packages with $CERT_FILE for authenticity..." -# Should only be one .nupkg file at this point, since we deleted the old ones -for file in $(find "$FOLDER" -name "$FILE_PATTERN"); do - # Sign the file in-place - echo "Signing $file..." - dotnet nuget sign "$file" --timestamper "$TIMESTAMP_SERVER" --certificate-path "$CERT_FILE" --certificate-password "$CERT_PASSWORD" -done diff --git a/scripts/unix/strong_name_dlls.sh b/scripts/unix/strong_name_dlls.sh deleted file mode 100755 index b6920de29..000000000 --- a/scripts/unix/strong_name_dlls.sh +++ /dev/null @@ -1,20 +0,0 @@ -# This script will find and finish strong-naming any DLLs with a provided PFX certificate - -# Requirements: -# - dotnet is installed on the machine and is accessible everywhere (added to PATH) -# - sn is installed on the machine and is accessible everywhere (added to PATH) - -# Parse command line arguments -CERT_FILE=$1 - -# Set variables -FOLDER="lib" -FILE_PATTERN="*.dll" - -# Strong-name all DLLs found in the lib folder -echo "Strong-naming (finishing delayed signing) DLLs with $CERT_FILE..." -for file in $(find "$FOLDER" -name "$FILE_PATTERN"); do - echo "Strong-naming $file..." - # Strong-name the file to a new file with added suffix - sn -R "$file" "$CERT_FILE" -done diff --git a/scripts/win/build_release_nuget.bat b/scripts/win/build_release_nuget.bat index 2ee1f2ecf..8965c2a94 100644 --- a/scripts/win/build_release_nuget.bat +++ b/scripts/win/build_release_nuget.bat @@ -11,9 +11,8 @@ :: Parse command line arguments SET projectName=%1 SET strongNameCertFile=%2 -SET authCertFile=%3 -SET authCertPass=%4 -SET buildMode=%5 +SET authCertFingerprint=%3 +SET buildMode=%4 :: Delete old files CALL "scripts\win\delete_old_assemblies.bat" @@ -25,13 +24,13 @@ CALL "scripts\win\build_project.bat" %buildMode% || GOTO :commandFailed CALL "scripts\win\strong_name_dlls.bat" %strongNameCertFile% || GOTO :commandFailed :: Sign the DLLs for authenticity -CALL "scripts\win\sign_dlls.bat" %authCertFile% %authCertPass% || GOTO :commandFailed +CALL "scripts\win\sign_dlls.bat" %authCertFingerprint% || GOTO :commandFailed :: Package the DLLs in a NuGet package (will fail if DLLs are missing) CALL "scripts\win\pack_nuget.bat" %projectName% || GOTO :commandFailed :: Sign the NuGet package for authenticity -CALL "scripts\win\sign_nuget.bat" %authCertFile% %authCertPass% || GOTO :commandFailed +CALL "scripts\win\sign_nuget.bat" %authCertFingerprint% || GOTO :commandFailed SET nugetFileName= FOR /R %%F IN (*.nupkg) DO ( SET nugetFileName="%%F" @@ -50,7 +49,7 @@ GOTO :eof :usage @ECHO: -@ECHO Usage: %0 +@ECHO Usage: %0 GOTO :exitWithError :commandFailed diff --git a/scripts/win/sign_dlls.bat b/scripts/win/sign_dlls.bat index faaed8971..ce8e67051 100644 --- a/scripts/win/sign_dlls.bat +++ b/scripts/win/sign_dlls.bat @@ -7,14 +7,14 @@ @ECHO OFF :: Parse command line arguments -SET certFile=%1 -SET certPass=%2 +SET certFingerprint=%1 :: Sign all DLLs found in the lib folder with our certificate to guarantee authenticity @ECHO: -@ECHO Signing DLLs with %certFile% for authenticity... +@ECHO Signing DLLs for authenticity... FOR /R "lib" %%F IN (*.dll) DO ( - signtool sign /f %certFile% /p %certPass% /v /tr http://timestamp.digicert.com?alg=sha256 /td SHA256 /fd SHA256 "%%F" || GOTO :commandFailed + signtool sign /sha1 "%certFingerprint%" /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "%%F" || GOTO :commandFailed + signtool verify /v /pa "%%F" || GOTO :commandFailed ) EXIT /B 0 diff --git a/scripts/win/sign_nuget.bat b/scripts/win/sign_nuget.bat index c7190c3bf..a4b5027a2 100644 --- a/scripts/win/sign_nuget.bat +++ b/scripts/win/sign_nuget.bat @@ -6,15 +6,15 @@ @ECHO OFF :: Parse command line arguments -SET certFile=%1 -SET certPass=%2 +SET certFingerprint=%1 :: Sign all NuGet packages found with our certificate to guarantee authenticity @ECHO: -@ECHO Signing NuGet package with %certFile% for authenticity... +@ECHO Signing NuGet package for authenticity... :: Should only be one .nupkg file at this point, since we deleted the old ones FOR /R %%F IN (*.nupkg) DO ( - nuget sign "%%F" -Timestamper http://timestamp.digicert.com -CertificatePath "%certFile%" -CertificatePassword "%certPass%" || GOTO :commandFailed + nuget sign "%%F" -Timestamper http://timestamp.digicert.com -CertificateFingerprint "%certFingerprint%" -HashAlgorithm SHA256 -Verbosity detailed -Overwrite || GOTO :commandFailed + nuget verify -All "%%F" || GOTO :commandFailed ) EXIT /B 0