Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support ecdsa and RSA keys (#270 with backwards compatibility) #357

Merged
merged 2 commits into from Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 18 additions & 5 deletions cmd/tuf/gen_key.go
Expand Up @@ -6,11 +6,12 @@ import (

"github.com/flynn/go-docopt"
"github.com/theupdateframework/go-tuf"
"github.com/theupdateframework/go-tuf/data"
)

func init() {
register("gen-key", cmdGenKey, `
usage: tuf gen-key [--expires=<days>] <role>
usage: tuf gen-key [--expires=<days>] [--scheme=<scheme>] <role>

Generate a new signing key for the given role.

Expand All @@ -23,28 +24,40 @@ form of TUF_{{ROLE}}_PASSPHRASE

Options:
--expires=<days> Set the root metadata file to expire <days> days from now.
--scheme=<scheme> Set the key scheme to use [default: ed25519].
`)
}

func cmdGenKey(args *docopt.Args, repo *tuf.Repo) error {
role := args.String["<role>"]
var keyids []string

keyScheme := data.KeySchemeEd25519
switch t := args.String["--scheme"]; t {
trishankatdatadog marked this conversation as resolved.
Show resolved Hide resolved
case string(data.KeySchemeEd25519),
string(data.KeySchemeECDSA_SHA2_P256),
string(data.KeySchemeRSASSA_PSS_SHA256):
keyScheme = data.KeyScheme(t)
default:
fmt.Println("Using default key scheme", keyScheme)
}

var err error
var expires time.Time
if arg := args.String["--expires"]; arg != "" {
var expires time.Time
expires, err = parseExpires(arg)
if err != nil {
return err
}
keyids, err = repo.GenKeyWithExpires(role, expires)
} else {
keyids, err = repo.GenKey(role)
expires = data.DefaultExpires(role)
}
keyids, err = repo.GenKeyWithSchemeAndExpires(role, expires, keyScheme)
if err != nil {
return err
}
for _, id := range keyids {
fmt.Println("Generated", role, "key with ID", id)
fmt.Println("Generated", role, keyScheme, "key with ID", id)
}
return nil
}
39 changes: 25 additions & 14 deletions data/types.go
Expand Up @@ -15,18 +15,29 @@ import (
"github.com/secure-systems-lab/go-securesystemslib/cjson"
)

type KeyType string

type KeyScheme string

type HashAlgorithm string

const (
KeyIDLength = sha256.Size * 2
KeyTypeEd25519 = "ed25519"
KeyTypeECDSA_SHA2_P256 = "ecdsa-sha2-nistp256"
KeySchemeEd25519 = "ed25519"
KeySchemeECDSA_SHA2_P256 = "ecdsa-sha2-nistp256"
KeyTypeRSASSA_PSS_SHA256 = "rsa"
KeySchemeRSASSA_PSS_SHA256 = "rsassa-pss-sha256"
KeyIDLength = sha256.Size * 2

KeyTypeEd25519 KeyType = "ed25519"
KeyTypeECDSA_SHA2_P256 KeyType = "ecdsa-sha2-nistp256"
KeyTypeRSASSA_PSS_SHA256 KeyType = "rsa"

KeySchemeEd25519 KeyScheme = "ed25519"
KeySchemeECDSA_SHA2_P256 KeyScheme = "ecdsa-sha2-nistp256"
KeySchemeRSASSA_PSS_SHA256 KeyScheme = "rsassa-pss-sha256"

HashAlgorithmSHA256 HashAlgorithm = "sha256"
HashAlgorithmSHA512 HashAlgorithm = "sha512"
)

var (
HashAlgorithms = []string{"sha256", "sha512"}
HashAlgorithms = []HashAlgorithm{HashAlgorithmSHA256, HashAlgorithmSHA512}
ErrPathsAndPathHashesSet = errors.New("tuf: failed validation of delegated target: paths and path_hash_prefixes are both set")
)

Expand All @@ -41,19 +52,19 @@ type Signature struct {
}

type PublicKey struct {
Type string `json:"keytype"`
Scheme string `json:"scheme"`
Algorithms []string `json:"keyid_hash_algorithms,omitempty"`
Type KeyType `json:"keytype"`
Scheme KeyScheme `json:"scheme"`
Algorithms []HashAlgorithm `json:"keyid_hash_algorithms,omitempty"`
Value json.RawMessage `json:"keyval"`

ids []string
idOnce sync.Once
}

type PrivateKey struct {
Type string `json:"keytype"`
Scheme string `json:"scheme,omitempty"`
Algorithms []string `json:"keyid_hash_algorithms,omitempty"`
Type KeyType `json:"keytype"`
Scheme KeyScheme `json:"scheme,omitempty"`
Algorithms []HashAlgorithm `json:"keyid_hash_algorithms,omitempty"`
Value json.RawMessage `json:"keyval"`
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -8,6 +8,7 @@ require (
github.com/google/gofuzz v1.2.0
github.com/secure-systems-lab/go-securesystemslib v0.4.0
github.com/stretchr/testify v1.8.0
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
Expand All @@ -20,7 +21,6 @@ require (
github.com/kr/text v0.1.0 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
2 changes: 1 addition & 1 deletion internal/signer/sort_test.go
Expand Up @@ -26,7 +26,7 @@ func (s *mockSigner) PublicData() *data.PublicKey {
return &data.PublicKey{
Type: "mock",
Scheme: "mock",
Algorithms: []string{"mock"},
Algorithms: []data.HashAlgorithm{"mock"},
Value: s.value,
}
}
Expand Down
82 changes: 82 additions & 0 deletions pkg/deprecated/deprecated_repo_test.go
@@ -0,0 +1,82 @@
package deprecated

import (
"crypto"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/json"
"testing"

"github.com/secure-systems-lab/go-securesystemslib/cjson"
repo "github.com/theupdateframework/go-tuf"
"github.com/theupdateframework/go-tuf/data"
_ "github.com/theupdateframework/go-tuf/pkg/deprecated/set_ecdsa"
"github.com/theupdateframework/go-tuf/pkg/keys"
. "gopkg.in/check.v1"
)

func Test(t *testing.T) { TestingT(t) }

type RepoSuite struct{}

var _ = Suite(&RepoSuite{})

func genKey(c *C, r *repo.Repo, role string) []string {
keyids, err := r.GenKey(role)
c.Assert(err, IsNil)
c.Assert(len(keyids) > 0, Equals, true)
return keyids
}

// Deprecated ecdsa key support: Support verification against roots that were
// signed with hex-encoded ecdsa keys.
func (rs *RepoSuite) TestDeprecatedHexEncodedKeysSucceed(c *C) {
files := map[string][]byte{"foo.txt": []byte("foo")}
local := repo.MemoryStore(make(map[string]json.RawMessage), files)
r, err := repo.NewRepo(local)
c.Assert(err, IsNil)

r.Init(false)
// Add a root key with hex-encoded ecdsa format
signer, err := keys.GenerateEcdsaKey()
c.Assert(err, IsNil)
type deprecatedP256Verifier struct {
PublicKey data.HexBytes `json:"public"`
}
pub := signer.PublicKey
keyValBytes, err := json.Marshal(&deprecatedP256Verifier{PublicKey: elliptic.Marshal(pub.Curve, pub.X, pub.Y)})
c.Assert(err, IsNil)
publicData := &data.PublicKey{
Type: data.KeyTypeECDSA_SHA2_P256,
Scheme: data.KeySchemeECDSA_SHA2_P256,
Algorithms: data.HashAlgorithms,
Value: keyValBytes,
}
err = r.AddVerificationKey("root", publicData)
c.Assert(err, IsNil)
// Add other keys as normal
genKey(c, r, "targets")
genKey(c, r, "snapshot")
genKey(c, r, "timestamp")
c.Assert(r.AddTarget("foo.txt", nil), IsNil)

// Sign the root role manually
rootMeta, err := r.SignedMeta("root.json")
c.Assert(err, IsNil)
rootCanonical, err := cjson.EncodeCanonical(rootMeta.Signed)
c.Assert(err, IsNil)
hash := sha256.Sum256(rootCanonical)
rootSig, err := signer.PrivateKey.Sign(rand.Reader, hash[:], crypto.SHA256)
c.Assert(err, IsNil)
for _, id := range publicData.IDs() {
c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
KeyID: id,
Signature: rootSig}), IsNil)
}

// Committing should succeed because the deprecated key pkg is added.
c.Assert(r.Snapshot(), IsNil)
c.Assert(r.Timestamp(), IsNil)
c.Assert(r.Commit(), IsNil)
}
23 changes: 23 additions & 0 deletions pkg/deprecated/set_ecdsa/set_ecdsa.go
@@ -0,0 +1,23 @@
package set_ecdsa

import (
"errors"

"github.com/theupdateframework/go-tuf/data"
"github.com/theupdateframework/go-tuf/pkg/keys"
)

/*
Importing this package will allow support for both hex-encoded ECDSA
verifiers and PEM-encoded ECDSA verifiers.
Note that this package imports "github.com/theupdateframework/go-tuf/pkg/keys"
and overrides the ECDSA verifier loaded at init time in that package.
*/

func init() {
_, ok := keys.VerifierMap.Load(data.KeyTypeECDSA_SHA2_P256)
if !ok {
panic(errors.New("expected to override previously loaded PEM-only ECDSA verifier"))
}
keys.VerifierMap.Store(data.KeyTypeECDSA_SHA2_P256, keys.NewDeprecatedEcdsaVerifier)
}
103 changes: 103 additions & 0 deletions pkg/keys/deprecated_ecdsa.go
@@ -0,0 +1,103 @@
package keys

import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io"
"os"

"github.com/theupdateframework/go-tuf/data"
)

