├── .github └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ └── coverity.yml ├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── COPYING.txt ├── Config.h.in ├── Dockerfile ├── INSTALL.W32.md ├── LICENSE.txt ├── NEWS.md ├── README.md ├── TODO.md ├── applink.c ├── appx.c ├── cab.c ├── cat.c ├── cmake ├── CMakeDist.cmake ├── CMakeTest.cmake ├── FindHeaders.cmake ├── SetBashCompletion.cmake └── SetCompilerFlags.cmake ├── code_signing_ca.pem ├── get_code_signing_ca.py ├── helpers.c ├── helpers.h ├── misc ├── pagehash.py ├── softhsm-example-token │ ├── .gitignore │ ├── Makefile │ ├── README.md │ └── gen-token.sh └── valgrind.supp ├── msi.c ├── osslsigncode.bash ├── osslsigncode.c ├── osslsigncode.h ├── pe.c ├── script.c ├── tests ├── .gitignore ├── certs │ └── ca-bundle.crt ├── check_cryptography.py ├── client_http.py ├── conf │ ├── .gitignore │ └── openssl_tsa.cnf ├── exec.py ├── files │ ├── unsigned.256appx │ ├── unsigned.512appx │ ├── unsigned.cat │ ├── unsigned.ex_ │ ├── unsigned.exe │ ├── unsigned.js │ ├── unsigned.mof │ ├── unsigned.msi │ ├── unsigned.ps1 │ └── unsigned.psc1 ├── make_certificates.py ├── server_http.py ├── server_http.pyw ├── sources │ ├── CatalogDefinitionFileName.cdf │ ├── a │ ├── b │ ├── c │ ├── myapp.c │ └── sample.wxs └── start_server.py ├── utf.c ├── utf.h └── vcpkg.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 9 | BUILD_TYPE: Release 10 | version: osslsigncode-2.10-dev 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - id: ubuntu-24.04 19 | triplet: x64-linux 20 | compiler: gcc 21 | os: ubuntu-24.04 22 | generator: Unix Makefiles 23 | vcpkg_root: 24 | - id: ubuntu-22.04 25 | triplet: x64-linux 26 | compiler: gcc 27 | os: ubuntu-22.04 28 | generator: Unix Makefiles 29 | vcpkg_root: 30 | - id: macOS 31 | triplet: arm64-osx 32 | compiler: clang 33 | os: macOS-latest 34 | generator: Unix Makefiles 35 | vcpkg_root: /usr/local/share/vcpkg 36 | cache: /Users/runner/.cache/vcpkg/archives 37 | - id: windows-x64-vs 38 | triplet: x64-windows 39 | compiler: vs 40 | arch: x64 41 | os: windows-latest 42 | generator: Ninja 43 | vcpkg_root: C:/vcpkg 44 | cache: C:/Users/runneradmin/AppData/Local/vcpkg/archives 45 | - id: windows-x86-vs 46 | triplet: x86-windows 47 | compiler: vs 48 | arch: x86 49 | os: windows-latest 50 | generator: Ninja 51 | vcpkg_root: C:/vcpkg 52 | cache: C:/Users/runneradmin/AppData/Local/vcpkg/archives 53 | - id: windows-x64-static-vs 54 | triplet: x64-windows-static 55 | compiler: vs 56 | arch: x64 57 | os: windows-latest 58 | generator: Ninja 59 | vcpkg_root: C:/vcpkg 60 | cache: C:/Users/runneradmin/AppData/Local/vcpkg/archives 61 | - id: windows-x64-mingw 62 | triplet: x64-windows 63 | compiler: mingw 64 | os: windows-latest 65 | generator: Ninja 66 | vcpkg_root: C:/vcpkg 67 | cache: C:/Users/runneradmin/AppData/Local/vcpkg/archives 68 | 69 | runs-on: ${{matrix.os}} 70 | 71 | env: 72 | VCPKG_ROOT: ${{matrix.vcpkg_root}} 73 | 74 | steps: 75 | - uses: actions/checkout@v4 76 | 77 | - name: Cache the vcpkg archives 78 | if: matrix.cache != '' 79 | uses: actions/cache@v4 80 | with: 81 | path: ${{matrix.cache}} 82 | key: ${{matrix.id}}-${{hashFiles('vcpkg.json')}} 83 | restore-keys: | 84 | ${{matrix.id}}-${{hashFiles('vcpkg.json')}} 85 | ${{matrix.id}}- 86 | 87 | - name: Configure Visual Studio 88 | if: matrix.compiler == 'vs' 89 | uses: ilammy/msvc-dev-cmd@v1 90 | with: 91 | arch: ${{matrix.arch}} 92 | 93 | - name: Install MSYS2 94 | if: matrix.compiler == 'mingw' 95 | uses: msys2/setup-msys2@v2 96 | with: 97 | update: true 98 | install: mingw-w64-x86_64-ninja 99 | 100 | - name: Put MSYS2_MinGW64 on PATH 101 | if: matrix.compiler == 'mingw' 102 | run: echo "D:/a/_temp/msys64/mingw64/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 103 | 104 | - name: Set up Python (macOS) 105 | if: runner.os == 'macOS' 106 | uses: actions/setup-python@v4 107 | with: 108 | python-version: '3.13' 109 | update-environment: false 110 | architecture: 'arm64' 111 | 112 | - name: Set up Python virtual environment (Linux/macOS) 113 | if: runner.os != 'Windows' 114 | run: | 115 | python -m venv --system-site-packages --copies venv 116 | 117 | - name: Set up Python virtual environment (Windows) 118 | if: runner.os == 'Windows' 119 | run: | 120 | python.exe -m venv --system-site-packages --copies venv 121 | 122 | - name: Install Xcode (macOS) 123 | if: runner.os == 'macOS' 124 | uses: maxim-lobanov/setup-xcode@v1 125 | with: 126 | xcode-version: latest-stable 127 | 128 | - name: Setup the oldest supported version of cmake (macOS) 129 | if: runner.os == 'macOS' 130 | uses: jwlawson/actions-setup-cmake@v2.0 131 | 132 | - name: Install python3 cryptography module (Linux) 133 | if: runner.os == 'Linux' 134 | run: | 135 | source venv/bin/activate 136 | python -m pip install --upgrade pip 137 | python -m pip install --upgrade cryptography 138 | python -c "import sys; print(sys.executable)" 139 | python --version 140 | python -c "import cryptography; print(f'Python3 cryptography version {cryptography.__version__}')" 141 | 142 | - name: Install python3 cryptography module (macOS) 143 | if: runner.os == 'macOS' 144 | run: | 145 | source venv/bin/activate 146 | python -m pip install --upgrade pip 147 | ARCHFLAGS="-arch arm64" python -m pip install --upgrade cryptography 148 | python -c "import sys; print(sys.executable)" 149 | python --version 150 | python -c "import cryptography; print(f'Python3 cryptography version {cryptography.__version__}')" 151 | 152 | - name: Install python3 cryptography module (Windows) 153 | if: runner.os == 'Windows' 154 | run: | 155 | .\venv\Scripts\Activate.ps1 156 | python.exe -m ensurepip 157 | python.exe -m pip install --upgrade pip 158 | python.exe -m pip install cryptography 159 | python.exe -c "import sys; print(sys.executable)" 160 | python.exe --version 161 | python.exe -c "import cryptography; print(f'Python3 cryptography version {cryptography.__version__}')" 162 | 163 | - name: Configure CMake (Linux/macOS) 164 | if: runner.os != 'Windows' 165 | run: | 166 | source venv/bin/activate 167 | cmake \ 168 | -G "${{matrix.generator}}" \ 169 | -S "${{github.workspace}}" \ 170 | -B "${{github.workspace}}/build" \ 171 | -DCMAKE_OSX_ARCHITECTURES=arm64 \ 172 | -DCMAKE_BUILD_TYPE="${{env.BUILD_TYPE}}" \ 173 | -DCMAKE_INSTALL_PREFIX="${{github.workspace}}/dist" 174 | 175 | - name: Configure CMake (Windows) 176 | if: runner.os == 'Windows' 177 | run: | 178 | .\venv\Scripts\Activate.ps1 179 | cmake ` 180 | -G "${{matrix.generator}}" ` 181 | -S "${{github.workspace}}" ` 182 | -B "${{github.workspace}}/build" ` 183 | -DCMAKE_BUILD_TYPE="${{env.BUILD_TYPE}}" ` 184 | -DCMAKE_INSTALL_PREFIX="${{github.workspace}}/dist" 185 | 186 | - name: Build 187 | run: cmake 188 | --build ${{github.workspace}}/build 189 | --config ${{env.BUILD_TYPE}} 190 | 191 | - name: List files (Linux/macOS) 192 | if: runner.os != 'Windows' 193 | run: find .. -ls 194 | 195 | - name: List files (Windows) 196 | if: runner.os == 'Windows' 197 | run: Get-ChildItem -Recurse -Name .. 198 | 199 | - name: Test (Linux/macOS) 200 | if: runner.os != 'Windows' 201 | working-directory: ${{github.workspace}}/build 202 | run: | 203 | source ../venv/bin/activate 204 | ctest -C ${{env.BUILD_TYPE}} 205 | 206 | - name: Test (Windows) 207 | if: runner.os == 'Windows' 208 | working-directory: ${{github.workspace}}/build 209 | run: | 210 | ..\venv\Scripts\Activate.ps1 211 | ctest -C ${{env.BUILD_TYPE}} 212 | 213 | - name: Upload the errors 214 | uses: actions/upload-artifact@v4 215 | if: failure() 216 | with: 217 | name: errors-${{matrix.id}} 218 | path: | 219 | ${{github.workspace}}/build/Testing/Temporary/LastTest.log 220 | ${{github.workspace}}/build/Testing/conf/makecerts.log 221 | ${{github.workspace}}/build/Testing/logs/server.log 222 | ${{github.workspace}}/build/Testing/logs/port.log 223 | 224 | - name: Install 225 | run: cmake --install ${{github.workspace}}/build 226 | 227 | - name: Upload the executables 228 | uses: actions/upload-artifact@v4 229 | with: 230 | name: ${{env.version}}-${{matrix.id}} 231 | path: ${{github.workspace}}/dist 232 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ "master" ] 9 | schedule: 10 | - cron: '45 1 * * 2' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'cpp' ] 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v3 33 | with: 34 | languages: ${{ matrix.language }} 35 | 36 | # If you wish to specify custom queries, you can do so here or in a config file. 37 | # By default, queries listed here will override any specified in a config file. 38 | # Prefix the list here with "+" to use these queries and those in the config file. 39 | 40 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 41 | # queries: security-extended,security-and-quality 42 | 43 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 44 | # If this step fails, then you should remove it and run the build manually (see below) 45 | - name: Autobuild 46 | uses: github/codeql-action/autobuild@v3 47 | 48 | # ℹ️ Command-line programs to run using the OS shell. 49 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 50 | 51 | # If the Autobuild fails above, remove it and uncomment the following three lines. 52 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 53 | 54 | # - run: | 55 | # echo "Run, Build Application using script" 56 | # ./location_of_script_within_repo/buildscript.sh 57 | 58 | - name: Perform CodeQL Analysis 59 | uses: github/codeql-action/analyze@v3 60 | -------------------------------------------------------------------------------- /.github/workflows/coverity.yml: -------------------------------------------------------------------------------- 1 | name: Coverity Scan 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | coverity: 9 | runs-on: ubuntu-latest 10 | env: 11 | token: ${{secrets.COVERITY_SCAN_TOKEN}} 12 | steps: 13 | - uses: actions/checkout@v4 14 | if: env.token 15 | - name: Get ready for scanning 16 | if: env.token 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install -y libssl-dev libcurl4-openssl-dev 20 | cmake -S ${{github.workspace}} -B ${{github.workspace}}/build 21 | - uses: vapier/coverity-scan-action@v1 22 | if: env.token 23 | with: 24 | email: ${{secrets.COVERITY_SCAN_EMAIL}} 25 | token: ${{secrets.COVERITY_SCAN_TOKEN}} 26 | command: make -C ${{github.workspace}}/build 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | CMakeFiles/ 3 | _CPack_Packages/ 4 | Testing/ 5 | .vs/ 6 | 7 | CMakeCache.txt 8 | cmake_install.cmake 9 | config.h 10 | CPackConfig.cmake 11 | CPackSourceConfig.cmake 12 | CTestTestfile.cmake 13 | install_manifest.txt 14 | Makefile 15 | osslsigncode 16 | osslsigncode.exe 17 | stamp-h1 18 | 19 | .#*# 20 | .*.bak 21 | .*.orig 22 | .*.rej 23 | .*~ 24 | #*# 25 | *.asc 26 | *.bak 27 | *.bz2 28 | *.d 29 | *.def 30 | *.dll 31 | *.gz 32 | *.la 33 | *.lib 34 | *.lo 35 | *.orig 36 | *.pc 37 | *.pdb 38 | *.rej 39 | *.u 40 | *.rc 41 | *~ 42 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # required cmake version 2 | cmake_minimum_required(VERSION 3.17) 3 | 4 | # autodetect vcpkg CMAKE_TOOLCHAIN_FILE if VCPKG_ROOT is defined 5 | # this needs to be configured before the project() directive 6 | if((CMAKE_GENERATOR MATCHES "Ninja") AND DEFINED ENV{VCPKG_ROOT} AND NOT $ENV{VCPKG_ROOT} STREQUAL "" AND NOT DEFINED CMAKE_TOOLCHAIN_FILE) 7 | set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") 8 | endif((CMAKE_GENERATOR MATCHES "Ninja") AND DEFINED ENV{VCPKG_ROOT} AND NOT $ENV{VCPKG_ROOT} STREQUAL "" AND NOT DEFINED CMAKE_TOOLCHAIN_FILE) 9 | set(BUILTIN_SOCKET ON CACHE BOOL "") # for static Python 10 | 11 | # configure basic project information 12 | project(osslsigncode 13 | VERSION 2.10 14 | DESCRIPTION "OpenSSL based Authenticode signing for PE, CAB, CAT and MSI files" 15 | HOMEPAGE_URL "https://github.com/mtrojnar/osslsigncode" 16 | LANGUAGES C) 17 | 18 | # force nonstandard version format for development packages 19 | set(DEV "-dev") 20 | set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}${DEV}") 21 | 22 | # version and contact information 23 | set(PACKAGE_STRING "${PROJECT_NAME} ${PROJECT_VERSION}") 24 | set(PACKAGE_BUGREPORT "Michal.Trojnara@stunnel.org") 25 | 26 | # specify the C standard 27 | set(CMAKE_C_STANDARD 11) 28 | set(CMAKE_C_STANDARD_REQUIRED ON) 29 | 30 | if(WIN32) 31 | add_definitions(-DUSE_WIN32) 32 | endif() 33 | 34 | # load CMake library modules 35 | include(FindOpenSSL) 36 | if(OPENSSL_VERSION VERSION_LESS "1.1.1") 37 | message(FATAL_ERROR "OpenSSL version must be at least 1.1.1") 38 | endif() 39 | if(OPENSSL_VERSION VERSION_LESS "3.0.0") 40 | include(FindCURL) 41 | endif(OPENSSL_VERSION VERSION_LESS "3.0.0") 42 | include(FindZLIB) 43 | 44 | # load CMake project modules 45 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") 46 | include(SetBashCompletion) 47 | include(FindHeaders) 48 | 49 | # define the target 50 | add_executable(osslsigncode) 51 | 52 | # add compiler/linker flags 53 | include(SetCompilerFlags) 54 | 55 | # create and use config.h 56 | configure_file(Config.h.in config.h) 57 | target_compile_definitions(osslsigncode PRIVATE HAVE_CONFIG_H=1) 58 | 59 | # set sources 60 | target_sources(osslsigncode PRIVATE osslsigncode.c helpers.c utf.c msi.c pe.c cab.c cat.c appx.c script.c) 61 | if(NOT UNIX) 62 | target_sources(osslsigncode PRIVATE applink.c) 63 | endif(NOT UNIX) 64 | 65 | # set include directories 66 | target_include_directories(osslsigncode PRIVATE "${PROJECT_BINARY_DIR}") 67 | 68 | # set OpenSSL includes/libraries 69 | if(NOT OPENSSL_FOUND) 70 | message(FATAL_ERROR "OpenSSL library not found") 71 | endif(NOT OPENSSL_FOUND) 72 | target_include_directories(osslsigncode PRIVATE ${OPENSSL_INCLUDE_DIR}) 73 | target_link_libraries(osslsigncode PRIVATE ${OPENSSL_LIBRARIES}) 74 | 75 | # set cURL includes/libraries 76 | if(OPENSSL_VERSION VERSION_LESS "3.0.0") 77 | if(CURL_FOUND) 78 | target_compile_definitions(osslsigncode PRIVATE ENABLE_CURL=1) 79 | target_include_directories(osslsigncode PRIVATE ${CURL_INCLUDE_DIRS}) 80 | target_link_libraries(osslsigncode PRIVATE ${CURL_LIBRARIES}) 81 | message(STATUS "cURL support enabled") 82 | else(CURL_FOUND) 83 | message(STATUS "cURL support disabled (library not found)") 84 | endif(CURL_FOUND) 85 | endif(OPENSSL_VERSION VERSION_LESS "3.0.0") 86 | 87 | if(NOT ZLIB_FOUND) 88 | message(FATAL_ERROR "Zlib library not found") 89 | endif(NOT ZLIB_FOUND) 90 | target_include_directories(osslsigncode PRIVATE ${ZLIB_INCLUDE_DIR}) 91 | target_link_libraries(osslsigncode PRIVATE ${ZLIB_LIBRARIES}) 92 | 93 | if(NOT UNIX) 94 | # https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-shutdown 95 | target_link_libraries(osslsigncode PRIVATE ws2_32.lib crypt32.lib) 96 | endif(NOT UNIX) 97 | 98 | # add paths to linker search and installed rpath 99 | set_target_properties(osslsigncode PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE) 100 | 101 | # testing with CTest 102 | include(CMakeTest) 103 | 104 | # installation rules for a project 105 | set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin") 106 | install(TARGETS osslsigncode RUNTIME DESTINATION ${BINDIR}) 107 | if(UNIX) 108 | include(CMakeDist) 109 | else(UNIX) 110 | install( 111 | DIRECTORY ${PROJECT_BINARY_DIR}/ DESTINATION ${BINDIR} 112 | FILES_MATCHING 113 | PATTERN "*.dll" 114 | PATTERN "vcpkg_installed" EXCLUDE 115 | PATTERN "CMakeFiles" EXCLUDE 116 | PATTERN "Testing" EXCLUDE) 117 | endif(UNIX) 118 | 119 | #[[ 120 | Local Variables: 121 | c-basic-offset: 4 122 | tab-width: 4 123 | indent-tabs-mode: nil 124 | End: 125 | vim: set ts=4 expandtab: 126 | ]] 127 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x86-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "buildRoot": "${projectDir}\\out\\build\\${name}", 8 | "installRoot": "${projectDir}\\out\\install\\${name}", 9 | "cmakeCommandArgs": "", 10 | "buildCommandArgs": "", 11 | "ctestCommandArgs": "", 12 | "inheritEnvironments": [ "msvc_x86" ] 13 | }, 14 | { 15 | "name": "x86-Release", 16 | "generator": "Ninja", 17 | "configurationType": "RelWithDebInfo", 18 | "buildRoot": "${projectDir}\\out\\build\\${name}", 19 | "installRoot": "${projectDir}\\out\\install\\${name}", 20 | "cmakeCommandArgs": "", 21 | "buildCommandArgs": "", 22 | "ctestCommandArgs": "", 23 | "inheritEnvironments": [ "msvc_x86" ] 24 | }, 25 | { 26 | "name": "x64-Debug", 27 | "generator": "Ninja", 28 | "configurationType": "Debug", 29 | "buildRoot": "${projectDir}\\out\\build\\${name}", 30 | "installRoot": "${projectDir}\\out\\install\\${name}", 31 | "cmakeCommandArgs": "", 32 | "buildCommandArgs": "", 33 | "ctestCommandArgs": "", 34 | "inheritEnvironments": [ "msvc_x64_x64" ], 35 | "variables": [] 36 | }, 37 | { 38 | "name": "x64-Release", 39 | "generator": "Ninja", 40 | "configurationType": "RelWithDebInfo", 41 | "buildRoot": "${projectDir}\\out\\build\\${name}", 42 | "installRoot": "${projectDir}\\out\\install\\${name}", 43 | "cmakeCommandArgs": "", 44 | "buildCommandArgs": "", 45 | "ctestCommandArgs": "", 46 | "inheritEnvironments": [ "msvc_x64_x64" ], 47 | "variables": [] 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /Config.h.in: -------------------------------------------------------------------------------- 1 | /* the configured options and settings for osslsigncode */ 2 | #define VERSION_MAJOR "@osslsigncode_VERSION_MAJOR@" 3 | #define VERSION_MINOR "@osslsigncode_VERSION_MINOR@" 4 | #cmakedefine PACKAGE_STRING "@PACKAGE_STRING@" 5 | #cmakedefine PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" 6 | #cmakedefine HAVE_TERMIOS_H 7 | #cmakedefine HAVE_GETPASS 8 | #cmakedefine HAVE_SYS_MMAN_H 9 | #cmakedefine HAVE_MMAP 10 | #cmakedefine HAVE_MAPVIEWOFFILE 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build osslsigncode on Alpine 2 | FROM alpine:latest AS builder 3 | 4 | # Install build dependencies 5 | RUN apk add --no-cache build-base cmake openssl-dev zlib-dev 6 | 7 | # Copy osslsigncode source code into the image 8 | COPY . /source 9 | 10 | # Build osslsigncode 11 | RUN cd /source && \ 12 | mkdir -p build && \ 13 | cd build && \ 14 | rm -f CMakeCache.txt && \ 15 | cmake -S .. && \ 16 | cmake --build . && \ 17 | cmake --install . 18 | 19 | # Stage 2: Create final image without build environment 20 | FROM alpine:latest 21 | 22 | # Copy compiled binary from builder stage 23 | COPY --from=builder /usr/local/bin/osslsigncode /usr/local/bin/osslsigncode 24 | 25 | # Install necessary runtime libraries (latest version) 26 | RUN apk add --no-cache libcrypto3 27 | 28 | # Set working directory 29 | WORKDIR /workdir 30 | 31 | # Declare volume to mount files 32 | VOLUME [ "/workdir" ] 33 | -------------------------------------------------------------------------------- /INSTALL.W32.md: -------------------------------------------------------------------------------- 1 | # osslsigncode Windows install notes 2 | 3 | ### Building osslsigncode source with MSYS2 MinGW 64-bit and MSYS2 packages: 4 | 5 | 1) Download and install MSYS2 from https://msys2.github.io/ and follow installation instructions. 6 | Once up and running install the following packages: 7 | ``` 8 | pacman -S make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-openssl mingw-w64-x86_64-python-cryptography 9 | ``` 10 | mingw-w64-x86_64-zlib package is installed with dependencies. 11 | 12 | 2) Run "MSYS2 MinGW 64-bit" and build 64-bit Windows executables. 13 | ``` 14 | cd osslsigncode-folder 15 | mkdir build && cd build && cmake -S .. -DCMAKE_BUILD_TYPE=Release -G "MSYS Makefiles" 16 | cmake --build . --verbose 17 | ``` 18 | 19 | 3) Make tests. 20 | ``` 21 | ctest 22 | ``` 23 | 24 | 4) Run "Command prompt" and include "c:\msys64\mingw64\bin" folder as part of the path. 25 | ``` 26 | path=%path%;c:\msys64\mingw64\bin 27 | osslsigncode.exe -v 28 | osslsigncode 2.8, using: 29 | OpenSSL 3.2.0 23 Nov 2023 (Library: OpenSSL 3.2.0 23 Nov 2023) 30 | No default -CAfile location detected 31 | ``` 32 | 33 | ### Building OpenSSL and osslsigncode sources with MSYS2 MinGW 64-bit: 34 | 35 | 1) Download and install MSYS2 from https://msys2.github.io/ and follow installation instructions. 36 | Once up and running install even: perl make autoconf automake libtool pkg-config. 37 | ``` 38 | pacman -S perl make autoconf automake libtool pkg-config 39 | ``` 40 | Run "MSYS2 MinGW 64-bit" in the administrator mode. 41 | 42 | 2) Build and install OpenSSL. 43 | ``` 44 | cd openssl-(version) 45 | ./config --prefix='C:/OpenSSL' --openssldir='C:/OpenSSL' 46 | make && make install 47 | ``` 48 | 49 | 3) Configure a CMake project. 50 | ``` 51 | mkdir build && cd build && cmake -S .. -DCMAKE_BUILD_TYPE=Release -G "MSYS Makefiles" -DCMAKE_PREFIX_PATH="C:\OpenSSL" 52 | ``` 53 | 54 | 4) Run "Command prompt" and copy required libraries. 55 | ``` 56 | cd osslsigncode-folder 57 | copy C:\OpenSSL\bin\libssl-3-x64.dll 58 | copy C:\OpenSSL\bin\libcrypto-3-x64.dll 59 | ``` 60 | 61 | 5) Build 64-bit Windows executables. 62 | ``` 63 | cmake --build . --verbose 64 | ``` 65 | 66 | 6) Make tests. 67 | ``` 68 | ctest 69 | ``` 70 | 71 | ### Building OpenSSL and osslsigncode sources with Microsoft Visual Studio: 72 | 73 | 1) Install and integrate vcpkg: https://vcpkg.io/en/getting-started.html 74 | 75 | 2) Git clone osslsigncode: https://github.com/mtrojnar/osslsigncode/ 76 | 77 | 3) Build osslsigncode with GUI or cmake. 78 | Navigate to the build directory and run CMake to configure the osslsigncode project 79 | and generate a native build system: 80 | ``` 81 | mkdir build && cd build && cmake -S .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=[installation directory] -DCMAKE_TOOLCHAIN_FILE=[path to vcpkg]/scripts/buildsystems/vcpkg.cmake 82 | ``` 83 | Then call that build system to actually compile/link the osslsigncode project: 84 | ``` 85 | cmake --build . 86 | ``` 87 | 88 | 4) Make tests. 89 | ``` 90 | ctest -C Release 91 | ``` 92 | 93 | 5) Make install (with administrative privileges if necessary). 94 | ``` 95 | cmake --install . 96 | ``` 97 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | OpenSSL based Authenticode signing for PE/MSI/Java CAB files. 2 | 3 | Copyright (C) 2005-2014 Per Allansson 4 | Copyright (C) 2018-2022 Michał Trojnara 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | In addition, as a special exception, the copyright holders give 20 | permission to link the code of portions of this program with the 21 | OpenSSL library under certain conditions as described in each 22 | individual source file, and distribute linked combinations 23 | including the two. 24 | You must obey the GNU General Public License in all respects 25 | for all of the code used other than OpenSSL. If you modify 26 | file(s) with this exception, you may extend this exception to your 27 | version of the file(s), but you are not obligated to do so. If you 28 | do not wish to do so, delete this exception statement from your 29 | version. If you delete this exception statement from all source 30 | files in the program, then also delete it here. 31 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # osslsigncode change log 2 | 3 | ### 2.10 (unreleased) 4 | 5 | - added JavaScript signing 6 | - added PKCS#11 provider support (requires OpenSSL 3.0+) 7 | - added support for providers without specifying "-pkcs11module" option 8 | (OpenSSL 3.0+, e.g., for the upcoming CNG provider) 9 | - added compatiblity with the CNG engine version 1.1 or later 10 | - added the "-engineCtrl" option to control hardware and CNG engines 11 | - added the '-blobFile' option to specify a file containing the blob content 12 | - improved unauthenticated blob support (thanks to Asger Hautop Drewsen) 13 | - fixed support for multiple signerInfo contentType OIDs (CTL and Authenticode) 14 | - fixed tests for python-cryptography >= 43.0.0 15 | 16 | ### 2.9 (2024.06.29) 17 | 18 | - added a 64 bit long pseudo-random NONCE in the TSA request 19 | - missing NID_pkcs9_signingTime is no longer an error 20 | - added support for PEM-encoded CRLs 21 | - fixed the APPX central directory sorting order 22 | - added a special "-" file name to read the passphrase from stdin 23 | (by Steve McIntyre) 24 | - used native HTTP client with OpenSSL 3.x, removing libcurl dependency 25 | - added '-login' option to force a login to PKCS11 engines 26 | (by Brad Hughes) 27 | - added the "-ignore-crl" option to disable fetching and verifying 28 | CRL Distribution Points 29 | - changed error output to stderr instead of stdout 30 | - various testing framework improvements 31 | - various memory corruption fixes 32 | 33 | ### 2.8 (2024.03.03) 34 | 35 | - Microsoft PowerShell signing sponsored by Cisco Systems, Inc. 36 | - fixed setting unauthenticated attributes (Countersignature, Unauthenticated 37 | Data Blob) in a nested signature 38 | - added the "-index" option to verify a specific signature or modify its 39 | unauthenticated attributes 40 | - added CAT file verification 41 | - added listing the contents of a CAT file with the "-verbose" option 42 | - added the new "extract-data" command to extract a PKCS#7 data content to be 43 | signed with "sign" and attached with "attach-signature" 44 | - added PKCS9_SEQUENCE_NUMBER authenticated attribute support 45 | - added the "-ignore-cdp" option to disable CRL Distribution Points (CDP) 46 | online verification 47 | - unsuccessful CRL retrieval and verification changed into a critical error 48 | - the "-p" option modified to also use to configured proxy to connect CRL 49 | Distribution Points 50 | - added implicit allowlisting of the Microsoft Root Authority serial number 51 | 00C1008B3C3C8811D13EF663ECDF40 52 | - added listing of certificate chain retrieved from the signature in case of 53 | verification failure 54 | 55 | ### 2.7 (2023.09.19) 56 | 57 | - fixed signing CAB files (by Michael Brown) 58 | - fixed handling of unsupported commands (by Maxim Bagryantsev) 59 | - fixed writing DIFAT sectors 60 | - added APPX support (by Maciej Panek and Małgorzata Olszówka) 61 | - added a built-in TSA response generation (-TSA-certs, -TSA-key 62 | and -TSA-time options) 63 | 64 | ### 2.6 (2023.05.29) 65 | 66 | - modular architecture implemented to simplify adding file formats 67 | - added verification of CRLs specified in the signing certificate 68 | - added MSI DIFAT sectors support (by Max Bagryantsev) 69 | - added legacy provider support for OpenSSL 3.0.0 and later 70 | - fixed numerous bugs 71 | 72 | ### 2.5 (2022.08.12) 73 | 74 | - fixed the Unix executable install path 75 | - fixed the hardcoded "pkcs11" engine id 76 | - fixed building with MinGW 77 | - fixed testing with the python3 distributed with Ubuntu 18.04 78 | 79 | ### 2.4 (2022.08.02) 80 | 81 | - migrated the build system from GNU Autoconf to CMake 82 | - added the "-h" option to set the cryptographic hash function 83 | for the "attach -signature" and "add" commands 84 | - set the default hash function to "sha256" 85 | - added the "attach-signature" option to compute and compare the 86 | leaf certificate hash for the "add" command 87 | - renamed the "-st" option "-time" (the old name is accepted for 88 | compatibility) 89 | - updated the "-time" option to also set explicit verification time 90 | - added the "-ignore-timestamp" option to disable timestamp server 91 | signature verification 92 | - removed the "-timestamp-expiration" option 93 | - fixed several bugs 94 | - updated the included documentation 95 | - enabled additional compiler/linker hardening options 96 | - added CI based on GitHub Actions 97 | 98 | ### 2.3 (2022.03.06) 99 | 100 | **CRITICAL SECURITY VULNERABILITIES** 101 | 102 | This release fixes several critical memory corruption vulnerabilities. 103 | A malicious attacker could create a file, which, when processed with 104 | osslsigncode, triggers arbitrary code execution. Any previous version 105 | of osslsigncode should be immediately upgraded if the tool is used for 106 | processing of untrusted files. 107 | 108 | - fixed several memory safety issues 109 | - fixed non-interactive PVK (MSBLOB) key decryption 110 | - added a bash completion script 111 | - added CA bundle path auto-detection 112 | 113 | ### 2.2 (2021.08.15) 114 | 115 | - CAT files support (thanks to James McKenzie) 116 | - MSI support rewritten without libgsf dependency, which allows 117 | for handling of all the needed MSI metadata, such as dates 118 | - "-untrusted" option renamed to "-TSA-CAfile" 119 | - "-CRLuntrusted" option renamed to "-TSA-CRLfile" 120 | - numerous bug fixes and improvements 121 | 122 | ### 2.1 (2020-10-11) 123 | 124 | - certificate chain verification support 125 | - timestamp verification support 126 | - CRL verification support ("-CRLfile" option) 127 | - improved CAB signature support 128 | - nested signatures support 129 | - user-specified signing time ("-st" option) by vszakats 130 | - added more tests 131 | - fixed numerous bugs 132 | - dropped OpenSSL 1.1.0 support 133 | 134 | ### 2.0 (2018-12-04) 135 | 136 | - orphaned project adopted by Michał Trojnara 137 | - ported to OpenSSL 1.1.x 138 | - ported to SoftHSM2 139 | - add support for pkcs11-based hardware tokens 140 | (Patch from Leif Johansson) 141 | - improved error reporting of timestamping errors 142 | (Patch from Carlo Teubner) 143 | 144 | ### 1.7.1 (2014-07-11) 145 | 146 | - MSI: added -add-msi-dse option 147 | (Patch from Mikkel Krautz) 148 | - MSI: fix build when GSF_CAN_READ_MSI_METADATA defined 149 | (Patch from Mikkel Krautz) 150 | 151 | ### 1.7 (2014-07-10) 152 | 153 | - add support for nested signatures 154 | (Patch from Mikkel Krautz) 155 | - fix compilation problem with OpenSSL < 1.0.0 156 | - added OpenSSL linkage exception to license 157 | 158 | ### 1.6 (2014-01-21) 159 | 160 | - add support for reading password from file 161 | - add support for asking for password (on systems that 162 | provide support for it) 163 | - add support for compiling and running on Windows 164 | (Patch from Heiko Hund) 165 | - fix compilation without curl 166 | (Fix from Heiko Hund) 167 | - added support for giving multiple timestamp servers 168 | as arguments (first one that succeeds will be used) 169 | - signatures on hierarchical MSI files were broken 170 | (Fix from Mikkel Krautz) 171 | - MSI: Add support for MsiDigitalSignatureEx signature 172 | (Patch from Mikkel Krautz) 173 | - add support for adding additional/cross certificates 174 | through -ac option 175 | (Thanks to Lars Munch for idea + testing) 176 | - MSI: Add support for signature extract/remove/verify 177 | (Patches from Mikkel Krautz) 178 | - PE/MSI: Implement -require-leaf-hash for verify. 179 | (Patch from Mikkel Krautz) 180 | 181 | ### 1.5.2 (2013-03-13) 182 | 183 | - added support for signing with SHA-384 and SHA-512 184 | - added support for page hashing (-ph option) 185 | 186 | ### 1.5.1 (2013-03-12) 187 | 188 | - forgot to bump version number... 189 | 190 | ### 1.5 (2013-03-12) 191 | 192 | - added support for signing MSI files (patch from Marc-André Lureau) 193 | - calculate correct PE checksum instead of setting it to 0 194 | (patch from Roland Schwingel) 195 | - added support for RFC3161 timestamping (-ts option) 196 | - added support for extracting/removing/verifying signature on PE files 197 | - fixed problem with not being able to decode timestamps with no newlines 198 | - added stricter checks for PE file validity 199 | - added support for reading keys from PVK files (requires OpenSSL 1.0.0 or later) 200 | - added support for reading certificates from PEM files 201 | - renamed program option: -spc to -certs (old option name still valid) 202 | 203 | ### 1.4 (2011-08-12) 204 | 205 | - improved build system (patch from Alon Bar-Lev) 206 | - support reading cert+key from PKCS12 file (patch from Alon Bar-Lev) 207 | - support reading key from PEM file 208 | - added support for sha1/sha256 - default hash is now sha1 209 | - added flag for commercial signing (default is individual) 210 | 211 | ### 1.3.1 (2009-08-07) 212 | 213 | - support signing of 64-bit executables (fix from Paul Kendall) 214 | 215 | ### 1.3 (2008-01-31) 216 | 217 | - fixed padding problem (fix from Ryan Rubley) 218 | - allow signing of already signed files (fix from Ryan Rubley) 219 | - added Ryan Rubley's PVK-to-DER guide into the README 220 | 221 | ### 1.2 (2005-01-21) 222 | 223 | - autoconf:ed (Thanks to Roy Keene) 224 | - added documentation 225 | - don't override PKCS7_get_signed_attribute, it wasn't 226 | actually needed, it was me being confused. 227 | - compiles without curl, which means no timestamping 228 | - version number output 229 | 230 | ### 1.1 (2005-01-19) 231 | 232 | - Initial release 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | osslsigncode 2 | ============ 3 | 4 | ## BUILD STATUS 5 | 6 | [![CI](https://github.com/mtrojnar/osslsigncode/actions/workflows/ci.yml/badge.svg)](https://github.com/mtrojnar/osslsigncode/actions/workflows/ci.yml) 7 | 8 | ## WHAT IS IT? 9 | 10 | osslsigncode is a small tool that implements part of the functionality 11 | of the Microsoft tool signtool.exe - more exactly the Authenticode 12 | signing and timestamping. But osslsigncode is based on OpenSSL and cURL, 13 | and thus should be able to compile on most platforms where these exist. 14 | 15 | ## WHY? 16 | 17 | Why not use signtool.exe? Because I don't want to go to a Windows 18 | machine every time I need to sign a binary - I can compile and build 19 | the binaries using Wine on my Linux machine, but I can't sign them 20 | since the signtool.exe makes good use of the CryptoAPI in Windows, and 21 | these APIs aren't (yet?) fully implemented in Wine, so the signtool.exe 22 | tool would fail. And, so, osslsigncode was born. 23 | 24 | ## WHAT CAN IT DO? 25 | 26 | It can sign and timestamp PE (EXE/SYS/DLL/etc), CAB, CAT and MSI files. 27 | It supports the equivalent of signtool.exe's "-j javasign.dll -jp low", 28 | i.e. add a valid signature for a CAB file containing Java files. 29 | It supports getting the timestamp through a proxy as well. It also 30 | supports signature verification, removal and extraction. 31 | 32 | ## BUILDING 33 | 34 | This section covers building osslsigncode for [Unix-like](https://en.wikipedia.org/wiki/Unix-like) operating systems. 35 | See [INSTALL.W32.md](https://github.com/mtrojnar/osslsigncode/blob/master/INSTALL.W32.md) for Windows notes. 36 | We highly recommend downloading a [release tarball](https://github.com/mtrojnar/osslsigncode/releases) instead of cloning from a git repository. 37 | 38 | ### Configure, build, make tests and install osslsigncode 39 | 40 | * Install prerequisites on a Debian-based distributions, such as Ubuntu: 41 | ``` 42 | sudo apt update && sudo apt install cmake libssl-dev libcurl4-openssl-dev zlib1g-dev python3 43 | ``` 44 | * Install prerequisites on macOS with Homebrew: 45 | ``` 46 | brew install cmake pkg-config openssl@1.1 47 | export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig" 48 | ``` 49 | **NOTE:** osslsigncode requires CMake 3.17 or newer. 50 | 51 | You may need to use `cmake3` instead of `cmake` to complete the following steps on your system. 52 | * Navigate to the build directory and run CMake to configure the osslsigncode project 53 | and generate a native build system: 54 | ``` 55 | mkdir build && cd build && cmake -S .. 56 | ``` 57 | optional CMake parameters: 58 | ``` 59 | -DCMAKE_BUILD_TYPE=Debug 60 | -DCMAKE_C_COMPILER=clang 61 | -DCMAKE_PREFIX_PATH=[openssl directory];[curl directory] 62 | -DCMAKE_INSTALL_PREFIX=[installation directory] 63 | -DBASH_COMPLETION_USER_DIR=[bash completion installation directory] 64 | 65 | ``` 66 | * Then call that build system to actually compile/link the osslsigncode project (alias `make`): 67 | ``` 68 | cmake --build . 69 | ``` 70 | * Make test: 71 | ``` 72 | ctest -C Release 73 | ``` 74 | * Make install: 75 | ``` 76 | sudo cmake --install . 77 | ``` 78 | * Make tarball (simulate autotools' `make dist`): 79 | ``` 80 | cmake --build . --target package_source 81 | ``` 82 | 83 | ## USAGE 84 | 85 | Before you can sign a file you need a Software Publishing 86 | Certificate (spc) and a corresponding private key. 87 | 88 | This article provides a good starting point as to how 89 | to do the signing with the Microsoft signtool.exe: 90 | 91 | http://www.matthew-jones.com/articles/codesigning.html 92 | 93 | To sign with osslsigncode you need the certificate file mentioned in the 94 | article above, in SPC or PEM format, and you will also need the private 95 | key which must be a key file in DER or PEM format, or if osslsigncode was 96 | compiled against OpenSSL 1.0.0 or later, in PVK format. 97 | 98 | To sign a PE or MSI file you can now do: 99 | ``` 100 | osslsigncode sign -certs -key \ 101 | -n "Your Application" -i http://www.yourwebsite.com/ \ 102 | -in yourapp.exe -out yourapp-signed.exe 103 | ``` 104 | or if you are using a PEM or PVK key file with a password together 105 | with a PEM certificate: 106 | ``` 107 | osslsigncode sign -certs \ 108 | -key -pass \ 109 | -n "Your Application" -i http://www.yourwebsite.com/ \ 110 | -in yourapp.exe -out yourapp-signed.exe 111 | ``` 112 | or if you want to add a timestamp as well: 113 | ``` 114 | osslsigncode sign -certs -key \ 115 | -n "Your Application" -i http://www.yourwebsite.com/ \ 116 | -t http://timestamp.digicert.com \ 117 | -in yourapp.exe -out yourapp-signed.exe 118 | ``` 119 | You can use a certificate and key stored in a PKCS#12 container: 120 | ``` 121 | osslsigncode sign -pkcs12 -pass \ 122 | -n "Your Application" -i http://www.yourwebsite.com/ \ 123 | -in yourapp.exe -out yourapp-signed.exe 124 | ``` 125 | To sign a CAB file containing java class files: 126 | ``` 127 | osslsigncode sign -certs -key \ 128 | -n "Your Application" -i http://www.yourwebsite.com/ \ 129 | -jp low \ 130 | -in yourapp.cab -out yourapp-signed.cab 131 | ``` 132 | Only the 'low' parameter is currently supported. 133 | 134 | If you want to use a PKCS#11 token, you should specify the PKCS#11 engine and module. 135 | An example of using osslsigncode with SoftHSM: 136 | ``` 137 | osslsigncode sign \ 138 | -engine /usr/lib64/engines-1.1/pkcs11.so \ 139 | -pkcs11module /usr/lib64/pkcs11/libsofthsm2.so \ 140 | -pkcs11cert 'pkcs11:token=softhsm-token;object=cert' \ 141 | -key 'pkcs11:token=softhsm-token;object=key' \ 142 | -in yourapp.exe -out yourapp-signed.exe 143 | ``` 144 | 145 | Since OpenSSL 3.0, you can use a PKCS#11 token with the PKCS#11 provider. 146 | An example of using osslsigncode with OpenSC: 147 | ``` 148 | osslsigncode sign \ 149 | -provider /usr/lib64/ossl-modules/pkcs11prov.so \ 150 | -pkcs11module /usr/lib64/opensc-pkcs11.so \ 151 | -pkcs11cert 'pkcs11:token=my-token;object=cert' \ 152 | -key 'pkcs11:token=my-token;object=key' \ 153 | -in yourapp.exe -out yourapp-signed.exe 154 | ``` 155 | 156 | You can use a certificate and key stored in the Windows Certificate Store with 157 | the CNG engine version 1.1 or later. For more information, refer to 158 | 159 | https://www.stunnel.org/cng-engine.html 160 | 161 | A non-commercial edition of CNG engine is available for testing, personal, 162 | educational, or research purposes. 163 | 164 | To use the CNG engine with osslsigncode, ensure that the `cng.dll` library is 165 | placed in the same directory as the `osslsigncode.exe` executable. 166 | 167 | Below is an example of how to use osslsigncode with the CNG engine: 168 | ``` 169 | osslsigncode sign \ 170 | -engine cng \ 171 | -pkcs11cert osslsigncode_cert \ 172 | -key osslsigncode_cert \ 173 | -engineCtrl store_flags:0 \ 174 | -engineCtrl store_name:MY \ 175 | -engineCtrl PIN:yourpass \ 176 | -in yourapp.exe -out yourapp-signed.exe 177 | ``` 178 | 179 | You can check that the signed file is correct by right-clicking 180 | on it in Windows and choose Properties --> Digital Signatures, 181 | and then choose the signature from the list, and click on 182 | Details. You should then be presented with a dialog that says 183 | amongst other things that "This digital signature is OK". 184 | 185 | ## UNAUTHENTICATED BLOBS 186 | 187 | The "-addUnauthenticatedBlob" parameter adds a 1024-byte unauthenticated blob 188 | of data to the signature in the same area as the timestamp. This can be used 189 | while signing, while timestamping, after a file has been code signed, or by 190 | itself. This technique (but not this project) is used by Dropbox, GoToMeeting, 191 | and Summit Route. 192 | 193 | ### Example 1. Sign and add blob to unsigned file 194 | 195 | ```shell 196 | osslsigncode sign -addUnauthenticatedBlob -pkcs12 yourcert.pfx -pass your_password -n "Your Company" -i https://YourSite.com/ -in srepp.msi -out srepp_added.msi 197 | ``` 198 | 199 | ### Example 2. Timestamp and add blob to signed file 200 | 201 | ```shell 202 | osslsigncode.exe add -addUnauthenticatedBlob -t http://timestamp.digicert.com -in your_signed_file.exe -out out.exe 203 | ``` 204 | 205 | ### Example 3. Add blob to signed and time-stamped file 206 | 207 | ```shell 208 | osslsigncode.exe add -addUnauthenticatedBlob -in your_signed_file.exe -out out.exe 209 | ``` 210 | 211 | ### WARNING 212 | 213 | This feature allows for doing dumb things. Be very careful with what you put 214 | in the unauthenticated blob, as an attacker could modify this. Do NOT, under 215 | any circumstances, put a URL here that you will use to download an additional 216 | file. If you do do that, you would need to check the newly downloaded file is 217 | code signed AND that it has been signed with your cert AND that it is the 218 | version you expect. 219 | 220 | ## BUGS, QUESTIONS etc. 221 | 222 | Check whether your your question or suspected bug was already 223 | discussed on https://github.com/mtrojnar/osslsigncode/issues. 224 | Otherwise, open a new issue. 225 | 226 | BUT, if you have questions related to generating spc files, 227 | converting between different formats and so on, *please* 228 | spend a few minutes searching on google for your particular 229 | problem since many people probably already have had your 230 | problem and solved it as well. 231 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - signature extraction/removal/verificaton on MSI/CAB files 2 | - clean up / untangle code 3 | - separate timestamping 4 | - remove mmap usage to increase portability 5 | - fix other stuff marked 'XXX' 6 | -------------------------------------------------------------------------------- /applink.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2004-2021 The OpenSSL Project Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License 2.0 (the "License"). You may not use 5 | * this file except in compliance with the License. You can obtain a copy 6 | * in the file LICENSE in the source distribution or at 7 | * https://www.openssl.org/source/license.html 8 | */ 9 | 10 | #define APPLINK_STDIN 1 11 | #define APPLINK_STDOUT 2 12 | #define APPLINK_STDERR 3 13 | #define APPLINK_FPRINTF 4 14 | #define APPLINK_FGETS 5 15 | #define APPLINK_FREAD 6 16 | #define APPLINK_FWRITE 7 17 | #define APPLINK_FSETMOD 8 18 | #define APPLINK_FEOF 9 19 | #define APPLINK_FCLOSE 10 /* should not be used */ 20 | 21 | #define APPLINK_FOPEN 11 /* solely for completeness */ 22 | #define APPLINK_FSEEK 12 23 | #define APPLINK_FTELL 13 24 | #define APPLINK_FFLUSH 14 25 | #define APPLINK_FERROR 15 26 | #define APPLINK_CLEARERR 16 27 | #define APPLINK_FILENO 17 /* to be used with below */ 28 | 29 | #define APPLINK_OPEN 18 /* formally can't be used, as flags can vary */ 30 | #define APPLINK_READ 19 31 | #define APPLINK_WRITE 20 32 | #define APPLINK_LSEEK 21 33 | #define APPLINK_CLOSE 22 34 | #define APPLINK_MAX 22 /* always same as last macro */ 35 | 36 | #ifndef APPMACROS_ONLY 37 | # include 38 | # include 39 | # include 40 | 41 | # ifdef __BORLANDC__ 42 | /* _lseek in is a function-like macro so we can't take its address */ 43 | # undef _lseek 44 | # define _lseek lseek 45 | # endif 46 | 47 | static void *app_stdin(void) 48 | { 49 | return stdin; 50 | } 51 | 52 | static void *app_stdout(void) 53 | { 54 | return stdout; 55 | } 56 | 57 | static void *app_stderr(void) 58 | { 59 | return stderr; 60 | } 61 | 62 | static int app_feof(FILE *fp) 63 | { 64 | return feof(fp); 65 | } 66 | 67 | static int app_ferror(FILE *fp) 68 | { 69 | return ferror(fp); 70 | } 71 | 72 | static void app_clearerr(FILE *fp) 73 | { 74 | clearerr(fp); 75 | } 76 | 77 | static int app_fileno(FILE *fp) 78 | { 79 | return _fileno(fp); 80 | } 81 | 82 | static int app_fsetmod(FILE *fp, char mod) 83 | { 84 | return _setmode(_fileno(fp), mod == 'b' ? _O_BINARY : _O_TEXT); 85 | } 86 | 87 | #ifdef __cplusplus 88 | extern "C" { 89 | #endif 90 | 91 | __declspec(dllexport) 92 | void ** 93 | # if defined(__BORLANDC__) 94 | /* 95 | * __stdcall appears to be the only way to get the name 96 | * decoration right with Borland C. Otherwise it works 97 | * purely incidentally, as we pass no parameters. 98 | */ 99 | __stdcall 100 | # else 101 | __cdecl 102 | # endif 103 | #pragma warning(push, 2) 104 | OPENSSL_Applink(void) 105 | { 106 | static int once = 1; 107 | static void *OPENSSL_ApplinkTable[APPLINK_MAX + 1] = 108 | { (void *)APPLINK_MAX }; 109 | 110 | if (once) { 111 | OPENSSL_ApplinkTable[APPLINK_STDIN] = app_stdin; 112 | OPENSSL_ApplinkTable[APPLINK_STDOUT] = app_stdout; 113 | OPENSSL_ApplinkTable[APPLINK_STDERR] = app_stderr; 114 | OPENSSL_ApplinkTable[APPLINK_FPRINTF] = fprintf; 115 | OPENSSL_ApplinkTable[APPLINK_FGETS] = fgets; 116 | OPENSSL_ApplinkTable[APPLINK_FREAD] = fread; 117 | OPENSSL_ApplinkTable[APPLINK_FWRITE] = fwrite; 118 | OPENSSL_ApplinkTable[APPLINK_FSETMOD] = app_fsetmod; 119 | OPENSSL_ApplinkTable[APPLINK_FEOF] = app_feof; 120 | OPENSSL_ApplinkTable[APPLINK_FCLOSE] = fclose; 121 | 122 | OPENSSL_ApplinkTable[APPLINK_FOPEN] = fopen; 123 | OPENSSL_ApplinkTable[APPLINK_FSEEK] = fseek; 124 | OPENSSL_ApplinkTable[APPLINK_FTELL] = ftell; 125 | OPENSSL_ApplinkTable[APPLINK_FFLUSH] = fflush; 126 | OPENSSL_ApplinkTable[APPLINK_FERROR] = app_ferror; 127 | OPENSSL_ApplinkTable[APPLINK_CLEARERR] = app_clearerr; 128 | OPENSSL_ApplinkTable[APPLINK_FILENO] = app_fileno; 129 | 130 | OPENSSL_ApplinkTable[APPLINK_OPEN] = _open; 131 | OPENSSL_ApplinkTable[APPLINK_READ] = _read; 132 | OPENSSL_ApplinkTable[APPLINK_WRITE] = _write; 133 | OPENSSL_ApplinkTable[APPLINK_LSEEK] = _lseek; 134 | OPENSSL_ApplinkTable[APPLINK_CLOSE] = _close; 135 | 136 | once = 0; 137 | } 138 | 139 | return OPENSSL_ApplinkTable; 140 | } 141 | #pragma warning(pop) 142 | #ifdef __cplusplus 143 | } 144 | #endif 145 | #endif 146 | -------------------------------------------------------------------------------- /cat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CAT file support library 3 | * 4 | * Copyright (C) 2021-2023 Michał Trojnara 5 | * Author: Małgorzata Olszówka 6 | * 7 | * Catalog files are a bit odd, in that they are only a PKCS7 blob. 8 | * CAT files do not support nesting (multiple signature) 9 | */ 10 | 11 | #include "osslsigncode.h" 12 | #include "helpers.h" 13 | 14 | typedef struct { 15 | ASN1_BMPSTRING *tag; 16 | ASN1_INTEGER *flags; 17 | ASN1_OCTET_STRING *value; 18 | } CatNameValueContent; 19 | 20 | DECLARE_ASN1_FUNCTIONS(CatNameValueContent) 21 | 22 | ASN1_SEQUENCE(CatNameValueContent) = { 23 | ASN1_SIMPLE(CatNameValueContent, tag, ASN1_BMPSTRING), 24 | ASN1_SIMPLE(CatNameValueContent, flags, ASN1_INTEGER), 25 | ASN1_SIMPLE(CatNameValueContent, value, ASN1_OCTET_STRING) 26 | } ASN1_SEQUENCE_END(CatNameValueContent) 27 | 28 | IMPLEMENT_ASN1_FUNCTIONS(CatNameValueContent) 29 | 30 | struct cat_ctx_st { 31 | uint32_t sigpos; 32 | uint32_t siglen; 33 | uint32_t fileend; 34 | PKCS7 *p7; 35 | }; 36 | 37 | /* FILE_FORMAT method prototypes */ 38 | static FILE_FORMAT_CTX *cat_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata); 39 | static int cat_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7); 40 | static PKCS7 *cat_pkcs7_extract(FILE_FORMAT_CTX *ctx); 41 | static PKCS7 *cat_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash); 42 | static int cat_append_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7); 43 | static void cat_bio_free(BIO *hash, BIO *outdata); 44 | static void cat_ctx_cleanup(FILE_FORMAT_CTX *ctx); 45 | 46 | FILE_FORMAT file_format_cat = { 47 | .ctx_new = cat_ctx_new, 48 | .verify_digests = cat_verify_digests, 49 | .pkcs7_extract = cat_pkcs7_extract, 50 | .pkcs7_signature_new = cat_pkcs7_signature_new, 51 | .append_pkcs7 = cat_append_pkcs7, 52 | .bio_free = cat_bio_free, 53 | .ctx_cleanup = cat_ctx_cleanup, 54 | }; 55 | 56 | /* Prototypes */ 57 | static CAT_CTX *cat_ctx_get(char *indata, uint32_t filesize); 58 | static int cat_add_content_type(PKCS7 *p7, PKCS7 *cursig); 59 | static int cat_sign_content(PKCS7 *p7, PKCS7 *contents); 60 | static int cat_list_content(PKCS7 *p7); 61 | static int cat_print_content_member_digest(ASN1_TYPE *content); 62 | static int cat_print_content_member_name(ASN1_TYPE *content); 63 | static void cat_print_base64(ASN1_OCTET_STRING *value); 64 | static void cat_print_utf16_as_ascii(ASN1_OCTET_STRING *value); 65 | static int cat_check_file(FILE_FORMAT_CTX *ctx); 66 | 67 | /* 68 | * FILE_FORMAT method definitions 69 | */ 70 | 71 | /* 72 | * Allocate and return a CAT file format context. 73 | * [in, out] options: structure holds the input data 74 | * [out] hash: message digest BIO (unused) 75 | * [in] outdata: outdata file BIO (unused) 76 | * [returns] pointer to CAT file format context 77 | */ 78 | static FILE_FORMAT_CTX *cat_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata) 79 | { 80 | FILE_FORMAT_CTX *ctx; 81 | CAT_CTX *cat_ctx; 82 | uint32_t filesize; 83 | 84 | if (options->cmd == CMD_REMOVE || options->cmd==CMD_ATTACH || options->cmd == CMD_EXTRACT_DATA) { 85 | fprintf(stderr, "Unsupported command\n"); 86 | return NULL; /* FAILED */ 87 | } 88 | filesize = get_file_size(options->infile); 89 | if (filesize == 0) 90 | return NULL; /* FAILED */ 91 | 92 | options->indata = map_file(options->infile, filesize); 93 | if (!options->indata) { 94 | return NULL; /* FAILED */ 95 | } 96 | cat_ctx = cat_ctx_get(options->indata, filesize); 97 | if (!cat_ctx) { 98 | unmap_file(options->indata, filesize); 99 | return NULL; /* FAILED */ 100 | } 101 | ctx = OPENSSL_malloc(sizeof(FILE_FORMAT_CTX)); 102 | ctx->format = &file_format_cat; 103 | ctx->options = options; 104 | ctx->cat_ctx = cat_ctx; 105 | 106 | /* Push hash on outdata, if hash is NULL the function does nothing */ 107 | BIO_push(hash, outdata); 108 | 109 | if (options->cmd == CMD_VERIFY) 110 | printf("Warning: Use -catalog option to verify that a file, listed in catalog file, is signed\n"); 111 | if (options->jp >= 0) 112 | printf("Warning: -jp option is only valid for CAB files\n"); 113 | if (options->pagehash == 1) 114 | printf("Warning: -ph option is only valid for PE files\n"); 115 | if (options->add_msi_dse == 1) 116 | printf("Warning: -add-msi-dse option is only valid for MSI files\n"); 117 | return ctx; 118 | } 119 | 120 | /* 121 | * ContentInfo value is the inner content of pkcs7-signedData. 122 | * An extra verification is not necessary when a content type data 123 | * is the inner content of the signed-data type. 124 | */ 125 | static int cat_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7) 126 | { 127 | /* squash unused parameter warnings */ 128 | (void)ctx; 129 | (void)p7; 130 | return 1; /* OK */ 131 | } 132 | 133 | /* 134 | * Extract existing signature in DER format. 135 | * [in] ctx: structure holds input and output data 136 | * [returns] pointer to PKCS#7 structure 137 | */ 138 | static PKCS7 *cat_pkcs7_extract(FILE_FORMAT_CTX *ctx) 139 | { 140 | if (!cat_check_file(ctx)) { 141 | return NULL; /* FAILED */ 142 | } 143 | return PKCS7_dup(ctx->cat_ctx->p7); 144 | } 145 | 146 | /* 147 | * Create a new PKCS#7 signature. 148 | * [in, out] ctx: structure holds input and output data 149 | * [out] hash: message digest BIO (unused) 150 | * [returns] pointer to PKCS#7 structure 151 | */ 152 | static PKCS7 *cat_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash) 153 | { 154 | PKCS7 *p7 = NULL; 155 | 156 | /* squash unused parameter warnings */ 157 | (void)hash; 158 | 159 | p7 = pkcs7_create(ctx); 160 | if (!p7) { 161 | fprintf(stderr, "Creating a new signature failed\n"); 162 | return NULL; /* FAILED */ 163 | } 164 | if (!ctx->cat_ctx->p7 || !ctx->cat_ctx->p7->d.sign || !ctx->cat_ctx->p7->d.sign->contents) { 165 | fprintf(stderr, "Failed to get content\n"); 166 | PKCS7_free(p7); 167 | return NULL; /* FAILED */ 168 | } 169 | if (!cat_add_content_type(p7, ctx->cat_ctx->p7)) { 170 | fprintf(stderr, "Adding content type failed\n"); 171 | PKCS7_free(p7); 172 | return NULL; /* FAILED */ 173 | } 174 | if (!cat_sign_content(p7, ctx->cat_ctx->p7->d.sign->contents)) { 175 | fprintf(stderr, "Failed to set signed content\n"); 176 | PKCS7_free(p7); 177 | return NULL; /* FAILED */ 178 | } 179 | return p7; /* OK */ 180 | } 181 | 182 | /* 183 | * Append signature to the outfile. 184 | * [in, out] ctx: structure holds input and output data 185 | * [out] outdata: outdata file BIO 186 | * [in] p7: PKCS#7 signature 187 | * [returns] 1 on error or 0 on success 188 | */ 189 | static int cat_append_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7) 190 | { 191 | return data_write_pkcs7(ctx, outdata, p7); 192 | } 193 | 194 | /* 195 | * Free up an entire message digest BIO chain. 196 | * [out] hash: message digest BIO 197 | * [out] outdata: outdata file BIO (unused) 198 | * [returns] none 199 | */ 200 | static void cat_bio_free(BIO *hash, BIO *outdata) 201 | { 202 | /* squash the unused parameter warning */ 203 | (void)outdata; 204 | BIO_free_all(hash); 205 | } 206 | 207 | /* 208 | * Deallocate a FILE_FORMAT_CTX structure and CAT format specific structure, 209 | * unmap indata file. 210 | * [in, out] ctx: structure holds all input and output data 211 | * [out] hash: message digest BIO 212 | * [in] outdata: outdata file BIO 213 | * [returns] none 214 | */ 215 | static void cat_ctx_cleanup(FILE_FORMAT_CTX *ctx) 216 | { 217 | unmap_file(ctx->options->indata, ctx->cat_ctx->fileend); 218 | PKCS7_free(ctx->cat_ctx->p7); 219 | OPENSSL_free(ctx->cat_ctx); 220 | OPENSSL_free(ctx); 221 | } 222 | 223 | /* 224 | * CAT helper functions 225 | */ 226 | 227 | /* 228 | * Verify mapped PKCS#7 (CAT) file and create CAT format specific structure. 229 | * [in] indata: mapped file 230 | * [in] filesize: size of file 231 | * [returns] pointer to CAT format specific structure 232 | */ 233 | static CAT_CTX *cat_ctx_get(char *indata, uint32_t filesize) 234 | { 235 | CAT_CTX *cat_ctx; 236 | PKCS7 *p7; 237 | 238 | p7 = pkcs7_read_data(indata, filesize); 239 | if (!p7) 240 | return NULL; /* FAILED */ 241 | if (!PKCS7_type_is_signed(p7)) { 242 | PKCS7_free(p7); 243 | return NULL; /* FAILED */ 244 | } 245 | cat_ctx = OPENSSL_zalloc(sizeof(CAT_CTX)); 246 | cat_ctx->p7 = p7; 247 | cat_ctx->sigpos = 0; 248 | cat_ctx->siglen = filesize; 249 | cat_ctx->fileend = filesize; 250 | return cat_ctx; /* OK */ 251 | } 252 | 253 | /* 254 | * Add a content type OID to the PKCS#7 signature structure. 255 | * The content type can be: 256 | * - "1.3.6.1.4.1.311.10.1" (MS_CTL_OBJID) for Certificate Trust Lists (CTL), 257 | * - "1.3.6.1.4.1.311.2.1.4" (SPC_INDIRECT_DATA_OBJID) for Authenticode data. 258 | * [in, out] p7: new PKCS#7 signature 259 | * [in] cursig: current PKCS#7 signature to determine content type 260 | * [returns] 0 on error or 1 on success 261 | */ 262 | static int cat_add_content_type(PKCS7 *p7, PKCS7 *cursig) 263 | { 264 | const char *content_type; 265 | STACK_OF(PKCS7_SIGNER_INFO) *signer_info; 266 | PKCS7_SIGNER_INFO *si; 267 | 268 | if (is_content_type(cursig, SPC_INDIRECT_DATA_OBJID)) { 269 | /* Authenticode content */ 270 | content_type = SPC_INDIRECT_DATA_OBJID; 271 | } else if (is_content_type(cursig, MS_CTL_OBJID)) { 272 | /* Certificate Trust List (CTL) */ 273 | content_type = MS_CTL_OBJID; 274 | } else { 275 | fprintf(stderr, "Unsupported content type\n"); 276 | return 0; /* FAILED */ 277 | } 278 | signer_info = PKCS7_get_signer_info(p7); 279 | if (!signer_info) 280 | return 0; /* FAILED */ 281 | si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0); 282 | if (!si) 283 | return 0; /* FAILED */ 284 | if (!PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, 285 | V_ASN1_OBJECT, OBJ_txt2obj(content_type, 1))) 286 | return 0; /* FAILED */ 287 | return 1; /* OK */ 288 | } 289 | 290 | /* 291 | * Sign the MS CTL blob. 292 | * Certificate Trust List (CTL) is a list of file names or thumbprints. 293 | * All the items in this list are authenticated (approved) by the signing entity. 294 | * [in, out] p7: new PKCS#7 signature 295 | * [in] contents: Certificate Trust List (CTL) 296 | * [returns] 0 on error or 1 on success 297 | */ 298 | static int cat_sign_content(PKCS7 *p7, PKCS7 *contents) 299 | { 300 | u_char *content; 301 | int seqhdrlen, content_length; 302 | 303 | if (!contents->d.other || !contents->d.other->value.sequence 304 | || !contents->d.other->value.sequence->data) { 305 | fprintf(stderr, "Failed to get content value\n"); 306 | return 0; /* FAILED */ 307 | } 308 | seqhdrlen = asn1_simple_hdr_len(contents->d.other->value.sequence->data, 309 | contents->d.other->value.sequence->length); 310 | content = contents->d.other->value.sequence->data + seqhdrlen; 311 | content_length = contents->d.other->value.sequence->length - seqhdrlen; 312 | 313 | if (!pkcs7_sign_content(p7, content, content_length)) { 314 | fprintf(stderr, "Failed to sign content\n"); 315 | return 0; /* FAILED */ 316 | } 317 | if (!PKCS7_set_content(p7, PKCS7_dup(contents))) { 318 | fprintf(stderr, "PKCS7_set_content failed\n"); 319 | return 0; /* FAILED */ 320 | } 321 | return 1; /* OK */ 322 | } 323 | 324 | /* 325 | * Print each member of the CAT file by using the "-verbose" option. 326 | * [in, out] p7: catalog file to verify 327 | * [returns] 1 on error or 0 on success 328 | */ 329 | static int cat_list_content(PKCS7 *p7) 330 | { 331 | MsCtlContent *ctlc; 332 | int i; 333 | 334 | ctlc = ms_ctl_content_get(p7); 335 | if (!ctlc) { 336 | fprintf(stderr, "Failed to extract MS_CTL_OBJID data\n"); 337 | return 1; /* FAILED */ 338 | } 339 | printf("\nCatalog members:\n"); 340 | for (i = 0; i < sk_CatalogInfo_num(ctlc->header_attributes); i++) { 341 | int j, found = 0; 342 | CatalogInfo *header_attr = sk_CatalogInfo_value(ctlc->header_attributes, i); 343 | if (header_attr == NULL) 344 | continue; 345 | for (j = 0; j < sk_CatalogAuthAttr_num(header_attr->attributes); j++) { 346 | char object_txt[128]; 347 | CatalogAuthAttr *attribute; 348 | ASN1_TYPE *content; 349 | 350 | attribute = sk_CatalogAuthAttr_value(header_attr->attributes, j); 351 | if (!attribute) 352 | continue; 353 | content = catalog_content_get(attribute); 354 | if (!content) 355 | continue; 356 | object_txt[0] = 0x00; 357 | OBJ_obj2txt(object_txt, sizeof object_txt, attribute->type, 1); 358 | if (!strcmp(object_txt, CAT_NAMEVALUE_OBJID)) { 359 | /* CAT_NAMEVALUE_OBJID OID: 1.3.6.1.4.1.311.12.2.1 */ 360 | found |= cat_print_content_member_name(content); 361 | } else if (!strcmp(object_txt, SPC_INDIRECT_DATA_OBJID)) { 362 | /* SPC_INDIRECT_DATA_OBJID OID: 1.3.6.1.4.1.311.2.1.4 */ 363 | found |= cat_print_content_member_digest(content); 364 | } 365 | ASN1_TYPE_free(content); 366 | } 367 | if (found) 368 | printf("\n"); 369 | } 370 | MsCtlContent_free(ctlc); 371 | ERR_print_errors_fp(stderr); 372 | return 0; /* OK */ 373 | } 374 | 375 | /* 376 | * Print a hash algorithm and a message digest from the SPC_INDIRECT_DATA_OBJID attribute. 377 | * [in] content: catalog file content 378 | * [returns] 0 on error or 1 on success 379 | */ 380 | static int cat_print_content_member_digest(ASN1_TYPE *content) 381 | { 382 | SpcIndirectDataContent *idc; 383 | u_char mdbuf[EVP_MAX_MD_SIZE]; 384 | const u_char *data ; 385 | int mdtype = -1; 386 | ASN1_STRING *value; 387 | 388 | value = content->value.sequence; 389 | data = ASN1_STRING_get0_data(value); 390 | idc = d2i_SpcIndirectDataContent(NULL, &data, ASN1_STRING_length(value)); 391 | if (!idc) 392 | return 0; /* FAILED */ 393 | if (idc->messageDigest && idc->messageDigest->digest && idc->messageDigest->digestAlgorithm) { 394 | /* get a digest algorithm a message digest of the file from the content */ 395 | mdtype = OBJ_obj2nid(idc->messageDigest->digestAlgorithm->algorithm); 396 | memcpy(mdbuf, idc->messageDigest->digest->data, (size_t)idc->messageDigest->digest->length); 397 | } 398 | SpcIndirectDataContent_free(idc); 399 | if (mdtype == -1) { 400 | fprintf(stderr, "Failed to extract current message digest\n\n"); 401 | return 0; /* FAILED */ 402 | } 403 | printf("\tHash algorithm: %s\n", OBJ_nid2sn(mdtype)); 404 | print_hash("\tMessage digest", "", mdbuf, EVP_MD_size(EVP_get_digestbynid(mdtype))); 405 | return 1; /* OK */ 406 | } 407 | 408 | /* 409 | * Print a file name from the CAT_NAMEVALUE_OBJID attribute. 410 | * [in] content: catalog file content 411 | * [returns] 0 on error or 1 on success 412 | */ 413 | static int cat_print_content_member_name(ASN1_TYPE *content) 414 | { 415 | CatNameValueContent *nvc; 416 | const u_char *data = NULL; 417 | ASN1_STRING *value; 418 | 419 | value = content->value.sequence; 420 | data = ASN1_STRING_get0_data(value); 421 | nvc = d2i_CatNameValueContent(NULL, &data, ASN1_STRING_length(value)); 422 | if (!nvc) { 423 | return 0; /* FAILED */ 424 | } 425 | printf("\tFile name: "); 426 | if (ASN1_INTEGER_get(nvc->flags) & 0x00020000) { 427 | cat_print_base64(nvc->value); 428 | } else { 429 | cat_print_utf16_as_ascii(nvc->value); 430 | } 431 | printf("\n"); 432 | CatNameValueContent_free(nvc); 433 | return 1; /* OK */ 434 | } 435 | 436 | /* 437 | * Print a CAT_NAMEVALUE_OBJID attribute represented in base-64 encoding. 438 | * [in] value: catalog member file name 439 | * [returns] none 440 | */ 441 | static void cat_print_base64(ASN1_OCTET_STRING *value) 442 | { 443 | BIO *stdbio, *b64; 444 | stdbio = BIO_new_fp(stdout, BIO_NOCLOSE); 445 | b64 = BIO_new(BIO_f_base64()); 446 | BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 447 | stdbio = BIO_push(b64, stdbio); 448 | ASN1_STRING_print_ex(stdbio, value, 0); 449 | BIO_free_all(stdbio); 450 | } 451 | 452 | /* 453 | * Print a CAT_NAMEVALUE_OBJID attribute represented in plaintext. 454 | * [in] value: catalog member file name 455 | * [returns] none 456 | */ 457 | static void cat_print_utf16_as_ascii(ASN1_OCTET_STRING *value) 458 | { 459 | const u_char *data; 460 | int len, i; 461 | 462 | data = ASN1_STRING_get0_data(value); 463 | len = ASN1_STRING_length(value); 464 | for (i = 0; i < len && (data[i] || data[i+1]); i+=2) 465 | putchar(isprint(data[i]) && !data[i+1] ? data[i] : '.'); 466 | } 467 | 468 | /* 469 | * Check if the signature exists. 470 | * [in, out] ctx: structure holds input and output data 471 | * [returns] 0 on error or 1 on success 472 | */ 473 | static int cat_check_file(FILE_FORMAT_CTX *ctx) 474 | { 475 | STACK_OF(PKCS7_SIGNER_INFO) *signer_info; 476 | PKCS7_SIGNER_INFO *si; 477 | 478 | if (!ctx) { 479 | fprintf(stderr, "Init error\n"); 480 | return 0; /* FAILED */ 481 | } 482 | signer_info = PKCS7_get_signer_info(ctx->cat_ctx->p7); 483 | if (!signer_info) { 484 | fprintf(stderr, "Failed catalog file\n"); 485 | return 0; /* FAILED */ 486 | } 487 | si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0); 488 | if (!si) { 489 | fprintf(stderr, "No signature found\n"); 490 | return 0; /* FAILED */ 491 | } 492 | if (ctx->options->verbose) { 493 | (void)cat_list_content(ctx->cat_ctx->p7); 494 | } 495 | return 1; /* OK */ 496 | } 497 | /* 498 | Local Variables: 499 | c-basic-offset: 4 500 | tab-width: 4 501 | indent-tabs-mode: nil 502 | End: 503 | 504 | vim: set ts=4 expandtab: 505 | */ 506 | -------------------------------------------------------------------------------- /cmake/CMakeDist.cmake: -------------------------------------------------------------------------------- 1 | # make dist 2 | # cmake --build . --target package_source 3 | 4 | set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) 5 | set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) 6 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "OpenSSL based Authenticode signing for PE, CAB, CAT and MSI files") 7 | set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) 8 | set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 9 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING.txt") 10 | set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") 11 | set(CPACK_SOURCE_GENERATOR "TGZ") 12 | set(CPACK_SOURCE_IGNORE_FILES "\.git/;\.gitignore") 13 | list(APPEND CPACK_SOURCE_IGNORE_FILES "Makefile") 14 | list(APPEND CPACK_SOURCE_IGNORE_FILES "CMakeCache.txt") 15 | list(APPEND CPACK_SOURCE_IGNORE_FILES "CMakeFiles") 16 | list(APPEND CPACK_SOURCE_IGNORE_FILES "CPackConfig.cmake") 17 | list(APPEND CPACK_SOURCE_IGNORE_FILES "CPackSourceConfig.cmake") 18 | list(APPEND CPACK_SOURCE_IGNORE_FILES "CTestTestfile.cmake") 19 | list(APPEND CPACK_SOURCE_IGNORE_FILES "cmake_install.cmake") 20 | list(APPEND CPACK_SOURCE_IGNORE_FILES "config.h") 21 | list(APPEND CPACK_SOURCE_IGNORE_FILES "/CMakeFiles/") 22 | list(APPEND CPACK_SOURCE_IGNORE_FILES "/Testing/") 23 | list(APPEND CPACK_SOURCE_IGNORE_FILES "/_CPack_Packages/") 24 | list(APPEND CPACK_SOURCE_IGNORE_FILES "/build/") 25 | 26 | include(CPack) 27 | add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source) 28 | 29 | #[[ 30 | Local Variables: 31 | c-basic-offset: 4 32 | tab-width: 4 33 | indent-tabs-mode: nil 34 | End: 35 | vim: set ts=4 expandtab: 36 | ]] 37 | -------------------------------------------------------------------------------- /cmake/FindHeaders.cmake: -------------------------------------------------------------------------------- 1 | include(CheckIncludeFile) 2 | include(CheckFunctionExists) 3 | 4 | if(UNIX) 5 | check_function_exists(getpass HAVE_GETPASS) 6 | check_include_file(termios.h HAVE_TERMIOS_H) 7 | check_include_file(sys/mman.h HAVE_SYS_MMAN_H) 8 | if(HAVE_SYS_MMAN_H) 9 | check_function_exists(mmap HAVE_MMAP) 10 | endif(HAVE_SYS_MMAN_H) 11 | else(UNIX) 12 | check_include_file(windows.h HAVE_MAPVIEWOFFILE) 13 | endif(UNIX) 14 | 15 | if(NOT (HAVE_MMAP OR HAVE_MAPVIEWOFFILE)) 16 | message(FATAL_ERROR "Error: Need file mapping function to build.") 17 | endif(NOT (HAVE_MMAP OR HAVE_MAPVIEWOFFILE)) 18 | 19 | #[[ 20 | Local Variables: 21 | c-basic-offset: 4 22 | tab-width: 4 23 | indent-tabs-mode: nil 24 | End: 25 | vim: set ts=4 expandtab: 26 | ]] 27 | -------------------------------------------------------------------------------- /cmake/SetBashCompletion.cmake: -------------------------------------------------------------------------------- 1 | # This list describes the default variables included in the bash-completion package: 2 | # BASH_COMPLETION_VERSION "@VERSION@" 3 | # BASH_COMPLETION_PREFIX "@prefix@" 4 | # BASH_COMPLETION_COMPATDIR "@sysconfdir@/bash_completion.d" 5 | # BASH_COMPLETION_COMPLETIONSDIR "@datadir@/@PACKAGE@/completions" 6 | # BASH_COMPLETION_HELPERSDIR "@datadir@/@PACKAGE@/helpers" 7 | # BASH_COMPLETION_FOUND "TRUE" 8 | # https://github.com/scop/bash-completion/blob/master/bash-completion-config.cmake.in 9 | 10 | if(NOT MSVC) 11 | if(BASH_COMPLETION_USER_DIR) 12 | set(BASH_COMPLETION_COMPLETIONSDIR "${BASH_COMPLETION_USER_DIR}/bash-completion/completions") 13 | else(BASH_COMPLETION_USER_DIR) 14 | find_package(bash-completion QUIET) 15 | if(NOT BASH_COMPLETION_FOUND) 16 | set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share") 17 | set(BASH_COMPLETION_COMPLETIONSDIR "${SHAREDIR}/bash-completion/completions") 18 | endif(NOT BASH_COMPLETION_FOUND) 19 | endif(BASH_COMPLETION_USER_DIR) 20 | 21 | message(STATUS "Using bash completions dir ${BASH_COMPLETION_COMPLETIONSDIR}") 22 | install(FILES "osslsigncode.bash" DESTINATION ${BASH_COMPLETION_COMPLETIONSDIR}) 23 | endif(NOT MSVC) 24 | 25 | #[[ 26 | Local Variables: 27 | c-basic-offset: 4 28 | tab-width: 4 29 | indent-tabs-mode: nil 30 | End: 31 | vim: set ts=4 expandtab: 32 | ]] 33 | -------------------------------------------------------------------------------- /cmake/SetCompilerFlags.cmake: -------------------------------------------------------------------------------- 1 | include(CheckCCompilerFlag) 2 | 3 | set(CMAKE_REQUIRED_QUIET ON) 4 | 5 | function(add_debug_flag_if_supported flagname targets) 6 | check_c_compiler_flag("${flagname}" HAVE_FLAG_${flagname}) 7 | if (HAVE_FLAG_${flagname}) 8 | foreach(target ${targets}) 9 | target_compile_options(${target} PRIVATE $<$:${flagname}>) 10 | endforeach(target ${targets}) 11 | endif(HAVE_FLAG_${flagname}) 12 | endfunction(add_debug_flag_if_supported flagname targets) 13 | 14 | function(add_compile_flag_to_targets targets) 15 | set(CHECKED_DEBUG_FLAGS 16 | "-ggdb" 17 | "-g" 18 | "-O2" 19 | "-pedantic" 20 | "-Wall" 21 | "-Wextra" 22 | "-Wno-long-long" 23 | "-Wconversion" 24 | "-D_FORTIFY_SOURCE=2" 25 | "-Wformat=2" 26 | "-Wredundant-decls" 27 | "-Wcast-qual" 28 | "-Wnull-dereference" 29 | "-Wno-deprecated-declarations" 30 | "-Wmissing-declarations" 31 | "-Wmissing-prototypes" 32 | "-Wmissing-noreturn" 33 | "-Wmissing-braces" 34 | "-Wparentheses" 35 | "-Wstrict-aliasing=3" 36 | "-Wstrict-overflow=2" 37 | "-Wlogical-op" 38 | "-Wwrite-strings" 39 | "-Wcast-align=strict" 40 | "-Wdisabled-optimization" 41 | "-Wshift-overflow=2" 42 | "-Wundef" 43 | "-Wshadow" 44 | "-Wmisleading-indentation" 45 | "-Wabsolute-value" 46 | "-Wunused-parameter" 47 | "-Wunused-function") 48 | foreach(flag ${CHECKED_DEBUG_FLAGS}) 49 | add_debug_flag_if_supported(${flag} ${targets}) 50 | endforeach(flag ${CHECKED_DEBUG_FLAGS}) 51 | endfunction(add_compile_flag_to_targets targets) 52 | 53 | function(add_compile_flags target) 54 | if(MSVC) 55 | # Enable parallel builds 56 | target_compile_options(${target} PRIVATE /MP) 57 | # Use address space layout randomization, generate PIE code for ASLR (default on) 58 | target_link_options(${target} PRIVATE /DYNAMICBASE) 59 | # Create terminal server aware application (default on) 60 | target_link_options(${target} PRIVATE /TSAWARE) 61 | # Mark the binary as compatible with Intel Control-flow Enforcement Technology (CET) Shadow Stack 62 | target_link_options(${target} PRIVATE /CETCOMPAT) 63 | # Enable compiler generation of Control Flow Guard security checks 64 | target_compile_options(${target} PRIVATE /guard:cf) 65 | target_link_options(${target} PRIVATE /guard:cf) 66 | # Buffer Security Check 67 | target_compile_options(${target} PRIVATE /GS) 68 | # Suppress startup banner 69 | target_link_options(${target} PRIVATE /NOLOGO) 70 | # Generate debug info 71 | target_link_options(${target} PRIVATE /DEBUG) 72 | if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") 73 | # High entropy ASLR for 64 bits targets (default on) 74 | target_link_options(${target} PRIVATE /HIGHENTROPYVA) 75 | # Enable generation of EH Continuation (EHCONT) metadata by the compiler 76 | #target_compile_options(${target} PRIVATE /guard:ehcont) 77 | #target_link_options(${target} PRIVATE /guard:ehcont) 78 | else("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") 79 | # Can handle addresses larger than 2 gigabytes 80 | target_link_options(${target} PRIVATE /LARGEADDRESSAWARE) 81 | # Safe structured exception handlers (x86 only) 82 | target_link_options(${target} PRIVATE /SAFESEH) 83 | endif("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") 84 | target_compile_options(${target} PRIVATE $<$:/D_FORTIFY_SOURCE=2>) 85 | # Unrecognized compiler options are errors 86 | target_compile_options(${target} PRIVATE $<$:/options:strict>) 87 | else(MSVC) 88 | check_c_compiler_flag("-fstack-protector-all" HAVE_STACK_PROTECTOR_ALL) 89 | if(HAVE_STACK_PROTECTOR_ALL) 90 | target_link_options(${target} PRIVATE -fstack-protector-all) 91 | else(HAVE_STACK_PROTECTOR_ALL) 92 | check_c_compiler_flag("-fstack-protector" HAVE_STACK_PROTECTOR) 93 | if(HAVE_STACK_PROTECTOR) 94 | target_link_options(${target} PRIVATE -fstack-protector) 95 | else(HAVE_STACK_PROTECTOR) 96 | message(WARNING "No stack protection supported") 97 | endif(HAVE_STACK_PROTECTOR) 98 | endif(HAVE_STACK_PROTECTOR_ALL) 99 | # Support address space layout randomization (ASLR) 100 | if(NOT (MINGW OR CYGWIN OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang" 101 | OR ((CMAKE_SYSTEM_NAME MATCHES Darwin) AND (CMAKE_C_COMPILER_ID MATCHES Clang)))) 102 | target_compile_options(${target} PRIVATE -fPIE) 103 | target_link_options(${target} PRIVATE -fPIE -pie) 104 | target_link_options(${target} PRIVATE -Wl,-z,relro) 105 | target_link_options(${target} PRIVATE -Wl,-z,now) 106 | target_link_options(${target} PRIVATE -Wl,-z,noexecstack) 107 | endif(NOT (MINGW OR CYGWIN OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang" 108 | OR ((CMAKE_SYSTEM_NAME MATCHES Darwin) AND (CMAKE_C_COMPILER_ID MATCHES Clang)))) 109 | target_link_options(${target} PRIVATE -fstack-check) 110 | add_compile_flag_to_targets(${target}) 111 | endif(MSVC) 112 | endfunction(add_compile_flags target) 113 | 114 | add_compile_flags(osslsigncode) 115 | 116 | #[[ 117 | Local Variables: 118 | c-basic-offset: 4 119 | tab-width: 4 120 | indent-tabs-mode: nil 121 | End: 122 | vim: set ts=4 expandtab: 123 | ]] 124 | -------------------------------------------------------------------------------- /get_code_signing_ca.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # © 2024 Michal Trojnara 3 | # This script downloads Microsoft code signing certificates 4 | # Tor is required for this script to work 5 | # Redirect the script output to a PEM file 6 | 7 | from sys import stderr 8 | from time import sleep 9 | from csv import reader 10 | from requests import get 11 | from requests.exceptions import RequestException 12 | from concurrent.futures import ThreadPoolExecutor 13 | 14 | def download_cert(hash): 15 | for attempt in range(10): 16 | if attempt > 0: 17 | sleep(10) 18 | try: 19 | creds = f'{attempt}{hash}:{attempt}{hash}' 20 | resp = get(f'https://crt.sh/?d={hash}', 21 | proxies=dict(https=f'socks5://{creds}@127.0.0.1:9050')) 22 | resp.raise_for_status() 23 | print('.', file=stderr, end='') 24 | stderr.flush() 25 | return resp.content.decode('utf-8') 26 | except RequestException as e: 27 | print(f'\nAttempt {attempt}: {e}', file=stderr) 28 | print('\nGiving up on', hash, file=stderr) 29 | 30 | resp = get('https://ccadb-public.secure.force.com/microsoft/IncludedCACertificateReportForMSFTCSV') 31 | resp.raise_for_status() 32 | lines = resp.content.decode('utf-8').splitlines()[1:] 33 | hashes = [row[4] for row in reader(lines) 34 | if row[0] != 'Disabled' 35 | or row[4] == 'F38406E540D7A9D90CB4A9479299640FFB6DF9E224ECC7A01C0D9558D8DAD77D'] 36 | with ThreadPoolExecutor(max_workers=20) as executor: 37 | certs = executor.map(download_cert, hashes) 38 | for cert in certs: 39 | if cert is not None: 40 | print(cert) 41 | print('\nDone', file=stderr) 42 | -------------------------------------------------------------------------------- /helpers.c: -------------------------------------------------------------------------------- 1 | /* 2 | * osslsigncode support library 3 | * 4 | * Copyright (C) 2021-2023 Michał Trojnara 5 | * Author: Małgorzata Olszówka 6 | */ 7 | 8 | #include "osslsigncode.h" 9 | #include "helpers.h" 10 | 11 | /* Prototypes */ 12 | static SpcSpOpusInfo *spc_sp_opus_info_create(FILE_FORMAT_CTX *ctx); 13 | static int spc_indirect_data_content_create(u_char **blob, int *len, FILE_FORMAT_CTX *ctx); 14 | static int pkcs7_signer_info_add_spc_sp_opus_info(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx); 15 | static int pkcs7_signer_info_add_signing_time(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx); 16 | static int pkcs7_signer_info_add_purpose(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx); 17 | static int pkcs7_signer_info_add_sequence_number(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx); 18 | static STACK_OF(X509) *X509_chain_get_sorted(FILE_FORMAT_CTX *ctx, int signer); 19 | static int X509_compare(const X509 *const *a, const X509 *const *b); 20 | 21 | /* 22 | * Common functions 23 | */ 24 | 25 | /* 26 | * [in] infile 27 | * [returns] file size 28 | */ 29 | uint32_t get_file_size(const char *infile) 30 | { 31 | int ret; 32 | #ifdef _WIN32 33 | struct _stat64 st; 34 | ret = _stat64(infile, &st); 35 | #else 36 | struct stat st; 37 | ret = stat(infile, &st); 38 | #endif 39 | if (ret) { 40 | fprintf(stderr, "Failed to open file: %s\n", infile); 41 | return 0; 42 | } 43 | 44 | if (st.st_size < 4) { 45 | fprintf(stderr, "Unrecognized file type - file is too short: %s\n", infile); 46 | return 0; 47 | } 48 | if (st.st_size > UINT32_MAX) { 49 | fprintf(stderr, "Unsupported file - too large: %s\n", infile); 50 | return 0; 51 | } 52 | return (uint32_t)st.st_size; 53 | } 54 | 55 | /* 56 | * [in] infile: starting address for the new mapping 57 | * [returns] pointer to the mapped area 58 | */ 59 | char *map_file(const char *infile, const size_t size) 60 | { 61 | char *indata = NULL; 62 | #ifdef WIN32 63 | HANDLE fhandle, fmap; 64 | (void)size; 65 | fhandle = CreateFile(infile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 66 | if (fhandle == INVALID_HANDLE_VALUE) { 67 | return NULL; 68 | } 69 | fmap = CreateFileMapping(fhandle, NULL, PAGE_READONLY, 0, 0, NULL); 70 | CloseHandle(fhandle); 71 | if (fmap == NULL) { 72 | return NULL; 73 | } 74 | indata = (char *)MapViewOfFile(fmap, FILE_MAP_READ, 0, 0, 0); 75 | CloseHandle(fmap); 76 | #else 77 | #ifdef HAVE_SYS_MMAN_H 78 | int fd = open(infile, O_RDONLY); 79 | if (fd < 0) { 80 | return NULL; 81 | } 82 | indata = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0); 83 | if (indata == MAP_FAILED) { 84 | close(fd); 85 | return NULL; 86 | } 87 | close(fd); 88 | #else 89 | fprintf(stderr, "No file mapping function\n"); 90 | return NULL; 91 | #endif /* HAVE_SYS_MMAN_H */ 92 | #endif /* WIN32 */ 93 | return indata; 94 | } 95 | 96 | /* 97 | * [in] indata: starting address space 98 | * [in] size: mapped area length 99 | * [returns] none 100 | */ 101 | void unmap_file(char *indata, const size_t size) 102 | { 103 | if (!indata) 104 | return; 105 | #ifdef WIN32 106 | (void)size; 107 | UnmapViewOfFile(indata); 108 | #else 109 | munmap(indata, size); 110 | #endif /* WIN32 */ 111 | } 112 | 113 | /* 114 | * Retrieve a decoded PKCS#7 structure 115 | * [in] data: encoded PEM or DER data 116 | * [in] size: data size 117 | * [returns] pointer to PKCS#7 structure 118 | */ 119 | PKCS7 *pkcs7_read_data(char *data, uint32_t size) 120 | { 121 | PKCS7 *p7 = NULL; 122 | BIO *bio; 123 | const char pemhdr[] = "-----BEGIN PKCS7-----"; 124 | 125 | bio = BIO_new_mem_buf(data, (int)size); 126 | if (size >= sizeof pemhdr && !memcmp(data, pemhdr, sizeof pemhdr - 1)) { 127 | /* PEM format */ 128 | p7 = PEM_read_bio_PKCS7(bio, NULL, NULL, NULL); 129 | } else { /* DER format */ 130 | p7 = d2i_PKCS7_bio(bio, NULL); 131 | } 132 | BIO_free_all(bio); 133 | return p7; 134 | } 135 | 136 | /* 137 | * [in, out] ctx: structure holds input and output data 138 | * [out] outdata: BIO outdata file 139 | * [in] p7: PKCS#7 signature 140 | * [returns] 1 on error or 0 on success 141 | */ 142 | int data_write_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7) 143 | { 144 | int ret; 145 | 146 | (void)BIO_reset(outdata); 147 | if (ctx->options->output_pkcs7) { 148 | /* PEM format */ 149 | ret = !PEM_write_bio_PKCS7(outdata, p7); 150 | } else { 151 | /* default DER format */ 152 | ret = !i2d_PKCS7_bio(outdata, p7); 153 | } 154 | if (ret) { 155 | fprintf(stderr, "Unable to write pkcs7 object\n"); 156 | } 157 | return ret; 158 | } 159 | 160 | /* 161 | * Allocate, set type, add content and return a new PKCS#7 signature 162 | * [in] ctx: structure holds input and output data 163 | * [returns] pointer to PKCS#7 structure 164 | */ 165 | PKCS7 *pkcs7_create(FILE_FORMAT_CTX *ctx) 166 | { 167 | int i, signer = -1; 168 | PKCS7_SIGNER_INFO *si = NULL; 169 | STACK_OF(X509) *chain = NULL; 170 | PKCS7 *p7 = PKCS7_new(); 171 | 172 | if (!p7) 173 | return NULL; 174 | 175 | PKCS7_set_type(p7, NID_pkcs7_signed); 176 | PKCS7_content_new(p7, NID_pkcs7_data); 177 | 178 | /* find the signer's certificate located somewhere in the whole certificate chain */ 179 | for (i=0; ioptions->certs); i++) { 180 | X509 *signcert = sk_X509_value(ctx->options->certs, i); 181 | 182 | if (X509_check_private_key(signcert, ctx->options->pkey)) { 183 | si = PKCS7_add_signature(p7, signcert, ctx->options->pkey, ctx->options->md); 184 | signer = i; 185 | if (signer > 0) 186 | printf("Warning: For optimal performance, consider placing the signer certificate at the beginning of the certificate chain.\n"); 187 | break; 188 | } 189 | } 190 | if (!si) { 191 | fprintf(stderr, "Failed to checking the consistency of a private key: %s\n", 192 | ctx->options->keyfile); 193 | fprintf(stderr, " with a public key in any X509 certificate: %s\n\n", 194 | #if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L 195 | ctx->options->certfile ? ctx->options->certfile : ctx->options->p11cert); 196 | #else 197 | ctx->options->certfile); 198 | #endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */ 199 | goto err; 200 | } 201 | 202 | if (!pkcs7_signer_info_add_signing_time(si, ctx)) { 203 | goto err; 204 | } 205 | if (!pkcs7_signer_info_add_purpose(si, ctx)) { 206 | goto err; 207 | } 208 | if ((ctx->options->desc || ctx->options->url) && 209 | !pkcs7_signer_info_add_spc_sp_opus_info(si, ctx)) { 210 | fprintf(stderr, "Couldn't allocate memory for opus info\n"); 211 | goto err; 212 | } 213 | if ((ctx->options->nested_number >= 0) && 214 | !pkcs7_signer_info_add_sequence_number(si, ctx)) { 215 | goto err; 216 | } 217 | /* create X509 chain sorted in ascending order by their DER encoding */ 218 | chain = X509_chain_get_sorted(ctx, signer); 219 | if (!chain) { 220 | fprintf(stderr, "Failed to create a sorted certificate chain\n"); 221 | goto err; 222 | } 223 | /* add sorted certificate chain */ 224 | for (i=0; ioptions->crls) { 229 | for (i=0; ioptions->crls); i++) 230 | (void)PKCS7_add_crl(p7, sk_X509_CRL_value(ctx->options->crls, i)); 231 | } 232 | sk_X509_free(chain); 233 | return p7; /* OK */ 234 | 235 | err: 236 | PKCS7_free(p7); 237 | return NULL; /* FAILED */ 238 | } 239 | 240 | /* 241 | * PE, MSI, CAB and APPX file specific 242 | * Add "1.3.6.1.4.1.311.2.1.4" SPC_INDIRECT_DATA_OBJID signed attribute 243 | * [in, out] p7: new PKCS#7 signature 244 | * [returns] 0 on error or 1 on success 245 | */ 246 | int add_indirect_data_object(PKCS7 *p7) 247 | { 248 | STACK_OF(PKCS7_SIGNER_INFO) *signer_info; 249 | PKCS7_SIGNER_INFO *si; 250 | 251 | signer_info = PKCS7_get_signer_info(p7); 252 | if (!signer_info) 253 | return 0; /* FAILED */ 254 | si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0); 255 | if (!si) 256 | return 0; /* FAILED */ 257 | if (!PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, 258 | V_ASN1_OBJECT, OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1))) 259 | return 0; /* FAILED */ 260 | return 1; /* OK */ 261 | } 262 | 263 | /* 264 | * PE, MSI, CAB and APPX format specific 265 | * Sign the MS Authenticode spcIndirectDataContent blob. 266 | * The spcIndirectDataContent structure is used in Authenticode signatures 267 | * to store the digest and other attributes of the signed file. 268 | * [in, out] p7: new PKCS#7 signature 269 | * [in] content: spcIndirectDataContent 270 | * [returns] 0 on error or 1 on success 271 | */ 272 | int sign_spc_indirect_data_content(PKCS7 *p7, ASN1_OCTET_STRING *content) 273 | { 274 | int len, inf, tag, class; 275 | long plen; 276 | const u_char *data, *p; 277 | PKCS7 *td7; 278 | 279 | p = data = ASN1_STRING_get0_data(content); 280 | len = ASN1_STRING_length(content); 281 | inf = ASN1_get_object(&p, &plen, &tag, &class, len); 282 | if (inf != V_ASN1_CONSTRUCTED || tag != V_ASN1_SEQUENCE 283 | || !pkcs7_sign_content(p7, p, (int)plen)) { 284 | fprintf(stderr, "Failed to sign spcIndirectDataContent\n"); 285 | return 0; /* FAILED */ 286 | } 287 | td7 = PKCS7_new(); 288 | if (!td7) { 289 | fprintf(stderr, "PKCS7_new failed\n"); 290 | return 0; /* FAILED */ 291 | } 292 | td7->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1); 293 | td7->d.other = ASN1_TYPE_new(); 294 | td7->d.other->type = V_ASN1_SEQUENCE; 295 | td7->d.other->value.sequence = ASN1_STRING_new(); 296 | ASN1_STRING_set(td7->d.other->value.sequence, data, len); 297 | if (!PKCS7_set_content(p7, td7)) { 298 | fprintf(stderr, "PKCS7_set_content failed\n"); 299 | PKCS7_free(td7); 300 | return 0; /* FAILED */ 301 | } 302 | return 1; /* OK */ 303 | } 304 | 305 | /* 306 | * Add encapsulated content to signed PKCS7 structure. 307 | * [in] content: spcIndirectDataContent 308 | * [returns] new PKCS#7 signature with encapsulated content 309 | */ 310 | PKCS7 *pkcs7_set_content(ASN1_OCTET_STRING *content) 311 | { 312 | PKCS7 *p7, *td7; 313 | 314 | p7 = PKCS7_new(); 315 | if (!p7) { 316 | return NULL; /* FAILED */ 317 | } 318 | if (!PKCS7_set_type(p7, NID_pkcs7_signed)) { 319 | PKCS7_free(p7); 320 | return NULL; /* FAILED */ 321 | } 322 | if (!PKCS7_content_new(p7, NID_pkcs7_data)) { 323 | PKCS7_free(p7); 324 | return NULL; /* FAILED */ 325 | } 326 | td7 = PKCS7_new(); 327 | if (!td7) { 328 | PKCS7_free(p7); 329 | return NULL; /* FAILED */ 330 | } 331 | td7->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1); 332 | td7->d.other = ASN1_TYPE_new(); 333 | td7->d.other->type = V_ASN1_SEQUENCE; 334 | td7->d.other->value.sequence = content; 335 | if (!PKCS7_set_content(p7, td7)) { 336 | PKCS7_free(td7); 337 | PKCS7_free(p7); 338 | return NULL; /* FAILED */ 339 | } 340 | return p7; 341 | } 342 | 343 | /* 344 | * Return spcIndirectDataContent. 345 | * [in] hash: message digest BIO 346 | * [in] ctx: structure holds input and output data 347 | * [returns] content 348 | */ 349 | ASN1_OCTET_STRING *spc_indirect_data_content_get(BIO *hash, FILE_FORMAT_CTX *ctx) 350 | { 351 | ASN1_OCTET_STRING *content; 352 | u_char mdbuf[5 * EVP_MAX_MD_SIZE + 24]; 353 | int mdlen, hashlen, len = 0; 354 | u_char *data, *p = NULL; 355 | 356 | content = ASN1_OCTET_STRING_new(); 357 | if (!content) { 358 | return NULL; /* FAILED */ 359 | } 360 | if (!spc_indirect_data_content_create(&p, &len, ctx)) { 361 | ASN1_OCTET_STRING_free(content); 362 | return NULL; /* FAILED */ 363 | } 364 | hashlen = ctx->format->hash_length_get(ctx); 365 | if (hashlen > EVP_MAX_MD_SIZE) { 366 | /* APPX format specific */ 367 | mdlen = BIO_read(hash, (char*)mdbuf, hashlen); 368 | } else { 369 | mdlen = BIO_gets(hash, (char*)mdbuf, EVP_MAX_MD_SIZE); 370 | } 371 | data = OPENSSL_malloc((size_t)(len + mdlen)); 372 | memcpy(data, p, (size_t)len); 373 | OPENSSL_free(p); 374 | memcpy(data + len, mdbuf, (size_t)mdlen); 375 | if (!ASN1_OCTET_STRING_set(content, data, len + mdlen)) { 376 | ASN1_OCTET_STRING_free(content); 377 | OPENSSL_free(data); 378 | return NULL; /* FAILED */ 379 | } 380 | OPENSSL_free(data); 381 | return content; 382 | } 383 | 384 | /* 385 | * Signs the data and place the signature in p7 386 | * [in, out] p7: new PKCS#7 signature 387 | * [in] data: content data 388 | * [in] len: content length 389 | */ 390 | int pkcs7_sign_content(PKCS7 *p7, const u_char *data, int len) 391 | { 392 | BIO *p7bio; 393 | 394 | if ((p7bio = PKCS7_dataInit(p7, NULL)) == NULL) { 395 | fprintf(stderr, "PKCS7_dataInit failed\n"); 396 | return 0; /* FAILED */ 397 | } 398 | BIO_write(p7bio, data, len); 399 | (void)BIO_flush(p7bio); 400 | if (!PKCS7_dataFinal(p7, p7bio)) { 401 | fprintf(stderr, "PKCS7_dataFinal failed\n"); 402 | BIO_free_all(p7bio); 403 | return 0; /* FAILED */ 404 | } 405 | BIO_free_all(p7bio); 406 | return 1; /* OK */ 407 | } 408 | 409 | /* Return the header length (tag and length octets) of the ASN.1 type 410 | * [in] p: ASN.1 data 411 | * [in] len: ASN.1 data length 412 | * [returns] header length 413 | */ 414 | int asn1_simple_hdr_len(const u_char *p, int len) 415 | { 416 | if (len <= 2 || p[0] > 0x31) 417 | return 0; 418 | return (p[1]&0x80) ? (2 + (p[1]&0x7f)) : 2; 419 | } 420 | 421 | /* 422 | * [in, out] hash: BIO with message digest method 423 | * [in] indata: starting address space 424 | * [in] idx: offset 425 | * [in] fileend: the length of the hashed area 426 | * [returns] 0 on error or 1 on success 427 | */ 428 | int bio_hash_data(BIO *hash, char *indata, size_t idx, size_t fileend) 429 | { 430 | while (idx < fileend) { 431 | size_t want, written; 432 | want = fileend - idx; 433 | if (want > SIZE_64K) 434 | want = SIZE_64K; 435 | if (!BIO_write_ex(hash, indata + idx, want, &written)) 436 | return 0; /* FAILED */ 437 | idx += written; 438 | } 439 | return 1; /* OK */ 440 | } 441 | 442 | /* 443 | * [in] descript1, descript2: descriptions 444 | * [in] mdbuf: message digest 445 | * [in] len: message digest length 446 | * [returns] none 447 | */ 448 | void print_hash(const char *descript1, const char *descript2, const u_char *mdbuf, int len) 449 | { 450 | char *hexbuf = NULL; 451 | int size, i, j = 0; 452 | 453 | size = 2 * len + 1; 454 | hexbuf = OPENSSL_malloc((size_t)size); 455 | for (i = 0; i < len; i++) { 456 | #ifdef WIN32 457 | j += sprintf_s(hexbuf + j, size - j, "%02X", mdbuf[i]); 458 | #else 459 | j += sprintf(hexbuf + j, "%02X", mdbuf[i]); 460 | #endif /* WIN32 */ 461 | } 462 | printf("%s: %s %s\n", descript1, hexbuf, descript2); 463 | OPENSSL_free(hexbuf); 464 | } 465 | 466 | /* 467 | * [in] p7: PKCS#7 signature 468 | * [in] objid: Microsoft OID Authenticode 469 | * [returns] 0 on error or 1 on success 470 | */ 471 | int is_content_type(PKCS7 *p7, const char *objid) 472 | { 473 | ASN1_OBJECT *indir_objid; 474 | int ret; 475 | 476 | indir_objid = OBJ_txt2obj(objid, 1); 477 | ret = p7 && PKCS7_type_is_signed(p7) && 478 | !OBJ_cmp(p7->d.sign->contents->type, indir_objid) && 479 | (p7->d.sign->contents->d.other->type == V_ASN1_SEQUENCE || 480 | p7->d.sign->contents->d.other->type == V_ASN1_OCTET_STRING); 481 | ASN1_OBJECT_free(indir_objid); 482 | return ret; 483 | } 484 | 485 | /* 486 | * [in] p7: new PKCS#7 signature 487 | * [returns] pointer to MsCtlContent structure 488 | */ 489 | MsCtlContent *ms_ctl_content_get(PKCS7 *p7) 490 | { 491 | ASN1_STRING *value; 492 | const u_char *data; 493 | 494 | if (!is_content_type(p7, MS_CTL_OBJID)) { 495 | fprintf(stderr, "Failed to find MS_CTL_OBJID\n"); 496 | return NULL; /* FAILED */ 497 | } 498 | value = p7->d.sign->contents->d.other->value.sequence; 499 | data = ASN1_STRING_get0_data(value); 500 | return d2i_MsCtlContent(NULL, &data, ASN1_STRING_length(value)); 501 | } 502 | 503 | /* 504 | * [in] attribute: catalog attribute 505 | * [returns] catalog content 506 | */ 507 | ASN1_TYPE *catalog_content_get(CatalogAuthAttr *attribute) 508 | { 509 | ASN1_STRING *value; 510 | STACK_OF(ASN1_TYPE) *contents; 511 | ASN1_TYPE *content; 512 | const u_char *contents_data; 513 | 514 | value = attribute->contents->value.sequence; 515 | contents_data = ASN1_STRING_get0_data(value); 516 | contents = d2i_ASN1_SET_ANY(NULL, &contents_data, ASN1_STRING_length(value)); 517 | if (!contents) 518 | return 0; /* FAILED */ 519 | content = sk_ASN1_TYPE_value(contents, 0); 520 | sk_ASN1_TYPE_free(contents); 521 | return content; 522 | } 523 | 524 | /* 525 | * PE and CAB format specific 526 | * [in] none 527 | * [returns] pointer to SpcLink 528 | */ 529 | SpcLink *spc_link_obsolete_get(void) 530 | { 531 | const u_char obsolete[] = { 532 | 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x4f, 533 | 0x00, 0x62, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c, 534 | 0x00, 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x3e, 535 | 0x00, 0x3e, 0x00, 0x3e 536 | }; 537 | SpcLink *link = SpcLink_new(); 538 | link->type = 2; 539 | link->value.file = SpcString_new(); 540 | link->value.file->type = 0; 541 | link->value.file->value.unicode = ASN1_BMPSTRING_new(); 542 | ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof obsolete); 543 | return link; 544 | } 545 | 546 | /* 547 | * [in] mdbuf, cmdbuf: message digests 548 | * [in] mdtype: message digest algorithm type 549 | * [returns] 0 on error or 1 on success 550 | */ 551 | int compare_digests(u_char *mdbuf, u_char *cmdbuf, int mdtype) 552 | { 553 | int mdlen = EVP_MD_size(EVP_get_digestbynid(mdtype)); 554 | int mdok = !memcmp(mdbuf, cmdbuf, (size_t)mdlen); 555 | printf("Message digest algorithm : %s\n", OBJ_nid2sn(mdtype)); 556 | print_hash("Current message digest ", "", mdbuf, mdlen); 557 | print_hash("Calculated message digest ", mdok ? "\n" : " MISMATCH!!!\n", cmdbuf, mdlen); 558 | return mdok; 559 | } 560 | 561 | /* 562 | * Helper functions 563 | */ 564 | 565 | /* 566 | * [in] ctx: FILE_FORMAT_CTX structure 567 | * [returns] pointer to SpcSpOpusInfo structure 568 | */ 569 | static SpcSpOpusInfo *spc_sp_opus_info_create(FILE_FORMAT_CTX *ctx) 570 | { 571 | SpcSpOpusInfo *info = SpcSpOpusInfo_new(); 572 | 573 | if (ctx->options->desc) { 574 | info->programName = SpcString_new(); 575 | info->programName->type = 1; 576 | info->programName->value.ascii = ASN1_IA5STRING_new(); 577 | ASN1_STRING_set((ASN1_STRING *)info->programName->value.ascii, 578 | ctx->options->desc, (int)strlen(ctx->options->desc)); 579 | } 580 | if (ctx->options->url) { 581 | info->moreInfo = SpcLink_new(); 582 | info->moreInfo->type = 0; 583 | info->moreInfo->value.url = ASN1_IA5STRING_new(); 584 | ASN1_STRING_set((ASN1_STRING *)info->moreInfo->value.url, 585 | ctx->options->url, (int)strlen(ctx->options->url)); 586 | } 587 | return info; 588 | } 589 | 590 | /* 591 | * [out] blob: SpcIndirectDataContent data 592 | * [out] len: SpcIndirectDataContent data length 593 | * [in] ctx: FILE_FORMAT_CTX structure 594 | * [returns] 0 on error or 1 on success 595 | */ 596 | static int spc_indirect_data_content_create(u_char **blob, int *len, FILE_FORMAT_CTX *ctx) 597 | { 598 | u_char *p = NULL; 599 | int mdtype, hashlen, l = 0; 600 | void *hash; 601 | SpcIndirectDataContent *idc = SpcIndirectDataContent_new(); 602 | 603 | if (!ctx->format->data_blob_get || !ctx->format->hash_length_get) { 604 | return 0; /* FAILED */ 605 | } 606 | if (ctx->format->md_get) { 607 | /* APPX file specific - use a hash algorithm specified in the AppxBlockMap.xml file */ 608 | mdtype = EVP_MD_nid(ctx->format->md_get(ctx)); 609 | } else { 610 | mdtype = EVP_MD_nid(ctx->options->md); 611 | } 612 | idc->data->value = ASN1_TYPE_new(); 613 | idc->data->value->type = V_ASN1_SEQUENCE; 614 | idc->data->value->value.sequence = ASN1_STRING_new(); 615 | idc->data->type = ctx->format->data_blob_get(&p, &l, ctx); 616 | idc->data->value->value.sequence->data = p; 617 | idc->data->value->value.sequence->length = l; 618 | idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(mdtype); 619 | idc->messageDigest->digestAlgorithm->parameters = ASN1_TYPE_new(); 620 | idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL; 621 | 622 | hashlen = ctx->format->hash_length_get(ctx); 623 | hash = OPENSSL_zalloc((size_t)hashlen); 624 | ASN1_OCTET_STRING_set(idc->messageDigest->digest, hash, hashlen); 625 | OPENSSL_free(hash); 626 | 627 | *len = i2d_SpcIndirectDataContent(idc, NULL); 628 | *blob = OPENSSL_malloc((size_t)*len); 629 | p = *blob; 630 | i2d_SpcIndirectDataContent(idc, &p); 631 | SpcIndirectDataContent_free(idc); 632 | *len -= hashlen; 633 | return 1; /* OK */ 634 | } 635 | 636 | /* 637 | * [in, out] si: PKCS7_SIGNER_INFO structure 638 | * [in] ctx: FILE_FORMAT_CTX structure 639 | * [returns] 0 on error or 1 on success 640 | */ 641 | static int pkcs7_signer_info_add_spc_sp_opus_info(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx) 642 | { 643 | SpcSpOpusInfo *opus; 644 | ASN1_STRING *astr; 645 | int len; 646 | u_char *p = NULL; 647 | 648 | opus = spc_sp_opus_info_create(ctx); 649 | if ((len = i2d_SpcSpOpusInfo(opus, NULL)) <= 0 650 | || (p = OPENSSL_malloc((size_t)len)) == NULL) { 651 | SpcSpOpusInfo_free(opus); 652 | return 0; /* FAILED */ 653 | } 654 | i2d_SpcSpOpusInfo(opus, &p); 655 | p -= len; 656 | astr = ASN1_STRING_new(); 657 | ASN1_STRING_set(astr, p, len); 658 | OPENSSL_free(p); 659 | SpcSpOpusInfo_free(opus); 660 | return PKCS7_add_signed_attribute(si, OBJ_txt2nid(SPC_SP_OPUS_INFO_OBJID), 661 | V_ASN1_SEQUENCE, astr); 662 | } 663 | 664 | /* 665 | * Add a custom, non-trusted time to the PKCS7 structure to prevent OpenSSL 666 | * adding the _current_ time. This allows to create a deterministic signature 667 | * when no trusted timestamp server was specified, making osslsigncode 668 | * behaviour closer to signtool.exe (which doesn't include any non-trusted 669 | * time in this case.) 670 | * [in, out] si: PKCS7_SIGNER_INFO structure 671 | * [in] ctx: structure holds input and output data 672 | * [returns] 0 on error or 1 on success 673 | */ 674 | static int pkcs7_signer_info_add_signing_time(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx) 675 | { 676 | if (ctx->options->time == INVALID_TIME) /* -time option was not specified */ 677 | return 1; /* SUCCESS */ 678 | return PKCS7_add_signed_attribute(si, NID_pkcs9_signingTime, V_ASN1_UTCTIME, 679 | ASN1_TIME_adj(NULL, ctx->options->time, 0, 0)); 680 | } 681 | 682 | /* 683 | * [in, out] si: PKCS7_SIGNER_INFO structure 684 | * [in] ctx: structure holds input and output data 685 | * [returns] 0 on error or 1 on success 686 | */ 687 | static int pkcs7_signer_info_add_purpose(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx) 688 | { 689 | static const u_char purpose_ind[] = { 690 | 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 691 | 0x01, 0x82, 0x37, 0x02, 0x01, 0x15 692 | }; 693 | static const u_char purpose_comm[] = { 694 | 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 695 | 0x01, 0x82, 0x37, 0x02, 0x01, 0x16 696 | }; 697 | ASN1_STRING *purpose = ASN1_STRING_new(); 698 | 699 | if (ctx->options->comm) { 700 | ASN1_STRING_set(purpose, purpose_comm, sizeof purpose_comm); 701 | } else { 702 | ASN1_STRING_set(purpose, purpose_ind, sizeof purpose_ind); 703 | } 704 | return PKCS7_add_signed_attribute(si, OBJ_txt2nid(SPC_STATEMENT_TYPE_OBJID), 705 | V_ASN1_SEQUENCE, purpose); 706 | } 707 | 708 | /* 709 | * [in, out] si: PKCS7_SIGNER_INFO structure 710 | * [in] ctx: structure holds input and output data 711 | * [returns] 0 on error or 1 on success 712 | */ 713 | static int pkcs7_signer_info_add_sequence_number(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx) 714 | { 715 | ASN1_INTEGER *number = ASN1_INTEGER_new(); 716 | 717 | if (!number) 718 | return 0; /* FAILED */ 719 | if (!ASN1_INTEGER_set(number, ctx->options->nested_number + 1)) { 720 | ASN1_INTEGER_free(number); 721 | return 0; /* FAILED */ 722 | } 723 | return PKCS7_add_signed_attribute(si, OBJ_txt2nid(PKCS9_SEQUENCE_NUMBER), 724 | V_ASN1_INTEGER, number); 725 | } 726 | 727 | /* 728 | * Create certificate chain sorted in ascending order by their DER encoding. 729 | * [in] ctx: structure holds input and output data 730 | * [in] signer: signer's certificate number in the certificate chain 731 | * [returns] sorted certificate chain 732 | */ 733 | static STACK_OF(X509) *X509_chain_get_sorted(FILE_FORMAT_CTX *ctx, int signer) 734 | { 735 | int i; 736 | STACK_OF(X509) *chain = sk_X509_new(X509_compare); 737 | 738 | if (signer != -1 && !sk_X509_push(chain, sk_X509_value(ctx->options->certs, signer))) { 739 | sk_X509_free(chain); 740 | return NULL; 741 | } 742 | /* add the certificate chain */ 743 | for (i=0; ioptions->certs); i++) { 744 | if (i == signer) 745 | continue; 746 | if (!sk_X509_push(chain, sk_X509_value(ctx->options->certs, i))) { 747 | sk_X509_free(chain); 748 | return NULL; 749 | } 750 | } 751 | /* add all cross certificates */ 752 | if (ctx->options->xcerts) { 753 | for (i=0; ioptions->xcerts); i++) { 754 | if (!sk_X509_push(chain, sk_X509_value(ctx->options->xcerts, i))) { 755 | sk_X509_free(chain); 756 | return NULL; 757 | } 758 | } 759 | } 760 | /* sort certificate chain using the supplied comparison function */ 761 | sk_X509_sort(chain); 762 | return chain; 763 | } 764 | 765 | /* 766 | * X.690-compliant certificate comparison function 767 | * Windows requires catalog files to use PKCS#7 768 | * content ordering specified in X.690 section 11.6 769 | * https://support.microsoft.com/en-us/topic/october-13-2020-kb4580358-security-only-update-d3f6eb3c-d7c4-a9cb-0de6-759386bf7113 770 | * This algorithm is different from X509_cmp() 771 | * [in] a_ptr, b_ptr: pointers to X509 certificates 772 | * [returns] certificates order 773 | */ 774 | static int X509_compare(const X509 *const *a, const X509 *const *b) 775 | { 776 | u_char *a_data, *b_data, *a_tmp, *b_tmp; 777 | size_t a_len, b_len; 778 | int ret; 779 | 780 | #if OPENSSL_VERSION_NUMBER<0x30000000L 781 | #if defined(__clang__) 782 | #pragma clang diagnostic push 783 | #pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers" 784 | #elif defined(__GNUC__) 785 | #pragma GCC diagnostic push 786 | #pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" 787 | #endif 788 | #endif /* OPENSSL_VERSION_NUMBER<0x30000000L */ 789 | a_len = (size_t)i2d_X509(*a, NULL); 790 | a_tmp = a_data = OPENSSL_malloc(a_len); 791 | i2d_X509(*a, &a_tmp); 792 | 793 | b_len = (size_t)i2d_X509(*b, NULL); 794 | b_tmp = b_data = OPENSSL_malloc(b_len); 795 | i2d_X509(*b, &b_tmp); 796 | #if OPENSSL_VERSION_NUMBER<0x30000000L 797 | #if defined(__clang__) 798 | #pragma clang diagnostic pop 799 | #elif defined(__GNUC__) 800 | #pragma GCC diagnostic pop 801 | #endif 802 | #endif /* OPENSSL_VERSION_NUMBER<0x30000000L */ 803 | 804 | ret = memcmp(a_data, b_data, MIN(a_len, b_len)); 805 | OPENSSL_free(a_data); 806 | OPENSSL_free(b_data); 807 | 808 | if (ret == 0 && a_len != b_len) /* identical up to the length of the shorter DER */ 809 | ret = a_len < b_len ? -1 : 1; /* shorter is smaller */ 810 | return ret; 811 | } 812 | 813 | /* 814 | Local Variables: 815 | c-basic-offset: 4 816 | tab-width: 4 817 | indent-tabs-mode: nil 818 | End: 819 | 820 | vim: set ts=4 expandtab: 821 | */ 822 | -------------------------------------------------------------------------------- /helpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * osslsigncode support library 3 | * 4 | * Copyright (C) 2021-2023 Michał Trojnara 5 | * Author: Małgorzata Olszówka 6 | */ 7 | 8 | /* Common functions */ 9 | uint32_t get_file_size(const char *infile); 10 | char *map_file(const char *infile, const size_t size); 11 | void unmap_file(char *indata, const size_t size); 12 | PKCS7 *pkcs7_read_data(char *indata, uint32_t size); 13 | int data_write_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7); 14 | PKCS7 *pkcs7_create(FILE_FORMAT_CTX *ctx); 15 | int add_indirect_data_object(PKCS7 *p7); 16 | int sign_spc_indirect_data_content(PKCS7 *p7, ASN1_OCTET_STRING *content); 17 | PKCS7 *pkcs7_set_content(ASN1_OCTET_STRING *content); 18 | ASN1_OCTET_STRING *spc_indirect_data_content_get(BIO *hash, FILE_FORMAT_CTX *ctx); 19 | int pkcs7_sign_content(PKCS7 *p7, const u_char *data, int len); 20 | int asn1_simple_hdr_len(const u_char *p, int len); 21 | int bio_hash_data(BIO *hash, char *indata, size_t idx, size_t fileend); 22 | void print_hash(const char *descript1, const char *descript2, const u_char *hashbuf, int length); 23 | int is_content_type(PKCS7 *p7, const char *objid); 24 | MsCtlContent *ms_ctl_content_get(PKCS7 *p7); 25 | ASN1_TYPE *catalog_content_get(CatalogAuthAttr *attribute); 26 | SpcLink *spc_link_obsolete_get(void); 27 | int compare_digests(u_char *mdbuf, u_char *cmdbuf, int mdtype); 28 | 29 | /* 30 | Local Variables: 31 | c-basic-offset: 4 32 | tab-width: 4 33 | indent-tabs-mode: nil 34 | End: 35 | 36 | vim: set ts=4 expandtab: 37 | */ 38 | -------------------------------------------------------------------------------- /misc/pagehash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import struct 4 | import sys 5 | import hashlib 6 | from pyasn1.type import univ 7 | from pyasn1.codec.ber import encoder, decoder 8 | 9 | f = open(sys.argv[1], 'rb') 10 | filehdr = f.read(1024) 11 | if filehdr[0:2] != 'MZ': 12 | print "Not a DOS file." 13 | sys.exit(0) 14 | pepos = struct.unpack('config.py < 3 | # Author: Małgorzata Olszówka 4 | 5 | bind 'set show-all-if-ambiguous on' 6 | bind 'set completion-ignore-case on' 7 | COMP_WORDBREAKS=${COMP_WORDBREAKS//:} 8 | 9 | _comp_cmd_osslsigncode() 10 | { 11 | local cur prev words cword 12 | _init_completion || return 13 | 14 | local commands command options timestamps rfc3161 15 | 16 | commands="--help --version -v 17 | sign add attach-signature extract-signature remove-signature verify" 18 | 19 | timestamps="http://timestamp.digicert.com 20 | http://time.certum.pl 21 | http://timestamp.sectigo.com 22 | http://timestamp.globalsign.com/?signature=sha2" 23 | 24 | rfc3161="http://timestamp.digicert.com 25 | http://time.certum.pl 26 | http://timestamp.entrust.net/TSS/RFC3161sha2TS 27 | http://tss.accv.es:8318/tsa 28 | http://kstamp.keynectis.com/KSign/ 29 | http://sha256timestamp.ws.symantec.com/sha256/timestamp" 30 | 31 | 32 | if ((cword == 1)); then 33 | COMPREPLY=($(compgen -W "${commands}" -- ${cur})) 34 | else 35 | command=${words[1]} 36 | case $prev in 37 | -ac | -c | -catalog | -certs | -spc | -key | -pkcs12 | -pass | \ 38 | -readpass | -pkcs11engine | -pkcs11module | -in | -out | -sigin | \ 39 | -n | -CAfile | -CRLfile | -TSA-CAfile | -TSA-CRLfile) 40 | _filedir 41 | return 42 | ;; 43 | -h | -require-leaf-hash) 44 | COMPREPLY=($(compgen -W 'md5 sha1 sha2 sha256 sha384 sha512' \ 45 | -- "$cur")) 46 | return 47 | ;; 48 | -jp) 49 | COMPREPLY=($(compgen -W 'low medium high' -- "$cur")) 50 | return 51 | ;; 52 | -t) 53 | COMPREPLY=($(compgen -W "${timestamps}" -- "$cur")) 54 | return 55 | ;; 56 | -ts) 57 | COMPREPLY=($(compgen -W "${rfc3161}" -- "$cur")) 58 | return 59 | ;; 60 | -i | -p) 61 | _known_hosts_real -- "$cur" 62 | return 63 | ;; 64 | esac 65 | 66 | if [[ $cur == -* ]]; then 67 | # possible options for the command 68 | options=$(_parse_help "$1" "$command --help" 2>/dev/null) 69 | COMPREPLY=($(compgen -W "${options}" -- ${cur})) 70 | fi 71 | fi 72 | 73 | } && 74 | complete -F _comp_cmd_osslsigncode osslsigncode 75 | 76 | # ex: filetype=sh 77 | -------------------------------------------------------------------------------- /osslsigncode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021-2023 Michał Trojnara 3 | * Author: Małgorzata Olszówka 4 | */ 5 | 6 | #define OPENSSL_API_COMPAT 0x10100000L 7 | #define OPENSSL_NO_DEPRECATED 8 | 9 | #if defined(_MSC_VER) || defined(__MINGW32__) 10 | #define HAVE_WINDOWS_H 11 | #endif /* _MSC_VER || __MINGW32__ */ 12 | 13 | #ifdef HAVE_WINDOWS_H 14 | #define NOCRYPT 15 | #define WIN32_LEAN_AND_MEAN 16 | #include 17 | #include 18 | #endif /* HAVE_WINDOWS_H */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif /* HAVE_CONFIG_H */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #ifndef _WIN32 35 | #include 36 | #include 37 | #ifdef HAVE_SYS_MMAN_H 38 | #include 39 | #endif /* HAVE_SYS_MMAN_H */ 40 | #ifdef HAVE_TERMIOS_H 41 | #include 42 | #endif /* HAVE_TERMIOS_H */ 43 | #endif /* _WIN32 */ 44 | 45 | #include 46 | #include 47 | 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #ifndef OPENSSL_NO_ENGINE 55 | #include 56 | #endif /* OPENSSL_NO_ENGINE */ 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #if OPENSSL_VERSION_NUMBER>=0x30000000L 64 | #include 65 | #endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */ 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include /* X509_PURPOSE */ 74 | 75 | #ifdef ENABLE_CURL 76 | #ifdef __CYGWIN__ 77 | #ifndef SOCKET 78 | #define SOCKET UINT_PTR 79 | #endif /* SOCKET */ 80 | #endif /* __CYGWIN__ */ 81 | #include 82 | #endif /* ENABLE_CURL */ 83 | 84 | /* Request nonce length, in bits (must be a multiple of 8). */ 85 | #define NONCE_LENGTH 64 86 | #define MAX_TS_SERVERS 256 87 | 88 | #if defined (HAVE_TERMIOS_H) || defined (HAVE_GETPASS) 89 | #define PROVIDE_ASKPASS 1 90 | #endif 91 | 92 | #ifdef _MSC_VER 93 | /* not WIN32, because strcasecmp exists in MinGW */ 94 | #define strcasecmp _stricmp 95 | #define fseeko _fseeki64 96 | #define ftello _ftelli64 97 | #endif /* _MSC_VER */ 98 | 99 | #ifdef WIN32 100 | #define remove_file(filename) _unlink(filename) 101 | #else 102 | #define remove_file(filename) unlink(filename) 103 | #endif /* WIN32 */ 104 | 105 | #define GET_UINT8_LE(p) ((const u_char *)(p))[0] 106 | 107 | #define GET_UINT16_LE(p) (uint16_t)(((const u_char *)(p))[0] | \ 108 | (((const u_char *)(p))[1] << 8)) 109 | 110 | #define GET_UINT32_LE(p) (uint32_t)(((const u_char *)(p))[0] | \ 111 | (((const u_char *)(p))[1] << 8) | \ 112 | (((const u_char *)(p))[2] << 16) | \ 113 | (((const u_char *)(p))[3] << 24)) 114 | 115 | #define PUT_UINT8_LE(i, p) ((u_char *)(p))[0] = (u_char)((i) & 0xff); 116 | 117 | #define PUT_UINT16_LE(i,p) ((u_char *)(p))[0] = (u_char)((i) & 0xff); \ 118 | ((u_char *)(p))[1] = (u_char)(((i) >> 8) & 0xff) 119 | 120 | #define PUT_UINT32_LE(i,p) ((u_char *)(p))[0] = (u_char)((i) & 0xff); \ 121 | ((u_char *)(p))[1] = (u_char)(((i) >> 8) & 0xff); \ 122 | ((u_char *)(p))[2] = (u_char)(((i) >> 16) & 0xff); \ 123 | ((u_char *)(p))[3] = (u_char)(((i) >> 24) & 0xff) 124 | 125 | #ifndef FALSE 126 | #define FALSE 0 127 | #endif 128 | 129 | #ifndef TRUE 130 | #define TRUE 1 131 | #endif 132 | 133 | #define SIZE_64K 65536 /* 2^16 */ 134 | #define SIZE_16M 16777216 /* 2^24 */ 135 | 136 | /* 137 | * Macro names: 138 | * linux: __BYTE_ORDER == __LITTLE_ENDIAN | __BIG_ENDIAN 139 | * BYTE_ORDER == LITTLE_ENDIAN | BIG_ENDIAN 140 | * bsd: _BYTE_ORDER == _LITTLE_ENDIAN | _BIG_ENDIAN 141 | * BYTE_ORDER == LITTLE_ENDIAN | BIG_ENDIAN 142 | * solaris: _LITTLE_ENDIAN | _BIG_ENDIAN 143 | */ 144 | 145 | #ifndef BYTE_ORDER 146 | #define LITTLE_ENDIAN 1234 147 | #define BIG_ENDIAN 4321 148 | #define BYTE_ORDER LITTLE_ENDIAN 149 | #endif /* BYTE_ORDER */ 150 | 151 | #if !defined(BYTE_ORDER) || !defined(LITTLE_ENDIAN) || !defined(BIG_ENDIAN) 152 | #error "Cannot determine the endian-ness of this platform" 153 | #endif 154 | 155 | #ifndef LOWORD 156 | #define LOWORD(x) ((x) & 0xFFFF) 157 | #endif /* LOWORD */ 158 | #ifndef HIWORD 159 | #define HIWORD(x) (((x) >> 16) & 0xFFFF) 160 | #endif /* HIWORD */ 161 | 162 | #if BYTE_ORDER == BIG_ENDIAN 163 | #define LE_UINT16(x) ((((x) >> 8) & 0x00FF) | \ 164 | (((x) << 8) & 0xFF00)) 165 | #define LE_UINT32(x) (((x) >> 24) | \ 166 | (((x) & 0x00FF0000) >> 8) | \ 167 | (((x) & 0x0000FF00) << 8) | \ 168 | ((x) << 24)) 169 | #else 170 | #define LE_UINT16(x) (x) 171 | #define LE_UINT32(x) (x) 172 | #endif /* BYTE_ORDER == BIG_ENDIAN */ 173 | 174 | #define MIN(a,b) ((a) < (b) ? a : b) 175 | #define INVALID_TIME ((time_t)-1) 176 | 177 | /* Microsoft OID Authenticode */ 178 | #define SPC_INDIRECT_DATA_OBJID "1.3.6.1.4.1.311.2.1.4" 179 | #define SPC_STATEMENT_TYPE_OBJID "1.3.6.1.4.1.311.2.1.11" 180 | #define SPC_SP_OPUS_INFO_OBJID "1.3.6.1.4.1.311.2.1.12" 181 | #define SPC_PE_IMAGE_DATA_OBJID "1.3.6.1.4.1.311.2.1.15" 182 | #define SPC_CAB_DATA_OBJID "1.3.6.1.4.1.311.2.1.25" 183 | #define SPC_SIPINFO_OBJID "1.3.6.1.4.1.311.2.1.30" 184 | #define SPC_PE_IMAGE_PAGE_HASHES_V1 "1.3.6.1.4.1.311.2.3.1" /* SHA1 */ 185 | #define SPC_PE_IMAGE_PAGE_HASHES_V2 "1.3.6.1.4.1.311.2.3.2" /* SHA256 */ 186 | #define SPC_NESTED_SIGNATURE_OBJID "1.3.6.1.4.1.311.2.4.1" 187 | /* Microsoft OID Time Stamping */ 188 | #define SPC_TIME_STAMP_REQUEST_OBJID "1.3.6.1.4.1.311.3.2.1" 189 | #define SPC_RFC3161_OBJID "1.3.6.1.4.1.311.3.3.1" 190 | /* Microsoft OID Crypto 2.0 */ 191 | #define MS_CTL_OBJID "1.3.6.1.4.1.311.10.1" 192 | /* Microsoft OID Catalog */ 193 | #define CAT_NAMEVALUE_OBJID "1.3.6.1.4.1.311.12.2.1" 194 | /* Microsoft OID Microsoft_Java */ 195 | #define MS_JAVA_SOMETHING "1.3.6.1.4.1.311.15.1" 196 | 197 | #define SPC_UNAUTHENTICATED_DATA_BLOB_OBJID "1.3.6.1.4.1.42921.1.2.1" 198 | 199 | /* Public Key Cryptography Standards PKCS#9 */ 200 | #define PKCS9_MESSAGE_DIGEST "1.2.840.113549.1.9.4" 201 | #define PKCS9_SIGNING_TIME "1.2.840.113549.1.9.5" 202 | #define PKCS9_COUNTER_SIGNATURE "1.2.840.113549.1.9.6" 203 | #define PKCS9_SEQUENCE_NUMBER "1.2.840.113549.1.9.25.4" 204 | 205 | /* WIN_CERTIFICATE structure declared in Wintrust.h */ 206 | #define WIN_CERT_REVISION_2_0 0x0200 207 | #define WIN_CERT_TYPE_PKCS_SIGNED_DATA 0x0002 208 | 209 | /* 210 | * FLAG_PREV_CABINET is set if the cabinet file is not the first in a set 211 | * of cabinet files. When this bit is set, the szCabinetPrev and szDiskPrev 212 | * fields are present in this CFHEADER. 213 | */ 214 | #define FLAG_PREV_CABINET 0x0001 215 | /* 216 | * FLAG_NEXT_CABINET is set if the cabinet file is not the last in a set of 217 | * cabinet files. When this bit is set, the szCabinetNext and szDiskNext 218 | * fields are present in this CFHEADER. 219 | */ 220 | #define FLAG_NEXT_CABINET 0x0002 221 | /* 222 | * FLAG_RESERVE_PRESENT is set if the cabinet file contains any reserved 223 | * fields. When this bit is set, the cbCFHeader, cbCFFolder, and cbCFData 224 | * fields are present in this CFHEADER. 225 | */ 226 | #define FLAG_RESERVE_PRESENT 0x0004 227 | 228 | #define DO_EXIT_0(x) { fprintf(stderr, x); goto err_cleanup; } 229 | #define DO_EXIT_1(x, y) { fprintf(stderr, x, y); goto err_cleanup; } 230 | #define DO_EXIT_2(x, y, z) { fprintf(stderr, x, y, z); goto err_cleanup; } 231 | 232 | /* Default policy if request did not specify it. */ 233 | #define TSA_POLICY1 "1.2.3.4.1" 234 | 235 | typedef enum { 236 | CMD_SIGN, 237 | CMD_EXTRACT, 238 | CMD_EXTRACT_DATA, 239 | CMD_REMOVE, 240 | CMD_VERIFY, 241 | CMD_ADD, 242 | CMD_ATTACH, 243 | CMD_HELP, 244 | CMD_DEFAULT 245 | } cmd_type_t; 246 | 247 | typedef unsigned char u_char; 248 | 249 | #ifndef OPENSSL_NO_ENGINE 250 | typedef struct { 251 | ASN1_OCTET_STRING *cmd; 252 | ASN1_OCTET_STRING *param; 253 | } EngineControl; 254 | 255 | DECLARE_ASN1_FUNCTIONS(EngineControl) 256 | DEFINE_STACK_OF(EngineControl) 257 | #endif /* OPENSSL_NO_ENGINE */ 258 | 259 | typedef struct { 260 | char *infile; 261 | char *outfile; 262 | char *sigfile; 263 | char *certfile; 264 | char *xcertfile; 265 | char *keyfile; 266 | char *pvkfile; 267 | char *pkcs12file; 268 | int output_pkcs7; 269 | #ifndef OPENSSL_NO_ENGINE 270 | char *p11engine; 271 | STACK_OF(EngineControl) *engine_ctrls; 272 | int login; 273 | #endif /* OPENSSL_NO_ENGINE */ 274 | #if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L 275 | char *p11module; 276 | char *p11cert; 277 | #endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */ 278 | int askpass; 279 | char *readpass; 280 | char *pass; 281 | int comm; 282 | int pagehash; 283 | char *desc; 284 | const EVP_MD *md; 285 | char *url; 286 | time_t time; 287 | char *turl[MAX_TS_SERVERS]; 288 | int nturl; 289 | char *tsurl[MAX_TS_SERVERS]; 290 | int ntsurl; 291 | char *proxy; 292 | int noverifypeer; 293 | int addBlob; 294 | const char *blob_file; 295 | int nest; 296 | int index; 297 | int ignore_timestamp; 298 | int ignore_cdp; 299 | int ignore_crl; 300 | int verbose; 301 | int add_msi_dse; 302 | char *catalog; 303 | char *cafile; 304 | char *crlfile; 305 | char *https_cafile; 306 | char *https_crlfile; 307 | char *tsa_cafile; 308 | char *tsa_crlfile; 309 | char *leafhash; 310 | int jp; 311 | #if OPENSSL_VERSION_NUMBER>=0x30000000L 312 | int legacy; 313 | char *provider; 314 | #endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */ 315 | EVP_PKEY *pkey; 316 | STACK_OF(X509) *certs; 317 | STACK_OF(X509) *xcerts; 318 | STACK_OF(X509_CRL) *crls; 319 | cmd_type_t cmd; 320 | char *indata; 321 | char *tsa_certfile; 322 | char *tsa_keyfile; 323 | time_t tsa_time; 324 | int nested_number; 325 | } GLOBAL_OPTIONS; 326 | 327 | /* 328 | * ASN.1 definitions (more or less from official MS Authenticode docs) 329 | */ 330 | typedef struct { 331 | int type; 332 | union { 333 | ASN1_BMPSTRING *unicode; 334 | ASN1_IA5STRING *ascii; 335 | } value; 336 | } SpcString; 337 | 338 | DECLARE_ASN1_FUNCTIONS(SpcString) 339 | 340 | typedef struct { 341 | ASN1_OCTET_STRING *classId; 342 | ASN1_OCTET_STRING *serializedData; 343 | } SpcSerializedObject; 344 | 345 | DECLARE_ASN1_FUNCTIONS(SpcSerializedObject) 346 | 347 | typedef struct { 348 | int type; 349 | union { 350 | ASN1_IA5STRING *url; 351 | SpcSerializedObject *moniker; 352 | SpcString *file; 353 | } value; 354 | } SpcLink; 355 | 356 | DECLARE_ASN1_FUNCTIONS(SpcLink) 357 | 358 | typedef struct { 359 | SpcString *programName; 360 | SpcLink *moreInfo; 361 | } SpcSpOpusInfo; 362 | 363 | DECLARE_ASN1_FUNCTIONS(SpcSpOpusInfo) 364 | 365 | typedef struct { 366 | ASN1_INTEGER *a; 367 | ASN1_OCTET_STRING *string; 368 | ASN1_INTEGER *b; 369 | ASN1_INTEGER *c; 370 | ASN1_INTEGER *d; 371 | ASN1_INTEGER *e; 372 | ASN1_INTEGER *f; 373 | } SpcSipInfo; 374 | 375 | DECLARE_ASN1_FUNCTIONS(SpcSipInfo) 376 | 377 | typedef struct { 378 | ASN1_OBJECT *type; 379 | ASN1_TYPE *value; 380 | } SpcAttributeTypeAndOptionalValue; 381 | 382 | DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue) 383 | 384 | typedef struct { 385 | ASN1_OBJECT *algorithm; 386 | ASN1_TYPE *parameters; 387 | } AlgorithmIdentifier; 388 | 389 | DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier) 390 | 391 | typedef struct { 392 | AlgorithmIdentifier *digestAlgorithm; 393 | ASN1_OCTET_STRING *digest; 394 | } DigestInfo; 395 | 396 | DECLARE_ASN1_FUNCTIONS(DigestInfo) 397 | 398 | typedef struct { 399 | SpcAttributeTypeAndOptionalValue *data; 400 | DigestInfo *messageDigest; 401 | } SpcIndirectDataContent; 402 | 403 | DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent) 404 | 405 | typedef struct CatalogAuthAttr_st { 406 | ASN1_OBJECT *type; 407 | ASN1_TYPE *contents; 408 | } CatalogAuthAttr; 409 | 410 | DEFINE_STACK_OF(CatalogAuthAttr) 411 | DECLARE_ASN1_FUNCTIONS(CatalogAuthAttr) 412 | 413 | typedef struct { 414 | AlgorithmIdentifier *digestAlgorithm; 415 | ASN1_OCTET_STRING *digest; 416 | } MessageImprint; 417 | 418 | DECLARE_ASN1_FUNCTIONS(MessageImprint) 419 | 420 | typedef struct { 421 | ASN1_OBJECT *type; 422 | ASN1_OCTET_STRING *signature; 423 | } TimeStampRequestBlob; 424 | 425 | DECLARE_ASN1_FUNCTIONS(TimeStampRequestBlob) 426 | 427 | typedef struct { 428 | ASN1_OBJECT *type; 429 | TimeStampRequestBlob *blob; 430 | } TimeStampRequest; 431 | 432 | DECLARE_ASN1_FUNCTIONS(TimeStampRequest) 433 | 434 | /* RFC3161 Time stamping */ 435 | 436 | typedef struct { 437 | ASN1_INTEGER *status; 438 | STACK_OF(ASN1_UTF8STRING) *statusString; 439 | ASN1_BIT_STRING *failInfo; 440 | } PKIStatusInfo; 441 | 442 | DECLARE_ASN1_FUNCTIONS(PKIStatusInfo) 443 | 444 | typedef struct { 445 | PKIStatusInfo *status; 446 | PKCS7 *token; 447 | } TimeStampResp; 448 | 449 | DECLARE_ASN1_FUNCTIONS(TimeStampResp) 450 | 451 | typedef struct { 452 | ASN1_INTEGER *version; 453 | MessageImprint *messageImprint; 454 | ASN1_OBJECT *reqPolicy; 455 | ASN1_INTEGER *nonce; 456 | ASN1_BOOLEAN certReq; 457 | STACK_OF(X509_EXTENSION) *extensions; 458 | } TimeStampReq; 459 | 460 | DECLARE_ASN1_FUNCTIONS(TimeStampReq) 461 | 462 | typedef struct { 463 | ASN1_INTEGER *seconds; 464 | ASN1_INTEGER *millis; 465 | ASN1_INTEGER *micros; 466 | } TimeStampAccuracy; 467 | 468 | DECLARE_ASN1_FUNCTIONS(TimeStampAccuracy) 469 | 470 | typedef struct { 471 | ASN1_INTEGER *version; 472 | ASN1_OBJECT *policy_id; 473 | MessageImprint *messageImprint; 474 | ASN1_INTEGER *serial; 475 | ASN1_GENERALIZEDTIME *time; 476 | TimeStampAccuracy *accuracy; 477 | ASN1_BOOLEAN ordering; 478 | ASN1_INTEGER *nonce; 479 | GENERAL_NAME *tsa; 480 | STACK_OF(X509_EXTENSION) *extensions; 481 | } TimeStampToken; 482 | 483 | DECLARE_ASN1_FUNCTIONS(TimeStampToken) 484 | 485 | typedef struct { 486 | ASN1_OCTET_STRING *digest; 487 | STACK_OF(CatalogAuthAttr) *attributes; 488 | } CatalogInfo; 489 | 490 | DEFINE_STACK_OF(CatalogInfo) 491 | DECLARE_ASN1_FUNCTIONS(CatalogInfo) 492 | 493 | typedef struct { 494 | /* 1.3.6.1.4.1.311.12.1.1 MS_CATALOG_LIST */ 495 | SpcAttributeTypeAndOptionalValue *type; 496 | ASN1_OCTET_STRING *identifier; 497 | ASN1_UTCTIME *time; 498 | /* 1.3.6.1.4.1.311.12.1.2 CatalogVersion = 1 499 | * 1.3.6.1.4.1.311.12.1.3 CatalogVersion = 2 */ 500 | SpcAttributeTypeAndOptionalValue *version; 501 | STACK_OF(CatalogInfo) *header_attributes; 502 | /* 1.3.6.1.4.1.311.12.2.1 CAT_NAMEVALUE_OBJID */ 503 | ASN1_TYPE *filename; 504 | } MsCtlContent; 505 | 506 | DECLARE_ASN1_FUNCTIONS(MsCtlContent) 507 | 508 | typedef struct { 509 | char *server; 510 | const char *port; 511 | int use_proxy; 512 | int timeout; 513 | SSL_CTX *ssl_ctx; 514 | } HTTP_TLS_Info; 515 | 516 | typedef struct file_format_st FILE_FORMAT; 517 | 518 | typedef struct script_ctx_st SCRIPT_CTX; 519 | typedef struct msi_ctx_st MSI_CTX; 520 | typedef struct pe_ctx_st PE_CTX; 521 | typedef struct cab_ctx_st CAB_CTX; 522 | typedef struct cat_ctx_st CAT_CTX; 523 | typedef struct appx_ctx_st APPX_CTX; 524 | 525 | typedef struct { 526 | FILE_FORMAT *format; 527 | GLOBAL_OPTIONS *options; 528 | union { 529 | SCRIPT_CTX *script_ctx; 530 | MSI_CTX *msi_ctx; 531 | PE_CTX *pe_ctx; 532 | CAB_CTX *cab_ctx; 533 | CAT_CTX *cat_ctx; 534 | APPX_CTX *appx_ctx; 535 | }; 536 | } FILE_FORMAT_CTX; 537 | 538 | extern FILE_FORMAT file_format_script; 539 | extern FILE_FORMAT file_format_msi; 540 | extern FILE_FORMAT file_format_pe; 541 | extern FILE_FORMAT file_format_cab; 542 | extern FILE_FORMAT file_format_cat; 543 | extern FILE_FORMAT file_format_appx; 544 | 545 | struct file_format_st { 546 | FILE_FORMAT_CTX *(*ctx_new) (GLOBAL_OPTIONS *option, BIO *hash, BIO *outdata); 547 | const EVP_MD *(*md_get) (FILE_FORMAT_CTX *ctx); 548 | ASN1_OBJECT *(*data_blob_get) (u_char **p, int *plen, FILE_FORMAT_CTX *ctx); 549 | PKCS7 *(*pkcs7_contents_get) (FILE_FORMAT_CTX *ctx, BIO *hash, const EVP_MD *md); 550 | int (*hash_length_get) (FILE_FORMAT_CTX *ctx); 551 | u_char *(*digest_calc) (FILE_FORMAT_CTX *ctx, const EVP_MD *md); 552 | int (*verify_digests) (FILE_FORMAT_CTX *ctx, PKCS7 *p7); 553 | int (*verify_indirect_data) (FILE_FORMAT_CTX *ctx, SpcAttributeTypeAndOptionalValue *obj); 554 | PKCS7 *(*pkcs7_extract) (FILE_FORMAT_CTX *ctx); 555 | PKCS7 *(*pkcs7_extract_to_nest) (FILE_FORMAT_CTX *ctx); 556 | int (*remove_pkcs7) (FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata); 557 | int (*process_data) (FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata); 558 | PKCS7 *(*pkcs7_signature_new) (FILE_FORMAT_CTX *ctx, BIO *hash); 559 | int (*append_pkcs7) (FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7); 560 | void (*update_data_size) (FILE_FORMAT_CTX *data, BIO *outdata, PKCS7 *p7); 561 | void (*bio_free) (BIO *hash, BIO *outdata); 562 | void (*ctx_cleanup) (FILE_FORMAT_CTX *ctx); 563 | int (*is_detaching_supported) (void); 564 | }; 565 | 566 | /* 567 | Local Variables: 568 | c-basic-offset: 4 569 | tab-width: 4 570 | indent-tabs-mode: nil 571 | End: 572 | 573 | vim: set ts=4 expandtab: 574 | */ 575 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .pylintrc 3 | -------------------------------------------------------------------------------- /tests/certs/ca-bundle.crt: -------------------------------------------------------------------------------- 1 | # Certum Trusted Network CA 2 | -----BEGIN CERTIFICATE----- 3 | MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM 4 | MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D 5 | ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU 6 | cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 7 | WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg 8 | Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw 9 | IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B 10 | AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH 11 | UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM 12 | TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU 13 | BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM 14 | kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x 15 | AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV 16 | HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV 17 | HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y 18 | sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL 19 | I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 20 | J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY 21 | VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 22 | 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= 23 | -----END CERTIFICATE----- 24 | 25 | # DigiCert Assured ID Root CA 26 | -----BEGIN CERTIFICATE----- 27 | MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl 28 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 29 | d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv 30 | b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG 31 | EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl 32 | cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi 33 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c 34 | JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP 35 | mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ 36 | wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 37 | VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ 38 | AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB 39 | AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW 40 | BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun 41 | pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC 42 | dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf 43 | fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm 44 | NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx 45 | H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe 46 | +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== 47 | -----END CERTIFICATE----- 48 | -------------------------------------------------------------------------------- /tests/check_cryptography.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """Check cryptography module.""" 3 | 4 | import sys 5 | 6 | try: 7 | import cryptography 8 | print(cryptography.__version__, end="") 9 | except ModuleNotFoundError as ierr: 10 | print("Module not installed: {}".format(ierr)) 11 | sys.exit(1) 12 | except ImportError as ierr: 13 | print("Module not found: {}".format(ierr)) 14 | sys.exit(1) 15 | 16 | class UnsupportedVersion(Exception): 17 | """Unsupported version""" 18 | 19 | def main() -> None: 20 | """Check python3-cryptography version""" 21 | try: 22 | version = tuple(int(num) for num in cryptography.__version__.split('.')) 23 | if version < (37, 0, 2): 24 | raise UnsupportedVersion("unsupported python3-cryptography version") 25 | except UnsupportedVersion as err: 26 | print(" {}".format(err), end="") 27 | sys.exit(1) 28 | 29 | 30 | if __name__ == '__main__': 31 | main() 32 | 33 | # pylint: disable=pointless-string-statement 34 | """Local Variables: 35 | c-basic-offset: 4 36 | tab-width: 4 37 | indent-tabs-mode: nil 38 | End: 39 | vim: set ts=4 expandtab: 40 | """ 41 | -------------------------------------------------------------------------------- /tests/client_http.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """Implementation of a HTTP client""" 3 | 4 | import os 5 | import sys 6 | import http.client 7 | 8 | RESULT_PATH = os.getcwd() 9 | 10 | 11 | def main() -> None: 12 | """Creating a POST Request""" 13 | ret = 0 14 | try: 15 | file_path = os.path.join(RESULT_PATH, "./Testing/logs/url.log") 16 | with open(file_path, mode="r", encoding="utf-8") as file: 17 | url = file.readline() 18 | host, port = url.split(":") 19 | conn = http.client.HTTPConnection(host, port) 20 | conn.request('POST', '/kill_server') 21 | response = conn.getresponse() 22 | print("HTTP status code:", response.getcode(), end=', ') 23 | try: 24 | text = response.read() 25 | print(text.decode("UTF-8"), end='', flush=True) 26 | except OSError as err: 27 | print(f"Warning: {err}") 28 | conn.close() 29 | except OSError as err: 30 | print(f"OSError: {err}") 31 | ret = err.errno 32 | except Exception as err: # pylint: disable=broad-except 33 | print(f"HTTP client error: {err}") 34 | ret = err 35 | finally: 36 | sys.exit(ret) 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | 42 | 43 | # pylint: disable=pointless-string-statement 44 | """ 45 | Local Variables: 46 | c-basic-offset: 4 47 | tab-width: 4 48 | indent-tabs-mode: nil 49 | End: 50 | vim: set ts=4 expandtab: 51 | """ 52 | -------------------------------------------------------------------------------- /tests/conf/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /tests/conf/openssl_tsa.cnf: -------------------------------------------------------------------------------- 1 | # OpenSSL Timestamp Authority configuration file 2 | 3 | oid_section = new_oids 4 | 5 | [ new_oids ] 6 | tsa_policy1 = 1.2.3.4.1 7 | tsa_policy2 = 1.2.3.4.5.6 8 | tsa_policy3 = 1.2.3.4.5.7 9 | 10 | [ req ] 11 | # Options for the `req` tool 12 | default_bits = 2048 13 | encrypt_key = yes 14 | default_md = sha256 15 | utf8 = yes 16 | string_mask = utf8only 17 | prompt = no 18 | distinguished_name = ca_distinguished_name 19 | 20 | [ ca_distinguished_name ] 21 | countryName = "PL" 22 | organizationName = "osslsigncode" 23 | organizationalUnitName = "Timestamp Authority" 24 | commonName = "Test TSA" 25 | 26 | 27 | # Time Stamping Authority command "openssl-ts" 28 | 29 | [ tsa ] 30 | default_tsa = tsa_config 31 | 32 | [ tsa_config ] 33 | dir = ./Testing/certs 34 | signer_cert = $dir/TSA.pem 35 | signer_key = $dir/TSA.key 36 | certs = $dir/tsa-chain.pem 37 | serial = $dir/tsa-serial 38 | default_policy = tsa_policy1 39 | other_policies = tsa_policy2, tsa_policy3 40 | signer_digest = sha256 41 | digests = sha256, sha384, sha512 42 | accuracy = secs:1, millisecs:500, microsecs:100 43 | ordering = yes 44 | tsa_name = yes 45 | ess_cert_id_chain = yes 46 | ess_cert_id_alg = sha256 47 | crypto_device = builtin 48 | -------------------------------------------------------------------------------- /tests/exec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """Implementation of a single ctest script.""" 3 | 4 | import sys 5 | from subprocess import Popen, PIPE 6 | 7 | 8 | def parse(value): 9 | """Read parameter from file.""" 10 | prefix = 'FILE ' 11 | if value.startswith(prefix): 12 | with open(value[len(prefix):], mode="r", encoding="utf-8") as file: 13 | return file.read().strip() 14 | return value 15 | 16 | 17 | def main() -> None: 18 | """Run osslsigncode with its options.""" 19 | if len(sys.argv) > 1: 20 | try: 21 | params = map(parse, sys.argv[1:]) 22 | proc = Popen(params, stdout=PIPE, stderr=PIPE, text=True) 23 | stdout, stderr = proc.communicate() 24 | print(stdout, file=sys.stderr) 25 | if stderr: 26 | print("Error:\n" + "-" * 58 + "\n" + stderr, file=sys.stderr) 27 | sys.exit(proc.returncode) 28 | except Exception as err: # pylint: disable=broad-except 29 | # all exceptions are critical 30 | print(err, file=sys.stderr) 31 | else: 32 | print("Usage:\n\t{} COMMAND [ARG]...'".format(sys.argv[0]), file=sys.stderr) 33 | sys.exit(1) 34 | 35 | 36 | if __name__ == "__main__": 37 | main() 38 | 39 | 40 | # pylint: disable=pointless-string-statement 41 | """Local Variables: 42 | c-basic-offset: 4 43 | tab-width: 4 44 | indent-tabs-mode: nil 45 | End: 46 | vim: set ts=4 expandtab: 47 | """ 48 | -------------------------------------------------------------------------------- /tests/files/unsigned.256appx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrojnar/osslsigncode/dd9b81281fabe66e2df46d8c530707483332b9df/tests/files/unsigned.256appx -------------------------------------------------------------------------------- /tests/files/unsigned.512appx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrojnar/osslsigncode/dd9b81281fabe66e2df46d8c530707483332b9df/tests/files/unsigned.512appx -------------------------------------------------------------------------------- /tests/files/unsigned.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrojnar/osslsigncode/dd9b81281fabe66e2df46d8c530707483332b9df/tests/files/unsigned.cat -------------------------------------------------------------------------------- /tests/files/unsigned.ex_: -------------------------------------------------------------------------------- 1 | MSCFx,lDTs tests\sources\aDT's tests\sources\be 2 | f 3 | a 4 | b 5 | -------------------------------------------------------------------------------- /tests/files/unsigned.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrojnar/osslsigncode/dd9b81281fabe66e2df46d8c530707483332b9df/tests/files/unsigned.exe -------------------------------------------------------------------------------- /tests/files/unsigned.js: -------------------------------------------------------------------------------- 1 | console.log("Hello, world!"); -------------------------------------------------------------------------------- /tests/files/unsigned.mof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrojnar/osslsigncode/dd9b81281fabe66e2df46d8c530707483332b9df/tests/files/unsigned.mof -------------------------------------------------------------------------------- /tests/files/unsigned.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrojnar/osslsigncode/dd9b81281fabe66e2df46d8c530707483332b9df/tests/files/unsigned.msi -------------------------------------------------------------------------------- /tests/files/unsigned.ps1: -------------------------------------------------------------------------------- 1 | cls 2 | Write-Host "żółć" -------------------------------------------------------------------------------- /tests/files/unsigned.psc1: -------------------------------------------------------------------------------- 1 |  2 | 3 | 5.1.19041.3930 4 | 5 | -------------------------------------------------------------------------------- /tests/make_certificates.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """Make test certificates""" 3 | 4 | import os 5 | import datetime 6 | import cryptography 7 | 8 | # Explicit imports of cryptography submodules 9 | import cryptography.x509 10 | import cryptography.x509.oid 11 | import cryptography.hazmat.primitives.hashes 12 | import cryptography.hazmat.primitives.asymmetric.rsa 13 | import cryptography.hazmat.primitives.serialization 14 | import cryptography.hazmat.primitives.serialization.pkcs12 15 | 16 | # Import classes and functions from the cryptography module 17 | from cryptography.x509 import ( 18 | AuthorityKeyIdentifier, 19 | BasicConstraints, 20 | Certificate, 21 | CertificateBuilder, 22 | CertificateRevocationListBuilder, 23 | CRLDistributionPoints, 24 | CRLNumber, 25 | CRLReason, 26 | DistributionPoint, 27 | DNSName, 28 | ExtendedKeyUsage, 29 | KeyUsage, 30 | Name, 31 | NameAttribute, 32 | NameConstraints, 33 | random_serial_number, 34 | RevokedCertificateBuilder, 35 | ReasonFlags, 36 | SubjectKeyIdentifier, 37 | UniformResourceIdentifier 38 | ) 39 | from cryptography.x509.oid import ( 40 | ExtendedKeyUsageOID, 41 | NameOID 42 | ) 43 | from cryptography.hazmat.primitives.hashes import SHA256 44 | from cryptography.hazmat.primitives.asymmetric.rsa import ( 45 | generate_private_key, 46 | RSAPrivateKey 47 | ) 48 | from cryptography.hazmat.primitives.serialization import ( 49 | BestAvailableEncryption, 50 | Encoding, 51 | NoEncryption, 52 | PrivateFormat 53 | ) 54 | from cryptography.hazmat.primitives.serialization.pkcs12 import serialize_key_and_certificates 55 | 56 | try: 57 | if cryptography.__version__ >= '38.0.0': 58 | from cryptography.hazmat.primitives.serialization.pkcs12 import PBES 59 | except ImportError: 60 | pass 61 | 62 | RESULT_PATH = os.getcwd() 63 | CERTS_PATH = os.path.join(RESULT_PATH, "./Testing/certs/") 64 | 65 | date_20170101 = datetime.datetime(2017, 1, 1) 66 | date_20180101 = datetime.datetime(2018, 1, 1) 67 | date_20190101 = datetime.datetime(2019, 1, 1) 68 | 69 | PASSWORD='passme' 70 | 71 | 72 | class X509Extensions(): 73 | """Base class for X509 Extensions""" 74 | 75 | def __init__(self, unit_name, cdp_port, cdp_name): 76 | self.unit_name = unit_name 77 | self.port = cdp_port 78 | self.name = cdp_name 79 | 80 | def create_x509_name(self, common_name) -> Name: 81 | """Return x509.Name""" 82 | return Name( 83 | [ 84 | NameAttribute(NameOID.COUNTRY_NAME, "PL"), 85 | NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Mazovia Province"), 86 | NameAttribute(NameOID.LOCALITY_NAME, "Warsaw"), 87 | NameAttribute(NameOID.ORGANIZATION_NAME, "osslsigncode"), 88 | NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, self.unit_name), 89 | NameAttribute(NameOID.COMMON_NAME, common_name) 90 | ] 91 | ) 92 | 93 | def create_x509_crldp(self) -> CRLDistributionPoints: 94 | """Return x509.CRLDistributionPoints""" 95 | return CRLDistributionPoints( 96 | [ 97 | DistributionPoint( 98 | full_name=[UniformResourceIdentifier( 99 | "http://127.0.0.1:" + str(self.port) + "/" + str(self.name)) 100 | ], 101 | relative_name=None, 102 | reasons=None, 103 | crl_issuer=None 104 | ) 105 | ] 106 | ) 107 | 108 | def create_x509_name_constraints(self) -> NameConstraints: 109 | """Return x509.NameConstraints""" 110 | return NameConstraints( 111 | permitted_subtrees = [DNSName('test.com'), DNSName('test.org')], 112 | excluded_subtrees = None 113 | ) 114 | 115 | class IntermediateCACertificate(X509Extensions): 116 | """Base class for Intermediate CA certificate""" 117 | 118 | def __init__(self, issuer_cert, issuer_key): 119 | self.issuer_cert = issuer_cert 120 | self.issuer_key = issuer_key 121 | super().__init__("Certification Authority", 0, None) 122 | 123 | def make_cert(self) -> (Certificate, RSAPrivateKey): 124 | """Generate intermediate CA certificate""" 125 | key = generate_private_key(public_exponent=65537, key_size=2048) 126 | key_public = key.public_key() 127 | authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( 128 | self.issuer_cert.extensions.get_extension_for_class(SubjectKeyIdentifier).value 129 | ) 130 | key_usage = KeyUsage( 131 | digital_signature=True, 132 | content_commitment=False, 133 | key_encipherment=False, 134 | data_encipherment=False, 135 | key_agreement=False, 136 | key_cert_sign=True, 137 | crl_sign=True, 138 | encipher_only=False, 139 | decipher_only=False 140 | ) 141 | cert = ( 142 | CertificateBuilder() 143 | .subject_name(self.create_x509_name("Intermediate CA")) 144 | .issuer_name(self.issuer_cert.subject) 145 | .public_key(key_public) 146 | .serial_number(random_serial_number()) 147 | .not_valid_before(date_20180101) 148 | .not_valid_after(date_20180101 + datetime.timedelta(days=7300)) 149 | .add_extension(BasicConstraints(ca=True, path_length=0), critical=True) 150 | .add_extension(SubjectKeyIdentifier.from_public_key(key_public), critical=False) 151 | .add_extension(authority_key, critical=False) 152 | .add_extension(key_usage, critical=True) 153 | .sign(self.issuer_key, SHA256()) 154 | ) 155 | file_path=os.path.join(CERTS_PATH, "intermediateCA.pem") 156 | with open(file_path, mode="wb") as file: 157 | file.write(cert.public_bytes(encoding=Encoding.PEM)) 158 | 159 | return cert, key 160 | 161 | 162 | class RootCACertificate(X509Extensions): 163 | """Base class for Root CA certificate""" 164 | 165 | def __init__(self): 166 | self.key_usage = KeyUsage( 167 | digital_signature=True, 168 | content_commitment=False, 169 | key_encipherment=False, 170 | data_encipherment=False, 171 | key_agreement=False, 172 | key_cert_sign=True, 173 | crl_sign=True, 174 | encipher_only=False, 175 | decipher_only=False 176 | ) 177 | super().__init__("Certification Authority", 0, None) 178 | 179 | def make_cert(self) -> (Certificate, RSAPrivateKey): 180 | """Generate CA certificates""" 181 | ca_root, root_key = self.make_ca_cert("Trusted Root CA", "CAroot.pem") 182 | ca_cert, ca_key = self.make_ca_cert("Root CA", "CACert.pem") 183 | self.make_cross_cert(ca_root, root_key, ca_cert, ca_key) 184 | return ca_cert, ca_key 185 | 186 | def make_ca_cert(self, common_name, file_name) -> None: 187 | """Generate self-signed root CA certificate""" 188 | ca_key = generate_private_key(public_exponent=65537, key_size=2048) 189 | ca_public = ca_key.public_key() 190 | authority_key = AuthorityKeyIdentifier.from_issuer_public_key(ca_public) 191 | name = self.create_x509_name(common_name) 192 | ca_cert = ( 193 | CertificateBuilder() 194 | .subject_name(name) 195 | .issuer_name(name) 196 | .public_key(ca_public) 197 | .serial_number(random_serial_number()) 198 | .not_valid_before(date_20170101) 199 | .not_valid_after(date_20170101 + datetime.timedelta(days=7300)) 200 | .add_extension(BasicConstraints(ca=True, path_length=None), critical=True) 201 | .add_extension(SubjectKeyIdentifier.from_public_key(ca_public), critical=False) 202 | .add_extension(authority_key, critical=False) 203 | .add_extension(self.key_usage, critical=True) 204 | .sign(ca_key, SHA256()) 205 | ) 206 | file_path=os.path.join(CERTS_PATH, file_name) 207 | with open(file_path, mode="wb") as file: 208 | file.write(ca_cert.public_bytes(encoding=Encoding.PEM)) 209 | return ca_cert, ca_key 210 | 211 | def make_cross_cert(self, ca_root, root_key, ca_cert, ca_key) -> None: 212 | """Generate cross-signed root CA certificate""" 213 | ca_public = ca_key.public_key() 214 | authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( 215 | ca_root.extensions.get_extension_for_class(SubjectKeyIdentifier).value 216 | ) 217 | ca_cross = ( 218 | CertificateBuilder() 219 | .subject_name(ca_cert.subject) 220 | .issuer_name(ca_root.subject) 221 | .public_key(ca_public) 222 | .serial_number(ca_cert.serial_number) 223 | .not_valid_before(date_20180101) 224 | .not_valid_after(date_20180101 + datetime.timedelta(days=7300)) 225 | .add_extension(BasicConstraints(ca=True, path_length=None), critical=True) 226 | .add_extension(SubjectKeyIdentifier.from_public_key(ca_public), critical=False) 227 | .add_extension(authority_key, critical=False) 228 | .add_extension(self.key_usage, critical=True) 229 | .sign(root_key, SHA256()) 230 | ) 231 | file_path=os.path.join(CERTS_PATH, "CAcross.pem") 232 | with open(file_path, mode="wb") as file: 233 | file.write(ca_cross.public_bytes(encoding=Encoding.PEM)) 234 | 235 | def write_key(self, key, file_name) -> None: 236 | """Write a private RSA key""" 237 | # Write password 238 | file_path = os.path.join(CERTS_PATH, "password.txt") 239 | with open(file_path, mode="w", encoding="utf-8") as file: 240 | file.write("{}".format(PASSWORD)) 241 | 242 | # Write encrypted key in PEM format 243 | file_path = os.path.join(CERTS_PATH, file_name + "p.pem") 244 | with open(file_path, mode="wb") as file: 245 | file.write(key.private_bytes( 246 | encoding=Encoding.PEM, 247 | format=PrivateFormat.PKCS8, 248 | encryption_algorithm=BestAvailableEncryption(PASSWORD.encode()) 249 | ) 250 | ) 251 | # Write decrypted key in PEM format 252 | file_path = os.path.join(CERTS_PATH, file_name + ".pem") 253 | with open(file_path, mode="wb") as file: 254 | file.write(key.private_bytes( 255 | encoding=Encoding.PEM, 256 | format=PrivateFormat.PKCS8, 257 | encryption_algorithm=NoEncryption() 258 | ) 259 | ) 260 | # Write the key in DER format 261 | file_path = os.path.join(CERTS_PATH, file_name + ".der") 262 | with open(file_path, mode="wb") as file: 263 | file.write(key.private_bytes( 264 | encoding=Encoding.DER, 265 | format=PrivateFormat.PKCS8, 266 | encryption_algorithm=NoEncryption() 267 | ) 268 | ) 269 | 270 | 271 | class TSARootCACertificate(X509Extensions): 272 | """Base class for TSA certificates""" 273 | 274 | def __init__(self): 275 | super().__init__("Timestamp Authority Root CA", 0, None) 276 | 277 | def make_cert(self) -> (Certificate, RSAPrivateKey): 278 | """Generate a Time Stamp Authority certificate""" 279 | ca_key = generate_private_key(public_exponent=65537, key_size=2048) 280 | ca_public = ca_key.public_key() 281 | authority_key = AuthorityKeyIdentifier.from_issuer_public_key(ca_public) 282 | name = self.create_x509_name("TSA Root CA") 283 | key_usage = KeyUsage( 284 | digital_signature=False, 285 | content_commitment=False, 286 | key_encipherment=False, 287 | data_encipherment=False, 288 | key_agreement=False, 289 | key_cert_sign=True, 290 | crl_sign=True, 291 | encipher_only=False, 292 | decipher_only=False 293 | ) 294 | ca_cert = ( 295 | CertificateBuilder() 296 | .subject_name(name) 297 | .issuer_name(name) 298 | .public_key(ca_public) 299 | .serial_number(random_serial_number()) 300 | .not_valid_before(date_20170101) 301 | .not_valid_after(date_20170101 + datetime.timedelta(days=7300)) 302 | .add_extension(BasicConstraints(ca=True, path_length=None), critical=True) 303 | .add_extension(SubjectKeyIdentifier.from_public_key(ca_public), critical=False) 304 | .add_extension(authority_key, critical=False) 305 | .add_extension(key_usage, critical=True) 306 | .sign(ca_key, SHA256()) 307 | ) 308 | file_path=os.path.join(CERTS_PATH, "TSACA.pem") 309 | with open(file_path, mode="wb") as file: 310 | file.write(ca_cert.public_bytes(encoding=Encoding.PEM)) 311 | 312 | return ca_cert, ca_key 313 | 314 | def write_key(self, key, file_name) -> None: 315 | """Write decrypted private RSA key into PEM format""" 316 | file_path = os.path.join(CERTS_PATH, file_name + ".key") 317 | with open(file_path, mode="wb") as file: 318 | file.write(key.private_bytes( 319 | encoding=Encoding.PEM, 320 | format=PrivateFormat.PKCS8, 321 | encryption_algorithm=NoEncryption() 322 | ) 323 | ) 324 | 325 | 326 | class LeafCertificate(X509Extensions): 327 | """Base class for a leaf certificate""" 328 | 329 | def __init__(self, issuer_cert, issuer_key, unit_name, common_name, cdp_port, cdp_name): 330 | #pylint: disable=too-many-arguments 331 | self.issuer_cert = issuer_cert 332 | self.issuer_key = issuer_key 333 | self.common_name = common_name 334 | super().__init__(unit_name, cdp_port, cdp_name) 335 | 336 | def make_cert(self, public_key, not_before, days) -> Certificate: 337 | """Generate a leaf certificate""" 338 | authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( 339 | self.issuer_cert.extensions.get_extension_for_class(SubjectKeyIdentifier).value 340 | ) 341 | extended_key_usage = ExtendedKeyUsage( 342 | [ExtendedKeyUsageOID.CODE_SIGNING] 343 | ) 344 | cert = ( 345 | CertificateBuilder() 346 | .subject_name(self.create_x509_name(self.common_name)) 347 | .issuer_name(self.issuer_cert.subject) 348 | .public_key(public_key) 349 | .serial_number(random_serial_number()) 350 | .not_valid_before(not_before) 351 | .not_valid_after(not_before + datetime.timedelta(days=days)) 352 | .add_extension(BasicConstraints(ca=False, path_length=None), critical=False) 353 | .add_extension(SubjectKeyIdentifier.from_public_key(public_key), critical=False) 354 | .add_extension(authority_key, critical=False) 355 | .add_extension(extended_key_usage, critical=False) 356 | .add_extension(self.create_x509_crldp(), critical=False) 357 | .sign(self.issuer_key, SHA256()) 358 | ) 359 | # Write PEM file and attach intermediate certificate 360 | file_path = os.path.join(CERTS_PATH, self.common_name + ".pem") 361 | with open(file_path, mode="wb") as file: 362 | file.write(cert.public_bytes(encoding=Encoding.PEM)) 363 | file.write(self.issuer_cert.public_bytes(encoding=Encoding.PEM)) 364 | 365 | return cert 366 | 367 | def revoke_cert(self, serial_number, file_name) -> None: 368 | """Revoke a certificate""" 369 | revoked = ( 370 | RevokedCertificateBuilder() 371 | .serial_number(serial_number) 372 | .revocation_date(date_20190101) 373 | .add_extension(CRLReason(ReasonFlags.superseded), critical=False) 374 | .build() 375 | ) 376 | # Generate CRL 377 | authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( 378 | self.issuer_cert.extensions.get_extension_for_class(SubjectKeyIdentifier).value 379 | ) 380 | crl = ( 381 | CertificateRevocationListBuilder() 382 | .issuer_name(self.issuer_cert.subject) 383 | .last_update(date_20190101) 384 | .next_update(date_20190101 + datetime.timedelta(days=7300)) 385 | .add_extension(authority_key, critical=False) 386 | .add_extension(CRLNumber(4097), critical=False) 387 | .add_revoked_certificate(revoked) 388 | .sign(self.issuer_key, SHA256()) 389 | ) 390 | # Write CRL file 391 | file_path = os.path.join(CERTS_PATH, file_name + ".pem") 392 | with open(file_path, mode="wb") as file: 393 | file.write(crl.public_bytes(encoding=Encoding.PEM)) 394 | 395 | file_path = os.path.join(CERTS_PATH, file_name + ".der") 396 | with open(file_path, mode="wb") as file: 397 | file.write(crl.public_bytes(encoding=Encoding.DER)) 398 | 399 | 400 | class LeafCACertificate(LeafCertificate): 401 | """Base class for a leaf certificate""" 402 | 403 | def __init__(self, issuer_cert, issuer_key, common, cdp_port): 404 | super().__init__(issuer_cert, issuer_key, "CSP", common, cdp_port, "intermediateCA") 405 | 406 | 407 | class LeafTSACertificate(LeafCertificate): 408 | """Base class for a TSA leaf certificate""" 409 | 410 | def __init__(self, issuer_cert, issuer_key, common, cdp_port): 411 | self.issuer_cert = issuer_cert 412 | self.issuer_key = issuer_key 413 | self.common_name = common 414 | super().__init__(issuer_cert, issuer_key, "Timestamp Root CA", common, cdp_port, "TSACA") 415 | 416 | def make_cert(self, public_key, not_before, days) -> Certificate: 417 | """Generate a TSA leaf certificate""" 418 | 419 | authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( 420 | self.issuer_cert.extensions.get_extension_for_class(SubjectKeyIdentifier).value 421 | ) 422 | 423 | # The TSA signing certificate must have exactly one extended key usage 424 | # assigned to it: timeStamping. The extended key usage must also be critical, 425 | # otherwise the certificate is going to be refused. 426 | extended_key_usage = ExtendedKeyUsage( 427 | [ExtendedKeyUsageOID.TIME_STAMPING] 428 | ) 429 | cert = ( 430 | CertificateBuilder() 431 | .subject_name(self.create_x509_name(self.common_name)) 432 | .issuer_name(self.issuer_cert.subject) 433 | .public_key(public_key) 434 | .serial_number(random_serial_number()) 435 | .not_valid_before(not_before) 436 | .not_valid_after(not_before + datetime.timedelta(days=days)) 437 | .add_extension(BasicConstraints(ca=False, path_length=None), critical=True) 438 | .add_extension(SubjectKeyIdentifier.from_public_key(public_key), critical=False) 439 | .add_extension(authority_key, critical=False) 440 | .add_extension(extended_key_usage, critical=True) 441 | .add_extension(self.create_x509_crldp(), critical=False) 442 | .add_extension(self.create_x509_name_constraints(), critical=False) 443 | .sign(self.issuer_key, SHA256()) 444 | ) 445 | # Write PEM file and attach intermediate certificate 446 | file_path = os.path.join(CERTS_PATH, self.common_name + ".pem") 447 | with open(file_path, mode="wb") as file: 448 | file.write(cert.public_bytes(encoding=Encoding.PEM)) 449 | file.write(self.issuer_cert.public_bytes(encoding=Encoding.PEM)) 450 | 451 | return cert 452 | 453 | 454 | class CertificateMaker(): 455 | """Base class for test certificates""" 456 | 457 | def __init__(self, cdp_port, logs): 458 | self.cdp_port = cdp_port 459 | self.logs = logs 460 | 461 | def make_certs(self) -> None: 462 | """Make test certificates""" 463 | try: 464 | self.make_ca_certs() 465 | self.make_tsa_certs() 466 | logs = os.path.join(CERTS_PATH, "./cert.log") 467 | with open(logs, mode="w", encoding="utf-8") as file: 468 | file.write("Test certificates generation succeeded") 469 | except Exception as err: # pylint: disable=broad-except 470 | with open(self.logs, mode="a", encoding="utf-8") as file: 471 | file.write("Error: {}".format(err)) 472 | 473 | def make_ca_certs(self): 474 | """Make test certificates""" 475 | 476 | # Generate root CA certificate 477 | root = RootCACertificate() 478 | ca_cert, ca_key = root.make_cert() 479 | 480 | # Generate intermediate root CA certificate 481 | intermediate = IntermediateCACertificate(ca_cert, ca_key) 482 | issuer_cert, issuer_key = intermediate.make_cert() 483 | 484 | # Generate private RSA key 485 | private_key = generate_private_key(public_exponent=65537, key_size=2048) 486 | public_key = private_key.public_key() 487 | root.write_key(key=private_key, file_name="key") 488 | 489 | # Generate expired certificate 490 | expired = LeafCACertificate(issuer_cert, issuer_key, "expired", self.cdp_port) 491 | expired.make_cert(public_key, date_20180101, 365) 492 | 493 | # Generate revoked certificate 494 | revoked = LeafCACertificate(issuer_cert, issuer_key, "revoked", self.cdp_port) 495 | cert = revoked.make_cert(public_key, date_20180101, 5840) 496 | revoked.revoke_cert(cert.serial_number, "CACertCRL") 497 | 498 | # Generate code signing certificate 499 | signer = LeafCACertificate(issuer_cert, issuer_key, "cert", self.cdp_port) 500 | cert = signer.make_cert(public_key, date_20180101, 5840) 501 | 502 | # Write a certificate and a key into PKCS#12 container 503 | self.write_pkcs12_container( 504 | cert=cert, 505 | key=private_key, 506 | issuer=issuer_cert 507 | ) 508 | 509 | # Write DER file and attach intermediate certificate 510 | file_path = os.path.join(CERTS_PATH, "cert.der") 511 | with open(file_path, mode="wb") as file: 512 | file.write(cert.public_bytes(encoding=Encoding.DER)) 513 | 514 | def make_tsa_certs(self): 515 | """Make test TSA certificates""" 516 | 517 | # Time Stamp Authority certificate 518 | root = TSARootCACertificate() 519 | issuer_cert, issuer_key = root.make_cert() 520 | 521 | # Generate private RSA key 522 | private_key = generate_private_key(public_exponent=65537, key_size=2048) 523 | public_key = private_key.public_key() 524 | root.write_key(key=private_key, file_name="TSA") 525 | 526 | # Generate revoked TSA certificate 527 | revoked = LeafTSACertificate(issuer_cert, issuer_key, "TSA_revoked", self.cdp_port) 528 | cert = revoked.make_cert(public_key, date_20180101, 7300) 529 | revoked.revoke_cert(cert.serial_number, "TSACertCRL") 530 | 531 | # Generate TSA certificate 532 | signer = LeafTSACertificate(issuer_cert, issuer_key, "TSA", self.cdp_port) 533 | cert = signer.make_cert(public_key, date_20180101, 7300) 534 | 535 | # Save the chain to be included in the TSA response 536 | file_path = os.path.join(CERTS_PATH, "tsa-chain.pem") 537 | with open(file_path, mode="wb") as file: 538 | file.write(cert.public_bytes(encoding=Encoding.PEM)) 539 | file.write(issuer_cert.public_bytes(encoding=Encoding.PEM)) 540 | 541 | 542 | def write_pkcs12_container(self, cert, key, issuer) -> None: 543 | """Write a certificate and a key into a PKCS#12 container""" 544 | 545 | # Set an encryption algorithm 546 | if cryptography.__version__ >= "38.0.0": 547 | # For OpenSSL legacy mode use the default algorithm for certificate 548 | # and private key encryption: DES-EDE3-CBC (vel 3DES_CBC) 549 | # pylint: disable=no-member 550 | encryption = ( 551 | PrivateFormat.PKCS12.encryption_builder() 552 | .key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC) 553 | .kdf_rounds(5000) 554 | .build(PASSWORD.encode()) 555 | ) 556 | else: 557 | encryption = BestAvailableEncryption(PASSWORD.encode()) 558 | 559 | # Generate PKCS#12 struct 560 | pkcs12 = serialize_key_and_certificates( 561 | name=b'certificate', 562 | key=key, 563 | cert=cert, 564 | cas=(issuer,), 565 | encryption_algorithm=encryption 566 | ) 567 | 568 | # Write into a PKCS#12 container 569 | file_path = os.path.join(CERTS_PATH, "cert.p12") 570 | with open(file_path, mode="wb") as file: 571 | file.write(pkcs12) 572 | 573 | 574 | # pylint: disable=pointless-string-statement 575 | """Local Variables: 576 | c-basic-offset: 4 577 | tab-width: 4 578 | indent-tabs-mode: nil 579 | End: 580 | vim: set ts=4 expandtab: 581 | """ 582 | -------------------------------------------------------------------------------- /tests/server_http.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """Implementation of a HTTP server""" 3 | 4 | import argparse 5 | import os 6 | import subprocess 7 | import sys 8 | import threading 9 | from urllib.parse import urlparse 10 | from http.server import SimpleHTTPRequestHandler, HTTPServer 11 | from socketserver import ThreadingMixIn 12 | from make_certificates import CertificateMaker 13 | 14 | RESULT_PATH = os.getcwd() 15 | FILES_PATH = os.path.join(RESULT_PATH, "./Testing/files/") 16 | CERTS_PATH = os.path.join(RESULT_PATH, "./Testing/certs/") 17 | CONF_PATH = os.path.join(RESULT_PATH, "./Testing/conf/") 18 | LOGS_PATH = os.path.join(RESULT_PATH, "./Testing/logs/") 19 | REQUEST = os.path.join(FILES_PATH, "./jreq.tsq") 20 | RESPONS = os.path.join(FILES_PATH, "./jresp.tsr") 21 | OPENSSL_CONF = os.path.join(CONF_PATH, "./openssl_tsa.cnf") 22 | SERVER_LOG = os.path.join(LOGS_PATH, "./server.log") 23 | URL_LOG = os.path.join(LOGS_PATH, "./url.log") 24 | 25 | OPENSSL_TS = ["openssl", "ts", 26 | "-reply", "-config", OPENSSL_CONF, 27 | "-passin", "pass:passme", 28 | "-queryfile", REQUEST, 29 | "-out", RESPONS] 30 | 31 | 32 | class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): 33 | """This variant of HTTPServer creates a new thread for every connection""" 34 | daemon_threads = True 35 | 36 | 37 | class RequestHandler(SimpleHTTPRequestHandler): 38 | """Handle the HTTP POST request that arrive at the server""" 39 | 40 | def __init__(self, request, client_address, server): 41 | # Save the server handle 42 | self.server = server 43 | SimpleHTTPRequestHandler.__init__(self, request, client_address, server) 44 | 45 | def do_GET(self): # pylint: disable=invalid-name 46 | """"Serves the GET request type""" 47 | try: 48 | url = urlparse(self.path) 49 | self.send_response(200) 50 | self.send_header("Content-type", "application/pkix-crl") 51 | self.end_headers() 52 | resp_data = b'' 53 | # Read the file and send the contents 54 | if url.path == "/intermediateCA": 55 | file_path = os.path.join(CERTS_PATH, "./CACertCRL.der") 56 | with open(file_path, 'rb') as file: 57 | resp_data = file.read() 58 | if url.path == "/TSACA": 59 | file_path = os.path.join(CERTS_PATH, "./TSACertCRL.der") 60 | with open(file_path, 'rb') as file: 61 | resp_data = file.read() 62 | self.wfile.write(resp_data) 63 | except Exception as err: # pylint: disable=broad-except 64 | print("HTTP GET request error: {}".format(err)) 65 | 66 | 67 | def do_POST(self): # pylint: disable=invalid-name 68 | """"Serves the POST request type""" 69 | try: 70 | url = urlparse(self.path) 71 | self.send_response(200) 72 | if url.path == "/kill_server": 73 | self.log_message(f"Deleting file: {URL_LOG}") 74 | os.remove(f"{URL_LOG}") 75 | self.send_header('Content-type', 'text/plain') 76 | self.end_headers() 77 | self.wfile.write(bytes('Shutting down HTTP server', 'utf-8')) 78 | self.server.shutdown() 79 | else: 80 | content_length = int(self.headers['Content-Length']) 81 | post_data = self.rfile.read(content_length) 82 | with open(REQUEST, mode="wb") as file: 83 | file.write(post_data) 84 | openssl = subprocess.run(OPENSSL_TS, check=True, universal_newlines=True) 85 | openssl.check_returncode() 86 | self.send_header("Content-type", "application/timestamp-reply") 87 | self.end_headers() 88 | resp_data = b'' 89 | with open(RESPONS, mode="rb") as file: 90 | resp_data = file.read() 91 | self.wfile.write(resp_data) 92 | 93 | except Exception as err: # pylint: disable=broad-except 94 | print("HTTP POST request error: {}".format(err)) 95 | 96 | 97 | class HttpServerThread(): 98 | """TSA server thread handler""" 99 | # pylint: disable=too-few-public-methods 100 | 101 | def __init__(self): 102 | self.server = None 103 | self.server_thread = None 104 | 105 | def start_server(self, port) -> (int): 106 | """Starting HTTP server on 127.0.0.1 and a random available port for binding""" 107 | self.server = ThreadingHTTPServer(('127.0.0.1', port), RequestHandler) 108 | self.server_thread = threading.Thread(target=self.server.serve_forever) 109 | self.server_thread.start() 110 | hostname, port = self.server.server_address[:2] 111 | print("HTTP server started, URL http://{}:{}".format(hostname, port)) 112 | return port 113 | 114 | 115 | def main() -> None: 116 | """Start HTTP server, make test certificates.""" 117 | 118 | ret = 0 119 | parser = argparse.ArgumentParser() 120 | parser.add_argument( 121 | "--port", 122 | type=int, 123 | default=0, 124 | help="port number" 125 | ) 126 | args = parser.parse_args() 127 | try: 128 | server = HttpServerThread() 129 | port = server.start_server(args.port) 130 | with open(URL_LOG, mode="w", encoding="utf-8") as file: 131 | file.write("127.0.0.1:{}".format(port)) 132 | tests = CertificateMaker(port, SERVER_LOG) 133 | tests.make_certs() 134 | except OSError as err: 135 | print("OSError: {}".format(err)) 136 | ret = err.errno 137 | except Exception as err: # pylint: disable=broad-except 138 | print("Error: {}".format(err)) 139 | ret = 1 140 | finally: 141 | sys.exit(ret) 142 | 143 | 144 | if __name__ == '__main__': 145 | try: 146 | fpid = os.fork() 147 | if fpid > 0: 148 | sys.exit(0) 149 | with open(SERVER_LOG, mode='w', encoding='utf-8') as log: 150 | os.dup2(log.fileno(), sys.stdout.fileno()) 151 | os.dup2(log.fileno(), sys.stderr.fileno()) 152 | except OSError as ferr: 153 | print("Fork #1 failed: {} {}".format(ferr.errno, ferr.strerror)) 154 | sys.exit(1) 155 | 156 | try: 157 | fpid = os.fork() 158 | if fpid > 0: 159 | sys.exit(0) 160 | except OSError as ferr: 161 | print("Fork #2 failed: {} {}".format(ferr.errno, ferr.strerror)) 162 | sys.exit(1) 163 | 164 | # Start the daemon main loop 165 | main() 166 | 167 | 168 | # pylint: disable=pointless-string-statement 169 | """Local Variables: 170 | c-basic-offset: 4 171 | tab-width: 4 172 | indent-tabs-mode: nil 173 | End: 174 | vim: set ts=4 expandtab: 175 | """ 176 | -------------------------------------------------------------------------------- /tests/server_http.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """Windows: Implementation of a HTTP server""" 3 | 4 | import argparse 5 | import os 6 | import subprocess 7 | import sys 8 | import threading 9 | from urllib.parse import urlparse 10 | from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer 11 | from make_certificates import CertificateMaker 12 | 13 | RESULT_PATH = os.getcwd() 14 | FILES_PATH = os.path.join(RESULT_PATH, "./Testing/files/") 15 | CERTS_PATH = os.path.join(RESULT_PATH, "./Testing/certs/") 16 | CONF_PATH = os.path.join(RESULT_PATH, "./Testing/conf/") 17 | LOGS_PATH = os.path.join(RESULT_PATH, "./Testing/logs/") 18 | REQUEST = os.path.join(FILES_PATH, "./jreq.tsq") 19 | RESPONS = os.path.join(FILES_PATH, "./jresp.tsr") 20 | OPENSSL_CONF = os.path.join(CONF_PATH, "./openssl_tsa.cnf") 21 | SERVER_LOG = os.path.join(LOGS_PATH, "./server.log") 22 | URL_LOG = os.path.join(LOGS_PATH, "./url.log") 23 | 24 | 25 | OPENSSL_TS = ["openssl", "ts", 26 | "-reply", "-config", OPENSSL_CONF, 27 | "-passin", "pass:passme", 28 | "-queryfile", REQUEST, 29 | "-out", RESPONS] 30 | 31 | 32 | class RequestHandler(SimpleHTTPRequestHandler): 33 | """Handle the HTTP POST request that arrive at the server""" 34 | 35 | def __init__(self, request, client_address, server): 36 | # Save the server handle 37 | self.server = server 38 | SimpleHTTPRequestHandler.__init__(self, request, client_address, server) 39 | 40 | def do_GET(self): # pylint: disable=invalid-name 41 | """"Serves the GET request type""" 42 | try: 43 | url = urlparse(self.path) 44 | self.send_response(200) 45 | self.send_header("Content-type", "application/pkix-crl") 46 | self.end_headers() 47 | resp_data = b'' 48 | # Read the file and send the contents 49 | if url.path == "/intermediateCA": 50 | file_path = os.path.join(CERTS_PATH, "./CACertCRL.der") 51 | with open(file_path, 'rb') as file: 52 | resp_data = file.read() 53 | if url.path == "/TSACA": 54 | file_path = os.path.join(CERTS_PATH, "./TSACertCRL.der") 55 | with open(file_path, 'rb') as file: 56 | resp_data = file.read() 57 | self.wfile.write(resp_data) 58 | except Exception as err: # pylint: disable=broad-except 59 | print("HTTP GET request error: {}".format(err)) 60 | 61 | 62 | def do_POST(self): # pylint: disable=invalid-name 63 | """"Serves the POST request type""" 64 | try: 65 | url = urlparse(self.path) 66 | self.send_response(200) 67 | if url.path == "/kill_server": 68 | self.log_message(f"Deleting file: {URL_LOG}") 69 | os.remove(f"{URL_LOG}") 70 | self.send_header('Content-type', 'text/plain') 71 | self.end_headers() 72 | self.wfile.write(bytes('Shutting down HTTP server', 'utf-8')) 73 | self.server.shutdown() 74 | else: 75 | content_length = int(self.headers['Content-Length']) 76 | post_data = self.rfile.read(content_length) 77 | with open(REQUEST, mode="wb") as file: 78 | file.write(post_data) 79 | openssl = subprocess.run(OPENSSL_TS, 80 | check=True, universal_newlines=True) 81 | openssl.check_returncode() 82 | self.send_header("Content-type", "application/timestamp-reply") 83 | self.end_headers() 84 | resp_data = b'' 85 | with open(RESPONS, mode="rb") as file: 86 | resp_data = file.read() 87 | self.wfile.write(resp_data) 88 | except Exception as err: # pylint: disable=broad-except 89 | print("HTTP POST request error: {}".format(err)) 90 | 91 | 92 | class HttpServerThread(): 93 | """TSA server thread handler""" 94 | # pylint: disable=too-few-public-methods 95 | 96 | def __init__(self): 97 | self.server = None 98 | self.server_thread = None 99 | 100 | def start_server(self, port) -> (int): 101 | """Starting HTTP server on 127.0.0.1 and a random available port for binding""" 102 | self.server = ThreadingHTTPServer(('127.0.0.1', port), RequestHandler) 103 | self.server_thread = threading.Thread(target=self.server.serve_forever) 104 | self.server_thread.start() 105 | hostname, port = self.server.server_address[:2] 106 | print("HTTP server started, URL http://{}:{}".format(hostname, port)) 107 | return port 108 | 109 | 110 | def main() -> None: 111 | """Start HTTP server""" 112 | 113 | ret = 0 114 | parser = argparse.ArgumentParser() 115 | parser.add_argument( 116 | "--port", 117 | type=int, 118 | default=0, 119 | help="port number" 120 | ) 121 | args = parser.parse_args() 122 | try: 123 | sys.stdout = open(SERVER_LOG, "w") 124 | sys.stderr = open(SERVER_LOG, "a") 125 | server = HttpServerThread() 126 | port = server.start_server(args.port) 127 | with open(URL_LOG, mode="w") as file: 128 | file.write("127.0.0.1:{}".format(port)) 129 | tests = CertificateMaker(port, SERVER_LOG) 130 | tests.make_certs() 131 | except OSError as err: 132 | print("OSError: {}".format(err)) 133 | ret = err.errno 134 | finally: 135 | sys.exit(ret) 136 | 137 | 138 | if __name__ == '__main__': 139 | main() 140 | 141 | 142 | # pylint: disable=pointless-string-statement 143 | """Local Variables: 144 | c-basic-offset: 4 145 | tab-width: 4 146 | indent-tabs-mode: nil 147 | End: 148 | vim: set ts=4 expandtab: 149 | """ 150 | -------------------------------------------------------------------------------- /tests/sources/CatalogDefinitionFileName.cdf: -------------------------------------------------------------------------------- 1 | # https://learn.microsoft.com/en-us/windows/win32/seccrypto/makecat 2 | # makecat -v CatalogDefinitionFileName.cdf 3 | 4 | # Define information about the entire catalog file. 5 | [CatalogHeader] 6 | 7 | # Name of the catalog file, including its extension. 8 | Name=unsigned.cat 9 | 10 | # Directory where the created unsigned.cat file will be placed. 11 | ResultDir=..\files 12 | 13 | # This option is not supported. Default value 1 is used. 14 | PublicVersion=0x0000001 15 | 16 | # Catalog version. 17 | # If the version is set to 2, the HashAlgorithms option must contain SHA256. 18 | CatalogVersion=2 19 | 20 | # Name of the hashing algorithm used. 21 | HashAlgorithms=SHA256 22 | 23 | # Specifies whether to hash the files listed in the option in the [CatalogFiles] section 24 | PageHashes=true 25 | 26 | # Type of message encoding used. 27 | # The default EncodingType is PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0x00010001 28 | EncodingType=0x00010001 29 | 30 | # Specify an attribute of the catalog file. 31 | # Set 1.3.6.1.4.1.311.12.2.1 CAT_NAMEVALUE_OBJID 32 | # CATATTR1={type}:{oid}:{value} (optional) 33 | # The OSAttr attribute specifies the target Windows version 34 | CATATTR1=0x11010001:OSAttr:2:6.0 35 | 36 | # Define each member of the catalog file. 37 | [CatalogFiles] 38 | 39 | PEfile=..\files\unsigned.exe 40 | # 0x00010000 Attribute is represented in plaintext. No conversion will be done. 41 | PEfileATTR1=0x11010001:File:unsigned.exe 42 | 43 | MSIfile=..\files\unsigned.msi 44 | # 0x00020000 Attribute is represented in base-64 encoding. 45 | MSIfileATTR1=0x11020001:File:dW5zaWduZWQubXNp 46 | 47 | CABfile=..\files\unsigned.ex_ 48 | CABfileATTR1=0x11010001:File:unsigned.ex_ 49 | 50 | PS1file=..\files\unsigned.ps1 51 | PS1fileATTR1=0x11010001:File:unsigned.ps1 52 | 53 | PSC1file=..\files\unsigned.psc1 54 | PSC1fileATTR1=0x11010001:File:unsigned.psc1 55 | 56 | MOFfile=..\files\unsigned.mof 57 | MOFfileATTR1=0x11010001:File:unsigned.mof 58 | 59 | JSfile=..\files\unsigned.js 60 | JSfileATTR1=0x11010001:File:unsigned.js 61 | -------------------------------------------------------------------------------- /tests/sources/a: -------------------------------------------------------------------------------- 1 | aaa 2 | -------------------------------------------------------------------------------- /tests/sources/b: -------------------------------------------------------------------------------- 1 | bbb 2 | -------------------------------------------------------------------------------- /tests/sources/c: -------------------------------------------------------------------------------- 1 | ccc 2 | -------------------------------------------------------------------------------- /tests/sources/myapp.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void main(void) 4 | { 5 | printf("Hello world!"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/sources/sample.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/start_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """Wait for all tests certificate, compute leafhash""" 3 | 4 | import argparse 5 | import binascii 6 | import hashlib 7 | import os 8 | import pathlib 9 | import platform 10 | import subprocess 11 | import sys 12 | import time 13 | 14 | RESULT_PATH = os.getcwd() 15 | CERTS_PATH = os.path.join(RESULT_PATH, "./Testing/certs/") 16 | LOGS_PATH = os.path.join(RESULT_PATH, "./Testing/logs/") 17 | SERVER_LOG = os.path.join(LOGS_PATH, "./server.log") 18 | if platform.system() == 'Windows': 19 | DEFAULT_PYTHON = "C:/Program Files/Python/Python311/pythonw.exe" 20 | DEFAULT_PROG = os.path.join(RESULT_PATH, "./Testing/server_http.pyw") 21 | else: 22 | DEFAULT_PYTHON = "/usr/bin/python3" 23 | DEFAULT_PROG = os.path.join(RESULT_PATH, "./Testing/server_http.py") 24 | 25 | 26 | def compute_sha256(file_name) -> str: 27 | """Compute a SHA256 hash of the leaf certificate (in DER form)""" 28 | 29 | sha256_hash = hashlib.sha256() 30 | file_path = os.path.join(CERTS_PATH, file_name) 31 | with open(file_path, mode="rb") as file: 32 | for bajt in iter(lambda: file.read(4096),b""): 33 | sha256_hash.update(bajt) 34 | return sha256_hash.hexdigest() 35 | 36 | def clear_catalog(certs_path) -> None: 37 | """"Clear a test certificates catalog.""" 38 | 39 | if os.path.exists(certs_path): 40 | #Remove old test certificates 41 | for root, _, files in os.walk(certs_path): 42 | for file in files: 43 | os.remove(os.path.join(root, file)) 44 | else: 45 | os.mkdir(certs_path) 46 | 47 | # Generate 16 random bytes and convert to hex 48 | random_hex = binascii.b2a_hex(os.urandom(16)).decode() 49 | serial = os.path.join(certs_path, "./tsa-serial") 50 | with open(serial, mode="w", encoding="utf-8") as file: 51 | file.write(random_hex) 52 | 53 | def main() -> None: 54 | """Wait for all tests certificate, compute leafhash""" 55 | 56 | parser = argparse.ArgumentParser() 57 | parser.add_argument( 58 | "--exe", 59 | type=pathlib.Path, 60 | default=DEFAULT_PYTHON, 61 | help=f"the path to the python3 executable to use" 62 | f"(default: {DEFAULT_PYTHON})", 63 | ) 64 | parser.add_argument( 65 | "--script", 66 | type=pathlib.Path, 67 | default=DEFAULT_PROG, 68 | help=f"the path to the python script to run" 69 | f"(default: {DEFAULT_PROG})", 70 | ) 71 | args = parser.parse_args() 72 | try: 73 | clear_catalog(CERTS_PATH) 74 | #pylint: disable=consider-using-with 75 | subprocess.Popen([str(args.exe), str(args.script)]) 76 | 77 | cert_log = os.path.join(CERTS_PATH, "./cert.log") 78 | while not (os.path.exists(cert_log) and os.path.getsize(cert_log) > 0): 79 | time.sleep(1) 80 | 81 | leafhash = compute_sha256("cert.der") 82 | file_path = os.path.join(CERTS_PATH, "./leafhash.txt") 83 | with open(file_path, mode="w", encoding="utf-8") as file: 84 | file.write("SHA256:{}".format(leafhash)) 85 | 86 | except OSError as err: 87 | with open(SERVER_LOG, mode="w", encoding="utf-8") as file: 88 | file.write("OSError: {}".format(err)) 89 | sys.exit(1) 90 | 91 | except Exception as err: # pylint: disable=broad-except 92 | with open(SERVER_LOG, mode="w", encoding="utf-8") as file: 93 | file.write("Error: {}".format(err)) 94 | sys.exit(1) 95 | 96 | 97 | if __name__ == "__main__": 98 | main() 99 | 100 | 101 | # pylint: disable=pointless-string-statement 102 | """Local Variables: 103 | c-basic-offset: 4 104 | tab-width: 4 105 | indent-tabs-mode: nil 106 | End: 107 | vim: set ts=4 expandtab: 108 | """ 109 | -------------------------------------------------------------------------------- /utf.c: -------------------------------------------------------------------------------- 1 | // utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/ 2 | // 10 november 2016 3 | #include "utf.h" 4 | 5 | // this code imitates Go's unicode/utf8 and unicode/utf16 6 | // the biggest difference is that a rune is unsigned instead of signed (because Go guarantees what a right shift on a signed number will do, whereas C does not) 7 | // it is also an imitation so we can license it under looser terms than the Go source 8 | #define badrune 0xFFFD 9 | 10 | // encoded must be at most 4 bytes 11 | // TODO clean this code up somehow 12 | size_t utf8EncodeRune(uint32_t rune, char *encoded) 13 | { 14 | uint8_t b, c, d, e; 15 | size_t n; 16 | 17 | // not in the valid range for Unicode 18 | if (rune > 0x10FFFF) 19 | rune = badrune; 20 | // surrogate runes cannot be encoded 21 | if (rune >= 0xD800 && rune < 0xE000) 22 | rune = badrune; 23 | 24 | if (rune < 0x80) { // ASCII bytes represent themselves 25 | b = (uint8_t) (rune & 0xFF); 26 | n = 1; 27 | goto done; 28 | } 29 | if (rune < 0x800) { // two-byte encoding 30 | c = (uint8_t) (rune & 0x3F); 31 | c |= 0x80; 32 | rune >>= 6; 33 | b = (uint8_t) (rune & 0x1F); 34 | b |= 0xC0; 35 | n = 2; 36 | goto done; 37 | } 38 | if (rune < 0x10000) { // three-byte encoding 39 | d = (uint8_t) (rune & 0x3F); 40 | d |= 0x80; 41 | rune >>= 6; 42 | c = (uint8_t) (rune & 0x3F); 43 | c |= 0x80; 44 | rune >>= 6; 45 | b = (uint8_t) (rune & 0x0F); 46 | b |= 0xE0; 47 | n = 3; 48 | goto done; 49 | } 50 | // otherwise use a four-byte encoding 51 | e = (uint8_t) (rune & 0x3F); 52 | e |= 0x80; 53 | rune >>= 6; 54 | d = (uint8_t) (rune & 0x3F); 55 | d |= 0x80; 56 | rune >>= 6; 57 | c = (uint8_t) (rune & 0x3F); 58 | c |= 0x80; 59 | rune >>= 6; 60 | b = (uint8_t) (rune & 0x07); 61 | b |= 0xF0; 62 | n = 4; 63 | 64 | done: 65 | encoded[0] = (char)b; 66 | if (n > 1) 67 | encoded[1] = (char)c; 68 | if (n > 2) 69 | encoded[2] = (char)d; 70 | if (n > 3) 71 | encoded[3] = (char)e; 72 | return n; 73 | } 74 | 75 | const char *utf8DecodeRune(const char *s, size_t nElem, uint32_t *rune) 76 | { 77 | uint8_t b, c; 78 | uint8_t lowestAllowed, highestAllowed; 79 | size_t i, expected; 80 | int bad; 81 | 82 | b = (uint8_t) (*s); 83 | if (b < 0x80) { // ASCII bytes represent themselves 84 | *rune = b; 85 | s++; 86 | return s; 87 | } 88 | // 0xC0 and 0xC1 cover 2-byte overlong equivalents 89 | // 0xF5 to 0xFD cover values > 0x10FFFF 90 | // 0xFE and 0xFF were never defined (always illegal) 91 | if (b < 0xC2 || b > 0xF4) { // invalid 92 | *rune = badrune; 93 | s++; 94 | return s; 95 | } 96 | 97 | // this determines the range of allowed first continuation bytes 98 | lowestAllowed = 0x80; 99 | highestAllowed = 0xBF; 100 | switch (b) { 101 | case 0xE0: 102 | // disallow 3-byte overlong equivalents 103 | lowestAllowed = 0xA0; 104 | break; 105 | case 0xED: 106 | // disallow surrogate characters 107 | highestAllowed = 0x9F; 108 | break; 109 | case 0xF0: 110 | // disallow 4-byte overlong equivalents 111 | lowestAllowed = 0x90; 112 | break; 113 | case 0xF4: 114 | // disallow values > 0x10FFFF 115 | highestAllowed = 0x8F; 116 | break; 117 | } 118 | 119 | // and this determines how many continuation bytes are expected 120 | expected = 1; 121 | if (b >= 0xE0) 122 | expected++; 123 | if (b >= 0xF0) 124 | expected++; 125 | if (nElem != 0) { // are there enough bytes? 126 | nElem--; 127 | if (nElem < expected) { // nope 128 | *rune = badrune; 129 | s++; 130 | return s; 131 | } 132 | } 133 | 134 | // ensure that everything is correct 135 | // if not, **only** consume the initial byte 136 | bad = 0; 137 | for (i = 0; i < expected; i++) { 138 | c = (uint8_t) (s[1 + i]); 139 | if (c < lowestAllowed || c > highestAllowed) { 140 | bad = 1; 141 | break; 142 | } 143 | // the old lowestAllowed and highestAllowed is only for the first continuation byte 144 | lowestAllowed = 0x80; 145 | highestAllowed = 0xBF; 146 | } 147 | if (bad) { 148 | *rune = badrune; 149 | s++; 150 | return s; 151 | } 152 | 153 | // now do the topmost bits 154 | if (b < 0xE0) 155 | *rune = b & 0x1F; 156 | else if (b < 0xF0) 157 | *rune = b & 0x0F; 158 | else 159 | *rune = b & 0x07; 160 | s++; // we can finally move on 161 | 162 | // now do the continuation bytes 163 | for (; expected; expected--) { 164 | c = (uint8_t) (*s); 165 | s++; 166 | c &= 0x3F; // strip continuation bits 167 | *rune <<= 6; 168 | *rune |= c; 169 | } 170 | 171 | return s; 172 | } 173 | 174 | // encoded must have at most 2 elements 175 | size_t utf16EncodeRune(uint32_t rune, uint16_t *encoded) 176 | { 177 | uint16_t low, high; 178 | 179 | // not in the valid range for Unicode 180 | if (rune > 0x10FFFF) 181 | rune = badrune; 182 | // surrogate runes cannot be encoded 183 | if (rune >= 0xD800 && rune < 0xE000) 184 | rune = badrune; 185 | 186 | if (rune < 0x10000) { 187 | encoded[0] = (uint16_t) rune; 188 | return 1; 189 | } 190 | 191 | rune -= 0x10000; 192 | low = (uint16_t) (rune & 0x3FF); 193 | rune >>= 10; 194 | high = (uint16_t) (rune & 0x3FF); 195 | encoded[0] = high | 0xD800; 196 | encoded[1] = low | 0xDC00; 197 | return 2; 198 | } 199 | 200 | // TODO see if this can be cleaned up somehow 201 | const uint16_t *utf16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune) 202 | { 203 | uint16_t high, low; 204 | 205 | if (*s < 0xD800 || *s >= 0xE000) { 206 | // self-representing character 207 | *rune = *s; 208 | s++; 209 | return s; 210 | } 211 | if (*s >= 0xDC00) { 212 | // out-of-order surrogates 213 | *rune = badrune; 214 | s++; 215 | return s; 216 | } 217 | if (nElem == 1) { // not enough elements 218 | *rune = badrune; 219 | s++; 220 | return s; 221 | } 222 | high = *s; 223 | high &= 0x3FF; 224 | if (s[1] < 0xDC00 || s[1] >= 0xE000) { 225 | // bad surrogate pair 226 | *rune = badrune; 227 | s++; 228 | return s; 229 | } 230 | s++; 231 | low = *s; 232 | s++; 233 | low &= 0x3FF; 234 | *rune = high; 235 | *rune <<= 10; 236 | *rune |= low; 237 | *rune += 0x10000; 238 | return s; 239 | } 240 | 241 | // TODO find a way to reduce the code in all of these somehow 242 | // TODO find a way to remove u as well 243 | size_t utf8RuneCount(const char *s, size_t nElem) 244 | { 245 | size_t len; 246 | uint32_t rune; 247 | 248 | if (nElem != 0) { 249 | const char *t, *u; 250 | 251 | len = 0; 252 | t = s; 253 | while (nElem != 0) { 254 | u = utf8DecodeRune(t, nElem, &rune); 255 | len++; 256 | nElem -= (size_t)(u - t); 257 | t = u; 258 | } 259 | return len; 260 | } 261 | len = 0; 262 | while (*s) { 263 | s = utf8DecodeRune(s, nElem, &rune); 264 | len++; 265 | } 266 | return len; 267 | } 268 | 269 | size_t utf8UTF16Count(const char *s, size_t nElem) 270 | { 271 | size_t len; 272 | uint32_t rune; 273 | uint16_t encoded[2]; 274 | 275 | if (nElem != 0) { 276 | const char *t, *u; 277 | 278 | len = 0; 279 | t = s; 280 | while (nElem != 0) { 281 | u = utf8DecodeRune(t, nElem, &rune); 282 | len += utf16EncodeRune(rune, encoded); 283 | nElem -= (size_t)(u - t); 284 | t = u; 285 | } 286 | return len; 287 | } 288 | len = 0; 289 | while (*s) { 290 | s = utf8DecodeRune(s, nElem, &rune); 291 | len += utf16EncodeRune(rune, encoded); 292 | } 293 | return len; 294 | } 295 | 296 | size_t utf16RuneCount(const uint16_t *s, size_t nElem) 297 | { 298 | size_t len; 299 | uint32_t rune; 300 | 301 | if (nElem != 0) { 302 | const uint16_t *t, *u; 303 | 304 | len = 0; 305 | t = s; 306 | while (nElem != 0) { 307 | u = utf16DecodeRune(t, nElem, &rune); 308 | len++; 309 | nElem -= (size_t)(u - t); 310 | t = u; 311 | } 312 | return len; 313 | } 314 | len = 0; 315 | while (*s) { 316 | s = utf16DecodeRune(s, nElem, &rune); 317 | len++; 318 | } 319 | return len; 320 | } 321 | 322 | size_t utf16UTF8Count(const uint16_t *s, size_t nElem) 323 | { 324 | size_t len; 325 | uint32_t rune; 326 | char encoded[4]; 327 | 328 | if (nElem != 0) { 329 | const uint16_t *t, *u; 330 | 331 | len = 0; 332 | t = s; 333 | while (nElem != 0) { 334 | u = utf16DecodeRune(t, nElem, &rune); 335 | len += utf8EncodeRune(rune, encoded); 336 | nElem -= (size_t)(u - t); 337 | t = u; 338 | } 339 | return len; 340 | } 341 | len = 0; 342 | while (*s) { 343 | s = utf16DecodeRune(s, nElem, &rune); 344 | len += utf8EncodeRune(rune, encoded); 345 | } 346 | return len; 347 | } 348 | -------------------------------------------------------------------------------- /utf.h: -------------------------------------------------------------------------------- 1 | // utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/ 2 | // 10 november 2016 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | // if nElem == 0, assume the buffer has no upper limit and is '\0' terminated 12 | // otherwise, assume buffer is NOT '\0' terminated but is bounded by nElem *elements* 13 | 14 | extern size_t utf8EncodeRune(uint32_t rune, char *encoded); 15 | extern const char *utf8DecodeRune(const char *s, size_t nElem, uint32_t *rune); 16 | extern size_t utf16EncodeRune(uint32_t rune, uint16_t *encoded); 17 | extern const uint16_t *utf16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune); 18 | 19 | extern size_t utf8RuneCount(const char *s, size_t nElem); 20 | extern size_t utf8UTF16Count(const char *s, size_t nElem); 21 | extern size_t utf16RuneCount(const uint16_t *s, size_t nElem); 22 | extern size_t utf16UTF8Count(const uint16_t *s, size_t nElem); 23 | 24 | #ifdef __cplusplus 25 | } 26 | 27 | // Provide overloads on Windows for using these functions with wchar_t and WCHAR when wchar_t is a keyword in C++ mode (the default). 28 | // Otherwise, you'd need to cast to pass a wchar_t pointer, WCHAR pointer, or equivalent to these functions. 29 | // We use __wchar_t to be independent of the setting; see https://blogs.msdn.microsoft.com/oldnewthing/20161201-00/?p=94836 (ironically posted one day after I initially wrote this code!). 30 | // TODO check this on MinGW-w64 31 | // TODO check this under /Wall 32 | // TODO C-style casts enough? or will that fail in /Wall? 33 | // TODO same for UniChar/unichar on Mac? if both are unsigned then we have nothing to worry about 34 | #if defined(_MSC_VER) 35 | 36 | inline size_t utf16EncodeRune(uint32_t rune, __wchar_t *encoded) 37 | { 38 | return utf16EncodeRune(rune, reinterpret_cast(encoded)); 39 | } 40 | 41 | inline const __wchar_t *utf16DecodeRune(const __wchar_t *s, size_t nElem, uint32_t *rune) 42 | { 43 | const uint16_t *ret; 44 | 45 | ret = utf16DecodeRune(reinterpret_cast(s), nElem, rune); 46 | return reinterpret_cast(ret); 47 | } 48 | 49 | inline size_t utf16RuneCount(const __wchar_t *s, size_t nElem) 50 | { 51 | return utf16RuneCount(reinterpret_cast(s), nElem); 52 | } 53 | 54 | inline size_t utf16UTF8Count(const __wchar_t *s, size_t nElem) 55 | { 56 | return utf16UTF8Count(reinterpret_cast(s), nElem); 57 | } 58 | 59 | #endif 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "osslsigncode", 3 | "version-string": "2.4", 4 | "dependencies": [ 5 | "openssl", 6 | "zlib" 7 | ], 8 | "builtin-baseline": "9edb1b8e590cc086563301d735cae4b6e732d2d2" 9 | } 10 | --------------------------------------------------------------------------------