diff --git a/cmd/tuf/gen_key.go b/cmd/tuf/gen_key.go index b994843a..bd4334ae 100644 --- a/cmd/tuf/gen_key.go +++ b/cmd/tuf/gen_key.go @@ -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=] +usage: tuf gen-key [--expires=] [--scheme=] Generate a new signing key for the given role. @@ -23,28 +24,40 @@ form of TUF_{{ROLE}}_PASSPHRASE Options: --expires= Set the root metadata file to expire days from now. + --scheme= Set the key scheme to use [default: ed25519]. `) } func cmdGenKey(args *docopt.Args, repo *tuf.Repo) error { role := args.String[""] var keyids []string + + keyScheme := data.KeySchemeEd25519 + switch t := args.String["--scheme"]; t { + 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 } diff --git a/data/types.go b/data/types.go index 0fcc9218..d051b762 100644 --- a/data/types.go +++ b/data/types.go @@ -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") ) @@ -41,9 +52,9 @@ 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 @@ -51,9 +62,9 @@ type PublicKey struct { } 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"` } diff --git a/go.mod b/go.mod index 1a838de1..a5151da0 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 diff --git a/internal/signer/sort_test.go b/internal/signer/sort_test.go index eb9f94d7..afda5f87 100644 --- a/internal/signer/sort_test.go +++ b/internal/signer/sort_test.go @@ -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, } } diff --git a/pkg/deprecated/deprecated_repo_test.go b/pkg/deprecated/deprecated_repo_test.go new file mode 100644 index 00000000..6d194ab5 --- /dev/null +++ b/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) +} diff --git a/pkg/deprecated/set_ecdsa/set_ecdsa.go b/pkg/deprecated/set_ecdsa/set_ecdsa.go new file mode 100644 index 00000000..de3771e3 --- /dev/null +++ b/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) +} diff --git a/pkg/keys/deprecated_ecdsa.go b/pkg/keys/deprecated_ecdsa.go new file mode 100644 index 00000000..4a8f151e --- /dev/null +++ b/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 +} diff --git a/pkg/keys/deprecated_ecdsa_test.go b/pkg/keys/deprecated_ecdsa_test.go new file mode 100644 index 00000000..ddfaa84d --- /dev/null +++ b/pkg/keys/deprecated_ecdsa_test.go @@ -0,0 +1,129 @@ +package keys + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/json" + "errors" + + "github.com/theupdateframework/go-tuf/data" + . "gopkg.in/check.v1" +) + +type DeprecatedECDSASuite struct{} + +var _ = Suite(DeprecatedECDSASuite{}) + +type deprecatedEcdsaSigner struct { + *ecdsa.PrivateKey +} + +type deprecatedEcdsaPublic struct { + PublicKey data.HexBytes `json:"public"` +} + +func (s deprecatedEcdsaSigner) PublicData() *data.PublicKey { + pub := s.Public().(*ecdsa.PublicKey) + keyValBytes, _ := json.Marshal(deprecatedEcdsaPublic{ + PublicKey: elliptic.Marshal(pub.Curve, pub.X, pub.Y)}) + return &data.PublicKey{ + Type: data.KeyTypeECDSA_SHA2_P256, + Scheme: data.KeySchemeECDSA_SHA2_P256, + Algorithms: data.HashAlgorithms, + Value: keyValBytes, + } +} + +func (s deprecatedEcdsaSigner) SignMessage(message []byte) ([]byte, error) { + hash := sha256.Sum256(message) + return s.PrivateKey.Sign(rand.Reader, hash[:], crypto.SHA256) +} + +func (s deprecatedEcdsaSigner) ContainsID(id string) bool { + return s.PublicData().ContainsID(id) +} + +func (deprecatedEcdsaSigner) MarshalPrivateKey() (*data.PrivateKey, error) { + return nil, errors.New("not implemented for test") +} + +func (deprecatedEcdsaSigner) UnmarshalPrivateKey(key *data.PrivateKey) error { + return errors.New("not implemented for test") +} + +func generatedDeprecatedSigner() (*deprecatedEcdsaSigner, error) { + privkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + return &deprecatedEcdsaSigner{privkey}, nil +} + +func (DeprecatedECDSASuite) TestSignVerifyDeprecatedFormat(c *C) { + // Create an ecdsa key with a deprecated format. + signer, err := generatedDeprecatedSigner() + c.Assert(err, IsNil) + msg := []byte("foo") + sig, err := signer.SignMessage(msg) + c.Assert(err, IsNil) + + 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, + } + + deprecatedEcdsa := NewDeprecatedEcdsaVerifier() + err = deprecatedEcdsa.UnmarshalPublicKey(publicData) + c.Assert(err, IsNil) + c.Assert(deprecatedEcdsa.Verify(msg, sig), IsNil) +} + +func (DeprecatedECDSASuite) TestECDSAVerifyMismatchMessage(c *C) { + signer, err := generatedDeprecatedSigner() + c.Assert(err, IsNil) + msg := []byte("foo") + sig, err := signer.SignMessage(msg) + c.Assert(err, IsNil) + publicData := signer.PublicData() + deprecatedEcdsa := NewDeprecatedEcdsaVerifier() + err = deprecatedEcdsa.UnmarshalPublicKey(publicData) + c.Assert(err, IsNil) + c.Assert(deprecatedEcdsa.Verify([]byte("notfoo"), sig), ErrorMatches, "tuf: deprecated ecdsa signature verification failed") +} + +func (DeprecatedECDSASuite) TestECDSAVerifyMismatchPubKey(c *C) { + signer, err := generatedDeprecatedSigner() + c.Assert(err, IsNil) + msg := []byte("foo") + sig, err := signer.SignMessage(msg) + c.Assert(err, IsNil) + + signerNew, err := generatedDeprecatedSigner() + c.Assert(err, IsNil) + deprecatedEcdsa := NewDeprecatedEcdsaVerifier() + err = deprecatedEcdsa.UnmarshalPublicKey(signerNew.PublicData()) + c.Assert(err, IsNil) + c.Assert(deprecatedEcdsa.Verify([]byte("notfoo"), sig), ErrorMatches, "tuf: deprecated ecdsa signature verification failed") +} + +func (DeprecatedECDSASuite) TestMarshalUnmarshalPublicKey(c *C) { + signer, err := generatedDeprecatedSigner() + c.Assert(err, IsNil) + + pub := signer.PublicData() + + deprecatedEcdsa := NewDeprecatedEcdsaVerifier() + err = deprecatedEcdsa.UnmarshalPublicKey(pub) + c.Assert(err, IsNil) + + c.Assert(deprecatedEcdsa.MarshalPublicKey(), DeepEquals, pub) +} diff --git a/pkg/keys/ecdsa.go b/pkg/keys/ecdsa.go index 22ed12b0..ee93e330 100644 --- a/pkg/keys/ecdsa.go +++ b/pkg/keys/ecdsa.go @@ -4,64 +4,65 @@ import ( "bytes" "crypto/ecdsa" "crypto/elliptic" + "crypto/rand" "crypto/sha256" - "encoding/asn1" + "crypto/x509" "encoding/json" + "encoding/pem" "errors" "fmt" "io" - "math/big" "github.com/theupdateframework/go-tuf/data" ) func init() { - VerifierMap.Store(data.KeyTypeECDSA_SHA2_P256, NewEcdsaVerifier) + // Note: we use LoadOrStore here to prevent accidentally overriding the + // an explicit deprecated ECDSA verifier. + // TODO: When deprecated ECDSA is removed, this can switch back to Store. + VerifierMap.LoadOrStore(data.KeyTypeECDSA_SHA2_P256, NewEcdsaVerifier) + SignerMap.Store(data.KeyTypeECDSA_SHA2_P256, newEcdsaSigner) } func NewEcdsaVerifier() Verifier { - return &p256Verifier{} + return &EcdsaVerifier{} } -type ecdsaSignature struct { - R, S *big.Int +func newEcdsaSigner() Signer { + return &ecdsaSigner{} } -type p256Verifier struct { - PublicKey data.HexBytes `json:"public"` +type EcdsaVerifier struct { + PublicKey *PKIXPublicKey `json:"public"` + ecdsaKey *ecdsa.PublicKey key *data.PublicKey } -func (p *p256Verifier) Public() string { - return p.PublicKey.String() -} - -func (p *p256Verifier) Verify(msg, sigBytes []byte) error { - x, y := elliptic.Unmarshal(elliptic.P256(), p.PublicKey) - k := &ecdsa.PublicKey{ - Curve: elliptic.P256(), - X: x, - Y: y, - } - - var sig ecdsaSignature - if _, err := asn1.Unmarshal(sigBytes, &sig); err != nil { - return err +func (p *EcdsaVerifier) Public() string { + // This is already verified to succeed when unmarshalling a public key. + r, err := x509.MarshalPKIXPublicKey(p.ecdsaKey) + if err != nil { + // TODO: Gracefully handle these errors. + // See https://github.com/theupdateframework/go-tuf/issues/363 + panic(err) } + return string(r) +} +func (p *EcdsaVerifier) Verify(msg, sigBytes []byte) error { hash := sha256.Sum256(msg) - if !ecdsa.Verify(k, hash[:], sig.R, sig.S) { + if !ecdsa.VerifyASN1(p.ecdsaKey, hash[:], sigBytes) { return errors.New("tuf: ecdsa signature verification failed") } return nil } -func (p *p256Verifier) MarshalPublicKey() *data.PublicKey { +func (p *EcdsaVerifier) MarshalPublicKey() *data.PublicKey { return p.key } -func (p *p256Verifier) UnmarshalPublicKey(key *data.PublicKey) error { +func (p *EcdsaVerifier) UnmarshalPublicKey(key *data.PublicKey) error { // Prepare decoder limited to 512Kb dec := json.NewDecoder(io.LimitReader(bytes.NewReader(key.Value), MaxJSONKeySize)) @@ -73,14 +74,98 @@ func (p *p256Verifier) UnmarshalPublicKey(key *data.PublicKey) error { return err } - curve := elliptic.P256() + ecdsaKey, ok := p.PublicKey.PublicKey.(*ecdsa.PublicKey) + if !ok { + return fmt.Errorf("invalid public key") + } - // Parse as uncompressed marshalled point. - x, _ := elliptic.Unmarshal(curve, p.PublicKey) - if x == nil { - return errors.New("tuf: invalid ecdsa public key point") + if _, err := x509.MarshalPKIXPublicKey(ecdsaKey); err != nil { + return fmt.Errorf("marshalling to PKIX key: invalid public key") } + p.ecdsaKey = ecdsaKey p.key = key return nil } + +type ecdsaSigner struct { + *ecdsa.PrivateKey +} + +type ecdsaPrivateKeyValue struct { + Private string `json:"private"` + Public *PKIXPublicKey `json:"public"` +} + +func (s *ecdsaSigner) PublicData() *data.PublicKey { + // This uses a trusted public key JSON format with a trusted Public value. + keyValBytes, _ := json.Marshal(EcdsaVerifier{PublicKey: &PKIXPublicKey{PublicKey: s.Public()}}) + return &data.PublicKey{ + Type: data.KeyTypeECDSA_SHA2_P256, + Scheme: data.KeySchemeECDSA_SHA2_P256, + Algorithms: data.HashAlgorithms, + Value: keyValBytes, + } +} + +func (s *ecdsaSigner) SignMessage(message []byte) ([]byte, error) { + hash := sha256.Sum256(message) + return ecdsa.SignASN1(rand.Reader, s.PrivateKey, hash[:]) +} + +func (s *ecdsaSigner) MarshalPrivateKey() (*data.PrivateKey, error) { + priv, err := x509.MarshalECPrivateKey(s.PrivateKey) + if err != nil { + return nil, err + } + pemKey := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: priv}) + val, err := json.Marshal(ecdsaPrivateKeyValue{ + Private: string(pemKey), + Public: &PKIXPublicKey{PublicKey: s.Public()}, + }) + if err != nil { + return nil, err + } + return &data.PrivateKey{ + Type: data.KeyTypeECDSA_SHA2_P256, + Scheme: data.KeySchemeECDSA_SHA2_P256, + Algorithms: data.HashAlgorithms, + Value: val, + }, nil +} + +func (s *ecdsaSigner) UnmarshalPrivateKey(key *data.PrivateKey) error { + val := ecdsaPrivateKeyValue{} + if err := json.Unmarshal(key.Value, &val); err != nil { + return err + } + block, _ := pem.Decode([]byte(val.Private)) + if block == nil { + return errors.New("invalid PEM value") + } + if block.Type != "EC PRIVATE KEY" { + return fmt.Errorf("invalid block type: %s", block.Type) + } + k, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return err + } + if k.Curve != elliptic.P256() { + return errors.New("unsupported ecdsa curve") + } + if _, err := json.Marshal(EcdsaVerifier{ + PublicKey: &PKIXPublicKey{PublicKey: k.Public()}}); err != nil { + return fmt.Errorf("invalid public key: %s", err) + } + + s.PrivateKey = k + return nil +} + +func GenerateEcdsaKey() (*ecdsaSigner, error) { + privkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + return &ecdsaSigner{privkey}, nil +} diff --git a/pkg/keys/ecdsa_test.go b/pkg/keys/ecdsa_test.go index a141d582..2fe6348e 100644 --- a/pkg/keys/ecdsa_test.go +++ b/pkg/keys/ecdsa_test.go @@ -17,28 +17,99 @@ import ( type ECDSASuite struct{} -var _ = Suite(&ECDSASuite{}) +var _ = Suite(ECDSASuite{}) -func (ECDSASuite) TestUnmarshalECDSA(c *C) { - priv, err := ecdsa.GenerateKey(elliptic.P256(), strings.NewReader("00001-deterministic-buffer-for-key-generation")) +func (ECDSASuite) TestSignVerify(c *C) { + signer, err := GenerateEcdsaKey() + c.Assert(err, IsNil) + msg := []byte("foo") + sig, err := signer.SignMessage(msg) + c.Assert(err, IsNil) + publicData := signer.PublicData() + pubKey, err := GetVerifier(publicData) + c.Assert(err, IsNil) + c.Assert(pubKey.Verify(msg, sig), IsNil) +} + +func (ECDSASuite) TestECDSAVerifyMismatchMessage(c *C) { + signer, err := GenerateEcdsaKey() + c.Assert(err, IsNil) + msg := []byte("foo") + sig, err := signer.SignMessage(msg) + c.Assert(err, IsNil) + publicData := signer.PublicData() + pubKey, err := GetVerifier(publicData) c.Assert(err, IsNil) + c.Assert(pubKey.Verify([]byte("notfoo"), sig), ErrorMatches, "tuf: ecdsa signature verification failed") +} - // Marshall as non compressed point - pub := elliptic.Marshal(elliptic.P256(), priv.X, priv.Y) +func (ECDSASuite) TestECDSAVerifyMismatchPubKey(c *C) { + signer, err := GenerateEcdsaKey() + c.Assert(err, IsNil) + msg := []byte("foo") + sig, err := signer.SignMessage(msg) + c.Assert(err, IsNil) - publicKey, err := json.Marshal(map[string]string{ - "public": hex.EncodeToString(pub), - }) + signerNew, err := GenerateEcdsaKey() c.Assert(err, IsNil) + pubKey, err := GetVerifier(signerNew.PublicData()) + c.Assert(err, IsNil) + c.Assert(pubKey.Verify([]byte("notfoo"), sig), ErrorMatches, "tuf: ecdsa signature verification failed") +} - badKey := &data.PublicKey{ +func (ECDSASuite) TestSignVerifyDeprecatedFails(c *C) { + // Create an ecdsa key with a deprecated format. + signer, err := 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: publicKey, + Value: keyValBytes, } + + _, err = GetVerifier(publicData) + c.Assert(err, ErrorMatches, "tuf: error unmarshalling key: invalid PEM value") +} + +func (ECDSASuite) TestMarshalUnmarshalPublicKey(c *C) { + signer, err := GenerateEcdsaKey() + c.Assert(err, IsNil) + publicData := signer.PublicData() + pubKey, err := GetVerifier(publicData) + c.Assert(err, IsNil) + c.Assert(pubKey.MarshalPublicKey(), DeepEquals, publicData) +} + +func (ECDSASuite) TestMarshalUnmarshalPrivateKey(c *C) { + signer, err := GenerateEcdsaKey() + c.Assert(err, IsNil) + privateData, err := signer.MarshalPrivateKey() + c.Assert(err, IsNil) + c.Assert(privateData.Type, Equals, data.KeyTypeECDSA_SHA2_P256) + c.Assert(privateData.Scheme, Equals, data.KeySchemeECDSA_SHA2_P256) + c.Assert(privateData.Algorithms, DeepEquals, data.HashAlgorithms) + s, err := GetSigner(privateData) + c.Assert(err, IsNil) + c.Assert(s, DeepEquals, signer) +} + +func (ECDSASuite) TestUnmarshalECDSA(c *C) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), strings.NewReader("00001-deterministic-buffer-for-key-generation")) + c.Assert(err, IsNil) + + signer := &ecdsaSigner{priv} + goodKey := signer.PublicData() + verifier := NewEcdsaVerifier() - c.Assert(verifier.UnmarshalPublicKey(badKey), IsNil) + c.Assert(verifier.UnmarshalPublicKey(goodKey), IsNil) } func (ECDSASuite) TestUnmarshalECDSA_Invalid(c *C) { diff --git a/pkg/keys/ed25519.go b/pkg/keys/ed25519.go index 82178f5d..1e4c66cc 100644 --- a/pkg/keys/ed25519.go +++ b/pkg/keys/ed25519.go @@ -15,8 +15,8 @@ import ( ) func init() { - SignerMap.Store(data.KeySchemeEd25519, NewEd25519Signer) - VerifierMap.Store(data.KeySchemeEd25519, NewEd25519Verifier) + SignerMap.Store(data.KeyTypeEd25519, NewEd25519Signer) + VerifierMap.Store(data.KeyTypeEd25519, NewEd25519Verifier) } func NewEd25519Signer() Signer { @@ -73,10 +73,6 @@ type Ed25519PrivateKeyValue struct { type ed25519Signer struct { ed25519.PrivateKey - - keyType string - keyScheme string - keyAlgorithms []string } func GenerateEd25519Key() (*ed25519Signer, error) { @@ -88,19 +84,13 @@ func GenerateEd25519Key() (*ed25519Signer, error) { return nil, err } return &ed25519Signer{ - PrivateKey: ed25519.PrivateKey(data.HexBytes(private)), - keyType: data.KeyTypeEd25519, - keyScheme: data.KeySchemeEd25519, - keyAlgorithms: data.HashAlgorithms, + PrivateKey: ed25519.PrivateKey(data.HexBytes(private)), }, nil } func NewEd25519SignerFromKey(keyValue Ed25519PrivateKeyValue) *ed25519Signer { return &ed25519Signer{ - PrivateKey: ed25519.PrivateKey(data.HexBytes(keyValue.Private)), - keyType: data.KeyTypeEd25519, - keyScheme: data.KeySchemeEd25519, - keyAlgorithms: data.HashAlgorithms, + PrivateKey: ed25519.PrivateKey(data.HexBytes(keyValue.Private)), } } @@ -117,9 +107,9 @@ func (e *ed25519Signer) MarshalPrivateKey() (*data.PrivateKey, error) { return nil, err } return &data.PrivateKey{ - Type: e.keyType, - Scheme: e.keyScheme, - Algorithms: e.keyAlgorithms, + Type: data.KeyTypeEd25519, + Scheme: data.KeySchemeEd25519, + Algorithms: data.HashAlgorithms, Value: valueBytes, }, nil } @@ -155,10 +145,7 @@ func (e *ed25519Signer) UnmarshalPrivateKey(key *data.PrivateKey) error { // Prepare signer *e = ed25519Signer{ - PrivateKey: ed25519.PrivateKey(data.HexBytes(keyValue.Private)), - keyType: key.Type, - keyScheme: key.Scheme, - keyAlgorithms: key.Algorithms, + PrivateKey: ed25519.PrivateKey(data.HexBytes(keyValue.Private)), } return nil } @@ -166,9 +153,9 @@ func (e *ed25519Signer) UnmarshalPrivateKey(key *data.PrivateKey) error { func (e *ed25519Signer) PublicData() *data.PublicKey { keyValBytes, _ := json.Marshal(ed25519Verifier{PublicKey: []byte(e.PrivateKey.Public().(ed25519.PublicKey))}) return &data.PublicKey{ - Type: e.keyType, - Scheme: e.keyScheme, - Algorithms: e.keyAlgorithms, + Type: data.KeyTypeEd25519, + Scheme: data.KeySchemeEd25519, + Algorithms: data.HashAlgorithms, Value: keyValBytes, } } diff --git a/pkg/keys/keys_test.go b/pkg/keys/keys_test.go index 956a318e..c1a7d018 100644 --- a/pkg/keys/keys_test.go +++ b/pkg/keys/keys_test.go @@ -3,6 +3,7 @@ package keys import ( "testing" + "github.com/theupdateframework/go-tuf/data" . "gopkg.in/check.v1" ) @@ -32,7 +33,7 @@ func (KeysSuite) TestSignerKeyIDs(c *C) { c.Assert(err, IsNil) privKey, err = signer.MarshalPrivateKey() c.Assert(err, IsNil) - privKey.Algorithms = []string{} + privKey.Algorithms = []data.HashAlgorithm{} err = signer.UnmarshalPrivateKey(privKey) c.Assert(err, IsNil) } diff --git a/pkg/keys/pkix.go b/pkg/keys/pkix.go new file mode 100644 index 00000000..e58d4c9f --- /dev/null +++ b/pkg/keys/pkix.go @@ -0,0 +1,56 @@ +package keys + +import ( + "bytes" + "crypto" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" +) + +type PKIXPublicKey struct { + crypto.PublicKey +} + +func (p *PKIXPublicKey) MarshalJSON() ([]byte, error) { + bytes, err := x509.MarshalPKIXPublicKey(p.PublicKey) + if err != nil { + return nil, err + } + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: bytes, + }) + return json.Marshal(string(pemBytes)) +} + +func (p *PKIXPublicKey) UnmarshalJSON(b []byte) error { + var pemValue string + // Prepare decoder limited to 512Kb + dec := json.NewDecoder(io.LimitReader(bytes.NewReader(b), MaxJSONKeySize)) + + // Unmarshal key value + if err := dec.Decode(&pemValue); 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 + } + + block, _ := pem.Decode([]byte(pemValue)) + if block == nil { + return errors.New("invalid PEM value") + } + if block.Type != "PUBLIC KEY" { + return fmt.Errorf("invalid block type: %s", block.Type) + } + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + p.PublicKey = pub + return nil +} diff --git a/pkg/keys/pkix_test.go b/pkg/keys/pkix_test.go new file mode 100644 index 00000000..4debddeb --- /dev/null +++ b/pkg/keys/pkix_test.go @@ -0,0 +1,62 @@ +package keys + +import ( + "crypto/ecdsa" + "crypto/rand" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "io" + + . "gopkg.in/check.v1" +) + +const ecdsaKey = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEftgasQA68yvumeXZmcOTSIHKfbmx +WT1oYuRF0Un3tKxnzip6xAYwlz0Dt96DUh+0P7BruHH2O6s4MiRR9/TuNw== +-----END PUBLIC KEY----- +` + +type PKIXSuite struct{} + +var _ = Suite(&PKIXSuite{}) + +func (PKIXSuite) TestMarshalJSON(c *C) { + block, _ := pem.Decode([]byte(ecdsaKey)) + key, err := x509.ParsePKIXPublicKey(block.Bytes) + c.Assert(err, IsNil) + k := PKIXPublicKey{PublicKey: key} + buf, err := json.Marshal(&k) + c.Assert(err, IsNil) + var val string + err = json.Unmarshal(buf, &val) + c.Assert(err, IsNil) + c.Assert(val, Equals, ecdsaKey) +} + +func (PKIXSuite) TestUnmarshalJSON(c *C) { + buf, err := json.Marshal(ecdsaKey) + c.Assert(err, IsNil) + var k PKIXPublicKey + err = json.Unmarshal(buf, &k) + c.Assert(err, IsNil) + c.Assert(k.PublicKey, FitsTypeOf, (*ecdsa.PublicKey)(nil)) +} + +func (PKIXSuite) TestUnmarshalPKIX_TooLongContent(c *C) { + randomSeed := make([]byte, MaxJSONKeySize) + _, err := io.ReadFull(rand.Reader, randomSeed) + c.Assert(err, IsNil) + + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: randomSeed, + }) + tooLongPayload, err := json.Marshal(string(pemBytes)) + c.Assert(err, IsNil) + + var k PKIXPublicKey + err = json.Unmarshal(tooLongPayload, &k) + c.Assert(errors.Is(err, io.ErrUnexpectedEOF), Equals, true) +} diff --git a/pkg/keys/rsa.go b/pkg/keys/rsa.go index 28c82d14..618f104e 100644 --- a/pkg/keys/rsa.go +++ b/pkg/keys/rsa.go @@ -1,6 +1,7 @@ package keys import ( + "bytes" "crypto" "crypto/rand" "crypto/rsa" @@ -9,36 +10,38 @@ import ( "encoding/json" "encoding/pem" "errors" + "fmt" + "io" "github.com/theupdateframework/go-tuf/data" ) func init() { - VerifierMap.Store(data.KeyTypeRSASSA_PSS_SHA256, NewRsaVerifier) - SignerMap.Store(data.KeyTypeRSASSA_PSS_SHA256, NewRsaSigner) + VerifierMap.Store(data.KeyTypeRSASSA_PSS_SHA256, newRsaVerifier) + SignerMap.Store(data.KeyTypeRSASSA_PSS_SHA256, newRsaSigner) } -func NewRsaVerifier() Verifier { +func newRsaVerifier() Verifier { return &rsaVerifier{} } -func NewRsaSigner() Signer { +func newRsaSigner() Signer { return &rsaSigner{} } type rsaVerifier struct { - PublicKey string `json:"public"` + PublicKey *PKIXPublicKey `json:"public"` rsaKey *rsa.PublicKey key *data.PublicKey } func (p *rsaVerifier) Public() string { - // Unique public key identifier, use a uniform encodng + // This is already verified to succeed when unmarshalling a public key. r, err := x509.MarshalPKIXPublicKey(p.rsaKey) if err != nil { - // This shouldn't happen with a valid rsa key, but fallback on the - // JSON public key string - return string(p.PublicKey) + // TODO: Gracefully handle these errors. + // See https://github.com/theupdateframework/go-tuf/issues/363 + panic(err) } return string(r) } @@ -54,56 +57,42 @@ func (p *rsaVerifier) MarshalPublicKey() *data.PublicKey { } func (p *rsaVerifier) UnmarshalPublicKey(key *data.PublicKey) error { - if err := json.Unmarshal(key.Value, p); err != nil { - return err - } - var err error - p.rsaKey, err = parseKey(p.PublicKey) - if err != nil { + // 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 } - p.key = key - return nil -} -// parseKey tries to parse a PEM []byte slice by attempting PKCS1 and PKIX in order. -func parseKey(data string) (*rsa.PublicKey, error) { - block, _ := pem.Decode([]byte(data)) - if block == nil { - return nil, errors.New("tuf: pem decoding public key failed") + rsaKey, ok := p.PublicKey.PublicKey.(*rsa.PublicKey) + if !ok { + return fmt.Errorf("invalid public key") } - rsaPub, err := x509.ParsePKCS1PublicKey(block.Bytes) - if err == nil { - return rsaPub, nil - } - key, err := x509.ParsePKIXPublicKey(block.Bytes) - if err == nil { - rsaPub, ok := key.(*rsa.PublicKey) - if !ok { - return nil, errors.New("tuf: invalid rsa key") - } - return rsaPub, nil + + if _, err := x509.MarshalPKIXPublicKey(rsaKey); err != nil { + return fmt.Errorf("marshalling to PKIX key: invalid public key") } - return nil, errors.New("tuf: error unmarshalling rsa key") + + p.rsaKey = rsaKey + p.key = key + return nil } type rsaSigner struct { *rsa.PrivateKey } -type rsaPublic struct { - // PEM encoded public key. - PublicKey string `json:"public"` +type rsaPrivateKeyValue struct { + Private string `json:"private"` + Public *PKIXPublicKey `json:"public"` } func (s *rsaSigner) PublicData() *data.PublicKey { - pub, _ := x509.MarshalPKIXPublicKey(s.Public().(*rsa.PublicKey)) - pubBytes := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PUBLIC KEY", - Bytes: pub, - }) - - keyValBytes, _ := json.Marshal(rsaPublic{PublicKey: string(pubBytes)}) + keyValBytes, _ := json.Marshal(rsaVerifier{PublicKey: &PKIXPublicKey{PublicKey: s.Public()}}) return &data.PublicKey{ Type: data.KeyTypeRSASSA_PSS_SHA256, Scheme: data.KeySchemeRSASSA_PSS_SHA256, @@ -122,11 +111,46 @@ func (s *rsaSigner) ContainsID(id string) bool { } func (s *rsaSigner) MarshalPrivateKey() (*data.PrivateKey, error) { - return nil, errors.New("not implemented for test") + priv := x509.MarshalPKCS1PrivateKey(s.PrivateKey) + pemKey := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: priv}) + val, err := json.Marshal(rsaPrivateKeyValue{ + Private: string(pemKey), + Public: &PKIXPublicKey{PublicKey: s.Public()}, + }) + if err != nil { + return nil, err + } + return &data.PrivateKey{ + Type: data.KeyTypeRSASSA_PSS_SHA256, + Scheme: data.KeySchemeRSASSA_PSS_SHA256, + Algorithms: data.HashAlgorithms, + Value: val, + }, nil } func (s *rsaSigner) UnmarshalPrivateKey(key *data.PrivateKey) error { - return errors.New("not implemented for test") + val := rsaPrivateKeyValue{} + if err := json.Unmarshal(key.Value, &val); err != nil { + return err + } + block, _ := pem.Decode([]byte(val.Private)) + if block == nil { + return errors.New("invalid PEM value") + } + if block.Type != "RSA PRIVATE KEY" { + return fmt.Errorf("invalid block type: %s", block.Type) + } + k, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return err + } + if _, err := json.Marshal(rsaVerifier{ + PublicKey: &PKIXPublicKey{PublicKey: k.Public()}}); err != nil { + return fmt.Errorf("invalid public key: %s", err) + } + + s.PrivateKey = k + return nil } func GenerateRsaKey() (*rsaSigner, error) { diff --git a/pkg/keys/rsa_test.go b/pkg/keys/rsa_test.go index d0e3e862..73520003 100644 --- a/pkg/keys/rsa_test.go +++ b/pkg/keys/rsa_test.go @@ -1,6 +1,13 @@ package keys import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" + "io" + + "github.com/theupdateframework/go-tuf/data" . "gopkg.in/check.v1" ) @@ -20,7 +27,34 @@ func (RsaSuite) TestSignVerify(c *C) { c.Assert(pubKey.Verify(msg, sig), IsNil) } -func (RsaSuite) TestMarshalUnmarshal(c *C) { +func (RsaSuite) TestRSAVerifyMismatchMessage(c *C) { + signer, err := GenerateRsaKey() + c.Assert(err, IsNil) + msg := []byte("foo") + sig, err := signer.SignMessage(msg) + c.Assert(err, IsNil) + publicData := signer.PublicData() + pubKey, err := GetVerifier(publicData) + c.Assert(err, IsNil) + c.Assert(pubKey.Verify([]byte("notfoo"), sig), ErrorMatches, "crypto/rsa: verification error") +} + +func (RsaSuite) TestRSAVerifyMismatchPubKey(c *C) { + signer, err := GenerateRsaKey() + c.Assert(err, IsNil) + msg := []byte("foo") + sig, err := signer.SignMessage(msg) + c.Assert(err, IsNil) + + signerNew, err := GenerateRsaKey() + c.Assert(err, IsNil) + + pubKey, err := GetVerifier(signerNew.PublicData()) + c.Assert(err, IsNil) + c.Assert(pubKey.Verify([]byte("notfoo"), sig), ErrorMatches, "crypto/rsa: verification error") +} + +func (RsaSuite) TestMarshalUnmarshalPublicKey(c *C) { signer, err := GenerateRsaKey() c.Assert(err, IsNil) publicData := signer.PublicData() @@ -28,3 +62,64 @@ func (RsaSuite) TestMarshalUnmarshal(c *C) { c.Assert(err, IsNil) c.Assert(pubKey.MarshalPublicKey(), DeepEquals, publicData) } + +func (RsaSuite) TestMarshalUnmarshalPrivateKey(c *C) { + signer, err := GenerateRsaKey() + c.Assert(err, IsNil) + privateData, err := signer.MarshalPrivateKey() + c.Assert(err, IsNil) + c.Assert(privateData.Type, Equals, data.KeyTypeRSASSA_PSS_SHA256) + c.Assert(privateData.Scheme, Equals, data.KeySchemeRSASSA_PSS_SHA256) + c.Assert(privateData.Algorithms, DeepEquals, data.HashAlgorithms) + s, err := GetSigner(privateData) + c.Assert(err, IsNil) + c.Assert(s, DeepEquals, signer) +} + +func (ECDSASuite) TestUnmarshalRSA_Invalid(c *C) { + badKeyValue, err := json.Marshal(true) + c.Assert(err, IsNil) + + badKey := &data.PublicKey{ + Type: data.KeyTypeECDSA_SHA2_P256, + Scheme: data.KeySchemeECDSA_SHA2_P256, + Algorithms: data.HashAlgorithms, + Value: badKeyValue, + } + verifier := NewEcdsaVerifier() + c.Assert(verifier.UnmarshalPublicKey(badKey), ErrorMatches, "json: cannot unmarshal.*") +} + +func (ECDSASuite) TestUnmarshalRSAPublicKey(c *C) { + priv, err := GenerateRsaKey() + c.Assert(err, IsNil) + + signer := &rsaSigner{priv.PrivateKey} + goodKey := signer.PublicData() + + verifier := newRsaVerifier() + c.Assert(verifier.UnmarshalPublicKey(goodKey), IsNil) +} + +func (ECDSASuite) TestUnmarshalRSA_TooLongContent(c *C) { + randomSeed := make([]byte, MaxJSONKeySize) + _, err := io.ReadFull(rand.Reader, randomSeed) + c.Assert(err, IsNil) + + tooLongPayload, err := json.Marshal( + &ed25519Verifier{ + PublicKey: data.HexBytes(hex.EncodeToString(randomSeed)), + }, + ) + c.Assert(err, IsNil) + + badKey := &data.PublicKey{ + Type: data.KeyTypeECDSA_SHA2_P256, + Scheme: data.KeySchemeECDSA_SHA2_P256, + Algorithms: data.HashAlgorithms, + Value: tooLongPayload, + } + verifier := newRsaVerifier() + err = verifier.UnmarshalPublicKey(badKey) + c.Assert(errors.Is(err, io.ErrUnexpectedEOF), Equals, true) +} diff --git a/repo.go b/repo.go index b4ca62b9..603785f1 100644 --- a/repo.go +++ b/repo.go @@ -309,19 +309,33 @@ func (r *Repo) GenKey(role string) ([]string, error) { } func (r *Repo) GenKeyWithExpires(keyRole string, expires time.Time) (keyids []string, err error) { - // Not compatible with delegated targets roles, since delegated targets keys - // are associated with a delegation (edge), not a role (node). + return r.GenKeyWithSchemeAndExpires(keyRole, expires, data.KeySchemeEd25519) +} - signer, err := keys.GenerateEd25519Key() +func (r *Repo) GenKeyWithSchemeAndExpires(role string, expires time.Time, keyScheme data.KeyScheme) ([]string, error) { + var signer keys.Signer + var err error + switch keyScheme { + case data.KeySchemeEd25519: + signer, err = keys.GenerateEd25519Key() + case data.KeySchemeECDSA_SHA2_P256: + signer, err = keys.GenerateEcdsaKey() + case data.KeySchemeRSASSA_PSS_SHA256: + signer, err = keys.GenerateRsaKey() + default: + return nil, errors.New("unknown key type") + } if err != nil { - return []string{}, err + return nil, err } - if err = r.AddPrivateKeyWithExpires(keyRole, signer, expires); err != nil { - return []string{}, err + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). + + if err = r.AddPrivateKeyWithExpires(role, signer, expires); err != nil { + return nil, err } - keyids = signer.PublicData().IDs() - return + return signer.PublicData().IDs(), nil } func (r *Repo) AddPrivateKey(role string, signer keys.Signer) error { diff --git a/repo_test.go b/repo_test.go index 87536ee4..2f3aebb4 100644 --- a/repo_test.go +++ b/repo_test.go @@ -3,6 +3,7 @@ package tuf import ( "bytes" "crypto" + "crypto/elliptic" "crypto/rand" "encoding/hex" "encoding/json" @@ -2705,3 +2706,31 @@ func (rs *RepoSuite) TestTargetMetadataLength(c *C) { c.Assert(json.Unmarshal(lengthMsg, &length), IsNil) c.Assert(length, Equals, int64(0)) } + +func (rs *RepoSuite) TestDeprecatedHexEncodedKeysFails(c *C) { + files := map[string][]byte{"foo.txt": []byte("foo")} + local := MemoryStore(make(map[string]json.RawMessage), files) + r, err := 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) + // TODO: AddVerificationKey does no validation, so perform a sign operation. + c.Assert(r.Sign("root.json"), ErrorMatches, "tuf: error unmarshalling key: invalid PEM value") +} diff --git a/verify/db.go b/verify/db.go index b67ff260..04f5bf1c 100644 --- a/verify/db.go +++ b/verify/db.go @@ -55,7 +55,7 @@ func NewDBFromDelegations(d *data.Delegations) (*DB, error) { func (db *DB) AddKey(id string, k *data.PublicKey) error { verifier, err := keys.GetVerifier(k) if err != nil { - return ErrInvalidKey + return err // ErrInvalidKey } // TUF is considering in TAP-12 removing the diff --git a/verify/verify_test.go b/verify/verify_test.go index 4d9d3794..afbf79d8 100644 --- a/verify/verify_test.go +++ b/verify/verify_test.go @@ -31,12 +31,11 @@ type ecdsaSigner struct { } type ecdsaPublic struct { - PublicKey data.HexBytes `json:"public"` + PublicKey *keys.PKIXPublicKey `json:"public"` } func (s ecdsaSigner) PublicData() *data.PublicKey { - pub := s.Public().(*ecdsa.PublicKey) - keyValBytes, _ := json.Marshal(ecdsaPublic{PublicKey: elliptic.Marshal(pub.Curve, pub.X, pub.Y)}) + keyValBytes, _ := json.Marshal(ecdsaPublic{PublicKey: &keys.PKIXPublicKey{PublicKey: s.Public()}}) return &data.PublicKey{ Type: data.KeyTypeECDSA_SHA2_P256, Scheme: data.KeySchemeECDSA_SHA2_P256,