mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2024-10-01 01:06:10 -04:00
Add basic signing of app bundle and binaries (#2472)
Adds verification functionality to codesign script Adds required context to enable XCode to perform the signing Adds install time check + signing for all binaries Adds instructions allowing macdeployqt to sign the finalized app bundle Signed-off-by: John Parent <john.parent@kitware.com>
This commit is contained in:
parent
dc6d01a0bb
commit
23e8b187a4
@ -56,6 +56,15 @@ jobs:
|
|||||||
key: macos-qt-cache-v3
|
key: macos-qt-cache-v3
|
||||||
paths:
|
paths:
|
||||||
- ~/Qt
|
- ~/Qt
|
||||||
|
- run:
|
||||||
|
name: Setup Keychain
|
||||||
|
command: |
|
||||||
|
echo $MAC_SIGNING_CERT | base64 --decode > cert.p12
|
||||||
|
security create-keychain -p "$MAC_KEYCHAIN_KEY" sign.keychain
|
||||||
|
security default-keychain -s sign.keychain
|
||||||
|
security unlock-keychain -p "$MAC_KEYCHAIN_KEY" sign.keychain
|
||||||
|
security import cert.p12 -k sign.keychain -P "$MAC_SIGNING_CERT_PWD" -T /usr/bin/codesign
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MAC_KEYCHAIN_KEY" sign.keychain
|
||||||
- run:
|
- run:
|
||||||
name: Build
|
name: Build
|
||||||
command: |
|
command: |
|
||||||
@ -67,6 +76,7 @@ jobs:
|
|||||||
-DBUILD_UNIVERSAL=ON \
|
-DBUILD_UNIVERSAL=ON \
|
||||||
-DMACDEPLOYQT=~/Qt/6.5.1/macos/bin/macdeployqt \
|
-DMACDEPLOYQT=~/Qt/6.5.1/macos/bin/macdeployqt \
|
||||||
-DGPT4ALL_OFFLINE_INSTALLER=ON \
|
-DGPT4ALL_OFFLINE_INSTALLER=ON \
|
||||||
|
-DGPT4ALL_SIGN_INSTALL=ON \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_PREFIX_PATH:PATH=~/Qt/6.5.1/macos/lib/cmake/Qt6 \
|
-DCMAKE_PREFIX_PATH:PATH=~/Qt/6.5.1/macos/lib/cmake/Qt6 \
|
||||||
-DCMAKE_MAKE_PROGRAM:FILEPATH=~/Qt/Tools/Ninja/ninja \
|
-DCMAKE_MAKE_PROGRAM:FILEPATH=~/Qt/Tools/Ninja/ninja \
|
||||||
|
@ -33,6 +33,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
|
|
||||||
option(GPT4ALL_LOCALHOST OFF "Build installer for localhost repo")
|
option(GPT4ALL_LOCALHOST OFF "Build installer for localhost repo")
|
||||||
option(GPT4ALL_OFFLINE_INSTALLER "Build an offline installer" OFF)
|
option(GPT4ALL_OFFLINE_INSTALLER "Build an offline installer" OFF)
|
||||||
|
option(GPT4ALL_SIGN_INSTALL "Sign installed binaries and installers (requires signing identities)" OFF)
|
||||||
|
|
||||||
# Generate a header file with the version number
|
# Generate a header file with the version number
|
||||||
configure_file(
|
configure_file(
|
||||||
@ -220,6 +221,13 @@ set_target_properties(chat PROPERTIES
|
|||||||
WIN32_EXECUTABLE TRUE
|
WIN32_EXECUTABLE TRUE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
macro(REPORT_MISSING_SIGNING_CONTEXT)
|
||||||
|
message(FATAL_ERROR [=[
|
||||||
|
Signing requested but no identity configured.
|
||||||
|
Please set the correct env variable or provide the MAC_SIGNING_IDENTITY argument on the command line
|
||||||
|
]=])
|
||||||
|
endmacro()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
set_target_properties(chat PROPERTIES
|
set_target_properties(chat PROPERTIES
|
||||||
MACOSX_BUNDLE TRUE
|
MACOSX_BUNDLE TRUE
|
||||||
@ -230,6 +238,28 @@ if (APPLE)
|
|||||||
OUTPUT_NAME gpt4all
|
OUTPUT_NAME gpt4all
|
||||||
)
|
)
|
||||||
add_dependencies(chat ggml-metal)
|
add_dependencies(chat ggml-metal)
|
||||||
|
|
||||||
|
if(NOT MAC_SIGNING_IDENTITY)
|
||||||
|
if(NOT DEFINED ENV{MAC_SIGNING_CERT_NAME} AND GPT4ALL_SIGN_INSTALL)
|
||||||
|
REPORT_MISSING_SIGNING_CONTEXT()
|
||||||
|
endif()
|
||||||
|
set(MAC_SIGNING_IDENTITY $ENV{MAC_SIGNING_CERT_NAME})
|
||||||
|
endif()
|
||||||
|
if(NOT MAC_SIGNING_TID)
|
||||||
|
if(NOT DEFINED ENV{MAC_NOTARIZATION_TID} AND GPT4ALL_SIGN_INSTALL)
|
||||||
|
REPORT_MISSING_SIGNING_CONTEXT()
|
||||||
|
endif()
|
||||||
|
set(MAC_SIGNING_TID $ENV{MAC_NOTARIZATION_TID})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Setup MacOS signing for individual binaries
|
||||||
|
set_target_properties(chat PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Manual"
|
||||||
|
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${MAC_SIGNING_TID}
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ${MAC_SIGNING_IDENTITY}
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED True
|
||||||
|
XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--timestamp=http://timestamp.apple.com/ts01 --options=runtime,library"
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_compile_definitions(chat
|
target_compile_definitions(chat
|
||||||
@ -254,6 +284,10 @@ target_link_libraries(chat
|
|||||||
|
|
||||||
# -- install --
|
# -- install --
|
||||||
|
|
||||||
|
function(install_sign_osx tgt)
|
||||||
|
install(CODE "execute_process(COMMAND codesign --options runtime --timestamp -s \"${MAC_SIGNING_IDENTITY}\" $<TARGET_FILE:${tgt}>)")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
set(COMPONENT_NAME_MAIN ${PROJECT_NAME})
|
set(COMPONENT_NAME_MAIN ${PROJECT_NAME})
|
||||||
|
|
||||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
@ -296,6 +330,14 @@ install(
|
|||||||
RUNTIME DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
RUNTIME DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(APPLE AND GPT4ALL_SIGN_INSTALL)
|
||||||
|
install_sign_osx(chat)
|
||||||
|
install_sign_osx(llmodel)
|
||||||
|
foreach(tgt ${MODEL_IMPL_TARGETS})
|
||||||
|
install_sign_osx(${tgt})
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
if (LLMODEL_CUDA)
|
if (LLMODEL_CUDA)
|
||||||
set_property(TARGET llamamodel-mainline-cuda llamamodel-mainline-cuda-avxonly
|
set_property(TARGET llamamodel-mainline-cuda llamamodel-mainline-cuda-avxonly
|
||||||
APPEND PROPERTY INSTALL_RPATH "$ORIGIN")
|
APPEND PROPERTY INSTALL_RPATH "$ORIGIN")
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
set(MACDEPLOYQT "@MACDEPLOYQT@")
|
set(MACDEPLOYQT "@MACDEPLOYQT@")
|
||||||
set(COMPONENT_NAME_MAIN "@COMPONENT_NAME_MAIN@")
|
set(COMPONENT_NAME_MAIN "@COMPONENT_NAME_MAIN@")
|
||||||
set(CMAKE_CURRENT_SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@")
|
set(CMAKE_CURRENT_SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@")
|
||||||
execute_process(COMMAND ${MACDEPLOYQT} ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -verbose=2)
|
set(GPT4ALL_SIGNING_ID "@MAC_SIGNING_IDENTITY@")
|
||||||
|
execute_process(COMMAND ${MACDEPLOYQT} ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -verbose=2 -sign-for-notarization=${GPT4ALL_SIGNING_ID})
|
||||||
file(GLOB MYGPTJLIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libgptj*)
|
file(GLOB MYGPTJLIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libgptj*)
|
||||||
file(GLOB MYLLAMALIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libllama*)
|
file(GLOB MYLLAMALIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libllama*)
|
||||||
file(GLOB MYLLMODELLIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libllmodel.*)
|
file(GLOB MYLLMODELLIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libllmodel.*)
|
||||||
|
@ -4,6 +4,7 @@ import subprocess
|
|||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
import click
|
import click
|
||||||
|
import re
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
# Requires click
|
# Requires click
|
||||||
@ -20,7 +21,8 @@ from typing import Optional
|
|||||||
@click.option('--output-dmg', required=True, help='Path to the output signed DMG file.')
|
@click.option('--output-dmg', required=True, help='Path to the output signed DMG file.')
|
||||||
@click.option('--sha1-hash', help='SHA-1 hash of the Developer ID Application certificate')
|
@click.option('--sha1-hash', help='SHA-1 hash of the Developer ID Application certificate')
|
||||||
@click.option('--signing-identity', default=None, help='Common name of the Developer ID Application certificate')
|
@click.option('--signing-identity', default=None, help='Common name of the Developer ID Application certificate')
|
||||||
def sign_dmg(input_dmg: str, output_dmg: str, signing_identity: Optional[str] = None, sha1_hash: Optional[str] = None) -> None:
|
@click.option('--verify', is_flag=True, show_default=True, required=False, default=False, help='Perform verification of signed app bundle' )
|
||||||
|
def sign_dmg(input_dmg: str, output_dmg: str, signing_identity: Optional[str] = None, sha1_hash: Optional[str] = None, verify: Optional[bool] = False) -> None:
|
||||||
if not signing_identity and not sha1_hash:
|
if not signing_identity and not sha1_hash:
|
||||||
print("Error: Either --signing-identity or --sha1-hash must be provided.")
|
print("Error: Either --signing-identity or --sha1-hash must be provided.")
|
||||||
exit(1)
|
exit(1)
|
||||||
@ -64,6 +66,43 @@ def sign_dmg(input_dmg: str, output_dmg: str, signing_identity: Optional[str] =
|
|||||||
shutil.rmtree(mount_point)
|
shutil.rmtree(mount_point)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
# Validate signature and entitlements of signed app bundle
|
||||||
|
if verify:
|
||||||
|
try:
|
||||||
|
code_ver_proc = subprocess.run([
|
||||||
|
'codesign',
|
||||||
|
'--deep',
|
||||||
|
'--verify',
|
||||||
|
'--verbose=2',
|
||||||
|
'--strict',
|
||||||
|
app_bundle
|
||||||
|
], check=True, capture_output=True)
|
||||||
|
if not re.search(fr"{app_bundle}: valid", code_ver_proc.stdout.decode()):
|
||||||
|
raise RuntimeError(f"codesign validation failed: {code_ver_proc.stdout.decode()}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error during codesign validation: {e}")
|
||||||
|
# Clean up temporary directories
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
shutil.rmtree(mount_point)
|
||||||
|
exit(1)
|
||||||
|
try:
|
||||||
|
spctl_proc = subprocess.run([
|
||||||
|
'spctl',
|
||||||
|
'-a',
|
||||||
|
'-t',
|
||||||
|
'exec',
|
||||||
|
'-vv',
|
||||||
|
app_bundle
|
||||||
|
], check=True, capture_output=True)
|
||||||
|
if not re.search(fr"{app_bundle}: accepted", spctl_proc.stdout.decode()):
|
||||||
|
raise RuntimeError(f"spctl validation failed: {spctl_proc.stdout.decode()}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error during spctl validation: {e}")
|
||||||
|
# Clean up temporary directories
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
shutil.rmtree(mount_point)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
# Create a new DMG containing the signed .app bundle
|
# Create a new DMG containing the signed .app bundle
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
'hdiutil', 'create',
|
'hdiutil', 'create',
|
||||||
|
Loading…
Reference in New Issue
Block a user