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: Implement full support for ECDSA and RSA keys #270

Closed
Closed
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
26 changes: 20 additions & 6 deletions cmd/tuf/gen_key.go
@@ -1,16 +1,18 @@
package main

import (
"errors"
"fmt"
"time"

"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>] [--type=<type>] <role>

Generate a new signing key for the given role.

Expand All @@ -23,23 +25,35 @@ form of TUF_{{ROLE}}_PASSPHRASE

Options:
--expires=<days> Set the root metadata file to expire <days> days from now.
--type=<type> Set the type of key to generate [default: ed25519].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please document the other key type possibilities? it's a bit hard to discern in the code because they are consts.

`)
}

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

var keyType data.KeyType
switch t := args.String["--type"]; t {
case string(data.KeyTypeEd25519),
string(data.KeyTypeECDSA_SHA2_P256),
string(data.KeyTypeRSASSA_PSS_SHA256):
keyType = data.KeyType(t)
default:
return errors.New("invalid key type")
}

var expires time.Time
if arg := args.String["--expires"]; arg != "" {
var expires time.Time
expires, err = parseExpires(arg)
exp, err := parseExpires(arg)
if err != nil {
return err
}
keyids, err = repo.GenKeyWithExpires(role, expires)
expires = exp
} else {
keyids, err = repo.GenKey(role)
expires = data.DefaultExpires(role)
}
keyids, err := repo.GenKeyWithTypeAndExpires(role, expires, data.KeyType(keyType))
if err != nil {
return err
}
Expand Down
46 changes: 46 additions & 0 deletions data/pkix.go
@@ -0,0 +1,46 @@
package data

import (
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
)

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
if err := json.Unmarshal(b, &pemValue); err != nil {
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
}
42 changes: 42 additions & 0 deletions data/pkix_test.go
@@ -0,0 +1,42 @@
package data

import (
"crypto/ecdsa"
"crypto/x509"
"encoding/json"
"encoding/pem"

. "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))
}
39 changes: 25 additions & 14 deletions data/types.go
Expand Up @@ -14,18 +14,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 @@ -40,19 +51,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 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
132 changes: 101 additions & 31 deletions pkg/keys/ecdsa.go
Expand Up @@ -3,69 +3,139 @@ package keys
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/asn1"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"math/big"
"fmt"

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

func init() {
VerifierMap.Store(data.KeyTypeECDSA_SHA2_P256, NewEcdsaVerifier)
VerifierMap.Store(data.KeyTypeECDSA_SHA2_P256, newEcdsaVerifier)
SignerMap.Store(data.KeyTypeECDSA_SHA2_P256, newEcdsaSigner)
}

func NewEcdsaVerifier() Verifier {
return &p256Verifier{}
func newEcdsaVerifier() Verifier {
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 *data.PKIXPublicKey `json:"public"`
ecdsaKey *ecdsa.PublicKey
key *data.PublicKey
}

func (p *p256Verifier) Public() string {
return p.PublicKey.String()
func (p *ecdsaVerifier) Public() string {
r, _ := x509.MarshalPKIXPublicKey(p.ecdsaKey)
return string(r)
}

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) Verify(msg, sigBytes []byte) error {
hash := sha256.Sum256(msg)

if !ecdsa.Verify(k, hash[:], sig.R, sig.S) {
return errors.New("tuf: ecdsa signature verification failed")
if !ecdsa.VerifyASN1(p.ecdsaKey, hash[:], sigBytes) {
return errors.New("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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: To keep backwards compatibility with existing roots (at least for 1-2 releases), could we do something like rename the old ecdsaVerifier to deprecatedEcdsaVerifier, and attempt to json.Unmarshal into that one?

I'm hoping to be able to still support old verifiers.

For example, if TUF attempts to UnmarshalPublicKey on an old ecdsa key, the resulting object would return the deprecatd Public() and Marshal() as the received data.

if err := json.Unmarshal(key.Value, p); err != nil {
return err
}
x, _ := elliptic.Unmarshal(elliptic.P256(), p.PublicKey)
if x == nil {
return errors.New("tuf: invalid ecdsa public key point")
ecdsaKey, ok := p.PublicKey.PublicKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("invalid public key")
}
p.ecdsaKey = ecdsaKey
p.key = key
return nil
}

type ecdsaSigner struct {
*ecdsa.PrivateKey
}

type ecdsaPrivateKeyValue struct {
Private string `json:"private"`
Public *data.PKIXPublicKey `json:"public"`
}

func (s *ecdsaSigner) PublicData() *data.PublicKey {
keyValBytes, _ := json.Marshal(ecdsaVerifier{PublicKey: &data.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: &data.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("invalid ecdsa curve")
}
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
}