Skip to content

Commit

Permalink
Add helper to verify pgp v3 clear signed message
Browse files Browse the repository at this point in the history
it adds GetVerifiedClearsignedMsgV3() which returns clear text  msg
and no error if the signature is valid for the supplied pubkey

this uses the golang.org/x/crypto/openpgp library as the maintained
fork of it that is previously used doesn't support the old v3 signs
the other fork of x/crypto at github.com/keybase/go-crypto has less
active contributions
  • Loading branch information
anjannath authored and praveenkumar committed May 5, 2023
1 parent 496d03e commit d7c26f5
Show file tree
Hide file tree
Showing 6 changed files with 571 additions and 19 deletions.
18 changes: 0 additions & 18 deletions pkg/crc/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,6 @@ const (

OpenShiftIngressHTTPPort = 80
OpenShiftIngressHTTPSPort = 443

// This public key is owned by the CRC team (crc@crc.dev), and is used
// to sign bundles uploaded to an image registry.
// It can be fetched with: `gpg --recv-key DC7EAC400A1BFDFB`
GPGPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEYrvgDRYJKwYBBAHaRw8BAQdAoW+hjSRYpTAdLEE1u6ZuYNER1g97e8ygT4ic
mvo1AKi0MmNyYyAoS2V5IHRvIHNpZ24gYnVuZGxlIHVzZWQgYnkgY3JjKSA8Y3Jj
QGNyYy5kZXY+iJkEExYKAEEWIQS4RlW/rByOBn/ZyofcfqxAChv9+wUCYrvgDQIb
AwUJEswDAAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRDcfqxAChv9+/ep
APwISi03R7npwimqdL7NYKDGMO8ikOwmmPkqh9CKwt4CdwD8Cc6HNcZumHDpJ4gH
x7FXxIS9KLwDihpm1Gxr4t1t5Qy4OARiu+ANEgorBgEEAZdVAQUBAQdA/w7pM7hf
bxZ2qwSuoBuhcA1sAlPSb3NrIZf3CceoqzQDAQgHiH4EGBYKACYWIQS4RlW/rByO
Bn/ZyofcfqxAChv9+wUCYrvgDQIbDAUJEswDAAAKCRDcfqxAChv9+2UkAQCNCdaf
vnhbvfPHDltmwDZ3aD4l3jjSKpeySeKQocgjQAD6A7kawst/50k4wb+vUDUnEoYo
9Ix7lKfKWCXil/z0vg4=
=lmb/
-----END PGP PUBLIC KEY BLOCK-----`
)

var adminHelperExecutableForOs = map[string]string{
Expand Down
50 changes: 50 additions & 0 deletions pkg/crc/constants/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package constants

const (
// This public key is owned by the CRC team (crc@crc.dev), and is used
// to sign bundles uploaded to an image registry.
// It can be fetched with: `gpg --recv-key DC7EAC400A1BFDFB`
CrcOrgPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEYrvgDRYJKwYBBAHaRw8BAQdAoW+hjSRYpTAdLEE1u6ZuYNER1g97e8ygT4ic
mvo1AKi0MmNyYyAoS2V5IHRvIHNpZ24gYnVuZGxlIHVzZWQgYnkgY3JjKSA8Y3Jj
QGNyYy5kZXY+iJkEExYKAEEWIQS4RlW/rByOBn/ZyofcfqxAChv9+wUCYrvgDQIb
AwUJEswDAAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRDcfqxAChv9+/ep
APwISi03R7npwimqdL7NYKDGMO8ikOwmmPkqh9CKwt4CdwD8Cc6HNcZumHDpJ4gH
x7FXxIS9KLwDihpm1Gxr4t1t5Qy4OARiu+ANEgorBgEEAZdVAQUBAQdA/w7pM7hf
bxZ2qwSuoBuhcA1sAlPSb3NrIZf3CceoqzQDAQgHiH4EGBYKACYWIQS4RlW/rByO
Bn/ZyofcfqxAChv9+wUCYrvgDQIbDAUJEswDAAAKCRDcfqxAChv9+2UkAQCNCdaf
vnhbvfPHDltmwDZ3aD4l3jjSKpeySeKQocgjQAD6A7kawst/50k4wb+vUDUnEoYo
9Ix7lKfKWCXil/z0vg4=
=lmb/
-----END PGP PUBLIC KEY BLOCK-----`

RedHatReleaseKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF
0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF
0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c
u7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh
XGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H
5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW
9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj
/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1
PcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY
HVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF
buhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB
tDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0
LmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK
CRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC
2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf
C/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5
un3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E
0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE
IGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh
8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL
Ght5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki
JUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25
OFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq
dzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==
=zbHE
-----END PGP PUBLIC KEY BLOCK-----`
)
52 changes: 51 additions & 1 deletion pkg/crc/gpg/gpg.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ package gpg
import (
"bytes"
"fmt"
"io"
"os"
"strings"

"github.com/ProtonMail/go-crypto/openpgp"
"github.com/crc-org/crc/pkg/crc/constants"
"github.com/crc-org/crc/pkg/crc/logging"
goOpenpgp "golang.org/x/crypto/openpgp" //nolint
goClearsign "golang.org/x/crypto/openpgp/clearsign" //nolint
)

func Verify(filePath, signatureFilePath string) error {
Expand All @@ -22,7 +27,7 @@ func Verify(filePath, signatureFilePath string) error {
}
defer signature.Close()

keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(constants.GPGPublicKey))
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(constants.CrcOrgPublicKey))
if err != nil {
return fmt.Errorf("failed to parse public key: %s", err)
}
Expand All @@ -32,3 +37,48 @@ func Verify(filePath, signatureFilePath string) error {
}
return nil
}

func GetVerifiedClearsignedMsgV3(pubkey, clearSignedMsg string) (string, error) {
k, err := goOpenpgp.ReadArmoredKeyRing(bytes.NewBufferString(pubkey))
if err != nil {
return "", fmt.Errorf("Unable to read pubkey: %w", err)
}
block, rest := goClearsign.Decode([]byte(clearSignedMsg))
if len(rest) != 0 {
return "", fmt.Errorf("Error decoding clear signed message: %w", err)
}
sig, err := io.ReadAll(block.ArmoredSignature.Body)
if err != nil {
return "", fmt.Errorf("Error reading signature: %w", err)
}

// CheckDetachedSignature method expects the clear text msg
// in the canonical format as defined in the pgp spec which
// says that each line of the text needs to end with \r\n
clearTextMsg := make([]byte, len(block.Bytes))
copy(clearTextMsg, block.Bytes)
canonicalizedMsgText := canonicalize(trimEachLine(string(clearTextMsg)))

id, err := goOpenpgp.CheckDetachedSignature(k, bytes.NewBufferString(canonicalizedMsgText), bytes.NewBuffer(sig))
if err != nil {
return "", fmt.Errorf("Invalid signature: %w", err)
}
logging.Debugf("Got valid signature from key id: %s", id.PrimaryKey.KeyIdString())
return trimEachLine(string(clearTextMsg)), nil
}

// https://github.com/ProtonMail/gopenpgp/blob/5aebf6a366fd8b81e80c337186fdaa0793597354/internal/common.go#L10-L12
func canonicalize(text string) string {
return strings.ReplaceAll(strings.ReplaceAll(text, "\r\n", "\n"), "\n", "\r\n")
}

// https://github.com/ProtonMail/gopenpgp/blob/5aebf6a366fd8b81e80c337186fdaa0793597354/internal/common.go#L14-L22
func trimEachLine(text string) string {
lines := strings.Split(text, "\n")

for i := range lines {
lines[i] = strings.TrimRight(lines[i], " \t\r")
}

return strings.Join(lines, "\n")
}
45 changes: 45 additions & 0 deletions pkg/crc/gpg/gpg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package gpg

import (
"testing"

"github.com/crc-org/crc/pkg/crc/constants"
"github.com/stretchr/testify/assert"
)

const (
testMsg = `-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
a8267d09eac58e3c7f0db093f3cba83091390e5ac623b0a0282f6f55102b7681 crc_hyperv_4.12.9_amd64.crcbundle
f57ab331ad092d8cb1f354b4308046c5ffd15bd143b19f841cb64b0fda89db67 crc_libvirt_4.12.9_amd64.crcbundle
7e83b6a4c4da6766b6b4981655d4bb38fd8f9da36ef1a5d16d017cd07d6ee7e9 crc_vfkit_4.12.9_amd64.crcbundle
412d20e4969e872c24b14e55cbaa892848a1657b95a20f4af8ad4629ffdf73ab crc_vfkit_4.12.9_arm64.crcbundle
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIVAwUBZCQdgxmeL5H9Qx1RAQhUrxAAiLMQqfTZliHzSTmLsgTYj5YOoRly8ax1
lEGxJZ6jTmrkDaC60S76wAoobD7zbVUifNp0XDYs8f7x6VVANpARoKp/p7NDtWP5
K19NhlG6Hip7/RmzUnEfEMH6sYcrNr0mbNQpsTJpje13u7HuwEXEpiceyU3GcDaA
VcdARo6hcP7bZIieHwJtpWrb8knc/OE2lCMKjanBbeC4+vZtj0xU1kpZTsBq8q84
WFdkR7C76XXhdRnuQqBBTbTuXGTI0OjB963Rx0+3Ej1liiDokH7JWvlUVaX7MKS3
IRf4X7q/Q3acsBtfJ9aNjrTCZJoMNg+F/eCbj+iVpXUK5rdOEzDKQitkMrB8VYgF
SCMe+FMyNqDS6MewR+wJzzKoVZDf6SDuAlSej1FthJ4QZAljuXlpRrDbFq2SRiFz
WgVipg15Bl6vTftjOnCkwHg/GQt1bQuzudGyulRlTu/Nnezvf9/sej++91z2aA86
nJnofMLLw3KkA1HCCgIiE5upMvjzLUobKAFxYyaDal6gNPG9S/l8N91UHujhKCpg
otaQ9Awtg7Z/F8pCLH08Nen4J/CqYaG+JHRORm/i17eD3qBEc+EgIZ7m0sLvm1aV
1Tg7G/6pu+LdPIYvJQKSgGuI8eP/p1zc8zzgHtaSWd2AVL3M/iOjteaba8eU5VCd
uaTo5yjgVLY=
=x4q3
-----END PGP SIGNATURE-----`
expectedMsg = `a8267d09eac58e3c7f0db093f3cba83091390e5ac623b0a0282f6f55102b7681 crc_hyperv_4.12.9_amd64.crcbundle
f57ab331ad092d8cb1f354b4308046c5ffd15bd143b19f841cb64b0fda89db67 crc_libvirt_4.12.9_amd64.crcbundle
7e83b6a4c4da6766b6b4981655d4bb38fd8f9da36ef1a5d16d017cd07d6ee7e9 crc_vfkit_4.12.9_amd64.crcbundle
412d20e4969e872c24b14e55cbaa892848a1657b95a20f4af8ad4629ffdf73ab crc_vfkit_4.12.9_arm64.crcbundle`
)

func TestVerify(t *testing.T) {
msg, err := GetVerifiedClearsignedMsgV3(constants.RedHatReleaseKey, testMsg)
assert.NoError(t, err)
assert.Equal(t, expectedMsg, msg)
}
Loading

0 comments on commit d7c26f5

Please sign in to comment.