func NewDeprecatedEcdsaVerifier() Verifier {
return &ecdsaVerifierWithDeprecatedSupport{}
}

type ecdsaVerifierWithDeprecatedSupport struct {
key *data.PublicKey
// This will switch based on whether this is a PEM-encoded key
// or a deprecated hex-encoded key.
Verifier
}

func (p *ecdsaVerifierWithDeprecatedSupport) UnmarshalPublicKey(key *data.PublicKey) error {
p.key = key
pemVerifier := &EcdsaVerifier{}
if err := pemVerifier.UnmarshalPublicKey(key); err != nil {
// Try the deprecated hex-encoded verifier
hexVerifier := &deprecatedP256Verifier{}
if err := hexVerifier.UnmarshalPublicKey(key); err != nil {
return err
}
p.Verifier = hexVerifier
return nil
}
p.Verifier = pemVerifier
return nil
}

/*
Deprecated ecdsaVerifier that used hex-encoded public keys.
This MAY be used to verify existing metadata that used this
old format. This will be deprecated soon, ensure that repositories
are re-signed and clients receieve a fully compliant root.
*/

type deprecatedP256Verifier struct {
PublicKey data.HexBytes `json:"public"`
key *data.PublicKey
}

func (p *deprecatedP256Verifier) Public() string {
return p.PublicKey.String()
}

