[OBJECT-16744] Add helpers for signing Nuget packages #57

Merged
PavelGrossSpb merged 2 commits from potyarkin/frostfs-sdk-csharp:feature/code-signing into master 2025-04-11 10:19:26 +00:00
6 changed files with 253 additions and 3 deletions

View file

@ -1,5 +1,4 @@
on:
push:
workflow_dispatch:
jobs:
@ -16,7 +15,7 @@ jobs:
# `dotnet build` implies and replaces `dotnet pack` thanks to `GeneratePackageOnBuild`
run: dotnet build
- name: Publish NuGet packages
- name: Publish unsigned NuGet packages
run: |-
dotnet nuget add source \
--name "$NUGET_REGISTRY" \

5
.gitignore vendored
View file

@ -32,5 +32,8 @@ antlr-*.jar
# binary
bin/
release/
obj/
# Repository signing keys
release/maintainer.*
release/ca.*

58
Makefile Normal file
View file

@ -0,0 +1,58 @@
DOTNET?=dotnet
DOCKER?=docker
NUGET_REGISTRY?=TrueCloudLab
NUGET_REGISTRY_URL?=https://git.frostfs.info/api/packages/TrueCloudLab/nuget/index.json
NUGET_REGISTRY_USER?=
NUGET_REGISTRY_PASSWORD?=
NUPKG=find -iname '*.nupkg' | grep . | xargs -d'\n' -t -r -n1
RFC3161_TSA?=http://timestamp.digicert.com
.PHONY: build
build:
$(DOTNET) build
.PHONY: sign
sign: export NUGET_CERT_REVOCATION_MODE=offline
sign: release/maintainer.pfx
$(NUPKG) $(DOTNET) nuget sign --overwrite --certificate-path $< --timestamper "$(RFC3161_TSA)"
@rm -v "$<" # maintainer.pfx is not password protected and must be ephemeral
$(NUPKG) $(DOTNET) nuget verify
.PHONY: publish
publish:
$(NUPKG) $(DOTNET) nuget verify
$(NUPKG) $(DOTNET) nuget push --source "$(NUGET_REGISTRY)"
.PHONY: nuget-registry
nuget-registry:
ifeq (,$(NUGET_REGISTRY_USER))
$(error NUGET_REGISTRY_USER not set)
endif
ifeq (,$(NUGET_REGISTRY_PASSWORD))
$(error NUGET_REGISTRY_PASSWORD not set)
endif
$(DOTNET) nuget add source \
--name "$(NUGET_REGISTRY)" \
--username "$(NUGET_REGISTRY_USER)" \
--password "$(NUGET_REGISTRY_PASSWORD)" \
--store-password-in-clear-text \
"$(NUGET_REGISTRY_URL)"
.PHONY: clean
clean:
-$(NUPKG) rm -v
.PHONY: container
container:
$(DOCKER) run --pull=always --rm -it -v "$$PWD:/src" -w /src git.frostfs.info/truecloudlab/env:dotnet-8.0
include release/codesign.mk

82
release/README.md Normal file
View file

@ -0,0 +1,82 @@
# Release process
## Preparing release
_TBD_
## Trusting TrueCloudLab code signing CA certificate
Verifying signatures (and signing) TrueCloudLab packages requires adding
[TrueCloudLab Code Signing CA](ca.cert) to the list of trusted roots.
On Linux this can be done by appending [release/ca.cert](ca.cert) to one of:
- `/etc/pki/ca-trust/extracted/pem/objsign-ca-bundle.pem`: compatible with
[update-ca-trust] and originally proposed in [.NET design docs]
- `…/dotnet/sdk/X.Y.ZZZ/trustedroots/codesignctl.pem`: [fallback] codesigning certificate trust list for .NET
[update-ca-trust]: https://www.linux.org/docs/man8/update-ca-trust.html
[.NET design docs]: https://github.com/dotnet/designs/blob/main/accepted/2021/signed-package-verification/re-enable-signed-package-verification-technical.md#linux
[fallback]: https://github.com/dotnet/sdk/blob/11150c0ec9020625308edeec555a8b78dbfb2aa5/src/Layout/redist/trustedroots/README.md
## Signing Nuget packages
Repository maintainer places `maintainer.cert` and `maintainer.key` (see below
regarding obtaining these files) into `release/` directory and then
executes:
```console
$ make build sign
```
## Uploading packages to Nuget registry
**IMPORTANT: the following steps upload all `*.nupkg` files located under
`src/`. Maintainer MUST make sure that no unnecessary package versions will be
uploaded to the registry.**
Configure registry credentials (once per machine):
```console
$ make nuget-registry NUGET_REGISTRY_USER=username NUGET_REGISTRY_PASSWORD=token
```
Publish all locally built packages (implicitly clear existing `*.nupkg` and
rebuild current version only):
```console
$ make clean build sign publish
```
## Obtaining release signing certificate
Repository maintainer owns and keeps safe the release signing key
(`maintainer.key`). Private key should never leave maintainer's machine and
should be considered a highly sensitive secret.
- Generating new maintainer key and the corresponding CSR:
```console
$ make maintainer.csr
...lines skipped...
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
IMPORTANT: Keep maintainer.key private!
Certificate signing request is ready.
Send maintainer.csr to CA administrator to obtain the certificate.
```
Resulting CSR (`maintainer.csr`) does not contain any sensitive
cryptographic material and may be passed to CA administrator through regular
communication channels.
- CA administrator then issues the certificate (`make maintainer.cert`) and
sends it back to the maintainer to be used in combination with
`maintainer.key`
This procedure should be repeated once per machine per `maintainer.cert`
lifetime (1 year) - typically just once per year since we expect the
maintainer to use only a single computer to sign releases.

