mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 05:40:11 +01:00
Implemented loading of PGP keys.
This commit is contained in:
parent
fe09d0ea65
commit
f945937a6d
3 changed files with 142 additions and 13 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
20
util/json.go
Normal 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)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue