Aller au contenu principal
S'applique à :
DuoKey Cockpit v2SignTool · jarsigner · Git · cosignAPI de signature à distance

Vue d'ensemble​

Une application de signature de code associe une clé de vault non exportable, éventuellement une chaîne de certificats de signature de code et éventuellement une politique d'approbation. Les machines de build et les développeurs envoient un condensat ou une petite charge utile à l'API de signature ; le vault signe ; la signature revient à l'outil standard, qui l'intègre. La clé ne quitte jamais le vault, chaque signature peut exiger une approbation M-of-N, et chaque signature est inscrite au journal de l'application.

Ce qui est pris en charge​

UsageOutilFonctionnement
Authenticode (EXE, DLL, MSI, CAB…)Microsoft SignToolsigntool sign /dg écrit le condensat, le client DuoKey le fait signer par le vault, signtool sign /di intègre la signature
Archives Java (JAR)Moteur de signature JAR du JDK, via le signataire JAR DuoKey ; vérification avec jarsigner -verifyLe JDK construit le bloc de signature JAR ; l'opération de signature est réalisée par DuoKey
Commits et tags GitSignature SSH de Git (gpg.format=ssh) avec le client DuoKey comme gpg.ssh.programSignature OpenSSH réalisée avec la clé de l'application
Images de conteneurscosign generate / cosign attach signature / cosign verify --keyLa charge utile de signature est signée via le client DuoKey
Toute signature détachéeTout outil capable de faire signer un condensat ou un petit fichier en HTTPSPOST /api/apps/{id}/code-sign/sign
Modules noyau, macros VBA Office—Les types d'application existent et utilisent la même API de signature ; aucune intégration d'outil n'est fournie

Non pris en charge : la signature Notation (Notary Project) et les fournisseurs PKCS#11 ou Windows CNG locaux pour la signature de code. Les outils s'intègrent via l'API de signature à distance et les clients de référence DuoKey : dkesign.py (Python 3.8+, bibliothèque standard uniquement) et DuoKeyJarSigner.java (JDK 17+, sans dépendance), disponibles auprès du support DuoKey.

Modèle de sécurité​

  • La clé reste dans le vault. La clé associée doit être RSA 2048/4096 ou EC P-256/P-384, avoir la capacité de signature et avoir été créée non exportable. L'exportabilité est à sens unique (une clé non exportable ne peut jamais devenir exportable), et tous les chemins d'export de clé la refusent.
  • Pas de contournement de l'application. L'API générique de signature par clé refuse une clé associée à une application de signature de code, et une clé ne sert qu'à une application par tenant : une seconde application sans approbation ne peut pas utiliser la même clé.
  • Approbation. Une application peut exiger que chaque signature soit approuvée selon une politique de quorum qui impose le double contrôle (seuil de 2 ou plus, ou demandeur exclu des approbateurs). L'approbation est liée à l'application, au condensat, à l'algorithme et au nom de l'artefact, utilisable une seule fois et par le seul demandeur. Désactiver l'approbation, la déplacer vers une autre politique ou supprimer l'application nécessite elle-même une approbation.
  • Journal. Chaque signature est journalisée avant l'appel au vault puis complétée, si bien qu'aucune signature ne sort sans son enregistrement. Les signatures, modifications d'application et demandes de certificat sont aussi inscrites au journal d'activité.
  • Permissions. Signer et vérifier nécessitent Operations.Apps.CodeSignAccess ; lire la configuration et le journal nécessite Operations.Apps.Read. Les accès sont limités au tenant et à l'unité organisationnelle. Les politiques cryptographiques s'appliquent à chaque signature.
  • Identifiants. Les clients s'authentifient avec Authorization: Bearer <token> : une clé d'API machine (stockée uniquement sous forme de hachage) ou un jeton de session. La clé ne figure jamais dans une URL et n'est jamais journalisée.

Mettre en place une application​

1

Créer la clé

Créez une clé RSA ou EC dans un vault avec l'option exportable désactivée et l'opération Sign activée. Pour Authenticode, choisissez un vault qui signe des condensats précalculés (voir Prise en charge des vaults).

2

Créer l'application

Dans l'assistant d'application, choisissez SignTool, signature JAR, signature de commits Git ou signature de conteneurs, puis sélectionnez la clé.

3

Ajouter un certificat (Authenticode et JAR)