func (p *deprecatedP256Verifier) Verify(msg, sigBytes []byte) error {
x, y := elliptic.Unmarshal(elliptic.P256(), p.PublicKey)
k := &ecdsa.PublicKey{
Curve: elliptic.P256(),
X: x,
Y: y,
}

hash := sha256.Sum256(msg)

if !ecdsa.VerifyASN1(k, hash[:], sigBytes) {
return errors.New("tuf: deprecated ecdsa signature verification failed")
}
return nil
}

func (p *deprecatedP256Verifier) MarshalPublicKey() *data.PublicKey {
return p.key
}

func (p *deprecatedP256Verifier) UnmarshalPublicKey(key *data.PublicKey) error {
// Prepare decoder limited to 512Kb
dec := json.NewDecoder(io.LimitReader(bytes.NewReader(key.Value), MaxJSONKeySize))

// Unmarshal key value
if err := dec.Decode(p); err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
return fmt.Errorf("tuf: the public key is truncated or too large: %w", err)
}
return err
}

curve := elliptic.P256()

// Parse as uncompressed marshalled point.
x, _ := elliptic.Unmarshal(curve, p.PublicKey)
if x == nil {
return errors.New("tuf: invalid ecdsa public key point")
}

p.key = key
fmt.Fprintln(os.Stderr, "tuf: warning using deprecated ecdsa hex-encoded keys")
return nil
}