No special treatment for Hashi signatures (#185)

This commit is contained in:
Marcin Wyszynski 2023-08-28 13:41:25 +02:00 committed by GitHub
parent 7cabaf7de9
commit b36ff25ea7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 39 additions and 201 deletions

View File

@ -809,7 +809,7 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
},
FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) {
var keyID string
if authResult != nil && authResult.ThirdPartySigned() {
if authResult != nil && authResult.Signed() {
keyID = authResult.KeyID
}
if keyID != "" {
@ -856,13 +856,13 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) {
thirdPartySigned := false
for _, authResult := range authResults {
if authResult.ThirdPartySigned() {
if authResult.Signed() {
thirdPartySigned = true
break
}
}
if thirdPartySigned {
c.Ui.Info(fmt.Sprintf("\nPartner and community providers are signed by their developers.\n" +
c.Ui.Info(fmt.Sprintf("\nProviders are signed by their developers.\n" +
"If you'd like to know more about provider signing, you can read about it here:\n" +
"https://www.placeholderplaceholderplaceholder.io/docs/cli/plugins/signing.html"))
}

View File

@ -232,7 +232,7 @@ func (c *ProvidersLockCommand) Run(args []string) int {
},
FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, auth *getproviders.PackageAuthenticationResult) {
var keyID string
if auth != nil && auth.ThirdPartySigned() {
if auth != nil && auth.Signed() {
keyID = auth.KeyID
}
if keyID != "" {

View File

@ -13,7 +13,6 @@ import (
"strings"
"github.com/ProtonMail/go-crypto/openpgp"
openpgpArmor "github.com/ProtonMail/go-crypto/openpgp/armor"
openpgpErrors "github.com/ProtonMail/go-crypto/openpgp/errors"
openpgpPacket "github.com/ProtonMail/go-crypto/openpgp/packet"
)
@ -22,9 +21,7 @@ type packageAuthenticationResult int
const (
verifiedChecksum packageAuthenticationResult = iota
officialProvider
partnerProvider
communityProvider
signed
)
var (
@ -50,59 +47,24 @@ func (t *PackageAuthenticationResult) String() string {
}
return []string{
"verified checksum",
"signed by HashiCorp",
"signed by a HashiCorp partner",
"self-signed",
"signed",
}[t.result]
}
// SignedByHashiCorp returns whether the package was authenticated as signed
// by HashiCorp.
func (t *PackageAuthenticationResult) SignedByHashiCorp() bool {
// Signed returns whether the package was authenticated as signed by anyone.
func (t *PackageAuthenticationResult) Signed() bool {
if t == nil {
return false
}
if t.result == officialProvider {
return true
}
return false
return t.result == signed
}
// SignedByAnyParty returns whether the package was authenticated as signed
// by either HashiCorp or by a third-party.
func (t *PackageAuthenticationResult) SignedByAnyParty() bool {
if t == nil {
return false
}
if t.result == officialProvider || t.result == partnerProvider || t.result == communityProvider {
return true
}
return false
}
// ThirdPartySigned returns whether the package was authenticated as signed by a party
// other than HashiCorp.
func (t *PackageAuthenticationResult) ThirdPartySigned() bool {
if t == nil {
return false
}
if t.result == partnerProvider || t.result == communityProvider {
return true
}
return false
}
// SigningKey represents a key used to sign packages from a registry, along
// with an optional trust signature from the registry operator. These are
// SigningKey represents a key used to sign packages from a registry. These are
// both in ASCII armored OpenPGP format.
//
// The JSON struct tags represent the field names used by the Registry API.
type SigningKey struct {
ASCIIArmor string `json:"ascii_armor"`
TrustSignature string `json:"trust_signature"`
ASCIIArmor string `json:"ascii_armor"`
}
// PackageAuthentication is an interface implemented by the optional package
@ -257,7 +219,7 @@ func (a packageHashAuthentication) AuthenticatePackage(localLocation PackageLoca
return nil, fmt.Errorf("provider package doesn't match the expected checksum %q", a.RequiredHashes[0].String())
}
// It's non-ideal that this doesn't actually list the expected checksums,
// but in the many-checksum case the message would get pretty unweildy.
// but in the many-checksum case the message would get pretty unwieldy.
// In practice today we typically use this authenticator only with a
// single hash returned from a network mirror, so the better message
// above will prevail in that case. Maybe we'll improve on this somehow
@ -408,51 +370,13 @@ func NewSignatureAuthentication(document, signature []byte, keys []SigningKey) P
func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (*PackageAuthenticationResult, error) {
// Find the key that signed the checksum file. This can fail if there is no
// valid signature for any of the provided keys.
signingKey, keyID, err := s.findSigningKey()
_, keyID, err := s.findSigningKey()
if err != nil {
return nil, err
}
// Verify the signature using the HashiCorp public key. If this succeeds,
// this is an official provider.
hashicorpKeyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(HashicorpPublicKey))
if err != nil {
return nil, fmt.Errorf("error creating HashiCorp keyring: %s", err)
}
_, err = openpgp.CheckDetachedSignature(hashicorpKeyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature), openpgpConfig)
if err == nil {
return &PackageAuthenticationResult{result: officialProvider, KeyID: keyID}, nil
}
// If the signing key has a trust signature, attempt to verify it with the
// HashiCorp partners public key.
if signingKey.TrustSignature != "" {
hashicorpPartnersKeyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(HashicorpPartnersKey))
if err != nil {
return nil, fmt.Errorf("error creating HashiCorp Partners keyring: %s", err)
}
authorKey, err := openpgpArmor.Decode(strings.NewReader(signingKey.ASCIIArmor))
if err != nil {
return nil, fmt.Errorf("error decoding signing key: %s", err)
}
trustSignature, err := openpgpArmor.Decode(strings.NewReader(signingKey.TrustSignature))
if err != nil {
return nil, fmt.Errorf("error decoding trust signature: %s", err)
}
_, err = openpgp.CheckDetachedSignature(hashicorpPartnersKeyring, authorKey.Body, trustSignature.Body, openpgpConfig)
if err != nil {
return nil, fmt.Errorf("error verifying trust signature: %s", err)
}
return &PackageAuthenticationResult{result: partnerProvider, KeyID: keyID}, nil
}
// We have a valid signature, but it's not from the HashiCorp key, and it
// also isn't a trusted partner. This is a community provider.
return &PackageAuthenticationResult{result: communityProvider, KeyID: keyID}, nil
// We have a valid signature.
return &PackageAuthenticationResult{result: signed, KeyID: keyID}, nil
}
func (s signatureAuthentication) AcceptableHashes() []Hash {

View File

@ -41,20 +41,8 @@ func TestPackageAuthenticationResult(t *testing.T) {
"unauthenticated",
},
{
&PackageAuthenticationResult{result: verifiedChecksum},
"verified checksum",
},
{
&PackageAuthenticationResult{result: officialProvider},
"signed by HashiCorp",
},
{
&PackageAuthenticationResult{result: partnerProvider},
"signed by a HashiCorp partner",
},
{
&PackageAuthenticationResult{result: communityProvider},
"self-signed",
&PackageAuthenticationResult{result: signed},
"signed",
},
}
for _, test := range tests {
@ -87,10 +75,10 @@ var _ PackageAuthentication = (*mockAuthentication)(nil)
func TestPackageAuthenticationAll_success(t *testing.T) {
result, err := PackageAuthenticationAll(
&mockAuthentication{result: verifiedChecksum},
&mockAuthentication{result: communityProvider},
&mockAuthentication{result: signed},
).AuthenticatePackage(nil)
want := PackageAuthenticationResult{result: communityProvider}
want := PackageAuthenticationResult{result: signed}
if result == nil || *result != want {
t.Errorf("wrong result: want %#v, got %#v", want, result)
}
@ -106,7 +94,7 @@ func TestPackageAuthenticationAll_failure(t *testing.T) {
result, err := PackageAuthenticationAll(
&mockAuthentication{result: verifiedChecksum},
&mockAuthentication{err: someError},
&mockAuthentication{result: communityProvider},
&mockAuthentication{result: signed},
).AuthenticatePackage(nil)
if result != nil {
@ -337,26 +325,13 @@ func TestMatchingChecksumAuthentication_failure(t *testing.T) {
// Signature authentication takes a checksum document, a signature, and a list
// of signing keys. If the document is signed by one of the given keys, the
// authentication is successful. The value of the result depends on the signing
// key and its trust signature.
// key.
func TestSignatureAuthentication_success(t *testing.T) {
tests := map[string]struct {
signature string
keys []SigningKey
result PackageAuthenticationResult
}{
"partner provider": {
testAuthorSignatureGoodBase64,
[]SigningKey{
{
ASCIIArmor: testAuthorKeyArmor,
TrustSignature: testAuthorKeyTrustSignatureArmor,
},
},
PackageAuthenticationResult{
result: partnerProvider,
KeyID: testAuthorKeyID,
},
},
"community provider": {
testAuthorSignatureGoodBase64,
[]SigningKey{
@ -365,7 +340,7 @@ func TestSignatureAuthentication_success(t *testing.T) {
},
},
PackageAuthenticationResult{
result: communityProvider,
result: signed,
KeyID: testAuthorKeyID,
},
},
@ -373,14 +348,14 @@ func TestSignatureAuthentication_success(t *testing.T) {
testAuthorSignatureGoodBase64,
[]SigningKey{
{
ASCIIArmor: HashicorpPartnersKey,
ASCIIArmor: anotherPublicKey,
},
{
ASCIIArmor: testAuthorKeyArmor,
},
},
PackageAuthenticationResult{
result: communityProvider,
result: signed,
KeyID: testAuthorKeyID,
},
},
@ -419,11 +394,11 @@ func TestNewSignatureAuthentication_success(t *testing.T) {
testHashicorpSignatureGoodBase64,
[]SigningKey{
{
ASCIIArmor: HashicorpPublicKey,
ASCIIArmor: TestingPublicKey,
},
},
PackageAuthenticationResult{
result: officialProvider,
result: signed,
KeyID: testHashiCorpPublicKeyID,
},
},
@ -482,31 +457,11 @@ func TestSignatureAuthentication_failure(t *testing.T) {
testAuthorSignatureGoodBase64,
[]SigningKey{
{
ASCIIArmor: HashicorpPublicKey,
ASCIIArmor: TestingPublicKey,
},
},
"authentication signature from unknown issuer",
},
"invalid trust signature": {
testAuthorSignatureGoodBase64,
[]SigningKey{
{
ASCIIArmor: testAuthorKeyArmor,
TrustSignature: "invalid PGP armor value",
},
},
"error decoding trust signature: EOF",
},
"unverified trust signature": {
testAuthorSignatureGoodBase64,
[]SigningKey{
{
ASCIIArmor: testAuthorKeyArmor,
TrustSignature: testOtherKeyTrustSignatureArmor,
},
},
"error verifying trust signature: openpgp: invalid signature: RSA verification failure",
},
}
for name, test := range tests {
@ -610,46 +565,8 @@ O74RFokGZzbPtoIvutb8eYoA/1QxxyqE/8A4Z21azYEO0j563LRa8SkZcB5UPDy3
=Xb0o
-----END PGP PUBLIC KEY BLOCK-----`
// testAuthorKeyTrustSignatureArmor is a trust signature of the data in
// testAuthorKeyArmor signed with HashicorpPartnersKey.
const testAuthorKeyTrustSignatureArmor = `-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEUYkGV8Ws20uCMIZWfXLUJo5GYPwFAl5w9+YACgkQfXLUJo5G
YPwjRBAAvy9jo3vvetb4qx/z2qhbRH2JbZN9byKuqlIggPzDhhaIsVJVZ9L6H6bE
AMgPe/NaH58wfiqMYenulYxj9tZwJORT/OK0Y9ZFXXZk6kWPMNv7TEppyB0wKgqq
ORKf07KjDcVQslDG9ARgnvDq2GA4UTHxhT0chKHdIKeDLmTm0VSkfNeOhQIkW7vB
S/WT9y78319QJek8OKwJo0Jv0O93rvZZI0JFjXGtP15XNBfObMtPXn3l8qoLzhsv
pJJG/u+BsVZ+y1JDQQlHaD1P2TLW/nGymFq12k693IOCmNyaIOa01Wa9B/j3a3RY
v4SdkULvJKbttNMNBgIMJ74wZp5EUhEFs68sllrIrmthH8bW2fbcHEQ1g/MJCe3+
43c9aoW8yNQmuEe7yre9lgqcJOIOxlb5XEJhH0Lh+8OBi5aHA/5wXGU5WrhWqHCR
npXBsNqy2sKUuVkEzvn3Hd6aoKncVLrgNR8xA3VP86jJhawvO+M+YYMr1wOVHc/I
PYq9hlyUR8qJ/0RpnaIE1iLbPYfEpGTg7oHORpbQVoZAUwMN/Sdox7sMkqCOb1RJ
Cmy9J5o7iiNOoshvps5cxcbsM7LNfbf0vDhWpckAvsQehrS1mfVuFHkIiotVQhH1
QXPfvB2cVF/SxMqqHWpnT+8c8klfS03kXSb0BdknrQ4DNPq1H5A=
=3A1s
-----END PGP SIGNATURE-----`
// testOtherKeyTrustSignatureArmor is a trust signature of another key (not the
// author key), signed with HashicorpPartnersKey.
const testOtherKeyTrustSignatureArmor = `-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEUYkGV8Ws20uCMIZWfXLUJo5GYPwFAl6POvsACgkQfXLUJo5G
YPyGihAAomM1kGmrC5KRgWQ+V47r8wFoIkhsTgAYb9ENOzn/RVJt3SJSstcKxfA3
7HW5R4kqAoXH1hcPYpUcOcdeAvtZxjGRQ9JgErV8NBg6sR11aQccCzAG4Hy0hWav
/jB5NzTEX5JFEXH6WhpWI1avh0l2j6JxO1K1s+5+5PI3KbuO+XSqeZ3QmUz9FwGu
pr0J6oYcERupzrpnmgMb5fbkpHfzffR2/MOYdF9Hae4EvDS1b7tokuuKsStNnCm0
ge7PFdekwbj/OiQrQlqM1pOw2siPX3ouWCtW8oExm9tAxNw31Bn2g3oaNMkHMqJd
hlVUZlqeJMyylUat3cY7GTQONfCnoyUHe/wv8exBUbV3v2glp9y2g9i2XmXkHOrV
Z+pnNBc+jdp3a4O0Y8fXXZdjiIolZKY8BbvzheuMrQQIOmw4N3KrZbTpLKuqz8rb
h8bqUbU42oWcJmBvzF4NZ4tQ+aFHs4CbOnjfDfS14baQr2Gqo9BqTfrzS5Pbs8lq
AhY0r+zi71lQ1rBfgZfjd8zWlOzpDO//nwKhGCqYOWke/C/T6o0zxM0R4uR4zXwT
KhvXK8/kK/L8Flaxqme0d5bzXLbsMe9I6I76DY5iNhkiFnnWt4+FhGoIDR03MTKS
SnHodBLlpKLyUXi36DCDy/iKVsieqLsAdcYe0nQFuhoQcOme33A=
=aHOG
-----END PGP SIGNATURE-----`
// testShaSumsPlaceholder is a string that represents a signed document that
// the signature authenticator will check. Some of the signature valuesin
// the signature authenticator will check. Some of the signature values in
// other constants in this file are signing this string.
const testShaSumsPlaceholder = "example shasums data"
@ -741,12 +658,12 @@ func TestEntityString(t *testing.T) {
},
{
"HashicorpPublicKey",
testReadArmoredEntity(t, HashicorpPublicKey),
testReadArmoredEntity(t, TestingPublicKey),
"34365D9472D7468F HashiCorp Security (hashicorp.com/security) <security@hashicorp.com>",
},
{
"HashicorpPartnersKey",
testReadArmoredEntity(t, HashicorpPartnersKey),
testReadArmoredEntity(t, anotherPublicKey),
"7D72D4268E4660FC HashiCorp Security (Terraform Partner Signing) <security+terraform@hashicorp.com>",
},
}

View File

@ -3,9 +3,8 @@
package getproviders
// HashicorpPublicKey is the HashiCorp public key, also available at
// https://www.hashicorp.com/security
const HashicorpPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
// TestingPublicKey is some public key used for testing.
const TestingPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGB9+xkBEACabYZOWKmgZsHTdRDiyPJxhbuUiKX65GUWkyRMJKi/1dviVxOX
PG6hBPtF48IFnVgxKpIb7G6NjBousAV+CuLlv5yqFKpOZEGC6sBV+Gx8Vu1CICpl
@ -128,9 +127,7 @@ ZF5q4h4I33PSGDdSvGXn9UMY5Isjpg==
=7pIB
-----END PGP PUBLIC KEY BLOCK-----`
// HashicorpPartnersKey is a key created by HashiCorp, used to generate and
// verify trust signatures for Partner tier providers.
const HashicorpPartnersKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
const anotherPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBF5vdGkBEADKi3Nm83oqMcar+YSDFKBup7+/Ty7m+SldtDH4/RWT0vgVHuQ1
0joA+TrjITR5/aBVQ1/i2pOiBiImnaWsykccjFw9f9AuJqHo520YrAbNCeA6LuGH

View File

@ -287,7 +287,7 @@ func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) {
"signing_keys": map[string]interface{}{
"gpg_public_keys": []map[string]interface{}{
{
"ascii_armor": HashicorpPublicKey,
"ascii_armor": TestingPublicKey,
},
},
},

View File

@ -148,7 +148,7 @@ func TestSourcePackageMeta(t *testing.T) {
[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
[]byte("GPG signature"),
[]SigningKey{
{ASCIIArmor: HashicorpPublicKey},
{ASCIIArmor: TestingPublicKey},
},
),
),

View File

@ -675,7 +675,7 @@ NeedProvider:
}
var signedHashes []getproviders.Hash
if authResult.SignedByAnyParty() {
if authResult.Signed() {
// We'll trust new hashes from upstream only if they were verified
// as signed by a suitable key. Otherwise, we'd record only
// a new hash we just calculated ourselves from the bytes on disk,

View File

@ -2367,7 +2367,7 @@ func TestEnsureProviderVersions_local_source(t *testing.T) {
}
}
// This test only verifies protocol errors and does not try for successfull
// This test only verifies protocol errors and does not try for successful
// installation (at the time of writing, the test files aren't signed so the
// signature verification fails); that's left to the e2e tests.
func TestEnsureProviderVersions_protocol_errors(t *testing.T) {
@ -2622,7 +2622,7 @@ func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) {
"signing_keys": map[string]interface{}{
"gpg_public_keys": []map[string]interface{}{
{
"ascii_armor": getproviders.HashicorpPublicKey,
"ascii_armor": getproviders.TestingPublicKey,
},
},
},
@ -2659,7 +2659,7 @@ func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) {
"signing_keys": map[string]interface{}{
"gpg_public_keys": []map[string]interface{}{
{
"ascii_armor": getproviders.HashicorpPublicKey,
"ascii_armor": getproviders.TestingPublicKey,
},
},
},