From ae140cba9b459458acf6fd985b2c980114c82595 Mon Sep 17 00:00:00 2001 From: Andreas Gerstmayr Date: Mon, 7 Jun 2021 17:24:19 +0200 Subject: [PATCH] remove unused crypto, use OpenSSL if FIPS mode is enabled --- 008-remove-unused-frontend-crypto.patch | 26 ++++ 009-patch-unused-backend-crypto.patch | 168 ++++++++++++++++++++++++ 010-fips.patch | 140 ++++++++++++++++++++ Makefile | 17 ++- grafana.spec | 38 +++++- list_bundled_nodejs_packages.py | 3 + sources | 4 +- 7 files changed, 389 insertions(+), 7 deletions(-) create mode 100644 008-remove-unused-frontend-crypto.patch create mode 100644 009-patch-unused-backend-crypto.patch create mode 100644 010-fips.patch diff --git a/008-remove-unused-frontend-crypto.patch b/008-remove-unused-frontend-crypto.patch new file mode 100644 index 0000000..8008075 --- /dev/null +++ b/008-remove-unused-frontend-crypto.patch @@ -0,0 +1,26 @@ +diff --git a/package.json b/package.json +index 280e171804..13468e56bd 100644 +--- a/package.json ++++ b/package.json +@@ -295,7 +295,8 @@ + }, + "resolutions": { + "caniuse-db": "1.0.30000772", +- "react-use-measure": "https://github.com/mckn/react-use-measure.git#remove-cjs-export" ++ "react-use-measure": "https://github.com/mckn/react-use-measure.git#remove-cjs-export", ++ "crypto-browserify": "https://registry.yarnpkg.com/@favware/skip-dependency/-/skip-dependency-1.1.1.tgz" + }, + "workspaces": { + "packages": [ +diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js +index 3e56d31c37..a03ed1a67a 100644 +--- a/scripts/webpack/webpack.common.js ++++ b/scripts/webpack/webpack.common.js +@@ -66,6 +66,7 @@ module.exports = { + }, + node: { + fs: 'empty', ++ crypto: false, + }, + plugins: [ + new MonacoWebpackPlugin({ diff --git a/009-patch-unused-backend-crypto.patch b/009-patch-unused-backend-crypto.patch new file mode 100644 index 0000000..12be571 --- /dev/null +++ b/009-patch-unused-backend-crypto.patch @@ -0,0 +1,168 @@ +diff --git a/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go b/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go +new file mode 100644 +index 0000000..871e612 +--- /dev/null ++++ b/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go +@@ -0,0 +1,25 @@ ++package elgamal ++ ++import ( ++ "io" ++ "math/big" ++) ++ ++// PublicKey represents an ElGamal public key. ++type PublicKey struct { ++ G, P, Y *big.Int ++} ++ ++// PrivateKey represents an ElGamal private key. ++type PrivateKey struct { ++ PublicKey ++ X *big.Int ++} ++ ++func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) { ++ panic("ElGamal encryption not available") ++} ++ ++func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) { ++ panic("ElGamal encryption not available") ++} +diff --git a/vendor/golang.org/x/crypto/openpgp/packet/packet.go b/vendor/golang.org/x/crypto/openpgp/packet/packet.go +index 9728d61..9f04c2d 100644 +--- a/vendor/golang.org/x/crypto/openpgp/packet/packet.go ++++ b/vendor/golang.org/x/crypto/openpgp/packet/packet.go +@@ -16,7 +16,6 @@ import ( + "math/big" + "math/bits" + +- "golang.org/x/crypto/cast5" + "golang.org/x/crypto/openpgp/errors" + ) + +@@ -487,7 +486,7 @@ func (cipher CipherFunction) KeySize() int { + case Cipher3DES: + return 24 + case CipherCAST5: +- return cast5.KeySize ++ panic("cast5 cipher not available") + case CipherAES128: + return 16 + case CipherAES192: +@@ -517,7 +516,7 @@ func (cipher CipherFunction) new(key []byte) (block cipher.Block) { + case Cipher3DES: + block, _ = des.NewTripleDESCipher(key) + case CipherCAST5: +- block, _ = cast5.NewCipher(key) ++ panic("cast5 cipher not available") + case CipherAES128, CipherAES192, CipherAES256: + block, _ = aes.NewCipher(key) + } +diff --git a/vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go b/vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go +index 6126030..3a54c5f 100644 +--- a/vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go ++++ b/vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go +@@ -5,13 +5,12 @@ + package packet + + import ( +- "crypto/cipher" + "crypto/sha1" + "crypto/subtle" +- "golang.org/x/crypto/openpgp/errors" + "hash" + "io" +- "strconv" ++ ++ "golang.org/x/crypto/openpgp/errors" + ) + + // SymmetricallyEncrypted represents a symmetrically encrypted byte string. The +@@ -45,46 +44,7 @@ func (se *SymmetricallyEncrypted) parse(r io.Reader) error { + // packet can be read. An incorrect key can, with high probability, be detected + // immediately and this will result in a KeyIncorrect error being returned. + func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) { +- keySize := c.KeySize() +- if keySize == 0 { +- return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c))) +- } +- if len(key) != keySize { +- return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length") +- } +- +- if se.prefix == nil { +- se.prefix = make([]byte, c.blockSize()+2) +- _, err := readFull(se.contents, se.prefix) +- if err != nil { +- return nil, err +- } +- } else if len(se.prefix) != c.blockSize()+2 { +- return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths") +- } +- +- ocfbResync := OCFBResync +- if se.MDC { +- // MDC packets use a different form of OCFB mode. +- ocfbResync = OCFBNoResync +- } +- +- s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync) +- if s == nil { +- return nil, errors.ErrKeyIncorrect +- } +- +- plaintext := cipher.StreamReader{S: s, R: se.contents} +- +- if se.MDC { +- // MDC packets have an embedded hash that we need to check. +- h := sha1.New() +- h.Write(se.prefix) +- return &seMDCReader{in: plaintext, h: h}, nil +- } +- +- // Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser. +- return seReader{plaintext}, nil ++ panic("OCFB cipher not available") + } + + // seReader wraps an io.Reader with a no-op Close method. +@@ -254,37 +214,5 @@ func (c noOpCloser) Close() error { + // written. + // If config is nil, sensible defaults will be used. + func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, key []byte, config *Config) (contents io.WriteCloser, err error) { +- if c.KeySize() != len(key) { +- return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length") +- } +- writeCloser := noOpCloser{w} +- ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC) +- if err != nil { +- return +- } +- +- _, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion}) +- if err != nil { +- return +- } +- +- block := c.new(key) +- blockSize := block.BlockSize() +- iv := make([]byte, blockSize) +- _, err = config.Random().Read(iv) +- if err != nil { +- return +- } +- s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync) +- _, err = ciphertext.Write(prefix) +- if err != nil { +- return +- } +- plaintext := cipher.StreamWriter{S: s, W: ciphertext} +- +- h := sha1.New() +- h.Write(iv) +- h.Write(iv[blockSize-2:]) +- contents = &seMDCWriter{w: plaintext, h: h} +- return ++ panic("OCFB cipher not available") + } diff --git a/010-fips.patch b/010-fips.patch new file mode 100644 index 0000000..f9adee9 --- /dev/null +++ b/010-fips.patch @@ -0,0 +1,140 @@ +diff --git a/vendor/golang.org/x/crypto/internal/boring/boring.go b/vendor/golang.org/x/crypto/internal/boring/boring.go +new file mode 100644 +index 0000000..a9c550e +--- /dev/null ++++ b/vendor/golang.org/x/crypto/internal/boring/boring.go +@@ -0,0 +1,74 @@ ++// Copyright 2017 The Go Authors. All rights reserved. ++// Copyright 2021 Red Hat. ++// Use of this source code is governed by a BSD-style ++// license that can be found in the LICENSE file. ++ ++// +build linux ++// +build !android ++// +build !no_openssl ++// +build !cmd_go_bootstrap ++// +build !msan ++ ++package boring ++ ++// #include "openssl_pbkdf2.h" ++// #cgo LDFLAGS: -ldl ++import "C" ++import ( ++ "bytes" ++ "crypto/sha1" ++ "crypto/sha256" ++ "hash" ++ "unsafe" ++) ++ ++var ( ++ emptySha1 = sha1.Sum([]byte{}) ++ emptySha256 = sha256.Sum256([]byte{}) ++) ++ ++func hashToMD(h hash.Hash) *C.GO_EVP_MD { ++ emptyHash := h.Sum([]byte{}) ++ ++ switch { ++ case bytes.Equal(emptyHash, emptySha1[:]): ++ return C._goboringcrypto_EVP_sha1() ++ case bytes.Equal(emptyHash, emptySha256[:]): ++ return C._goboringcrypto_EVP_sha256() ++ } ++ return nil ++} ++ ++// charptr returns the address of the underlying array in b, ++// being careful not to panic when b has zero length. ++func charptr(b []byte) *C.char { ++ if len(b) == 0 { ++ return nil ++ } ++ return (*C.char)(unsafe.Pointer(&b[0])) ++} ++ ++// ucharptr returns the address of the underlying array in b, ++// being careful not to panic when b has zero length. ++func ucharptr(b []byte) *C.uchar { ++ if len(b) == 0 { ++ return nil ++ } ++ return (*C.uchar)(unsafe.Pointer(&b[0])) ++} ++ ++func Pbkdf2Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { ++ // println("[debug] using pbkdf2 from OpenSSL") ++ ch := h() ++ md := hashToMD(ch) ++ if md == nil { ++ return nil ++ } ++ ++ out := make([]byte, keyLen) ++ ok := C._goboringcrypto_PKCS5_PBKDF2_HMAC(charptr(password), C.int(len(password)), ucharptr(salt), C.int(len(salt)), C.int(iter), md, C.int(keyLen), ucharptr(out)) ++ if ok != 1 { ++ panic("boringcrypto: PKCS5_PBKDF2_HMAC failed") ++ } ++ return out ++} +diff --git a/vendor/golang.org/x/crypto/internal/boring/notboring.go b/vendor/golang.org/x/crypto/internal/boring/notboring.go +new file mode 100644 +index 0000000..e244fb5 +--- /dev/null ++++ b/vendor/golang.org/x/crypto/internal/boring/notboring.go +@@ -0,0 +1,16 @@ ++// Copyright 2017 The Go Authors. All rights reserved. ++// Copyright 2021 Red Hat. ++// Use of this source code is governed by a BSD-style ++// license that can be found in the LICENSE file. ++ ++// +build !linux !cgo android cmd_go_bootstrap msan no_openssl ++ ++package boring ++ ++import ( ++ "hash" ++) ++ ++func Pbkdf2Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { ++ panic("boringcrypto: not available") ++} +diff --git a/vendor/golang.org/x/crypto/internal/boring/openssl_pbkdf2.h b/vendor/golang.org/x/crypto/internal/boring/openssl_pbkdf2.h +new file mode 100644 +index 0000000..6dfdf10 +--- /dev/null ++++ b/vendor/golang.org/x/crypto/internal/boring/openssl_pbkdf2.h +@@ -0,0 +1,5 @@ ++#include "/usr/lib/golang/src/crypto/internal/boring/goboringcrypto.h" ++ ++DEFINEFUNC(int, PKCS5_PBKDF2_HMAC, ++ (const char *pass, int passlen, const unsigned char *salt, int saltlen, int iter, EVP_MD *digest, int keylen, unsigned char *out), ++ (pass, passlen, salt, saltlen, iter, digest, keylen, out)) +diff --git a/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go +index 593f653..799a611 100644 +--- a/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go ++++ b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go +@@ -19,8 +19,11 @@ pbkdf2.Key. + package pbkdf2 // import "golang.org/x/crypto/pbkdf2" + + import ( ++ "crypto/boring" + "crypto/hmac" + "hash" ++ ++ xboring "golang.org/x/crypto/internal/boring" + ) + + // Key derives a key from the password, salt and iteration count, returning a +@@ -40,6 +43,10 @@ import ( + // Using a higher iteration count will increase the cost of an exhaustive + // search but will also make derivation proportionally slower. + func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { ++ if boring.Enabled() { ++ return xboring.Pbkdf2Key(password, salt, iter, keyLen, h) ++ } ++ + prf := hmac.New(h, password) + hashLen := prf.Size() + numBlocks := (keyLen + hashLen - 1) / hashLen diff --git a/Makefile b/Makefile index 1244a96..9269789 100644 --- a/Makefile +++ b/Makefile @@ -5,16 +5,25 @@ all: grafana-$(VER).tar.gz \ grafana-$(VER).tar.gz: wget https://github.com/grafana/grafana/archive/v$(VER)/grafana-$(VER).tar.gz +ALL_PATCHES := $(wildcard *.patch) +PATCHES_TO_APPLY := $(filter-out 009-patch-unused-backend-crypto.patch 010-fips.patch,$(ALL_PATCHES)) + grafana-vendor-$(VER).tar.xz: grafana-$(VER).tar.gz rm -rf grafana-$(VER) tar xfz grafana-$(VER).tar.gz - # patches can affect Go or Node.js dependencies - cd grafana-$(VER) && shopt -s nullglob && \ - for patch in ../*.patch; do patch -p1 --fuzz=0 < $$patch; done + # patches can affect Go or Node.js dependencies, or the webpack + for patch in $(PATCHES_TO_APPLY); do patch -d grafana-$(VER) -p1 --fuzz=0 < $$patch; done # Go cd grafana-$(VER) && go mod vendor -v + # Remove unused crypto + rm grafana-$(VER)/vendor/golang.org/x/crypto/cast5/cast5.go + rm grafana-$(VER)/vendor/golang.org/x/crypto/ed25519/ed25519.go + rm grafana-$(VER)/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go + rm grafana-$(VER)/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go + rm grafana-$(VER)/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go + rm grafana-$(VER)/vendor/golang.org/x/crypto/openpgp/packet/ocfb.go awk '$$2~/^v/ && $$4 != "indirect" {print "Provides: bundled(golang(" $$1 ")) = " substr($$2, 2)}' grafana-$(VER)/go.mod | \ sed -E 's/=(.*)-(.*)-(.*)/=\1-\2.\3/g' > $@.manifest @@ -22,7 +31,7 @@ grafana-vendor-$(VER).tar.xz: grafana-$(VER).tar.gz cd grafana-$(VER) && yarn install --pure-lockfile # Remove files with licensing issues find grafana-$(VER) -type d -name 'node-notifier' -prune -exec rm -r {} \; - find grafana-$(VER) -name '*.exe' -delete + find grafana-$(VER) -type f -name '*.exe' -delete ./list_bundled_nodejs_packages.py grafana-$(VER)/ >> $@.manifest # Create tarball diff --git a/grafana.spec b/grafana.spec index 3198f99..f459232 100644 --- a/grafana.spec +++ b/grafana.spec @@ -12,9 +12,15 @@ end} # is attached as a webpack tarball (in case of an unsuitable nodejs version on the build system) %define compile_frontend 0 +%if 0%{?rhel} +%define enable_fips_mode 1 +%else +%define enable_fips_mode 0 +%endif + Name: grafana Version: 7.5.7 -Release: 1%{?dist} +Release: 2%{?dist} Summary: Metrics dashboard and graph editor License: ASL 2.0 URL: https://grafana.org @@ -62,6 +68,19 @@ Patch6: 006-fix-gtime-test-32bit.patch Patch7: 007-remove-duplicate-grafana-aws-sdk-dependency.patch +Patch8: 008-remove-unused-frontend-crypto.patch + +# The Makefile removes a few files with crypto implementations +# from the vendor tarball, which are not used in Grafana. +# This patch removes all references to the deleted files. +Patch9: 009-patch-unused-backend-crypto.patch + +%if %{enable_fips_mode} +# This patch modifies the x/crypto/pbkdf2 function to use OpenSSL +# if FIPS mode is enabled. +Patch10: 010-fips.patch +%endif + # Intersection of go_arches and nodejs_arches ExclusiveArch: %{grafana_arches} @@ -69,10 +88,15 @@ BuildRequires: systemd, golang, go-srpm-macros %if 0%{?fedora} >= 31 BuildRequires: go-rpm-macros %endif + %if %{compile_frontend} BuildRequires: nodejs >= 1:14, yarnpkg %endif +%if %{enable_fips_mode} +BuildRequires: openssl-devel +%endif + # omit golang debugsource, see BZ995136 and related %global dwz_low_mem_die_limit 0 %global _debugsource_template %{nil} @@ -451,6 +475,11 @@ rm -r plugins-bundled %patch5 -p1 %patch6 -p1 %patch7 -p1 +%patch8 -p1 +%patch9 -p1 +%if %{enable_fips_mode} +%patch10 -p1 +%endif # Set up build subdirs and links mkdir -p %{_builddir}/src/github.com/grafana @@ -580,6 +609,9 @@ export TZ=GMT %gotest ./pkg/... +%if %{enable_fips_mode} +GOLANG_FIPS=1 go test -v ./pkg/util -run TestEncryption +%endif %files # binaries and wrappers @@ -626,6 +658,10 @@ export TZ=GMT %changelog +* Fri Jun 11 2021 Andreas Gerstmayr 7.5.7-2 +- remove unused cryptographic implementations +- use cryptographic functions from OpenSSL if FIPS mode is enabled + * Tue May 25 2021 Andreas Gerstmayr 7.5.7-1 - update to 7.5.7 tagged upstream community sources, see CHANGELOG diff --git a/list_bundled_nodejs_packages.py b/list_bundled_nodejs_packages.py index a7c5e22..3158c2c 100755 --- a/list_bundled_nodejs_packages.py +++ b/list_bundled_nodejs_packages.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# +# generates Provides: bundled(npm(...)) = ... lines for each declared dependency and devDependency of package.json +# import sys import json import re diff --git a/sources b/sources index 46d5ab2..a3d0a2c 100644 --- a/sources +++ b/sources @@ -1,3 +1,3 @@ SHA512 (grafana-7.5.7.tar.gz) = e8adbfffca91bfb43cf810b9e6b0fa6a0abe765ae4a45f6a1add09c35b1a5bc1f241dc91fad69669b437bfcd107b46f7a09bee9d3272670aaf6e4e501a84edec -SHA512 (grafana-webpack-7.5.7.tar.gz) = 0a40019e859f5c658d6cc25df9504ca350bda48bd2641a87a5efdd9244b7644e19f84d035ca3b4c90263d16d39029b762bed0f3ac00496a27601255fa1437031 -SHA512 (grafana-vendor-7.5.7.tar.xz) = 19a90e6cc7442b7575a1de035441d3b0dd8a347cddd766ad161b4f5e409fdf20cdf68d0cf8dfcd377ea150e800bbe79f5012b161882b014fbb102a128399f3ea +SHA512 (grafana-webpack-7.5.7.tar.gz) = 9a4fc0ff83ef607cf15529aa32b48178de4fdcc16deafd0409ffd6e21d284fe9f897986ba7b68ffdbf1984731cd040d789aa3246896b9da73d31d57b8a0b3389 +SHA512 (grafana-vendor-7.5.7.tar.xz) = 0b22e9d9bb3f6a9ab0dabdca76d2ec43e43f79ea2a3d34d09d1855d5c4aadf1fcc42ebd127d989b71a93bd527971ad7796fd48a99421df8f4376d9247d67d2ce