@@ -3,11 +3,36 @@ package getproviders
33import (
44 "bytes"
55 "crypto/sha256"
6+ "encoding/hex"
67 "fmt"
78 "io"
89 "os"
10+ "strings"
11+
12+ "golang.org/x/crypto/openpgp"
13+ openpgpArmor "golang.org/x/crypto/openpgp/armor"
14+ openpgpErrors "golang.org/x/crypto/openpgp/errors"
915)
1016
17+ // FIXME docs
18+ type PackageAuthenticationResult struct {
19+ Result string
20+ Warning string
21+ }
22+
23+ func (t * PackageAuthenticationResult ) String () string {
24+ if t == nil {
25+ return "Unauthenticated"
26+ }
27+ return t .Result
28+ }
29+
30+ // FIXME docs
31+ type SigningKey struct {
32+ ASCIIArmor string `json:"ascii_armor"`
33+ TrustSignature string `json:"trust_signature"`
34+ }
35+
1136// PackageAuthentication is an interface implemented by the optional package
1237// authentication implementations a source may include on its PackageMeta
1338// objects.
@@ -24,7 +49,7 @@ type PackageAuthentication interface {
2449 //
2550 // The localLocation is guaranteed not to be a PackageHTTPURL: a
2651 // remote package will always be staged locally for inspection first.
27- AuthenticatePackage (meta PackageMeta , localLocation PackageLocation ) error
52+ AuthenticatePackage (meta PackageMeta , localLocation PackageLocation ) ( * PackageAuthenticationResult , error )
2853}
2954
3055type packageAuthenticationAll []PackageAuthentication
@@ -38,14 +63,16 @@ func PackageAuthenticationAll(checks ...PackageAuthentication) PackageAuthentica
3863 return packageAuthenticationAll (checks )
3964}
4065
41- func (checks packageAuthenticationAll ) AuthenticatePackage (meta PackageMeta , localLocation PackageLocation ) error {
66+ func (checks packageAuthenticationAll ) AuthenticatePackage (meta PackageMeta , localLocation PackageLocation ) (* PackageAuthenticationResult , error ) {
67+ var authResult * PackageAuthenticationResult
4268 for _ , check := range checks {
43- err := check .AuthenticatePackage (meta , localLocation )
69+ var err error
70+ authResult , err = check .AuthenticatePackage (meta , localLocation )
4471 if err != nil {
45- return err
72+ return authResult , err
4673 }
4774 }
48- return nil
75+ return authResult , nil
4976}
5077
5178type archiveHashAuthentication struct {
@@ -65,29 +92,192 @@ func NewArchiveChecksumAuthentication(wantSHA256Sum [sha256.Size]byte) PackageAu
6592 return archiveHashAuthentication {wantSHA256Sum }
6693}
6794
68- func (a archiveHashAuthentication ) AuthenticatePackage (meta PackageMeta , localLocation PackageLocation ) error {
95+ func (a archiveHashAuthentication ) AuthenticatePackage (meta PackageMeta , localLocation PackageLocation ) ( * PackageAuthenticationResult , error ) {
6996 archiveLocation , ok := localLocation .(PackageLocalArchive )
7097 if ! ok {
7198 // A source should not use this authentication type for non-archive
7299 // locations.
73- return fmt .Errorf ("cannot check archive hash for non-archive location %s" , localLocation )
100+ return nil , fmt .Errorf ("cannot check archive hash for non-archive location %s" , localLocation )
74101 }
75102
76103 f , err := os .Open (string (archiveLocation ))
77104 if err != nil {
78- return err
105+ return nil , err
79106 }
80107 defer f .Close ()
81108
82109 h := sha256 .New ()
83110 _ , err = io .Copy (h , f )
84111 if err != nil {
85- return err
112+ return nil , err
86113 }
87114
88115 gotHash := h .Sum (nil )
89116 if ! bytes .Equal (gotHash , a .WantSHA256Sum [:]) {
90- return fmt .Errorf ("archive has incorrect SHA-256 checksum %x (expected %x)" , gotHash , a .WantSHA256Sum [:])
117+ return nil , fmt .Errorf ("archive has incorrect SHA-256 checksum %x (expected %x)" , gotHash , a .WantSHA256Sum [:])
118+ }
119+ return & PackageAuthenticationResult {Result : "verified checksum" }, nil
120+ }
121+
122+ type matchingChecksumAuthentication struct {
123+ Document []byte
124+ Filename string
125+ WantSHA256Sum [sha256 .Size ]byte
126+ }
127+
128+ // NewMatchingChecksumAuthentication FIXME
129+ func NewMatchingChecksumAuthentication (document []byte , filename string , wantSHA256Sum [sha256 .Size ]byte ) PackageAuthentication {
130+ return matchingChecksumAuthentication {
131+ Document : document ,
132+ Filename : filename ,
133+ WantSHA256Sum : wantSHA256Sum ,
134+ }
135+ }
136+
137+ func (m matchingChecksumAuthentication ) AuthenticatePackage (meta PackageMeta , location PackageLocation ) (* PackageAuthenticationResult , error ) {
138+ if _ , ok := meta .Location .(PackageHTTPURL ); ! ok {
139+ // A source should not use this authentication type for non-HTTP
140+ // source locations.
141+ return nil , fmt .Errorf ("cannot verify matching checksum for non-HTTP location %s" , meta .Location )
142+ }
143+
144+ // Find the checksum in the list with matching filename. The document is
145+ // in the form "0123456789abcdef filename.zip".
146+ filename := []byte (m .Filename )
147+ var checksum []byte
148+ for _ , line := range bytes .Split (m .Document , []byte ("\n " )) {
149+ parts := bytes .Fields (line )
150+ if len (parts ) > 1 && bytes .Equal (parts [1 ], filename ) {
151+ checksum = parts [0 ]
152+ break
153+ }
154+ }
155+ if checksum == nil {
156+ return nil , fmt .Errorf ("checksum list has no SHA-256 hash for %q" , m .Filename )
157+ }
158+
159+ // Decode the ASCII checksum into a byte array for comparison.
160+ var gotSHA256Sum [sha256 .Size ]byte
161+ if _ , err := hex .Decode (gotSHA256Sum [:], checksum ); err != nil {
162+ return nil , fmt .Errorf ("checksum list has invalid SHA256 hash %q: %s" , string (checksum ), err )
163+ }
164+
165+ // If thee checksums don't match, authentication fails.
166+ if ! bytes .Equal (gotSHA256Sum [:], m .WantSHA256Sum [:]) {
167+ return nil , fmt .Errorf ("checksum list has unexpected SHA-256 hash %x (expected %x)" , gotSHA256Sum , m .WantSHA256Sum [:])
168+ }
169+
170+ // Success! But this doesn't result in any real authentication, only a
171+ // lack of authentication errors, so we return a nil result.
172+ return nil , nil
173+ }
174+
175+ type signatureAuthentication struct {
176+ Document []byte
177+ Signature []byte
178+ Keys []SigningKey
179+ }
180+
181+ // NewSignatureAuthentication returns a PackageAuthentication implementation
182+ // that verifies the cryptographic signature for a package against a given key.
183+ func NewSignatureAuthentication (document , signature []byte , keys []SigningKey ) PackageAuthentication {
184+ return signatureAuthentication {
185+ Document : document ,
186+ Signature : signature ,
187+ Keys : keys ,
188+ }
189+ }
190+
191+ func (s signatureAuthentication ) AuthenticatePackage (meta PackageMeta , location PackageLocation ) (* PackageAuthenticationResult , error ) {
192+ if _ , ok := location .(PackageLocalArchive ); ! ok {
193+ // A source should not use this authentication type for non-archive
194+ // locations.
195+ return nil , fmt .Errorf ("cannot check archive hash for non-archive location %s" , location )
91196 }
92- return nil
197+
198+ if _ , ok := meta .Location .(PackageHTTPURL ); ! ok {
199+ // A source should not use this authentication type for non-HTTP source
200+ // locations.
201+ return nil , fmt .Errorf ("cannot check archive hash for non-HTTP location %s" , meta .Location )
202+ }
203+
204+ // Attempt to verify the signature using each of the keys returned by the
205+ // registry. Note: currently the registry only returns one key, but this
206+ // may change in the future. We must check each key in turn to find the
207+ // matching signing entity before proceeding.
208+ var signingKey * SigningKey
209+ for _ , key := range s .Keys {
210+ keyring , err := openpgp .ReadArmoredKeyRing (strings .NewReader (key .ASCIIArmor ))
211+ if err != nil {
212+ return nil , err
213+ }
214+
215+ _ , err = openpgp .CheckDetachedSignature (keyring , bytes .NewReader (s .Document ), bytes .NewReader (s .Signature ))
216+
217+ // If the signature issuer does not match the the key, keep trying the
218+ // rest of the provided keys.
219+ if err == openpgpErrors .ErrUnknownIssuer {
220+ continue
221+ }
222+
223+ // Any other signature error is terminal.
224+ if err != nil {
225+ return nil , err
226+ }
227+
228+ signingKey = & key
229+ break
230+ }
231+
232+ // If none of the provided keys issued the signature, this package is
233+ // unsigned. This is currently a terminal authentication error.
234+ if signingKey == nil {
235+ return nil , fmt .Errorf ("Authentication signature from unknown issuer" )
236+ }
237+
238+ // Verify the signature using the HashiCorp public key. If this succeeds,
239+ // this is an official provider.
240+ hashicorpKeyring , err := openpgp .ReadArmoredKeyRing (strings .NewReader (HashicorpPublicKey ))
241+ if err != nil {
242+ return nil , fmt .Errorf ("Error creating HashiCorp Partners keyring: %s" , err )
243+ }
244+ _ , err = openpgp .CheckDetachedSignature (hashicorpKeyring , bytes .NewReader (s .Document ), bytes .NewReader (s .Signature ))
245+ if err == nil {
246+ return & PackageAuthenticationResult {Result : "HashiCorp provider" }, nil
247+ }
248+
249+ // If the signing key has a trust signature, attempt to verify it with the
250+ // HashiCorp partners public key.
251+ if signingKey .TrustSignature != "" {
252+ hashicorpPartnersKeyring , err := openpgp .ReadArmoredKeyRing (strings .NewReader (HashicorpPartnersKey ))
253+ if err != nil {
254+ return nil , fmt .Errorf ("Error creating HashiCorp Partners keyring: %s" , err )
255+ }
256+
257+ authorKey , err := openpgpArmor .Decode (strings .NewReader (signingKey .ASCIIArmor ))
258+ if err != nil {
259+ return nil , err
260+ }
261+
262+ trustSignature , err := openpgpArmor .Decode (strings .NewReader (signingKey .TrustSignature ))
263+ if err != nil {
264+ return nil , err
265+ }
266+
267+ _ , err = openpgp .CheckDetachedSignature (hashicorpPartnersKeyring , authorKey .Body , trustSignature .Body )
268+ if err != nil {
269+ return nil , fmt .Errorf ("Error verifying trust signature: %s" , err )
270+ }
271+
272+ return & PackageAuthenticationResult {Result : "Partner provider" }, nil
273+ }
274+
275+ // We have a valid signature, but it's not from the HashiCorp key, and it
276+ // also isn't a trusted partner. This is a community provider.
277+ return & PackageAuthenticationResult {
278+ Result : "community provider" ,
279+ Warning : communityProviderWarning ,
280+ }, nil
93281}
282+
283+ const communityProviderWarning = `community providers are not trusted by HashiCorp. Use at your own risk.`
0 commit comments