34
release/ca.cert Normal file
View file

@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIF2zCCA8OgAwIBAgIUa9xC/RgvFtUG/xeR016nn0B4K0YwDQYJKoZIhvcNAQEL
BQAwdTELMAkGA1UEBhMCUlUxFTATBgNVBAoMDFRydWVDbG91ZExhYjEVMBMGA1UE
CwwMVHJ1ZUNsb3VkTGFiMTgwNgYDVQQDDC9UcnVlQ2xvdWRMYWIgQ29kZSBTaWdu
aW5nIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0yNTA0MTAxNTI2MTFaFw0zNTA0
MDgxNTI2MTFaMHUxCzAJBgNVBAYTAlJVMRUwEwYDVQQKDAxUcnVlQ2xvdWRMYWIx
FTATBgNVBAsMDFRydWVDbG91ZExhYjE4MDYGA1UEAwwvVHJ1ZUNsb3VkTGFiIENv
ZGUgU2lnbmluZyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCyANB4cjf+ZEAFx9RiUYXCAOPMV+jyqgcVbhzh2YKc
9SlvGKRlc00Ar1RlFcrycdkIrTKmhhobiWsFp7UgphwLqRTCb5NB6qfUoWhnfiD9
m0OBgeVX5wivVaibRI9PSTbFDcIhYUiNvwFJ6GduH/9zOxf1BvuL7LMaoyhIDcg/
XVLuekE2lnX83zsedv0v/2jyyMY9Ct6N2BXzyHSAzSdYYg0F9Qu9fIMAPjoKhWPc
PnotqaACjb1DScLUr3E/o2W1FfprTT2Pip/0AXxO4wixl4QWh9HeOKV22KRcCHo6
3wNdg5q1ZVGTNBW0+yoB4jsSG8/JM+2Ujhc1ZnYH10armvGq/0Oc2YQE00960Wy8
t0drCFWJUO1XHNeBxkkupmj7N1TAPbixtfiGZJhECOWOJwyMpcKixlt5P0cNH4N/
p3vjyrGQxGLBIkgV/QgjfGkpTHKT1/H40YK6DliWJc01KfNTqn0K+/TIyF0n26kD
BWYVlvDh5P1+V9DGuD2zeXB3PstoifD/Pd7D8wuqpm17noFE19MLp94xv03q9nEa
jRMEd2J2buTLvMh5BBVH0Sm38QAHpSIZ9O3dSLvvjlALbVtwmcsNE9fgxiue3vTB
iXNW8wWs+/DMYwbWyBoCwORxVOdOyc1JLn7qAAEUBweilPVXpWuzMLdUsifPiqrV
dQIDAQABo2MwYTAdBgNVHQ4EFgQUEz4y/RvQMmbUFvf5JbGe/0PZR90wHwYDVR0j
BBgwFoAUEz4y/RvQMmbUFvf5JbGe/0PZR90wDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggIBAF79W9hMGnrKUWLDOcoeXDEn
+gZxd5rjeF0tNEQGbWKGTJAGQdprOkzWQ47PewpFeS+ePpjOglBAt7cV2ZnOugAT
Brx31vYpNAvYnUCYn+/IUqD8S/U4uErVS9zG9QjirZWo56eJP62vnScKuApCQCbA
pp0zrIyJ+2lQKzlMOENRqzYYA/UTOqYTtnW6x2D8goVqCmDXpgpxEp5XliFjJSr6
dOjiopNWMvaV3R/Bnd4i41taM7M6HpIV+gbXmEKEFS0ejVfzT8z1pTigN7GBqbxf
nXD03eLUIsbMDv4ZQPrheN7nKnjRUn8kxz0SSK1m2YDrXW51m8fOs6aTvwC/cNe+
FJMqQMF32i4IXVfUbyUJi+JMvawqm2wEY46vrh7siprY6rXsAzCKJo07i6jvUwnT
TXMldSCPgTEqzT2JBzzr0tRfuPKsv0/NqflHvwfuMRCpcZ7jJZ700iN92xXkiQHP
DmCZOILXcNclAth3nAnyY4XE5a8myv8bwYaPdJdIFlV+BoU/8mClDeA8ck4rDy12
T5YChKew2oiL4j4B6v9/yrDjD1IT0gv4BWyPhb/n390BCEXt8g9auNcT0s6O8kEc
VUDVc1519ocMCuWVuqUK9o2w0zu50/pBn4hVLfT3QyW8sqtlRKghOWtqZzigvCWF
VjATeO5F/Z7OSDebHUGv
-----END CERTIFICATE-----

74
release/codesign.mk Normal file
View file

@ -0,0 +1,74 @@
PKI_ROLE?=maintainer
PKI_DIR?=release
# Note: Only RSA signatures are supported (NU3013)
# https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3013)
ifeq ($(PKI_ROLE),maintainer)
.PHONY: maintainer.csr
maintainer.csr: $(PKI_DIR)/maintainer.csr
$(PKI_DIR)/maintainer.csr: KEY=$(patsubst %.csr,%.key,$@)
$(PKI_DIR)/maintainer.csr:
openssl req \
-new \
-newkey rsa:4096 \
-keyout $(KEY) \
-out $@ \
-sha256 \
-addext keyUsage=critical,digitalSignature \
-addext extendedKeyUsage=critical,codeSigning,msCodeCom \
-subj "/C=RU/O=TrueCloudLab/OU=TrueCloudLab/CN=frostfs-sdk-csharp Release Team"
@echo "IMPORTANT: Keep $(KEY) private!\n"
@echo "Certificate signing request is ready.\nSend $@ to CA administrator to obtain the certificate."
$(PKI_DIR)/maintainer.pfx: $(PKI_DIR)/maintainer.cert $(PKI_DIR)/maintainer.key $(PKI_DIR)/ca.cert
openssl verify \
-CAfile $(PKI_DIR)/ca.cert \
$(PKI_DIR)/maintainer.cert
openssl pkcs12 \
-export \
-out $@ \
-inkey $(PKI_DIR)/maintainer.key \
-in $(PKI_DIR)/maintainer.cert \
-CAfile $(PKI_DIR)/ca.cert \
-chain \
-passout pass:
endif
ifeq ($(PKI_ROLE),ca)
.PHONY: maintainer.cert
maintainer.cert: $(PKI_DIR)/maintainer.cert
$(PKI_DIR)/maintainer.cert: CSR=$(patsubst %.cert,%.csr,$@)
$(PKI_DIR)/maintainer.cert: $(PKI_DIR)/ca.key $(PKI_DIR)/ca.cert
openssl req -noout -text -in $(CSR)
@read -p "Review the CSR above. Press Enter to continue, Ctrl+C to cancel " -r null
openssl x509 \
-req \
-days 365 \
-in $(CSR) \
-copy_extensions copy \
-ext keyUsage,extendedKeyUsage \
-CA $(PKI_DIR)/ca.cert \
-CAkey $(PKI_DIR)/ca.key \
-CAcreateserial \
-out $@
echo >> $@
cat $(PKI_DIR)/ca.cert >> $@
openssl x509 -noout -text -in $@ -fingerprint -sha256
@echo "Certificate is ready.\nSend $@ back to maintainer."
$(PKI_DIR)/ca.key: CERT=$(patsubst %.key,%.cert,$@)
$(PKI_DIR)/ca.key:
openssl req \
-x509 \
-newkey rsa:4096 \
-keyout $@ \
-out $(CERT) \
-sha256 \
-days 3650 \
-addext keyUsage=critical,keyCertSign \
-subj "/C=RU/O=TrueCloudLab/OU=TrueCloudLab/CN=TrueCloudLab Code Signing Certificate Authority"
@echo "IMPORTANT: Keep $@ private!\n"
endif