Skip to content

Commit d7c26f5

Browse files
anjannathpraveenkumar
authored andcommitted
Add helper to verify pgp v3 clear signed message
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
1 parent 496d03e commit d7c26f5

File tree

6 files changed

+571
-19
lines changed

6 files changed

+571
-19
lines changed

pkg/crc/constants/constants.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,6 @@ const (
4545

4646
OpenShiftIngressHTTPPort = 80
4747
OpenShiftIngressHTTPSPort = 443
48-
49-
// This public key is owned by the CRC team ([email protected]), and is used
50-
// to sign bundles uploaded to an image registry.
51-
// It can be fetched with: `gpg --recv-key DC7EAC400A1BFDFB`
52-
GPGPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
53-
54-
mDMEYrvgDRYJKwYBBAHaRw8BAQdAoW+hjSRYpTAdLEE1u6ZuYNER1g97e8ygT4ic
55-
mvo1AKi0MmNyYyAoS2V5IHRvIHNpZ24gYnVuZGxlIHVzZWQgYnkgY3JjKSA8Y3Jj
56-
QGNyYy5kZXY+iJkEExYKAEEWIQS4RlW/rByOBn/ZyofcfqxAChv9+wUCYrvgDQIb
57-
AwUJEswDAAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRDcfqxAChv9+/ep
58-
APwISi03R7npwimqdL7NYKDGMO8ikOwmmPkqh9CKwt4CdwD8Cc6HNcZumHDpJ4gH
59-
x7FXxIS9KLwDihpm1Gxr4t1t5Qy4OARiu+ANEgorBgEEAZdVAQUBAQdA/w7pM7hf
60-
bxZ2qwSuoBuhcA1sAlPSb3NrIZf3CceoqzQDAQgHiH4EGBYKACYWIQS4RlW/rByO
61-
Bn/ZyofcfqxAChv9+wUCYrvgDQIbDAUJEswDAAAKCRDcfqxAChv9+2UkAQCNCdaf
62-
vnhbvfPHDltmwDZ3aD4l3jjSKpeySeKQocgjQAD6A7kawst/50k4wb+vUDUnEoYo
63-
9Ix7lKfKWCXil/z0vg4=
64-
=lmb/
65-
-----END PGP PUBLIC KEY BLOCK-----`
6648
)
6749

6850
var adminHelperExecutableForOs = map[string]string{

pkg/crc/constants/keys.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package constants
2+
3+
const (
4+
// This public key is owned by the CRC team ([email protected]), and is used
5+
// to sign bundles uploaded to an image registry.
6+
// It can be fetched with: `gpg --recv-key DC7EAC400A1BFDFB`
7+
CrcOrgPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
8+
9+
mDMEYrvgDRYJKwYBBAHaRw8BAQdAoW+hjSRYpTAdLEE1u6ZuYNER1g97e8ygT4ic
10+
mvo1AKi0MmNyYyAoS2V5IHRvIHNpZ24gYnVuZGxlIHVzZWQgYnkgY3JjKSA8Y3Jj
11+
QGNyYy5kZXY+iJkEExYKAEEWIQS4RlW/rByOBn/ZyofcfqxAChv9+wUCYrvgDQIb
12+
AwUJEswDAAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRDcfqxAChv9+/ep
13+
APwISi03R7npwimqdL7NYKDGMO8ikOwmmPkqh9CKwt4CdwD8Cc6HNcZumHDpJ4gH
14+
x7FXxIS9KLwDihpm1Gxr4t1t5Qy4OARiu+ANEgorBgEEAZdVAQUBAQdA/w7pM7hf
15+
bxZ2qwSuoBuhcA1sAlPSb3NrIZf3CceoqzQDAQgHiH4EGBYKACYWIQS4RlW/rByO
16+
Bn/ZyofcfqxAChv9+wUCYrvgDQIbDAUJEswDAAAKCRDcfqxAChv9+2UkAQCNCdaf
17+
vnhbvfPHDltmwDZ3aD4l3jjSKpeySeKQocgjQAD6A7kawst/50k4wb+vUDUnEoYo
18+
9Ix7lKfKWCXil/z0vg4=
19+
=lmb/
20+
-----END PGP PUBLIC KEY BLOCK-----`
21+
22+
RedHatReleaseKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
23+
24+
mQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF
25+
0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF
26+
0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c
27+
u7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh
28+
XGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H
29+
5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW
30+
9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj
31+
/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1
32+
PcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY
33+
HVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF
34+
buhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB
35+
tDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0
36+
LmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK
37+
CRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC
38+
2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf
39+
C/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5
40+
un3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E
41+
0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE
42+
IGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh
43+
8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL
44+
Ght5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki
45+
JUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25
46+
OFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq
47+
dzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==
48+
=zbHE
49+
-----END PGP PUBLIC KEY BLOCK-----`
50+
)

pkg/crc/gpg/gpg.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ package gpg
33
import (
44
"bytes"
55
"fmt"
6+
"io"
67
"os"
8+
"strings"
79

810
"github.com/ProtonMail/go-crypto/openpgp"
911
"github.com/crc-org/crc/pkg/crc/constants"
12+
"github.com/crc-org/crc/pkg/crc/logging"
13+
goOpenpgp "golang.org/x/crypto/openpgp" //nolint
14+
goClearsign "golang.org/x/crypto/openpgp/clearsign" //nolint
1015
)
1116

1217
func Verify(filePath, signatureFilePath string) error {
@@ -22,7 +27,7 @@ func Verify(filePath, signatureFilePath string) error {
2227
}
2328
defer signature.Close()
2429

25-
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(constants.GPGPublicKey))
30+
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(constants.CrcOrgPublicKey))
2631
if err != nil {
2732
return fmt.Errorf("failed to parse public key: %s", err)
2833
}
@@ -32,3 +37,48 @@ func Verify(filePath, signatureFilePath string) error {
3237
}
3338
return nil
3439
}
40+
41+
func GetVerifiedClearsignedMsgV3(pubkey, clearSignedMsg string) (string, error) {
42+
k, err := goOpenpgp.ReadArmoredKeyRing(bytes.NewBufferString(pubkey))
43+
if err != nil {
44+
return "", fmt.Errorf("Unable to read pubkey: %w", err)
45+
}
46+
block, rest := goClearsign.Decode([]byte(clearSignedMsg))
47+
if len(rest) != 0 {
48+
return "", fmt.Errorf("Error decoding clear signed message: %w", err)
49+
}
50+
sig, err := io.ReadAll(block.ArmoredSignature.Body)
51+
if err != nil {
52+
return "", fmt.Errorf("Error reading signature: %w", err)
53+
}
54+
55+
// CheckDetachedSignature method expects the clear text msg
56+
// in the canonical format as defined in the pgp spec which
57+
// says that each line of the text needs to end with \r\n
58+
clearTextMsg := make([]byte, len(block.Bytes))
59+
copy(clearTextMsg, block.Bytes)
60+
canonicalizedMsgText := canonicalize(trimEachLine(string(clearTextMsg)))
61+
62+
id, err := goOpenpgp.CheckDetachedSignature(k, bytes.NewBufferString(canonicalizedMsgText), bytes.NewBuffer(sig))
63+
if err != nil {
64+
return "", fmt.Errorf("Invalid signature: %w", err)
65+
}
66+
logging.Debugf("Got valid signature from key id: %s", id.PrimaryKey.KeyIdString())
67+
return trimEachLine(string(clearTextMsg)), nil
68+
}
69+
70+
// https://github.com/ProtonMail/gopenpgp/blob/5aebf6a366fd8b81e80c337186fdaa0793597354/internal/common.go#L10-L12
71+
func canonicalize(text string) string {
72+
return strings.ReplaceAll(strings.ReplaceAll(text, "\r\n", "\n"), "\n", "\r\n")
73+
}
74+
75+
// https://github.com/ProtonMail/gopenpgp/blob/5aebf6a366fd8b81e80c337186fdaa0793597354/internal/common.go#L14-L22
76+
func trimEachLine(text string) string {
77+
lines := strings.Split(text, "\n")
78+
79+
for i := range lines {
80+
lines[i] = strings.TrimRight(lines[i], " \t\r")
81+
}
82+
83+
return strings.Join(lines, "\n")
84+
}

pkg/crc/gpg/gpg_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package gpg
2+
3+
import (
4+
"testing"
5+
6+
"github.com/crc-org/crc/pkg/crc/constants"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
const (
11+
testMsg = `-----BEGIN PGP SIGNED MESSAGE-----
12+
Hash: SHA256
13+
14+
a8267d09eac58e3c7f0db093f3cba83091390e5ac623b0a0282f6f55102b7681 crc_hyperv_4.12.9_amd64.crcbundle
15+
f57ab331ad092d8cb1f354b4308046c5ffd15bd143b19f841cb64b0fda89db67 crc_libvirt_4.12.9_amd64.crcbundle
16+
7e83b6a4c4da6766b6b4981655d4bb38fd8f9da36ef1a5d16d017cd07d6ee7e9 crc_vfkit_4.12.9_amd64.crcbundle
17+
412d20e4969e872c24b14e55cbaa892848a1657b95a20f4af8ad4629ffdf73ab crc_vfkit_4.12.9_arm64.crcbundle
18+
-----BEGIN PGP SIGNATURE-----
19+
Version: GnuPG v1
20+
21+
iQIVAwUBZCQdgxmeL5H9Qx1RAQhUrxAAiLMQqfTZliHzSTmLsgTYj5YOoRly8ax1
22+
lEGxJZ6jTmrkDaC60S76wAoobD7zbVUifNp0XDYs8f7x6VVANpARoKp/p7NDtWP5
23+
K19NhlG6Hip7/RmzUnEfEMH6sYcrNr0mbNQpsTJpje13u7HuwEXEpiceyU3GcDaA
24+
VcdARo6hcP7bZIieHwJtpWrb8knc/OE2lCMKjanBbeC4+vZtj0xU1kpZTsBq8q84
25+
WFdkR7C76XXhdRnuQqBBTbTuXGTI0OjB963Rx0+3Ej1liiDokH7JWvlUVaX7MKS3
26+
IRf4X7q/Q3acsBtfJ9aNjrTCZJoMNg+F/eCbj+iVpXUK5rdOEzDKQitkMrB8VYgF
27+
SCMe+FMyNqDS6MewR+wJzzKoVZDf6SDuAlSej1FthJ4QZAljuXlpRrDbFq2SRiFz
28+
WgVipg15Bl6vTftjOnCkwHg/GQt1bQuzudGyulRlTu/Nnezvf9/sej++91z2aA86
29+
nJnofMLLw3KkA1HCCgIiE5upMvjzLUobKAFxYyaDal6gNPG9S/l8N91UHujhKCpg
30+
otaQ9Awtg7Z/F8pCLH08Nen4J/CqYaG+JHRORm/i17eD3qBEc+EgIZ7m0sLvm1aV
31+
1Tg7G/6pu+LdPIYvJQKSgGuI8eP/p1zc8zzgHtaSWd2AVL3M/iOjteaba8eU5VCd
32+
uaTo5yjgVLY=
33+
=x4q3
34+
-----END PGP SIGNATURE-----`
35+
expectedMsg = `a8267d09eac58e3c7f0db093f3cba83091390e5ac623b0a0282f6f55102b7681 crc_hyperv_4.12.9_amd64.crcbundle
36+
f57ab331ad092d8cb1f354b4308046c5ffd15bd143b19f841cb64b0fda89db67 crc_libvirt_4.12.9_amd64.crcbundle
37+
7e83b6a4c4da6766b6b4981655d4bb38fd8f9da36ef1a5d16d017cd07d6ee7e9 crc_vfkit_4.12.9_amd64.crcbundle
38+
412d20e4969e872c24b14e55cbaa892848a1657b95a20f4af8ad4629ffdf73ab crc_vfkit_4.12.9_arm64.crcbundle`
39+
)
40+
41+
func TestVerify(t *testing.T) {
42+
msg, err := GetVerifiedClearsignedMsgV3(constants.RedHatReleaseKey, testMsg)
43+
assert.NoError(t, err)
44+
assert.Equal(t, expectedMsg, msg)
45+
}

0 commit comments

Comments
 (0)