1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 18:15:42 +01:00

Implemented loading of PGP keys.

This commit is contained in:
Sascha L. Teichmann 2021-12-13 19:24:44 +01:00
parent fe09d0ea65
commit f945937a6d
3 changed files with 142 additions and 13 deletions

View file

@ -11,6 +11,7 @@ package main
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"crypto/sha256" "crypto/sha256"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
@ -22,7 +23,11 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/PaesslerAG/gval"
"github.com/PaesslerAG/jsonpath"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/csaf-poc/csaf_distribution/csaf" "github.com/csaf-poc/csaf_distribution/csaf"
"github.com/csaf-poc/csaf_distribution/util"
) )
type processor struct { type processor struct {
@ -30,6 +35,9 @@ type processor struct {
redirects map[string]string redirects map[string]string
noneTLS map[string]struct{} noneTLS map[string]struct{}
pmd256 []byte pmd256 []byte
pmd interface{}
builder gval.Language
keys []*crypto.Key
} }
type check interface { type check interface {
@ -43,6 +51,7 @@ func newProcessor(opts *options) *processor {
opts: opts, opts: opts,
redirects: map[string]string{}, redirects: map[string]string{},
noneTLS: map[string]struct{}{}, noneTLS: map[string]struct{}{},
builder: gval.Full(jsonpath.Language()),
} }
} }
@ -54,6 +63,8 @@ func (p *processor) clean() {
delete(p.noneTLS, k) delete(p.noneTLS, k)
} }
p.pmd256 = nil p.pmd256 = nil
p.pmd = nil
p.keys = nil
} }
func (p *processor) run(checks []check, domains []string) (*Report, error) { func (p *processor) run(checks []check, domains []string) (*Report, error) {
@ -83,6 +94,17 @@ func (p *processor) run(checks []check, domains []string) (*Report, error) {
return &report, nil return &report, nil
} }
func (p *processor) jsonPath(expr string) (interface{}, error) {
if p.pmd == nil {
return nil, errors.New("no provider metadata loaded")
}
eval, err := p.builder.NewEvaluable(expr)
if err != nil {
return nil, err
}
return eval(context.Background(), p.pmd)
}
func (p *processor) checkTLS(url string) { func (p *processor) checkTLS(url string) {
if !strings.HasPrefix(strings.ToLower(url), "https://") { if !strings.HasPrefix(strings.ToLower(url), "https://") {
p.noneTLS[url] = struct{}{} p.noneTLS[url] = struct{}{}
@ -279,12 +301,11 @@ func (pmdc *providerMetadataCheck) run(p *processor, domain string) error {
} }
p.pmd256 = h.Sum(nil) p.pmd256 = h.Sum(nil)
var doc interface{} if err := json.NewDecoder(bytes.NewReader(data)).Decode(&p.pmd); err != nil {
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&doc); err != nil {
msg := fmt.Sprintf("Decoding JSON failed: %s.", err.Error()) msg := fmt.Sprintf("Decoding JSON failed: %s.", err.Error())
pmdc.add(msg) pmdc.add(msg)
} }
errors, err := csaf.ValidateProviderMetadata(doc) errors, err := csaf.ValidateProviderMetadata(p.pmd)
if err != nil { if err != nil {
return err return err
} }
@ -421,7 +442,97 @@ func (sc *signaturesCheck) run(*processor, string) error {
return nil return nil
} }
func (ppkc *publicPGPKeyCheck) run(*processor, string) error { func reserialize(dst, src interface{}) error {
// TODO: Implement me! s, err := json.Marshal(src)
if err != nil {
return err
}
return json.Unmarshal(s, dst)
}
func (ppkc *publicPGPKeyCheck) run(p *processor, domain string) error {
src, err := p.jsonPath("$.pgp_keys")
if err != nil {
ppkc.add(fmt.Sprintf("No PGP keys found: %v.", err))
return nil
}
var keys []csaf.PGPKey
if err := util.ReMarshalJSON(&keys, src); err != nil {
ppkc.add(fmt.Sprintf("PGP keys invalid: %v.", err))
return nil
}
if len(keys) == 0 {
ppkc.add("No PGP keys found.")
return nil
}
// Try to load
client := p.httpClient()
base, err := url.Parse("https://" + domain + "/.well-known/csaf/provider-metadata.json")
if err != nil {
return err
}
for i := range keys {
key := &keys[i]
if key.URL == nil {
ppkc.add(fmt.Sprintf("Missing URL for fingerprint %x.", key.Fingerprint))
continue
}
up, err := url.Parse(*key.URL)
if err != nil {
ppkc.add(fmt.Sprintf("Invalid URL '%s': %v", *key.URL, err))
continue
}
up = base.ResolveReference(up)
u := up.String()
p.checkTLS(u)
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return err
}
res, err := client.Do(req)
if err != nil {
ppkc.add(fmt.Sprintf("Fetching PGP key %s failed: %v.", u, err))
continue
}
if res.StatusCode != http.StatusOK {
ppkc.add(fmt.Sprintf("Fetching PGP key %s status code: %d (%s)", u, res.StatusCode, res.Status))
continue
}
ckey, err := func() (*crypto.Key, error) {
defer res.Body.Close()
return crypto.NewKeyFromArmoredReader(res.Body)
}()
if err != nil {
ppkc.add(fmt.Sprintf("Reading PGP key %s failed: %v", u, err))
continue
}
if ckey.GetFingerprint() != string(key.Fingerprint) {
ppkc.add(fmt.Sprintf("Fingerprint of PGP key %s do not match remotely loaded.", u))
continue
}
p.keys = append(p.keys, ckey)
}
if len(p.keys) == 0 {
ppkc.add("No PGP keys loaded.")
return nil
}
if len(ppkc.baseCheck.messages) == 0 {
ppkc.add(fmt.Sprintf("%d PGP key(s) loaded successfully.", len(p.keys)))
}
return nil return nil
} }

View file

@ -9,9 +9,7 @@
package main package main
import ( import (
"bytes"
"context" "context"
"encoding/json"
"errors" "errors"
"time" "time"
@ -19,6 +17,7 @@ import (
"github.com/PaesslerAG/jsonpath" "github.com/PaesslerAG/jsonpath"
"github.com/csaf-poc/csaf_distribution/csaf" "github.com/csaf-poc/csaf_distribution/csaf"
"github.com/csaf-poc/csaf_distribution/util"
) )
const ( const (
@ -112,14 +111,13 @@ func (e *extraction) extractPublisher(path extractFunc) error {
// XXX: It's a bit cumbersome to serialize and deserialize // XXX: It's a bit cumbersome to serialize and deserialize
// it into our own structure. // it into our own structure.
var buf bytes.Buffer publisher := new(csaf.Publisher)
enc := json.NewEncoder(&buf) if err := util.ReMarshalJSON(publisher, p); err != nil {
if err := enc.Encode(p); err != nil {
return err return err
} }
e.publisher = new(csaf.Publisher) if err := publisher.Validate(); err != nil {
if err := json.Unmarshal(buf.Bytes(), e.publisher); err != nil {
return err return err
} }
return e.publisher.Validate() e.publisher = publisher
return nil
} }

20
util/json.go Normal file
View file

@ -0,0 +1,20 @@
// This file is Free Software under the MIT License
// without warranty, see README.md and LICENSES/MIT.txt for details.
//
// SPDX-License-Identifier: MIT
//
// SPDX-FileCopyrightText: 2021 German Federal Office for Information Security (BSI) <https://www.bsi.bund.de>
// Software-Engineering: 2021 Intevation GmbH <https://intevation.de>
package util
import "encoding/json"
// ReMarshalJSON transforms data from src to dst via JSON marshalling.
func ReMarshalJSON(dst, src interface{}) error {
intermediate, err := json.Marshal(src)
if err != nil {
return err
}
return json.Unmarshal(intermediate, dst)
}