Générez une demande de certificat signée par la clé du vault (CSR sur la page de l'application), faites émettre un certificat de signature de code par une CA et enregistrez la chaîne (certificat final en premier). La chaîne est refusée si le certificat final ne certifie pas la clé, a expiré ou porte une utilisation étendue sans signature de code. Les signatures Git et de conteneurs ne nécessitent pas de certificat.

4

Exiger une approbation (optionnel)

Activez approbation requise et choisissez une politique de quorum à double contrôle sur la page de l'application.

5

Créer une clé machine pour la CI

Créez une clé d'API pour un utilisateur de service qui ne détient que Operations.Apps.CodeSignAccess et Operations.Apps.Read.

Certificats de signature de code publiquement reconnus

Les exigences du CA/Browser Forum pour la signature de code imposent que les clés de signature de code publiquement reconnues soient détenues dans du matériel. Utilisez un vault adossé à un HSM et suivez la procédure d'attestation de clé de votre CA ; Cockpit ne produit pas de déclaration d'attestation pour les clés de signature de code.

Prise en charge des vaults​

La signature d'une charge utile (data_b64) fonctionne sur tout vault qui signe. La signature d'un condensat calculé par l'appelant (digest_b64) — exigée par le flux /dg de SignTool — est disponible sur le Software Vault, Azure Key Vault / Managed HSM et Google Cloud KMS ; les autres vaults répondent 400 et demandent la charge utile. Authenticode nécessite donc l'un de ces trois vaults. Les clés EC P-256 signent en SHA-256, les clés EC P-384 en SHA-384.

Recettes​

Environnement commun du client DuoKey (en CI, mappez explicitement le secret et ne l'affichez jamais) :

export DKE_URL=https://cockpit.example.com
export DKE_CODESIGN_APP=<application uuid>
export DKE_API_KEY=<machine API key> # secret
export DKE_APPROVAL_WAIT=0 # seconds to wait for approvers; 0 = exit 3 if pending
python3 dkesign.py config --out-dir signing # public key, OpenSSH key and certificate chain

Authenticode avec SignTool​

rem 1. digest of the file's to-be-signed content
signtool sign /dg out /fd SHA256 /f signing\leaf.cer app.exe
rem 2. vault signature of that digest
python dkesign.py sign-digest --digest-file out\app.exe.dig --out out\app.exe.dig.signed --artifact app.exe
rem 3. embed it
signtool sign /di out app.exe
rem 4. optional RFC 3161 timestamp from your timestamping authority
signtool timestamp /tr https://tsa.example.com /td SHA256 app.exe
signtool verify /pa /v app.exe

Utilisez le même /fd que l'algorithme de condensat du client. Produisez le certificat final en DER avec openssl x509 -in signing/chain.pem -outform DER -out signing/leaf.cer. Utilisez un certificat de signature avec une durée de validité normale : SignTool ne sélectionne pas les certificats dont la date d'expiration est extrême.

Signature JAR​

java DuoKeyJarSigner.java app.jar app-signed.jar SHA256
jarsigner -verify -verbose -certs app-signed.jar

Le moteur de signature JAR du JDK construit la signature ; le nom du signataire dans META-INF est DUOKEY. L'horodatage n'est pas intégré au signataire JAR de référence.

Signature de commits et tags Git​

python3 dkesign.py config --out-dir ~/.config/duokey-signing
git config --global gpg.format ssh
git config --global user.signingkey "key::$(cat ~/.config/duokey-signing/signing-key.pub)"
git config --global gpg.ssh.program /usr/local/bin/dkesign-ssh
git config --global commit.gpgsign true # and/or tag.gpgSign true

/usr/local/bin/dkesign-ssh :

#!/bin/sh
exec python3 /opt/duokey/dkesign.py ssh-keygen "$@"

Vérifiez avec un fichier de signataires autorisés (git config gpg.ssh.allowedSignersFile allowed_signers, puis git verify-commit HEAD). Enregistrez la même clé publique comme clé de signature de l'auteur sur votre service d'hébergement Git pour que les commits apparaissent comme vérifiés.

Images de conteneurs avec cosign​

cosign generate registry.example.com/app@sha256:<digest> > payload.json
python3 dkesign.py sign-file --in payload.json --out payload.sig \
--artifact registry.example.com/app@sha256:<digest>
cosign attach signature --payload payload.json --signature payload.sig \
registry.example.com/app@sha256:<digest>
cosign verify --key signing/public-key.pem --insecure-ignore-tlog=true \
registry.example.com/app@sha256:<digest>

Signez les images par condensat, pas par tag. --insecure-ignore-tlog=true est nécessaire car la signature n'est pas publiée dans un journal de transparence ; la vérification repose sur la clé publique de l'application.

Pipelines CI​

La clé d'API machine est une variable secrète mappée dans l'environnement de l'étape. Le client ne l'envoie que dans l'en-tête Authorization : elle n'apparaît jamais dans une URL, une ligne de commande ou un journal. Lorsque l'approbation est requise, DKE_APPROVAL_WAIT fait renvoyer la demande toutes les 15 secondes jusqu'à la décision des approbateurs (ou la fin de l'attente, code de sortie 3). L'approbation est liée au condensat : l'artefact signé est exactement celui que les approbateurs ont vu.

Les exemples ci-dessous montrent le principe ; adaptez les chemins et les étapes de build à votre pipeline.

Azure Pipelines​

# Variable group "duokey-signing": DKE_URL, DKE_CODESIGN_APP (plain),
# DKE_API_KEY (secret, lock icon).
variables:
- group: duokey-signing

jobs:
- job: sign_windows
pool: { vmImage: windows-latest }
steps:
- script: dotnet publish -c Release -o out
displayName: Build
- pwsh: |
python tools/dkesign.py config --out-dir signing
openssl x509 -in signing/chain.pem -outform DER -out signing/leaf.cer
signtool sign /dg dig /fd SHA256 /f signing/leaf.cer out/app.exe
python tools/dkesign.py sign-digest --digest-file dig/app.exe.dig --out dig/app.exe.dig.signed --artifact "app.exe $(Build.BuildNumber)"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
signtool sign /di dig out/app.exe
signtool verify /pa out/app.exe
displayName: Sign with DuoKey
env:
DKE_URL: $(DKE_URL)
DKE_CODESIGN_APP: $(DKE_CODESIGN_APP)
DKE_API_KEY: $(DKE_API_KEY) # secret variables are only visible when mapped
DKE_APPROVAL_WAIT: "1800" # wait up to 30 min for approvers

GitHub Actions​

on: { push: { tags: ["v*"] } }
permissions: { contents: read }

jobs:
sign-jar:
runs-on: ubuntu-latest
environment: release # protect the secret with environment reviewers if wanted
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: "21" }
- run: ./gradlew jar
- name: Sign with DuoKey
env:
DKE_URL: ${{ vars.DKE_URL }}
DKE_CODESIGN_APP: ${{ vars.DKE_CODESIGN_APP }}
DKE_API_KEY: ${{ secrets.DKE_API_KEY }}
DKE_APPROVAL_WAIT: "1800"
run: |
java tools/DuoKeyJarSigner.java build/libs/app.jar build/libs/app-signed.jar SHA256
jarsigner -verify build/libs/app-signed.jar

sign-image:
runs-on: ubuntu-latest
permissions: { contents: read, packages: write }
steps:
- uses: actions/checkout@v4
- uses: sigstore/cosign-installer@v3
- uses: docker/login-action@v3
with: { registry: ghcr.io, username: "${{ github.actor }}", password: "${{ secrets.GITHUB_TOKEN }}" }
- id: build
uses: docker/build-push-action@v6
with: { push: true, tags: "ghcr.io/${{ github.repository }}:${{ github.ref_name }}" }
- name: Sign image
env:
DKE_URL: ${{ vars.DKE_URL }}
DKE_CODESIGN_APP: ${{ vars.DKE_CODESIGN_APP }}
DKE_API_KEY: ${{ secrets.DKE_API_KEY }}
IMAGE: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
run: |
cosign generate "$IMAGE" > payload.json
python3 tools/dkesign.py sign-file --in payload.json --out payload.sig --artifact "$IMAGE"
cosign attach signature --payload payload.json --signature payload.sig "$IMAGE"

API de signature​

Toutes les routes sont sous /api et authentifiées avec Authorization: Bearer.

RouteRôle
POST /apps/code-sign, GET /apps/code-sign, GET / PUT / DELETE /apps/code-sign/{id}Créer, lister, lire, modifier (nom, chaîne de certificats, approbation), supprimer. Affaiblir l'approbation ou supprimer répond 202 jusqu'à approbation
POST /apps/{id}/code-sign/enable, /disableUne application désactivée refuse de signer
GET /apps/{id}/code-sign/configClé publique (PEM et OpenSSH), chaîne de certificats, algorithmes de condensat. Rien de secret
POST /apps/{id}/code-sign/signSigner digest_b64 (condensat calculé par l'appelant) ou data_b64 (1 Mio au plus), avec digest_algorithm (sha256, sha384, sha512), rsa_padding (pkcs1 ou pss) et artifact_name. Répond 202 avec un request_id tant que l'approbation est en attente
POST /apps/{id}/code-sign/verifyVérifier une signature avec la clé publique de l'application
POST /apps/{id}/code-sign/csrDemande PKCS#10 pour la clé, signée dans le vault
GET /apps/{id}/code-sign/healthActivée, clé non exportable et utilisable, vault joignable, certificat valide, politique d'approbation toujours à double contrôle
GET /apps/{id}/code-sign/operationsJournal de signature

Lorsqu'une signature attend une approbation, les approbateurs décident dans Demandes de quorum ; le demandeur renvoie ensuite la même demande et reçoit la signature. Répéter la demande pendant l'attente renvoie le même request_id.

Limites​

  • Notation, les fournisseurs PKCS#11 / CNG locaux pour la signature de code et les déclarations d'attestation de clé ne sont pas fournis.
  • Authenticode nécessite un vault qui signe des condensats précalculés (Software Vault, Azure Key Vault / Managed HSM, Google Cloud KMS).
  • Le signataire JAR de référence n'ajoute pas d'horodatage RFC 3161 ; utilisez la commande timestamp de SignTool pour Authenticode.
  • La signature de modules noyau et de macros Office n'est disponible que par l'API.