Skip to content

Commit

Permalink
[SINT-408] Test Security of Uptane Partial Verification (#11449)
Browse files Browse the repository at this point in the history
* list TODO as comment

* test rejection of unsigned targets

* test rejects invalid signature

* test rejection of revoked keys

also test valid root key rotation

* minor: adding comments

* fix linter error (comment exported values)

* minor: add some documentation

* factorize similar error handling

* (minor) use require.New

* use "config" as filename

resolves #11449 (comment)
  • Loading branch information
cedricvanrompay-datadog authored Apr 4, 2022
1 parent 90a4a63 commit d2b9d5c
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 26 deletions.
14 changes: 10 additions & 4 deletions pkg/config/remote/uptane/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,11 @@ func newTestRepository(version int, configTargets data.TargetFiles, directorTarg
repos.directorTimestampVersion = 20 + version
repos.directorTargetsVersion = 200 + version
repos.directorSnapshotVersion = 2000 + version
repos.configRoot = generateRoot(repos.configRootKey, version, repos.configTimestampKey, repos.configTargetsKey, repos.configSnapshotKey)
repos.configRoot = generateRoot(repos.configRootKey, version, repos.configTimestampKey, repos.configTargetsKey, repos.configSnapshotKey, nil)
repos.configTargets = generateTargets(repos.configTargetsKey, 100+version, configTargets)
repos.configSnapshot = generateSnapshot(repos.configSnapshotKey, 1000+version, repos.configTargetsVersion)
repos.configTimestamp = generateTimestamp(repos.configTimestampKey, 10+version, repos.configSnapshotVersion, repos.configSnapshot)
repos.directorRoot = generateRoot(repos.directorRootKey, version, repos.directorTimestampKey, repos.directorTargetsKey, repos.directorSnapshotKey)
repos.directorRoot = generateRoot(repos.directorRootKey, version, repos.directorTimestampKey, repos.directorTargetsKey, repos.directorSnapshotKey, nil)
repos.directorTargets = generateTargets(repos.directorTargetsKey, 200+version, directorTargets)
repos.directorSnapshot = generateSnapshot(repos.directorSnapshotKey, 2000+version, repos.directorTargetsVersion)
repos.directorTimestamp = generateTimestamp(repos.directorTimestampKey, 20+version, repos.directorSnapshotVersion, repos.directorSnapshot)
Expand All @@ -326,7 +326,7 @@ func (r testRepositories) toUpdate() *pbgo.LatestConfigsResponse {
}
}

func generateRoot(key keys.Signer, version int, timestampKey keys.Signer, targetsKey keys.Signer, snapshotKey keys.Signer) []byte {
func generateRoot(key keys.Signer, version int, timestampKey keys.Signer, targetsKey keys.Signer, snapshotKey keys.Signer, previousRootKey keys.Signer) []byte {
root := data.NewRoot()
root.Version = version
root.Expires = time.Now().Add(1 * time.Hour)
Expand All @@ -350,7 +350,13 @@ func generateRoot(key keys.Signer, version int, timestampKey keys.Signer, target
KeyIDs: snapshotKey.PublicData().IDs(),
Threshold: 1,
}
signedRoot, _ := sign.Marshal(&root, key)

rootSigners := []keys.Signer{key}
if previousRootKey != nil {
rootSigners = append(rootSigners, previousRootKey)
}

signedRoot, _ := sign.Marshal(&root, rootSigners...)
serializedRoot, _ := json.Marshal(signedRoot)
return serializedRoot
}
Expand Down
25 changes: 23 additions & 2 deletions pkg/config/remote/uptane/partial_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package uptane
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
Expand Down Expand Up @@ -52,6 +53,7 @@ type PartialState struct {
}

// PartialClient is a partial uptane client
// (see https://uptane.github.io/papers/uptane-standard.1.2.0.html#rfc.section.5.4.4.1)
type PartialClient struct {
sync.Mutex

Expand All @@ -68,6 +70,7 @@ type PartialClient struct {
}

// NewPartialClient creates a new partial uptane client
// (see https://uptane.github.io/papers/uptane-standard.1.2.0.html#rfc.section.5.4.4.1)
func NewPartialClient() (*PartialClient, error) {
localStore := client.MemoryLocalStore()
err := localStore.SetMeta("root.json", json.RawMessage(meta.RootsDirector().Last()))
Expand Down Expand Up @@ -167,7 +170,15 @@ func (c *PartialClient) Update(response *pbgo.ClientGetConfigsResponse) error {
}
err = c.validateAndUpdateTargets(response.Targets.Raw)
if err != nil {
return err
if (errors.Is(err, verify.ErrInvalid) ||
errors.As(err, &verify.ErrRoleThreshold{})) {
return fmt.Errorf(
"updating targets: %w",
&ErrInvalid{err.Error()},
)
}

return fmt.Errorf("updating target: error with unexpected type (%T): %w", err, err)
}
c.targetFiles = response.TargetFiles
for _, target := range response.TargetFiles {
Expand Down Expand Up @@ -217,6 +228,16 @@ func (c *PartialClient) TargetFile(path string) ([]byte, error) {
return c.targetFile(path)
}

// ErrInvalid represents the Uptane client rejecting invalid data
// (malformed or not signed properly)
type ErrInvalid struct {
msg string
}

func (err *ErrInvalid) Error() string {
return err.msg
}

func (c *PartialClient) targetFile(path string) ([]byte, error) {
var targetFile *pbgo.File
for _, target := range c.targetFiles {
Expand All @@ -229,7 +250,7 @@ func (c *PartialClient) targetFile(path string) ([]byte, error) {
}
targetMeta, hasMeta := c.targetMetas[path]
if !hasMeta {
return nil, fmt.Errorf("target file meta %s not found", path)
return nil, &ErrInvalid{fmt.Sprintf("target file meta %s not found", path)}
}
if len(targetMeta.HashAlgorithms()) == 0 {
return nil, fmt.Errorf("target file %s has no hash", path)
Expand Down
Loading

0 comments on commit d2b9d5c

Please sign in to comment.