From 9248874a9ec1bbfa1385bfc2d1b14fb148993db0 Mon Sep 17 00:00:00 2001 From: Vitaliy Potyarkin Date: Wed, 9 Apr 2025 17:09:12 +0300 Subject: [PATCH] [OBJECT-16744] Add helpers for signing Nuget packages Signed-off-by: Vitaliy Potyarkin --- .gitignore | 5 ++- Makefile | 58 ++++++++++++++++++++++++++++++++ release/README.md | 82 +++++++++++++++++++++++++++++++++++++++++++++ release/ca.cert | 34 +++++++++++++++++++ release/codesign.mk | 74 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 release/README.md create mode 100644 release/ca.cert create mode 100644 release/codesign.mk diff --git a/.gitignore b/.gitignore index ae0b81f..449284e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,8 @@ antlr-*.jar # binary bin/ -release/ obj/ + +# Repository signing keys +release/maintainer.* +release/ca.* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f02478e --- /dev/null +++ b/Makefile @@ -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 diff --git a/release/README.md b/release/README.md new file mode 100644 index 0000000..96f8c25 --- /dev/null +++ b/release/README.md @@ -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. diff --git a/release/ca.cert b/release/ca.cert new file mode 100644 index 0000000..4f4c2f4 --- /dev/null +++ b/release/ca.cert @@ -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----- diff --git a/release/codesign.mk b/release/codesign.mk new file mode 100644 index 0000000..1c39361 --- /dev/null +++ b/release/codesign.mk @@ -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