From 14a8de0c47e110daea86652a1ed1c78d5b401616 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 23 Jun 2020 11:28:11 +0200 Subject: [PATCH 01/86] use more specific names LoadPublicKey() and VerifySignature() were too generic. Let's rename them to be more precise in what they are achieving. --- in_toto/examples_test.go | 2 +- in_toto/keylib.go | 8 ++++---- in_toto/keylib_test.go | 22 +++++++++++----------- in_toto/model.go | 4 ++-- in_toto/model_test.go | 8 ++++---- in_toto/verifylib_test.go | 6 +++--- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/in_toto/examples_test.go b/in_toto/examples_test.go index 6525bff0..282c7c2f 100644 --- a/in_toto/examples_test.go +++ b/in_toto/examples_test.go @@ -23,7 +23,7 @@ func ExampleInTotoVerify() { // InTotoVerify. The layout represents the root of trust so it is a good // idea to sign it using multiple keys. var pubKey Key - err := pubKey.LoadPublicKey(LayoutKeyPath) + err := pubKey.LoadRSAPublicKey(LayoutKeyPath) if err != nil { fmt.Printf("Unable to load public key: %s", err) } diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 91cc9178..378c24f3 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -46,11 +46,11 @@ func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) { } /* -LoadPublicKey parses an RSA public key from a PEM formatted file at the passed +LoadRSAPublicKey parses an RSA public key from a PEM formatted file at the passed path into the Key object on which it was called. It returns an error if the file at path does not exist or is not a PEM formatted RSA public key. */ -func (k *Key) LoadPublicKey(path string) (err error) { +func (k *Key) LoadRSAPublicKey(path string) (err error) { keyFile, err := os.Open(path) if err != nil { return err @@ -128,11 +128,11 @@ func (k *Key) LoadPublicKey(path string) (err error) { } /* -VerifySignature uses the passed Key to verify the passed Signature over the +VerifyRSASignature uses the passed Key to verify the passed Signature over the passed data. It returns an error if the key is not a valid RSA public key or if the signature is not valid for the data. */ -func VerifySignature(key Key, sig Signature, data []byte) error { +func VerifyRSASignature(key Key, sig Signature, data []byte) error { // Create rsa.PublicKey object from DER encoded public key string as // found in the public part of the keyval part of a securesystemslib key dict keyReader := strings.NewReader(key.KeyVal.Public) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index b250f1aa..f6ec0b35 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -59,27 +59,27 @@ yMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE= func TestLoadPublicKey(t *testing.T) { // Test loading valid public rsa key from pem-formatted file var key Key - err := key.LoadPublicKey("alice.pub") + err := key.LoadRSAPublicKey("alice.pub") if err != nil { - t.Errorf("LoadPublicKey returned %s, expected no error", err) + t.Errorf("LoadRSAPublicKey returned %s, expected no error", err) } expectedKeyID := "556caebdc0877eed53d419b60eddb1e57fa773e4e31d70698b588f3e9cc48b35" if key.KeyId != expectedKeyID { - t.Errorf("LoadPublicKey parsed KeyId '%s', expected '%s'", + t.Errorf("LoadRSAPublicKey parsed KeyId '%s', expected '%s'", key.KeyId, expectedKeyID) } // Test loading error: // - Not a pem formatted rsa public key expectedError := "Could not find a public key PEM block" - err = key.LoadPublicKey("demo.layout.template") + err = key.LoadRSAPublicKey("demo.layout.template") if err == nil || !strings.Contains(err.Error(), expectedError) { - t.Errorf("LoadPublicKey returned (%s), expected '%s' error", err, + t.Errorf("LoadRSAPublicKey returned (%s), expected '%s' error", err, expectedError) } // Test not existing file - err = key.LoadPublicKey("inToToRocks") + err = key.LoadRSAPublicKey("inToToRocks") if !errors.Is(err, os.ErrNotExist) { t.Errorf("Invalid file load returned (%s), expected '%s' error", err, os.ErrNotExist) } @@ -121,9 +121,9 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` // Test verifying valid signature - err := VerifySignature(validKey, validSig, []byte(validData)) + err := VerifyRSASignature(validKey, validSig, []byte(validData)) if err != nil { - t.Errorf("VerifySignature returned '%s', expected nil", err) + t.Errorf("VerifyRSASignature returned '%s', expected nil", err) } // Test signature verification errors: @@ -133,7 +133,7 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= // - Right key and data, but wrong signature // - Right key and data, but invalid signature var wrongKey Key - if err := wrongKey.LoadPublicKey("alice.pub"); err != nil { + if err := wrongKey.LoadRSAPublicKey("alice.pub"); err != nil { fmt.Printf("Unable to load key alice.pub: %s", err) } wrongSig := Signature{ @@ -146,9 +146,9 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= data := []string{"bad data", validData, validData, validData, validData} for i := 0; i < len(sigs); i++ { - err := VerifySignature(keys[i], sigs[i], []byte(data[i])) + err := VerifyRSASignature(keys[i], sigs[i], []byte(data[i])) if err == nil { - t.Errorf("VerifySignature returned '%s', expected error", err) + t.Errorf("VerifyRSASignature returned '%s', expected error", err) } } } diff --git a/in_toto/model.go b/in_toto/model.go index 8ee503e7..1ea0521b 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -569,7 +569,7 @@ func (mb *Metablock) GetSignableRepresentation() ([]byte, error) { } /* -VerifySignature verifies the first signature, corresponding to the passed Key, +VerifyRSASignature verifies the first signature, corresponding to the passed Key, that it finds in the Signatures field of the Metablock on which it was called. It returns an error if Signatures does not contain a Signature corresponding to the passed Key, the object in Signed cannot be canonicalized, or the Signature @@ -593,7 +593,7 @@ func (mb *Metablock) VerifySignature(key Key) error { return err } - if err := VerifySignature(key, sig, dataCanonical); err != nil { + if err := VerifyRSASignature(key, sig, dataCanonical); err != nil { return err } return nil diff --git a/in_toto/model_test.go b/in_toto/model_test.go index f940a1b8..ea70089a 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -249,7 +249,7 @@ func TestMetablockVerifySignature(t *testing.T) { // - wrong signature for key // - invalid metadata (can't canonicalize) var key Key - if err := key.LoadPublicKey("alice.pub"); err != nil { + if err := key.LoadRSAPublicKey("alice.pub"); err != nil { t.Errorf("Cannot load public key file: %s", err) } // Test missing key, bad signature and bad metadata @@ -271,7 +271,7 @@ func TestMetablockVerifySignature(t *testing.T) { for i := 0; i < len(mbs); i++ { err := mbs[i].VerifySignature(key) if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { - t.Errorf("Metablock.VerifySignature returned '%s', expected '%s'", + t.Errorf("Metablock.VerifyRSASignature returned '%s', expected '%s'", err, expectedErrors[i]) } } @@ -283,7 +283,7 @@ func TestMetablockVerifySignature(t *testing.T) { } err := mb.VerifySignature(key) if err != nil { - t.Errorf("Metablock.VerifySignature returned '%s', expected nil", err) + t.Errorf("Metablock.VerifyRSASignature returned '%s', expected nil", err) } } @@ -1168,7 +1168,7 @@ func TestMetablockSignWithEd25519(t *testing.T) { t.Errorf("Cannot parse template file: %s", err) } - if err := key.LoadPublicKey("alice.pub"); err != nil { + if err := key.LoadRSAPublicKey("alice.pub"); err != nil { t.Errorf("Cannot load public key file: %s", err) } err := mb.Sign(key) diff --git a/in_toto/verifylib_test.go b/in_toto/verifylib_test.go index a144797b..29b4e94c 100644 --- a/in_toto/verifylib_test.go +++ b/in_toto/verifylib_test.go @@ -24,7 +24,7 @@ func TestInTotoVerifyPass(t *testing.T) { } var pubKey Key - if err := pubKey.LoadPublicKey(pubKeyPath); err != nil { + if err := pubKey.LoadRSAPublicKey(pubKeyPath); err != nil { t.Error(err) } @@ -99,7 +99,7 @@ func TestGetSummaryLink(t *testing.T) { func TestVerifySublayouts(t *testing.T) { sublayoutName := "sub_layout" var aliceKey Key - if err := aliceKey.LoadPublicKey("alice.pub"); err != nil { + if err := aliceKey.LoadRSAPublicKey("alice.pub"); err != nil { t.Errorf("Unable to load Alice's public key") } sublayoutDirectory := fmt.Sprintf(SublayoutLinkDirFormat, sublayoutName, @@ -686,7 +686,7 @@ func TestVerifyLayoutSignatures(t *testing.T) { t.Errorf("Unable to load template file: %s", err) } var layoutKey Key - if err := layoutKey.LoadPublicKey("alice.pub"); err != nil { + if err := layoutKey.LoadRSAPublicKey("alice.pub"); err != nil { t.Errorf("Unable to load public key file: %s", err) } From 4969540585698215c5cfa70124edf24cffe32a80 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 23 Jun 2020 12:45:49 +0200 Subject: [PATCH 02/86] implement VerifyEd25519Signature func --- in_toto/keylib.go | 21 +++++++++++++++++++++ in_toto/keylib_test.go | 8 +++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 378c24f3..bcf034c7 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "encoding/json" "encoding/pem" + "errors" "fmt" "golang.org/x/crypto/ed25519" "io/ioutil" @@ -219,3 +220,23 @@ func GenerateEd25519Signature(signable []byte, key Key) (Signature, error) { return signature, nil } + +/* +VerifyEd25519Signature uses the passed Key to verify the passed Signature over the +passed data. It returns an error if the key is not a valid ed25519 public key or +if the signature is not valid for the data. +*/ +func VerifyEd25519Signature(key Key, sig Signature, data []byte) error { + pubHex, err := hex.DecodeString(key.KeyVal.Public) + if err != nil { + return err + } + sigHex, err := hex.DecodeString(sig.Sig) + if err != nil { + return err + } + if ok := ed25519.Verify(pubHex, data, sigHex); !ok { + return errors.New("invalid ed25519 signature") + } + return nil +} diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index f6ec0b35..e4724030 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -204,10 +204,16 @@ func TestGenerateEd25519Signature(t *testing.T) { signature, err := GenerateEd25519Signature([]uint8("ohmywhatatest"), key) if err != nil { - t.Errorf("GenerateEd25519Signature shouldn't have returned error (%s)", + t.Errorf("GenerateEd25519Signature shouldn't have returned an error (%s)", err) } + // validate signature + err = VerifyEd25519Signature(key, signature, []uint8("ohmywhatatest")) + if err != nil { + t.Errorf("VerifyEd25519Signature shouldn't have returned an error (%s)", err) + } + if signature.KeyId != key.KeyId { t.Errorf("GenerateEd25519Signature should've returned matching keyids!") } From 45a8e933ac965682582cde998c86006548f1bb9b Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 23 Jun 2020 13:10:23 +0200 Subject: [PATCH 03/86] add LoadEd25519PublicKey func In this commit we add a LoadEd25519PublicKey func for loading ed25519 keys in PrivateJSON format from a ed25519 public key file --- in_toto/keylib.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index bcf034c7..9524362e 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -240,3 +240,53 @@ func VerifyEd25519Signature(key Key, sig Signature, data []byte) error { } return nil } + +/* LoadEd25519PublicKey loads a ed25519 pub key file +and parses it via ParseEd25519FromPrivateJSON. +The pub key file has to be in the in-toto PrivateJSON format +For example: + + { + "keytype": "ed25519", + "scheme": "ed25519", + "keyid_hash_algorithms": + [ + "sha256", + "sha512" + ], + "keyval": + { + "public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70" + } + } + +*/ +func (k *Key) LoadEd25519PublicKey(path string) (err error) { + keyFile, err := os.Open(path) + if err != nil { + return err + } + defer func() { + if closeErr := keyFile.Close(); closeErr != nil { + err = closeErr + } + }() + + keyBytes, err := ioutil.ReadAll(keyFile) + if err != nil { + return err + } + // contrary to LoadRSAPublicKey we use the returned key object + keyObj, err := ParseEd25519FromPrivateJSON(string(keyBytes)) + if err != nil { + return err + } + // I am not sure if there is a faster way to fill the Key struct + // without touching the ParseEd25519FromPrivateJSON function + k.KeyId = keyObj.KeyId + k.KeyType = keyObj.KeyType + k.KeyIdHashAlgorithms = keyObj.KeyIdHashAlgorithms + k.KeyVal = keyObj.KeyVal + k.Scheme = keyObj.Scheme + return nil +} From 76f9f94fbf70eca124490f148cee13b0bbd26e52 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 23 Jun 2020 13:26:47 +0200 Subject: [PATCH 04/86] add ParseEd25519FromPublicJSON func The format of a public key is different to the private key JSON format for ed25519 in-toto pubkeys. Therefore we need another function for parsing ed25519 pub keys. --- in_toto/keylib.go | 39 +++++++++++++++++++++++++++++++++++---- in_toto/keylib_test.go | 7 +++++++ test/data/bob.pub | 1 + 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 test/data/bob.pub diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 9524362e..ea9555c5 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -176,7 +176,6 @@ These ed25519 keys have the format as generated using in-toto-keygen: } */ func ParseEd25519FromPrivateJSON(JSONString string) (Key, error) { - var keyObj Key err := json.Unmarshal([]uint8(JSONString), &keyObj) if err != nil { @@ -199,6 +198,38 @@ func ParseEd25519FromPrivateJSON(JSONString string) (Key, error) { return keyObj, nil } +/* +ParseEd25519FromPublicJSON parses an ed25519 public key from the json string. +These ed25519 keys have the format as generated using in-toto-keygen: + + { + "keytype": "ed25519", + "scheme": "ed25519", + "keyid_hash_algorithms": [...], + "keyval": {"public": "..."} + } + +*/ +func ParseEd25519FromPublicJSON(JSONString string) (Key, error) { + var keyObj Key + err := json.Unmarshal([]uint8(JSONString), &keyObj) + if err != nil { + return keyObj, fmt.Errorf("this is not a valid JSON key object") + } + + if keyObj.KeyType != "ed25519" || keyObj.Scheme != "ed25519" { + return keyObj, fmt.Errorf("this doesn't appear to be an ed25519 key") + } + + if keyObj.KeyVal.Private != "" { + return keyObj, fmt.Errorf("this key is not a public key") + } + + // TODO: check for digits + + return keyObj, nil +} + /* GenerateEd25519Signature creates an ed25519 signature using the key and the signable buffer provided. It returns an error if the underlying signing library @@ -242,8 +273,8 @@ func VerifyEd25519Signature(key Key, sig Signature, data []byte) error { } /* LoadEd25519PublicKey loads a ed25519 pub key file -and parses it via ParseEd25519FromPrivateJSON. -The pub key file has to be in the in-toto PrivateJSON format +and parses it via ParseEd25519FromPublicJSON. +The pub key file has to be in the in-toto PublicJSON format For example: { @@ -277,7 +308,7 @@ func (k *Key) LoadEd25519PublicKey(path string) (err error) { return err } // contrary to LoadRSAPublicKey we use the returned key object - keyObj, err := ParseEd25519FromPrivateJSON(string(keyBytes)) + keyObj, err := ParseEd25519FromPublicJSON(string(keyBytes)) if err != nil { return err } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index e4724030..8518ec9d 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -224,3 +224,10 @@ func TestGenerateEd25519Signature(t *testing.T) { signature.Sig) } } + +func TestLoad25519PublicKey(t *testing.T) { + var rightKey Key + if err := rightKey.LoadEd25519PublicKey("bob.pub"); err != nil { + t.Errorf("Failed to load ed25519 public key from file: (%s)", err) + } +} diff --git a/test/data/bob.pub b/test/data/bob.pub new file mode 100644 index 00000000..3b4d580b --- /dev/null +++ b/test/data/bob.pub @@ -0,0 +1 @@ +{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}} \ No newline at end of file From c6cb9c07d93fe1c06e90d0ad0ab9cb7d5410a213 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 23 Jun 2020 14:31:51 +0200 Subject: [PATCH 05/86] add additional error checks --- in_toto/keylib.go | 5 ++- in_toto/keylib_test.go | 85 +++++++++++++++++++++++++++++++++++++-- test/data/bob-invalid.pub | 1 + 3 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 test/data/bob-invalid.pub diff --git a/in_toto/keylib.go b/in_toto/keylib.go index ea9555c5..0349d49e 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -225,7 +225,10 @@ func ParseEd25519FromPublicJSON(JSONString string) (Key, error) { return keyObj, fmt.Errorf("this key is not a public key") } - // TODO: check for digits + // 64 hexadecimal digits => 32 bytes for the public portion of the key + if len(keyObj.KeyVal.Public) != 64 { + return keyObj, fmt.Errorf("the public field on this key is malformed") + } return keyObj, nil } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 8518ec9d..34bf73fe 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -1,6 +1,7 @@ package in_toto import ( + "encoding/hex" "errors" "fmt" "os" @@ -208,12 +209,47 @@ func TestGenerateEd25519Signature(t *testing.T) { err) } - // validate signature + // validate correct signature err = VerifyEd25519Signature(key, signature, []uint8("ohmywhatatest")) if err != nil { t.Errorf("VerifyEd25519Signature shouldn't have returned an error (%s)", err) } + //validate incorrect signature + var incorrectSig Signature + incorrectSig.Sig = "e8912b58f47ae04a65d7437e3c82eb361f82d952" + err = VerifyEd25519Signature(key, incorrectSig, []uint8("ohmywhatatest")) + if err == nil { + t.Errorf("Given signature is valid, but should be invalid") + } + + // validate InvalidByte signature + var malformedSig Signature + malformedSig.Sig = "InTotoRocks" + err = VerifyEd25519Signature(key, malformedSig, []uint8("ohmywhatatest")) + // use type conversion for checking for hex.InvalidByteError + var invalidByteError hex.InvalidByteError + if !errors.As(err, &invalidByteError) { + t.Errorf("We received %s, but we should get: invalid byte error", err) + } + + // validate invalidLength signature + // the following signature is too short + var invLengthSig Signature + invLengthSig.Sig = "e8912b58f47ae04a65d74" + err = VerifyEd25519Signature(key, invLengthSig, []uint8("ohmywhatatest")) + if !errors.Is(err, hex.ErrLength) { + t.Errorf("We received %s, but we should get: %s", err, hex.ErrLength) + } + + // validate invalidKey + wrongKey := key + wrongKey.KeyVal.Public = "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7" + err = VerifyEd25519Signature(wrongKey, signature, []uint8("ohmywhatatest")) + if err == nil { + t.Errorf("The invalid testKey passed the signature test, this should not happen") + } + if signature.KeyId != key.KeyId { t.Errorf("GenerateEd25519Signature should've returned matching keyids!") } @@ -226,8 +262,51 @@ func TestGenerateEd25519Signature(t *testing.T) { } func TestLoad25519PublicKey(t *testing.T) { - var rightKey Key - if err := rightKey.LoadEd25519PublicKey("bob.pub"); err != nil { + var key Key + if err := key.LoadEd25519PublicKey("bob.pub"); err != nil { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) } + + expectedPubKey := "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70" + if expectedPubKey != key.KeyVal.Public { + t.Errorf("Loaded pubkey is not the expected key") + } + + // try to load nonexistent file + if err := key.LoadEd25519PublicKey("this-does-not-exist"); err == nil { + t.Errorf("LoadEd25519PublicKey loaded a file that does not exist") + } + + // load invalid file + if err := key.LoadEd25519PublicKey("bob-invalid.pub"); err == nil { + t.Errorf("LoadEd25519PublicKey has successfully loaded an invalid key file") + } +} + +func TestParseEd25519FromPublicJSON(t *testing.T) { + tables := []struct { + invalidKey string + expectedError string + }{ + {"not a json", "this is not a valid JSON key object"}, + {`{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70", "private": "861fd1b466cfc6f73"}}`, "this key is not a public key"}, + {`{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d74"}}`, "the public field on this key is malformed"}, + {`{"keytype": "25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}}`, "this doesn't appear to be an ed25519 key"}, + {`{"keytype": "ed25519", "scheme": "cd25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}}`, "his doesn't appear to be an ed25519 key"}, + } + + for _, table := range tables { + _, err := ParseEd25519FromPublicJSON(table.invalidKey) + if err == nil || !strings.Contains(err.Error(), table.expectedError) { + t.Errorf("ParseEd25519FromPublicJSON returned (%s), expected '%s'", err, table.expectedError) + } + } + + // Generated through in-toto run 0.4.1 and thus it should be a happy key + validKey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}}` + _, err := ParseEd25519FromPublicJSON(validKey) + if err != nil { + t.Errorf("ParseEd25519FromPublicJSON returned (%s), expected no error", + err) + } } diff --git a/test/data/bob-invalid.pub b/test/data/bob-invalid.pub new file mode 100644 index 00000000..824f4ffa --- /dev/null +++ b/test/data/bob-invalid.pub @@ -0,0 +1 @@ +{"keytype": "25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}} \ No newline at end of file From ae5e82dcd0791e82f6638b2d119574ed858a66dc Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 26 Jun 2020 09:06:59 +0200 Subject: [PATCH 06/86] add LoadPrivateKey func for ed25519 and more tests This commit adds more test material such like symmetric encrypted private keys. --- in_toto/keylib.go | 50 ++++++++++++++++++++++++++++++++++++++++++ in_toto/keylib_test.go | 22 +++++++++++++++++++ test/data/bob | 1 + test/data/carol | 1 + test/data/carol.pub | 1 + 5 files changed, 75 insertions(+) create mode 100644 test/data/bob create mode 100644 test/data/carol create mode 100644 test/data/carol.pub diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 0349d49e..6962cd25 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -324,3 +324,53 @@ func (k *Key) LoadEd25519PublicKey(path string) (err error) { k.Scheme = keyObj.Scheme return nil } + +/* LoadEd25519PrivateKey loads a ed25519 private key file +and parses it via ParseEd25519FromPrivateJSON. +The pub key file has to be in the in-toto PrivateJSON format +For example: + + { + "keytype": "ed25519", + "scheme": "ed25519", + "keyid_hash_algorithms": + [ + "sha256", + "sha512" + ], + "keyval": + { + "public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70" + } + } + +*/ +func (k *Key) LoadEd25519PrivateKey(path string) (err error) { + keyFile, err := os.Open(path) + if err != nil { + return err + } + defer func() { + if closeErr := keyFile.Close(); closeErr != nil { + err = closeErr + } + }() + + keyBytes, err := ioutil.ReadAll(keyFile) + if err != nil { + return err + } + // contrary to LoadRSAPublicKey we use the returned key object + keyObj, err := ParseEd25519FromPrivateJSON(string(keyBytes)) + if err != nil { + return err + } + // I am not sure if there is a faster way to fill the Key struct + // without touching the ParseEd25519FromPrivateJSON function + k.KeyId = keyObj.KeyId + k.KeyType = keyObj.KeyType + k.KeyIdHashAlgorithms = keyObj.KeyIdHashAlgorithms + k.KeyVal = keyObj.KeyVal + k.Scheme = keyObj.Scheme + return nil +} diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 34bf73fe..9e0f500a 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -283,6 +283,28 @@ func TestLoad25519PublicKey(t *testing.T) { } } +func TestLoad25519PrivateKey(t *testing.T) { + var key Key + if err := key.LoadEd25519PrivateKey("carol"); err != nil { + t.Errorf("Failed to load ed25519 public key from file: (%s)", err) + } + + expectedPrivateKey := "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162" + if expectedPrivateKey != key.KeyVal.Private { + t.Errorf("Loaded pubkey is not the expected key") + } + + // try to load nonexistent file + if err := key.LoadEd25519PrivateKey("this-does-not-exist"); err == nil { + t.Errorf("LoadEd25519PublicKey loaded a file that does not exist") + } + + // load invalid file + if err := key.LoadEd25519PrivateKey("bob-invalid.pub"); err == nil { + t.Errorf("LoadEd25519PublicKey has successfully loaded an invalid key file") + } +} + func TestParseEd25519FromPublicJSON(t *testing.T) { tables := []struct { invalidKey string diff --git a/test/data/bob b/test/data/bob new file mode 100644 index 00000000..c6672ab8 --- /dev/null +++ b/test/data/bob @@ -0,0 +1 @@ +abea63039d8e147ed1fe99edca11c2cb@@@@100000@@@@8e5b3a3a121a6f73824fd05e33cc9c3eca4d7e2b0a66402133434b48e8f125d4@@@@c631a0e172cf014f8fbcf8a16471f279@@@@6da6c4a134c42e89f1aec309263d057e46d7eae21e214a34603541a75d0a119f62cef4bb541a590e60a71b515eee172523e5160e02597d0a6f62e9f316d444b69445d20f46fe431d3feb095d96c329821a0272ec005fedc3fa74e9def46f051aa6a08a8645a33b4fee75663daff8da0a8daa81febf09d6290fbf9de393cd32d165f372a1af2699c3e14e9a1333e62901cc874fc35ec6e01940fd04ca7312b3f0ea65896933d9221c4a291d59412a299d834fbbf8e30c6e0ad5b415c14ecae9dbac46d31438abfd1c2e3c546daf548a2621dccc6e4e451a67f178a9153c6049b0591460811102458961e854f8f27491c459f6d48bda7e124a254033d898f7bcc7da6c780b604ed6534c70be3828382e81ce2b9b469ebccc98e9e0646f4ff142479712338f6910c98f41c93c7561fb0c259b6852fab94217d30ef1ab83c3e373f6f580fcbc161359f46931e3a068648ff9 \ No newline at end of file diff --git a/test/data/carol b/test/data/carol new file mode 100644 index 00000000..0d1d10b7 --- /dev/null +++ b/test/data/carol @@ -0,0 +1 @@ +{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", "private": "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162"}} \ No newline at end of file diff --git a/test/data/carol.pub b/test/data/carol.pub new file mode 100644 index 00000000..1af4d653 --- /dev/null +++ b/test/data/carol.pub @@ -0,0 +1 @@ +{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}} \ No newline at end of file From d868be75bc4c15bbdae08aed30050c23989418c8 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 26 Jun 2020 09:30:03 +0200 Subject: [PATCH 07/86] mention encrypted private keys in comments This commit also changes the example keys in the documentation. We use the keypair of "carol" (see test/data/carol{.pub}) now. --- in_toto/keylib.go | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 6962cd25..c5084b13 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -275,7 +275,7 @@ func VerifyEd25519Signature(key Key, sig Signature, data []byte) error { return nil } -/* LoadEd25519PublicKey loads a ed25519 pub key file +/* LoadEd25519PublicKey loads an ed25519 pub key file and parses it via ParseEd25519FromPublicJSON. The pub key file has to be in the in-toto PublicJSON format For example: @@ -283,15 +283,11 @@ For example: { "keytype": "ed25519", "scheme": "ed25519", - "keyid_hash_algorithms": - [ - "sha256", - "sha512" - ], + "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": - { - "public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70" - } + { + "public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037" + } } */ @@ -325,27 +321,28 @@ func (k *Key) LoadEd25519PublicKey(path string) (err error) { return nil } -/* LoadEd25519PrivateKey loads a ed25519 private key file +/* LoadEd25519PrivateKey loads an ed25519 private key file and parses it via ParseEd25519FromPrivateJSON. -The pub key file has to be in the in-toto PrivateJSON format +ParseEd25519FromPrivateKey does not support encrypted private keys yet. +The private key file has to be in the in-toto PrivateJSON format For example: { "keytype": "ed25519", "scheme": "ed25519", - "keyid_hash_algorithms": - [ - "sha256", - "sha512" - ], + "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", + "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": - { - "public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70" - } + { + "public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", + "private": "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162" + } } */ func (k *Key) LoadEd25519PrivateKey(path string) (err error) { + // TODO: Support for encrypted private Keys + // See also: https://github.com/secure-systems-lab/securesystemslib/blob/01a0c95af5f458235f96367922357958bfcf7b01/securesystemslib/keys.py#L1309 keyFile, err := os.Open(path) if err != nil { return err From f54d2e5bc42ea617d21b012955b5d63c43534156 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 26 Jun 2020 10:37:15 +0200 Subject: [PATCH 08/86] implement Parse/Load RSA private key We use PKCS1 for parsing/loading RSA private keys. This means we do not support ECDSA yet. --- in_toto/keylib.go | 104 +++++++++++++++++++++++++++++++++++++++++ in_toto/keylib_test.go | 31 +++++++++++- test/data/dan | 39 ++++++++++++++++ test/data/dan.pub | 11 +++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 test/data/dan create mode 100644 test/data/dan.pub diff --git a/in_toto/keylib.go b/in_toto/keylib.go index c5084b13..cc02e3e4 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -46,6 +46,28 @@ func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) { return rsaPub, nil } +/* +ParseRSAPrivateKeyFromPEM parses the passed pemBytes as e.g. read from a PEM +formatted file, and instantiates and returns the corresponding RSA Private key. +If the no RSA Private key can be parsed, the first return value is nil and the +second return value is the error. +*/ +func ParseRSAPrivateKeyFromPEM(pemBytes []byte) (*rsa.PrivateKey, error) { + // TODO: There could be more key data in _, which we silently ignore here. + // Should we handle it / fail / say something about it? + data, _ := pem.Decode(pemBytes) + if data == nil { + return nil, fmt.Errorf("Could not find a Private key PEM block") + } + + priv, err := x509.ParsePKCS1PrivateKey(data.Bytes) + if err != nil { + return nil, err + } + + return priv, nil +} + /* LoadRSAPublicKey parses an RSA public key from a PEM formatted file at the passed path into the Key object on which it was called. It returns an error if the @@ -128,6 +150,88 @@ func (k *Key) LoadRSAPublicKey(path string) (err error) { return nil } +/* +LoadRSAPrivateKey parses an RSA Private key from a PEM formatted file at the passed +path into the Key object on which it was called. It returns an error if the +file at path does not exist or is not a PEM formatted RSA Private key. +*/ +func (k *Key) LoadRSAPrivateKey(path string) (err error) { + keyFile, err := os.Open(path) + if err != nil { + return err + } + defer func() { + if closeErr := keyFile.Close(); closeErr != nil { + err = closeErr + } + }() + + // Read key bytes and decode PEM + keyBytes, err := ioutil.ReadAll(keyFile) + if err != nil { + return err + } + + // We only parse to see if this is indeed a pem formatted rsa Private key, + // but don't use the returned *rsa.PrivateKey. Instead, we continue with + // the original keyBytes from above. + _, err = ParseRSAPrivateKeyFromPEM(keyBytes) + if err != nil { + return err + } + + // Strip leading and trailing data from PEM file like securesystemslib does + // TODO: Should we instead use the parsed Private key to reconstruct the PEM? + keyHeader := "-----BEGIN RSA PRIVATE KEY-----" + keyFooter := "-----END RSA PRIVATE KEY-----" + keyStart := strings.Index(string(keyBytes), keyHeader) + keyEnd := strings.Index(string(keyBytes), keyFooter) + len(keyFooter) + // Successful call to ParseRSAPrivateKeyFromPEM already guarantees that + // header and footer are present, i.e. `!(keyStart == -1 || keyEnd == -1)` + keyBytesStripped := keyBytes[keyStart:keyEnd] + + // Declare values for key + // TODO: Do not hardcode here, but define defaults elsewhere and add support + // for parametrization + keyType := "rsa" + scheme := "rsassa-pss-sha256" + keyIdHashAlgorithms := []string{"sha256", "sha512"} + + // Create partial key map used to create the keyid + // Unfortunately, we can't use the Key object because this also carries + // yet unwanted fields, such as KeyId and KeyVal.Private and therefore + // produces a different hash + var keyToBeHashed = map[string]interface{}{ + "keytype": keyType, + "scheme": scheme, + "keyid_hash_algorithms": keyIdHashAlgorithms, + "keyval": map[string]string{ + "Private": string(keyBytesStripped), + }, + } + + // Canonicalize key and get hex representation of hash + keyCanonical, err := EncodeCanonical(keyToBeHashed) + if err != nil { + return err + } + keyHashed := sha256.Sum256(keyCanonical) + + // Unmarshalling the canonicalized key into the Key object would seem natural + // Unfortunately, our mandated canonicalization function produces a byte + // slice that cannot be unmarshalled by Golang's json decoder, hence we have + // to manually assign the values + k.KeyType = keyType + k.KeyVal = KeyVal{ + Private: string(keyBytesStripped), + } + k.Scheme = scheme + k.KeyIdHashAlgorithms = keyIdHashAlgorithms + k.KeyId = fmt.Sprintf("%x", keyHashed) + + return nil +} + /* VerifyRSASignature uses the passed Key to verify the passed Signature over the passed data. It returns an error if the key is not a valid RSA public key or diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 9e0f500a..5efbc608 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -57,7 +57,7 @@ yMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE= } } -func TestLoadPublicKey(t *testing.T) { +func TestLoadRSAPublicKey(t *testing.T) { // Test loading valid public rsa key from pem-formatted file var key Key err := key.LoadRSAPublicKey("alice.pub") @@ -86,6 +86,35 @@ func TestLoadPublicKey(t *testing.T) { } } +func TestLoadRSAPrivateKey(t *testing.T) { + // Test loading valid Private rsa key from pem-formatted file + var key Key + err := key.LoadRSAPrivateKey("dan") + if err != nil { + t.Errorf("LoadRSAPrivateKey returned %s, expected no error", err) + } + expectedKeyID := "f29cb6877d14ebcf28b136a96a4d64935522afaddcc84e6b70ff6b9eaefb8fcf" + if key.KeyId != expectedKeyID { + t.Errorf("LoadRSAPrivateKey parsed KeyId '%s', expected '%s'", + key.KeyId, expectedKeyID) + } + + // Test loading error: + // - Not a pem formatted rsa Private key + expectedError := "Could not find a Private key PEM block" + err = key.LoadRSAPrivateKey("demo.layout.template") + if err == nil || !strings.Contains(err.Error(), expectedError) { + t.Errorf("LoadRSAPrivateKey returned (%s), expected '%s' error", err, + expectedError) + } + + // Test not existing file + err = key.LoadRSAPrivateKey("inToToRocks") + if !errors.Is(err, os.ErrNotExist) { + t.Errorf("Invalid file load returned (%s), expected '%s' error", err, os.ErrNotExist) + } +} + func TestVerifySignature(t *testing.T) { validSig := Signature{ KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498", diff --git a/test/data/dan b/test/data/dan new file mode 100644 index 00000000..9eec51fd --- /dev/null +++ b/test/data/dan @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/test/data/dan.pub b/test/data/dan.pub new file mode 100644 index 00000000..54aa0b6c --- /dev/null +++ b/test/data/dan.pub @@ -0,0 +1,11 @@ +-----BEGIN PUBLIC KEY----- +MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l +8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP +r3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz +eUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT +vpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2 +LFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5 +ujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/ +Vk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf +p8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE= +-----END PUBLIC KEY----- \ No newline at end of file From 9b5413f4e13a2f98a0a9c686072ad8ce749b5ee1 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 26 Jun 2020 10:47:42 +0200 Subject: [PATCH 09/86] add test function for ParseRSAPrivateKey --- in_toto/keylib.go | 2 +- in_toto/keylib_test.go | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index cc02e3e4..0f81aca8 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -57,7 +57,7 @@ func ParseRSAPrivateKeyFromPEM(pemBytes []byte) (*rsa.PrivateKey, error) { // Should we handle it / fail / say something about it? data, _ := pem.Decode(pemBytes) if data == nil { - return nil, fmt.Errorf("Could not find a Private key PEM block") + return nil, fmt.Errorf("Could not find a private key PEM block") } priv, err := x509.ParsePKCS1PrivateKey(data.Bytes) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 5efbc608..ef95e35a 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -57,6 +57,32 @@ yMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE= } } +func TestParseRSAPrivateKeyFromPEM(t *testing.T) { + // Test parsing errors: + // - Missing pem headers, + // - Missing pem body + // We only support RSA private keys, therefore we don't need to check for other keys. + // Other keys should fail at ParsePKCS1 stage already. + invalidRSA := []string{ + "not a PEM block", + `-----BEGIN PRIVATE KEY----- + +-----END PRIVATE KEY-----`, + } + expectedErrors := []string{ + "Could not find a private key PEM block", + "truncated", + } + + for i := 0; i < len(invalidRSA); i++ { + result, err := ParseRSAPrivateKeyFromPEM([]byte(invalidRSA[i])) + if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { + t.Errorf("ParseRSAPrivateKeyFromPEM returned (%p, %s), expected '%s'"+ + " error", result, err, expectedErrors[i]) + } + } +} + func TestLoadRSAPublicKey(t *testing.T) { // Test loading valid public rsa key from pem-formatted file var key Key @@ -101,7 +127,7 @@ func TestLoadRSAPrivateKey(t *testing.T) { // Test loading error: // - Not a pem formatted rsa Private key - expectedError := "Could not find a Private key PEM block" + expectedError := "Could not find a private key PEM block" err = key.LoadRSAPrivateKey("demo.layout.template") if err == nil || !strings.Contains(err.Error(), expectedError) { t.Errorf("LoadRSAPrivateKey returned (%s), expected '%s' error", err, From 66e49b8a7f5e507ea5d8caf8e9d5f6c7163b1f68 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 26 Jun 2020 12:40:20 +0200 Subject: [PATCH 10/86] add GenerateRSASignature This adds support for signing byte data with rsassa-pss. TODO: We need to verify if rsa.SignPSS(rand=nil,...) is secure! --- in_toto/keylib.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 0f81aca8..d4c5bd8d 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -232,6 +232,40 @@ func (k *Key) LoadRSAPrivateKey(path string) (err error) { return nil } +/* +GenerateRSASignature generates a rsassa-pss signature, based +on the passed key and signable data. If something goes wrong +it will return an uninitialized Signature with an error. +If everything goes right, the function will return an initialized +signature with err=nil. +*/ +func GenerateRSASignature(signable []byte, key Key) (Signature, error) { + var signature Signature + keyReader := strings.NewReader(key.KeyVal.Private) + pemBytes, err := ioutil.ReadAll(keyReader) + if err != nil { + return signature, err + } + rsaPriv, err := ParseRSAPrivateKeyFromPEM(pemBytes) + if err != nil { + return signature, err + } + + hashed := sha256.Sum256(signable) + + // TODO: verify if rand=nil is secure!!! + signatureBuffer, err := rsa.SignPSS(nil, rsaPriv, crypto.SHA256, hashed[:], + &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return signature, err + } + + signature.Sig = hex.EncodeToString(signatureBuffer) + signature.KeyId = key.KeyId + + return signature, nil +} + /* VerifyRSASignature uses the passed Key to verify the passed Signature over the passed data. It returns an error if the key is not a valid RSA public key or From fe86f294708c63862581c2273d72797ddcaf6385 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 26 Jun 2020 13:03:06 +0200 Subject: [PATCH 11/86] fix random source for rsa.SignPSS and add test function --- in_toto/keylib.go | 5 ++-- in_toto/keylib_test.go | 55 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index d4c5bd8d..b6024d08 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -2,6 +2,7 @@ package in_toto import ( "crypto" + "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" @@ -253,8 +254,8 @@ func GenerateRSASignature(signable []byte, key Key) (Signature, error) { hashed := sha256.Sum256(signable) - // TODO: verify if rand=nil is secure!!! - signatureBuffer, err := rsa.SignPSS(nil, rsaPriv, crypto.SHA256, hashed[:], + // We use rand.Reader as secure random source for rsa.SignPSS() + signatureBuffer, err := rsa.SignPSS(rand.Reader, rsaPriv, crypto.SHA256, hashed[:], &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) if err != nil { return signature, err diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index ef95e35a..5e8578d7 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -141,7 +141,60 @@ func TestLoadRSAPrivateKey(t *testing.T) { } } -func TestVerifySignature(t *testing.T) { +func TestGenerateRSASignature(t *testing.T) { + validKey := Key{ + KeyId: "f29cb6877d14ebcf28b136a96a4d64935522afaddcc84e6b70ff6b9eaefb8fcf", + KeyVal: KeyVal{ + Private: `-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY-----`, + }, + } + // We are not verifying the signature yet.. + validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` + _, err := GenerateRSASignature([]byte(validData), validKey) + if err != nil { + t.Errorf("GenerateRSASignature from validKey and data failed: %s", err) + } +} + +func TestVerifyRSASignature(t *testing.T) { validSig := Signature{ KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498", Sig: "a8adf1587659ca1d064b2d64debb6f03cba08a01d6d13c8b205ac7cb79ab8729159" + From c8ced5b8aa80f38dfc727eb1f796be83e155532f Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 26 Jun 2020 14:04:43 +0200 Subject: [PATCH 12/86] add verification for the generated signature --- in_toto/keylib_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 5e8578d7..15012f69 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -145,6 +145,17 @@ func TestGenerateRSASignature(t *testing.T) { validKey := Key{ KeyId: "f29cb6877d14ebcf28b136a96a4d64935522afaddcc84e6b70ff6b9eaefb8fcf", KeyVal: KeyVal{ + Public: `-----BEGIN PUBLIC KEY----- +MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l +8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP +r3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz +eUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT +vpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2 +LFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5 +ujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/ +Vk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf +p8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE= +-----END PUBLIC KEY-----`, Private: `-----BEGIN RSA PRIVATE KEY----- MIIG5QIBAAKCAYEAyCTik98953hKl6+B6n5l8DVIDwDnvrJfpasbJ3+Rw66YcawO ZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXPr3foPHF455TlrqPVfCZiFQ+O4Caf @@ -188,10 +199,14 @@ lQqaoEO7ScdRrzjgvVxXkEY3nwLcWdM61/RZTL0+be8goDw5cWt+PaA= } // We are not verifying the signature yet.. validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` - _, err := GenerateRSASignature([]byte(validData), validKey) + validSig, err := GenerateRSASignature([]byte(validData), validKey) if err != nil { t.Errorf("GenerateRSASignature from validKey and data failed: %s", err) } + if err := VerifyRSASignature(validKey, validSig, []byte(validData)); err != nil { + t.Errorf("VerifyRSASignature from validSignature and data has failed: %s", err) + } + } func TestVerifyRSASignature(t *testing.T) { From 825355621a8a90265ce1aaab422365c391596116 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 26 Jun 2020 15:41:54 +0200 Subject: [PATCH 13/86] add a first draft for signing links in InTotoRun We use the model.Sign() func for signing keys. This commit also removes unrelated code in TestMetablockSignWithEd25519 because we **indeed** support RSA now. This adds support for signing links in InTotoRun via a specific key --- in_toto/model.go | 14 ++++++++++---- in_toto/model_test.go | 10 ---------- in_toto/runlib.go | 14 +++++++++++--- in_toto/runlib_test.go | 4 ++-- in_toto/verifylib.go | 2 +- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/in_toto/model.go b/in_toto/model.go index 1ea0521b..b8713520 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -642,14 +642,20 @@ func (mb *Metablock) Sign(key Key) error { // FIXME: we could be fancier about signature-generation using a dispatch // table or something but for now let's just be explicit // (also, lolnogenerics) - if key.KeyType == "ed25519" && key.Scheme == "ed25519" { + switch key.Scheme { + case "ed25519": newSignature, err = GenerateEd25519Signature(dataCanonical, key) if err != nil { return err } - } else { - return fmt.Errorf("This key type or signature (%s, %s) scheme is "+ - "not supported yet!", key.KeyType, key.Scheme) + case "rsassa-pss-sha256": + newSignature, err = GenerateRSASignature(dataCanonical, key) + if err != nil { + return err + } + default: + return fmt.Errorf("this key type or signature (%s, %s) scheme is "+ + "not supported yet", key.KeyType, key.Scheme) } mb.Signatures = append(mb.Signatures, newSignature) diff --git a/in_toto/model_test.go b/in_toto/model_test.go index ea70089a..4100e219 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -1168,16 +1168,6 @@ func TestMetablockSignWithEd25519(t *testing.T) { t.Errorf("Cannot parse template file: %s", err) } - if err := key.LoadRSAPublicKey("alice.pub"); err != nil { - t.Errorf("Cannot load public key file: %s", err) - } - err := mb.Sign(key) - if err == nil || !strings.Contains(err.Error(), "supported yet") { - t.Errorf("Metablock.Sign returned (%s), expected it to claim this "+ - "key type/scheme is unsupported", err) - - } - pubkey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": ""}}` badkey, err := ParseEd25519FromPrivateJSON(pubkey) diff --git a/in_toto/runlib.go b/in_toto/runlib.go index 733b9968..fd12545f 100644 --- a/in_toto/runlib.go +++ b/in_toto/runlib.go @@ -249,7 +249,7 @@ return value is an empty Metablock and the second return value is the error. NOTE: Currently InTotoRun cannot be used to sign Link metadata. */ func InTotoRun(name string, materialPaths []string, productPaths []string, - cmdArgs []string) (Metablock, error) { + cmdArgs []string, key Key) (Metablock, error) { var linkMb Metablock materials, err := RecordArtifacts(materialPaths) if err != nil { @@ -266,8 +266,7 @@ func InTotoRun(name string, materialPaths []string, productPaths []string, return linkMb, err } - linkMb.Signatures = []Signature{} - linkMb.Signed = Link{ + link := Link{ Type: "link", Name: name, Materials: materials, @@ -277,5 +276,14 @@ func InTotoRun(name string, materialPaths []string, productPaths []string, Environment: map[string]interface{}{}, } + linkMb.Signatures = []Signature{} + // we expect that key has been initialized if it has a valid KeyId + if key.KeyId != "" { + if err := linkMb.Sign(key); err != nil { + return linkMb, err + } + } + linkMb.Signed = link + return linkMb, nil } diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index 42012876..d68caf18 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -317,7 +317,7 @@ func TestInTotoRun(t *testing.T) { } for i := 0; i < len(parameters); i++ { result, err := InTotoRun(linkName, parameters[i]["materialPaths"], - parameters[i]["productPaths"], parameters[i]["cmdArgs"]) + parameters[i]["productPaths"], parameters[i]["cmdArgs"], Key{}) if !reflect.DeepEqual(result, expected[i]) { t.Errorf("InTotoRun returned '(%s, %s)', expected '(%s, nil)'", result, err, expected[i]) @@ -348,7 +348,7 @@ func TestInTotoRun(t *testing.T) { for i := 0; i < len(parameters); i++ { result, err := InTotoRun(linkName, parameters[i]["materialPaths"], - parameters[i]["productPaths"], parameters[i]["cmdArgs"]) + parameters[i]["productPaths"], parameters[i]["cmdArgs"], Key{}) if err == nil { t.Errorf("InTotoRun returned '(%s, %s)', expected '(%s, )'", result, err, expected[i]) diff --git a/in_toto/verifylib.go b/in_toto/verifylib.go index f8f7b0f3..cde95844 100644 --- a/in_toto/verifylib.go +++ b/in_toto/verifylib.go @@ -39,7 +39,7 @@ func RunInspections(layout Layout) (map[string]Metablock, error) { for _, inspection := range layout.Inspect { linkMb, err := InTotoRun(inspection.Name, []string{"."}, []string{"."}, - inspection.Run) + inspection.Run, Key{}) if err != nil { return nil, err } From ed01d7bbf672e95b5068f2531ce1ff73b31036b0 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sun, 5 Jul 2020 16:31:58 +0200 Subject: [PATCH 14/86] add new validatePrivateKey function + add key id to pub key In the past in-toto-keygen generated pubkeys did not have a public key ID in their JSON structure. This is going to change in the securesystemslib: https://github.com/secure-systems-lab/securesystemslib/pull/250 This commit adds the key ID to all our public key tests + and the carol.pub key. --- in_toto/keylib.go | 8 ++++---- in_toto/keylib_test.go | 22 +++++++--------------- in_toto/model.go | 13 +++++++++++++ in_toto/model_test.go | 2 +- test/data/carol.pub | 2 +- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index b6024d08..fdfbe1e0 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -325,8 +325,8 @@ func ParseEd25519FromPrivateJSON(JSONString string) (Key, error) { return keyObj, fmt.Errorf("this doesn't appear to be an ed25519 key") } - if keyObj.KeyVal.Private == "" { - return keyObj, fmt.Errorf("this key is not a private key") + if err := validatePrivateKey(keyObj); err != nil { + return keyObj, err } // 64 hexadecimal digits => 32 bytes for the private portion of the key @@ -360,8 +360,8 @@ func ParseEd25519FromPublicJSON(JSONString string) (Key, error) { return keyObj, fmt.Errorf("this doesn't appear to be an ed25519 key") } - if keyObj.KeyVal.Private != "" { - return keyObj, fmt.Errorf("this key is not a public key") + if err := validatePubKey(keyObj); err != nil { + return keyObj, err } // 64 hexadecimal digits => 32 bytes for the public portion of the key diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 15012f69..70378085 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -293,7 +293,7 @@ func TestParseEd25519FromPrivateJSON(t *testing.T) { expectedErrors := []string{ "this is not a valid JSON key object", - "this key is not a private key", + "in key '308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64': private key cannot be empty", "the private field on this key is malformed", "this doesn't appear to be an ed25519 key", "this doesn't appear to be an ed25519 key", @@ -386,11 +386,11 @@ func TestGenerateEd25519Signature(t *testing.T) { func TestLoad25519PublicKey(t *testing.T) { var key Key - if err := key.LoadEd25519PublicKey("bob.pub"); err != nil { + if err := key.LoadEd25519PublicKey("carol.pub"); err != nil { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) } - expectedPubKey := "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70" + expectedPubKey := "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037" if expectedPubKey != key.KeyVal.Public { t.Errorf("Loaded pubkey is not the expected key") } @@ -434,10 +434,10 @@ func TestParseEd25519FromPublicJSON(t *testing.T) { expectedError string }{ {"not a json", "this is not a valid JSON key object"}, - {`{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70", "private": "861fd1b466cfc6f73"}}`, "this key is not a public key"}, - {`{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d74"}}`, "the public field on this key is malformed"}, - {`{"keytype": "25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}}`, "this doesn't appear to be an ed25519 key"}, - {`{"keytype": "ed25519", "scheme": "cd25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}}`, "his doesn't appear to be an ed25519 key"}, + {`{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", "private": "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162"}}`, "private key found"}, + {`{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64"}}`, "the public field on this key is malformed"}, + {`{"keytype": "25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}}`, "this doesn't appear to be an ed25519 key"}, + {`{"keytype": "ed25519", "scheme": "ec25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}}}`, "this is not a valid JSON key object"}, } for _, table := range tables { @@ -446,12 +446,4 @@ func TestParseEd25519FromPublicJSON(t *testing.T) { t.Errorf("ParseEd25519FromPublicJSON returned (%s), expected '%s'", err, table.expectedError) } } - - // Generated through in-toto run 0.4.1 and thus it should be a happy key - validKey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}}` - _, err := ParseEd25519FromPublicJSON(validKey) - if err != nil { - t.Errorf("ParseEd25519FromPublicJSON returned (%s), expected no error", - err) - } } diff --git a/in_toto/model.go b/in_toto/model.go index b8713520..867ca1b2 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -63,6 +63,19 @@ func validatePubKey(key Key) error { return nil } +/* +validatePrivateKey is a general function to validate if a key is a valid private key. +*/ +func validatePrivateKey(key Key) error { + if err := validateHexString(key.KeyId); err != nil { + return fmt.Errorf("keyid: %s", err.Error()) + } + if key.KeyVal.Private == "" { + return fmt.Errorf("in key '%s': private key cannot be empty", key.KeyId) + } + return nil +} + /* validateRSAPubKey checks if a passed key is a valid RSA public key. */ diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 4100e219..4d010d28 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -1171,7 +1171,7 @@ func TestMetablockSignWithEd25519(t *testing.T) { pubkey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": ""}}` badkey, err := ParseEd25519FromPrivateJSON(pubkey) - if err == nil || !strings.Contains(err.Error(), "this key is not a private key") { + if err == nil || !strings.Contains(err.Error(), "private key cannot be empty") { t.Errorf("Metablock.Sign returned (%s), expected it to claim this "+ "key is not a private key", err) diff --git a/test/data/carol.pub b/test/data/carol.pub index 1af4d653..e80d7f25 100644 --- a/test/data/carol.pub +++ b/test/data/carol.pub @@ -1 +1 @@ -{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}} \ No newline at end of file +{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}} \ No newline at end of file From 17c679bfc4328845331f7586d28c0e23abb02b18 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sun, 5 Jul 2020 17:12:02 +0200 Subject: [PATCH 15/86] add more test case + table tests to TestInTotoRun Table tests are easier to maintain, also we are testing invalid paths and invalid keys now. --- in_toto/runlib_test.go | 50 +++++++++++++++++++---------------------- test/data/carol-invalid | 1 + 2 files changed, 24 insertions(+), 27 deletions(-) create mode 100644 test/data/carol-invalid diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index d68caf18..84b31237 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -324,34 +324,30 @@ func TestInTotoRun(t *testing.T) { } } - // Test in-toto run errors: - // - error due to nonexistent material path - // - error due to nonexistent product path - // - error due to nonexistent run command - parameters = []map[string][]string{ - { - "materialPaths": {"material-does-not-exist"}, - "productPaths": {""}, - "cmdArgs": {"sh", "-c", "printf test"}, - }, - { - "materialPaths": {}, - "productPaths": {"product-does-not-exist"}, - "cmdArgs": {"sh", "-c", "printf test"}, - }, - { - "materialPaths": {}, - "productPaths": {}, - "cmdArgs": {"command-does-not-exist"}, - }, - } - - for i := 0; i < len(parameters); i++ { - result, err := InTotoRun(linkName, parameters[i]["materialPaths"], - parameters[i]["productPaths"], parameters[i]["cmdArgs"], Key{}) + tablesInvalid := []struct { + materialPaths []string + productPaths []string + cmdArgs []string + key Key + }{ + {[]string{"material-does-not-exist"}, []string{""}, []string{"sh", "-c", "printf test"}, Key{}}, + {[]string{"demo.layout.template"}, []string{"product-does-not-exist"}, []string{"sh", "-c", "printf test"}, Key{}}, + {[]string{""}, []string{"/invalid-path/"}, []string{"sh", "-c", "printf test"}, Key{}}, + {[]string{}, []string{}, []string{"command-does-not-exist"}, Key{}}, + {[]string{"demo.layout.template"}, []string{"foo.tar.gz"}, []string{"sh", "-c", "printf out; printf err >&2"}, Key{ + KeyId: "this-is-invalid", + KeyIdHashAlgorithms: nil, + KeyType: "", + KeyVal: KeyVal{}, + Scheme: "", + }}, + } + + for _, table := range tablesInvalid { + result, err := InTotoRun(linkName, table.materialPaths, table.productPaths, table.cmdArgs, table.key) if err == nil { - t.Errorf("InTotoRun returned '(%s, %s)', expected '(%s, )'", - result, err, expected[i]) + t.Errorf("InTotoRun returned '(%s, %s)', expected error", + result, err) } } } diff --git a/test/data/carol-invalid b/test/data/carol-invalid new file mode 100644 index 00000000..cd13859e --- /dev/null +++ b/test/data/carol-invalid @@ -0,0 +1 @@ +{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", "private": "4cedf4d"}} \ No newline at end of file From 74133910dee0aaa66160c55fb1bc061b501b42bd Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sun, 5 Jul 2020 22:02:03 +0200 Subject: [PATCH 16/86] add more model test cases We need to cover signing with invalid keys and validating private keys --- in_toto/model_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 4d010d28..9095615a 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -698,6 +698,19 @@ func TestValidateHexSchema(t *testing.T) { } } +func TestValidatePrivateKey(t *testing.T) { + invalidKey := Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: nil, + KeyType: "", + KeyVal: KeyVal{}, + Scheme: "", + } + if err := validatePrivateKey(invalidKey); err == nil { + t.Errorf("validating a private key with an invalid keyID should fail") + } +} + func TestValidatePubKey(t *testing.T) { testKey := Key{ KeyId: "776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5", @@ -1156,6 +1169,24 @@ func TestValidateSupplyChainItem(t *testing.T) { } } +func TestMetablockSignWithRSA(t *testing.T) { + var mb Metablock + if err := mb.Load("demo.layout.template"); err != nil { + t.Errorf("Cannot parse template file: %s", err) + } + invalidKey := Key{ + KeyId: "test", + KeyIdHashAlgorithms: nil, + KeyType: "rsa", + KeyVal: KeyVal{}, + Scheme: "rsassa-pss-sha256", + } + + if err := mb.Sign(invalidKey); err == nil { + t.Errorf("signing with an invalid RSA key should fail") + } +} + func TestMetablockSignWithEd25519(t *testing.T) { // Test metablock signing (with ed25519) // - Pass non-ed25519 key From d5b38f7261af291eb84e0eee8c456d23c23a1fd0 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sun, 5 Jul 2020 22:59:47 +0200 Subject: [PATCH 17/86] make sure to sign the link data + tests In the past we always signed an empty Link{} artifact. Now we are really signing something + testing for a valid signature after signing real data. --- in_toto/runlib.go | 5 +---- in_toto/runlib_test.go | 39 ++++++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/in_toto/runlib.go b/in_toto/runlib.go index fd12545f..af4ab5fb 100644 --- a/in_toto/runlib.go +++ b/in_toto/runlib.go @@ -266,7 +266,7 @@ func InTotoRun(name string, materialPaths []string, productPaths []string, return linkMb, err } - link := Link{ + linkMb.Signed = Link{ Type: "link", Name: name, Materials: materials, @@ -275,7 +275,6 @@ func InTotoRun(name string, materialPaths []string, productPaths []string, Command: cmdArgs, Environment: map[string]interface{}{}, } - linkMb.Signatures = []Signature{} // we expect that key has been initialized if it has a valid KeyId if key.KeyId != "" { @@ -283,7 +282,5 @@ func InTotoRun(name string, materialPaths []string, productPaths []string, return linkMb, err } } - linkMb.Signed = link - return linkMb, nil } diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index 84b31237..823047a3 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -284,16 +284,20 @@ func TestRunCommand(t *testing.T) { func TestInTotoRun(t *testing.T) { // Successfully run InTotoRun linkName := "Name" - parameters := []map[string][]string{ - { - "materialPaths": {"demo.layout.template"}, - "productPaths": {"foo.tar.gz"}, - "cmdArgs": {"sh", "-c", "printf out; printf err >&2"}, - }, + + var validKey Key + if err := validKey.LoadEd25519PrivateKey("carol"); err != nil { + t.Error(err) } - expected := []Metablock{ - { - Signatures: []Signature{}, + + tablesCorrect := []struct { + materialPaths []string + productPaths []string + cmdArgs []string + key Key + result Metablock + }{ + {[]string{"demo.layout.template"}, []string{"foo.tar.gz"}, []string{"sh", "-c", "printf out; printf err >&2"}, validKey, Metablock{ Signed: Link{ Name: linkName, Type: "link", @@ -313,17 +317,22 @@ func TestInTotoRun(t *testing.T) { Command: []string{"sh", "-c", "printf out; printf err >&2"}, Environment: map[string]interface{}{}, }, + Signatures: []Signature{{ + KeyId: "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", + Sig: "ce7f736866cee27e58544ad9daee88e211376b29451619f50a4d16abd796931b1bbb4b78dd120290998155bf5db458c4f7a768d26b39f70efeb74ac634625b0b", + }}, + }, }, } - for i := 0; i < len(parameters); i++ { - result, err := InTotoRun(linkName, parameters[i]["materialPaths"], - parameters[i]["productPaths"], parameters[i]["cmdArgs"], Key{}) - if !reflect.DeepEqual(result, expected[i]) { - t.Errorf("InTotoRun returned '(%s, %s)', expected '(%s, nil)'", - result, err, expected[i]) + + for _, table := range tablesCorrect { + result, err := InTotoRun(linkName, table.materialPaths, table.productPaths, table.cmdArgs, table.key) + if !reflect.DeepEqual(result, table.result) { + t.Errorf("InTotoRun returned '(%s, %s)', expected '(%s, nil)'", result, err, table.result) } } + // Run InToToRun with errors tablesInvalid := []struct { materialPaths []string productPaths []string From 17dc020d5263b7e13d954b363fe97f2bd4507af6 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 6 Jul 2020 18:59:40 +0200 Subject: [PATCH 18/86] implement GenerateKeyId + remove keyId from pubkeys In this commit we remove the keyId from the ed25519 pubkeys again, because we decided to not support keyIds in key material. Instead we are generating a keyId if the keyId is empty. --- in_toto/keylib.go | 42 ++++++++++++++++++++++++++++++++++++++++++ in_toto/keylib_test.go | 8 ++++---- test/data/carol.pub | 2 +- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index fdfbe1e0..bf97de1c 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -18,6 +18,34 @@ import ( "strings" ) +/* +GenerateKeyId creates a partial key map and generates the key ID +based on the created partial key map via the SHA256 method. +The resulting keyID will be directly saved in the corresponding key object. +*/ +func (k *Key) GenerateKeyId() error { + // Create partial key map used to create the keyid + // Unfortunately, we can't use the Key object because this also carries + // yet unwanted fields, such as KeyId and KeyVal.Private and therefore + // produces a different hash + var keyToBeHashed = map[string]interface{}{ + "keytype": k.KeyType, + "scheme": k.Scheme, + "keyid_hash_algorithms": k.KeyIdHashAlgorithms, + "keyval": map[string]string{ + "public": k.KeyVal.Public, + }, + } + keyCanonical, err := EncodeCanonical(keyToBeHashed) + if err != nil { + return err + } + // calculate sha256 and return string representation of keyId + keyHashed := sha256.Sum256(keyCanonical) + k.KeyId = fmt.Sprintf("%x", keyHashed) + return nil +} + /* ParseRSAPublicKeyFromPEM parses the passed pemBytes as e.g. read from a PEM formatted file, and instantiates and returns the corresponding RSA public key. @@ -325,6 +353,13 @@ func ParseEd25519FromPrivateJSON(JSONString string) (Key, error) { return keyObj, fmt.Errorf("this doesn't appear to be an ed25519 key") } + // if the keyId is empty we try to generate the keyId + if keyObj.KeyId == "" { + if err := keyObj.GenerateKeyId(); err != nil { + return keyObj, err + } + } + if err := validatePrivateKey(keyObj); err != nil { return keyObj, err } @@ -360,6 +395,13 @@ func ParseEd25519FromPublicJSON(JSONString string) (Key, error) { return keyObj, fmt.Errorf("this doesn't appear to be an ed25519 key") } + // if the keyId is empty we try to generate the keyId + if keyObj.KeyId == "" { + if err := keyObj.GenerateKeyId(); err != nil { + return keyObj, err + } + } + if err := validatePubKey(keyObj); err != nil { return keyObj, err } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 70378085..132de26f 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -434,10 +434,10 @@ func TestParseEd25519FromPublicJSON(t *testing.T) { expectedError string }{ {"not a json", "this is not a valid JSON key object"}, - {`{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", "private": "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162"}}`, "private key found"}, - {`{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64"}}`, "the public field on this key is malformed"}, - {`{"keytype": "25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}}`, "this doesn't appear to be an ed25519 key"}, - {`{"keytype": "ed25519", "scheme": "ec25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}}}`, "this is not a valid JSON key object"}, + {`{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", "private": "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162"}}`, "private key found"}, + {`{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64"}}`, "the public field on this key is malformed"}, + {`{"keytype": "25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}}`, "this doesn't appear to be an ed25519 key"}, + {`{"keytype": "ed25519", "scheme": "ec25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}}}`, "this is not a valid JSON key object"}, } for _, table := range tables { diff --git a/test/data/carol.pub b/test/data/carol.pub index e80d7f25..1af4d653 100644 --- a/test/data/carol.pub +++ b/test/data/carol.pub @@ -1 +1 @@ -{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}} \ No newline at end of file +{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}} \ No newline at end of file From 07ef081e4b774ecada6706998f014fe3d5c1c1ef Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 6 Jul 2020 22:45:58 +0200 Subject: [PATCH 19/86] use generateKeyId in LoadRSA functions This commit integrates our new GenerateKeyId func into the LoadRSA* functions. More importantly it fixes a major security issue(!). Before this commit we have calculated the the keyID of the private key with the private key **included**. The private key should never be used for calculating the keyID. --- in_toto/keylib.go | 48 ++++++------------------------------------ in_toto/keylib_test.go | 2 +- 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index bf97de1c..4d7f2597 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -144,26 +144,6 @@ func (k *Key) LoadRSAPublicKey(path string) (err error) { scheme := "rsassa-pss-sha256" keyIdHashAlgorithms := []string{"sha256", "sha512"} - // Create partial key map used to create the keyid - // Unfortunately, we can't use the Key object because this also carries - // yet unwanted fields, such as KeyId and KeyVal.Private and therefore - // produces a different hash - var keyToBeHashed = map[string]interface{}{ - "keytype": keyType, - "scheme": scheme, - "keyid_hash_algorithms": keyIdHashAlgorithms, - "keyval": map[string]string{ - "public": string(keyBytesStripped), - }, - } - - // Canonicalize key and get hex representation of hash - keyCanonical, err := EncodeCanonical(keyToBeHashed) - if err != nil { - return err - } - keyHashed := sha256.Sum256(keyCanonical) - // Unmarshalling the canonicalized key into the Key object would seem natural // Unfortunately, our mandated canonicalization function produces a byte // slice that cannot be unmarshalled by Golang's json decoder, hence we have @@ -174,7 +154,9 @@ func (k *Key) LoadRSAPublicKey(path string) (err error) { } k.Scheme = scheme k.KeyIdHashAlgorithms = keyIdHashAlgorithms - k.KeyId = fmt.Sprintf("%x", keyHashed) + if err := k.GenerateKeyId(); err != nil { + return err + } return nil } @@ -226,26 +208,6 @@ func (k *Key) LoadRSAPrivateKey(path string) (err error) { scheme := "rsassa-pss-sha256" keyIdHashAlgorithms := []string{"sha256", "sha512"} - // Create partial key map used to create the keyid - // Unfortunately, we can't use the Key object because this also carries - // yet unwanted fields, such as KeyId and KeyVal.Private and therefore - // produces a different hash - var keyToBeHashed = map[string]interface{}{ - "keytype": keyType, - "scheme": scheme, - "keyid_hash_algorithms": keyIdHashAlgorithms, - "keyval": map[string]string{ - "Private": string(keyBytesStripped), - }, - } - - // Canonicalize key and get hex representation of hash - keyCanonical, err := EncodeCanonical(keyToBeHashed) - if err != nil { - return err - } - keyHashed := sha256.Sum256(keyCanonical) - // Unmarshalling the canonicalized key into the Key object would seem natural // Unfortunately, our mandated canonicalization function produces a byte // slice that cannot be unmarshalled by Golang's json decoder, hence we have @@ -256,7 +218,9 @@ func (k *Key) LoadRSAPrivateKey(path string) (err error) { } k.Scheme = scheme k.KeyIdHashAlgorithms = keyIdHashAlgorithms - k.KeyId = fmt.Sprintf("%x", keyHashed) + if err := k.GenerateKeyId(); err != nil { + return err + } return nil } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 132de26f..236b2959 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -119,7 +119,7 @@ func TestLoadRSAPrivateKey(t *testing.T) { if err != nil { t.Errorf("LoadRSAPrivateKey returned %s, expected no error", err) } - expectedKeyID := "f29cb6877d14ebcf28b136a96a4d64935522afaddcc84e6b70ff6b9eaefb8fcf" + expectedKeyID := "564da1dce337f108dd6984454f5f521c2cc76b80e7e18bbd41a11c159a3003b3" if key.KeyId != expectedKeyID { t.Errorf("LoadRSAPrivateKey parsed KeyId '%s', expected '%s'", key.KeyId, expectedKeyID) From 5f468800580d4fd97d50a2515166b65876a68b03 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 7 Jul 2020 21:06:45 +0200 Subject: [PATCH 20/86] Infer RSA Public Key from Private Key We need to infer the RSA Public Key from the RSA Private Key, otherwise we can't a calculate a unique keyID for a RSA Private Key. --- in_toto/keylib.go | 45 ++++++++++++++++++------------------------ in_toto/keylib_test.go | 4 ++-- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 4d7f2597..a2060354 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -127,16 +127,6 @@ func (k *Key) LoadRSAPublicKey(path string) (err error) { return err } - // Strip leading and trailing data from PEM file like securesystemslib does - // TODO: Should we instead use the parsed public key to reconstruct the PEM? - keyHeader := "-----BEGIN PUBLIC KEY-----" - keyFooter := "-----END PUBLIC KEY-----" - keyStart := strings.Index(string(keyBytes), keyHeader) - keyEnd := strings.Index(string(keyBytes), keyFooter) + len(keyFooter) - // Successful call to ParseRSAPublicKeyFromPEM already guarantees that - // header and footer are present, i.e. `!(keyStart == -1 || keyEnd == -1)` - keyBytesStripped := keyBytes[keyStart:keyEnd] - // Declare values for key // TODO: Do not hardcode here, but define defaults elsewhere and add support // for parametrization @@ -150,7 +140,7 @@ func (k *Key) LoadRSAPublicKey(path string) (err error) { // to manually assign the values k.KeyType = keyType k.KeyVal = KeyVal{ - Public: string(keyBytesStripped), + Public: string(keyBytes), } k.Scheme = scheme k.KeyIdHashAlgorithms = keyIdHashAlgorithms @@ -178,28 +168,30 @@ func (k *Key) LoadRSAPrivateKey(path string) (err error) { }() // Read key bytes and decode PEM - keyBytes, err := ioutil.ReadAll(keyFile) + privateKeyBytes, err := ioutil.ReadAll(keyFile) if err != nil { return err } - // We only parse to see if this is indeed a pem formatted rsa Private key, - // but don't use the returned *rsa.PrivateKey. Instead, we continue with - // the original keyBytes from above. - _, err = ParseRSAPrivateKeyFromPEM(keyBytes) + // We load the private key here for inferring the public key + // from the private key. We need the public key for keyId generation + privateKey, err := ParseRSAPrivateKeyFromPEM(privateKeyBytes) + if err != nil { + return err + } + pubKeyBytes, err := x509.MarshalPKIXPublicKey(privateKey.Public()) if err != nil { return err } - // Strip leading and trailing data from PEM file like securesystemslib does - // TODO: Should we instead use the parsed Private key to reconstruct the PEM? - keyHeader := "-----BEGIN RSA PRIVATE KEY-----" - keyFooter := "-----END RSA PRIVATE KEY-----" - keyStart := strings.Index(string(keyBytes), keyHeader) - keyEnd := strings.Index(string(keyBytes), keyFooter) + len(keyFooter) - // Successful call to ParseRSAPrivateKeyFromPEM already guarantees that - // header and footer are present, i.e. `!(keyStart == -1 || keyEnd == -1)` - keyBytesStripped := keyBytes[keyStart:keyEnd] + // construct pemBlock + publicKeyPemBlock := &pem.Block{ + Type: "PUBLIC KEY", + Headers: nil, + Bytes: pubKeyBytes, + } + + publicKeyPemBlockBytes := pem.EncodeToMemory(publicKeyPemBlock) // Declare values for key // TODO: Do not hardcode here, but define defaults elsewhere and add support @@ -214,7 +206,8 @@ func (k *Key) LoadRSAPrivateKey(path string) (err error) { // to manually assign the values k.KeyType = keyType k.KeyVal = KeyVal{ - Private: string(keyBytesStripped), + Public: string(publicKeyPemBlockBytes), + Private: string(privateKeyBytes), } k.Scheme = scheme k.KeyIdHashAlgorithms = keyIdHashAlgorithms diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 236b2959..a8a94139 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -90,7 +90,7 @@ func TestLoadRSAPublicKey(t *testing.T) { if err != nil { t.Errorf("LoadRSAPublicKey returned %s, expected no error", err) } - expectedKeyID := "556caebdc0877eed53d419b60eddb1e57fa773e4e31d70698b588f3e9cc48b35" + expectedKeyID := "df265e976f626556ebc1f51957d75f60455337890fef08c8b7f4c681dbf6d5ee" if key.KeyId != expectedKeyID { t.Errorf("LoadRSAPublicKey parsed KeyId '%s', expected '%s'", key.KeyId, expectedKeyID) @@ -119,7 +119,7 @@ func TestLoadRSAPrivateKey(t *testing.T) { if err != nil { t.Errorf("LoadRSAPrivateKey returned %s, expected no error", err) } - expectedKeyID := "564da1dce337f108dd6984454f5f521c2cc76b80e7e18bbd41a11c159a3003b3" + expectedKeyID := "4e642891e4221fb79ef583e0f8342c93cd8fbd74584290ccb49714f8c11f057c" if key.KeyId != expectedKeyID { t.Errorf("LoadRSAPrivateKey parsed KeyId '%s', expected '%s'", key.KeyId, expectedKeyID) From 7c1074afdafc44ee965e1ad6ab5a292077a76efc Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 7 Jul 2020 21:51:44 +0200 Subject: [PATCH 21/86] trim spaces and newlines around PEM block We need to make sure to trim spaces and newlines around the PEM blocks --- in_toto/keylib.go | 6 +++--- in_toto/keylib_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index a2060354..9508c742 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -140,7 +140,7 @@ func (k *Key) LoadRSAPublicKey(path string) (err error) { // to manually assign the values k.KeyType = keyType k.KeyVal = KeyVal{ - Public: string(keyBytes), + Public: strings.TrimSpace(string(keyBytes)), } k.Scheme = scheme k.KeyIdHashAlgorithms = keyIdHashAlgorithms @@ -206,8 +206,8 @@ func (k *Key) LoadRSAPrivateKey(path string) (err error) { // to manually assign the values k.KeyType = keyType k.KeyVal = KeyVal{ - Public: string(publicKeyPemBlockBytes), - Private: string(privateKeyBytes), + Public: strings.TrimSpace(string(publicKeyPemBlockBytes)), + Private: strings.TrimSpace(string(privateKeyBytes)), } k.Scheme = scheme k.KeyIdHashAlgorithms = keyIdHashAlgorithms diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index a8a94139..bd129ce1 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -90,7 +90,7 @@ func TestLoadRSAPublicKey(t *testing.T) { if err != nil { t.Errorf("LoadRSAPublicKey returned %s, expected no error", err) } - expectedKeyID := "df265e976f626556ebc1f51957d75f60455337890fef08c8b7f4c681dbf6d5ee" + expectedKeyID := "556caebdc0877eed53d419b60eddb1e57fa773e4e31d70698b588f3e9cc48b35" if key.KeyId != expectedKeyID { t.Errorf("LoadRSAPublicKey parsed KeyId '%s', expected '%s'", key.KeyId, expectedKeyID) @@ -119,7 +119,7 @@ func TestLoadRSAPrivateKey(t *testing.T) { if err != nil { t.Errorf("LoadRSAPrivateKey returned %s, expected no error", err) } - expectedKeyID := "4e642891e4221fb79ef583e0f8342c93cd8fbd74584290ccb49714f8c11f057c" + expectedKeyID := "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401" if key.KeyId != expectedKeyID { t.Errorf("LoadRSAPrivateKey parsed KeyId '%s', expected '%s'", key.KeyId, expectedKeyID) From dd2bd384e2e8b6fc0b21eb0bf7e5ae93dc8f7a5f Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 7 Jul 2020 22:08:34 +0200 Subject: [PATCH 22/86] fix documentation mention that we follow the securesystemslib regarding key generation for keeping interoperability. --- in_toto/keylib.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 9508c742..f876d414 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -27,7 +27,9 @@ func (k *Key) GenerateKeyId() error { // Create partial key map used to create the keyid // Unfortunately, we can't use the Key object because this also carries // yet unwanted fields, such as KeyId and KeyVal.Private and therefore - // produces a different hash + // produces a different hash. We generate the keyId exactly as we do in + // the securesystemslib to keep interoperability between other in-toto + // implementations. var keyToBeHashed = map[string]interface{}{ "keytype": k.KeyType, "scheme": k.Scheme, @@ -49,7 +51,7 @@ func (k *Key) GenerateKeyId() error { /* ParseRSAPublicKeyFromPEM parses the passed pemBytes as e.g. read from a PEM formatted file, and instantiates and returns the corresponding RSA public key. -If the no RSA public key can be parsed, the first return value is nil and the +If no RSA public key can be parsed, the first return value is nil and the second return value is the error. */ func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) { @@ -78,7 +80,7 @@ func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) { /* ParseRSAPrivateKeyFromPEM parses the passed pemBytes as e.g. read from a PEM formatted file, and instantiates and returns the corresponding RSA Private key. -If the no RSA Private key can be parsed, the first return value is nil and the +If no RSA Private key can be parsed, the first return value is nil and the second return value is the error. */ func ParseRSAPrivateKeyFromPEM(pemBytes []byte) (*rsa.PrivateKey, error) { @@ -152,9 +154,9 @@ func (k *Key) LoadRSAPublicKey(path string) (err error) { } /* -LoadRSAPrivateKey parses an RSA Private key from a PEM formatted file at the passed +LoadRSAPrivateKey parses an RSA private key from a PEM formatted file at the passed path into the Key object on which it was called. It returns an error if the -file at path does not exist or is not a PEM formatted RSA Private key. +file at path does not exist or is not a PEM formatted RSA private key. */ func (k *Key) LoadRSAPrivateKey(path string) (err error) { keyFile, err := os.Open(path) From 263383166f928ad9128d125e2be64a895b48429e Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 8 Jul 2020 20:01:55 +0200 Subject: [PATCH 23/86] Add generic LoadKey function With a generic LoadKey function we have several advantages. First we can reduce our code surface, because we just need to take care about one function for loading keys. Second the LoadKey function will automatically infer the right pem and key type. This makes the function very easy to use. --- in_toto/keylib.go | 114 +++++++++++++++++++++++++++++++++++++++++ in_toto/keylib_test.go | 16 +++--- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index f876d414..a70bf047 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -77,6 +77,120 @@ func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) { return rsaPub, nil } +/* +GeneratePublicPemBlock creates a "PUBLIC KEY" PEM block from public key byte data. +If successful it returns PEM block as []byte slice. +*/ +func GeneratePublicPemBlock(pubKeyBytes []byte) []byte { + // construct PEM block + publicKeyPemBlock := &pem.Block{ + Type: "PUBLIC KEY", + Headers: nil, + Bytes: pubKeyBytes, + } + return pem.EncodeToMemory(publicKeyPemBlock) +} + +/* +SetKeyComponents sets all components in our key object. +Furthermore it makes sure to remove any trailing and leading whitespaces or newlines. +*/ +func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyType string, scheme string, keyIdHashAlgorithms []string) error { + if len(privateKeyBytes) > 0 { + // assume we have a privateKey + k.KeyVal = KeyVal{ + Private: strings.TrimSpace(string(privateKeyBytes)), + Public: strings.TrimSpace(string(GeneratePublicPemBlock(pubKeyBytes))), + } + } else { + k.KeyVal = KeyVal{ + Public: strings.TrimSpace(string(pubKeyBytes)), + } + } + k.KeyType = keyType + k.Scheme = scheme + k.KeyIdHashAlgorithms = keyIdHashAlgorithms + if err := k.GenerateKeyId(); err != nil { + return err + } + return nil +} + +/* +LoadKey loads the key file at specified file path into the key object. +It automatically derives the PEM type (PKCS1/PKCS8) and the key type (RSA/ed25519). +On success it will return nil, otherwise an error. +*/ +func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) error { + pemFile, err := os.Open(path) + if err != nil { + return err + } + defer func() { + if closeErr := pemFile.Close(); closeErr != nil { + err = closeErr + } + }() + // Read key bytes and decode PEM + pemBytes, err := ioutil.ReadAll(pemFile) + if err != nil { + return err + } + + // TODO: There could be more key data in _, which we silently ignore here. + // Should we handle it / fail / say something about it? + data, _ := pem.Decode(pemBytes) + if data == nil { + return fmt.Errorf("could not find a private key PEM block") + } + + // Try to load private key, if this fails try to load + // key as public key + key, err := x509.ParsePKCS8PrivateKey(data.Bytes) + if err.Error() == "x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)" { + key, err = x509.ParsePKCS1PrivateKey(data.Bytes) + if err != nil { + return fmt.Errorf("error while loading PKCS1PrivateKey: %s", err) + } + } else if err != nil { + var err2 error + key, err2 = x509.ParsePKIXPublicKey(data.Bytes) + if err2 != nil { + return fmt.Errorf("could not find a private key nor a public key: %s, %s", err, err2) + } + } + // Use type switch to identify the key format + switch key.(type) { + case *rsa.PublicKey: + if err := k.SetKeyComponents(pemBytes, []byte{}, "rsa", scheme, keyIdHashAlgorithms); err != nil { + return err + } + case *rsa.PrivateKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*rsa.PrivateKey).Public()) + if err != nil { + return err + } + if err := k.SetKeyComponents(pubKeyBytes, pemBytes, "rsa", scheme, keyIdHashAlgorithms); err != nil { + return err + } + case *ed25519.PublicKey: + if err := k.SetKeyComponents(pemBytes, []byte{}, "ed25519", scheme, keyIdHashAlgorithms); err != nil { + return err + } + case *ed25519.PrivateKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*ed25519.PrivateKey).Public()) + if err != nil { + return err + } + if err := k.SetKeyComponents(pubKeyBytes, pemBytes, "ed25519", scheme, keyIdHashAlgorithms); err != nil { + return err + } + default: + return fmt.Errorf("unsupported key type: %T", key) + } + return nil +} + /* ParseRSAPrivateKeyFromPEM parses the passed pemBytes as e.g. read from a PEM formatted file, and instantiates and returns the corresponding RSA Private key. diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index bd129ce1..9d94f43a 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -86,7 +86,7 @@ func TestParseRSAPrivateKeyFromPEM(t *testing.T) { func TestLoadRSAPublicKey(t *testing.T) { // Test loading valid public rsa key from pem-formatted file var key Key - err := key.LoadRSAPublicKey("alice.pub") + err := key.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}) if err != nil { t.Errorf("LoadRSAPublicKey returned %s, expected no error", err) } @@ -115,27 +115,27 @@ func TestLoadRSAPublicKey(t *testing.T) { func TestLoadRSAPrivateKey(t *testing.T) { // Test loading valid Private rsa key from pem-formatted file var key Key - err := key.LoadRSAPrivateKey("dan") + err := key.LoadKey("dan", "rsassa-pss-sha256", []string{"sha256", "sha512"}) if err != nil { - t.Errorf("LoadRSAPrivateKey returned %s, expected no error", err) + t.Errorf("LoadKeyKey returned %s, expected no error", err) } expectedKeyID := "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401" if key.KeyId != expectedKeyID { - t.Errorf("LoadRSAPrivateKey parsed KeyId '%s', expected '%s'", + t.Errorf("LoadKeyKey parsed KeyId '%s', expected '%s'", key.KeyId, expectedKeyID) } // Test loading error: // - Not a pem formatted rsa Private key - expectedError := "Could not find a private key PEM block" - err = key.LoadRSAPrivateKey("demo.layout.template") + expectedError := "could not find a private key PEM block" + err = key.LoadKey("demo.layout.template", "", []string{}) if err == nil || !strings.Contains(err.Error(), expectedError) { - t.Errorf("LoadRSAPrivateKey returned (%s), expected '%s' error", err, + t.Errorf("LoadKey returned (%s), expected '%s' error", err, expectedError) } // Test not existing file - err = key.LoadRSAPrivateKey("inToToRocks") + err = key.LoadKey("inToToRocks", "", []string{}) if !errors.Is(err, os.ErrNotExist) { t.Errorf("Invalid file load returned (%s), expected '%s' error", err, os.ErrNotExist) } From b8a5023b819c61c1c8bfcf831f755aac4be7759f Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 8 Jul 2020 20:45:00 +0200 Subject: [PATCH 24/86] change missing test cases Make sure to use the new generic LoadKey function for all our test cases --- in_toto/verifylib_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/in_toto/verifylib_test.go b/in_toto/verifylib_test.go index 29b4e94c..6fe4dc1b 100644 --- a/in_toto/verifylib_test.go +++ b/in_toto/verifylib_test.go @@ -24,7 +24,7 @@ func TestInTotoVerifyPass(t *testing.T) { } var pubKey Key - if err := pubKey.LoadRSAPublicKey(pubKeyPath); err != nil { + if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil { t.Error(err) } @@ -99,7 +99,7 @@ func TestGetSummaryLink(t *testing.T) { func TestVerifySublayouts(t *testing.T) { sublayoutName := "sub_layout" var aliceKey Key - if err := aliceKey.LoadRSAPublicKey("alice.pub"); err != nil { + if err := aliceKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil { t.Errorf("Unable to load Alice's public key") } sublayoutDirectory := fmt.Sprintf(SublayoutLinkDirFormat, sublayoutName, @@ -686,7 +686,7 @@ func TestVerifyLayoutSignatures(t *testing.T) { t.Errorf("Unable to load template file: %s", err) } var layoutKey Key - if err := layoutKey.LoadRSAPublicKey("alice.pub"); err != nil { + if err := layoutKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil { t.Errorf("Unable to load public key file: %s", err) } From fa002a241106aa30f32e9dd4e925f191b02c9d34 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 9 Jul 2020 16:54:38 +0200 Subject: [PATCH 25/86] enhance documentation + more readable pem parser section Make sure to always add a return value description + make the PEM parser section more readable. We could move this block into an own function in the future and maybe make a dispatch table out of it. --- in_toto/keylib.go | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index a70bf047..1f13b0e1 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -22,6 +22,8 @@ import ( GenerateKeyId creates a partial key map and generates the key ID based on the created partial key map via the SHA256 method. The resulting keyID will be directly saved in the corresponding key object. +On success GenerateKeyId will return nil, in case of errors while encoding +there will be an error. */ func (k *Key) GenerateKeyId() error { // Create partial key map used to create the keyid @@ -79,7 +81,9 @@ func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) { /* GeneratePublicPemBlock creates a "PUBLIC KEY" PEM block from public key byte data. -If successful it returns PEM block as []byte slice. +If successful it returns PEM block as []byte slice. This function should always +succeed, if pubKeyBytes is empty the PEM block will have an empty byte block. +Therefore only header and footer will exist. */ func GeneratePublicPemBlock(pubKeyBytes []byte) []byte { // construct PEM block @@ -118,8 +122,25 @@ func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyTy /* LoadKey loads the key file at specified file path into the key object. -It automatically derives the PEM type (PKCS1/PKCS8) and the key type (RSA/ed25519). -On success it will return nil, otherwise an error. +It automatically derives the PEM type and the key type. +Right now the following PEM types are supported: + + * PKCS1 for private keys + * PKCS8 for private keys + * PKIX for public keys + +The following key types are supported: + + * ed25519 + * RSA + +On success it will return nil. The following errors can happen: + + * path not found or not readable + * no PEM block in the loaded file + * no valid PKCS8/PKCS1 private key or PKIX public key + * errors while marshalling + * unsupported key types */ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) error { pemFile, err := os.Open(path) @@ -146,19 +167,21 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) // Try to load private key, if this fails try to load // key as public key + // TODO: It might makes sense to make a dispatch table out of this + // Idea: Moving this section into an own function and looping over all parsing functions. key, err := x509.ParsePKCS8PrivateKey(data.Bytes) - if err.Error() == "x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)" { + if err != nil { + var err2 error key, err = x509.ParsePKCS1PrivateKey(data.Bytes) if err != nil { - return fmt.Errorf("error while loading PKCS1PrivateKey: %s", err) - } - } else if err != nil { - var err2 error - key, err2 = x509.ParsePKIXPublicKey(data.Bytes) - if err2 != nil { - return fmt.Errorf("could not find a private key nor a public key: %s, %s", err, err2) + var err3 error + key, err = x509.ParsePKIXPublicKey(data.Bytes) + if err != nil { + return fmt.Errorf("unsupported key file. No public or private key: ('%s', '%s', '%s')", err, err2, err3) + } } } + // Use type switch to identify the key format switch key.(type) { case *rsa.PublicKey: @@ -166,6 +189,8 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) return err } case *rsa.PrivateKey: + // Note: We store the public key as PKCS8 key here, although the private key get's stored as PKCS1 key + // This behavior is consistent to the securesystemslib pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*rsa.PrivateKey).Public()) if err != nil { return err From e92423381b0c73a74da9eb5159d803f91d048659 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 9 Jul 2020 18:04:17 +0200 Subject: [PATCH 26/86] move parsing to ParseKey function + enhance error handling We are introducing two new error types "ErrFailedPEMParsing", "errNoPEMBlock" and "ErrUnsupportedKeyType". We also use error wrapping as stated in: https://blog.golang.org/go1.13-errors Furthermore the parsing has its own function now. --- in_toto/keylib.go | 54 +++++++++++++++++++++++++++++++----------- in_toto/keylib_test.go | 7 ++---- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 1f13b0e1..3c01e109 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -18,6 +18,15 @@ import ( "strings" ) +// ErrFailedPEMParsing gets returned when PKCS1, PKCS8 or PKIX key parsing fails +var ErrFailedPEMParsing = errors.New("failed parsing the PEM block: unsupported PEM type") + +// ErrNoPEMBlock gets triggered when there is no PEM block in the provided file +var ErrNoPEMBLock = errors.New("failed to decode the data as PEM block (are you sure this is a pem file?)") + +// ErrUnsupportedKeyType is returned when we are dealing with a key type different to ed25519 or RSA +var ErrUnsupportedKeyType = errors.New("unsupported key type") + /* GenerateKeyId creates a partial key map and generates the key ID based on the created partial key map via the SHA256 method. @@ -120,6 +129,33 @@ func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyTy return nil } +/* +ParseKey tries to parse a PEM []byte slice. +Supported are: + + * PKCS8 + * PKCS1 + * PKIX + +On success it returns the parsed key and nil. +On failure it returns nil and the error ErrFailedPEMParsing +*/ +func ParseKey(data []byte) (interface{}, error) { + key, err := x509.ParsePKCS8PrivateKey(data) + if err == nil { + return key, nil + } + key, err = x509.ParsePKCS1PrivateKey(data) + if err == nil { + return key, nil + } + key, err = x509.ParsePKIXPublicKey(data) + if err == nil { + return key, nil + } + return nil, ErrFailedPEMParsing +} + /* LoadKey loads the key file at specified file path into the key object. It automatically derives the PEM type and the key type. @@ -162,24 +198,14 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) // Should we handle it / fail / say something about it? data, _ := pem.Decode(pemBytes) if data == nil { - return fmt.Errorf("could not find a private key PEM block") + return ErrNoPEMBLock } // Try to load private key, if this fails try to load // key as public key - // TODO: It might makes sense to make a dispatch table out of this - // Idea: Moving this section into an own function and looping over all parsing functions. - key, err := x509.ParsePKCS8PrivateKey(data.Bytes) + key, err := ParseKey(data.Bytes) if err != nil { - var err2 error - key, err = x509.ParsePKCS1PrivateKey(data.Bytes) - if err != nil { - var err3 error - key, err = x509.ParsePKIXPublicKey(data.Bytes) - if err != nil { - return fmt.Errorf("unsupported key file. No public or private key: ('%s', '%s', '%s')", err, err2, err3) - } - } + return err } // Use type switch to identify the key format @@ -211,7 +237,7 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) return err } default: - return fmt.Errorf("unsupported key type: %T", key) + return fmt.Errorf("%w: %T", ErrUnsupportedKeyType, key) } return nil } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 9d94f43a..c2be9f99 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -125,13 +125,10 @@ func TestLoadRSAPrivateKey(t *testing.T) { key.KeyId, expectedKeyID) } - // Test loading error: - // - Not a pem formatted rsa Private key - expectedError := "could not find a private key PEM block" err = key.LoadKey("demo.layout.template", "", []string{}) - if err == nil || !strings.Contains(err.Error(), expectedError) { + if err == nil || !errors.Is(err, ErrNoPEMBLock) { t.Errorf("LoadKey returned (%s), expected '%s' error", err, - expectedError) + ErrNoPEMBLock.Error()) } // Test not existing file From 1c2415eb2c8ded3053464c4e040cacc7bf182ca7 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 9 Jul 2020 21:14:00 +0200 Subject: [PATCH 27/86] add more generic GenerateSignature function The generic GeneratureSignature function will automatically detect the right key, return an error if we have an invalid key and sign the signable data. Also it utilizes our new error types. TODO: implementing ed25519 signature. I am not sure yet, if we can store ed25519 PEM blocks in our in-memory key data. If yes, the part will be a little bit different to the current GenerateEd25519Signature function --- in_toto/keylib.go | 299 ++++++++++------------------------------------ 1 file changed, 65 insertions(+), 234 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 3c01e109..0c781a03 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -59,35 +59,6 @@ func (k *Key) GenerateKeyId() error { return nil } -/* -ParseRSAPublicKeyFromPEM parses the passed pemBytes as e.g. read from a PEM -formatted file, and instantiates and returns the corresponding RSA public key. -If no RSA public key can be parsed, the first return value is nil and the -second return value is the error. -*/ -func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) { - // TODO: There could be more key data in _, which we silently ignore here. - // Should we handle it / fail / say something about it? - data, _ := pem.Decode(pemBytes) - if data == nil { - return nil, fmt.Errorf("Could not find a public key PEM block") - } - - pub, err := x509.ParsePKIXPublicKey(data.Bytes) - if err != nil { - return nil, err - } - - //ParsePKIXPublicKey might return an rsa, dsa, or ecdsa public key - rsaPub, isRsa := pub.(*rsa.PublicKey) - if !isRsa { - return nil, fmt.Errorf("We currently only support rsa keys: got '%s'", - reflect.TypeOf(pub)) - } - - return rsaPub, nil -} - /* GeneratePublicPemBlock creates a "PUBLIC KEY" PEM block from public key byte data. If successful it returns PEM block as []byte slice. This function should always @@ -243,146 +214,103 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) } /* -ParseRSAPrivateKeyFromPEM parses the passed pemBytes as e.g. read from a PEM -formatted file, and instantiates and returns the corresponding RSA Private key. -If no RSA Private key can be parsed, the first return value is nil and the +ParseRSAPublicKeyFromPEM parses the passed pemBytes as e.g. read from a PEM +formatted file, and instantiates and returns the corresponding RSA public key. +If no RSA public key can be parsed, the first return value is nil and the second return value is the error. */ -func ParseRSAPrivateKeyFromPEM(pemBytes []byte) (*rsa.PrivateKey, error) { +func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) { // TODO: There could be more key data in _, which we silently ignore here. // Should we handle it / fail / say something about it? data, _ := pem.Decode(pemBytes) if data == nil { - return nil, fmt.Errorf("Could not find a private key PEM block") + return nil, ErrNoPEMBLock } - priv, err := x509.ParsePKCS1PrivateKey(data.Bytes) + pub, err := x509.ParsePKIXPublicKey(data.Bytes) if err != nil { return nil, err } - return priv, nil + //ParsePKIXPublicKey might return an rsa, dsa, or ecdsa public key + rsaPub, isRsa := pub.(*rsa.PublicKey) + if !isRsa { + return nil, fmt.Errorf("We currently only support rsa keys: got '%s'", + reflect.TypeOf(pub)) + } + + return rsaPub, nil } /* -LoadRSAPublicKey parses an RSA public key from a PEM formatted file at the passed -path into the Key object on which it was called. It returns an error if the -file at path does not exist or is not a PEM formatted RSA public key. +ParseRSAPrivateKeyFromPEM parses the passed pemBytes as e.g. read from a PEM +formatted file, and instantiates and returns the corresponding RSA Private key. +If no RSA Private key can be parsed, the first return value is nil and the +second return value is the error. */ -func (k *Key) LoadRSAPublicKey(path string) (err error) { - keyFile, err := os.Open(path) - if err != nil { - return err - } - defer func() { - if closeErr := keyFile.Close(); closeErr != nil { - err = closeErr - } - }() - - // Read key bytes and decode PEM - keyBytes, err := ioutil.ReadAll(keyFile) - if err != nil { - return err +func ParseRSAPrivateKeyFromPEM(pemBytes []byte) (*rsa.PrivateKey, error) { + // TODO: There could be more key data in _, which we silently ignore here. + // Should we handle it / fail / say something about it? + data, _ := pem.Decode(pemBytes) + if data == nil { + return nil, ErrNoPEMBLock } - // We only parse to see if this is indeed a pem formatted rsa public key, - // but don't use the returned *rsa.PublicKey. Instead, we continue with - // the original keyBytes from above. - _, err = ParseRSAPublicKeyFromPEM(keyBytes) + priv, err := x509.ParsePKCS1PrivateKey(data.Bytes) if err != nil { - return err - } - - // Declare values for key - // TODO: Do not hardcode here, but define defaults elsewhere and add support - // for parametrization - keyType := "rsa" - scheme := "rsassa-pss-sha256" - keyIdHashAlgorithms := []string{"sha256", "sha512"} - - // Unmarshalling the canonicalized key into the Key object would seem natural - // Unfortunately, our mandated canonicalization function produces a byte - // slice that cannot be unmarshalled by Golang's json decoder, hence we have - // to manually assign the values - k.KeyType = keyType - k.KeyVal = KeyVal{ - Public: strings.TrimSpace(string(keyBytes)), - } - k.Scheme = scheme - k.KeyIdHashAlgorithms = keyIdHashAlgorithms - if err := k.GenerateKeyId(); err != nil { - return err + return nil, err } - return nil + return priv, nil } /* -LoadRSAPrivateKey parses an RSA private key from a PEM formatted file at the passed -path into the Key object on which it was called. It returns an error if the -file at path does not exist or is not a PEM formatted RSA private key. -*/ -func (k *Key) LoadRSAPrivateKey(path string) (err error) { - keyFile, err := os.Open(path) - if err != nil { - return err - } - defer func() { - if closeErr := keyFile.Close(); closeErr != nil { - err = closeErr - } - }() +GenerateSignature will automatically detect the key type and sign the signable data +with the provided key. If everything goes right GenerateSignature will return +a for the key valid signature and err=nil. If something goes wrong it will +return an not initialized signature and an error. Possible errors are: - // Read key bytes and decode PEM - privateKeyBytes, err := ioutil.ReadAll(keyFile) - if err != nil { - return err - } + * ErrNoPEMBlock + * ErrUnsupportedKeyType - // We load the private key here for inferring the public key - // from the private key. We need the public key for keyId generation - privateKey, err := ParseRSAPrivateKeyFromPEM(privateKeyBytes) +*/ +func GenerateSignature(signable []byte, key Key) (Signature, error) { + var signature Signature + keyReader := strings.NewReader(key.KeyVal.Private) + pemBytes, err := ioutil.ReadAll(keyReader) if err != nil { - return err + return signature, err } - pubKeyBytes, err := x509.MarshalPKIXPublicKey(privateKey.Public()) - if err != nil { - return err + // TODO: There could be more key data in _, which we silently ignore here. + // Should we handle it / fail / say something about it? + data, _ := pem.Decode(pemBytes) + if data == nil { + return signature, ErrNoPEMBLock } - - // construct pemBlock - publicKeyPemBlock := &pem.Block{ - Type: "PUBLIC KEY", - Headers: nil, - Bytes: pubKeyBytes, + parsedKey, err := ParseKey(data.Bytes) + if err != nil { + return signature, err } - publicKeyPemBlockBytes := pem.EncodeToMemory(publicKeyPemBlock) - - // Declare values for key - // TODO: Do not hardcode here, but define defaults elsewhere and add support - // for parametrization - keyType := "rsa" - scheme := "rsassa-pss-sha256" - keyIdHashAlgorithms := []string{"sha256", "sha512"} - - // Unmarshalling the canonicalized key into the Key object would seem natural - // Unfortunately, our mandated canonicalization function produces a byte - // slice that cannot be unmarshalled by Golang's json decoder, hence we have - // to manually assign the values - k.KeyType = keyType - k.KeyVal = KeyVal{ - Public: strings.TrimSpace(string(publicKeyPemBlockBytes)), - Private: strings.TrimSpace(string(privateKeyBytes)), - } - k.Scheme = scheme - k.KeyIdHashAlgorithms = keyIdHashAlgorithms - if err := k.GenerateKeyId(); err != nil { - return err + var signatureBuffer []byte + // Go type switch for interfering the key type + switch parsedKey.(type) { + case *rsa.PrivateKey: + hashed := sha256.Sum256(signable) + // We use rand.Reader as secure random source for rsa.SignPSS() + signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], + &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return signature, err + } + case *ed25519.PrivateKey: + // TODO: implement signatures for ed25519 keys + default: + return signature, fmt.Errorf("%w: %T", ErrUnsupportedKeyType, parsedKey) } - - return nil + signature.Sig = hex.EncodeToString(signatureBuffer) + signature.KeyId = key.KeyId + return signature, nil } /* @@ -579,100 +507,3 @@ func VerifyEd25519Signature(key Key, sig Signature, data []byte) error { } return nil } - -/* LoadEd25519PublicKey loads an ed25519 pub key file -and parses it via ParseEd25519FromPublicJSON. -The pub key file has to be in the in-toto PublicJSON format -For example: - - { - "keytype": "ed25519", - "scheme": "ed25519", - "keyid_hash_algorithms": ["sha256", "sha512"], - "keyval": - { - "public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037" - } - } - -*/ -func (k *Key) LoadEd25519PublicKey(path string) (err error) { - keyFile, err := os.Open(path) - if err != nil { - return err - } - defer func() { - if closeErr := keyFile.Close(); closeErr != nil { - err = closeErr - } - }() - - keyBytes, err := ioutil.ReadAll(keyFile) - if err != nil { - return err - } - // contrary to LoadRSAPublicKey we use the returned key object - keyObj, err := ParseEd25519FromPublicJSON(string(keyBytes)) - if err != nil { - return err - } - // I am not sure if there is a faster way to fill the Key struct - // without touching the ParseEd25519FromPrivateJSON function - k.KeyId = keyObj.KeyId - k.KeyType = keyObj.KeyType - k.KeyIdHashAlgorithms = keyObj.KeyIdHashAlgorithms - k.KeyVal = keyObj.KeyVal - k.Scheme = keyObj.Scheme - return nil -} - -/* LoadEd25519PrivateKey loads an ed25519 private key file -and parses it via ParseEd25519FromPrivateJSON. -ParseEd25519FromPrivateKey does not support encrypted private keys yet. -The private key file has to be in the in-toto PrivateJSON format -For example: - - { - "keytype": "ed25519", - "scheme": "ed25519", - "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", - "keyid_hash_algorithms": ["sha256", "sha512"], - "keyval": - { - "public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", - "private": "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162" - } - } - -*/ -func (k *Key) LoadEd25519PrivateKey(path string) (err error) { - // TODO: Support for encrypted private Keys - // See also: https://github.com/secure-systems-lab/securesystemslib/blob/01a0c95af5f458235f96367922357958bfcf7b01/securesystemslib/keys.py#L1309 - keyFile, err := os.Open(path) - if err != nil { - return err - } - defer func() { - if closeErr := keyFile.Close(); closeErr != nil { - err = closeErr - } - }() - - keyBytes, err := ioutil.ReadAll(keyFile) - if err != nil { - return err - } - // contrary to LoadRSAPublicKey we use the returned key object - keyObj, err := ParseEd25519FromPrivateJSON(string(keyBytes)) - if err != nil { - return err - } - // I am not sure if there is a faster way to fill the Key struct - // without touching the ParseEd25519FromPrivateJSON function - k.KeyId = keyObj.KeyId - k.KeyType = keyObj.KeyType - k.KeyIdHashAlgorithms = keyObj.KeyIdHashAlgorithms - k.KeyVal = keyObj.KeyVal - k.Scheme = keyObj.Scheme - return nil -} From 65a734ff1460ed809c36032ffb925125d6360832 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 9 Jul 2020 21:14:50 +0200 Subject: [PATCH 28/86] cleanup tests Use the new LoadKey function for loading keys, this is not yet implemented for ed25519 keys, because our test keys are still in custom JSON format. This will be changed --- in_toto/examples_test.go | 2 +- in_toto/keylib_test.go | 6 +++--- in_toto/model_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/in_toto/examples_test.go b/in_toto/examples_test.go index 282c7c2f..9eb82697 100644 --- a/in_toto/examples_test.go +++ b/in_toto/examples_test.go @@ -23,7 +23,7 @@ func ExampleInTotoVerify() { // InTotoVerify. The layout represents the root of trust so it is a good // idea to sign it using multiple keys. var pubKey Key - err := pubKey.LoadRSAPublicKey(LayoutKeyPath) + err := pubKey.LoadKey(LayoutKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}) if err != nil { fmt.Printf("Unable to load public key: %s", err) } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index c2be9f99..9f49fcf8 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -99,14 +99,14 @@ func TestLoadRSAPublicKey(t *testing.T) { // Test loading error: // - Not a pem formatted rsa public key expectedError := "Could not find a public key PEM block" - err = key.LoadRSAPublicKey("demo.layout.template") + err = key.LoadKey("demo.layout.template", "rsassa-pss-sha256", []string{"sha256", "sha512"}) if err == nil || !strings.Contains(err.Error(), expectedError) { t.Errorf("LoadRSAPublicKey returned (%s), expected '%s' error", err, expectedError) } // Test not existing file - err = key.LoadRSAPublicKey("inToToRocks") + err = key.LoadKey("inToToRocks", "rsassa-pss-sha256", []string{"sha256", "sha512"}) if !errors.Is(err, os.ErrNotExist) { t.Errorf("Invalid file load returned (%s), expected '%s' error", err, os.ErrNotExist) } @@ -254,7 +254,7 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= // - Right key and data, but wrong signature // - Right key and data, but invalid signature var wrongKey Key - if err := wrongKey.LoadRSAPublicKey("alice.pub"); err != nil { + if err := wrongKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil { fmt.Printf("Unable to load key alice.pub: %s", err) } wrongSig := Signature{ diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 9095615a..8fd1abf0 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -249,7 +249,7 @@ func TestMetablockVerifySignature(t *testing.T) { // - wrong signature for key // - invalid metadata (can't canonicalize) var key Key - if err := key.LoadRSAPublicKey("alice.pub"); err != nil { + if err := key.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil { t.Errorf("Cannot load public key file: %s", err) } // Test missing key, bad signature and bad metadata From 5ee4dcaa1fbcda9f5f1d455a8456b3692096f888 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 13 Jul 2020 14:16:58 +0200 Subject: [PATCH 29/86] implement generic VerifySignature We now have a generic VerifySignature that automatically retrieves the key type based on the passed key. With this function we are now able to drop all tests that did RSA or ed25519 specific key operations --- in_toto/keylib.go | 259 +++-------------------------- in_toto/keylib_test.go | 362 +++++++++++++++++------------------------ in_toto/model.go | 6 +- in_toto/model_test.go | 132 +++++++-------- in_toto/runlib_test.go | 2 +- 5 files changed, 247 insertions(+), 514 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 0c781a03..c8a6f96e 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -7,14 +7,12 @@ import ( "crypto/sha256" "crypto/x509" "encoding/hex" - "encoding/json" "encoding/pem" "errors" "fmt" "golang.org/x/crypto/ed25519" "io/ioutil" "os" - "reflect" "strings" ) @@ -27,6 +25,9 @@ var ErrNoPEMBLock = errors.New("failed to decode the data as PEM block (are you // ErrUnsupportedKeyType is returned when we are dealing with a key type different to ed25519 or RSA var ErrUnsupportedKeyType = errors.New("unsupported key type") +// ErrInvalidSignature is returned when the signature is invalid +var ErrInvalidSignature = errors.New("invalid signature") + /* GenerateKeyId creates a partial key map and generates the key ID based on the created partial key map via the SHA256 method. @@ -213,57 +214,6 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) return nil } -/* -ParseRSAPublicKeyFromPEM parses the passed pemBytes as e.g. read from a PEM -formatted file, and instantiates and returns the corresponding RSA public key. -If no RSA public key can be parsed, the first return value is nil and the -second return value is the error. -*/ -func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) { - // TODO: There could be more key data in _, which we silently ignore here. - // Should we handle it / fail / say something about it? - data, _ := pem.Decode(pemBytes) - if data == nil { - return nil, ErrNoPEMBLock - } - - pub, err := x509.ParsePKIXPublicKey(data.Bytes) - if err != nil { - return nil, err - } - - //ParsePKIXPublicKey might return an rsa, dsa, or ecdsa public key - rsaPub, isRsa := pub.(*rsa.PublicKey) - if !isRsa { - return nil, fmt.Errorf("We currently only support rsa keys: got '%s'", - reflect.TypeOf(pub)) - } - - return rsaPub, nil -} - -/* -ParseRSAPrivateKeyFromPEM parses the passed pemBytes as e.g. read from a PEM -formatted file, and instantiates and returns the corresponding RSA Private key. -If no RSA Private key can be parsed, the first return value is nil and the -second return value is the error. -*/ -func ParseRSAPrivateKeyFromPEM(pemBytes []byte) (*rsa.PrivateKey, error) { - // TODO: There could be more key data in _, which we silently ignore here. - // Should we handle it / fail / say something about it? - data, _ := pem.Decode(pemBytes) - if data == nil { - return nil, ErrNoPEMBLock - } - - priv, err := x509.ParsePKCS1PrivateKey(data.Bytes) - if err != nil { - return nil, err - } - - return priv, nil -} - /* GenerateSignature will automatically detect the key type and sign the signable data with the provided key. If everything goes right GenerateSignature will return @@ -304,7 +254,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { return signature, err } case *ed25519.PrivateKey: - // TODO: implement signatures for ed25519 keys + signatureBuffer = ed25519.Sign(parsedKey.(ed25519.PrivateKey), signable) default: return signature, fmt.Errorf("%w: %T", ErrUnsupportedKeyType, parsedKey) } @@ -313,46 +263,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { return signature, nil } -/* -GenerateRSASignature generates a rsassa-pss signature, based -on the passed key and signable data. If something goes wrong -it will return an uninitialized Signature with an error. -If everything goes right, the function will return an initialized -signature with err=nil. -*/ -func GenerateRSASignature(signable []byte, key Key) (Signature, error) { - var signature Signature - keyReader := strings.NewReader(key.KeyVal.Private) - pemBytes, err := ioutil.ReadAll(keyReader) - if err != nil { - return signature, err - } - rsaPriv, err := ParseRSAPrivateKeyFromPEM(pemBytes) - if err != nil { - return signature, err - } - - hashed := sha256.Sum256(signable) - - // We use rand.Reader as secure random source for rsa.SignPSS() - signatureBuffer, err := rsa.SignPSS(rand.Reader, rsaPriv, crypto.SHA256, hashed[:], - &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) - if err != nil { - return signature, err - } - - signature.Sig = hex.EncodeToString(signatureBuffer) - signature.KeyId = key.KeyId - - return signature, nil -} - -/* -VerifyRSASignature uses the passed Key to verify the passed Signature over the -passed data. It returns an error if the key is not a valid RSA public key or -if the signature is not valid for the data. -*/ -func VerifyRSASignature(key Key, sig Signature, data []byte) error { +func VerifySignature(key Key, sig Signature, unverified []byte) error { // Create rsa.PublicKey object from DER encoded public key string as // found in the public part of the keyval part of a securesystemslib key dict keyReader := strings.NewReader(key.KeyVal.Public) @@ -360,150 +271,34 @@ func VerifyRSASignature(key Key, sig Signature, data []byte) error { if err != nil { return err } - rsaPub, err := ParseRSAPublicKeyFromPEM(pemBytes) - if err != nil { - return err + // TODO: There could be more key data in _, which we silently ignore here. + // Should we handle it / fail / say something about it? + data, _ := pem.Decode(pemBytes) + if data == nil { + return ErrNoPEMBLock } - - hashed := sha256.Sum256(data) - - // Create hex bytes from the signature hex string - sigHex, _ := hex.DecodeString(sig.Sig) - - // SecSysLib uses a SaltLength of `hashes.SHA256().digest_size`, i.e. 32 - if err := rsa.VerifyPSS(rsaPub, crypto.SHA256, hashed[:], sigHex, - &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}); err != nil { + parsedKey, err := ParseKey(data.Bytes) + if err != nil { return err } - - return nil -} - -/* -ParseEd25519FromPrivateJSON parses an ed25519 private key from the json string. -These ed25519 keys have the format as generated using in-toto-keygen: - - { - "keytype: "ed25519", - "scheme": "ed25519", - "keyid": ... - "keyid_hash_algorithms": [...] - "keyval": { - "public": "..." # 32 bytes - "private": "..." # 32 bytes + switch parsedKey.(type) { + case *rsa.PublicKey: + hashed := sha256.Sum256(unverified) + sigHex, _ := hex.DecodeString(sig.Sig) + err := rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigHex, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return fmt.Errorf("%w: %s", ErrInvalidSignature, err) } - } -*/ -func ParseEd25519FromPrivateJSON(JSONString string) (Key, error) { - var keyObj Key - err := json.Unmarshal([]uint8(JSONString), &keyObj) - if err != nil { - return keyObj, fmt.Errorf("this is not a valid JSON key object") - } - - if keyObj.KeyType != "ed25519" || keyObj.Scheme != "ed25519" { - return keyObj, fmt.Errorf("this doesn't appear to be an ed25519 key") - } - - // if the keyId is empty we try to generate the keyId - if keyObj.KeyId == "" { - if err := keyObj.GenerateKeyId(); err != nil { - return keyObj, err + case *ed25519.PublicKey: + sigHex, err := hex.DecodeString(sig.Sig) + if err != nil { + return err } - } - - if err := validatePrivateKey(keyObj); err != nil { - return keyObj, err - } - - // 64 hexadecimal digits => 32 bytes for the private portion of the key - if len(keyObj.KeyVal.Private) != 64 { - return keyObj, fmt.Errorf("the private field on this key is malformed") - } - - return keyObj, nil -} - -/* -ParseEd25519FromPublicJSON parses an ed25519 public key from the json string. -These ed25519 keys have the format as generated using in-toto-keygen: - - { - "keytype": "ed25519", - "scheme": "ed25519", - "keyid_hash_algorithms": [...], - "keyval": {"public": "..."} - } - -*/ -func ParseEd25519FromPublicJSON(JSONString string) (Key, error) { - var keyObj Key - err := json.Unmarshal([]uint8(JSONString), &keyObj) - if err != nil { - return keyObj, fmt.Errorf("this is not a valid JSON key object") - } - - if keyObj.KeyType != "ed25519" || keyObj.Scheme != "ed25519" { - return keyObj, fmt.Errorf("this doesn't appear to be an ed25519 key") - } - - // if the keyId is empty we try to generate the keyId - if keyObj.KeyId == "" { - if err := keyObj.GenerateKeyId(); err != nil { - return keyObj, err + if ok := ed25519.Verify(parsedKey.(ed25519.PublicKey), unverified, sigHex); !ok { + return fmt.Errorf("%w: ed25519", ErrInvalidSignature) } - } - - if err := validatePubKey(keyObj); err != nil { - return keyObj, err - } - - // 64 hexadecimal digits => 32 bytes for the public portion of the key - if len(keyObj.KeyVal.Public) != 64 { - return keyObj, fmt.Errorf("the public field on this key is malformed") - } - - return keyObj, nil -} - -/* -GenerateEd25519Signature creates an ed25519 signature using the key and the -signable buffer provided. It returns an error if the underlying signing library -fails. -*/ -func GenerateEd25519Signature(signable []byte, key Key) (Signature, error) { - - var signature Signature - - seed, err := hex.DecodeString(key.KeyVal.Private) - if err != nil { - return signature, err - } - privkey := ed25519.NewKeyFromSeed(seed) - signatureBuffer := ed25519.Sign(privkey, signable) - - signature.Sig = hex.EncodeToString(signatureBuffer) - signature.KeyId = key.KeyId - - return signature, nil -} - -/* -VerifyEd25519Signature uses the passed Key to verify the passed Signature over the -passed data. It returns an error if the key is not a valid ed25519 public key or -if the signature is not valid for the data. -*/ -func VerifyEd25519Signature(key Key, sig Signature, data []byte) error { - pubHex, err := hex.DecodeString(key.KeyVal.Public) - if err != nil { - return err - } - sigHex, err := hex.DecodeString(sig.Sig) - if err != nil { - return err - } - if ok := ed25519.Verify(pubHex, data, sigHex); !ok { - return errors.New("invalid ed25519 signature") + default: + return fmt.Errorf("%w: Key has type %T", ErrInvalidSignature, parsedKey) } return nil } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 9f49fcf8..696e1eb8 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -1,87 +1,85 @@ package in_toto import ( - "encoding/hex" "errors" "fmt" "os" - "strings" "testing" ) -func TestParseRSAPublicKeyFromPEM(t *testing.T) { - // Test parsing errors: - // - Missing pem headers, - // - Missing pem body - // - Not an rsa key - invalidRSA := []string{ - "not a PEM block", - `-----BEGIN PUBLIC KEY----- - ------END PUBLIC KEY-----`, - `-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESkhkURrGhKzC8IyJTP1H3QCVi4CU -z5OxbcSn3IR+/9W02DOVayQHTnMlBc1SoStYMvbGwnPraQuh6t+U/NBHYQ== ------END PUBLIC KEY-----`, - } - expectedErrors := []string{ - "Could not find a public key PEM block", - "truncated", - "only support rsa", - } - - for i := 0; i < len(invalidRSA); i++ { - result, err := ParseRSAPublicKeyFromPEM([]byte(invalidRSA[i])) - if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { - t.Errorf("ParseRSAPublicKeyFromPEM returned (%p, %s), expected '%s'"+ - " error", result, err, expectedErrors[i]) - } - } - - // Test parsing valid public rsa key from PEM bytes - validRSA := `-----BEGIN PUBLIC KEY----- -MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxPX3kFs/z645x4UOC3KF -Y3V80YQtKrp6YS3qU+Jlvx/XzK53lb4sCDRU9jqBBx3We45TmFUibroMd8tQXCUS -e8gYCBUBqBmmz0dEHJYbW0tYF7IoapMIxhRYn76YqNdl1JoRTcmzIaOJ7QrHxQrS -GpivvTm6kQ9WLeApG1GLYJ3C3Wl4bnsI1bKSv55Zi45/JawHzTzYUAIXX9qCd3Io -HzDucz9IAj9Ookw0va/q9FjoPGrRB80IReVxLVnbo6pYJfu/O37jvEobHFa8ckHd -YxUIg8wvkIOy1O3M74lBDm6CVI0ZO25xPlDB/4nHAE1PbA3aF3lw8JGuxLDsetxm -fzgAleVt4vXLQiCrZaLf+0cM97JcT7wdHcbIvRLsij9LNP+2tWZgeZ/hIAOEdaDq -cYANPDIAxfTvbe9I0sXrCtrLer1SS7GqUmdFCdkdun8erXdNF0ls9Rp4cbYhjdf3 -yMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE= ------END PUBLIC KEY-----` - result, err := ParseRSAPublicKeyFromPEM([]byte(validRSA)) - if err != nil { - t.Errorf("ParseRSAPublicKeyFromPEM returned (%p, %s), expected no error", - result, err) - } -} - -func TestParseRSAPrivateKeyFromPEM(t *testing.T) { - // Test parsing errors: - // - Missing pem headers, - // - Missing pem body - // We only support RSA private keys, therefore we don't need to check for other keys. - // Other keys should fail at ParsePKCS1 stage already. - invalidRSA := []string{ - "not a PEM block", - `-----BEGIN PRIVATE KEY----- - ------END PRIVATE KEY-----`, - } - expectedErrors := []string{ - "Could not find a private key PEM block", - "truncated", - } - - for i := 0; i < len(invalidRSA); i++ { - result, err := ParseRSAPrivateKeyFromPEM([]byte(invalidRSA[i])) - if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { - t.Errorf("ParseRSAPrivateKeyFromPEM returned (%p, %s), expected '%s'"+ - " error", result, err, expectedErrors[i]) - } - } -} +//func TestParseRSAPublicKeyFromPEM(t *testing.T) { +// // Test parsing errors: +// // - Missing pem headers, +// // - Missing pem body +// // - Not an rsa key +// invalidRSA := []string{ +// "not a PEM block", +// `-----BEGIN PUBLIC KEY----- +// +//-----END PUBLIC KEY-----`, +// `-----BEGIN PUBLIC KEY----- +//MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESkhkURrGhKzC8IyJTP1H3QCVi4CU +//z5OxbcSn3IR+/9W02DOVayQHTnMlBc1SoStYMvbGwnPraQuh6t+U/NBHYQ== +//-----END PUBLIC KEY-----`, +// } +// expectedErrors := []string{ +// "Could not find a public key PEM block", +// "truncated", +// "only support rsa", +// } +// +// for i := 0; i < len(invalidRSA); i++ { +// result, err := ParseRSAPublicKeyFromPEM([]byte(invalidRSA[i])) +// if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { +// t.Errorf("ParseRSAPublicKeyFromPEM returned (%p, %s), expected '%s'"+ +// " error", result, err, expectedErrors[i]) +// } +// } +// +// // Test parsing valid public rsa key from PEM bytes +// validRSA := `-----BEGIN PUBLIC KEY----- +//MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxPX3kFs/z645x4UOC3KF +//Y3V80YQtKrp6YS3qU+Jlvx/XzK53lb4sCDRU9jqBBx3We45TmFUibroMd8tQXCUS +//e8gYCBUBqBmmz0dEHJYbW0tYF7IoapMIxhRYn76YqNdl1JoRTcmzIaOJ7QrHxQrS +//GpivvTm6kQ9WLeApG1GLYJ3C3Wl4bnsI1bKSv55Zi45/JawHzTzYUAIXX9qCd3Io +//HzDucz9IAj9Ookw0va/q9FjoPGrRB80IReVxLVnbo6pYJfu/O37jvEobHFa8ckHd +//YxUIg8wvkIOy1O3M74lBDm6CVI0ZO25xPlDB/4nHAE1PbA3aF3lw8JGuxLDsetxm +//fzgAleVt4vXLQiCrZaLf+0cM97JcT7wdHcbIvRLsij9LNP+2tWZgeZ/hIAOEdaDq +//cYANPDIAxfTvbe9I0sXrCtrLer1SS7GqUmdFCdkdun8erXdNF0ls9Rp4cbYhjdf3 +//yMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE= +//-----END PUBLIC KEY-----` +// result, err := ParseRSAPublicKeyFromPEM([]byte(validRSA)) +// if err != nil { +// t.Errorf("ParseRSAPublicKeyFromPEM returned (%p, %s), expected no error", +// result, err) +// } +//} +// +//func TestParseRSAPrivateKeyFromPEM(t *testing.T) { +// // Test parsing errors: +// // - Missing pem headers, +// // - Missing pem body +// // We only support RSA private keys, therefore we don't need to check for other keys. +// // Other keys should fail at ParsePKCS1 stage already. +// invalidRSA := []string{ +// "not a PEM block", +// `-----BEGIN PRIVATE KEY----- +// +//-----END PRIVATE KEY-----`, +// } +// expectedErrors := []string{ +// "Could not find a private key PEM block", +// "truncated", +// } +// +// for i := 0; i < len(invalidRSA); i++ { +// result, err := ParseRSAPrivateKeyFromPEM([]byte(invalidRSA[i])) +// if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { +// t.Errorf("ParseRSAPrivateKeyFromPEM returned (%p, %s), expected '%s'"+ +// " error", result, err, expectedErrors[i]) +// } +// } +//} func TestLoadRSAPublicKey(t *testing.T) { // Test loading valid public rsa key from pem-formatted file @@ -100,7 +98,7 @@ func TestLoadRSAPublicKey(t *testing.T) { // - Not a pem formatted rsa public key expectedError := "Could not find a public key PEM block" err = key.LoadKey("demo.layout.template", "rsassa-pss-sha256", []string{"sha256", "sha512"}) - if err == nil || !strings.Contains(err.Error(), expectedError) { + if !errors.Is(err, ErrNoPEMBLock) { t.Errorf("LoadRSAPublicKey returned (%s), expected '%s' error", err, expectedError) } @@ -196,11 +194,11 @@ lQqaoEO7ScdRrzjgvVxXkEY3nwLcWdM61/RZTL0+be8goDw5cWt+PaA= } // We are not verifying the signature yet.. validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` - validSig, err := GenerateRSASignature([]byte(validData), validKey) + validSig, err := GenerateSignature([]byte(validData), validKey) if err != nil { t.Errorf("GenerateRSASignature from validKey and data failed: %s", err) } - if err := VerifyRSASignature(validKey, validSig, []byte(validData)); err != nil { + if err := VerifySignature(validKey, validSig, []byte(validData)); err != nil { t.Errorf("VerifyRSASignature from validSignature and data has failed: %s", err) } @@ -242,7 +240,7 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` // Test verifying valid signature - err := VerifyRSASignature(validKey, validSig, []byte(validData)) + err := VerifySignature(validKey, validSig, []byte(validData)) if err != nil { t.Errorf("VerifyRSASignature returned '%s', expected nil", err) } @@ -267,123 +265,83 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= data := []string{"bad data", validData, validData, validData, validData} for i := 0; i < len(sigs); i++ { - err := VerifyRSASignature(keys[i], sigs[i], []byte(data[i])) + err := VerifySignature(keys[i], sigs[i], []byte(data[i])) if err == nil { t.Errorf("VerifyRSASignature returned '%s', expected error", err) } } } -func TestParseEd25519FromPrivateJSON(t *testing.T) { - // Test parsing errors: - // - Not JSON, - // - Missing private field - // - private field is the wrong length - // - scheme and keytype are not ed25519 - invalidKey := []string{ - "not a json", - `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": ""}}`, - `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73"}}`, - `{"keytype": "25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73f8ed630f99d8eda250421f0e3a6123fd5c311cc001bda49"}}`, - `{"keytype": "ed25519", "scheme": "cd25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73f8ed630f99d8eda250421f0e3a6123fd5c311cc001bda49"}}`, - } - - expectedErrors := []string{ - "this is not a valid JSON key object", - "in key '308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64': private key cannot be empty", - "the private field on this key is malformed", - "this doesn't appear to be an ed25519 key", - "this doesn't appear to be an ed25519 key", - } - - for i := 0; i < len(invalidKey); i++ { - _, err := ParseEd25519FromPrivateJSON(invalidKey[i]) - if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { - t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected '%s'"+ - " error", err, expectedErrors[i]) - } - } - - // Generated through in-toto run 0.4.1 and thus it should be a happy key - validKey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73f8ed630f99d8eda250421f0e3a6123fd5c311cc001bda49"}}` - _, err := ParseEd25519FromPrivateJSON(validKey) - if err != nil { - t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", - err) - } - -} - -func TestGenerateEd25519Signature(t *testing.T) { - // let's load a key in memory here first - validKey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73f8ed630f99d8eda250421f0e3a6123fd5c311cc001bda49"}}` - key, err := ParseEd25519FromPrivateJSON(validKey) - if err != nil { - t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", - err) - } - - signature, err := GenerateEd25519Signature([]uint8("ohmywhatatest"), key) - if err != nil { - t.Errorf("GenerateEd25519Signature shouldn't have returned an error (%s)", - err) - } - - // validate correct signature - err = VerifyEd25519Signature(key, signature, []uint8("ohmywhatatest")) - if err != nil { - t.Errorf("VerifyEd25519Signature shouldn't have returned an error (%s)", err) - } - - //validate incorrect signature - var incorrectSig Signature - incorrectSig.Sig = "e8912b58f47ae04a65d7437e3c82eb361f82d952" - err = VerifyEd25519Signature(key, incorrectSig, []uint8("ohmywhatatest")) - if err == nil { - t.Errorf("Given signature is valid, but should be invalid") - } - - // validate InvalidByte signature - var malformedSig Signature - malformedSig.Sig = "InTotoRocks" - err = VerifyEd25519Signature(key, malformedSig, []uint8("ohmywhatatest")) - // use type conversion for checking for hex.InvalidByteError - var invalidByteError hex.InvalidByteError - if !errors.As(err, &invalidByteError) { - t.Errorf("We received %s, but we should get: invalid byte error", err) - } - - // validate invalidLength signature - // the following signature is too short - var invLengthSig Signature - invLengthSig.Sig = "e8912b58f47ae04a65d74" - err = VerifyEd25519Signature(key, invLengthSig, []uint8("ohmywhatatest")) - if !errors.Is(err, hex.ErrLength) { - t.Errorf("We received %s, but we should get: %s", err, hex.ErrLength) - } - - // validate invalidKey - wrongKey := key - wrongKey.KeyVal.Public = "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7" - err = VerifyEd25519Signature(wrongKey, signature, []uint8("ohmywhatatest")) - if err == nil { - t.Errorf("The invalid testKey passed the signature test, this should not happen") - } - - if signature.KeyId != key.KeyId { - t.Errorf("GenerateEd25519Signature should've returned matching keyids!") - } - - // ed25519 signatures should be 64 bytes long => 128 hex digits - if len(signature.Sig) != 128 { - t.Errorf("GenerateEd25519Signature should've returned a 32 byte signature! %s", - signature.Sig) - } -} +//func TestGenerateEd25519Signature(t *testing.T) { +// // let's load a key in memory here first +// validKey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73f8ed630f99d8eda250421f0e3a6123fd5c311cc001bda49"}}` +// key, err := ParseEd25519FromPrivateJSON(validKey) +// if err != nil { +// t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", +// err) +// } +// +// signature, err := GenerateEd25519Signature([]uint8("ohmywhatatest"), key) +// if err != nil { +// t.Errorf("GenerateEd25519Signature shouldn't have returned an error (%s)", +// err) +// } +// +// // validate correct signature +// err = VerifyEd25519Signature(key, signature, []uint8("ohmywhatatest")) +// if err != nil { +// t.Errorf("VerifyEd25519Signature shouldn't have returned an error (%s)", err) +// } +// +// //validate incorrect signature +// var incorrectSig Signature +// incorrectSig.Sig = "e8912b58f47ae04a65d7437e3c82eb361f82d952" +// err = VerifyEd25519Signature(key, incorrectSig, []uint8("ohmywhatatest")) +// if err == nil { +// t.Errorf("Given signature is valid, but should be invalid") +// } +// +// // validate InvalidByte signature +// var malformedSig Signature +// malformedSig.Sig = "InTotoRocks" +// err = VerifyEd25519Signature(key, malformedSig, []uint8("ohmywhatatest")) +// // use type conversion for checking for hex.InvalidByteError +// var invalidByteError hex.InvalidByteError +// if !errors.As(err, &invalidByteError) { +// t.Errorf("We received %s, but we should get: invalid byte error", err) +// } +// +// // validate invalidLength signature +// // the following signature is too short +// var invLengthSig Signature +// invLengthSig.Sig = "e8912b58f47ae04a65d74" +// err = VerifyEd25519Signature(key, invLengthSig, []uint8("ohmywhatatest")) +// if !errors.Is(err, hex.ErrLength) { +// t.Errorf("We received %s, but we should get: %s", err, hex.ErrLength) +// } +// +// // validate invalidKey +// wrongKey := key +// wrongKey.KeyVal.Public = "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7" +// err = VerifyEd25519Signature(wrongKey, signature, []uint8("ohmywhatatest")) +// if err == nil { +// t.Errorf("The invalid testKey passed the signature test, this should not happen") +// } +// +// if signature.KeyId != key.KeyId { +// t.Errorf("GenerateEd25519Signature should've returned matching keyids!") +// } +// +// // ed25519 signatures should be 64 bytes long => 128 hex digits +// if len(signature.Sig) != 128 { +// t.Errorf("GenerateEd25519Signature should've returned a 32 byte signature! %s", +// signature.Sig) +// } +//} func TestLoad25519PublicKey(t *testing.T) { var key Key - if err := key.LoadEd25519PublicKey("carol.pub"); err != nil { + if err := key.LoadKey("carol.pub", "ed25519", []string{"sha256", "sha512"}); err != nil { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) } @@ -393,19 +351,19 @@ func TestLoad25519PublicKey(t *testing.T) { } // try to load nonexistent file - if err := key.LoadEd25519PublicKey("this-does-not-exist"); err == nil { + if err := key.LoadKey("this-does-not-exist", "ed25519", []string{"sha256", "sha512"}); err == nil { t.Errorf("LoadEd25519PublicKey loaded a file that does not exist") } // load invalid file - if err := key.LoadEd25519PublicKey("bob-invalid.pub"); err == nil { + if err := key.LoadKey("bob-invalid.pub", "ed25519", []string{"sha256", "sha512"}); err == nil { t.Errorf("LoadEd25519PublicKey has successfully loaded an invalid key file") } } func TestLoad25519PrivateKey(t *testing.T) { var key Key - if err := key.LoadEd25519PrivateKey("carol"); err != nil { + if err := key.LoadKey("carol", "ed25519", []string{"sha256", "sha512"}); err != nil { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) } @@ -415,32 +373,12 @@ func TestLoad25519PrivateKey(t *testing.T) { } // try to load nonexistent file - if err := key.LoadEd25519PrivateKey("this-does-not-exist"); err == nil { + if err := key.LoadKey("this-does-not-exist", "ed25519", []string{"sha256", "sha512"}); err == nil { t.Errorf("LoadEd25519PublicKey loaded a file that does not exist") } // load invalid file - if err := key.LoadEd25519PrivateKey("bob-invalid.pub"); err == nil { + if err := key.LoadKey("bob-invalid.pub", "ed25519", []string{"sha256", "sha512"}); err == nil { t.Errorf("LoadEd25519PublicKey has successfully loaded an invalid key file") } } - -func TestParseEd25519FromPublicJSON(t *testing.T) { - tables := []struct { - invalidKey string - expectedError string - }{ - {"not a json", "this is not a valid JSON key object"}, - {`{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", "private": "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162"}}`, "private key found"}, - {`{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64"}}`, "the public field on this key is malformed"}, - {`{"keytype": "25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}}`, "this doesn't appear to be an ed25519 key"}, - {`{"keytype": "ed25519", "scheme": "ec25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}}}`, "this is not a valid JSON key object"}, - } - - for _, table := range tables { - _, err := ParseEd25519FromPublicJSON(table.invalidKey) - if err == nil || !strings.Contains(err.Error(), table.expectedError) { - t.Errorf("ParseEd25519FromPublicJSON returned (%s), expected '%s'", err, table.expectedError) - } - } -} diff --git a/in_toto/model.go b/in_toto/model.go index 867ca1b2..25f694f1 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -606,7 +606,7 @@ func (mb *Metablock) VerifySignature(key Key) error { return err } - if err := VerifyRSASignature(key, sig, dataCanonical); err != nil { + if err := VerifySignature(key, sig, dataCanonical); err != nil { return err } return nil @@ -657,12 +657,12 @@ func (mb *Metablock) Sign(key Key) error { // (also, lolnogenerics) switch key.Scheme { case "ed25519": - newSignature, err = GenerateEd25519Signature(dataCanonical, key) + newSignature, err = GenerateSignature(dataCanonical, key) if err != nil { return err } case "rsassa-pss-sha256": - newSignature, err = GenerateRSASignature(dataCanonical, key) + newSignature, err = GenerateSignature(dataCanonical, key) if err != nil { return err } diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 8fd1abf0..14e69085 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -1187,69 +1187,69 @@ func TestMetablockSignWithRSA(t *testing.T) { } } -func TestMetablockSignWithEd25519(t *testing.T) { - // Test metablock signing (with ed25519) - // - Pass non-ed25519 key - // - Pass malformed ed25519 key - // - Pass unsupported invalid key type - // - Pass an ed25519 key and expect a signature back - var key Key - var mb Metablock - if err := mb.Load("demo.layout.template"); err != nil { - t.Errorf("Cannot parse template file: %s", err) - } - - pubkey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": ""}}` - - badkey, err := ParseEd25519FromPrivateJSON(pubkey) - if err == nil || !strings.Contains(err.Error(), "private key cannot be empty") { - t.Errorf("Metablock.Sign returned (%s), expected it to claim this "+ - "key is not a private key", err) - - } - - validKey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73f8ed630f99d8eda250421f0e3a6123fd5c311cc001bda49"}}` - - // Trigger error in Sign/GenerateEd25519Signature with malformed key data - badkey, err = ParseEd25519FromPrivateJSON(validKey) - // make sure to only set badkey, if prior operation has been successful - if err == nil { - badkey.KeyVal.Private = "xyz" - } - err = mb.Sign(badkey) - if err == nil || !strings.Contains(err.Error(), "invalid byte") { - t.Errorf("Metablock.Sign returned (%s), expected 'invalid byte' error ", - err) - } - - badkey, err = ParseEd25519FromPrivateJSON(validKey) - if err != nil { - t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", - err) - } - - badkey.Scheme = "ecdsa" - err = mb.Sign(badkey) - if err == nil || !strings.Contains(err.Error(), "not supported") { - t.Errorf("Metablock.Sign returned (%s), expected it to claim this "+ - "key type/scheme is unsupported", err) - } - - key, err = ParseEd25519FromPrivateJSON(validKey) - if err != nil { - t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", - err) - } - - err = mb.Sign(key) - if err != nil { - t.Errorf("Metablock.Sign returned (%s), expected no error", err) - } - - // note, there's a 2 because this template is already signed hehe - if len(mb.Signatures) != 2 { - t.Errorf("Expected a new signature to be appended, but got (%+v) (%d)", - mb.Signatures, len(mb.Signatures)) - } - -} +//func TestMetablockSignWithEd25519(t *testing.T) { +// // Test metablock signing (with ed25519) +// // - Pass non-ed25519 key +// // - Pass malformed ed25519 key +// // - Pass unsupported invalid key type +// // - Pass an ed25519 key and expect a signature back +// var key Key +// var mb Metablock +// if err := mb.Load("demo.layout.template"); err != nil { +// t.Errorf("Cannot parse template file: %s", err) +// } +// +// pubkey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": ""}}` +// +// badkey, err := ParseEd25519FromPrivateJSON(pubkey) +// if err == nil || !strings.Contains(err.Error(), "private key cannot be empty") { +// t.Errorf("Metablock.Sign returned (%s), expected it to claim this "+ +// "key is not a private key", err) +// +// } +// +// validKey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73f8ed630f99d8eda250421f0e3a6123fd5c311cc001bda49"}}` +// +// // Trigger error in Sign/GenerateEd25519Signature with malformed key data +// badkey, err = ParseEd25519FromPrivateJSON(validKey) +// // make sure to only set badkey, if prior operation has been successful +// if err == nil { +// badkey.KeyVal.Private = "xyz" +// } +// err = mb.Sign(badkey) +// if err == nil || !strings.Contains(err.Error(), "invalid byte") { +// t.Errorf("Metablock.Sign returned (%s), expected 'invalid byte' error ", +// err) +// } +// +// badkey, err = ParseEd25519FromPrivateJSON(validKey) +// if err != nil { +// t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", +// err) +// } +// +// badkey.Scheme = "ecdsa" +// err = mb.Sign(badkey) +// if err == nil || !strings.Contains(err.Error(), "not supported") { +// t.Errorf("Metablock.Sign returned (%s), expected it to claim this "+ +// "key type/scheme is unsupported", err) +// } +// +// key, err = ParseEd25519FromPrivateJSON(validKey) +// if err != nil { +// t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", +// err) +// } +// +// err = mb.Sign(key) +// if err != nil { +// t.Errorf("Metablock.Sign returned (%s), expected no error", err) +// } +// +// // note, there's a 2 because this template is already signed hehe +// if len(mb.Signatures) != 2 { +// t.Errorf("Expected a new signature to be appended, but got (%+v) (%d)", +// mb.Signatures, len(mb.Signatures)) +// } +// +//} diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index 823047a3..a7419ed2 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -286,7 +286,7 @@ func TestInTotoRun(t *testing.T) { linkName := "Name" var validKey Key - if err := validKey.LoadEd25519PrivateKey("carol"); err != nil { + if err := validKey.LoadKey("carol", "ed25519", []string{"sha256", "sha512"}); err != nil { t.Error(err) } From 7b2d7fa6e16be950ce5bb8b98172f3e1b8d462ee Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 13 Jul 2020 14:46:38 +0200 Subject: [PATCH 30/86] add valid ed25519 PEM key testdata This fixes a few ed25519 tests by adding valid ed25519 keys encoded as PEM (ASN.1 DER) Blocks. --- in_toto/keylib.go | 6 +++--- in_toto/keylib_test.go | 8 ++++---- test/data/carol | 4 +++- test/data/carol.pub | 4 +++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index c8a6f96e..16fd7883 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -196,12 +196,12 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) if err := k.SetKeyComponents(pubKeyBytes, pemBytes, "rsa", scheme, keyIdHashAlgorithms); err != nil { return err } - case *ed25519.PublicKey: + case ed25519.PublicKey: if err := k.SetKeyComponents(pemBytes, []byte{}, "ed25519", scheme, keyIdHashAlgorithms); err != nil { return err } - case *ed25519.PrivateKey: - pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*ed25519.PrivateKey).Public()) + case ed25519.PrivateKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(ed25519.PrivateKey).Public()) if err != nil { return err } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 696e1eb8..8fd51c20 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -345,8 +345,8 @@ func TestLoad25519PublicKey(t *testing.T) { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) } - expectedPubKey := "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037" - if expectedPubKey != key.KeyVal.Public { + expectedPubKey := "ad238d901104293b031d95f516dc00c68b07c9b7a66b2f2c871dc71a6aae0e46" + if expectedPubKey != key.KeyId { t.Errorf("Loaded pubkey is not the expected key") } @@ -367,8 +367,8 @@ func TestLoad25519PrivateKey(t *testing.T) { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) } - expectedPrivateKey := "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162" - if expectedPrivateKey != key.KeyVal.Private { + expectedPrivateKey := "ad238d901104293b031d95f516dc00c68b07c9b7a66b2f2c871dc71a6aae0e46" + if expectedPrivateKey != key.KeyId { t.Errorf("Loaded pubkey is not the expected key") } diff --git a/test/data/carol b/test/data/carol index 0d1d10b7..58024f56 100644 --- a/test/data/carol +++ b/test/data/carol @@ -1 +1,3 @@ -{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", "private": "4cedf4d3369f8c83af472d0d329aedaa86265b74efb74b708f6a1ed23f290162"}} \ No newline at end of file +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu +-----END PRIVATE KEY----- diff --git a/test/data/carol.pub b/test/data/carol.pub index 1af4d653..4309d6a0 100644 --- a/test/data/carol.pub +++ b/test/data/carol.pub @@ -1 +1,3 @@ -{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037"}} \ No newline at end of file +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAOT5nGyAPlkxJCD00qGf12YnsHGnfe2Z1j+RxyFkbE5w= +-----END PUBLIC KEY----- From 885c74311721c2c4ead5a6ad0329344cc70e61ff Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 13 Jul 2020 14:59:02 +0200 Subject: [PATCH 31/86] fix InTotoRun + fix test with new signature Our generic ParseKey function returns an interface on ed25519.PrivateKey *not* on *ed25519.PrivateKey, therefore we have to use the right one. I've also modified the test data, because we have generated a new ed25519 key. Therefore our ID and signature didn't match anymore --- in_toto/keylib.go | 2 +- in_toto/runlib_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 16fd7883..9cb7fe5f 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -253,7 +253,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { if err != nil { return signature, err } - case *ed25519.PrivateKey: + case ed25519.PrivateKey: signatureBuffer = ed25519.Sign(parsedKey.(ed25519.PrivateKey), signable) default: return signature, fmt.Errorf("%w: %T", ErrUnsupportedKeyType, parsedKey) diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index a7419ed2..a4821515 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -318,8 +318,8 @@ func TestInTotoRun(t *testing.T) { Environment: map[string]interface{}{}, }, Signatures: []Signature{{ - KeyId: "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", - Sig: "ce7f736866cee27e58544ad9daee88e211376b29451619f50a4d16abd796931b1bbb4b78dd120290998155bf5db458c4f7a768d26b39f70efeb74ac634625b0b", + KeyId: "ad238d901104293b031d95f516dc00c68b07c9b7a66b2f2c871dc71a6aae0e46", + Sig: "4e2af0ae36ad51aba2a9a0dc9e1864ab3fe5f3ec4f4de9c958d6648963999a99a5f663282a415b237f5d3e53972cf7f21151b65eb189f9c9add5eaf409455209", }}, }, }, From ec46fd02509f6288fd4fe10c2826e2a7db780217 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 13 Jul 2020 15:02:01 +0200 Subject: [PATCH 32/86] remove bob test data We have changed our on-disk key format. So the bob test data is no longer necessary --- test/data/bob | 1 - test/data/bob-invalid.pub | 1 - test/data/bob.pub | 1 - 3 files changed, 3 deletions(-) delete mode 100644 test/data/bob delete mode 100644 test/data/bob-invalid.pub delete mode 100644 test/data/bob.pub diff --git a/test/data/bob b/test/data/bob deleted file mode 100644 index c6672ab8..00000000 --- a/test/data/bob +++ /dev/null @@ -1 +0,0 @@ -abea63039d8e147ed1fe99edca11c2cb@@@@100000@@@@8e5b3a3a121a6f73824fd05e33cc9c3eca4d7e2b0a66402133434b48e8f125d4@@@@c631a0e172cf014f8fbcf8a16471f279@@@@6da6c4a134c42e89f1aec309263d057e46d7eae21e214a34603541a75d0a119f62cef4bb541a590e60a71b515eee172523e5160e02597d0a6f62e9f316d444b69445d20f46fe431d3feb095d96c329821a0272ec005fedc3fa74e9def46f051aa6a08a8645a33b4fee75663daff8da0a8daa81febf09d6290fbf9de393cd32d165f372a1af2699c3e14e9a1333e62901cc874fc35ec6e01940fd04ca7312b3f0ea65896933d9221c4a291d59412a299d834fbbf8e30c6e0ad5b415c14ecae9dbac46d31438abfd1c2e3c546daf548a2621dccc6e4e451a67f178a9153c6049b0591460811102458961e854f8f27491c459f6d48bda7e124a254033d898f7bcc7da6c780b604ed6534c70be3828382e81ce2b9b469ebccc98e9e0646f4ff142479712338f6910c98f41c93c7561fb0c259b6852fab94217d30ef1ab83c3e373f6f580fcbc161359f46931e3a068648ff9 \ No newline at end of file diff --git a/test/data/bob-invalid.pub b/test/data/bob-invalid.pub deleted file mode 100644 index 824f4ffa..00000000 --- a/test/data/bob-invalid.pub +++ /dev/null @@ -1 +0,0 @@ -{"keytype": "25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}} \ No newline at end of file diff --git a/test/data/bob.pub b/test/data/bob.pub deleted file mode 100644 index 3b4d580b..00000000 --- a/test/data/bob.pub +++ /dev/null @@ -1 +0,0 @@ -{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7aac70"}} \ No newline at end of file From 81f089465a09334cff0d74a899b15b1c52a8947d Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 13 Jul 2020 19:21:29 +0200 Subject: [PATCH 33/86] store ed25519 keys as hex encoded strings For interoperability with the securesystemslib and the in-toto python implementation we are defining an exception for the ed25519 key and loading it hex encoded as string directly into memory. For this we need to read the ed25519 key from the PEM on-disk format and operate directly on the ed25519 key object --- in_toto/keylib.go | 145 ++++++++++++++++++++++++----------------- in_toto/keylib_test.go | 10 +-- in_toto/runlib_test.go | 2 +- 3 files changed, 93 insertions(+), 64 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 9cb7fe5f..50dab9d3 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -81,16 +81,35 @@ SetKeyComponents sets all components in our key object. Furthermore it makes sure to remove any trailing and leading whitespaces or newlines. */ func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyType string, scheme string, keyIdHashAlgorithms []string) error { - if len(privateKeyBytes) > 0 { - // assume we have a privateKey - k.KeyVal = KeyVal{ - Private: strings.TrimSpace(string(privateKeyBytes)), - Public: strings.TrimSpace(string(GeneratePublicPemBlock(pubKeyBytes))), + // assume we have a privateKey if the key size is bigger than 0 + switch keyType { + case "rsa": + // We need to treat RSA differently, because of interoperability + // reasons with the securesystemslib and the in-toto python + // implementation + if len(privateKeyBytes) > 0 { + k.KeyVal = KeyVal{ + Private: strings.TrimSpace(string(privateKeyBytes)), + Public: strings.TrimSpace(string(GeneratePublicPemBlock(pubKeyBytes))), + } + } else { + k.KeyVal = KeyVal{ + Public: strings.TrimSpace(string(pubKeyBytes)), + } } - } else { - k.KeyVal = KeyVal{ - Public: strings.TrimSpace(string(pubKeyBytes)), + case "ed25519": + if len(privateKeyBytes) > 0 { + k.KeyVal = KeyVal{ + Private: strings.TrimSpace(hex.EncodeToString(privateKeyBytes)), + Public: strings.TrimSpace(hex.EncodeToString(pubKeyBytes)), + } + } else { + k.KeyVal = KeyVal{ + Public: strings.TrimSpace(hex.EncodeToString(pubKeyBytes)), + } } + default: + return fmt.Errorf("%w: %s", ErrUnsupportedKeyType, keyType) } k.KeyType = keyType k.Scheme = scheme @@ -197,15 +216,12 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) return err } case ed25519.PublicKey: - if err := k.SetKeyComponents(pemBytes, []byte{}, "ed25519", scheme, keyIdHashAlgorithms); err != nil { + if err := k.SetKeyComponents(key.(ed25519.PublicKey), []byte{}, "ed25519", scheme, keyIdHashAlgorithms); err != nil { return err } case ed25519.PrivateKey: - pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(ed25519.PrivateKey).Public()) - if err != nil { - return err - } - if err := k.SetKeyComponents(pubKeyBytes, pemBytes, "ed25519", scheme, keyIdHashAlgorithms); err != nil { + pubKeyBytes := key.(ed25519.PrivateKey).Public() + if err := k.SetKeyComponents(pubKeyBytes.(ed25519.PublicKey), key.(ed25519.PrivateKey), "ed25519", scheme, keyIdHashAlgorithms); err != nil { return err } default: @@ -226,26 +242,27 @@ return an not initialized signature and an error. Possible errors are: */ func GenerateSignature(signable []byte, key Key) (Signature, error) { var signature Signature - keyReader := strings.NewReader(key.KeyVal.Private) - pemBytes, err := ioutil.ReadAll(keyReader) - if err != nil { - return signature, err - } - // TODO: There could be more key data in _, which we silently ignore here. - // Should we handle it / fail / say something about it? - data, _ := pem.Decode(pemBytes) - if data == nil { - return signature, ErrNoPEMBLock - } - parsedKey, err := ParseKey(data.Bytes) - if err != nil { - return signature, err - } - var signatureBuffer []byte - // Go type switch for interfering the key type - switch parsedKey.(type) { - case *rsa.PrivateKey: + // The following switch block is needed for keeping interoperability + // with the securesystemslib and the python implementation + // in which we are storing RSA keys in PEM format, but ed25519 keys hex encoded. + switch key.KeyType { + case "rsa": + keyReader := strings.NewReader(key.KeyVal.Private) + pemBytes, err := ioutil.ReadAll(keyReader) + if err != nil { + return signature, err + } + // TODO: There could be more key data in _, which we silently ignore here. + // Should we handle it / fail / say something about it? + data, _ := pem.Decode(pemBytes) + if data == nil { + return signature, ErrNoPEMBLock + } + parsedKey, err := ParseKey(data.Bytes) + if err != nil { + return signature, err + } hashed := sha256.Sum256(signable) // We use rand.Reader as secure random source for rsa.SignPSS() signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], @@ -253,10 +270,16 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { if err != nil { return signature, err } - case ed25519.PrivateKey: - signatureBuffer = ed25519.Sign(parsedKey.(ed25519.PrivateKey), signable) + case "ed25519": + seed, err := hex.DecodeString(key.KeyVal.Private) + if err != nil { + return signature, err + } + // Note: We can directly use the key for signing and do not + // need to use ed25519.NewKeyFromSeed(). + signatureBuffer = ed25519.Sign(seed, signable) default: - return signature, fmt.Errorf("%w: %T", ErrUnsupportedKeyType, parsedKey) + return signature, fmt.Errorf("%w: %s", ErrUnsupportedKeyType, key.KeyType) } signature.Sig = hex.EncodeToString(signatureBuffer) signature.KeyId = key.KeyId @@ -264,41 +287,45 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { } func VerifySignature(key Key, sig Signature, unverified []byte) error { - // Create rsa.PublicKey object from DER encoded public key string as - // found in the public part of the keyval part of a securesystemslib key dict - keyReader := strings.NewReader(key.KeyVal.Public) - pemBytes, err := ioutil.ReadAll(keyReader) - if err != nil { - return err - } - // TODO: There could be more key data in _, which we silently ignore here. - // Should we handle it / fail / say something about it? - data, _ := pem.Decode(pemBytes) - if data == nil { - return ErrNoPEMBLock - } - parsedKey, err := ParseKey(data.Bytes) - if err != nil { - return err - } - switch parsedKey.(type) { - case *rsa.PublicKey: + switch key.KeyType { + case "rsa": + // Create rsa.PublicKey object from DER encoded public key string as + // found in the public part of the keyval part of a securesystemslib key dict + keyReader := strings.NewReader(key.KeyVal.Public) + pemBytes, err := ioutil.ReadAll(keyReader) + if err != nil { + return err + } + // TODO: There could be more key data in _, which we silently ignore here. + // Should we handle it / fail / say something about it? + data, _ := pem.Decode(pemBytes) + if data == nil { + return ErrNoPEMBLock + } + parsedKey, err := ParseKey(data.Bytes) + if err != nil { + return err + } hashed := sha256.Sum256(unverified) sigHex, _ := hex.DecodeString(sig.Sig) - err := rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigHex, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + err = rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigHex, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) if err != nil { return fmt.Errorf("%w: %s", ErrInvalidSignature, err) } - case *ed25519.PublicKey: + case "ed25519": + pubHex, err := hex.DecodeString(key.KeyVal.Public) + if err != nil { + return err + } sigHex, err := hex.DecodeString(sig.Sig) if err != nil { return err } - if ok := ed25519.Verify(parsedKey.(ed25519.PublicKey), unverified, sigHex); !ok { + if ok := ed25519.Verify(pubHex, unverified, sigHex); !ok { return fmt.Errorf("%w: ed25519", ErrInvalidSignature) } default: - return fmt.Errorf("%w: Key has type %T", ErrInvalidSignature, parsedKey) + return fmt.Errorf("%w: Key has type %s", ErrInvalidSignature, key.KeyType) } return nil } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 8fd51c20..850329a6 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -138,7 +138,8 @@ func TestLoadRSAPrivateKey(t *testing.T) { func TestGenerateRSASignature(t *testing.T) { validKey := Key{ - KeyId: "f29cb6877d14ebcf28b136a96a4d64935522afaddcc84e6b70ff6b9eaefb8fcf", + KeyId: "f29cb6877d14ebcf28b136a96a4d64935522afaddcc84e6b70ff6b9eaefb8fcf", + KeyType: "rsa", KeyVal: KeyVal{ Public: `-----BEGIN PUBLIC KEY----- MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l @@ -222,7 +223,8 @@ func TestVerifyRSASignature(t *testing.T) { } validKey := Key{ - KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498", + KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498", + KeyType: "rsa", KeyVal: KeyVal{ Public: `-----BEGIN PUBLIC KEY----- MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAzgLBsMFSgwBiWTBmVsyW @@ -345,7 +347,7 @@ func TestLoad25519PublicKey(t *testing.T) { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) } - expectedPubKey := "ad238d901104293b031d95f516dc00c68b07c9b7a66b2f2c871dc71a6aae0e46" + expectedPubKey := "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6" if expectedPubKey != key.KeyId { t.Errorf("Loaded pubkey is not the expected key") } @@ -367,7 +369,7 @@ func TestLoad25519PrivateKey(t *testing.T) { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) } - expectedPrivateKey := "ad238d901104293b031d95f516dc00c68b07c9b7a66b2f2c871dc71a6aae0e46" + expectedPrivateKey := "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6" if expectedPrivateKey != key.KeyId { t.Errorf("Loaded pubkey is not the expected key") } diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index a4821515..4972dd54 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -318,7 +318,7 @@ func TestInTotoRun(t *testing.T) { Environment: map[string]interface{}{}, }, Signatures: []Signature{{ - KeyId: "ad238d901104293b031d95f516dc00c68b07c9b7a66b2f2c871dc71a6aae0e46", + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", Sig: "4e2af0ae36ad51aba2a9a0dc9e1864ab3fe5f3ec4f4de9c958d6648963999a99a5f663282a415b237f5d3e53972cf7f21151b65eb189f9c9add5eaf409455209", }}, }, From cc2a58e74c46ace69e594a1eea97e01cfb5760cc Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 21 Jul 2020 19:35:53 +0200 Subject: [PATCH 34/86] Add comment for dropping rest of PEM block parsing We need to mention, that we drop the rest of the pam.Decode() call, because it does not represent a valid PEM block. Additionally we do not care about other data, than the actual key --- in_toto/keylib.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 50dab9d3..1b9c530d 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -253,8 +253,9 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { if err != nil { return signature, err } - // TODO: There could be more key data in _, which we silently ignore here. - // Should we handle it / fail / say something about it? + // pam.Decode returns the parsed pem block and a rest. + // The rest is everything, that could not be parsed as PEM block. + // Therefore we can drop this via using the blank identifier "_" data, _ := pem.Decode(pemBytes) if data == nil { return signature, ErrNoPEMBLock @@ -296,8 +297,9 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { if err != nil { return err } - // TODO: There could be more key data in _, which we silently ignore here. - // Should we handle it / fail / say something about it? + // pam.Decode returns the parsed pem block and a rest. + // The rest is everything, that could not be parsed as PEM block. + // Therefore we can drop this via using the blank identifier "_" data, _ := pem.Decode(pemBytes) if data == nil { return ErrNoPEMBLock From c31d709cd693d3c04b290d7b56de083628c26480 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 21 Jul 2020 19:37:35 +0200 Subject: [PATCH 35/86] remove comment that we do not support signing links --- in_toto/runlib.go | 1 - 1 file changed, 1 deletion(-) diff --git a/in_toto/runlib.go b/in_toto/runlib.go index af4ab5fb..49cd1615 100644 --- a/in_toto/runlib.go +++ b/in_toto/runlib.go @@ -246,7 +246,6 @@ metadata. Link metadata contains recorded products at the passed productPaths and materials at the passed materialPaths. The returned link is wrapped in a Metablock object. If command execution or artifact recording fails the first return value is an empty Metablock and the second return value is the error. -NOTE: Currently InTotoRun cannot be used to sign Link metadata. */ func InTotoRun(name string, materialPaths []string, productPaths []string, cmdArgs []string, key Key) (Metablock, error) { From 1ac76f055b22d99037300b7153abfa8207ae0c32 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 21 Jul 2020 22:42:08 +0200 Subject: [PATCH 36/86] remove outdated test The TestMetaBlockSignWithEd25519 used our custom JSON format and different loading functions, that do not exist anymore. Therefore we can remove it. --- in_toto/model_test.go | 67 ------------------------------------------- 1 file changed, 67 deletions(-) diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 14e69085..5142a439 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -1186,70 +1186,3 @@ func TestMetablockSignWithRSA(t *testing.T) { t.Errorf("signing with an invalid RSA key should fail") } } - -//func TestMetablockSignWithEd25519(t *testing.T) { -// // Test metablock signing (with ed25519) -// // - Pass non-ed25519 key -// // - Pass malformed ed25519 key -// // - Pass unsupported invalid key type -// // - Pass an ed25519 key and expect a signature back -// var key Key -// var mb Metablock -// if err := mb.Load("demo.layout.template"); err != nil { -// t.Errorf("Cannot parse template file: %s", err) -// } -// -// pubkey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": ""}}` -// -// badkey, err := ParseEd25519FromPrivateJSON(pubkey) -// if err == nil || !strings.Contains(err.Error(), "private key cannot be empty") { -// t.Errorf("Metablock.Sign returned (%s), expected it to claim this "+ -// "key is not a private key", err) -// -// } -// -// validKey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73f8ed630f99d8eda250421f0e3a6123fd5c311cc001bda49"}}` -// -// // Trigger error in Sign/GenerateEd25519Signature with malformed key data -// badkey, err = ParseEd25519FromPrivateJSON(validKey) -// // make sure to only set badkey, if prior operation has been successful -// if err == nil { -// badkey.KeyVal.Private = "xyz" -// } -// err = mb.Sign(badkey) -// if err == nil || !strings.Contains(err.Error(), "invalid byte") { -// t.Errorf("Metablock.Sign returned (%s), expected 'invalid byte' error ", -// err) -// } -// -// badkey, err = ParseEd25519FromPrivateJSON(validKey) -// if err != nil { -// t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", -// err) -// } -// -// badkey.Scheme = "ecdsa" -// err = mb.Sign(badkey) -// if err == nil || !strings.Contains(err.Error(), "not supported") { -// t.Errorf("Metablock.Sign returned (%s), expected it to claim this "+ -// "key type/scheme is unsupported", err) -// } -// -// key, err = ParseEd25519FromPrivateJSON(validKey) -// if err != nil { -// t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", -// err) -// } -// -// err = mb.Sign(key) -// if err != nil { -// t.Errorf("Metablock.Sign returned (%s), expected no error", err) -// } -// -// // note, there's a 2 because this template is already signed hehe -// if len(mb.Signatures) != 2 { -// t.Errorf("Expected a new signature to be appended, but got (%+v) (%d)", -// mb.Signatures, len(mb.Signatures)) -// } -// -//} From a2825c57cd2bab8c1111c17b4e1d835ba6fdcd20 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Tue, 21 Jul 2020 22:44:47 +0200 Subject: [PATCH 37/86] remove outdated keylib test cases We dropped support for the non generic key parsing functions. --- in_toto/keylib_test.go | 141 ----------------------------------------- 1 file changed, 141 deletions(-) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 850329a6..01242979 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -7,80 +7,6 @@ import ( "testing" ) -//func TestParseRSAPublicKeyFromPEM(t *testing.T) { -// // Test parsing errors: -// // - Missing pem headers, -// // - Missing pem body -// // - Not an rsa key -// invalidRSA := []string{ -// "not a PEM block", -// `-----BEGIN PUBLIC KEY----- -// -//-----END PUBLIC KEY-----`, -// `-----BEGIN PUBLIC KEY----- -//MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESkhkURrGhKzC8IyJTP1H3QCVi4CU -//z5OxbcSn3IR+/9W02DOVayQHTnMlBc1SoStYMvbGwnPraQuh6t+U/NBHYQ== -//-----END PUBLIC KEY-----`, -// } -// expectedErrors := []string{ -// "Could not find a public key PEM block", -// "truncated", -// "only support rsa", -// } -// -// for i := 0; i < len(invalidRSA); i++ { -// result, err := ParseRSAPublicKeyFromPEM([]byte(invalidRSA[i])) -// if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { -// t.Errorf("ParseRSAPublicKeyFromPEM returned (%p, %s), expected '%s'"+ -// " error", result, err, expectedErrors[i]) -// } -// } -// -// // Test parsing valid public rsa key from PEM bytes -// validRSA := `-----BEGIN PUBLIC KEY----- -//MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxPX3kFs/z645x4UOC3KF -//Y3V80YQtKrp6YS3qU+Jlvx/XzK53lb4sCDRU9jqBBx3We45TmFUibroMd8tQXCUS -//e8gYCBUBqBmmz0dEHJYbW0tYF7IoapMIxhRYn76YqNdl1JoRTcmzIaOJ7QrHxQrS -//GpivvTm6kQ9WLeApG1GLYJ3C3Wl4bnsI1bKSv55Zi45/JawHzTzYUAIXX9qCd3Io -//HzDucz9IAj9Ookw0va/q9FjoPGrRB80IReVxLVnbo6pYJfu/O37jvEobHFa8ckHd -//YxUIg8wvkIOy1O3M74lBDm6CVI0ZO25xPlDB/4nHAE1PbA3aF3lw8JGuxLDsetxm -//fzgAleVt4vXLQiCrZaLf+0cM97JcT7wdHcbIvRLsij9LNP+2tWZgeZ/hIAOEdaDq -//cYANPDIAxfTvbe9I0sXrCtrLer1SS7GqUmdFCdkdun8erXdNF0ls9Rp4cbYhjdf3 -//yMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE= -//-----END PUBLIC KEY-----` -// result, err := ParseRSAPublicKeyFromPEM([]byte(validRSA)) -// if err != nil { -// t.Errorf("ParseRSAPublicKeyFromPEM returned (%p, %s), expected no error", -// result, err) -// } -//} -// -//func TestParseRSAPrivateKeyFromPEM(t *testing.T) { -// // Test parsing errors: -// // - Missing pem headers, -// // - Missing pem body -// // We only support RSA private keys, therefore we don't need to check for other keys. -// // Other keys should fail at ParsePKCS1 stage already. -// invalidRSA := []string{ -// "not a PEM block", -// `-----BEGIN PRIVATE KEY----- -// -//-----END PRIVATE KEY-----`, -// } -// expectedErrors := []string{ -// "Could not find a private key PEM block", -// "truncated", -// } -// -// for i := 0; i < len(invalidRSA); i++ { -// result, err := ParseRSAPrivateKeyFromPEM([]byte(invalidRSA[i])) -// if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { -// t.Errorf("ParseRSAPrivateKeyFromPEM returned (%p, %s), expected '%s'"+ -// " error", result, err, expectedErrors[i]) -// } -// } -//} - func TestLoadRSAPublicKey(t *testing.T) { // Test loading valid public rsa key from pem-formatted file var key Key @@ -274,73 +200,6 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= } } -//func TestGenerateEd25519Signature(t *testing.T) { -// // let's load a key in memory here first -// validKey := `{"keytype": "ed25519", "scheme": "ed25519", "keyid": "308e3f53523b632983a988b72a2e39c85fe8fc967116043ce51fa8d92a6aef64", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8f93f549eb4cca8dc2142fb655ba2d0955d1824f79474f354e38d6a359e9d440", "private": "861fd1b466cfc6f73f8ed630f99d8eda250421f0e3a6123fd5c311cc001bda49"}}` -// key, err := ParseEd25519FromPrivateJSON(validKey) -// if err != nil { -// t.Errorf("ParseEd25519FromPrivateJSON returned (%s), expected no error", -// err) -// } -// -// signature, err := GenerateEd25519Signature([]uint8("ohmywhatatest"), key) -// if err != nil { -// t.Errorf("GenerateEd25519Signature shouldn't have returned an error (%s)", -// err) -// } -// -// // validate correct signature -// err = VerifyEd25519Signature(key, signature, []uint8("ohmywhatatest")) -// if err != nil { -// t.Errorf("VerifyEd25519Signature shouldn't have returned an error (%s)", err) -// } -// -// //validate incorrect signature -// var incorrectSig Signature -// incorrectSig.Sig = "e8912b58f47ae04a65d7437e3c82eb361f82d952" -// err = VerifyEd25519Signature(key, incorrectSig, []uint8("ohmywhatatest")) -// if err == nil { -// t.Errorf("Given signature is valid, but should be invalid") -// } -// -// // validate InvalidByte signature -// var malformedSig Signature -// malformedSig.Sig = "InTotoRocks" -// err = VerifyEd25519Signature(key, malformedSig, []uint8("ohmywhatatest")) -// // use type conversion for checking for hex.InvalidByteError -// var invalidByteError hex.InvalidByteError -// if !errors.As(err, &invalidByteError) { -// t.Errorf("We received %s, but we should get: invalid byte error", err) -// } -// -// // validate invalidLength signature -// // the following signature is too short -// var invLengthSig Signature -// invLengthSig.Sig = "e8912b58f47ae04a65d74" -// err = VerifyEd25519Signature(key, invLengthSig, []uint8("ohmywhatatest")) -// if !errors.Is(err, hex.ErrLength) { -// t.Errorf("We received %s, but we should get: %s", err, hex.ErrLength) -// } -// -// // validate invalidKey -// wrongKey := key -// wrongKey.KeyVal.Public = "e8912b58f47ae04a65d7437e3c82eb361f82d952b4d1b3dc5d90c6f37d7" -// err = VerifyEd25519Signature(wrongKey, signature, []uint8("ohmywhatatest")) -// if err == nil { -// t.Errorf("The invalid testKey passed the signature test, this should not happen") -// } -// -// if signature.KeyId != key.KeyId { -// t.Errorf("GenerateEd25519Signature should've returned matching keyids!") -// } -// -// // ed25519 signatures should be 64 bytes long => 128 hex digits -// if len(signature.Sig) != 128 { -// t.Errorf("GenerateEd25519Signature should've returned a 32 byte signature! %s", -// signature.Sig) -// } -//} - func TestLoad25519PublicKey(t *testing.T) { var key Key if err := key.LoadKey("carol.pub", "ed25519", []string{"sha256", "sha512"}); err != nil { From 0a28c13d3a6cebe6af577da9b9394083d1fdef03 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 22 Jul 2020 01:47:41 +0200 Subject: [PATCH 38/86] Add new test data + description This commit adds new PKCS8 and EC private/public key pairs for testing. Furthermore it adds a new README.md file in test/data that lists all of our test artifacts + a description for them --- test/data/README.md | 39 +++++++++++++++++++++++++++++++++++++++ test/data/erin | 5 +++++ test/data/erin.pub | 4 ++++ test/data/frank | 8 ++++++++ test/data/frank.ec | 7 +++++++ test/data/frank.pub | 6 ++++++ 6 files changed, 69 insertions(+) create mode 100644 test/data/README.md create mode 100644 test/data/erin create mode 100644 test/data/erin.pub create mode 100644 test/data/frank create mode 100644 test/data/frank.ec create mode 100644 test/data/frank.pub diff --git a/test/data/README.md b/test/data/README.md new file mode 100644 index 00000000..3f7d740f --- /dev/null +++ b/test/data/README.md @@ -0,0 +1,39 @@ +# Test Data + +## Go Specifics + +### ECDSA + +The Go ecdsa library only supports FIPS 186-3. +The following curves are supported: + +* secp521r1 +* ... + +The following curves are for example **not** supported: + +* secp256k1 +* ... + +## Test Data Overview + +| file | comment | +|------|---------| +| alice.pub | RSA public key | +| canonical-test.link | .. | +| carol | ed25519 key as PKCS8 | +| carol.pub | pub key of carol | +| carol-invalid | to be removed | +| dan | RSA private key | +| dan.pub | pub key of dan | +| erin | EC private Key (secp256k1) | +| erin.pub | EC public key of erin (secp256k1) | +| frank | EC private key PKCS8 (secp521r1) | +| frank.pub | EC public key of frank | +| foo.2f89b927.link | .. | +| foo.776a00e2.link | .. | +| foo.tar.gz | .. | +| package.2f89b927.link | .. | +| sub_layout.556caebd.link | .. | +| super.layout | .. | +| write-code.776a00e2.link | .. | diff --git a/test/data/erin b/test/data/erin new file mode 100644 index 00000000..0df79c68 --- /dev/null +++ b/test/data/erin @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK +oUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k +spRECJZDoiOV1OaMMIXjY4XNeoEBmw== +-----END EC PRIVATE KEY----- diff --git a/test/data/erin.pub b/test/data/erin.pub new file mode 100644 index 00000000..6f21d05f --- /dev/null +++ b/test/data/erin.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT +OZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw== +-----END PUBLIC KEY----- diff --git a/test/data/frank b/test/data/frank new file mode 100644 index 00000000..405c652e --- /dev/null +++ b/test/data/frank @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv +YTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R ++5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h +K2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs +B+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3 +rw== +-----END PRIVATE KEY----- diff --git a/test/data/frank.ec b/test/data/frank.ec new file mode 100644 index 00000000..2811ad34 --- /dev/null +++ b/test/data/frank.ec @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB6fQnV71xKx6kFgJvYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb +0ligpM65KyaeeRce6JR9eQW6TB6R+5pNzvOgBwYFK4EEACOhgYkDgYYABAFy0CeD +AyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1hK2QAanID3JuPff1NdhehhL/U +1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGsB+7KqOeO5ELkqHO5tsy4kvsN +rmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3rw== +-----END EC PRIVATE KEY----- diff --git a/test/data/frank.pub b/test/data/frank.pub new file mode 100644 index 00000000..bf675b2e --- /dev/null +++ b/test/data/frank.pub @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN +3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I +kv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b +tSVLwFVYX0W3UF7rt68= +-----END PUBLIC KEY----- From 0acd97f0f15d5f8ccb962f73828f052e9203fb16 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 22 Jul 2020 01:48:54 +0200 Subject: [PATCH 39/86] Add tests for new generic functions Our new generic functions needed testing. This adds testing for all generic functions, especially ed25519 and ecdsa unsupported key checking. TODO: In the future we want to support ecdsa keys --- in_toto/keylib.go | 19 +++--- in_toto/keylib_test.go | 135 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 10 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 1b9c530d..b4a8d789 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -265,20 +265,25 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { return signature, err } hashed := sha256.Sum256(signable) - // We use rand.Reader as secure random source for rsa.SignPSS() - signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], - &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) - if err != nil { - return signature, err + switch parsedKey.(type) { + case *rsa.PrivateKey: + // We use rand.Reader as secure random source for rsa.SignPSS() + signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], + &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return signature, err + } + default: + return signature, fmt.Errorf("%w: %T", ErrUnsupportedKeyType, parsedKey) } case "ed25519": - seed, err := hex.DecodeString(key.KeyVal.Private) + privateHex, err := hex.DecodeString(key.KeyVal.Private) if err != nil { return signature, err } // Note: We can directly use the key for signing and do not // need to use ed25519.NewKeyFromSeed(). - signatureBuffer = ed25519.Sign(seed, signable) + signatureBuffer = ed25519.Sign(privateHex, signable) default: return signature, fmt.Errorf("%w: %s", ErrUnsupportedKeyType, key.KeyType) } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 01242979..326622c7 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -128,7 +128,6 @@ lQqaoEO7ScdRrzjgvVxXkEY3nwLcWdM61/RZTL0+be8goDw5cWt+PaA= if err := VerifySignature(validKey, validSig, []byte(validData)); err != nil { t.Errorf("VerifyRSASignature from validSignature and data has failed: %s", err) } - } func TestVerifyRSASignature(t *testing.T) { @@ -200,7 +199,7 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= } } -func TestLoad25519PublicKey(t *testing.T) { +func TestLoadEd25519PublicKey(t *testing.T) { var key Key if err := key.LoadKey("carol.pub", "ed25519", []string{"sha256", "sha512"}); err != nil { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) @@ -222,7 +221,7 @@ func TestLoad25519PublicKey(t *testing.T) { } } -func TestLoad25519PrivateKey(t *testing.T) { +func TestLoadEd25519PrivateKey(t *testing.T) { var key Key if err := key.LoadKey("carol", "ed25519", []string{"sha256", "sha512"}); err != nil { t.Errorf("Failed to load ed25519 public key from file: (%s)", err) @@ -243,3 +242,133 @@ func TestLoad25519PrivateKey(t *testing.T) { t.Errorf("LoadEd25519PublicKey has successfully loaded an invalid key file") } } + +func TestGenerateEd25519Signature(t *testing.T) { + validKey := Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyType: "ed25519", + KeyVal: KeyVal{ + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + Private: "29ad59693fe94c9d623afbb66554b4f6bb248c47761689ada4875ebda94840ae393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + }, + } + // We are not verifying the signature yet.. + validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` + validSig, err := GenerateSignature([]byte(validData), validKey) + if err != nil { + t.Errorf("GenerateEd25519Signature from validKey and data failed: %s", err) + } + if err := VerifySignature(validKey, validSig, []byte(validData)); err != nil { + t.Errorf("VerifyEd25519Signature from validSignature and data has failed: %s", err) + } +} + +func TestVerifyEd25519Signature(t *testing.T) { + validSig := Signature{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + Sig: "f41d704809c0ae2356e1beaaf3432f4abfaaa4a26c043087d9eb6dc12b4a3c5df73f8c47a4e969e815a5d2c9853d7eba208b48c7459f6b865cd0b51a94e6d704", + } + + validKey := Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyType: "ed25519", + KeyVal: KeyVal{ + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + }, + } + validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` + + // Test verifying valid signature + err := VerifySignature(validKey, validSig, []byte(validData)) + if err != nil { + t.Errorf("VerifyEd25519Signature returned '%s', expected nil", err) + } +} + +func TestInvalidKeyComponent(t *testing.T) { + // The following is an invalid SetKeyComponents call + var key Key + err := key.SetKeyComponents([]byte{}, []byte{}, "inToTo", "scheme", []string{"md5", "yolo"}) + if !errors.Is(err, ErrUnsupportedKeyType) { + t.Errorf("TestInvalidKeyComponent failed. We got: %s, we should have got: %s", err, ErrUnsupportedKeyType) + } +} + +func TestInvalidPEMKey(t *testing.T) { + _, err := ParseKey([]byte{}) + if !errors.Is(err, ErrFailedPEMParsing) { + t.Errorf("TestInvalidPEMKey failed with zero byte data as test key. We got: %s, we should have got: %s", err, ErrFailedPEMParsing) + } +} + +func TestLoadKey(t *testing.T) { + tables := []struct { + name string + path string + scheme string + keyIdHashAlgorithms []string + result string + }{ + {"Test non existing path", "this/path/is/invalid.txt", "ed25519", []string{"sha256", "sha512"}, "open this/path/is/invalid.txt: no such file or directory"}, + {"Test invalid file", "canonical-test.link", "ecdsa", []string{"sha256", "sha512"}, "failed to decode the data as PEM block (are you sure this is a pem file?)"}, + {"Test unsupported EC private key", "erin", "ecdsa", []string{"sha256", "sha512"}, "failed parsing the PEM block: unsupported PEM type"}, + {"Test unsupported PKCS8 EC key", "frank", "ecdsa", []string{"sha256", "sha512"}, "unsupported key type: *ecdsa.PrivateKey"}, + } + + for _, table := range tables { + // initialize empty key object + var key Key + err := key.LoadKey(table.path, table.scheme, table.keyIdHashAlgorithms) + // NOTE: some errors do not support errors.Is() yet, therefore we need to compare the error strings here + // This can lead to nil pointer dereference + if err.Error() != table.result { + t.Errorf("%s: Loadkey('%s', '%s', '%s') failed with '%s', should got '%s'", table.name, table.path, table.scheme, table.keyIdHashAlgorithms, err, table.result) + } + } +} + +func TestGenerateKey(t *testing.T) { + tables := []struct { + name string + signable []byte + key Key + result string + }{ + {"Test unsupported EC private key", []byte{}, Key{ + KeyId: "", + KeyIdHashAlgorithms: []string{"sha256", "sha512"}, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ecdsa", + }, "unsupported key type: ecdsa"}, + {"Test wrong KeyType", []byte{}, Key{ + KeyId: "", + KeyIdHashAlgorithms: []string{"sha256", "sha512"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ecdsa", + }, "failed parsing the PEM block: unsupported PEM type"}, + {"Test wrong KeyType, but valid PKCS8 key", []byte{}, Key{ + KeyId: "", + KeyIdHashAlgorithms: []string{"sha256", "sha512"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----", + }, + }, "unsupported key type: *ecdsa.PrivateKey"}, + } + + for _, table := range tables { + _, err := GenerateSignature(table.signable, table.key) + if err.Error() != table.result { + t.Errorf("%s: GenerateKey failed with '%s', should got '%s'", table.name, table.result, err) + } + } +} From e601861751286f0aaa8923e136d1a90c5c0a3815 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 22 Jul 2020 17:02:22 +0200 Subject: [PATCH 40/86] add more tests This adds more test coverage for the Generate/Verify functions --- in_toto/keylib_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 326622c7..9b8a9d70 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -1,6 +1,7 @@ package in_toto import ( + "encoding/hex" "errors" "fmt" "os" @@ -197,6 +198,36 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= t.Errorf("VerifyRSASignature returned '%s', expected error", err) } } + + // pem.Decode errors + invalidKey := Key{ + KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498", + KeyIdHashAlgorithms: []string{"sha256", "sha512"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Public: "INVALID", + }, + Scheme: "rsassa-pss-sha256", + } + // just trigger pem.Decode function + err = VerifySignature(invalidKey, Signature{}, []byte{}) + if !errors.Is(err, ErrNoPEMBLock) { + t.Errorf("VerifySignature returned '%s', should got '%s'", err, ErrNoPEMBLock) + } + + // Test ParseKey errors via providing an EC key, but with wrong key type + invalidECKey := Key{ + KeyId: "", + KeyType: "rsa", + KeyVal: KeyVal{ + Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", + }, + } + // just trigger ParseKey function + err = VerifySignature(invalidECKey, Signature{}, []byte{}) + if !errors.Is(err, ErrFailedPEMParsing) { + t.Errorf("VerifySignature returned '%s', should got '%s'", err, ErrFailedPEMParsing) + } } func TestLoadEd25519PublicKey(t *testing.T) { @@ -283,6 +314,40 @@ func TestVerifyEd25519Signature(t *testing.T) { if err != nil { t.Errorf("VerifyEd25519Signature returned '%s', expected nil", err) } + + invalidSig := Signature{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + Sig: "f41d704809c0ae2356e1beaaf3432f4abfaa", + } + + invalidKey := Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyType: "ed25519", + KeyVal: KeyVal{ + Public: "INVALID", + }, + } + + invalidHexSig := Signature{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + Sig: "INVALID", + } + + err = VerifySignature(validKey, invalidSig, []byte(validData)) + if !errors.Is(err, ErrInvalidSignature) { + t.Errorf("VerifyEd25519Signature returned '%s', expected '%s'", err, ErrInvalidSignature) + } + + err = VerifySignature(invalidKey, validSig, []byte(validData)) + var hexError hex.InvalidByteError + if !errors.As(err, &hexError) { + t.Errorf("VerifyEd25519Signature returned '%s', expected '%s'", err, hexError) + } + + err = VerifySignature(validKey, invalidHexSig, []byte(validData)) + if !errors.As(err, &hexError) { + t.Errorf("VerifyEd25519Signature returned '%s', expected '%s'", err, hexError) + } } func TestInvalidKeyComponent(t *testing.T) { @@ -363,10 +428,24 @@ func TestGenerateKey(t *testing.T) { Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----", }, }, "unsupported key type: *ecdsa.PrivateKey"}, + {"Test invalid hex string for ed25519", []byte{}, Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha256", "sha512"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "INVALID", + Public: "INVALID", + }, + Scheme: "ed25519", + }, "encoding/hex: invalid byte: U+0049 'I'"}, } for _, table := range tables { _, err := GenerateSignature(table.signable, table.key) + // Note: Some of our errors do not yet support Go 1.13 error handling + // Thus we need to compare strings :(, this can lead to a nil pointer + // dereference. If you encounter a nil pointer dereference, expect that + // the GenerateSignature() func failed. if err.Error() != table.result { t.Errorf("%s: GenerateKey failed with '%s', should got '%s'", table.name, table.result, err) } From 4379ac51aea7c9c708eb9a3e3c443fbb3c43063d Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 22 Jul 2020 17:03:32 +0200 Subject: [PATCH 41/86] fix spelling Fix spelling for ErrNoPEMBlock --- in_toto/keylib.go | 8 ++++---- in_toto/keylib_test.go | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index b4a8d789..3ff5492a 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -20,7 +20,7 @@ import ( var ErrFailedPEMParsing = errors.New("failed parsing the PEM block: unsupported PEM type") // ErrNoPEMBlock gets triggered when there is no PEM block in the provided file -var ErrNoPEMBLock = errors.New("failed to decode the data as PEM block (are you sure this is a pem file?)") +var ErrNoPEMBlock = errors.New("failed to decode the data as PEM block (are you sure this is a pem file?)") // ErrUnsupportedKeyType is returned when we are dealing with a key type different to ed25519 or RSA var ErrUnsupportedKeyType = errors.New("unsupported key type") @@ -189,7 +189,7 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) // Should we handle it / fail / say something about it? data, _ := pem.Decode(pemBytes) if data == nil { - return ErrNoPEMBLock + return ErrNoPEMBlock } // Try to load private key, if this fails try to load @@ -258,7 +258,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { // Therefore we can drop this via using the blank identifier "_" data, _ := pem.Decode(pemBytes) if data == nil { - return signature, ErrNoPEMBLock + return signature, ErrNoPEMBlock } parsedKey, err := ParseKey(data.Bytes) if err != nil { @@ -307,7 +307,7 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { // Therefore we can drop this via using the blank identifier "_" data, _ := pem.Decode(pemBytes) if data == nil { - return ErrNoPEMBLock + return ErrNoPEMBlock } parsedKey, err := ParseKey(data.Bytes) if err != nil { diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 9b8a9d70..21b6457b 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -25,7 +25,7 @@ func TestLoadRSAPublicKey(t *testing.T) { // - Not a pem formatted rsa public key expectedError := "Could not find a public key PEM block" err = key.LoadKey("demo.layout.template", "rsassa-pss-sha256", []string{"sha256", "sha512"}) - if !errors.Is(err, ErrNoPEMBLock) { + if !errors.Is(err, ErrNoPEMBlock) { t.Errorf("LoadRSAPublicKey returned (%s), expected '%s' error", err, expectedError) } @@ -51,9 +51,9 @@ func TestLoadRSAPrivateKey(t *testing.T) { } err = key.LoadKey("demo.layout.template", "", []string{}) - if err == nil || !errors.Is(err, ErrNoPEMBLock) { + if err == nil || !errors.Is(err, ErrNoPEMBlock) { t.Errorf("LoadKey returned (%s), expected '%s' error", err, - ErrNoPEMBLock.Error()) + ErrNoPEMBlock.Error()) } // Test not existing file @@ -211,8 +211,8 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= } // just trigger pem.Decode function err = VerifySignature(invalidKey, Signature{}, []byte{}) - if !errors.Is(err, ErrNoPEMBLock) { - t.Errorf("VerifySignature returned '%s', should got '%s'", err, ErrNoPEMBLock) + if !errors.Is(err, ErrNoPEMBlock) { + t.Errorf("VerifySignature returned '%s', should got '%s'", err, ErrNoPEMBlock) } // Test ParseKey errors via providing an EC key, but with wrong key type From ad07bd6550206479257a97585363a909634ee1fc Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 22 Jul 2020 17:05:49 +0200 Subject: [PATCH 42/86] fix windows path error We just remove the slashes and make this test windows compatible. Full paths should be covered. For the future we should use path() for paths, for being consistent through different OS. --- in_toto/keylib_test.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 21b6457b..621c3779 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -374,7 +374,7 @@ func TestLoadKey(t *testing.T) { keyIdHashAlgorithms []string result string }{ - {"Test non existing path", "this/path/is/invalid.txt", "ed25519", []string{"sha256", "sha512"}, "open this/path/is/invalid.txt: no such file or directory"}, + {"Test non existing path", "invalid.txt", "ed25519", []string{"sha256", "sha512"}, "open invalid.txt: no such file or directory"}, {"Test invalid file", "canonical-test.link", "ecdsa", []string{"sha256", "sha512"}, "failed to decode the data as PEM block (are you sure this is a pem file?)"}, {"Test unsupported EC private key", "erin", "ecdsa", []string{"sha256", "sha512"}, "failed parsing the PEM block: unsupported PEM type"}, {"Test unsupported PKCS8 EC key", "frank", "ecdsa", []string{"sha256", "sha512"}, "unsupported key type: *ecdsa.PrivateKey"}, @@ -384,9 +384,14 @@ func TestLoadKey(t *testing.T) { // initialize empty key object var key Key err := key.LoadKey(table.path, table.scheme, table.keyIdHashAlgorithms) - // NOTE: some errors do not support errors.Is() yet, therefore we need to compare the error strings here - // This can lead to nil pointer dereference - if err.Error() != table.result { + // We need to handle the non existing path error differently, because OS produce different error messages here. + if table.name == "Test non existing path" { + if err == nil { + t.Errorf("%s: Loadkey('%s', '%s', '%s') failed with '%s', should got nil", table.name, table.path, table.scheme, table.keyIdHashAlgorithms, err) + } + // NOTE: some errors do not support errors.Is() yet, therefore we need to compare the error strings here + // This can lead to nil pointer dereference + } else if err.Error() != table.result { t.Errorf("%s: Loadkey('%s', '%s', '%s') failed with '%s', should got '%s'", table.name, table.path, table.scheme, table.keyIdHashAlgorithms, err, table.result) } } From d71ce001ab6abb536c53d59b73232b28fc8ad57d Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 23 Jul 2020 17:01:29 +0200 Subject: [PATCH 43/86] Add description for generating key files --- test/data/README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/data/README.md b/test/data/README.md index 3f7d740f..1bd3c9ee 100644 --- a/test/data/README.md +++ b/test/data/README.md @@ -1,5 +1,40 @@ # Test Data +## How to generate the test data + +We load keys from disk only in PKCS8, PKCS1 or PKCX format. +The next sections describe, how you can generate such keys via openssl. +Currently only keys **without password protection** are supported. + +### RSA + +TODO: write description for RSA key generation + +### ECDSA + +First you need to generate an ecdsa key in traditional ec key format via: + +`$ openssl ecparam -name secp521r1 -genkey -noout -out .ec` + +Then you can transform this key into PKCS8 format via: + +`$ openssl pkcs8 -topk8 -nocrypt -in .ec -out ` + +Next generate the public key via: + +`$ openssl ec -in -pubout -out .pub` + + +### ED25519 + +Private key: + +`$ openssl genpkey -algorithm ed25519 -outform PEM -out ` + +Public key: + +`$ openssl pkey -in -pubout > .pub` + ## Go Specifics ### ECDSA From 01da883b36293be003f7d9c6e741cb75fbcc5d70 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 23 Jul 2020 18:24:37 +0200 Subject: [PATCH 44/86] add test for dumping and loading a signed metablock This adds a small test section for dumping and loading signed links. It will dump a link to a file and load it. Looks like we have an issue with our dump function, because the dumped file wrong. --- in_toto/runlib_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index 4972dd54..37972562 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -329,6 +329,22 @@ func TestInTotoRun(t *testing.T) { result, err := InTotoRun(linkName, table.materialPaths, table.productPaths, table.cmdArgs, table.key) if !reflect.DeepEqual(result, table.result) { t.Errorf("InTotoRun returned '(%s, %s)', expected '(%s, nil)'", result, err, table.result) + } else { + // we do not need to check if result == nil here, because our reflect.DeepEqual was successful + if err := result.Dump(linkName + ".link"); err != nil { + t.Errorf("Error while dumping link metablock to file") + } + var loadedResult Metablock + if err := loadedResult.Load(linkName + ".link"); err != nil { + t.Errorf("Error while loading link metablock from file") + } + if !reflect.DeepEqual(loadedResult, result) { + t.Errorf("Dump and loading of signed Link failed. Loaded result: '%s', dumped result '%s'", loadedResult, result) + } else { + if err := os.Remove(linkName + ".link"); err != nil { + t.Errorf("Removing created link file failed") + } + } } } From e43522d1d932712b6198dc87c221ce1e54bfab7c Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 24 Jul 2020 15:22:26 +0200 Subject: [PATCH 45/86] try to fix unmarshalling type errors This commit adds a new Byproducts struct as representation for our byproducts. This is necessary, because Go unmarshalls number interfaces values to float. --- in_toto/model.go | 12 +++++++++- in_toto/model_test.go | 48 +++++++++++++++++++-------------------- in_toto/runlib.go | 16 ++++++------- in_toto/runlib_test.go | 26 ++++++++++----------- in_toto/verifylib.go | 2 +- in_toto/verifylib_test.go | 4 ++-- 6 files changed, 59 insertions(+), 49 deletions(-) diff --git a/in_toto/model.go b/in_toto/model.go index 25f694f1..fb08723f 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -143,11 +143,21 @@ type Link struct { Name string `json:"name"` Materials map[string]interface{} `json:"materials"` Products map[string]interface{} `json:"products"` - ByProducts map[string]interface{} `json:"byproducts"` + ByProducts ByProducts `json:"byproducts"` Command []string `json:"command"` Environment map[string]interface{} `json:"environment"` } +// ByProducts represents our byproducts of executed commands. +// We can not use a a map[string]interface{} here, +// because otherwise unserialization would fail. +// We store integers, but Go only serializes to float. +type ByProducts struct { + ReturnValue int `json:"return-value,omitempty"` + Stdout string `json:"stdout,omitempty"` + Stderr string `json:"stderr,omitempty"` +} + /* validateArtifacts is a general function used to validate products and materials. */ diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 5142a439..a4ccf768 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -169,10 +169,10 @@ func TestMetablockLoadDumpLoad(t *testing.T) { "sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355", }, }, - ByProducts: map[string]interface{}{ - "return-value": float64(0), - "stderr": "a foo.py\n", - "stdout": "", + ByProducts: ByProducts{ + ReturnValue: 0, + Stderr: "a foo.py\n", + Stdout: "", }, Environment: map[string]interface{}{}, }, @@ -316,10 +316,10 @@ func TestValidateLink(t *testing.T) { "sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355", }, }, - ByProducts: map[string]interface{}{ - "return-value": float64(0), - "stderr": "a foo.py\n", - "stdout": "", + ByProducts: ByProducts{ + ReturnValue: 0, + Stderr: "a foo.py\n", + Stdout: "", }, Environment: map[string]interface{}{}, }, @@ -351,10 +351,10 @@ func TestValidateLink(t *testing.T) { "36c1e5aabb7c98514f355", }, }, - ByProducts: map[string]interface{}{ - "return-value": float64(0), - "stderr": "a foo.py\n", - "stdout": "", + ByProducts: ByProducts{ + ReturnValue: 0, + Stderr: "a foo.py\n", + Stdout: "", }, Environment: map[string]interface{}{}, }, @@ -386,10 +386,10 @@ func TestValidateLink(t *testing.T) { "sha256": "!@#$%", }, }, - ByProducts: map[string]interface{}{ - "return-value": float64(0), - "stderr": "a foo.py\n", - "stdout": "", + ByProducts: ByProducts{ + ReturnValue: 0, + Stderr: "a foo.py\n", + Stdout: "", }, Environment: map[string]interface{}{}, }, @@ -934,10 +934,10 @@ func TestValidateMetablock(t *testing.T) { "1e5aabb7c98514f355", }, }, - ByProducts: map[string]interface{}{ - "return-value": float64(0), - "stderr": "a foo.py\n", - "stdout": "", + ByProducts: ByProducts{ + ReturnValue: 0, + Stderr: "a foo.py\n", + Stdout: "", }, Environment: map[string]interface{}{}, }, @@ -1025,10 +1025,10 @@ func TestValidateMetablock(t *testing.T) { "1e5aabb7c98514f355", }, }, - ByProducts: map[string]interface{}{ - "return-value": float64(0), - "stderr": "a foo.py\n", - "stdout": "", + ByProducts: ByProducts{ + ReturnValue: 0, + Stderr: "a foo.py\n", + Stdout: "", }, Environment: map[string]interface{}{}, }, diff --git a/in_toto/runlib.go b/in_toto/runlib.go index 49cd1615..f126a4f0 100644 --- a/in_toto/runlib.go +++ b/in_toto/runlib.go @@ -210,20 +210,20 @@ created the first return value is nil and the second return value is the error. NOTE: Since stdout and stderr are captured, they cannot be seen during the command execution. */ -func RunCommand(cmdArgs []string) (map[string]interface{}, error) { +func RunCommand(cmdArgs []string) (ByProducts, error) { cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) stderrPipe, err := cmd.StderrPipe() if err != nil { - return nil, err + return ByProducts{}, err } stdoutPipe, err := cmd.StdoutPipe() if err != nil { - return nil, err + return ByProducts{}, err } if err := cmd.Start(); err != nil { - return nil, err + return ByProducts{}, err } // TODO: duplicate stdout, stderr @@ -232,10 +232,10 @@ func RunCommand(cmdArgs []string) (map[string]interface{}, error) { retVal := WaitErrToExitCode(cmd.Wait()) - return map[string]interface{}{ - "return-value": retVal, - "stdout": stdout, - "stderr": stderr, + return ByProducts{ + ReturnValue: retVal, + Stdout: string(stdout), + Stderr: string(stderr), }, nil } diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index 37972562..007d52ee 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -259,24 +259,24 @@ func TestRunCommand(t *testing.T) { {"sh", "-c", "printf out"}, {"sh", "-c", "printf err >&2"}, } - expected := []map[string]interface{}{ - {"return-value": 0, "stdout": []byte(""), "stderr": []byte("")}, - {"return-value": 1, "stdout": []byte(""), "stderr": []byte("")}, - {"return-value": 0, "stdout": []byte("out"), "stderr": []byte("")}, - {"return-value": 0, "stdout": []byte(""), "stderr": []byte("err")}, + expected := []ByProducts{ + {ReturnValue: 0, Stdout: "", Stderr: ""}, + {ReturnValue: 1, Stdout: "", Stderr: ""}, + {ReturnValue: 0, Stdout: "out", Stderr: ""}, + {ReturnValue: 0, Stdout: "", Stderr: "err"}, } for i := 0; i < len(parameters); i++ { result, err := RunCommand(parameters[i]) if !reflect.DeepEqual(result, expected[i]) || err != nil { - t.Errorf("RunCommand returned '(%s, %s)', expected '(%s, nil)'", + t.Errorf("RunCommand returned '(%#v, %s)', expected '(%#v, nil)'", result, err, expected[i]) } } // Fail run command result, err := RunCommand([]string{"command-does-not-exist"}) - if result != nil || err == nil { - t.Errorf("RunCommand returned '(%s, %s)', expected '(nil, *exec.Error)'", + if err == nil { + t.Errorf("RunCommand returned '(%#v, %s)', expected '(nil, *exec.Error)'", result, err) } } @@ -311,15 +311,15 @@ func TestInTotoRun(t *testing.T) { "sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355", }, }, - ByProducts: map[string]interface{}{ - "return-value": 0, "stdout": []byte("out"), "stderr": []byte("err"), + ByProducts: ByProducts{ + ReturnValue: 0, Stdout: "out", Stderr: "err", }, Command: []string{"sh", "-c", "printf out; printf err >&2"}, Environment: map[string]interface{}{}, }, Signatures: []Signature{{ KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - Sig: "4e2af0ae36ad51aba2a9a0dc9e1864ab3fe5f3ec4f4de9c958d6648963999a99a5f663282a415b237f5d3e53972cf7f21151b65eb189f9c9add5eaf409455209", + Sig: "98500032c08c4d8a0d023fad192af3d6d618d3f0ffa10ac7f67046e57d6dcb503832a8c338a98f1f6fa663b0dcc5f0713ca012275aca4170e667513676514e08", }}, }, }, @@ -328,7 +328,7 @@ func TestInTotoRun(t *testing.T) { for _, table := range tablesCorrect { result, err := InTotoRun(linkName, table.materialPaths, table.productPaths, table.cmdArgs, table.key) if !reflect.DeepEqual(result, table.result) { - t.Errorf("InTotoRun returned '(%s, %s)', expected '(%s, nil)'", result, err, table.result) + t.Errorf("InTotoRun returned '(%#v, %s)', expected '(%#v, nil)'", result, err, table.result) } else { // we do not need to check if result == nil here, because our reflect.DeepEqual was successful if err := result.Dump(linkName + ".link"); err != nil { @@ -339,7 +339,7 @@ func TestInTotoRun(t *testing.T) { t.Errorf("Error while loading link metablock from file") } if !reflect.DeepEqual(loadedResult, result) { - t.Errorf("Dump and loading of signed Link failed. Loaded result: '%s', dumped result '%s'", loadedResult, result) + t.Errorf("Dump and loading of signed Link failed. Loaded result: '%#v', dumped result '%#v'", loadedResult, result) } else { if err := os.Remove(linkName + ".link"); err != nil { t.Errorf("Removing created link file failed") diff --git a/in_toto/verifylib.go b/in_toto/verifylib.go index cde95844..4a8917d2 100644 --- a/in_toto/verifylib.go +++ b/in_toto/verifylib.go @@ -44,7 +44,7 @@ func RunInspections(layout Layout) (map[string]Metablock, error) { return nil, err } - retVal := linkMb.Signed.(Link).ByProducts["return-value"] + retVal := linkMb.Signed.(Link).ByProducts.ReturnValue if retVal != 0 { return nil, fmt.Errorf("Inspection command '%s' of inspection '%s'"+ " returned a non-zero value: %d", inspection.Run, inspection.Name, diff --git a/in_toto/verifylib_test.go b/in_toto/verifylib_test.go index 6fe4dc1b..33431e75 100644 --- a/in_toto/verifylib_test.go +++ b/in_toto/verifylib_test.go @@ -90,8 +90,8 @@ func TestGetSummaryLink(t *testing.T) { } if !reflect.DeepEqual(summaryLink.Signed.(Link).ByProducts, packageLink.Signed.(Link).ByProducts) { - t.Errorf("Summary Link by-products don't match. Expected '%s', "+ - "returned '%s", packageLink.Signed.(Link).ByProducts, + t.Errorf("Summary Link by-products don't match. Expected '%#v', "+ + "returned '%#v", packageLink.Signed.(Link).ByProducts, summaryLink.Signed.(Link).ByProducts) } } From adfdd99e855517a3a09ec688c393d48df52dedc8 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 25 Jul 2020 17:44:08 +0200 Subject: [PATCH 46/86] Revert "try to fix unmarshalling type errors" This reverts commit e43522d1d932712b6198dc87c221ce1e54bfab7c. --- in_toto/model.go | 12 +--------- in_toto/model_test.go | 48 +++++++++++++++++++-------------------- in_toto/runlib.go | 16 ++++++------- in_toto/runlib_test.go | 26 ++++++++++----------- in_toto/verifylib.go | 2 +- in_toto/verifylib_test.go | 4 ++-- 6 files changed, 49 insertions(+), 59 deletions(-) diff --git a/in_toto/model.go b/in_toto/model.go index fb08723f..25f694f1 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -143,21 +143,11 @@ type Link struct { Name string `json:"name"` Materials map[string]interface{} `json:"materials"` Products map[string]interface{} `json:"products"` - ByProducts ByProducts `json:"byproducts"` + ByProducts map[string]interface{} `json:"byproducts"` Command []string `json:"command"` Environment map[string]interface{} `json:"environment"` } -// ByProducts represents our byproducts of executed commands. -// We can not use a a map[string]interface{} here, -// because otherwise unserialization would fail. -// We store integers, but Go only serializes to float. -type ByProducts struct { - ReturnValue int `json:"return-value,omitempty"` - Stdout string `json:"stdout,omitempty"` - Stderr string `json:"stderr,omitempty"` -} - /* validateArtifacts is a general function used to validate products and materials. */ diff --git a/in_toto/model_test.go b/in_toto/model_test.go index a4ccf768..5142a439 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -169,10 +169,10 @@ func TestMetablockLoadDumpLoad(t *testing.T) { "sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355", }, }, - ByProducts: ByProducts{ - ReturnValue: 0, - Stderr: "a foo.py\n", - Stdout: "", + ByProducts: map[string]interface{}{ + "return-value": float64(0), + "stderr": "a foo.py\n", + "stdout": "", }, Environment: map[string]interface{}{}, }, @@ -316,10 +316,10 @@ func TestValidateLink(t *testing.T) { "sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355", }, }, - ByProducts: ByProducts{ - ReturnValue: 0, - Stderr: "a foo.py\n", - Stdout: "", + ByProducts: map[string]interface{}{ + "return-value": float64(0), + "stderr": "a foo.py\n", + "stdout": "", }, Environment: map[string]interface{}{}, }, @@ -351,10 +351,10 @@ func TestValidateLink(t *testing.T) { "36c1e5aabb7c98514f355", }, }, - ByProducts: ByProducts{ - ReturnValue: 0, - Stderr: "a foo.py\n", - Stdout: "", + ByProducts: map[string]interface{}{ + "return-value": float64(0), + "stderr": "a foo.py\n", + "stdout": "", }, Environment: map[string]interface{}{}, }, @@ -386,10 +386,10 @@ func TestValidateLink(t *testing.T) { "sha256": "!@#$%", }, }, - ByProducts: ByProducts{ - ReturnValue: 0, - Stderr: "a foo.py\n", - Stdout: "", + ByProducts: map[string]interface{}{ + "return-value": float64(0), + "stderr": "a foo.py\n", + "stdout": "", }, Environment: map[string]interface{}{}, }, @@ -934,10 +934,10 @@ func TestValidateMetablock(t *testing.T) { "1e5aabb7c98514f355", }, }, - ByProducts: ByProducts{ - ReturnValue: 0, - Stderr: "a foo.py\n", - Stdout: "", + ByProducts: map[string]interface{}{ + "return-value": float64(0), + "stderr": "a foo.py\n", + "stdout": "", }, Environment: map[string]interface{}{}, }, @@ -1025,10 +1025,10 @@ func TestValidateMetablock(t *testing.T) { "1e5aabb7c98514f355", }, }, - ByProducts: ByProducts{ - ReturnValue: 0, - Stderr: "a foo.py\n", - Stdout: "", + ByProducts: map[string]interface{}{ + "return-value": float64(0), + "stderr": "a foo.py\n", + "stdout": "", }, Environment: map[string]interface{}{}, }, diff --git a/in_toto/runlib.go b/in_toto/runlib.go index f126a4f0..49cd1615 100644 --- a/in_toto/runlib.go +++ b/in_toto/runlib.go @@ -210,20 +210,20 @@ created the first return value is nil and the second return value is the error. NOTE: Since stdout and stderr are captured, they cannot be seen during the command execution. */ -func RunCommand(cmdArgs []string) (ByProducts, error) { +func RunCommand(cmdArgs []string) (map[string]interface{}, error) { cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) stderrPipe, err := cmd.StderrPipe() if err != nil { - return ByProducts{}, err + return nil, err } stdoutPipe, err := cmd.StdoutPipe() if err != nil { - return ByProducts{}, err + return nil, err } if err := cmd.Start(); err != nil { - return ByProducts{}, err + return nil, err } // TODO: duplicate stdout, stderr @@ -232,10 +232,10 @@ func RunCommand(cmdArgs []string) (ByProducts, error) { retVal := WaitErrToExitCode(cmd.Wait()) - return ByProducts{ - ReturnValue: retVal, - Stdout: string(stdout), - Stderr: string(stderr), + return map[string]interface{}{ + "return-value": retVal, + "stdout": stdout, + "stderr": stderr, }, nil } diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index 007d52ee..37972562 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -259,24 +259,24 @@ func TestRunCommand(t *testing.T) { {"sh", "-c", "printf out"}, {"sh", "-c", "printf err >&2"}, } - expected := []ByProducts{ - {ReturnValue: 0, Stdout: "", Stderr: ""}, - {ReturnValue: 1, Stdout: "", Stderr: ""}, - {ReturnValue: 0, Stdout: "out", Stderr: ""}, - {ReturnValue: 0, Stdout: "", Stderr: "err"}, + expected := []map[string]interface{}{ + {"return-value": 0, "stdout": []byte(""), "stderr": []byte("")}, + {"return-value": 1, "stdout": []byte(""), "stderr": []byte("")}, + {"return-value": 0, "stdout": []byte("out"), "stderr": []byte("")}, + {"return-value": 0, "stdout": []byte(""), "stderr": []byte("err")}, } for i := 0; i < len(parameters); i++ { result, err := RunCommand(parameters[i]) if !reflect.DeepEqual(result, expected[i]) || err != nil { - t.Errorf("RunCommand returned '(%#v, %s)', expected '(%#v, nil)'", + t.Errorf("RunCommand returned '(%s, %s)', expected '(%s, nil)'", result, err, expected[i]) } } // Fail run command result, err := RunCommand([]string{"command-does-not-exist"}) - if err == nil { - t.Errorf("RunCommand returned '(%#v, %s)', expected '(nil, *exec.Error)'", + if result != nil || err == nil { + t.Errorf("RunCommand returned '(%s, %s)', expected '(nil, *exec.Error)'", result, err) } } @@ -311,15 +311,15 @@ func TestInTotoRun(t *testing.T) { "sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355", }, }, - ByProducts: ByProducts{ - ReturnValue: 0, Stdout: "out", Stderr: "err", + ByProducts: map[string]interface{}{ + "return-value": 0, "stdout": []byte("out"), "stderr": []byte("err"), }, Command: []string{"sh", "-c", "printf out; printf err >&2"}, Environment: map[string]interface{}{}, }, Signatures: []Signature{{ KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - Sig: "98500032c08c4d8a0d023fad192af3d6d618d3f0ffa10ac7f67046e57d6dcb503832a8c338a98f1f6fa663b0dcc5f0713ca012275aca4170e667513676514e08", + Sig: "4e2af0ae36ad51aba2a9a0dc9e1864ab3fe5f3ec4f4de9c958d6648963999a99a5f663282a415b237f5d3e53972cf7f21151b65eb189f9c9add5eaf409455209", }}, }, }, @@ -328,7 +328,7 @@ func TestInTotoRun(t *testing.T) { for _, table := range tablesCorrect { result, err := InTotoRun(linkName, table.materialPaths, table.productPaths, table.cmdArgs, table.key) if !reflect.DeepEqual(result, table.result) { - t.Errorf("InTotoRun returned '(%#v, %s)', expected '(%#v, nil)'", result, err, table.result) + t.Errorf("InTotoRun returned '(%s, %s)', expected '(%s, nil)'", result, err, table.result) } else { // we do not need to check if result == nil here, because our reflect.DeepEqual was successful if err := result.Dump(linkName + ".link"); err != nil { @@ -339,7 +339,7 @@ func TestInTotoRun(t *testing.T) { t.Errorf("Error while loading link metablock from file") } if !reflect.DeepEqual(loadedResult, result) { - t.Errorf("Dump and loading of signed Link failed. Loaded result: '%#v', dumped result '%#v'", loadedResult, result) + t.Errorf("Dump and loading of signed Link failed. Loaded result: '%s', dumped result '%s'", loadedResult, result) } else { if err := os.Remove(linkName + ".link"); err != nil { t.Errorf("Removing created link file failed") diff --git a/in_toto/verifylib.go b/in_toto/verifylib.go index 4a8917d2..cde95844 100644 --- a/in_toto/verifylib.go +++ b/in_toto/verifylib.go @@ -44,7 +44,7 @@ func RunInspections(layout Layout) (map[string]Metablock, error) { return nil, err } - retVal := linkMb.Signed.(Link).ByProducts.ReturnValue + retVal := linkMb.Signed.(Link).ByProducts["return-value"] if retVal != 0 { return nil, fmt.Errorf("Inspection command '%s' of inspection '%s'"+ " returned a non-zero value: %d", inspection.Run, inspection.Name, diff --git a/in_toto/verifylib_test.go b/in_toto/verifylib_test.go index 33431e75..6fe4dc1b 100644 --- a/in_toto/verifylib_test.go +++ b/in_toto/verifylib_test.go @@ -90,8 +90,8 @@ func TestGetSummaryLink(t *testing.T) { } if !reflect.DeepEqual(summaryLink.Signed.(Link).ByProducts, packageLink.Signed.(Link).ByProducts) { - t.Errorf("Summary Link by-products don't match. Expected '%#v', "+ - "returned '%#v", packageLink.Signed.(Link).ByProducts, + t.Errorf("Summary Link by-products don't match. Expected '%s', "+ + "returned '%s", packageLink.Signed.(Link).ByProducts, summaryLink.Signed.(Link).ByProducts) } } From 455f141a24b572df55f209b2946800aa35abe9b9 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 25 Jul 2020 17:59:56 +0200 Subject: [PATCH 47/86] Fix inconsistent link dumping/loading Before this commit we used []byte64 and int in our in-memory link representation. This lead to numerous issues: 1. Using []byte64 for stderr/stdout meant, that we dump them as base64 in our JSON file. This was inconsistent to our in-toto python implementation, that stores output as strings in JSON files. 2. Go unmarshalls a number as float64, therefore we can't easily store the return-value as integer, although an integer would be a better choice. Storing it as a integer, would cost rewrites of the complete model and model testing. --- in_toto/model.go | 1 - in_toto/runlib.go | 6 +++--- in_toto/runlib_test.go | 12 ++++++------ in_toto/verifylib.go | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/in_toto/model.go b/in_toto/model.go index 25f694f1..fff913b1 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -522,7 +522,6 @@ func (mb *Metablock) Load(path string) (err error) { if err := decoder.Decode(&link); err != nil { return err } - mb.Signed = link } else if signed["_type"] == "layout" { diff --git a/in_toto/runlib.go b/in_toto/runlib.go index 49cd1615..4b4834b9 100644 --- a/in_toto/runlib.go +++ b/in_toto/runlib.go @@ -233,9 +233,9 @@ func RunCommand(cmdArgs []string) (map[string]interface{}, error) { retVal := WaitErrToExitCode(cmd.Wait()) return map[string]interface{}{ - "return-value": retVal, - "stdout": stdout, - "stderr": stderr, + "return-value": float64(retVal), + "stdout": string(stdout), + "stderr": string(stderr), }, nil } diff --git a/in_toto/runlib_test.go b/in_toto/runlib_test.go index 37972562..20febd05 100644 --- a/in_toto/runlib_test.go +++ b/in_toto/runlib_test.go @@ -260,10 +260,10 @@ func TestRunCommand(t *testing.T) { {"sh", "-c", "printf err >&2"}, } expected := []map[string]interface{}{ - {"return-value": 0, "stdout": []byte(""), "stderr": []byte("")}, - {"return-value": 1, "stdout": []byte(""), "stderr": []byte("")}, - {"return-value": 0, "stdout": []byte("out"), "stderr": []byte("")}, - {"return-value": 0, "stdout": []byte(""), "stderr": []byte("err")}, + {"return-value": float64(0), "stdout": "", "stderr": ""}, + {"return-value": float64(1), "stdout": "", "stderr": ""}, + {"return-value": float64(0), "stdout": "out", "stderr": ""}, + {"return-value": float64(0), "stdout": "", "stderr": "err"}, } for i := 0; i < len(parameters); i++ { result, err := RunCommand(parameters[i]) @@ -312,14 +312,14 @@ func TestInTotoRun(t *testing.T) { }, }, ByProducts: map[string]interface{}{ - "return-value": 0, "stdout": []byte("out"), "stderr": []byte("err"), + "return-value": float64(0), "stdout": "out", "stderr": "err", }, Command: []string{"sh", "-c", "printf out; printf err >&2"}, Environment: map[string]interface{}{}, }, Signatures: []Signature{{ KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - Sig: "4e2af0ae36ad51aba2a9a0dc9e1864ab3fe5f3ec4f4de9c958d6648963999a99a5f663282a415b237f5d3e53972cf7f21151b65eb189f9c9add5eaf409455209", + Sig: "08a6c42b8433502f2869bb3dc73f8348f6b6f89e42bbc63f91a33e7171d762e138ed5d695fb83cebec958203e17b2285f95b198d758bc62cf30e1f7408d6c10c", }}, }, }, diff --git a/in_toto/verifylib.go b/in_toto/verifylib.go index cde95844..553a035e 100644 --- a/in_toto/verifylib.go +++ b/in_toto/verifylib.go @@ -45,7 +45,7 @@ func RunInspections(layout Layout) (map[string]Metablock, error) { } retVal := linkMb.Signed.(Link).ByProducts["return-value"] - if retVal != 0 { + if retVal != float64(0) { return nil, fmt.Errorf("Inspection command '%s' of inspection '%s'"+ " returned a non-zero value: %d", inspection.Run, inspection.Name, retVal) From a4a40aede9b457b0c61f5301f58bd4fa01b1dea0 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 27 Jul 2020 21:52:40 +0200 Subject: [PATCH 48/86] add doc strings This commit adds more documentation to our test functions and keylib functions --- in_toto/keylib.go | 19 +++++++++++++++-- in_toto/keylib_test.go | 48 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 3ff5492a..13d7aaac 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -185,8 +185,9 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) return err } - // TODO: There could be more key data in _, which we silently ignore here. - // Should we handle it / fail / say something about it? + // pam.Decode returns the parsed pem block and a rest. + // The rest is everything, that could not be parsed as PEM block. + // Therefore we can drop this via using the blank identifier "_" data, _ := pem.Decode(pemBytes) if data == nil { return ErrNoPEMBlock @@ -292,6 +293,20 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { return signature, nil } +/* +VerifySignature will verify unverified byte data via a passed key and signature. +Supported key types are: + + * RSA + * ED25519 + +When encountering a RSA key, VerifySignature will decode the PEM block in the key +and will call rsa.VerifyPSS() for verifying the RSA signature. +When encountering an ed25519 key, Verifysignature will decode the hex string encoded +public key and will use ed25519.Verify() for verifying the ed25519 signature. +On success it will return nil. In case of an unsupported key type or any other error +it will return an error. +*/ func VerifySignature(key Key, sig Signature, unverified []byte) error { switch key.KeyType { case "rsa": diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 621c3779..876d7a38 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -8,6 +8,11 @@ import ( "testing" ) +/* +TestLoadRSAPublicKey loads the key test/data/alice.pub and compares it against +our specified keyID. Furthermore it will try to load a file, that is not a key +and makes sure, that LoadKey fails with such files. Same for non existing file paths. +*/ func TestLoadRSAPublicKey(t *testing.T) { // Test loading valid public rsa key from pem-formatted file var key Key @@ -37,6 +42,10 @@ func TestLoadRSAPublicKey(t *testing.T) { } } +/* +TestLoadRSAPrivateKey loads the RSA private key test/data/dan and will compare it +against our specified keyID +*/ func TestLoadRSAPrivateKey(t *testing.T) { // Test loading valid Private rsa key from pem-formatted file var key Key @@ -63,6 +72,10 @@ func TestLoadRSAPrivateKey(t *testing.T) { } } +/* +TestGenerateRSASignature generates a valid in-memory RSA key for generating a signature. +This signature should always be valid, with our specified in-memory RSA key. +*/ func TestGenerateRSASignature(t *testing.T) { validKey := Key{ KeyId: "f29cb6877d14ebcf28b136a96a4d64935522afaddcc84e6b70ff6b9eaefb8fcf", @@ -131,6 +144,10 @@ lQqaoEO7ScdRrzjgvVxXkEY3nwLcWdM61/RZTL0+be8goDw5cWt+PaA= } } +/* +TestVerifyRSASignature uses in-memory signatures and keys for validating, +that we succeed and fail as expected. +*/ func TestVerifyRSASignature(t *testing.T) { validSig := Signature{ KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498", @@ -230,6 +247,11 @@ k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= } } +/* +TestLoadEd25519PublicKey will load the ed25519 public key test/data/carol.pub and will +compare it against our specified keyID. It will also try to load different non existing keys, while +specifying the ed25519 scheme (although the keys are all invalid). +*/ func TestLoadEd25519PublicKey(t *testing.T) { var key Key if err := key.LoadKey("carol.pub", "ed25519", []string{"sha256", "sha512"}); err != nil { @@ -252,6 +274,10 @@ func TestLoadEd25519PublicKey(t *testing.T) { } } +/* +TestLoadEd25519PrivateKey loads the ed25519 private key test/data/carol and compares it against our specified +keyID. +*/ func TestLoadEd25519PrivateKey(t *testing.T) { var key Key if err := key.LoadKey("carol", "ed25519", []string{"sha256", "sha512"}); err != nil { @@ -274,6 +300,9 @@ func TestLoadEd25519PrivateKey(t *testing.T) { } } +/* +TestGenerateEd25519Signature will use an in-memory ed25519 key for testing the signature generation. +*/ func TestGenerateEd25519Signature(t *testing.T) { validKey := Key{ KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", @@ -294,6 +323,9 @@ func TestGenerateEd25519Signature(t *testing.T) { } } +/* +VerifyEd25519Signature will test the generic VerifySignature function with ed25519 signatures and keys. +*/ func TestVerifyEd25519Signature(t *testing.T) { validSig := Signature{ KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", @@ -350,6 +382,9 @@ func TestVerifyEd25519Signature(t *testing.T) { } } +/* +TestInvalidKeyComponent will test for invalid Key components like keyType, scheme or hash-algorithms +*/ func TestInvalidKeyComponent(t *testing.T) { // The following is an invalid SetKeyComponents call var key Key @@ -359,6 +394,9 @@ func TestInvalidKeyComponent(t *testing.T) { } } +/* +TestInvalidPEMKey tests the ParseKey function with an empty byte slice. +*/ func TestInvalidPEMKey(t *testing.T) { _, err := ParseKey([]byte{}) if !errors.Is(err, ErrFailedPEMParsing) { @@ -366,6 +404,10 @@ func TestInvalidPEMKey(t *testing.T) { } } +/* +TestLoadKey loads various invalid keys and makes sure none of them can be loaded. +Also it checks the correct error message. +*/ func TestLoadKey(t *testing.T) { tables := []struct { name string @@ -397,7 +439,11 @@ func TestLoadKey(t *testing.T) { } } -func TestGenerateKey(t *testing.T) { +/* +TestGenerateSignature tries to generate a signature with unsupported keys, like pure EC private keys or +ecdsa keys with wrong ciphers. +*/ +func TestGenerateSignature(t *testing.T) { tables := []struct { name string signable []byte From 84c9874b3db598e4040290bf187fe76c186d0677 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 29 Jul 2020 00:41:15 +0200 Subject: [PATCH 49/86] add ecdsa support This commit adds support for the ecdsa key scheme. We should support all FIPS 186-3 curves out of the box. However, we must note, that if we ever upgrade our hashing algorithm from SHA256, the code will get more complex, because the hash size must satisfy the curve size, otherwise the hash may get truncated. The latter could result in a security vulnerability (forming a hash, which truncated part is equal to the truncated part of the to verified bytes). Furthermore, we do no curve detection here and just save the signature parts r and s into a byte slice without a fixed length. Also it's still unclear if r and s are always the same of the size --- in_toto/keylib.go | 64 +++++++++++++++++++++++++++++++++++++----- in_toto/keylib_test.go | 5 ++-- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 13d7aaac..94eb188e 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -2,6 +2,7 @@ package in_toto import ( "crypto" + "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha256" @@ -12,6 +13,7 @@ import ( "fmt" "golang.org/x/crypto/ed25519" "io/ioutil" + "math/big" "os" "strings" ) @@ -28,6 +30,9 @@ var ErrUnsupportedKeyType = errors.New("unsupported key type") // ErrInvalidSignature is returned when the signature is invalid var ErrInvalidSignature = errors.New("invalid signature") +// ErrInvalidKeyType is returned when the key is valid, but it has a wrong key type attached to it +var ErrInvalidKeyType = errors.New("valid key, but not matching key type detected") + /* GenerateKeyId creates a partial key map and generates the key ID based on the created partial key map via the SHA256 method. @@ -83,7 +88,7 @@ Furthermore it makes sure to remove any trailing and leading whitespaces or newl func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyType string, scheme string, keyIdHashAlgorithms []string) error { // assume we have a privateKey if the key size is bigger than 0 switch keyType { - case "rsa": + case "rsa", "ecdsa": // We need to treat RSA differently, because of interoperability // reasons with the securesystemslib and the in-toto python // implementation @@ -160,6 +165,7 @@ The following key types are supported: * ed25519 * RSA + * ecdsa On success it will return nil. The following errors can happen: @@ -225,6 +231,14 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) if err := k.SetKeyComponents(pubKeyBytes.(ed25519.PublicKey), key.(ed25519.PrivateKey), "ed25519", scheme, keyIdHashAlgorithms); err != nil { return err } + case *ecdsa.PrivateKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*ecdsa.PrivateKey).Public()) + if err != nil { + return err + } + if err := k.SetKeyComponents(pubKeyBytes, pemBytes, "ecdsa", scheme, keyIdHashAlgorithms); err != nil { + return err + } default: return fmt.Errorf("%w: %T", ErrUnsupportedKeyType, key) } @@ -248,7 +262,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { // with the securesystemslib and the python implementation // in which we are storing RSA keys in PEM format, but ed25519 keys hex encoded. switch key.KeyType { - case "rsa": + case "rsa", "ecdsa": keyReader := strings.NewReader(key.KeyVal.Private) pemBytes, err := ioutil.ReadAll(keyReader) if err != nil { @@ -268,12 +282,30 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { hashed := sha256.Sum256(signable) switch parsedKey.(type) { case *rsa.PrivateKey: + if key.KeyType != "rsa" { + return signature, ErrInvalidKeyType + } // We use rand.Reader as secure random source for rsa.SignPSS() signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) if err != nil { return signature, err } + case *ecdsa.PrivateKey: + if key.KeyType != "ecdsa" { + return signature, ErrInvalidKeyType + } + // ecdsa.Sign returns a signature that consists of two components called: r and s + // We assume here, that r and s are of the same size nLen and that + // the signature is 2*nLen. Furthermore we must note that hashes get truncated + // if they are too long for the curve. We use SHA256 for hashing, thus we should be + // ok with using the FIPS186-3 curves P256, P384 and P521. + r, s, err := ecdsa.Sign(rand.Reader, parsedKey.(*ecdsa.PrivateKey), hashed[:]) + if err != nil { + return signature, nil + } + signatureBuffer = append(signatureBuffer, r.Bytes()...) + signatureBuffer = append(signatureBuffer, s.Bytes()...) default: return signature, fmt.Errorf("%w: %T", ErrUnsupportedKeyType, parsedKey) } @@ -309,7 +341,7 @@ it will return an error. */ func VerifySignature(key Key, sig Signature, unverified []byte) error { switch key.KeyType { - case "rsa": + case "rsa", "ecdsa": // Create rsa.PublicKey object from DER encoded public key string as // found in the public part of the keyval part of a securesystemslib key dict keyReader := strings.NewReader(key.KeyVal.Public) @@ -329,10 +361,28 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { return err } hashed := sha256.Sum256(unverified) - sigHex, _ := hex.DecodeString(sig.Sig) - err = rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigHex, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) - if err != nil { - return fmt.Errorf("%w: %s", ErrInvalidSignature, err) + sigBytes, _ := hex.DecodeString(sig.Sig) + switch parsedKey.(type) { + case *rsa.PublicKey: + err = rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigBytes, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return fmt.Errorf("%w: %s", ErrInvalidSignature, err) + } + case *ecdsa.PublicKey: + // We assume that r and s are of equal length. + rsSize := len(sigBytes) / 2 + // We need to create two big int from scratch + // and set the bytes manually for each of them + r := new(big.Int) + s := new(big.Int) + r.SetBytes(sigBytes[:rsSize]) + s.SetBytes(sigBytes[:rsSize]) + // This may fail if a bigger hashing algorithm than SHA256 has been used for generating the signature + if err := ecdsa.Verify(parsedKey.(*ecdsa.PublicKey), hashed[:], r, s); err == false { + return ErrInvalidSignature + } + default: + return fmt.Errorf("%w: Key has type %T", ErrInvalidSignature, parsedKey) } case "ed25519": pubHex, err := hex.DecodeString(key.KeyVal.Public) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 876d7a38..fedfe52d 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -419,7 +419,6 @@ func TestLoadKey(t *testing.T) { {"Test non existing path", "invalid.txt", "ed25519", []string{"sha256", "sha512"}, "open invalid.txt: no such file or directory"}, {"Test invalid file", "canonical-test.link", "ecdsa", []string{"sha256", "sha512"}, "failed to decode the data as PEM block (are you sure this is a pem file?)"}, {"Test unsupported EC private key", "erin", "ecdsa", []string{"sha256", "sha512"}, "failed parsing the PEM block: unsupported PEM type"}, - {"Test unsupported PKCS8 EC key", "frank", "ecdsa", []string{"sha256", "sha512"}, "unsupported key type: *ecdsa.PrivateKey"}, } for _, table := range tables { @@ -459,7 +458,7 @@ func TestGenerateSignature(t *testing.T) { Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", }, Scheme: "ecdsa", - }, "unsupported key type: ecdsa"}, + }, "failed parsing the PEM block: unsupported PEM type"}, {"Test wrong KeyType", []byte{}, Key{ KeyId: "", KeyIdHashAlgorithms: []string{"sha256", "sha512"}, @@ -478,7 +477,7 @@ func TestGenerateSignature(t *testing.T) { Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----", }, - }, "unsupported key type: *ecdsa.PrivateKey"}, + }, "valid key, but not matching key type detected"}, {"Test invalid hex string for ed25519", []byte{}, Key{ KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", KeyIdHashAlgorithms: []string{"sha256", "sha512"}, From 1b7c8f580b1cf1a6b2b623c457ee2c97172c06e2 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 29 Jul 2020 01:00:27 +0200 Subject: [PATCH 50/86] Remove unnecessary byte transformations We can directly cast key.KeyVal.(Public|Private) to a byte slice. No need, for the string reader. --- in_toto/keylib.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 94eb188e..e0ec3222 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -263,15 +263,10 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { // in which we are storing RSA keys in PEM format, but ed25519 keys hex encoded. switch key.KeyType { case "rsa", "ecdsa": - keyReader := strings.NewReader(key.KeyVal.Private) - pemBytes, err := ioutil.ReadAll(keyReader) - if err != nil { - return signature, err - } // pam.Decode returns the parsed pem block and a rest. // The rest is everything, that could not be parsed as PEM block. // Therefore we can drop this via using the blank identifier "_" - data, _ := pem.Decode(pemBytes) + data, _ := pem.Decode([]byte(key.KeyVal.Private)) if data == nil { return signature, ErrNoPEMBlock } @@ -342,17 +337,10 @@ it will return an error. func VerifySignature(key Key, sig Signature, unverified []byte) error { switch key.KeyType { case "rsa", "ecdsa": - // Create rsa.PublicKey object from DER encoded public key string as - // found in the public part of the keyval part of a securesystemslib key dict - keyReader := strings.NewReader(key.KeyVal.Public) - pemBytes, err := ioutil.ReadAll(keyReader) - if err != nil { - return err - } // pam.Decode returns the parsed pem block and a rest. // The rest is everything, that could not be parsed as PEM block. // Therefore we can drop this via using the blank identifier "_" - data, _ := pem.Decode(pemBytes) + data, _ := pem.Decode([]byte(key.KeyVal.Public)) if data == nil { return ErrNoPEMBlock } From 5b8743dde651a999148c5a3b51ea1e87d946c9cc Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 29 Jul 2020 21:32:16 +0200 Subject: [PATCH 51/86] add missing case for ecdsa public key This adds a missing case for ecdsa public key. The RSA and ecdsa key cases can be merged, maybe. --- in_toto/keylib.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index e0ec3222..cafe0bcf 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -239,6 +239,10 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) if err := k.SetKeyComponents(pubKeyBytes, pemBytes, "ecdsa", scheme, keyIdHashAlgorithms); err != nil { return err } + case *ecdsa.PublicKey: + if err := k.SetKeyComponents(pemBytes, []byte{}, "ecdsa", scheme, keyIdHashAlgorithms); err != nil { + return err + } default: return fmt.Errorf("%w: %T", ErrUnsupportedKeyType, key) } From a275a2592059d0f51c3cc2faca33341d0b9e39d5 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 29 Jul 2020 22:00:01 +0200 Subject: [PATCH 52/86] Fix ecdsa Signature Before this commit we used two times r instead of r and s. Of course ecdsa validation failed, because of this. --- in_toto/keylib.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index cafe0bcf..6b959848 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -368,7 +368,7 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { r := new(big.Int) s := new(big.Int) r.SetBytes(sigBytes[:rsSize]) - s.SetBytes(sigBytes[:rsSize]) + s.SetBytes(sigBytes[rsSize:]) // This may fail if a bigger hashing algorithm than SHA256 has been used for generating the signature if err := ecdsa.Verify(parsedKey.(*ecdsa.PublicKey), hashed[:], r, s); err == false { return ErrInvalidSignature From 4910972a460256bf45d620c1ec1f1b928c9e5908 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 29 Jul 2020 22:34:51 +0200 Subject: [PATCH 53/86] Complete Rewrite of keylib_test.go This commit rewrites the keylib tests completely from scratch. I have replaced all tests against table based testing methods The LOC shrinked from over 500 to under 100. --- in_toto/keylib_test.go | 551 ++++++----------------------------------- 1 file changed, 74 insertions(+), 477 deletions(-) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index fedfe52d..1a2953ad 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -1,503 +1,100 @@ package in_toto import ( - "encoding/hex" "errors" - "fmt" "os" "testing" ) -/* -TestLoadRSAPublicKey loads the key test/data/alice.pub and compares it against -our specified keyID. Furthermore it will try to load a file, that is not a key -and makes sure, that LoadKey fails with such files. Same for non existing file paths. -*/ -func TestLoadRSAPublicKey(t *testing.T) { - // Test loading valid public rsa key from pem-formatted file - var key Key - err := key.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}) - if err != nil { - t.Errorf("LoadRSAPublicKey returned %s, expected no error", err) - } - expectedKeyID := "556caebdc0877eed53d419b60eddb1e57fa773e4e31d70698b588f3e9cc48b35" - if key.KeyId != expectedKeyID { - t.Errorf("LoadRSAPublicKey parsed KeyId '%s', expected '%s'", - key.KeyId, expectedKeyID) - } - - // Test loading error: - // - Not a pem formatted rsa public key - expectedError := "Could not find a public key PEM block" - err = key.LoadKey("demo.layout.template", "rsassa-pss-sha256", []string{"sha256", "sha512"}) - if !errors.Is(err, ErrNoPEMBlock) { - t.Errorf("LoadRSAPublicKey returned (%s), expected '%s' error", err, - expectedError) - } - - // Test not existing file - err = key.LoadKey("inToToRocks", "rsassa-pss-sha256", []string{"sha256", "sha512"}) - if !errors.Is(err, os.ErrNotExist) { - t.Errorf("Invalid file load returned (%s), expected '%s' error", err, os.ErrNotExist) - } -} - -/* -TestLoadRSAPrivateKey loads the RSA private key test/data/dan and will compare it -against our specified keyID -*/ -func TestLoadRSAPrivateKey(t *testing.T) { - // Test loading valid Private rsa key from pem-formatted file - var key Key - err := key.LoadKey("dan", "rsassa-pss-sha256", []string{"sha256", "sha512"}) - if err != nil { - t.Errorf("LoadKeyKey returned %s, expected no error", err) - } - expectedKeyID := "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401" - if key.KeyId != expectedKeyID { - t.Errorf("LoadKeyKey parsed KeyId '%s', expected '%s'", - key.KeyId, expectedKeyID) - } - - err = key.LoadKey("demo.layout.template", "", []string{}) - if err == nil || !errors.Is(err, ErrNoPEMBlock) { - t.Errorf("LoadKey returned (%s), expected '%s' error", err, - ErrNoPEMBlock.Error()) - } - - // Test not existing file - err = key.LoadKey("inToToRocks", "", []string{}) - if !errors.Is(err, os.ErrNotExist) { - t.Errorf("Invalid file load returned (%s), expected '%s' error", err, os.ErrNotExist) - } -} - -/* -TestGenerateRSASignature generates a valid in-memory RSA key for generating a signature. -This signature should always be valid, with our specified in-memory RSA key. -*/ -func TestGenerateRSASignature(t *testing.T) { - validKey := Key{ - KeyId: "f29cb6877d14ebcf28b136a96a4d64935522afaddcc84e6b70ff6b9eaefb8fcf", - KeyType: "rsa", - KeyVal: KeyVal{ - Public: `-----BEGIN PUBLIC KEY----- -MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l -8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP -r3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz -eUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT -vpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2 -LFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5 -ujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/ -Vk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf -p8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE= ------END PUBLIC KEY-----`, - Private: `-----BEGIN RSA PRIVATE KEY----- -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 ------END RSA PRIVATE KEY-----`, - }, - } - // We are not verifying the signature yet.. - validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` - validSig, err := GenerateSignature([]byte(validData), validKey) - if err != nil { - t.Errorf("GenerateRSASignature from validKey and data failed: %s", err) - } - if err := VerifySignature(validKey, validSig, []byte(validData)); err != nil { - t.Errorf("VerifyRSASignature from validSignature and data has failed: %s", err) - } -} - -/* -TestVerifyRSASignature uses in-memory signatures and keys for validating, -that we succeed and fail as expected. -*/ -func TestVerifyRSASignature(t *testing.T) { - validSig := Signature{ - KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498", - Sig: "a8adf1587659ca1d064b2d64debb6f03cba08a01d6d13c8b205ac7cb79ab8729159" + - "ba6119762ac6a9a14d5ad675fd2e42ba8eb5a074ed5e8edb69fd34ad2c1b02d6d16" + - "8c097bf4b9f063c49d23384d9002c03a3f20f307ec748baad8fb4d76ae11a96c9c9" + - "0d9f663ddd1c0161fe22cfe528a9a5a8894806982a9e437664cfd55a56ebc8d61e9" + - "5efa66fe5b0bc9241829629033a0f1eee382c3181731cc8f5a9687a4045af572fed" + - "2e1835226ad00f91cc5799e325f532975190bfb685904aa81dd181421f3cfa04608" + - "0466c060cc3400e29d4d86b8f10764f2a1af865a1ffad2cde69cb540b38c1e7e42c" + - "fdd4a907fa1d38c99b46fcea2ddfab1b75372c1021f0c901165b6a1a8768f345641" + - "489a23489d3b909ce0c8b774060a0ab5083df7f8026a83aa66b3668410956d8b01d" + - "93b811d23cd276765ddbf41d54287994f5f8ff4ad4b94fcdb1e4d7ad407ee2a46c4" + - "3f51e436b46a9670f5d05e706a6cb0d68afc0e999c2407267879291d082a30ade2a" + - "49ea3e764c6eb1baa65f1d49b7a24bf", - } - - validKey := Key{ - KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498", - KeyType: "rsa", - KeyVal: KeyVal{ - Public: `-----BEGIN PUBLIC KEY----- -MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAzgLBsMFSgwBiWTBmVsyW -5KbJwLFSodAzdUhU2Bq6SdRz/W6UOBGdojZXibxupjRtAaEQW/eXDe+1CbKg6ENZ -Gt2D9HGFCQZgQS8ONgNDQGiNxgApMA0T21AaUhru0vEofzdN1DfEF4CAGv5AkcgK -salhTyONervFIjFEdXGelFZ7dVMV3Pp5WkZPG0jFQWjnmDZhUrtSxEtqbVghc3kK -AUj9Ll/3jyi2wS92Z1j5ueN8X62hWX2xBqQ6nViOMzdujkoiYCRSwuMLRqzW2CbT -L8hF1+S5KWKFzxl5sCVfpPe7V5HkgEHjwCILXTbCn2fCMKlaSbJ/MG2lW7qSY2Ro -wVXWkp1wDrsJ6Ii9f2dErv9vJeOVZeO9DsooQ5EuzLCfQLEU5mn7ul7bU7rFsb8J -xYOeudkNBatnNCgVMAkmDPiNA7E33bmL5ARRwU0iZicsqLQR32pmwdap8PjofxqQ -k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= ------END PUBLIC KEY-----`, - }, - } - validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` - - // Test verifying valid signature - err := VerifySignature(validKey, validSig, []byte(validData)) - if err != nil { - t.Errorf("VerifyRSASignature returned '%s', expected nil", err) - } - - // Test signature verification errors: - // - Right signature and key, but wrong data - // - Right signature and data, but wrong key - // - Right signature and data, but invalid key - // - Right key and data, but wrong signature - // - Right key and data, but invalid signature - var wrongKey Key - if err := wrongKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil { - fmt.Printf("Unable to load key alice.pub: %s", err) - } - wrongSig := Signature{ - KeyId: validSig.KeyId, - Sig: "b" + validSig.Sig[1:], - } - - sigs := []Signature{validSig, validSig, validSig, wrongSig, {}} - keys := []Key{validKey, wrongKey, {}, validKey, validKey} - data := []string{"bad data", validData, validData, validData, validData} - - for i := 0; i < len(sigs); i++ { - err := VerifySignature(keys[i], sigs[i], []byte(data[i])) - if err == nil { - t.Errorf("VerifyRSASignature returned '%s', expected error", err) +// TestLoadKey makes sure, that our LoadKey function loads keys correctly +// and that the key IDs of private and public key match. +func TestLoadKey(t *testing.T) { + validTables := []struct { + name string + path string + scheme string + hashAlgorithms []string + expectedKeyID string + }{ + {"rsa public key", "alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}, "556caebdc0877eed53d419b60eddb1e57fa773e4e31d70698b588f3e9cc48b35"}, + {"rsa private key", "dan", "rsassa-pss-sha256", []string{"sha256", "sha512"}, "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401"}, + {"rsa public key", "dan.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}, "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401"}, + {"ed25519 private key", "carol", "ed25519", []string{"sha256", "sha512"}, "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6"}, + {"ed25519 public key", "carol.pub", "ed25519", []string{"sha256", "sha512"}, "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6"}, + {"ecdsa private key", "frank", "ecdsa", []string{"sha256", "sha512"}, "d4cd6865653c3aaa9b9eb865e0e45dd8ed58c98cb39c0145d500e009d9817c32"}, + {"ecdsa public key", "frank.pub", "ecdsa", []string{"sha256", "sha512"}, "d4cd6865653c3aaa9b9eb865e0e45dd8ed58c98cb39c0145d500e009d9817c32"}, + } + for _, table := range validTables { + var key Key + err := key.LoadKey(table.path, table.scheme, table.hashAlgorithms) + if err != nil { + t.Errorf("failed key.LoadKey() for %s %s. Error: %s", table.name, table.path, err) + } + if table.expectedKeyID != key.KeyId { + t.Errorf("keyID for %s %s does not match expected keyID: %s. Got keyID: %s", table.name, table.path, table.expectedKeyID, key.KeyId) } - } - - // pem.Decode errors - invalidKey := Key{ - KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498", - KeyIdHashAlgorithms: []string{"sha256", "sha512"}, - KeyType: "rsa", - KeyVal: KeyVal{ - Public: "INVALID", - }, - Scheme: "rsassa-pss-sha256", - } - // just trigger pem.Decode function - err = VerifySignature(invalidKey, Signature{}, []byte{}) - if !errors.Is(err, ErrNoPEMBlock) { - t.Errorf("VerifySignature returned '%s', should got '%s'", err, ErrNoPEMBlock) - } - - // Test ParseKey errors via providing an EC key, but with wrong key type - invalidECKey := Key{ - KeyId: "", - KeyType: "rsa", - KeyVal: KeyVal{ - Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", - }, - } - // just trigger ParseKey function - err = VerifySignature(invalidECKey, Signature{}, []byte{}) - if !errors.Is(err, ErrFailedPEMParsing) { - t.Errorf("VerifySignature returned '%s', should got '%s'", err, ErrFailedPEMParsing) - } -} - -/* -TestLoadEd25519PublicKey will load the ed25519 public key test/data/carol.pub and will -compare it against our specified keyID. It will also try to load different non existing keys, while -specifying the ed25519 scheme (although the keys are all invalid). -*/ -func TestLoadEd25519PublicKey(t *testing.T) { - var key Key - if err := key.LoadKey("carol.pub", "ed25519", []string{"sha256", "sha512"}); err != nil { - t.Errorf("Failed to load ed25519 public key from file: (%s)", err) - } - - expectedPubKey := "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6" - if expectedPubKey != key.KeyId { - t.Errorf("Loaded pubkey is not the expected key") - } - - // try to load nonexistent file - if err := key.LoadKey("this-does-not-exist", "ed25519", []string{"sha256", "sha512"}); err == nil { - t.Errorf("LoadEd25519PublicKey loaded a file that does not exist") - } - - // load invalid file - if err := key.LoadKey("bob-invalid.pub", "ed25519", []string{"sha256", "sha512"}); err == nil { - t.Errorf("LoadEd25519PublicKey has successfully loaded an invalid key file") - } -} - -/* -TestLoadEd25519PrivateKey loads the ed25519 private key test/data/carol and compares it against our specified -keyID. -*/ -func TestLoadEd25519PrivateKey(t *testing.T) { - var key Key - if err := key.LoadKey("carol", "ed25519", []string{"sha256", "sha512"}); err != nil { - t.Errorf("Failed to load ed25519 public key from file: (%s)", err) - } - - expectedPrivateKey := "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6" - if expectedPrivateKey != key.KeyId { - t.Errorf("Loaded pubkey is not the expected key") - } - - // try to load nonexistent file - if err := key.LoadKey("this-does-not-exist", "ed25519", []string{"sha256", "sha512"}); err == nil { - t.Errorf("LoadEd25519PublicKey loaded a file that does not exist") - } - - // load invalid file - if err := key.LoadKey("bob-invalid.pub", "ed25519", []string{"sha256", "sha512"}); err == nil { - t.Errorf("LoadEd25519PublicKey has successfully loaded an invalid key file") - } -} - -/* -TestGenerateEd25519Signature will use an in-memory ed25519 key for testing the signature generation. -*/ -func TestGenerateEd25519Signature(t *testing.T) { - validKey := Key{ - KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - KeyType: "ed25519", - KeyVal: KeyVal{ - Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", - Private: "29ad59693fe94c9d623afbb66554b4f6bb248c47761689ada4875ebda94840ae393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", - }, - } - // We are not verifying the signature yet.. - validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` - validSig, err := GenerateSignature([]byte(validData), validKey) - if err != nil { - t.Errorf("GenerateEd25519Signature from validKey and data failed: %s", err) - } - if err := VerifySignature(validKey, validSig, []byte(validData)); err != nil { - t.Errorf("VerifyEd25519Signature from validSignature and data has failed: %s", err) - } -} - -/* -VerifyEd25519Signature will test the generic VerifySignature function with ed25519 signatures and keys. -*/ -func TestVerifyEd25519Signature(t *testing.T) { - validSig := Signature{ - KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - Sig: "f41d704809c0ae2356e1beaaf3432f4abfaaa4a26c043087d9eb6dc12b4a3c5df73f8c47a4e969e815a5d2c9853d7eba208b48c7459f6b865cd0b51a94e6d704", - } - - validKey := Key{ - KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - KeyType: "ed25519", - KeyVal: KeyVal{ - Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", - }, - } - validData := `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}` - - // Test verifying valid signature - err := VerifySignature(validKey, validSig, []byte(validData)) - if err != nil { - t.Errorf("VerifyEd25519Signature returned '%s', expected nil", err) - } - - invalidSig := Signature{ - KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - Sig: "f41d704809c0ae2356e1beaaf3432f4abfaa", - } - - invalidKey := Key{ - KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - KeyType: "ed25519", - KeyVal: KeyVal{ - Public: "INVALID", - }, - } - - invalidHexSig := Signature{ - KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - Sig: "INVALID", - } - - err = VerifySignature(validKey, invalidSig, []byte(validData)) - if !errors.Is(err, ErrInvalidSignature) { - t.Errorf("VerifyEd25519Signature returned '%s', expected '%s'", err, ErrInvalidSignature) - } - - err = VerifySignature(invalidKey, validSig, []byte(validData)) - var hexError hex.InvalidByteError - if !errors.As(err, &hexError) { - t.Errorf("VerifyEd25519Signature returned '%s', expected '%s'", err, hexError) - } - - err = VerifySignature(validKey, invalidHexSig, []byte(validData)) - if !errors.As(err, &hexError) { - t.Errorf("VerifyEd25519Signature returned '%s', expected '%s'", err, hexError) - } -} - -/* -TestInvalidKeyComponent will test for invalid Key components like keyType, scheme or hash-algorithms -*/ -func TestInvalidKeyComponent(t *testing.T) { - // The following is an invalid SetKeyComponents call - var key Key - err := key.SetKeyComponents([]byte{}, []byte{}, "inToTo", "scheme", []string{"md5", "yolo"}) - if !errors.Is(err, ErrUnsupportedKeyType) { - t.Errorf("TestInvalidKeyComponent failed. We got: %s, we should have got: %s", err, ErrUnsupportedKeyType) - } -} - -/* -TestInvalidPEMKey tests the ParseKey function with an empty byte slice. -*/ -func TestInvalidPEMKey(t *testing.T) { - _, err := ParseKey([]byte{}) - if !errors.Is(err, ErrFailedPEMParsing) { - t.Errorf("TestInvalidPEMKey failed with zero byte data as test key. We got: %s, we should have got: %s", err, ErrFailedPEMParsing) } } -/* -TestLoadKey loads various invalid keys and makes sure none of them can be loaded. -Also it checks the correct error message. -*/ -func TestLoadKey(t *testing.T) { - tables := []struct { - name string - path string - scheme string - keyIdHashAlgorithms []string - result string +// TestValidSignatures utilizes our TestLoadKey function, but does not check the expected keyID. +// Instead the test function generates a signature via GenerateSignature() over valid data and verifies the data +// via ValidateSignature() with the from the private key extracted public key. We know that our extracted public key +// is the same as our single public key because we have tested this in the TestLoadKey function. +func TestValidSignatures(t *testing.T) { + validTables := []struct { + name string + path string + scheme string + hashAlgorithms []string + signable string }{ - {"Test non existing path", "invalid.txt", "ed25519", []string{"sha256", "sha512"}, "open invalid.txt: no such file or directory"}, - {"Test invalid file", "canonical-test.link", "ecdsa", []string{"sha256", "sha512"}, "failed to decode the data as PEM block (are you sure this is a pem file?)"}, - {"Test unsupported EC private key", "erin", "ecdsa", []string{"sha256", "sha512"}, "failed parsing the PEM block: unsupported PEM type"}, + {"rsa private key", "dan", "rsassa-pss-sha256", []string{"sha256", "sha512"}, `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}`}, + {"ed25519 private key", "carol", "ed25519", []string{"sha256", "sha512"}, `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}`}, + {"ecdsa private key", "frank", "ecdsa", []string{"sha256", "sha512"}, `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}`}, } - for _, table := range tables { - // initialize empty key object + for _, table := range validTables { var key Key - err := key.LoadKey(table.path, table.scheme, table.keyIdHashAlgorithms) - // We need to handle the non existing path error differently, because OS produce different error messages here. - if table.name == "Test non existing path" { - if err == nil { - t.Errorf("%s: Loadkey('%s', '%s', '%s') failed with '%s', should got nil", table.name, table.path, table.scheme, table.keyIdHashAlgorithms, err) - } - // NOTE: some errors do not support errors.Is() yet, therefore we need to compare the error strings here - // This can lead to nil pointer dereference - } else if err.Error() != table.result { - t.Errorf("%s: Loadkey('%s', '%s', '%s') failed with '%s', should got '%s'", table.name, table.path, table.scheme, table.keyIdHashAlgorithms, err, table.result) + err := key.LoadKey(table.path, table.scheme, table.hashAlgorithms) + if err != nil { + t.Errorf("failed key.LoadKey() for %s %s. Error: %s", table.name, table.path, err) + } + validSig, err := GenerateSignature([]byte(table.signable), key) + if err != nil { + t.Errorf("failed GenerateSignature() for %s %s. Error: %s", table.name, table.path, err) + } + // We can directly verify the signatures, because all our key objects have been created from a private key + // therefore we are able to use the extracted public key for validating the signature. + err = VerifySignature(key, validSig, []byte(table.signable)) + if err != nil { + t.Errorf("failed VerifySignature() for %s %s. Error: %s", table.name, table.path, err) } } } -/* -TestGenerateSignature tries to generate a signature with unsupported keys, like pure EC private keys or -ecdsa keys with wrong ciphers. -*/ -func TestGenerateSignature(t *testing.T) { - tables := []struct { - name string - signable []byte - key Key - result string +// TestLoadKeyErrors tests the LoadKey functions for the most popular errors: +// +// * os.ErrNotExist (triggered, when the file does not exist) +// * ErrNoPEMBlock (for example if the passed file is not a PEM block) +// * ErrFailedPEMParsing (for example if we pass an EC key, instead a key in PKC8 format) +func TestLoadKeyErrors(t *testing.T) { + invalidTables := []struct { + name string + path string + scheme string + hashAlgorithms []string + err error }{ - {"Test unsupported EC private key", []byte{}, Key{ - KeyId: "", - KeyIdHashAlgorithms: []string{"sha256", "sha512"}, - KeyType: "ecdsa", - KeyVal: KeyVal{ - Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----\n", - Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", - }, - Scheme: "ecdsa", - }, "failed parsing the PEM block: unsupported PEM type"}, - {"Test wrong KeyType", []byte{}, Key{ - KeyId: "", - KeyIdHashAlgorithms: []string{"sha256", "sha512"}, - KeyType: "rsa", - KeyVal: KeyVal{ - Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----\n", - Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", - }, - Scheme: "ecdsa", - }, "failed parsing the PEM block: unsupported PEM type"}, - {"Test wrong KeyType, but valid PKCS8 key", []byte{}, Key{ - KeyId: "", - KeyIdHashAlgorithms: []string{"sha256", "sha512"}, - KeyType: "rsa", - KeyVal: KeyVal{ - Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", - Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----", - }, - }, "valid key, but not matching key type detected"}, - {"Test invalid hex string for ed25519", []byte{}, Key{ - KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - KeyIdHashAlgorithms: []string{"sha256", "sha512"}, - KeyType: "ed25519", - KeyVal: KeyVal{ - Private: "INVALID", - Public: "INVALID", - }, - Scheme: "ed25519", - }, "encoding/hex: invalid byte: U+0049 'I'"}, + {"not existing file", "inToToRocks", "rsassa-pss-sha256", []string{"sha256", "sha512"}, os.ErrNotExist}, + {"existing, but invalid file", "demo.layout.template", "ecdsa", []string{"sha512"}, ErrNoPEMBlock}, + {"EC private key file", "erin", "ecdsa", []string{"sha256", "sha512"}, ErrFailedPEMParsing}, } - for _, table := range tables { - _, err := GenerateSignature(table.signable, table.key) - // Note: Some of our errors do not yet support Go 1.13 error handling - // Thus we need to compare strings :(, this can lead to a nil pointer - // dereference. If you encounter a nil pointer dereference, expect that - // the GenerateSignature() func failed. - if err.Error() != table.result { - t.Errorf("%s: GenerateKey failed with '%s', should got '%s'", table.name, table.result, err) + for _, table := range invalidTables { + var key Key + err := key.LoadKey(table.path, table.scheme, table.hashAlgorithms) + if !errors.Is(err, table.err) { + t.Errorf("failed LoadKey() for %s %s, got error: %s. Should have: %s", table.name, table.path, err, table.err) } } } From 79383c9002f2d9afd7dfa0daa5a769d8e4ed1169 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 30 Jul 2020 17:54:00 +0200 Subject: [PATCH 54/86] implement proper ecdsa signature encoding We now use ASN1.DER for encoding the ecdsa signature parameters r and s. This fixes our problems with different r and s lengths. Furthermore it is the same way to deal with the signature as in our securesystemslib and it is therefore the interoperable way to handle an ecdsa signature. With Go 1.15 we might can switch to new ecdsa ASN1Sign methods, if necessary. --- in_toto/keylib.go | 28 ++++++++++++++++------------ in_toto/model.go | 27 +++++++++++++++++++-------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 6b959848..2bdc6132 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -7,13 +7,13 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" + "encoding/asn1" "encoding/hex" "encoding/pem" "errors" "fmt" "golang.org/x/crypto/ed25519" "io/ioutil" - "math/big" "os" "strings" ) @@ -303,8 +303,12 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { if err != nil { return signature, nil } - signatureBuffer = append(signatureBuffer, r.Bytes()...) - signatureBuffer = append(signatureBuffer, s.Bytes()...) + // Generate the ecdsa signature on the same way, as we do in the securesystemslib + // We construct a + signatureBuffer, err = asn1.Marshal(EcdsaSignature{ + R: r, + S: s, + }) default: return signature, fmt.Errorf("%w: %T", ErrUnsupportedKeyType, parsedKey) } @@ -361,16 +365,16 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { return fmt.Errorf("%w: %s", ErrInvalidSignature, err) } case *ecdsa.PublicKey: - // We assume that r and s are of equal length. - rsSize := len(sigBytes) / 2 - // We need to create two big int from scratch - // and set the bytes manually for each of them - r := new(big.Int) - s := new(big.Int) - r.SetBytes(sigBytes[:rsSize]) - s.SetBytes(sigBytes[rsSize:]) + var ecdsaSignature EcdsaSignature + // Unmarshal the ASN.1 DER marshalled ecdsa signature to + // ecdsaSignature. asn1.Unmarshal returns the rest and an error + // we can skip the rest here.. + _, err := asn1.Unmarshal(sigBytes, &ecdsaSignature) + if err != nil { + return err + } // This may fail if a bigger hashing algorithm than SHA256 has been used for generating the signature - if err := ecdsa.Verify(parsedKey.(*ecdsa.PublicKey), hashed[:], r, s); err == false { + if err := ecdsa.Verify(parsedKey.(*ecdsa.PublicKey), hashed[:], ecdsaSignature.R, ecdsaSignature.S); err == false { return ErrInvalidSignature } default: diff --git a/in_toto/model.go b/in_toto/model.go index fff913b1..9f431e7e 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math/big" "os" "reflect" "regexp" @@ -12,15 +13,13 @@ import ( ) /* -validateHexString is used to validate that a string passed to it contains -only valid hexadecimal characters. +EcdsaSignature is being used for constructing an ASN.1 marshalled ecdsa +signature. We use *big.Int here and not big.Int, because all functions +on big.Int are pointer receivers. `asn1:"tag2"` refers to ASN.1 INTEGER. */ -func validateHexString(str string) error { - formatCheck, _ := regexp.MatchString("^[a-fA-F0-9]+$", str) - if !formatCheck { - return fmt.Errorf("'%s' is not a valid hex string", str) - } - return nil +type EcdsaSignature struct { + R *big.Int `asn1:"tag:2"` + S *big.Int `asn1:"tag:2"` } /* @@ -47,6 +46,18 @@ type Key struct { Scheme string `json:"scheme"` } +/* +validateHexString is used to validate that a string passed to it contains +only valid hexadecimal characters. +*/ +func validateHexString(str string) error { + formatCheck, _ := regexp.MatchString("^[a-fA-F0-9]+$", str) + if !formatCheck { + return fmt.Errorf("'%s' is not a valid hex string", str) + } + return nil +} + /* validatePubKey is a general function to validate if a key is a valid public key. */ From af0cf4cc8dec08df670d51c1a7ec62beab49bc6a Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 1 Aug 2020 01:39:41 +0200 Subject: [PATCH 55/86] increase test coverage this commit adds various new test cases for increasing the test coverage. Sadly, we still need to use the legacy error string comparing for a few. Maybe we can wrap them in an our own errors in the future? --- in_toto/keylib_test.go | 261 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 1a2953ad..99a2358c 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -98,3 +98,264 @@ func TestLoadKeyErrors(t *testing.T) { } } } + +func TestSetKeyComponents(t *testing.T) { + invalidTables := []struct { + name string + pubkeyBytes []byte + privateKeyBytes []byte + keyType string + scheme string + keyIdHashAlgorithms []string + }{ + {"test invalid key type", []byte{}, []byte{}, "yolo", "ed25519", []string{"sha512"}}, + } + + for _, table := range invalidTables { + var key Key + err := key.SetKeyComponents(table.pubkeyBytes, table.privateKeyBytes, table.keyType, table.scheme, table.keyIdHashAlgorithms) + if !errors.Is(err, ErrUnsupportedKeyType) { + t.Errorf("'%s' failed, should have: '%s', got: '%s'", table.name, ErrUnsupportedKeyType, err) + } + } +} + +func TestGenerateSignatureErrors(t *testing.T) { + invalidTables := []struct { + name string + key Key + expectedError error + }{ + {"invalid type", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: []string{"sha512"}, + KeyType: "invalid", + KeyVal: KeyVal{ + Private: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----", + Public: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAOT5nGyAPlkxJCD00qGf12YnsHGnfe2Z1j+RxyFkbE5w=\n-----END PUBLIC KEY-----", + }, + Scheme: "ed25519", + }, ErrUnsupportedKeyType, + }, + { + "public key", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: []string{"sha512"}, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAOT5nGyAPlkxJCD00qGf12YnsHGnfe2Z1j+RxyFkbE5w=\n-----END PUBLIC KEY-----", + Public: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----", + }, + Scheme: "ecdsa", + }, ErrUnsupportedKeyType, + }, + { + "rsa private key, but wrong key type", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----", + Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", + }, + Scheme: "rsassa-psa-sha256", + }, ErrInvalidKeyType, + }, + { + "ecdsa private key, but wrong key type", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----", + Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ecdsa", + }, ErrInvalidKeyType, + }, + { + "empty key", Key{}, ErrUnsupportedKeyType, + }, + { + "invalid ec private key", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ecdas", + }, ErrFailedPEMParsing, + }, + } + + for _, table := range invalidTables { + _, err := GenerateSignature([]byte("test"), table.key) + if !errors.Is(err, table.expectedError) { + t.Errorf("test '%s' failed, should got error: '%s', but received: '%s'", table.name, table.expectedError, err) + } + } + + // test cases that do not support errors.Is (Go 1.13 error handling) + legacyInvalidTables := []struct { + name string + key Key + expectedError string + }{ + {"invalid ed25519 private key", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: []string{"sha512"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "invalid", + Public: "invalid", + }, + Scheme: "ed25519"}, + "encoding/hex: invalid byte: U+0069 'i'"}, + } + + for _, table := range legacyInvalidTables { + _, err := GenerateSignature([]byte("test"), table.key) + if err.Error() != table.expectedError { + t.Errorf("test '%s' failed, should got error: '%s', but received: '%s'", table.name, table.expectedError, err) + } + } +} + +func TestVerifySignatureErrors(t *testing.T) { + invalidTables := []struct { + name string + key Key + sig Signature + expectedError error + }{ + {"invalid keytype", Key{}, Signature{}, ErrInvalidSignature}, + {"invalid rsa/ecdsa public key", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: nil, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "", + Public: "", + }, + Scheme: "rsassa-psa-sha256", + }, Signature{}, ErrNoPEMBlock, + }, + { + "ec public key", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: nil, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----", + Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ecdsa", + }, Signature{}, ErrFailedPEMParsing, + }, + { + "rsa private key as public key", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: nil, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", + Public: "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----", + }, + Scheme: "rsassa-psa-sha256", + }, Signature{}, ErrInvalidSignature, + }, + { + "invalid ecdsa signature", Key{ + KeyId: "d4cd6865653c3aaa9b9eb865e0e45dd8ed58c98cb39c0145d500e009d9817c32", + KeyIdHashAlgorithms: []string{"sha256", "sha512"}, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ecdsa", + }, Signature{ + KeyId: "d4cd6865653c3aaa9b9eb865e0e45dd8ed58c98cb39c0145d500e009d9817c32", + Sig: "308188824201fae620e5b53e878f2b5cc9b59b8246165ecf8fb3438115dff7ecd567106c707606dceac37ffe5fa531fc03ebe310ce9397d814f1d59c78ddd975123825f976141b824201bca2b5931850d0e8453c41d8a727f136d28a7683bad34c54643978ee066a1eab3403d9dd4e82641cd6325693ee32385aa7a0b5f239f53d8b8b1174f9751e1ee114", + }, ErrInvalidSignature, + }, + { + "invalid ed25519 signature", Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha256", "sha512"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "", + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + }, + Scheme: "ed25519", + }, Signature{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + Sig: "BAAAAAAD", + }, ErrInvalidSignature, + }, + } + for _, table := range invalidTables { + err := VerifySignature(table.key, table.sig, []byte("invalid")) + if !errors.Is(err, table.expectedError) { + t.Errorf("test '%s' failed, should got error: '%s', but received: '%s'", table.name, table.expectedError, err) + } + } + + legacyInvalidTests := []struct { + name string + key Key + sig Signature + expectedError string + }{ + {"invalid asn1 structure", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: nil, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "", + Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ecdsa", + }, Signature{ + KeyId: "invalid", + Sig: "BAAAAAAD", + }, "asn1: syntax error: truncated tag or length", + }, + { + "ed25519 with invalid public key", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: nil, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "invalid", + Public: "invalid", + }, + Scheme: "ed25519", + }, Signature{}, "encoding/hex: invalid byte: U+0069 'i'", + }, + { + "ed25519 with invalid signature", Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: nil, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "", + Public: "", + }, + Scheme: "ed25519", + }, Signature{ + KeyId: "invalid", + Sig: "invalid", + }, "encoding/hex: invalid byte: U+0069 'i'", + }, + } + for _, table := range legacyInvalidTests { + err := VerifySignature(table.key, table.sig, []byte("invalid")) + if err.Error() != table.expectedError { + t.Errorf("test '%s' failed, should got error: '%s', but received: '%s'", table.name, table.expectedError, err) + } + } +} From bd1a6b6f63749d40774b8d714c6aedaa10abe40b Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sun, 2 Aug 2020 14:26:52 +0200 Subject: [PATCH 56/86] add ecdsa support for Metablock.Sign() Adds the "ecdsa" case to our Metablock.Sign function. Also adds a test case for an invalid ed25519 key. --- in_toto/model.go | 7 +++++-- in_toto/model_test.go | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/in_toto/model.go b/in_toto/model.go index 9f431e7e..de33fa6b 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -676,12 +676,15 @@ func (mb *Metablock) Sign(key Key) error { if err != nil { return err } + case "ecdsa": + newSignature, err = GenerateSignature(dataCanonical, key) + if err != nil { + return err + } default: return fmt.Errorf("this key type or signature (%s, %s) scheme is "+ "not supported yet", key.KeyType, key.Scheme) } - mb.Signatures = append(mb.Signatures, newSignature) - return nil } diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 5142a439..a68d486c 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -1186,3 +1186,24 @@ func TestMetablockSignWithRSA(t *testing.T) { t.Errorf("signing with an invalid RSA key should fail") } } + +func TestMetablockSignWithEd25519(t *testing.T) { + var mb Metablock + if err := mb.Load("demo.layout.template"); err != nil { + t.Errorf("Cannot parse template file: %s", err) + } + invalidKey := Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: nil, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "BAD", + Public: "BAD", + }, + Scheme: "ed25519", + } + + if err := mb.Sign(invalidKey); err == nil { + t.Errorf("signing with an invalid ed25519 key should fail") + } +} From 2d7b3d257a5acec8b125dfea4aaf26706de30925 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sun, 2 Aug 2020 18:57:01 +0200 Subject: [PATCH 57/86] Add new errors to model Our old key validation functions were not valid anymore. For example, we do compute the public key from a private key now. So every key object should have a public key. Furthermore this commit introduces our own error types for better error handling. --- in_toto/model.go | 104 +++++++++++++++++++++++++----------------- in_toto/model_test.go | 81 +++++++++++--------------------- 2 files changed, 87 insertions(+), 98 deletions(-) diff --git a/in_toto/model.go b/in_toto/model.go index de33fa6b..a248a832 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -2,6 +2,8 @@ package in_toto import ( "encoding/json" + "encoding/pem" + "errors" "fmt" "io/ioutil" "math/big" @@ -46,6 +48,12 @@ type Key struct { Scheme string `json:"scheme"` } +// Thrown by validateKey and validateKeyVal +var ErrEmptyKeyField = errors.New("empty field in key") + +// Thrown by validateHexString +var ErrInvalidHexString = errors.New("invalid hex string") + /* validateHexString is used to validate that a string passed to it contains only valid hexadecimal characters. @@ -53,53 +61,68 @@ only valid hexadecimal characters. func validateHexString(str string) error { formatCheck, _ := regexp.MatchString("^[a-fA-F0-9]+$", str) if !formatCheck { - return fmt.Errorf("'%s' is not a valid hex string", str) + return fmt.Errorf("%w: %s", ErrInvalidHexString, str) } return nil } -/* -validatePubKey is a general function to validate if a key is a valid public key. -*/ -func validatePubKey(key Key) error { - if err := validateHexString(key.KeyId); err != nil { - return fmt.Errorf("keyid: %s", err.Error()) - } - if key.KeyVal.Private != "" { - return fmt.Errorf("in key '%s': private key found", key.KeyId) - } +func validateKeyVal(key Key) error { if key.KeyVal.Public == "" { - return fmt.Errorf("in key '%s': public key cannot be empty", key.KeyId) + return fmt.Errorf("%w: keyval.public", ErrEmptyKeyField) + } + switch key.KeyType { + case "ed25519": + err := validateHexString(key.KeyVal.Public) + if err != nil { + return err + } + if key.KeyVal.Private != "" { + err := validateHexString(key.KeyVal.Private) + if err != nil { + return err + } + } + case "rsa", "ecdsa": + data, _ := pem.Decode([]byte(key.KeyVal.Public)) + if data == nil { + return ErrNoPEMBlock + } + if key.KeyVal.Private != "" { + data, _ := pem.Decode([]byte(key.KeyVal.Private)) + if data == nil { + return ErrNoPEMBlock + } + } + default: + return ErrUnsupportedKeyType } return nil } -/* -validatePrivateKey is a general function to validate if a key is a valid private key. -*/ -func validatePrivateKey(key Key) error { - if err := validateHexString(key.KeyId); err != nil { - return fmt.Errorf("keyid: %s", err.Error()) +func validateKey(key Key) error { + err := validateHexString(key.KeyId) + if err != nil { + return err } - if key.KeyVal.Private == "" { - return fmt.Errorf("in key '%s': private key cannot be empty", key.KeyId) + // This probably can be done more elegant with reflection + // but we care about performance, do we?! + if key.KeyId == "" { + return fmt.Errorf("%w: keyid", ErrEmptyKeyField) } - return nil -} - -/* -validateRSAPubKey checks if a passed key is a valid RSA public key. -*/ -func validateRSAPubKey(key Key) error { - if key.KeyType != "rsa" { - return fmt.Errorf("invalid KeyType for key '%s': should be 'rsa', got"+ - " '%s'", key.KeyId, key.KeyType) + if key.KeyType == "" { + return fmt.Errorf("%w: keytype", ErrEmptyKeyField) + } + if key.KeyVal.Public == "" { + return fmt.Errorf("%w: keyval.public", ErrEmptyKeyField) } - if key.Scheme != "rsassa-pss-sha256" { - return fmt.Errorf("invalid scheme for key '%s': should be "+ - "'rsassa-pss-sha256', got: '%s'", key.KeyId, key.Scheme) + if key.KeyIdHashAlgorithms == nil { + return fmt.Errorf("%w: keyid_hash_algorithms", ErrEmptyKeyField) } - if err := validatePubKey(key); err != nil { + if key.Scheme == "" { + return fmt.Errorf("%w: keyid_hash_algorithms", ErrEmptyKeyField) + } + err = validateKeyVal(key) + if err != nil { return err } return nil @@ -121,11 +144,10 @@ by inspecting the key ID and the signature itself. */ func validateSignature(signature Signature) error { if err := validateHexString(signature.KeyId); err != nil { - return fmt.Errorf("keyid: %s", err.Error()) + return err } if err := validateHexString(signature.Sig); err != nil { - return fmt.Errorf("signature with keyid '%s': %s", signature.KeyId, - err.Error()) + return err } return nil } @@ -328,8 +350,7 @@ func validateStep(step Step) error { } for _, keyId := range step.PubKeys { if err := validateHexString(keyId); err != nil { - return fmt.Errorf("in step '%s', keyid: %s", - step.SupplyChainItem.Name, err.Error()) + return err } } return nil @@ -397,9 +418,6 @@ func validateLayout(layout Layout) error { if key.KeyId != keyId { return fmt.Errorf("invalid key found") } - if err := validateRSAPubKey(key); err != nil { - return err - } } var namesSeen = make(map[string]bool) @@ -642,7 +660,7 @@ func validateMetablock(mb Metablock) error { } if err := validateSliceOfSignatures(mb.Signatures); err != nil { - return fmt.Errorf("validateSignature: %s", err) + return err } return nil diff --git a/in_toto/model_test.go b/in_toto/model_test.go index a68d486c..23ebc2e7 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -2,6 +2,7 @@ package in_toto import ( "encoding/hex" + "errors" "fmt" "io/ioutil" "os" @@ -362,7 +363,7 @@ func TestValidateLink(t *testing.T) { err = validateLink(testMb.Signed.(Link)) if err.Error() != "in materials of link 'test_material_hash': in artifact"+ - " 'foo.py', sha256 hash value: '!@#$%' is not a valid hex string" { + " 'foo.py', sha256 hash value: invalid hex string: !@#$%" { t.Error("validateLink error - invalid hashes not detected") } @@ -397,7 +398,7 @@ func TestValidateLink(t *testing.T) { err = validateLink(testMb.Signed.(Link)) if err.Error() != "in products of link 'test_product_hash': in artifact "+ - "'foo.tar.gz', sha256 hash value: '!@#$%' is not a valid hex string" { + "'foo.tar.gz', sha256 hash value: invalid hex string: !@#$%" { t.Error("validateLink error - invalid hashes not detected") } } @@ -595,10 +596,10 @@ func TestValidateLayout(t *testing.T) { Type: "layout", Expires: "2020-02-27T18:03:43Z", Keys: map[string]Key{ - "deadbeef": Key{KeyId: "deadbeef"}, + "deadbeef": Key{KeyId: "deabeef"}, }, }, - "invalid KeyType for key", + "invalid key found", }, } @@ -631,8 +632,7 @@ func TestValidateStep(t *testing.T) { }, } err = validateStep(testStep) - if err.Error() != "in step 'foo', keyid: '"+testStep.PubKeys[0]+"' is not"+ - " a valid hex string" { + if !errors.Is(err, ErrInvalidHexString) { t.Error("validateStep - validateHexString error - invalid key ID not " + "detected") } @@ -698,19 +698,6 @@ func TestValidateHexSchema(t *testing.T) { } } -func TestValidatePrivateKey(t *testing.T) { - invalidKey := Key{ - KeyId: "invalid", - KeyIdHashAlgorithms: nil, - KeyType: "", - KeyVal: KeyVal{}, - Scheme: "", - } - if err := validatePrivateKey(invalidKey); err == nil { - t.Errorf("validating a private key with an invalid keyID should fail") - } -} - func TestValidatePubKey(t *testing.T) { testKey := Key{ KeyId: "776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5", @@ -732,7 +719,8 @@ func TestValidatePubKey(t *testing.T) { Scheme: "rsassa-pss-sha256", } - if err := validateRSAPubKey(testKey); err != nil { + err := validateKey(testKey) + if !errors.Is(err, ErrEmptyKeyField) { t.Errorf("error validating public key: %s", err) } @@ -756,9 +744,9 @@ func TestValidatePubKey(t *testing.T) { Scheme: "rsassa-pss-sha256", } - err := validateRSAPubKey(testKey) - if err.Error() != "keyid: '"+testKey.KeyId+"' is not a valid hex string" { - t.Error("validateRSAPubKey error - invalid key ID not detected") + err = validateKey(testKey) + if !errors.Is(err, ErrInvalidHexString) { + t.Error("validateKey error - invalid key ID not detected") } testKey = Key{ @@ -781,10 +769,9 @@ func TestValidatePubKey(t *testing.T) { Scheme: "rsassa-pss-sha256", } - err = validateRSAPubKey(testKey) - if err.Error() != "in key '776a00e29f3559e0141b3b096f696abc6cfb0c657ab40"+ - "f441132b345b08453f5': private key found" { - t.Error("validateRSAPubKey error - private key not detected") + err = validateKey(testKey) + if !errors.Is(err, ErrEmptyKeyField) { + t.Error("validateKey error - private key not detected") } testKey = Key{ @@ -797,14 +784,13 @@ func TestValidatePubKey(t *testing.T) { Scheme: "rsassa-pss-sha256", } - err = validateRSAPubKey(testKey) - if err.Error() != "in key '776a00e29f3559e0141b3b096f696abc6cfb0c657ab40"+ - "f441132b345b08453f5': public key cannot be empty" { - t.Error("validateRSAPubKey error - empty public key not detected") + err = validateKey(testKey) + if !errors.Is(err, ErrEmptyKeyField) { + t.Error("validateKey error - empty public key not detected") } } -func TestValidateRSAPubKey(t *testing.T) { +func TestvalidateKey(t *testing.T) { key := Key{ KeyId: "776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5", KeyType: "invalid", @@ -824,10 +810,10 @@ func TestValidateRSAPubKey(t *testing.T) { }, Scheme: "rsassa-pss-sha256", } - if err := validateRSAPubKey(key); err.Error() != "invalid KeyType for key"+ + if err := validateKey(key); err.Error() != "invalid KeyType for key"+ " '776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5':"+ " should be 'rsa', got 'invalid'" { - t.Error("validateRSAPubKey error - invalid type not detected") + t.Error("validateKey error - invalid type not detected") } key = Key{ @@ -849,10 +835,10 @@ func TestValidateRSAPubKey(t *testing.T) { }, Scheme: "invalid", } - if err := validateRSAPubKey(key); err.Error() != "invalid scheme for key"+ + if err := validateKey(key); err.Error() != "invalid scheme for key"+ " '776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5'"+ ": should be 'rsassa-pss-sha256', got: 'invalid'" { - t.Error("validateRSAPubKey error - invalid scheme not detected") + t.Error("validateKey error - invalid scheme not detected") } } @@ -1070,9 +1056,8 @@ func TestValidateMetablock(t *testing.T) { }, } - if err := validateMetablock(testMetablock); err.Error() != - "validateSignature: keyid: 'Z556caebdc0877eed53d419b60eddb1e57fa773e4"+ - "e31d70698b58f3e9cc48b35' is not a valid hex string" { + err := validateMetablock(testMetablock) + if !errors.Is(err, ErrInvalidHexString) { t.Error("validateMetablock Error: invalid key ID not detected") } @@ -1107,22 +1092,8 @@ func TestValidateMetablock(t *testing.T) { }, } - if err := validateMetablock(testMetablock); err.Error() != - "validateSignature: signature with keyid '556caebdc0877eed53d419b60ed"+ - "db1e57fa773e4e31d70698b588f3e9cc48b35': '02813858670c66647c17802"+ - "d84f06453589f41850013a544609e9z33ba21fa19280e8371701f8274fb0c56b"+ - "d95ff4f34c418456b002af9836ca218b584f51eb0eaacbb1c9bb57448101b07d"+ - "058dec04d525551d157f6ae5e3679701735b1b8f52430f9b771d5476db1a2053"+ - "cd93e2354f20061178a01705f2fa9ac82c7aeca4dd830e2672eb227127178d52"+ - "328747ac819e50ec8ff52c662d7a4c58f5040d8f655fe595804f3e47c4fc9843"+ - "4c44e914445f7cb773439ebf813de8849dd1b533958f99f671d4e023d34c110d"+ - "4b169cc02c12a3755ebe537147ff2479d244daaf719e24cf6b2fa6f47d0410d5"+ - "2d67217bcf4d4d4c2c7c0b92cd2bcd321edc69bc1430f78a188e712b8cb1fff0"+ - "c14550cd01c41dae377256f31211fd249c5031bfee86e638bce6aa36aca349b7"+ - "87cef48255b0ef04bd0a21adb37b2a3da888d1530ca6ddeae5261e6fd65aa626"+ - "d5caebbfae2986f842bd2ce94bcefe5dd0ae9c5b2028a15bd63bbea61be73220"+ - "7f0f5b58d056f118c830981747cb2b245d1377e17' is not a valid hex"+ - " string" { + err = validateMetablock(testMetablock) + if !errors.Is(err, ErrInvalidHexString) { t.Error("validateMetablock error: invalid signature not detected") } From 3a3d27475c252d68201965db2faec8da11c6d671 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sun, 2 Aug 2020 20:11:12 +0200 Subject: [PATCH 58/86] cover new key object validation + more key lib tests This commit adds more tests for key object validation, also we *really* use the key object validation functions now, during generating a keyID --- in_toto/keylib.go | 4 ++ in_toto/keylib_test.go | 12 +++- in_toto/model.go | 8 +-- in_toto/model_test.go | 133 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 9 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 2bdc6132..f72b8c80 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -62,6 +62,10 @@ func (k *Key) GenerateKeyId() error { // calculate sha256 and return string representation of keyId keyHashed := sha256.Sum256(keyCanonical) k.KeyId = fmt.Sprintf("%x", keyHashed) + err = validateKey(*k) + if err != nil { + return err + } return nil } diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 99a2358c..f09615de 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -88,6 +88,12 @@ func TestLoadKeyErrors(t *testing.T) { {"not existing file", "inToToRocks", "rsassa-pss-sha256", []string{"sha256", "sha512"}, os.ErrNotExist}, {"existing, but invalid file", "demo.layout.template", "ecdsa", []string{"sha512"}, ErrNoPEMBlock}, {"EC private key file", "erin", "ecdsa", []string{"sha256", "sha512"}, ErrFailedPEMParsing}, + {"valid ed25519 private key, but invalid scheme", "carol", "", []string{"sha256"}, ErrEmptyKeyField}, + {"valid ed25519 public key, but invalid scheme", "carol.pub", "", []string{"sha256"}, ErrEmptyKeyField}, + {"valid rsa private key, but invalid hashalgo", "dan", "rsassa-psa-sha256", nil, ErrEmptyKeyField}, + {"valid rsa public key, but invalid hashalgo", "dan.pub", "rsassa-psa-sha256", nil, ErrEmptyKeyField}, + {"valid ecdsa private key, but invalid hashalgo", "frank", "ecdsa", nil, ErrEmptyKeyField}, + {"valid ecdsa public key, but invalid hashalgo", "frank.pub", "ecdsa", nil, ErrEmptyKeyField}, } for _, table := range invalidTables { @@ -107,14 +113,16 @@ func TestSetKeyComponents(t *testing.T) { keyType string scheme string keyIdHashAlgorithms []string + err error }{ - {"test invalid key type", []byte{}, []byte{}, "yolo", "ed25519", []string{"sha512"}}, + {"test invalid key type", []byte{}, []byte{}, "yolo", "ed25519", []string{"sha512"}, ErrUnsupportedKeyType}, + {"invalid scheme", []byte("393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c"), []byte{}, "ed25519", "", []string{"sha256"}, ErrEmptyKeyField}, } for _, table := range invalidTables { var key Key err := key.SetKeyComponents(table.pubkeyBytes, table.privateKeyBytes, table.keyType, table.scheme, table.keyIdHashAlgorithms) - if !errors.Is(err, ErrUnsupportedKeyType) { + if !errors.Is(err, table.err) { t.Errorf("'%s' failed, should have: '%s', got: '%s'", table.name, ErrUnsupportedKeyType, err) } } diff --git a/in_toto/model.go b/in_toto/model.go index a248a832..346357da 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -67,9 +67,6 @@ func validateHexString(str string) error { } func validateKeyVal(key Key) error { - if key.KeyVal.Public == "" { - return fmt.Errorf("%w: keyval.public", ErrEmptyKeyField) - } switch key.KeyType { case "ed25519": err := validateHexString(key.KeyVal.Public) @@ -106,9 +103,6 @@ func validateKey(key Key) error { } // This probably can be done more elegant with reflection // but we care about performance, do we?! - if key.KeyId == "" { - return fmt.Errorf("%w: keyid", ErrEmptyKeyField) - } if key.KeyType == "" { return fmt.Errorf("%w: keytype", ErrEmptyKeyField) } @@ -119,7 +113,7 @@ func validateKey(key Key) error { return fmt.Errorf("%w: keyid_hash_algorithms", ErrEmptyKeyField) } if key.Scheme == "" { - return fmt.Errorf("%w: keyid_hash_algorithms", ErrEmptyKeyField) + return fmt.Errorf("%w: scheme", ErrEmptyKeyField) } err = validateKeyVal(key) if err != nil { diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 23ebc2e7..34880faf 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -1178,3 +1178,136 @@ func TestMetablockSignWithEd25519(t *testing.T) { t.Errorf("signing with an invalid ed25519 key should fail") } } + +func TestMetaBlockSignWithEcdsa(t *testing.T) { + var mb Metablock + if err := mb.Load("demo.layout.template"); err != nil { + t.Errorf("Cannot parse template file: %s", err) + } + invalidKey := Key{ + KeyId: "invalid", + KeyIdHashAlgorithms: nil, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "BAD", + Public: "BAD", + }, + Scheme: "ecdsa", + } + if err := mb.Sign(invalidKey); err == nil { + t.Errorf("signing with an invalid ecdsa key should fail") + } +} + +func TestValidateKeyErrors(t *testing.T) { + invalidTables := []struct { + name string + key Key + err error + }{ + {"empty key", Key{ + KeyId: "", + KeyIdHashAlgorithms: nil, + KeyType: "", + KeyVal: KeyVal{}, + Scheme: "", + }, ErrInvalidHexString}, + {"keytype missing", Key{ + KeyId: "bad", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "", + KeyVal: KeyVal{ + Private: "", + Public: "", + }, + Scheme: "rsassa-psa-sha256", + }, ErrEmptyKeyField}, + {"key scheme missing", Key{ + KeyId: "bad", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "bad", + Public: "bad", + }, + Scheme: "", + }, ErrEmptyKeyField}, + { + name: "invalid rsa pub key", + key: Key{ + KeyId: "bad", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "", + Public: "invalid", + }, + Scheme: "rsassa-psa-sha56", + }, + err: ErrNoPEMBlock, + }, + { + name: "invalid rsa private key", + key: Key{ + KeyId: "bad", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "invalid", + Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxPX3kFs/z645x4UOC3KF\nY3V80YQtKrp6YS3qU+Jlvx/XzK53lb4sCDRU9jqBBx3We45TmFUibroMd8tQXCUS\ne8gYCBUBqBmmz0dEHJYbW0tYF7IoapMIxhRYn76YqNdl1JoRTcmzIaOJ7QrHxQrS\nGpivvTm6kQ9WLeApG1GLYJ3C3Wl4bnsI1bKSv55Zi45/JawHzTzYUAIXX9qCd3Io\nHzDucz9IAj9Ookw0va/q9FjoPGrRB80IReVxLVnbo6pYJfu/O37jvEobHFa8ckHd\nYxUIg8wvkIOy1O3M74lBDm6CVI0ZO25xPlDB/4nHAE1PbA3aF3lw8JGuxLDsetxm\nfzgAleVt4vXLQiCrZaLf+0cM97JcT7wdHcbIvRLsij9LNP+2tWZgeZ/hIAOEdaDq\ncYANPDIAxfTvbe9I0sXrCtrLer1SS7GqUmdFCdkdun8erXdNF0ls9Rp4cbYhjdf3\nyMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE=\n-----END PUBLIC KEY-----", + }, + Scheme: "rsassa-psa-sha256", + }, + err: ErrNoPEMBlock, + }, + { + name: "invalid ed25519 public key", + key: Key{ + KeyId: "bad", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "invalid", + Public: "invalid", + }, + Scheme: "ed25519", + }, + err: ErrInvalidHexString, + }, + { + name: "invalid ed25519 private key", + key: Key{ + KeyId: "bad", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "invalid", + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + }, + Scheme: "ed25519", + }, + err: ErrInvalidHexString, + }, + { + name: "invalid key type", + key: Key{ + KeyId: "bad", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "invalid", + KeyVal: KeyVal{ + Private: "invalid", + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + }, + Scheme: "ed25519", + }, + err: ErrUnsupportedKeyType, + }, + } + + for _, table := range invalidTables { + err := validateKey(table.key) + if !errors.Is(err, table.err) { + t.Errorf("test '%s' failed, expected error: '%s', got '%s'", table.name, table.err, err) + } + } +} From 089dd4c849d16739c265dd1ae307bd92e6f77bd8 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 6 Aug 2020 18:50:16 +0200 Subject: [PATCH 59/86] remove frank.ec frank.ec was only necessary for generating the ECDSA PEM keys. Therefore we can delete it. --- test/data/frank.ec | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 test/data/frank.ec diff --git a/test/data/frank.ec b/test/data/frank.ec deleted file mode 100644 index 2811ad34..00000000 --- a/test/data/frank.ec +++ /dev/null @@ -1,7 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MIHcAgEBBEIB6fQnV71xKx6kFgJvYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb -0ligpM65KyaeeRce6JR9eQW6TB6R+5pNzvOgBwYFK4EEACOhgYkDgYYABAFy0CeD -AyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1hK2QAanID3JuPff1NdhehhL/U -1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGsB+7KqOeO5ELkqHO5tsy4kvsN -rmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3rw== ------END EC PRIVATE KEY----- From e22b3cd214cfbed23540151ac36bdc4b4d7c2580 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 6 Aug 2020 18:53:34 +0200 Subject: [PATCH 60/86] remove switch block in Metablock.Sign The switch block logic moved inside of the GenerateSignature function, thus we can drop this extra switch block inside of Metablock.Sign. This fixes also the long-time FIXME in it :) --- in_toto/model.go | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/in_toto/model.go b/in_toto/model.go index 346357da..6537c01a 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -672,31 +672,12 @@ func (mb *Metablock) Sign(key Key) error { if err != nil { return err } - var newSignature Signature - // FIXME: we could be fancier about signature-generation using a dispatch - // table or something but for now let's just be explicit - // (also, lolnogenerics) - switch key.Scheme { - case "ed25519": - newSignature, err = GenerateSignature(dataCanonical, key) - if err != nil { - return err - } - case "rsassa-pss-sha256": - newSignature, err = GenerateSignature(dataCanonical, key) - if err != nil { - return err - } - case "ecdsa": - newSignature, err = GenerateSignature(dataCanonical, key) - if err != nil { - return err - } - default: - return fmt.Errorf("this key type or signature (%s, %s) scheme is "+ - "not supported yet", key.KeyType, key.Scheme) + newSignature, err := GenerateSignature(dataCanonical, key) + if err != nil { + return err } + mb.Signatures = append(mb.Signatures, newSignature) return nil } From 2db25b733576b0b2d50458635675cd795414351f Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 6 Aug 2020 18:55:32 +0200 Subject: [PATCH 61/86] remove carol-invalid The carol-invalid key is not necessary anymore, thus we can remove it. --- test/data/README.md | 1 - test/data/carol-invalid | 1 - 2 files changed, 2 deletions(-) delete mode 100644 test/data/carol-invalid diff --git a/test/data/README.md b/test/data/README.md index 1bd3c9ee..34ed147d 100644 --- a/test/data/README.md +++ b/test/data/README.md @@ -58,7 +58,6 @@ The following curves are for example **not** supported: | canonical-test.link | .. | | carol | ed25519 key as PKCS8 | | carol.pub | pub key of carol | -| carol-invalid | to be removed | | dan | RSA private key | | dan.pub | pub key of dan | | erin | EC private Key (secp256k1) | diff --git a/test/data/carol-invalid b/test/data/carol-invalid deleted file mode 100644 index cd13859e..00000000 --- a/test/data/carol-invalid +++ /dev/null @@ -1 +0,0 @@ -{"keytype": "ed25519", "scheme": "ed25519", "keyid": "d7c0baabc90b7bf218aa67461ec0c3c7f13a8a5d8552859c8fafe41588be01cf", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "8c93f633f2378cc64dd7cbb0ed35eac59e1f28065f90cbbddb59878436fec037", "private": "4cedf4d"}} \ No newline at end of file From f4ee8aebe7cbf4dd452c639f1145d20915bef596 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 6 Aug 2020 19:05:03 +0200 Subject: [PATCH 62/86] add more documentation Here we fix a small spelling issue and add more documentation for a few functions, that were missing documentation --- in_toto/model.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/in_toto/model.go b/in_toto/model.go index 6537c01a..dc2fd435 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -48,10 +48,10 @@ type Key struct { Scheme string `json:"scheme"` } -// Thrown by validateKey and validateKeyVal +// This error will be thrown in a field in our Key struct is empty. var ErrEmptyKeyField = errors.New("empty field in key") -// Thrown by validateHexString +// This error will be thrown, if a string doesn't match a hex string. var ErrInvalidHexString = errors.New("invalid hex string") /* @@ -66,6 +66,15 @@ func validateHexString(str string) error { return nil } +/* +validateKeyVal validates the KeyVal struct. In case of an ed25519 key, +it will check for a hex string for private and public key. In any other +case, validateKeyVal will try to decode the PEM block. If this succeeds, +we have a valid PEM block in our KeyVal struct. On success it will return nil +on failure it will return the corresponding error. This can be either +an ErrInvalidHexString, an ErrNoPEMBlock or an ErrUnsupportedKeyType +if the KeyType is unknown. +*/ func validateKeyVal(key Key) error { switch key.KeyType { case "ed25519": @@ -96,6 +105,12 @@ func validateKeyVal(key Key) error { return nil } +/* +validateKey checks the outer key object (everything, except the KeyVal struct). +It verifies the keyId for being a hex string and checks for empty fields. +On success it will return nil, on error it will return the corresponding error. +Either: ErrEmptyKeyField or ErrInvalidHexString. +*/ func validateKey(key Key) error { err := validateHexString(key.KeyId) if err != nil { @@ -604,7 +619,7 @@ func (mb *Metablock) GetSignableRepresentation() ([]byte, error) { } /* -VerifyRSASignature verifies the first signature, corresponding to the passed Key, +VerifySignature verifies the first signature, corresponding to the passed Key, that it finds in the Signatures field of the Metablock on which it was called. It returns an error if Signatures does not contain a Signature corresponding to the passed Key, the object in Signed cannot be canonicalized, or the Signature From f9e328aad529346973d5dd43bae6bde1877a016f Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 6 Aug 2020 19:21:20 +0200 Subject: [PATCH 63/86] use Go 1.13's IsZero() for checking for an uninitialized Key The keyID may be empty, so we check for an unitialized Key object instead. For this we prefer using reflect.ValueOf(key).IsZero() over reflect.DeepEqual(), because DeepEqual is more resource intensive. --- in_toto/runlib.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/in_toto/runlib.go b/in_toto/runlib.go index 4b4834b9..22720501 100644 --- a/in_toto/runlib.go +++ b/in_toto/runlib.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "syscall" ) @@ -275,8 +276,10 @@ func InTotoRun(name string, materialPaths []string, productPaths []string, Environment: map[string]interface{}{}, } linkMb.Signatures = []Signature{} - // we expect that key has been initialized if it has a valid KeyId - if key.KeyId != "" { + // We use a new feature from Go1.13 here, to check the key struct. + // IsZero() will return True, if the key hasn't been initialized + // with other values than the default ones. + if !reflect.ValueOf(key).IsZero() { if err := linkMb.Sign(key); err != nil { return linkMb, err } From 2a7e225729ce760f1bca7bdb2e69192549a1c618 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 6 Aug 2020 20:35:49 +0200 Subject: [PATCH 64/86] Use Go 1.13 Error handling We can drop the legacy error string comparing via using Go 1.13 errors.Is() function for comparing the unwrapped errors. --- in_toto/keylib_test.go | 48 ++++++++++-------------------------------- 1 file changed, 11 insertions(+), 37 deletions(-) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index f09615de..682838fa 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -1,6 +1,8 @@ package in_toto import ( + "encoding/asn1" + "encoding/hex" "errors" "os" "testing" @@ -196,21 +198,6 @@ func TestGenerateSignatureErrors(t *testing.T) { Scheme: "ecdas", }, ErrFailedPEMParsing, }, - } - - for _, table := range invalidTables { - _, err := GenerateSignature([]byte("test"), table.key) - if !errors.Is(err, table.expectedError) { - t.Errorf("test '%s' failed, should got error: '%s', but received: '%s'", table.name, table.expectedError, err) - } - } - - // test cases that do not support errors.Is (Go 1.13 error handling) - legacyInvalidTables := []struct { - name string - key Key - expectedError string - }{ {"invalid ed25519 private key", Key{ KeyId: "invalid", KeyIdHashAlgorithms: []string{"sha512"}, @@ -220,12 +207,13 @@ func TestGenerateSignatureErrors(t *testing.T) { Public: "invalid", }, Scheme: "ed25519"}, - "encoding/hex: invalid byte: U+0069 'i'"}, + hex.InvalidByteError(105), + }, } - for _, table := range legacyInvalidTables { + for _, table := range invalidTables { _, err := GenerateSignature([]byte("test"), table.key) - if err.Error() != table.expectedError { + if !errors.Is(err, table.expectedError) { t.Errorf("test '%s' failed, should got error: '%s', but received: '%s'", table.name, table.expectedError, err) } } @@ -304,20 +292,6 @@ func TestVerifySignatureErrors(t *testing.T) { Sig: "BAAAAAAD", }, ErrInvalidSignature, }, - } - for _, table := range invalidTables { - err := VerifySignature(table.key, table.sig, []byte("invalid")) - if !errors.Is(err, table.expectedError) { - t.Errorf("test '%s' failed, should got error: '%s', but received: '%s'", table.name, table.expectedError, err) - } - } - - legacyInvalidTests := []struct { - name string - key Key - sig Signature - expectedError string - }{ {"invalid asn1 structure", Key{ KeyId: "invalid", KeyIdHashAlgorithms: nil, @@ -330,7 +304,7 @@ func TestVerifySignatureErrors(t *testing.T) { }, Signature{ KeyId: "invalid", Sig: "BAAAAAAD", - }, "asn1: syntax error: truncated tag or length", + }, asn1.SyntaxError{Msg: "truncated tag or length"}, }, { "ed25519 with invalid public key", Key{ @@ -342,7 +316,7 @@ func TestVerifySignatureErrors(t *testing.T) { Public: "invalid", }, Scheme: "ed25519", - }, Signature{}, "encoding/hex: invalid byte: U+0069 'i'", + }, Signature{}, hex.InvalidByteError(105), }, { "ed25519 with invalid signature", Key{ @@ -357,12 +331,12 @@ func TestVerifySignatureErrors(t *testing.T) { }, Signature{ KeyId: "invalid", Sig: "invalid", - }, "encoding/hex: invalid byte: U+0069 'i'", + }, hex.InvalidByteError(105), }, } - for _, table := range legacyInvalidTests { + for _, table := range invalidTables { err := VerifySignature(table.key, table.sig, []byte("invalid")) - if err.Error() != table.expectedError { + if !errors.Is(err, table.expectedError) { t.Errorf("test '%s' failed, should got error: '%s', but received: '%s'", table.name, table.expectedError, err) } } From 5bce89f4acad06384b1ac24c891390dfd5108549 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 7 Aug 2020 13:56:25 +0200 Subject: [PATCH 65/86] enhance documentation In this commit we fix various spelling errors, deliver more detailed documentation and change the default error message of ErrInvalidKeyType. --- in_toto/keylib.go | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index f72b8c80..c2b46204 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -30,8 +30,8 @@ var ErrUnsupportedKeyType = errors.New("unsupported key type") // ErrInvalidSignature is returned when the signature is invalid var ErrInvalidSignature = errors.New("invalid signature") -// ErrInvalidKeyType is returned when the key is valid, but it has a wrong key type attached to it -var ErrInvalidKeyType = errors.New("valid key, but not matching key type detected") +// ErrInvalidKeyType is returned when the keytype is invalid for the given key +var ErrInvalidKeyType = errors.New("invalid key type for this key") /* GenerateKeyId creates a partial key map and generates the key ID @@ -88,14 +88,13 @@ func GeneratePublicPemBlock(pubKeyBytes []byte) []byte { /* SetKeyComponents sets all components in our key object. Furthermore it makes sure to remove any trailing and leading whitespaces or newlines. +We treat key types differently for interoperability reasons to the in-toto python +implementation and the securesystemslib. */ func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyType string, scheme string, keyIdHashAlgorithms []string) error { // assume we have a privateKey if the key size is bigger than 0 switch keyType { case "rsa", "ecdsa": - // We need to treat RSA differently, because of interoperability - // reasons with the securesystemslib and the in-toto python - // implementation if len(privateKeyBytes) > 0 { k.KeyVal = KeyVal{ Private: strings.TrimSpace(string(privateKeyBytes)), @@ -130,8 +129,8 @@ func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyTy } /* -ParseKey tries to parse a PEM []byte slice. -Supported are: +ParseKey tries to parse a PEM []byte slice. Using the following standards +in the given order: * PKCS8 * PKCS1 @@ -189,13 +188,13 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) err = closeErr } }() - // Read key bytes and decode PEM + // Read key bytes pemBytes, err := ioutil.ReadAll(pemFile) if err != nil { return err } - // pam.Decode returns the parsed pem block and a rest. + // pem.Decode returns the parsed pem block and a rest. // The rest is everything, that could not be parsed as PEM block. // Therefore we can drop this via using the blank identifier "_" data, _ := pem.Decode(pemBytes) @@ -217,7 +216,7 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) return err } case *rsa.PrivateKey: - // Note: We store the public key as PKCS8 key here, although the private key get's stored as PKCS1 key + // Note: RSA Public Keys will get stored as X.509 SubjectPublicKeyInfo (RFC5280) // This behavior is consistent to the securesystemslib pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*rsa.PrivateKey).Public()) if err != nil { @@ -257,11 +256,12 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) GenerateSignature will automatically detect the key type and sign the signable data with the provided key. If everything goes right GenerateSignature will return a for the key valid signature and err=nil. If something goes wrong it will -return an not initialized signature and an error. Possible errors are: +return a not initialized signature and an error. Possible errors are: * ErrNoPEMBlock * ErrUnsupportedKeyType +Currently supported is only one scheme per key. */ func GenerateSignature(signable []byte, key Key) (Signature, error) { var signature Signature @@ -271,7 +271,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { // in which we are storing RSA keys in PEM format, but ed25519 keys hex encoded. switch key.KeyType { case "rsa", "ecdsa": - // pam.Decode returns the parsed pem block and a rest. + // pem.Decode returns the parsed pem block and a rest. // The rest is everything, that could not be parsed as PEM block. // Therefore we can drop this via using the blank identifier "_" data, _ := pem.Decode([]byte(key.KeyVal.Private)) @@ -308,7 +308,8 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { return signature, nil } // Generate the ecdsa signature on the same way, as we do in the securesystemslib - // We construct a + // We are marshalling the ecdsaSignature struct as ASN.1 INTEGER SEQUENCES + // into an ASN.1 Object. signatureBuffer, err = asn1.Marshal(EcdsaSignature{ R: r, S: s, @@ -338,18 +339,21 @@ Supported key types are: * RSA * ED25519 + * ECDSA -When encountering a RSA key, VerifySignature will decode the PEM block in the key +When encountering an RSA key, VerifySignature will decode the PEM block in the key and will call rsa.VerifyPSS() for verifying the RSA signature. -When encountering an ed25519 key, Verifysignature will decode the hex string encoded +When encountering an ed25519 key, VerifySignature will decode the hex string encoded public key and will use ed25519.Verify() for verifying the ed25519 signature. +When the given key is an ecdsa key, VerifySignature will unmarshall the ASN1 object +and will use the retrieved ecdsa components 'r' and 's' for verifying the signature. On success it will return nil. In case of an unsupported key type or any other error it will return an error. */ func VerifySignature(key Key, sig Signature, unverified []byte) error { switch key.KeyType { case "rsa", "ecdsa": - // pam.Decode returns the parsed pem block and a rest. + // pem.Decode returns the parsed pem block and a rest. // The rest is everything, that could not be parsed as PEM block. // Therefore we can drop this via using the blank identifier "_" data, _ := pem.Decode([]byte(key.KeyVal.Public)) From 16a79ec008d036bde465220df2c90d2319149bf7 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 7 Aug 2020 15:11:52 +0200 Subject: [PATCH 66/86] use constant strings for keytypes This commit introduces three new constants called: rsaKeytype, ecdsaKeytype and ed25519KeyType. With these constants we are able to easily change the keytype if necessary, without finding/replacing strings. --- in_toto/keylib.go | 32 +++++++++++++++++++------------- in_toto/model.go | 4 ++-- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index c2b46204..57c663bf 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -18,6 +18,12 @@ import ( "strings" ) +const ( + rsaKeyType string = "rsa" + ecdsaKeyType string = "ecdsa" + ed25519KeyType string = "ed25519" +) + // ErrFailedPEMParsing gets returned when PKCS1, PKCS8 or PKIX key parsing fails var ErrFailedPEMParsing = errors.New("failed parsing the PEM block: unsupported PEM type") @@ -94,7 +100,7 @@ implementation and the securesystemslib. func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyType string, scheme string, keyIdHashAlgorithms []string) error { // assume we have a privateKey if the key size is bigger than 0 switch keyType { - case "rsa", "ecdsa": + case rsaKeyType, ecdsaKeyType: if len(privateKeyBytes) > 0 { k.KeyVal = KeyVal{ Private: strings.TrimSpace(string(privateKeyBytes)), @@ -105,7 +111,7 @@ func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyTy Public: strings.TrimSpace(string(pubKeyBytes)), } } - case "ed25519": + case ed25519KeyType: if len(privateKeyBytes) > 0 { k.KeyVal = KeyVal{ Private: strings.TrimSpace(hex.EncodeToString(privateKeyBytes)), @@ -212,7 +218,7 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) // Use type switch to identify the key format switch key.(type) { case *rsa.PublicKey: - if err := k.SetKeyComponents(pemBytes, []byte{}, "rsa", scheme, keyIdHashAlgorithms); err != nil { + if err := k.SetKeyComponents(pemBytes, []byte{}, rsaKeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case *rsa.PrivateKey: @@ -222,16 +228,16 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) if err != nil { return err } - if err := k.SetKeyComponents(pubKeyBytes, pemBytes, "rsa", scheme, keyIdHashAlgorithms); err != nil { + if err := k.SetKeyComponents(pubKeyBytes, pemBytes, rsaKeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case ed25519.PublicKey: - if err := k.SetKeyComponents(key.(ed25519.PublicKey), []byte{}, "ed25519", scheme, keyIdHashAlgorithms); err != nil { + if err := k.SetKeyComponents(key.(ed25519.PublicKey), []byte{}, ed25519KeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case ed25519.PrivateKey: pubKeyBytes := key.(ed25519.PrivateKey).Public() - if err := k.SetKeyComponents(pubKeyBytes.(ed25519.PublicKey), key.(ed25519.PrivateKey), "ed25519", scheme, keyIdHashAlgorithms); err != nil { + if err := k.SetKeyComponents(pubKeyBytes.(ed25519.PublicKey), key.(ed25519.PrivateKey), ed25519KeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case *ecdsa.PrivateKey: @@ -239,11 +245,11 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) if err != nil { return err } - if err := k.SetKeyComponents(pubKeyBytes, pemBytes, "ecdsa", scheme, keyIdHashAlgorithms); err != nil { + if err := k.SetKeyComponents(pubKeyBytes, pemBytes, ecdsaKeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case *ecdsa.PublicKey: - if err := k.SetKeyComponents(pemBytes, []byte{}, "ecdsa", scheme, keyIdHashAlgorithms); err != nil { + if err := k.SetKeyComponents(pemBytes, []byte{}, ecdsaKeyType, scheme, keyIdHashAlgorithms); err != nil { return err } default: @@ -270,7 +276,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { // with the securesystemslib and the python implementation // in which we are storing RSA keys in PEM format, but ed25519 keys hex encoded. switch key.KeyType { - case "rsa", "ecdsa": + case rsaKeyType, ecdsaKeyType: // pem.Decode returns the parsed pem block and a rest. // The rest is everything, that could not be parsed as PEM block. // Therefore we can drop this via using the blank identifier "_" @@ -295,7 +301,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { return signature, err } case *ecdsa.PrivateKey: - if key.KeyType != "ecdsa" { + if key.KeyType != ecdsaKeyType { return signature, ErrInvalidKeyType } // ecdsa.Sign returns a signature that consists of two components called: r and s @@ -317,7 +323,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { default: return signature, fmt.Errorf("%w: %T", ErrUnsupportedKeyType, parsedKey) } - case "ed25519": + case ed25519KeyType: privateHex, err := hex.DecodeString(key.KeyVal.Private) if err != nil { return signature, err @@ -352,7 +358,7 @@ it will return an error. */ func VerifySignature(key Key, sig Signature, unverified []byte) error { switch key.KeyType { - case "rsa", "ecdsa": + case rsaKeyType, ecdsaKeyType: // pem.Decode returns the parsed pem block and a rest. // The rest is everything, that could not be parsed as PEM block. // Therefore we can drop this via using the blank identifier "_" @@ -388,7 +394,7 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { default: return fmt.Errorf("%w: Key has type %T", ErrInvalidSignature, parsedKey) } - case "ed25519": + case ed25519KeyType: pubHex, err := hex.DecodeString(key.KeyVal.Public) if err != nil { return err diff --git a/in_toto/model.go b/in_toto/model.go index dc2fd435..1f2fdc5b 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -77,7 +77,7 @@ if the KeyType is unknown. */ func validateKeyVal(key Key) error { switch key.KeyType { - case "ed25519": + case ed25519KeyType: err := validateHexString(key.KeyVal.Public) if err != nil { return err @@ -88,7 +88,7 @@ func validateKeyVal(key Key) error { return err } } - case "rsa", "ecdsa": + case rsaKeyType, ecdsaKeyType: data, _ := pem.Decode([]byte(key.KeyVal.Public)) if data == nil { return ErrNoPEMBlock From b3da4964c48f7418004608a26c7258410fd70aa3 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 7 Aug 2020 17:57:51 +0200 Subject: [PATCH 67/86] implement scheme and keytype checking This bigger commit introduces new errors for key and scheme type checking. We also have another helper function in utils.go for checking for subsets in a superset of string slices. Furthermore it adds various tests for the new functions --- in_toto/keylib.go | 48 ++++++++++++++++++++++++++++---- in_toto/model.go | 46 +++++++++++++++++++++++++++++++ in_toto/model_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++ in_toto/util.go | 23 ++++++++++++++++ in_toto/util_test.go | 18 ++++++++++++ 5 files changed, 193 insertions(+), 6 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 57c663bf..cccd1792 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -18,12 +18,6 @@ import ( "strings" ) -const ( - rsaKeyType string = "rsa" - ecdsaKeyType string = "ecdsa" - ed25519KeyType string = "ed25519" -) - // ErrFailedPEMParsing gets returned when PKCS1, PKCS8 or PKIX key parsing fails var ErrFailedPEMParsing = errors.New("failed parsing the PEM block: unsupported PEM type") @@ -39,6 +33,48 @@ var ErrInvalidSignature = errors.New("invalid signature") // ErrInvalidKeyType is returned when the keytype is invalid for the given key var ErrInvalidKeyType = errors.New("invalid key type for this key") +const ( + rsaKeyType string = "rsa" + ecdsaKeyType string = "ecdsa" + ed25519KeyType string = "ed25519" +) + +/* +getSupportedKeyIdHashAlgorithms returns a string slice of supported +keyIdHashAlgorithms. We need to use this function instead of a constant, +because Go does not support global constant slices. +*/ +func getSupportedKeyIdHashAlgorithms() []string { + return []string{"sha256", "sha512"} +} + +/* +getSupportedRSASchemes returns a string slice of supported RSA Key schemes. +We need to use this function instead of a constant because Go does not support +global constant slices. +*/ +func getSupportedRSASchemes() []string { + return []string{"rsassa-pss-sha256"} +} + +/* +getSupportedEcdsaSchemes returns a string slice of supported ecdsa Key schemes. +We need to use this function instead of a constant because Go does not support +global constant slices. +*/ +func getSupportedEcdsaSchemes() []string { + return []string{"ecdsa"} +} + +/* +getSupportedEd25519Schemes returns a string slice of supported ed25519 Key +schemes. We need to use this function instead of a constant because Go does +not support global constant slices. +*/ +func getSupportedEd25519Schemes() []string { + return []string{"ed25519"} +} + /* GenerateKeyId creates a partial key map and generates the key ID based on the created partial key map via the SHA256 method. diff --git a/in_toto/model.go b/in_toto/model.go index 1f2fdc5b..add286e2 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -54,6 +54,12 @@ var ErrEmptyKeyField = errors.New("empty field in key") // This error will be thrown, if a string doesn't match a hex string. var ErrInvalidHexString = errors.New("invalid hex string") +// This error will be thrown, if the given scheme does not match the given key type or vice-versa. +var ErrSchemeKeyTypeMismatch = errors.New("the scheme or key type is unsupported for its vice-versa") + +// This error will be thrown, if the specified KeyIdHashAlgorithms is not supported. +var ErrUnsupportedKeyIdHashAlgorithms = errors.New("the given keyID hash algorithm is not supported") + /* validateHexString is used to validate that a string passed to it contains only valid hexadecimal characters. @@ -105,6 +111,39 @@ func validateKeyVal(key Key) error { return nil } +/* +matchKeyTypeScheme checks if the specified scheme matches our specified +keyType. If the keyType is not supported it will return an +ErrUnsupportedKeyType. If the keyType and scheme do not match it will return +an ErrSchemeKeyTypeMismatch. If the specified keyType and scheme are +compatible matchKeyTypeScheme will return nil. +*/ +func matchKeyTypeScheme(key Key) error { + switch key.KeyType { + case rsaKeyType: + for _, scheme := range getSupportedRSASchemes() { + if key.Scheme == scheme { + return nil + } + } + case ed25519KeyType: + for _, scheme := range getSupportedEd25519Schemes() { + if key.Scheme == scheme { + return nil + } + } + case ecdsaKeyType: + for _, scheme := range getSupportedEcdsaSchemes() { + if key.Scheme == scheme { + return nil + } + } + default: + return fmt.Errorf("%w: %s", ErrUnsupportedKeyType, key.KeyType) + } + return ErrSchemeKeyTypeMismatch +} + /* validateKey checks the outer key object (everything, except the KeyVal struct). It verifies the keyId for being a hex string and checks for empty fields. @@ -134,6 +173,13 @@ func validateKey(key Key) error { if err != nil { return err } + err = matchKeyTypeScheme(key) + if err != nil { + return err + } + if !subsetCheck(key.KeyIdHashAlgorithms, getSupportedKeyIdHashAlgorithms()) { + return fmt.Errorf("%w: %#v, supported are: %#v", ErrUnsupportedKeyIdHashAlgorithms, key.KeyIdHashAlgorithms, getSupportedKeyIdHashAlgorithms()) + } return nil } diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 34880faf..fc15d3e5 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -1302,6 +1302,34 @@ func TestValidateKeyErrors(t *testing.T) { }, err: ErrUnsupportedKeyType, }, + { + name: "keytype scheme mismatch", + key: Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "29ad59693fe94c9d623afbb66554b4f6bb248c47761689ada4875ebda94840ae393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + }, + Scheme: "rsassa-pss-sha256", + }, + err: ErrSchemeKeyTypeMismatch, + }, + { + name: "unsupported KeyIdHashAlgorithms", + key: Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha128"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "29ad59693fe94c9d623afbb66554b4f6bb248c47761689ada4875ebda94840ae393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + }, + Scheme: "ed25519", + }, + err: ErrUnsupportedKeyIdHashAlgorithms, + }, } for _, table := range invalidTables { @@ -1311,3 +1339,39 @@ func TestValidateKeyErrors(t *testing.T) { } } } + +func TestMatchKeyTypeScheme(t *testing.T) { + tables := []struct { + name string + key Key + err error + }{ + {name: "test for unsupported key type", + key: Key{ + KeyId: "", + KeyIdHashAlgorithms: nil, + KeyType: "invalid", + KeyVal: KeyVal{}, + Scheme: "", + }, + err: ErrUnsupportedKeyType, + }, + { + name: "test for scheme key type mismatch", + key: Key{ + KeyId: "", + KeyIdHashAlgorithms: nil, + KeyType: "rsa", + KeyVal: KeyVal{}, + Scheme: "ed25519", + }, + err: ErrSchemeKeyTypeMismatch, + }, + } + for _, table := range tables { + err := matchKeyTypeScheme(table.key) + if !errors.Is(err, table.err) { + t.Errorf("%s returned wrong error. We got: %s, we should have got: %s", table.name, err, table.err) + } + } +} diff --git a/in_toto/util.go b/in_toto/util.go index b1369945..569e2da2 100644 --- a/in_toto/util.go +++ b/in_toto/util.go @@ -129,3 +129,26 @@ func InterfaceKeyStrings(m map[string]interface{}) []string { } return res } + +/* +subsetCheck checks if all strings in a slice of strings +can be found in a superset slice of strings. +*/ +func subsetCheck(subset []string, superset []string) bool { + // We use a Go label here to break out to the outer loop +OUTER: + for _, sub := range subset { + for _, super := range superset { + if sub == super { + continue OUTER + } + } + // If we cannot find a substring from subset in the superset + // we return false. In terms of keyIdHashAlgorithms for example + // this would mean, that we have an unsupported hash algorithm + // in our keyIdHashAlgorithm slice. + return false + } + // return true if all substrings can be found in the superset + return true +} diff --git a/in_toto/util_test.go b/in_toto/util_test.go index ade9d976..43257638 100644 --- a/in_toto/util_test.go +++ b/in_toto/util_test.go @@ -92,3 +92,21 @@ func TestInterfaceKeyStrings(t *testing.T) { t.Errorf("expected: %s, got: %s", expected, res) } } + +func TestSubsetCheck(t *testing.T) { + tables := []struct { + subset []string + superset []string + result bool + }{ + {[]string{"sha256"}, []string{"sha256", "sha512"}, true}, + {[]string{"sha512"}, []string{"sha256"}, false}, + {[]string{"sha256", "sha512"}, []string{"sha128", "sha256", "sha512"}, true}, + } + for _, table := range tables { + result := subsetCheck(table.subset, table.superset) + if table.result != result { + t.Errorf("result mismatch for: %#v, %#v, got: %t, should have got: %t", table.subset, table.superset, result, table.result) + } + } +} From c426bcb22e8e8e05765a005b567dfbd34cde2cc4 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Fri, 7 Aug 2020 23:08:57 +0200 Subject: [PATCH 68/86] change last string to constant --- in_toto/keylib.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index cccd1792..e76c737d 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -327,7 +327,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { hashed := sha256.Sum256(signable) switch parsedKey.(type) { case *rsa.PrivateKey: - if key.KeyType != "rsa" { + if key.KeyType != rsaKeyType { return signature, ErrInvalidKeyType } // We use rand.Reader as secure random source for rsa.SignPSS() From 6cb021ac8b83d71bb44274f346a1cf8297767472 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 8 Aug 2020 01:32:25 +0200 Subject: [PATCH 69/86] implement a decodeAndParse function to minimize copy-paste code The decodeAndParse function decodes the given pemBytes and parses a key. --- in_toto/keylib.go | 56 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index e76c737d..853865f7 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -197,6 +197,30 @@ func ParseKey(data []byte) (interface{}, error) { return nil, ErrFailedPEMParsing } +/* +decodeAndParse receives potential PEM bytes decodes them via pem.Decode +and pushes them to ParseKey. If any error occurs during this process, +the function will return nil and an error (either ErrFailedPEMParsing +or ErrNoPEMBlock). On success it will return the key object interface +and nil as error. +*/ +func decodeAndParse(pemBytes []byte) (interface{}, error) { + // pem.Decode returns the parsed pem block and a rest. + // The rest is everything, that could not be parsed as PEM block. + // Therefore we can drop this via using the blank identifier "_" + data, _ := pem.Decode(pemBytes) + if data == nil { + return nil, ErrNoPEMBlock + } + // Try to load private key, if this fails try to load + // key as public key + key, err := ParseKey(data.Bytes) + if err != nil { + return key, err + } + return key, nil +} + /* LoadKey loads the key file at specified file path into the key object. It automatically derives the PEM type and the key type. @@ -236,17 +260,7 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) return err } - // pem.Decode returns the parsed pem block and a rest. - // The rest is everything, that could not be parsed as PEM block. - // Therefore we can drop this via using the blank identifier "_" - data, _ := pem.Decode(pemBytes) - if data == nil { - return ErrNoPEMBlock - } - - // Try to load private key, if this fails try to load - // key as public key - key, err := ParseKey(data.Bytes) + key, err := decodeAndParse(pemBytes) if err != nil { return err } @@ -313,16 +327,9 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { // in which we are storing RSA keys in PEM format, but ed25519 keys hex encoded. switch key.KeyType { case rsaKeyType, ecdsaKeyType: - // pem.Decode returns the parsed pem block and a rest. - // The rest is everything, that could not be parsed as PEM block. - // Therefore we can drop this via using the blank identifier "_" - data, _ := pem.Decode([]byte(key.KeyVal.Private)) - if data == nil { - return signature, ErrNoPEMBlock - } - parsedKey, err := ParseKey(data.Bytes) + parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) if err != nil { - return signature, err + return Signature{}, err } hashed := sha256.Sum256(signable) switch parsedKey.(type) { @@ -395,14 +402,7 @@ it will return an error. func VerifySignature(key Key, sig Signature, unverified []byte) error { switch key.KeyType { case rsaKeyType, ecdsaKeyType: - // pem.Decode returns the parsed pem block and a rest. - // The rest is everything, that could not be parsed as PEM block. - // Therefore we can drop this via using the blank identifier "_" - data, _ := pem.Decode([]byte(key.KeyVal.Public)) - if data == nil { - return ErrNoPEMBlock - } - parsedKey, err := ParseKey(data.Bytes) + parsedKey, err := decodeAndParse([]byte(key.KeyVal.Public)) if err != nil { return err } From 86d191d12f81cecf25b9f7d8b2c0fc208e2e9bb9 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 8 Aug 2020 16:08:59 +0200 Subject: [PATCH 70/86] call validateKey before signing signable data and validating signatures This commit adds a call to validateKey to the GenerateSignature and ValidateSignature functions. This way we can ensure, that we are always dealing with a good key. This commit also provides modified tests and more test cases --- in_toto/keylib.go | 127 +++++++++++++++++++++-------------------- in_toto/keylib_test.go | 65 +++++++++++++-------- in_toto/model.go | 81 +++++++++++++++++++++++--- in_toto/model_test.go | 44 +++++++++++++- 4 files changed, 222 insertions(+), 95 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 853865f7..b921709a 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -30,8 +30,8 @@ var ErrUnsupportedKeyType = errors.New("unsupported key type") // ErrInvalidSignature is returned when the signature is invalid var ErrInvalidSignature = errors.New("invalid signature") -// ErrInvalidKeyType is returned when the keytype is invalid for the given key -var ErrInvalidKeyType = errors.New("invalid key type for this key") +// ErrInvalidKey is returned when a given key is none of RSA, ECDSA or ED25519 +var ErrInvalidKey = errors.New("invalid key") const ( rsaKeyType string = "rsa" @@ -320,52 +320,50 @@ return a not initialized signature and an error. Possible errors are: Currently supported is only one scheme per key. */ func GenerateSignature(signable []byte, key Key) (Signature, error) { + err := validateKey(key) + if err != nil { + return Signature{}, err + } var signature Signature var signatureBuffer []byte // The following switch block is needed for keeping interoperability // with the securesystemslib and the python implementation // in which we are storing RSA keys in PEM format, but ed25519 keys hex encoded. switch key.KeyType { - case rsaKeyType, ecdsaKeyType: + case rsaKeyType: parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) if err != nil { return Signature{}, err } hashed := sha256.Sum256(signable) - switch parsedKey.(type) { - case *rsa.PrivateKey: - if key.KeyType != rsaKeyType { - return signature, ErrInvalidKeyType - } - // We use rand.Reader as secure random source for rsa.SignPSS() - signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], - &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) - if err != nil { - return signature, err - } - case *ecdsa.PrivateKey: - if key.KeyType != ecdsaKeyType { - return signature, ErrInvalidKeyType - } - // ecdsa.Sign returns a signature that consists of two components called: r and s - // We assume here, that r and s are of the same size nLen and that - // the signature is 2*nLen. Furthermore we must note that hashes get truncated - // if they are too long for the curve. We use SHA256 for hashing, thus we should be - // ok with using the FIPS186-3 curves P256, P384 and P521. - r, s, err := ecdsa.Sign(rand.Reader, parsedKey.(*ecdsa.PrivateKey), hashed[:]) - if err != nil { - return signature, nil - } - // Generate the ecdsa signature on the same way, as we do in the securesystemslib - // We are marshalling the ecdsaSignature struct as ASN.1 INTEGER SEQUENCES - // into an ASN.1 Object. - signatureBuffer, err = asn1.Marshal(EcdsaSignature{ - R: r, - S: s, - }) - default: - return signature, fmt.Errorf("%w: %T", ErrUnsupportedKeyType, parsedKey) + // We use rand.Reader as secure random source for rsa.SignPSS() + signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], + &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return signature, err } + case ecdsaKeyType: + parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) + if err != nil { + return Signature{}, err + } + hashed := sha256.Sum256(signable) + // ecdsa.Sign returns a signature that consists of two components called: r and s + // We assume here, that r and s are of the same size nLen and that + // the signature is 2*nLen. Furthermore we must note that hashes get truncated + // if they are too long for the curve. We use SHA256 for hashing, thus we should be + // ok with using the FIPS186-3 curves P256, P384 and P521. + r, s, err := ecdsa.Sign(rand.Reader, parsedKey.(*ecdsa.PrivateKey), hashed[:]) + if err != nil { + return signature, nil + } + // Generate the ecdsa signature on the same way, as we do in the securesystemslib + // We are marshalling the ecdsaSignature struct as ASN.1 INTEGER SEQUENCES + // into an ASN.1 Object. + signatureBuffer, err = asn1.Marshal(EcdsaSignature{ + R: r, + S: s, + }) case ed25519KeyType: privateHex, err := hex.DecodeString(key.KeyVal.Private) if err != nil { @@ -400,46 +398,49 @@ On success it will return nil. In case of an unsupported key type or any other e it will return an error. */ func VerifySignature(key Key, sig Signature, unverified []byte) error { + err := validateKey(key) + if err != nil { + return err + } + sigBytes, err := hex.DecodeString(sig.Sig) + if err != nil { + return err + } switch key.KeyType { - case rsaKeyType, ecdsaKeyType: + case rsaKeyType: parsedKey, err := decodeAndParse([]byte(key.KeyVal.Public)) if err != nil { return err } hashed := sha256.Sum256(unverified) - sigBytes, _ := hex.DecodeString(sig.Sig) - switch parsedKey.(type) { - case *rsa.PublicKey: - err = rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigBytes, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) - if err != nil { - return fmt.Errorf("%w: %s", ErrInvalidSignature, err) - } - case *ecdsa.PublicKey: - var ecdsaSignature EcdsaSignature - // Unmarshal the ASN.1 DER marshalled ecdsa signature to - // ecdsaSignature. asn1.Unmarshal returns the rest and an error - // we can skip the rest here.. - _, err := asn1.Unmarshal(sigBytes, &ecdsaSignature) - if err != nil { - return err - } - // This may fail if a bigger hashing algorithm than SHA256 has been used for generating the signature - if err := ecdsa.Verify(parsedKey.(*ecdsa.PublicKey), hashed[:], ecdsaSignature.R, ecdsaSignature.S); err == false { - return ErrInvalidSignature - } - default: - return fmt.Errorf("%w: Key has type %T", ErrInvalidSignature, parsedKey) + err = rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigBytes, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return fmt.Errorf("%w: %s", ErrInvalidSignature, err) } - case ed25519KeyType: - pubHex, err := hex.DecodeString(key.KeyVal.Public) + case ecdsaKeyType: + var ecdsaSignature EcdsaSignature + parsedKey, err := decodeAndParse([]byte(key.KeyVal.Public)) if err != nil { return err } - sigHex, err := hex.DecodeString(sig.Sig) + hashed := sha256.Sum256(unverified) + // Unmarshal the ASN.1 DER marshalled ecdsa signature to + // ecdsaSignature. asn1.Unmarshal returns the rest and an error + // we can skip the rest here.. + _, err = asn1.Unmarshal(sigBytes, &ecdsaSignature) + if err != nil { + return err + } + // This may fail if a bigger hashing algorithm than SHA256 has been used for generating the signature + if err := ecdsa.Verify(parsedKey.(*ecdsa.PublicKey), hashed[:], ecdsaSignature.R, ecdsaSignature.S); err == false { + return ErrInvalidSignature + } + case ed25519KeyType: + pubHex, err := hex.DecodeString(key.KeyVal.Public) if err != nil { return err } - if ok := ed25519.Verify(pubHex, unverified, sigHex); !ok { + if ok := ed25519.Verify(pubHex, unverified, sigBytes); !ok { return fmt.Errorf("%w: ed25519", ErrInvalidSignature) } default: diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 682838fa..965fb484 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -2,7 +2,6 @@ package in_toto import ( "encoding/asn1" - "encoding/hex" "errors" "os" "testing" @@ -137,7 +136,7 @@ func TestGenerateSignatureErrors(t *testing.T) { expectedError error }{ {"invalid type", Key{ - KeyId: "invalid", + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", KeyIdHashAlgorithms: []string{"sha512"}, KeyType: "invalid", KeyVal: KeyVal{ @@ -149,7 +148,7 @@ func TestGenerateSignatureErrors(t *testing.T) { }, { "public key", Key{ - KeyId: "invalid", + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", KeyIdHashAlgorithms: []string{"sha512"}, KeyType: "ecdsa", KeyVal: KeyVal{ @@ -157,11 +156,11 @@ func TestGenerateSignatureErrors(t *testing.T) { Public: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----", }, Scheme: "ecdsa", - }, ErrUnsupportedKeyType, + }, ErrInvalidKey, }, { "rsa private key, but wrong key type", Key{ - KeyId: "invalid", + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", KeyIdHashAlgorithms: []string{"sha256"}, KeyType: "ecdsa", KeyVal: KeyVal{ @@ -169,11 +168,11 @@ func TestGenerateSignatureErrors(t *testing.T) { Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", }, Scheme: "rsassa-psa-sha256", - }, ErrInvalidKeyType, + }, ErrKeyKeyTypeMismatch, }, { "ecdsa private key, but wrong key type", Key{ - KeyId: "invalid", + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", KeyIdHashAlgorithms: []string{"sha256"}, KeyType: "rsa", KeyVal: KeyVal{ @@ -181,14 +180,14 @@ func TestGenerateSignatureErrors(t *testing.T) { Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", }, Scheme: "ecdsa", - }, ErrInvalidKeyType, + }, ErrKeyKeyTypeMismatch, }, { - "empty key", Key{}, ErrUnsupportedKeyType, + "empty key", Key{}, ErrInvalidHexString, }, { "invalid ec private key", Key{ - KeyId: "invalid", + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", KeyIdHashAlgorithms: []string{"sha256"}, KeyType: "ecdsa", KeyVal: KeyVal{ @@ -199,7 +198,7 @@ func TestGenerateSignatureErrors(t *testing.T) { }, ErrFailedPEMParsing, }, {"invalid ed25519 private key", Key{ - KeyId: "invalid", + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", KeyIdHashAlgorithms: []string{"sha512"}, KeyType: "ed25519", KeyVal: KeyVal{ @@ -207,7 +206,7 @@ func TestGenerateSignatureErrors(t *testing.T) { Public: "invalid", }, Scheme: "ed25519"}, - hex.InvalidByteError(105), + ErrInvalidHexString, }, } @@ -226,9 +225,9 @@ func TestVerifySignatureErrors(t *testing.T) { sig Signature expectedError error }{ - {"invalid keytype", Key{}, Signature{}, ErrInvalidSignature}, + {"invalid keytype", Key{}, Signature{}, ErrInvalidHexString}, {"invalid rsa/ecdsa public key", Key{ - KeyId: "invalid", + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", KeyIdHashAlgorithms: nil, KeyType: "rsa", KeyVal: KeyVal{ @@ -236,12 +235,12 @@ func TestVerifySignatureErrors(t *testing.T) { Public: "", }, Scheme: "rsassa-psa-sha256", - }, Signature{}, ErrNoPEMBlock, + }, Signature{}, ErrEmptyKeyField, }, { "ec public key", Key{ - KeyId: "invalid", - KeyIdHashAlgorithms: nil, + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha256"}, KeyType: "ecdsa", KeyVal: KeyVal{ Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----", @@ -252,15 +251,15 @@ func TestVerifySignatureErrors(t *testing.T) { }, { "rsa private key as public key", Key{ - KeyId: "invalid", - KeyIdHashAlgorithms: nil, + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, KeyType: "rsa", KeyVal: KeyVal{ Private: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", Public: "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----", }, Scheme: "rsassa-psa-sha256", - }, Signature{}, ErrInvalidSignature, + }, Signature{}, ErrInvalidKey, }, { "invalid ecdsa signature", Key{ @@ -293,8 +292,8 @@ func TestVerifySignatureErrors(t *testing.T) { }, ErrInvalidSignature, }, {"invalid asn1 structure", Key{ - KeyId: "invalid", - KeyIdHashAlgorithms: nil, + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha256"}, KeyType: "ecdsa", KeyVal: KeyVal{ Private: "", @@ -316,7 +315,7 @@ func TestVerifySignatureErrors(t *testing.T) { Public: "invalid", }, Scheme: "ed25519", - }, Signature{}, hex.InvalidByteError(105), + }, Signature{}, ErrInvalidHexString, }, { "ed25519 with invalid signature", Key{ @@ -331,7 +330,25 @@ func TestVerifySignatureErrors(t *testing.T) { }, Signature{ KeyId: "invalid", Sig: "invalid", - }, hex.InvalidByteError(105), + }, ErrInvalidHexString, + }, + { + name: "rsa test invalid signature", + key: Key{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN RSA PRIVATE KEY-----\nMIIG5QIBAAKCAYEAyCTik98953hKl6+B6n5l8DVIDwDnvrJfpasbJ3+Rw66YcawO\nZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXPr3foPHF455TlrqPVfCZiFQ+O4Caf\nxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYzeUHH4tH9MNzqKWbbJoekBsDpCDIx\np1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcTvpfZVDbXazQ7VqZkidt7geWq2Bid\nOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2LFMQ04A1KnGn1jxO35/fd6/OW32n\njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5ujlvSDjyfZu7c5yUQ2asYfQPLvnj\nG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/Vk43riJs165TJGYGVuLUhIEhHgiQ\ntwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBfp8348k6vJtDMB093/t6V9sTGYQcS\nbgKPyEQo5Pk6Wd4ZAgMBAAECggGBAIb8YZiMA2tfNSfy5jNqhoQo223LFYIHOf05\nVvofzwbkdcqM2bVL1SpJ5d9MPr7Jio/VDJpfg3JUjdqFBkj7tJRK0eYaPgoq4XIU\n64JtPM+pi5pgUnfFsi8mwO1MXO7AN7hd/3J1RdLfanjEYS/ADB1nIVI4gIR5KrE7\nvujQqO8pIsI1YEnTLa+wqEA0fSDACfo90pLCjBz1clL6qVAzYmy0a46h4k5ajv7V\nAI/96OHmLYDLsRa1Z60T2K17Q7se0zmHSjfssLQ+d+0zdU5BK8wFn1n2DvCc310T\na0ip+V+YNT0FBtmknTobnr9S688bR8vfBK0q0JsZ1YataGyYS0Rp0RYeEInjKie8\nDIzGuYNRzEjrYMlIOCCY5ybo9mbRiQEQvlSunFAAoKyr8svwU8/e2HV4lXxqDY9v\nKZzxeNYVvX2ZUP3D/uz74VvUWe5fz+ZYmmHVW0erbQC8Cxv2Q6SG/eylcfiNDdLG\narf+HNxcvlJ3v7I2w79tqSbHPcJc1QKBwQD6E/zRYiuJCd0ydnJXPCzZ3dhs/Nz0\ny9QJXg7QyLuHPGEV6r2nIK/Ku3d0NHi/hWglCrg2m8ik7BKaIUjvwVI7M/E3gcZu\ngknmlWjt5QY+LLfQdVgBeqwJdqLHXtw2GAJch6LGSxIcZ5F+1MmqUbfElUJ4h/To\nno6CFGfmAc2n6+PSMWxHT6Oe/rrAFQ2B25Kl9kIrfAUeWhtLm+n0ARXo7wKr63rg\nyJBXwr5Rl3U1NJGnuagQqcS7zDdZ2Glaj1cCgcEAzOIwl5Z0I42vU+2z9e+23Tyc\nHnSyp7AaHLJeuv92T8j7sF8qV1brYQqqzUAGpIGR6OZ9Vj2niPdbtdAQpgcTav+9\nBY9Nyk6YDgsTuN+bQEWsM8VfMUFVUXQAdNFJT6VPO877Fi0PnWhqxVVzr7GuUJFM\nzTUSscsqT40Ht2v1v+qYM4EziPUtUlxUbfuc0RwtfbSpALJG+rpPjvdddQ4Xsdj0\nEIoq1r/0v+vo0Dbpdy63N0iYh9r9yHioiUdCPUgPAoHBAJhKL7260NRFQ4UFiKAD\nLzUF2lSUsGIK9nc15kPS2hCC/oSATTpHt4X4H8iOY7IOJdvY6VGoEMoOUU23U1le\nGxueiBjLWPHXOfXHqvykaebXCKFTtGJCOB4TNxG+fNAcUuPSXZfwA3l0wK/CGYU0\n+nomgzIvaT93v0UL9DGni3vlNPm9yziqEPQ0H7n1mCIqeuXCT413mw5exRyIODK1\nrogJdVEIt+3Hdc9b8tZxK5lZCBJiBy0OlZXfyR1XouDZRQKBwC1++N1gio+ukcVo\nXnL5dTjxkZVtwpJcF6BRt5l8yu/yqHlE2KkmYwRckwsa8Z6sKxN1w1VYQZC3pQTd\nnCTSI2y6N2Y5qUOIalmL+igud1IxZojkhjvwzxpUURmfs9Dc25hjYPxOq03/9t21\nGQhlw1ieu1hCNdGHVPDvV0xSy/J/DKc7RI9gKl1EpXb6zZrdz/g/GtxNuldI8gvE\nQFuS8o4KqD/X/qVLYPURVNSPrQ5LMGI1W7GnXn2a1YoOadYj3wKBwQCh+crvbhDr\njb2ud3CJfdCs5sS5SEKADiUcxiJPcypxhmu+7vhG1Nr6mT0SAYWaA36GDJkU7/Oo\nvoal+uigbOt/UugS1nQYnEzDRkTidQMm1gXVNcWRTBFTKwRP/Gd6yOp9BUHJlFCu\nM2q8HYFtmSqOele6xFOAUnHhwVx4QURJYa+S5A603Jm6ETv0+Y6xdHX/02vA+pRt\nlQqaoEO7ScdRrzjgvVxXkEY3nwLcWdM61/RZTL0+be8goDw5cWt+PaA=\n-----END RSA PRIVATE KEY-----", + Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", + }, + Scheme: "rsassa-pss-sha256", + }, + sig: Signature{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + Sig: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + }, + expectedError: ErrInvalidSignature, }, } for _, table := range invalidTables { diff --git a/in_toto/model.go b/in_toto/model.go index add286e2..e7aa0249 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -1,8 +1,9 @@ package in_toto import ( + "crypto/ecdsa" + "crypto/rsa" "encoding/json" - "encoding/pem" "errors" "fmt" "io/ioutil" @@ -60,6 +61,9 @@ var ErrSchemeKeyTypeMismatch = errors.New("the scheme or key type is unsupported // This error will be thrown, if the specified KeyIdHashAlgorithms is not supported. var ErrUnsupportedKeyIdHashAlgorithms = errors.New("the given keyID hash algorithm is not supported") +// This error will be thrown, if the specified keyType does not match the key +var ErrKeyKeyTypeMismatch = errors.New("the given key does not match its key type") + /* validateHexString is used to validate that a string passed to it contains only valid hexadecimal characters. @@ -84,6 +88,10 @@ if the KeyType is unknown. func validateKeyVal(key Key) error { switch key.KeyType { case ed25519KeyType: + // We cannot use matchPublicKeyKeyType or matchPrivateKeyKeyType here, + // because we retrieve the key not from PEM. Hence we are dealing with + // plain ed25519 key bytes. These bytes can't be typechecked like in the + // matchKeyKeytype functions. err := validateHexString(key.KeyVal.Public) if err != nil { return err @@ -95,14 +103,22 @@ func validateKeyVal(key Key) error { } } case rsaKeyType, ecdsaKeyType: - data, _ := pem.Decode([]byte(key.KeyVal.Public)) - if data == nil { - return ErrNoPEMBlock + parsedKey, err := decodeAndParse([]byte(key.KeyVal.Public)) + if err != nil { + return err + } + err = matchPublicKeyKeyType(parsedKey, key.KeyType) + if err != nil { + return err } if key.KeyVal.Private != "" { - data, _ := pem.Decode([]byte(key.KeyVal.Private)) - if data == nil { - return ErrNoPEMBlock + parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) + if err != nil { + return err + } + err = matchPrivateKeyKeyType(parsedKey, key.KeyType) + if err != nil { + return err } } default: @@ -111,6 +127,57 @@ func validateKeyVal(key Key) error { return nil } +/* +matchPublicKeyKeyType validates an interface if it can be asserted to a +the RSA or ECDSA public key type. We can only check RSA and ECDSA this way, +because we are storing them in PEM format. Ed25519 keys are stored as plain +ed25519 keys encoded as hex strings, thus we have no metadata for them. +This function will return nil on success. If the key type does not match +it will return an ErrKeyKeyTypeMismatch. +*/ +func matchPublicKeyKeyType(key interface{}, keyType string) error { + switch key.(type) { + case *rsa.PublicKey: + if keyType != rsaKeyType { + return ErrKeyKeyTypeMismatch + } + case *ecdsa.PublicKey: + if keyType != ecdsaKeyType { + return ErrKeyKeyTypeMismatch + } + default: + return ErrInvalidKey + } + return nil +} + +/* +matchPrivateKeyKeyType validates an interface if it can be asserted to a +the RSA or ECDSA private key type. We can only check RSA and ECDSA this way, +because we are storing them in PEM format. Ed25519 keys are stored as plain +ed25519 keys encoded as hex strings, thus we have no metadata for them. +This function will return nil on success. If the key type does not match +it will return an ErrKeyKeyTypeMismatch. +*/ +func matchPrivateKeyKeyType(key interface{}, keyType string) error { + // we can only check RSA and ECDSA this way, because we are storing them in PEM + // format. ed25519 keys are stored as plain ed25519 keys encoded as hex strings + // so we have no metadata for them. + switch key.(type) { + case *rsa.PrivateKey: + if keyType != rsaKeyType { + return ErrKeyKeyTypeMismatch + } + case *ecdsa.PrivateKey: + if keyType != ecdsaKeyType { + return ErrKeyKeyTypeMismatch + } + default: + return ErrInvalidKey + } + return nil +} + /* matchKeyTypeScheme checks if the specified scheme matches our specified keyType. If the keyType is not supported it will return an diff --git a/in_toto/model_test.go b/in_toto/model_test.go index fc15d3e5..0d13e503 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -266,7 +266,7 @@ func TestMetablockVerifySignature(t *testing.T) { } expectedErrors := []string{ "No signature found", - "verification error", + "encoding/hex: invalid byte: U+0020 ' '", "json: unsupported type", } for i := 0; i < len(mbs); i++ { @@ -1330,6 +1330,48 @@ func TestValidateKeyErrors(t *testing.T) { }, err: ErrUnsupportedKeyIdHashAlgorithms, }, + { + name: "valid rsa public, but bad private key", + key: Key{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", + }, + Scheme: "rsassa-pss-sha256", + }, + err: ErrKeyKeyTypeMismatch, + }, + { + name: "valid ecdsa public key, but invalid ecdsa private key", + key: Key{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----", + Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ecdsa", + }, + err: ErrKeyKeyTypeMismatch, + }, + { + name: "rsa key, but with ed25519 private key", + key: Key{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", + }, + Scheme: "rsassa-pss-sha256", + }, + err: ErrInvalidKey, + }, } for _, table := range invalidTables { From 52fe163ffbe1c229f1fa5ec98f4ea5c8a8d48bd9 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 12 Aug 2020 16:57:40 +0200 Subject: [PATCH 71/86] use type assertion in GenerateSignature/ValidateSignature This commit untangles validateKey and validateKeyVal. We do not parse keys twice in GenerateSignature/ValidateSignature now. Instead we call validateKey for validating the key container and then using type assertions for checking for the right key type. --- in_toto/keylib.go | 20 ++++- in_toto/keylib_test.go | 96 +++++++++++++++++++++-- in_toto/model.go | 4 - in_toto/model_test.go | 168 ++++++++++++++++++++++++++++++----------- 4 files changed, 232 insertions(+), 56 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index b921709a..be7ad675 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -335,6 +335,10 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { if err != nil { return Signature{}, err } + parsedKey, ok := parsedKey.(*rsa.PrivateKey) + if !ok { + return Signature{}, ErrKeyKeyTypeMismatch + } hashed := sha256.Sum256(signable) // We use rand.Reader as secure random source for rsa.SignPSS() signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], @@ -347,6 +351,10 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { if err != nil { return Signature{}, err } + parsedKey, ok := parsedKey.(*ecdsa.PrivateKey) + if !ok { + return Signature{}, ErrKeyKeyTypeMismatch + } hashed := sha256.Sum256(signable) // ecdsa.Sign returns a signature that consists of two components called: r and s // We assume here, that r and s are of the same size nLen and that @@ -367,7 +375,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { case ed25519KeyType: privateHex, err := hex.DecodeString(key.KeyVal.Private) if err != nil { - return signature, err + return signature, ErrInvalidHexString } // Note: We can directly use the key for signing and do not // need to use ed25519.NewKeyFromSeed(). @@ -412,6 +420,10 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { if err != nil { return err } + parsedKey, ok := parsedKey.(*rsa.PublicKey) + if !ok { + return ErrKeyKeyTypeMismatch + } hashed := sha256.Sum256(unverified) err = rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigBytes, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) if err != nil { @@ -423,6 +435,10 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { if err != nil { return err } + parsedKey, ok := parsedKey.(*ecdsa.PublicKey) + if !ok { + return ErrKeyKeyTypeMismatch + } hashed := sha256.Sum256(unverified) // Unmarshal the ASN.1 DER marshalled ecdsa signature to // ecdsaSignature. asn1.Unmarshal returns the rest and an error @@ -438,7 +454,7 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { case ed25519KeyType: pubHex, err := hex.DecodeString(key.KeyVal.Public) if err != nil { - return err + return ErrInvalidHexString } if ok := ed25519.Verify(pubHex, unverified, sigBytes); !ok { return fmt.Errorf("%w: ed25519", ErrInvalidSignature) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 965fb484..176d91c0 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -156,7 +156,7 @@ func TestGenerateSignatureErrors(t *testing.T) { Public: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----", }, Scheme: "ecdsa", - }, ErrInvalidKey, + }, ErrKeyKeyTypeMismatch, }, { "rsa private key, but wrong key type", Key{ @@ -167,8 +167,8 @@ func TestGenerateSignatureErrors(t *testing.T) { Private: "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----", Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", }, - Scheme: "rsassa-psa-sha256", - }, ErrKeyKeyTypeMismatch, + Scheme: "rsassa-pss-sha256", + }, ErrSchemeKeyTypeMismatch, }, { "ecdsa private key, but wrong key type", Key{ @@ -180,7 +180,7 @@ func TestGenerateSignatureErrors(t *testing.T) { Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", }, Scheme: "ecdsa", - }, ErrKeyKeyTypeMismatch, + }, ErrSchemeKeyTypeMismatch, }, { "empty key", Key{}, ErrInvalidHexString, @@ -194,7 +194,7 @@ func TestGenerateSignatureErrors(t *testing.T) { Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----\n", Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", }, - Scheme: "ecdas", + Scheme: "ecdsa", }, ErrFailedPEMParsing, }, {"invalid ed25519 private key", Key{ @@ -208,6 +208,34 @@ func TestGenerateSignatureErrors(t *testing.T) { Scheme: "ed25519"}, ErrInvalidHexString, }, + { + name: "fail parsing RSA key, because of EC private key", + key: Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", + }, + Scheme: "rsassa-pss-sha256", + }, + expectedError: ErrFailedPEMParsing, + }, + { + name: "RSA key, but with ecdsa values", + key: Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", + }, + Scheme: "rsassa-pss-sha256", + }, + expectedError: ErrKeyKeyTypeMismatch, + }, } for _, table := range invalidTables { @@ -258,8 +286,8 @@ func TestVerifySignatureErrors(t *testing.T) { Private: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", Public: "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----", }, - Scheme: "rsassa-psa-sha256", - }, Signature{}, ErrInvalidKey, + Scheme: "rsassa-pss-sha256", + }, Signature{}, ErrKeyKeyTypeMismatch, }, { "invalid ecdsa signature", Key{ @@ -350,6 +378,60 @@ func TestVerifySignatureErrors(t *testing.T) { }, expectedError: ErrInvalidSignature, }, + { + name: "fail RSA parsing", + key: Key{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", + }, + Scheme: "rsassa-pss-sha256", + }, + sig: Signature{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + Sig: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + }, + expectedError: ErrFailedPEMParsing, + }, + { + name: "ecdsa Key, but RSA KeyVal", + key: Key{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----", + Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", + }, + Scheme: "ecdsa", + }, + sig: Signature{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + Sig: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + }, + expectedError: ErrKeyKeyTypeMismatch, + }, + { + name: "invalid hex string for ed25519", + key: Key{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "invalid", + Public: "invalid", + }, + Scheme: "ed25519", + }, + sig: Signature{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + Sig: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + }, + expectedError: ErrInvalidHexString, + }, } for _, table := range invalidTables { err := VerifySignature(table.key, table.sig, []byte("invalid")) diff --git a/in_toto/model.go b/in_toto/model.go index e7aa0249..ced2c378 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -236,10 +236,6 @@ func validateKey(key Key) error { if key.Scheme == "" { return fmt.Errorf("%w: scheme", ErrEmptyKeyField) } - err = validateKeyVal(key) - if err != nil { - return err - } err = matchKeyTypeScheme(key) if err != nil { return err diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 0d13e503..f35a2a92 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -1233,19 +1233,63 @@ func TestValidateKeyErrors(t *testing.T) { Scheme: "", }, ErrEmptyKeyField}, { - name: "invalid rsa pub key", + name: "invalid key type", key: Key{ KeyId: "bad", KeyIdHashAlgorithms: []string{"sha256"}, - KeyType: "rsa", + KeyType: "invalid", KeyVal: KeyVal{ - Private: "", - Public: "invalid", + Private: "invalid", + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", }, - Scheme: "rsassa-psa-sha56", + Scheme: "ed25519", }, - err: ErrNoPEMBlock, + err: ErrUnsupportedKeyType, + }, + { + name: "keytype scheme mismatch", + key: Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "29ad59693fe94c9d623afbb66554b4f6bb248c47761689ada4875ebda94840ae393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + }, + Scheme: "rsassa-pss-sha256", + }, + err: ErrSchemeKeyTypeMismatch, }, + { + name: "unsupported KeyIdHashAlgorithms", + key: Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha128"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "29ad59693fe94c9d623afbb66554b4f6bb248c47761689ada4875ebda94840ae393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + }, + Scheme: "ed25519", + }, + err: ErrUnsupportedKeyIdHashAlgorithms, + }, + } + + for _, table := range invalidTables { + err := validateKey(table.key) + if !errors.Is(err, table.err) { + t.Errorf("test '%s' failed, expected error: '%s', got '%s'", table.name, table.err, err) + } + } +} + +func TestValidateKeyVal(t *testing.T) { + tables := []struct { + name string + key Key + err error + }{ { name: "invalid rsa private key", key: Key{ @@ -1256,7 +1300,21 @@ func TestValidateKeyErrors(t *testing.T) { Private: "invalid", Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxPX3kFs/z645x4UOC3KF\nY3V80YQtKrp6YS3qU+Jlvx/XzK53lb4sCDRU9jqBBx3We45TmFUibroMd8tQXCUS\ne8gYCBUBqBmmz0dEHJYbW0tYF7IoapMIxhRYn76YqNdl1JoRTcmzIaOJ7QrHxQrS\nGpivvTm6kQ9WLeApG1GLYJ3C3Wl4bnsI1bKSv55Zi45/JawHzTzYUAIXX9qCd3Io\nHzDucz9IAj9Ookw0va/q9FjoPGrRB80IReVxLVnbo6pYJfu/O37jvEobHFa8ckHd\nYxUIg8wvkIOy1O3M74lBDm6CVI0ZO25xPlDB/4nHAE1PbA3aF3lw8JGuxLDsetxm\nfzgAleVt4vXLQiCrZaLf+0cM97JcT7wdHcbIvRLsij9LNP+2tWZgeZ/hIAOEdaDq\ncYANPDIAxfTvbe9I0sXrCtrLer1SS7GqUmdFCdkdun8erXdNF0ls9Rp4cbYhjdf3\nyMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE=\n-----END PUBLIC KEY-----", }, - Scheme: "rsassa-psa-sha256", + Scheme: "rsassa-pss-sha256", + }, + err: ErrNoPEMBlock, + }, + { + name: "invalid rsa pub key", + key: Key{ + KeyId: "bad", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", + KeyVal: KeyVal{ + Private: "", + Public: "invalid", + }, + Scheme: "rsassa-pss-sha256", }, err: ErrNoPEMBlock, }, @@ -1289,93 +1347,117 @@ func TestValidateKeyErrors(t *testing.T) { err: ErrInvalidHexString, }, { - name: "invalid key type", + name: "valid rsa public, but bad private key", key: Key{ - KeyId: "bad", + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", KeyIdHashAlgorithms: []string{"sha256"}, - KeyType: "invalid", + KeyType: "rsa", KeyVal: KeyVal{ - Private: "invalid", - Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", }, - Scheme: "ed25519", + Scheme: "rsassa-pss-sha256", }, - err: ErrUnsupportedKeyType, + err: ErrKeyKeyTypeMismatch, }, { - name: "keytype scheme mismatch", + name: "valid ecdsa public key, but invalid ecdsa private key", key: Key{ - KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", KeyIdHashAlgorithms: []string{"sha256"}, - KeyType: "ed25519", + KeyType: "ecdsa", KeyVal: KeyVal{ - Private: "29ad59693fe94c9d623afbb66554b4f6bb248c47761689ada4875ebda94840ae393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", - Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + Private: "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----", + Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", }, - Scheme: "rsassa-pss-sha256", + Scheme: "ecdsa", }, - err: ErrSchemeKeyTypeMismatch, + err: ErrKeyKeyTypeMismatch, }, { - name: "unsupported KeyIdHashAlgorithms", + name: "rsa key, but with ed25519 private key", key: Key{ - KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", - KeyIdHashAlgorithms: []string{"sha128"}, - KeyType: "ed25519", + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "rsa", KeyVal: KeyVal{ - Private: "29ad59693fe94c9d623afbb66554b4f6bb248c47761689ada4875ebda94840ae393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", - Public: "393e671b200f964c49083d34a867f5d989ec1c69df7b66758fe471c8591b139c", + Private: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", }, - Scheme: "ed25519", + Scheme: "rsassa-pss-sha256", }, - err: ErrUnsupportedKeyIdHashAlgorithms, + err: ErrInvalidKey, }, { - name: "valid rsa public, but bad private key", + name: "unsupported key type", + key: Key{ + KeyId: "", + KeyIdHashAlgorithms: nil, + KeyType: "invalid", + KeyVal: KeyVal{}, + Scheme: "", + }, + err: ErrUnsupportedKeyType, + }, + { + name: "rsa key type, but ed25519 key", key: Key{ KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", KeyIdHashAlgorithms: []string{"sha256"}, KeyType: "rsa", KeyVal: KeyVal{ - Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", - Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", + Private: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAOT5nGyAPlkxJCD00qGf12YnsHGnfe2Z1j+RxyFkbE5w=\n-----END PUBLIC KEY-----\n", }, Scheme: "rsassa-pss-sha256", }, - err: ErrKeyKeyTypeMismatch, + err: ErrInvalidKey, }, { - name: "valid ecdsa public key, but invalid ecdsa private key", + name: "rsa key, but not ecdsa key type", key: Key{ KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", KeyIdHashAlgorithms: []string{"sha256"}, KeyType: "ecdsa", KeyVal: KeyVal{ - Private: "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----", - Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", + Private: "", + Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", }, Scheme: "ecdsa", }, err: ErrKeyKeyTypeMismatch, }, { - name: "rsa key, but with ed25519 private key", + name: "ecdsa key, but rsa key type", key: Key{ KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", KeyIdHashAlgorithms: []string{"sha256"}, KeyType: "rsa", KeyVal: KeyVal{ - Private: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----\n", - Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", + Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", }, Scheme: "rsassa-pss-sha256", }, - err: ErrInvalidKey, + err: ErrKeyKeyTypeMismatch, + }, + { + name: "ecdsa key, but rsa key type", + key: Key{ + KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", + KeyIdHashAlgorithms: []string{"sha256"}, + KeyType: "ecdsa", + KeyVal: KeyVal{ + Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ecdsa", + }, + err: nil, }, } - - for _, table := range invalidTables { - err := validateKey(table.key) + for _, table := range tables { + err := validateKeyVal(table.key) if !errors.Is(err, table.err) { t.Errorf("test '%s' failed, expected error: '%s', got '%s'", table.name, table.err, err) } From 4ce8e4c5bc03e815ed169fde2602668d165203d7 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 12 Aug 2020 17:29:38 +0200 Subject: [PATCH 72/86] introduce constants for the schemes We now use constants for the supported Key schemes. --- in_toto/keylib.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index be7ad675..d74c2aa1 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -34,9 +34,12 @@ var ErrInvalidSignature = errors.New("invalid signature") var ErrInvalidKey = errors.New("invalid key") const ( - rsaKeyType string = "rsa" - ecdsaKeyType string = "ecdsa" - ed25519KeyType string = "ed25519" + rsaKeyType string = "rsa" + ecdsaKeyType string = "ecdsa" + ed25519KeyType string = "ed25519" + rsassapsssha256Scheme string = "rsassa-pss-sha256" + ecdsaScheme string = "ecdsa" + ed25519Scheme string = "ed25519" ) /* @@ -54,7 +57,7 @@ We need to use this function instead of a constant because Go does not support global constant slices. */ func getSupportedRSASchemes() []string { - return []string{"rsassa-pss-sha256"} + return []string{rsassapsssha256Scheme} } /* @@ -63,7 +66,7 @@ We need to use this function instead of a constant because Go does not support global constant slices. */ func getSupportedEcdsaSchemes() []string { - return []string{"ecdsa"} + return []string{ecdsaScheme} } /* @@ -72,7 +75,7 @@ schemes. We need to use this function instead of a constant because Go does not support global constant slices. */ func getSupportedEd25519Schemes() []string { - return []string{"ed25519"} + return []string{ed25519Scheme} } /* From 49ee80812247770837b8d546f8111d6cfdd2a830 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 12 Aug 2020 17:33:53 +0200 Subject: [PATCH 73/86] use panic for the default switch cases We can call a panic if we run into code, where we never should get. --- in_toto/keylib.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index d74c2aa1..702ec8b6 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -306,7 +306,8 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) return err } default: - return fmt.Errorf("%w: %T", ErrUnsupportedKeyType, key) + // We should never get here, because we implement all from Go supported Key Types + panic("unexpected Error in LoadKey function") } return nil } @@ -384,7 +385,9 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { // need to use ed25519.NewKeyFromSeed(). signatureBuffer = ed25519.Sign(privateHex, signable) default: - return signature, fmt.Errorf("%w: %s", ErrUnsupportedKeyType, key.KeyType) + // We should never get here, because we call validateKey in the first + // line of the function. + panic("unexpected Error in GenerateSignature function") } signature.Sig = hex.EncodeToString(signatureBuffer) signature.KeyId = key.KeyId @@ -463,7 +466,9 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { return fmt.Errorf("%w: ed25519", ErrInvalidSignature) } default: - return fmt.Errorf("%w: Key has type %s", ErrInvalidSignature, key.KeyType) + // We should never get here, because we call validateKey in the first + // line of the function. + panic("unexpected Error in VerifySignature function") } return nil } From e33856836eb0a05bdc528e3ad9b78a2c092c64f1 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 12 Aug 2020 23:34:28 +0200 Subject: [PATCH 74/86] add Scheme checking in Sign + Verify This commit adds a key.Scheme switch in the GeneratureSignature and VerifySignature functions. This only applies to ecdsa and rsa, because ed25519 is SHA512 only, due to following the edDSA spec. --- in_toto/keylib.go | 101 +++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 37 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 702ec8b6..17c1198e 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -33,6 +33,9 @@ var ErrInvalidSignature = errors.New("invalid signature") // ErrInvalidKey is returned when a given key is none of RSA, ECDSA or ED25519 var ErrInvalidKey = errors.New("invalid key") +// ErrUnsupportedScheme is returned when the specified Scheme is not supported +var ErrUnsupportedScheme = errors.New("unsupported key scheme") + const ( rsaKeyType string = "rsa" ecdsaKeyType string = "ecdsa" @@ -343,12 +346,17 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { if !ok { return Signature{}, ErrKeyKeyTypeMismatch } - hashed := sha256.Sum256(signable) - // We use rand.Reader as secure random source for rsa.SignPSS() - signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], - &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) - if err != nil { - return signature, err + switch key.Scheme { + case rsassapsssha256Scheme: + hashed := sha256.Sum256(signable) + // We use rand.Reader as secure random source for rsa.SignPSS() + signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:], + &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return signature, err + } + default: + return Signature{}, ErrUnsupportedScheme } case ecdsaKeyType: parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) @@ -359,24 +367,31 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { if !ok { return Signature{}, ErrKeyKeyTypeMismatch } - hashed := sha256.Sum256(signable) - // ecdsa.Sign returns a signature that consists of two components called: r and s - // We assume here, that r and s are of the same size nLen and that - // the signature is 2*nLen. Furthermore we must note that hashes get truncated - // if they are too long for the curve. We use SHA256 for hashing, thus we should be - // ok with using the FIPS186-3 curves P256, P384 and P521. - r, s, err := ecdsa.Sign(rand.Reader, parsedKey.(*ecdsa.PrivateKey), hashed[:]) - if err != nil { - return signature, nil + switch key.Scheme { + case ecdsaScheme: + hashed := sha256.Sum256(signable) + // ecdsa.Sign returns a signature that consists of two components called: r and s + // We assume here, that r and s are of the same size nLen and that + // the signature is 2*nLen. Furthermore we must note that hashes get truncated + // if they are too long for the curve. We use SHA256 for hashing, thus we should be + // ok with using the FIPS186-3 curves P256, P384 and P521. + r, s, err := ecdsa.Sign(rand.Reader, parsedKey.(*ecdsa.PrivateKey), hashed[:]) + if err != nil { + return signature, nil + } + // Generate the ecdsa signature on the same way, as we do in the securesystemslib + // We are marshalling the ecdsaSignature struct as ASN.1 INTEGER SEQUENCES + // into an ASN.1 Object. + signatureBuffer, err = asn1.Marshal(EcdsaSignature{ + R: r, + S: s, + }) + default: + return Signature{}, ErrUnsupportedScheme } - // Generate the ecdsa signature on the same way, as we do in the securesystemslib - // We are marshalling the ecdsaSignature struct as ASN.1 INTEGER SEQUENCES - // into an ASN.1 Object. - signatureBuffer, err = asn1.Marshal(EcdsaSignature{ - R: r, - S: s, - }) case ed25519KeyType: + // We do not need a scheme switch here, because ed25519Sign *only* + // supports ed25519-sha512 aka edDSA. privateHex, err := hex.DecodeString(key.KeyVal.Private) if err != nil { return signature, ErrInvalidHexString @@ -430,10 +445,15 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { if !ok { return ErrKeyKeyTypeMismatch } - hashed := sha256.Sum256(unverified) - err = rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigBytes, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) - if err != nil { - return fmt.Errorf("%w: %s", ErrInvalidSignature, err) + switch key.Scheme { + case rsassapsssha256Scheme: + hashed := sha256.Sum256(unverified) + err = rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], sigBytes, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return fmt.Errorf("%w: %s", ErrInvalidSignature, err) + } + default: + return ErrUnsupportedScheme } case ecdsaKeyType: var ecdsaSignature EcdsaSignature @@ -445,19 +465,26 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { if !ok { return ErrKeyKeyTypeMismatch } - hashed := sha256.Sum256(unverified) - // Unmarshal the ASN.1 DER marshalled ecdsa signature to - // ecdsaSignature. asn1.Unmarshal returns the rest and an error - // we can skip the rest here.. - _, err = asn1.Unmarshal(sigBytes, &ecdsaSignature) - if err != nil { - return err - } - // This may fail if a bigger hashing algorithm than SHA256 has been used for generating the signature - if err := ecdsa.Verify(parsedKey.(*ecdsa.PublicKey), hashed[:], ecdsaSignature.R, ecdsaSignature.S); err == false { - return ErrInvalidSignature + switch key.Scheme { + case ecdsaScheme: + hashed := sha256.Sum256(unverified) + // Unmarshal the ASN.1 DER marshalled ecdsa signature to + // ecdsaSignature. asn1.Unmarshal returns the rest and an error + // we can skip the rest here.. + _, err = asn1.Unmarshal(sigBytes, &ecdsaSignature) + if err != nil { + return err + } + // This may fail if a bigger hashing algorithm than SHA256 has been used for generating the signature + if err := ecdsa.Verify(parsedKey.(*ecdsa.PublicKey), hashed[:], ecdsaSignature.R, ecdsaSignature.S); err == false { + return ErrInvalidSignature + } + default: + return ErrUnsupportedScheme } case ed25519KeyType: + // We do not need a scheme switch here, because ed25519Sign *only* + // supports ed25519-sha512 aka edDSA. pubHex, err := hex.DecodeString(key.KeyVal.Public) if err != nil { return ErrInvalidHexString From 3c4832c08b9a4cc41a1ba937bdcbcd562ea3e59c Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Wed, 12 Aug 2020 23:51:35 +0200 Subject: [PATCH 75/86] unexport all functions in Keylib except Load,Sign,Verify We should hide most of the functions in keylib.go. Devs using our API should only use our exported functions LoadKey, GenerateSignature and VerifySignature. --- in_toto/keylib.go | 38 +++++++++++++++++++------------------- in_toto/keylib_test.go | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 17c1198e..38681f69 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -82,13 +82,13 @@ func getSupportedEd25519Schemes() []string { } /* -GenerateKeyId creates a partial key map and generates the key ID +generateKeyID creates a partial key map and generates the key ID based on the created partial key map via the SHA256 method. The resulting keyID will be directly saved in the corresponding key object. -On success GenerateKeyId will return nil, in case of errors while encoding +On success generateKeyID will return nil, in case of errors while encoding there will be an error. */ -func (k *Key) GenerateKeyId() error { +func (k *Key) generateKeyID() error { // Create partial key map used to create the keyid // Unfortunately, we can't use the Key object because this also carries // yet unwanted fields, such as KeyId and KeyVal.Private and therefore @@ -118,12 +118,12 @@ func (k *Key) GenerateKeyId() error { } /* -GeneratePublicPemBlock creates a "PUBLIC KEY" PEM block from public key byte data. +generatePublicPEMBlock creates a "PUBLIC KEY" PEM block from public key byte data. If successful it returns PEM block as []byte slice. This function should always succeed, if pubKeyBytes is empty the PEM block will have an empty byte block. Therefore only header and footer will exist. */ -func GeneratePublicPemBlock(pubKeyBytes []byte) []byte { +func generatePublicPEMBlock(pubKeyBytes []byte) []byte { // construct PEM block publicKeyPemBlock := &pem.Block{ Type: "PUBLIC KEY", @@ -134,19 +134,19 @@ func GeneratePublicPemBlock(pubKeyBytes []byte) []byte { } /* -SetKeyComponents sets all components in our key object. +setKeyComponents sets all components in our key object. Furthermore it makes sure to remove any trailing and leading whitespaces or newlines. We treat key types differently for interoperability reasons to the in-toto python implementation and the securesystemslib. */ -func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyType string, scheme string, keyIdHashAlgorithms []string) error { +func (k *Key) setKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyType string, scheme string, keyIdHashAlgorithms []string) error { // assume we have a privateKey if the key size is bigger than 0 switch keyType { case rsaKeyType, ecdsaKeyType: if len(privateKeyBytes) > 0 { k.KeyVal = KeyVal{ Private: strings.TrimSpace(string(privateKeyBytes)), - Public: strings.TrimSpace(string(GeneratePublicPemBlock(pubKeyBytes))), + Public: strings.TrimSpace(string(generatePublicPEMBlock(pubKeyBytes))), } } else { k.KeyVal = KeyVal{ @@ -170,14 +170,14 @@ func (k *Key) SetKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyTy k.KeyType = keyType k.Scheme = scheme k.KeyIdHashAlgorithms = keyIdHashAlgorithms - if err := k.GenerateKeyId(); err != nil { + if err := k.generateKeyID(); err != nil { return err } return nil } /* -ParseKey tries to parse a PEM []byte slice. Using the following standards +parseKey tries to parse a PEM []byte slice. Using the following standards in the given order: * PKCS8 @@ -187,7 +187,7 @@ in the given order: On success it returns the parsed key and nil. On failure it returns nil and the error ErrFailedPEMParsing */ -func ParseKey(data []byte) (interface{}, error) { +func parseKey(data []byte) (interface{}, error) { key, err := x509.ParsePKCS8PrivateKey(data) if err == nil { return key, nil @@ -205,7 +205,7 @@ func ParseKey(data []byte) (interface{}, error) { /* decodeAndParse receives potential PEM bytes decodes them via pem.Decode -and pushes them to ParseKey. If any error occurs during this process, +and pushes them to parseKey. If any error occurs during this process, the function will return nil and an error (either ErrFailedPEMParsing or ErrNoPEMBlock). On success it will return the key object interface and nil as error. @@ -220,7 +220,7 @@ func decodeAndParse(pemBytes []byte) (interface{}, error) { } // Try to load private key, if this fails try to load // key as public key - key, err := ParseKey(data.Bytes) + key, err := parseKey(data.Bytes) if err != nil { return key, err } @@ -274,7 +274,7 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) // Use type switch to identify the key format switch key.(type) { case *rsa.PublicKey: - if err := k.SetKeyComponents(pemBytes, []byte{}, rsaKeyType, scheme, keyIdHashAlgorithms); err != nil { + if err := k.setKeyComponents(pemBytes, []byte{}, rsaKeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case *rsa.PrivateKey: @@ -284,16 +284,16 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) if err != nil { return err } - if err := k.SetKeyComponents(pubKeyBytes, pemBytes, rsaKeyType, scheme, keyIdHashAlgorithms); err != nil { + if err := k.setKeyComponents(pubKeyBytes, pemBytes, rsaKeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case ed25519.PublicKey: - if err := k.SetKeyComponents(key.(ed25519.PublicKey), []byte{}, ed25519KeyType, scheme, keyIdHashAlgorithms); err != nil { + if err := k.setKeyComponents(key.(ed25519.PublicKey), []byte{}, ed25519KeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case ed25519.PrivateKey: pubKeyBytes := key.(ed25519.PrivateKey).Public() - if err := k.SetKeyComponents(pubKeyBytes.(ed25519.PublicKey), key.(ed25519.PrivateKey), ed25519KeyType, scheme, keyIdHashAlgorithms); err != nil { + if err := k.setKeyComponents(pubKeyBytes.(ed25519.PublicKey), key.(ed25519.PrivateKey), ed25519KeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case *ecdsa.PrivateKey: @@ -301,11 +301,11 @@ func (k *Key) LoadKey(path string, scheme string, keyIdHashAlgorithms []string) if err != nil { return err } - if err := k.SetKeyComponents(pubKeyBytes, pemBytes, ecdsaKeyType, scheme, keyIdHashAlgorithms); err != nil { + if err := k.setKeyComponents(pubKeyBytes, pemBytes, ecdsaKeyType, scheme, keyIdHashAlgorithms); err != nil { return err } case *ecdsa.PublicKey: - if err := k.SetKeyComponents(pemBytes, []byte{}, ecdsaKeyType, scheme, keyIdHashAlgorithms); err != nil { + if err := k.setKeyComponents(pemBytes, []byte{}, ecdsaKeyType, scheme, keyIdHashAlgorithms); err != nil { return err } default: diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 176d91c0..7ccad32f 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -122,7 +122,7 @@ func TestSetKeyComponents(t *testing.T) { for _, table := range invalidTables { var key Key - err := key.SetKeyComponents(table.pubkeyBytes, table.privateKeyBytes, table.keyType, table.scheme, table.keyIdHashAlgorithms) + err := key.setKeyComponents(table.pubkeyBytes, table.privateKeyBytes, table.keyType, table.scheme, table.keyIdHashAlgorithms) if !errors.Is(err, table.err) { t.Errorf("'%s' failed, should have: '%s', got: '%s'", table.name, ErrUnsupportedKeyType, err) } From 3dddc66d36da7568ebcd4cd0f257dbf8832fe304 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Thu, 13 Aug 2020 18:33:06 +0200 Subject: [PATCH 76/86] set correct ecdsa scheme We set the correct ecdsa schemes as constants. Right now we don't really differ between curves, because Go's crypto/(ecdsa|x509) are fully transparent with curves. If we want to differ between curves we might need to add addition logic, right now it's up to the developer, to choose the right scheme for the right curve. --- in_toto/keylib.go | 9 +++++---- in_toto/keylib_test.go | 28 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 38681f69..add32d84 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -41,7 +41,8 @@ const ( ecdsaKeyType string = "ecdsa" ed25519KeyType string = "ed25519" rsassapsssha256Scheme string = "rsassa-pss-sha256" - ecdsaScheme string = "ecdsa" + ecdsaSha2nistp256 string = "ecdsa-sha2-nistp256" + ecdsaSha2nistp384 string = "ecdsa-sha2-nistp384" ed25519Scheme string = "ed25519" ) @@ -69,7 +70,7 @@ We need to use this function instead of a constant because Go does not support global constant slices. */ func getSupportedEcdsaSchemes() []string { - return []string{ecdsaScheme} + return []string{ecdsaSha2nistp256, ecdsaSha2nistp384} } /* @@ -368,7 +369,7 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { return Signature{}, ErrKeyKeyTypeMismatch } switch key.Scheme { - case ecdsaScheme: + case ecdsaSha2nistp256, ecdsaSha2nistp384: hashed := sha256.Sum256(signable) // ecdsa.Sign returns a signature that consists of two components called: r and s // We assume here, that r and s are of the same size nLen and that @@ -466,7 +467,7 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { return ErrKeyKeyTypeMismatch } switch key.Scheme { - case ecdsaScheme: + case ecdsaSha2nistp256, ecdsaSha2nistp384: hashed := sha256.Sum256(unverified) // Unmarshal the ASN.1 DER marshalled ecdsa signature to // ecdsaSignature. asn1.Unmarshal returns the rest and an error diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 7ccad32f..71b23a4d 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -22,8 +22,8 @@ func TestLoadKey(t *testing.T) { {"rsa public key", "dan.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}, "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401"}, {"ed25519 private key", "carol", "ed25519", []string{"sha256", "sha512"}, "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6"}, {"ed25519 public key", "carol.pub", "ed25519", []string{"sha256", "sha512"}, "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6"}, - {"ecdsa private key", "frank", "ecdsa", []string{"sha256", "sha512"}, "d4cd6865653c3aaa9b9eb865e0e45dd8ed58c98cb39c0145d500e009d9817c32"}, - {"ecdsa public key", "frank.pub", "ecdsa", []string{"sha256", "sha512"}, "d4cd6865653c3aaa9b9eb865e0e45dd8ed58c98cb39c0145d500e009d9817c32"}, + {"ecdsa private key", "frank", "ecdsa-sha2-nistp256", []string{"sha256", "sha512"}, "0ab02fd8a1195d902d4e71df38123be0d3fa9ea45ebc6e1246d8e82179acb6dd"}, + {"ecdsa public key", "frank.pub", "ecdsa-sha2-nistp256", []string{"sha256", "sha512"}, "0ab02fd8a1195d902d4e71df38123be0d3fa9ea45ebc6e1246d8e82179acb6dd"}, } for _, table := range validTables { var key Key @@ -51,7 +51,7 @@ func TestValidSignatures(t *testing.T) { }{ {"rsa private key", "dan", "rsassa-pss-sha256", []string{"sha256", "sha512"}, `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}`}, {"ed25519 private key", "carol", "ed25519", []string{"sha256", "sha512"}, `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}`}, - {"ecdsa private key", "frank", "ecdsa", []string{"sha256", "sha512"}, `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}`}, + {"ecdsa private key", "frank", "ecdsa-sha2-nistp256", []string{"sha256", "sha512"}, `{"_type":"link","byproducts":{},"command":[],"environment":{},"materials":{},"name":"foo","products":{}}`}, } for _, table := range validTables { @@ -87,14 +87,14 @@ func TestLoadKeyErrors(t *testing.T) { err error }{ {"not existing file", "inToToRocks", "rsassa-pss-sha256", []string{"sha256", "sha512"}, os.ErrNotExist}, - {"existing, but invalid file", "demo.layout.template", "ecdsa", []string{"sha512"}, ErrNoPEMBlock}, - {"EC private key file", "erin", "ecdsa", []string{"sha256", "sha512"}, ErrFailedPEMParsing}, + {"existing, but invalid file", "demo.layout.template", "ecdsa-sha2-nistp256", []string{"sha512"}, ErrNoPEMBlock}, + {"EC private key file", "erin", "ecdsa-sha2-nistp256", []string{"sha256", "sha512"}, ErrFailedPEMParsing}, {"valid ed25519 private key, but invalid scheme", "carol", "", []string{"sha256"}, ErrEmptyKeyField}, {"valid ed25519 public key, but invalid scheme", "carol.pub", "", []string{"sha256"}, ErrEmptyKeyField}, {"valid rsa private key, but invalid hashalgo", "dan", "rsassa-psa-sha256", nil, ErrEmptyKeyField}, {"valid rsa public key, but invalid hashalgo", "dan.pub", "rsassa-psa-sha256", nil, ErrEmptyKeyField}, - {"valid ecdsa private key, but invalid hashalgo", "frank", "ecdsa", nil, ErrEmptyKeyField}, - {"valid ecdsa public key, but invalid hashalgo", "frank.pub", "ecdsa", nil, ErrEmptyKeyField}, + {"valid ecdsa private key, but invalid hashalgo", "frank", "ecdsa-sha2-nistp256", nil, ErrEmptyKeyField}, + {"valid ecdsa public key, but invalid hashalgo", "frank.pub", "ecdsa-sha2-nistp256", nil, ErrEmptyKeyField}, } for _, table := range invalidTables { @@ -155,7 +155,7 @@ func TestGenerateSignatureErrors(t *testing.T) { Private: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAOT5nGyAPlkxJCD00qGf12YnsHGnfe2Z1j+RxyFkbE5w=\n-----END PUBLIC KEY-----", Public: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----", }, - Scheme: "ecdsa", + Scheme: "ecdsa-sha2-nistp256", }, ErrKeyKeyTypeMismatch, }, { @@ -179,7 +179,7 @@ func TestGenerateSignatureErrors(t *testing.T) { Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----", Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", }, - Scheme: "ecdsa", + Scheme: "ecdsa-sha2-nistp256", }, ErrSchemeKeyTypeMismatch, }, { @@ -194,7 +194,7 @@ func TestGenerateSignatureErrors(t *testing.T) { Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----\n", Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", }, - Scheme: "ecdsa", + Scheme: "ecdsa-sha2-nistp256", }, ErrFailedPEMParsing, }, {"invalid ed25519 private key", Key{ @@ -274,7 +274,7 @@ func TestVerifySignatureErrors(t *testing.T) { Private: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIJ+y3Jy7kstRBzPmoOfak4t70DsLpFmlZLtppfcP14V3oAcGBSuBBAAK\noUQDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMTOZkriRklJ4HXQbJUWRpv2X8k\nspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END EC PRIVATE KEY-----", Public: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELToC9CwqXL8bRTG54QMn3k6dqwI0sDMT\nOZkriRklJ4HXQbJUWRpv2X8kspRECJZDoiOV1OaMMIXjY4XNeoEBmw==\n-----END PUBLIC KEY-----\n", }, - Scheme: "ecdsa", + Scheme: "ecdsa-sha2-nistp256", }, Signature{}, ErrFailedPEMParsing, }, { @@ -298,7 +298,7 @@ func TestVerifySignatureErrors(t *testing.T) { Private: "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6fQnV71xKx6kFgJv\nYTMq0ytvWi2mDlYu6aNm1761c1OSInbBxBNb0ligpM65KyaeeRce6JR9eQW6TB6R\n+5pNzvOhgYkDgYYABAFy0CeDAyV/2mY1NqxLLgqEXSxaqM3fM8gYn/ZWzrLnO+1h\nK2QAanID3JuPff1NdhehhL/U1prXdyyaItA5X4ChkQHMTsiS/3HkWRuLR8L22SGs\nB+7KqOeO5ELkqHO5tsy4kvsNrmersCGRQGY6A5V/0JFhP1u1JUvAVVhfRbdQXuu3\nrw==\n-----END PRIVATE KEY-----\n", Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", }, - Scheme: "ecdsa", + Scheme: "ecdsa-sha2-nistp256", }, Signature{ KeyId: "d4cd6865653c3aaa9b9eb865e0e45dd8ed58c98cb39c0145d500e009d9817c32", Sig: "308188824201fae620e5b53e878f2b5cc9b59b8246165ecf8fb3438115dff7ecd567106c707606dceac37ffe5fa531fc03ebe310ce9397d814f1d59c78ddd975123825f976141b824201bca2b5931850d0e8453c41d8a727f136d28a7683bad34c54643978ee066a1eab3403d9dd4e82641cd6325693ee32385aa7a0b5f239f53d8b8b1174f9751e1ee114", @@ -327,7 +327,7 @@ func TestVerifySignatureErrors(t *testing.T) { Private: "", Public: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBctAngwMlf9pmNTasSy4KhF0sWqjN\n3zPIGJ/2Vs6y5zvtYStkAGpyA9ybj339TXYXoYS/1Naa13csmiLQOV+AoZEBzE7I\nkv9x5Fkbi0fC9tkhrAfuyqjnjuRC5KhzubbMuJL7Da5nq7AhkUBmOgOVf9CRYT9b\ntSVLwFVYX0W3UF7rt68=\n-----END PUBLIC KEY-----\n", }, - Scheme: "ecdsa", + Scheme: "ecdsa-sha2-nistp256", }, Signature{ KeyId: "invalid", Sig: "BAAAAAAD", @@ -406,7 +406,7 @@ func TestVerifySignatureErrors(t *testing.T) { Private: "-----BEGIN RSA PRIVATE KEY-----\nMIIG5QIBAAKCAYEAyCTik98953hKl6+B6n5l8DVIDwDnvrJfpasbJ3+Rw66YcawO\nZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXPr3foPHF455TlrqPVfCZiFQ+O4Caf\nxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYzeUHH4tH9MNzqKWbbJoekBsDpCDIx\np1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcTvpfZVDbXazQ7VqZkidt7geWq2Bid\nOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2LFMQ04A1KnGn1jxO35/fd6/OW32n\njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5ujlvSDjyfZu7c5yUQ2asYfQPLvnj\nG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/Vk43riJs165TJGYGVuLUhIEhHgiQ\ntwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBfp8348k6vJtDMB093/t6V9sTGYQcS\nbgKPyEQo5Pk6Wd4ZAgMBAAECggGBAIb8YZiMA2tfNSfy5jNqhoQo223LFYIHOf05\nVvofzwbkdcqM2bVL1SpJ5d9MPr7Jio/VDJpfg3JUjdqFBkj7tJRK0eYaPgoq4XIU\n64JtPM+pi5pgUnfFsi8mwO1MXO7AN7hd/3J1RdLfanjEYS/ADB1nIVI4gIR5KrE7\nvujQqO8pIsI1YEnTLa+wqEA0fSDACfo90pLCjBz1clL6qVAzYmy0a46h4k5ajv7V\nAI/96OHmLYDLsRa1Z60T2K17Q7se0zmHSjfssLQ+d+0zdU5BK8wFn1n2DvCc310T\na0ip+V+YNT0FBtmknTobnr9S688bR8vfBK0q0JsZ1YataGyYS0Rp0RYeEInjKie8\nDIzGuYNRzEjrYMlIOCCY5ybo9mbRiQEQvlSunFAAoKyr8svwU8/e2HV4lXxqDY9v\nKZzxeNYVvX2ZUP3D/uz74VvUWe5fz+ZYmmHVW0erbQC8Cxv2Q6SG/eylcfiNDdLG\narf+HNxcvlJ3v7I2w79tqSbHPcJc1QKBwQD6E/zRYiuJCd0ydnJXPCzZ3dhs/Nz0\ny9QJXg7QyLuHPGEV6r2nIK/Ku3d0NHi/hWglCrg2m8ik7BKaIUjvwVI7M/E3gcZu\ngknmlWjt5QY+LLfQdVgBeqwJdqLHXtw2GAJch6LGSxIcZ5F+1MmqUbfElUJ4h/To\nno6CFGfmAc2n6+PSMWxHT6Oe/rrAFQ2B25Kl9kIrfAUeWhtLm+n0ARXo7wKr63rg\nyJBXwr5Rl3U1NJGnuagQqcS7zDdZ2Glaj1cCgcEAzOIwl5Z0I42vU+2z9e+23Tyc\nHnSyp7AaHLJeuv92T8j7sF8qV1brYQqqzUAGpIGR6OZ9Vj2niPdbtdAQpgcTav+9\nBY9Nyk6YDgsTuN+bQEWsM8VfMUFVUXQAdNFJT6VPO877Fi0PnWhqxVVzr7GuUJFM\nzTUSscsqT40Ht2v1v+qYM4EziPUtUlxUbfuc0RwtfbSpALJG+rpPjvdddQ4Xsdj0\nEIoq1r/0v+vo0Dbpdy63N0iYh9r9yHioiUdCPUgPAoHBAJhKL7260NRFQ4UFiKAD\nLzUF2lSUsGIK9nc15kPS2hCC/oSATTpHt4X4H8iOY7IOJdvY6VGoEMoOUU23U1le\nGxueiBjLWPHXOfXHqvykaebXCKFTtGJCOB4TNxG+fNAcUuPSXZfwA3l0wK/CGYU0\n+nomgzIvaT93v0UL9DGni3vlNPm9yziqEPQ0H7n1mCIqeuXCT413mw5exRyIODK1\nrogJdVEIt+3Hdc9b8tZxK5lZCBJiBy0OlZXfyR1XouDZRQKBwC1++N1gio+ukcVo\nXnL5dTjxkZVtwpJcF6BRt5l8yu/yqHlE2KkmYwRckwsa8Z6sKxN1w1VYQZC3pQTd\nnCTSI2y6N2Y5qUOIalmL+igud1IxZojkhjvwzxpUURmfs9Dc25hjYPxOq03/9t21\nGQhlw1ieu1hCNdGHVPDvV0xSy/J/DKc7RI9gKl1EpXb6zZrdz/g/GtxNuldI8gvE\nQFuS8o4KqD/X/qVLYPURVNSPrQ5LMGI1W7GnXn2a1YoOadYj3wKBwQCh+crvbhDr\njb2ud3CJfdCs5sS5SEKADiUcxiJPcypxhmu+7vhG1Nr6mT0SAYWaA36GDJkU7/Oo\nvoal+uigbOt/UugS1nQYnEzDRkTidQMm1gXVNcWRTBFTKwRP/Gd6yOp9BUHJlFCu\nM2q8HYFtmSqOele6xFOAUnHhwVx4QURJYa+S5A603Jm6ETv0+Y6xdHX/02vA+pRt\nlQqaoEO7ScdRrzjgvVxXkEY3nwLcWdM61/RZTL0+be8goDw5cWt+PaA=\n-----END RSA PRIVATE KEY-----", Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l\n8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP\nr3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz\neUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT\nvpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2\nLFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5\nujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/\nVk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf\np8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=\n-----END PUBLIC KEY-----", }, - Scheme: "ecdsa", + Scheme: "ecdsa-sha2-nistp256", }, sig: Signature{ KeyId: "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401", From 075e168dd98cff2b41281e19b16773daaa937e9c Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 15 Aug 2020 01:15:46 +0200 Subject: [PATCH 77/86] call panic for never reached default cases We have a few default cases, that were never reached due to validateKey() at the beginning of the function. Let's call a panic, if we run into such a situation. --- in_toto/keylib.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index add32d84..4659377d 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -357,7 +357,8 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { return signature, err } default: - return Signature{}, ErrUnsupportedScheme + // supported key schemes will get checked in validateKey + panic("unexpected Error in GenerateSignature function") } case ecdsaKeyType: parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) @@ -388,7 +389,8 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { S: s, }) default: - return Signature{}, ErrUnsupportedScheme + // supported key schemes will get checked in validateKey + panic("unexpected Error in GenerateSignature function") } case ed25519KeyType: // We do not need a scheme switch here, because ed25519Sign *only* @@ -454,7 +456,8 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { return fmt.Errorf("%w: %s", ErrInvalidSignature, err) } default: - return ErrUnsupportedScheme + // supported key schemes will get checked in validateKey + panic("unexpected Error in GenerateSignature function") } case ecdsaKeyType: var ecdsaSignature EcdsaSignature @@ -481,7 +484,8 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { return ErrInvalidSignature } default: - return ErrUnsupportedScheme + // supported key schemes will get checked in validateKey + panic("unexpected Error in GenerateSignature function") } case ed25519KeyType: // We do not need a scheme switch here, because ed25519Sign *only* From 236acb803e2318f44e0c7c32c717e63729c0dd42 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 15 Aug 2020 01:36:17 +0200 Subject: [PATCH 78/86] Fix various strings and documentation This commit addresses various spelling issues, substantial mistakes or adds more documentation. --- in_toto/keylib.go | 19 +++++++++++++------ in_toto/keylib_test.go | 2 +- in_toto/model.go | 6 +++--- in_toto/model_test.go | 4 ++-- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 4659377d..26925cda 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -237,12 +237,19 @@ Right now the following PEM types are supported: * PKCS8 for private keys * PKIX for public keys -The following key types are supported: +The following key types are supported and will be automatically assigned to +the key type field: * ed25519 - * RSA + * rsa * ecdsa +The following schemes are supported: + + * ed25519 -> ed25519 + * rsa -> rsassa-pss-sha256 + * ecdsa -> ecdsa-sha256-nistp256 + On success it will return nil. The following errors can happen: * path not found or not readable @@ -393,8 +400,8 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { panic("unexpected Error in GenerateSignature function") } case ed25519KeyType: - // We do not need a scheme switch here, because ed25519Sign *only* - // supports ed25519-sha512 aka edDSA. + // We do not need a scheme switch here, because ed25519 + // only consist of sha256 and curve25519. privateHex, err := hex.DecodeString(key.KeyVal.Private) if err != nil { return signature, ErrInvalidHexString @@ -488,8 +495,8 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { panic("unexpected Error in GenerateSignature function") } case ed25519KeyType: - // We do not need a scheme switch here, because ed25519Sign *only* - // supports ed25519-sha512 aka edDSA. + // We do not need a scheme switch here, because ed25519 + // only consist of sha256 and curve25519. pubHex, err := hex.DecodeString(key.KeyVal.Public) if err != nil { return ErrInvalidHexString diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 71b23a4d..0bf3107e 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -77,7 +77,7 @@ func TestValidSignatures(t *testing.T) { // // * os.ErrNotExist (triggered, when the file does not exist) // * ErrNoPEMBlock (for example if the passed file is not a PEM block) -// * ErrFailedPEMParsing (for example if we pass an EC key, instead a key in PKC8 format) +// * ErrFailedPEMParsing (for example if we pass an EC key, instead a key in PKCS8 format) func TestLoadKeyErrors(t *testing.T) { invalidTables := []struct { name string diff --git a/in_toto/model.go b/in_toto/model.go index ced2c378..dbec4622 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -49,14 +49,14 @@ type Key struct { Scheme string `json:"scheme"` } -// This error will be thrown in a field in our Key struct is empty. +// This error will be thrown if a field in our Key struct is empty. var ErrEmptyKeyField = errors.New("empty field in key") // This error will be thrown, if a string doesn't match a hex string. var ErrInvalidHexString = errors.New("invalid hex string") -// This error will be thrown, if the given scheme does not match the given key type or vice-versa. -var ErrSchemeKeyTypeMismatch = errors.New("the scheme or key type is unsupported for its vice-versa") +// This error will be thrown, if the given scheme and key type are not supported together. +var ErrSchemeKeyTypeMismatch = errors.New("the scheme and key type are not supported together") // This error will be thrown, if the specified KeyIdHashAlgorithms is not supported. var ErrUnsupportedKeyIdHashAlgorithms = errors.New("the given keyID hash algorithm is not supported") diff --git a/in_toto/model_test.go b/in_toto/model_test.go index f35a2a92..7cc12a0a 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -272,7 +272,7 @@ func TestMetablockVerifySignature(t *testing.T) { for i := 0; i < len(mbs); i++ { err := mbs[i].VerifySignature(key) if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { - t.Errorf("Metablock.VerifyRSASignature returned '%s', expected '%s'", + t.Errorf("Metablock.VerifySignature returned '%s', expected '%s'", err, expectedErrors[i]) } } @@ -284,7 +284,7 @@ func TestMetablockVerifySignature(t *testing.T) { } err := mb.VerifySignature(key) if err != nil { - t.Errorf("Metablock.VerifyRSASignature returned '%s', expected nil", err) + t.Errorf("Metablock.VerifySignature returned '%s', expected nil", err) } } From 111dd2fd8b845f0778390118bdd70b710abb1ffc Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 15 Aug 2020 01:44:56 +0200 Subject: [PATCH 79/86] add todo for subsetCheck function This adds a short comment/todo to our subsetCheck function. In the future we might want to use Sets for our constant getters. --- in_toto/util.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/in_toto/util.go b/in_toto/util.go index 569e2da2..142c91d8 100644 --- a/in_toto/util.go +++ b/in_toto/util.go @@ -135,6 +135,8 @@ subsetCheck checks if all strings in a slice of strings can be found in a superset slice of strings. */ func subsetCheck(subset []string, superset []string) bool { + // TODO: This function might be better as addition + // to our Set interface. // We use a Go label here to break out to the outer loop OUTER: for _, sub := range subset { From 5d51843c9d6282d730ed00d347d8d8851f385141 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 15 Aug 2020 01:52:04 +0200 Subject: [PATCH 80/86] Rename TestSetKeyComponents to TestSetKeyComponentsErrors --- in_toto/keylib_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 0bf3107e..7e5a684c 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -106,7 +106,7 @@ func TestLoadKeyErrors(t *testing.T) { } } -func TestSetKeyComponents(t *testing.T) { +func TestSetKeyComponentsErrors(t *testing.T) { invalidTables := []struct { name string pubkeyBytes []byte From d1bf3c1906c25b9e7853bc82e253f58bef365cdc Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sat, 15 Aug 2020 02:29:02 +0200 Subject: [PATCH 81/86] remove misleading support for ecdsa-sha2-nistp384 This commits removes the misleading support for ecdsa-sha2-nistp384. --- in_toto/keylib.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 26925cda..5d30e33e 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -377,13 +377,13 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { return Signature{}, ErrKeyKeyTypeMismatch } switch key.Scheme { - case ecdsaSha2nistp256, ecdsaSha2nistp384: + // TODO: add support for more hash algorithms + case ecdsaSha2nistp256: hashed := sha256.Sum256(signable) // ecdsa.Sign returns a signature that consists of two components called: r and s // We assume here, that r and s are of the same size nLen and that // the signature is 2*nLen. Furthermore we must note that hashes get truncated - // if they are too long for the curve. We use SHA256 for hashing, thus we should be - // ok with using the FIPS186-3 curves P256, P384 and P521. + // if they are too long for the curve. r, s, err := ecdsa.Sign(rand.Reader, parsedKey.(*ecdsa.PrivateKey), hashed[:]) if err != nil { return signature, nil @@ -477,7 +477,8 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { return ErrKeyKeyTypeMismatch } switch key.Scheme { - case ecdsaSha2nistp256, ecdsaSha2nistp384: + // TODO: add support for more hash algorithms + case ecdsaSha2nistp256: hashed := sha256.Sum256(unverified) // Unmarshal the ASN.1 DER marshalled ecdsa signature to // ecdsaSignature. asn1.Unmarshal returns the rest and an error @@ -486,7 +487,6 @@ func VerifySignature(key Key, sig Signature, unverified []byte) error { if err != nil { return err } - // This may fail if a bigger hashing algorithm than SHA256 has been used for generating the signature if err := ecdsa.Verify(parsedKey.(*ecdsa.PublicKey), hashed[:], ecdsaSignature.R, ecdsaSignature.S); err == false { return ErrInvalidSignature } From 64b532593199d7898e3266519875ded0ebe8a09b Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Sun, 16 Aug 2020 23:49:11 +0000 Subject: [PATCH 82/86] implement validatePublicKey + tests with this commit implements the validatePublicKey function, for checking if we deal with a public key. If the private key value field is not empty, it will fail with the error ErrNoPublicKey. We also call this method in validateLayout from now on. --- in_toto/model.go | 23 ++++++++++++++++++ in_toto/model_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/in_toto/model.go b/in_toto/model.go index dbec4622..0be6a2d4 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -64,6 +64,9 @@ var ErrUnsupportedKeyIdHashAlgorithms = errors.New("the given keyID hash algorit // This error will be thrown, if the specified keyType does not match the key var ErrKeyKeyTypeMismatch = errors.New("the given key does not match its key type") +// ErrNoPublicKey gets returned, when the private key value is not empty. +var ErrNoPublicKey = errors.New("the given key is not a public key") + /* validateHexString is used to validate that a string passed to it contains only valid hexadecimal characters. @@ -246,6 +249,22 @@ func validateKey(key Key) error { return nil } +/* +validatePublicKey is a wrapper around validateKey. It test if the private key +value in the key is empty and then validates the key via calling validateKey. +On success it will return nil, on error it will return an ErrNoPublicKey error. +*/ +func validatePublicKey(key Key) error { + if key.KeyVal.Private != "" { + return ErrNoPublicKey + } + err := validateKey(key) + if err != nil { + return err + } + return nil +} + /* Signature represents a generic in-toto signature that contains the identifier of the Key, which was used to create the signature and the signature data. The @@ -536,6 +555,10 @@ func validateLayout(layout Layout) error { if key.KeyId != keyId { return fmt.Errorf("invalid key found") } + err := validatePublicKey(key) + if err != nil { + return err + } } var namesSeen = make(map[string]bool) diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 7cc12a0a..9dea72b7 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -1499,3 +1499,57 @@ func TestMatchKeyTypeScheme(t *testing.T) { } } } + +func TestValidatePublicKey(t *testing.T) { + validTables := []struct { + name string + key Key + }{ + { + name: "test with valid key", + key: Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha512"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "", + Public: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAOT5nGyAPlkxJCD00qGf12YnsHGnfe2Z1j+RxyFkbE5w=\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ed25519", + }, + }, + } + for _, table := range validTables { + err := validatePublicKey(table.key) + if err != nil { + t.Errorf("%s returned error %s, instead of nil", table.name, err) + } + } + + invalidTables := []struct { + name string + key Key + err error + }{ + { + name: "test with valid key", + key: Key{ + KeyId: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6", + KeyIdHashAlgorithms: []string{"sha512"}, + KeyType: "ed25519", + KeyVal: KeyVal{ + Private: "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICmtWWk/6UydYjr7tmVUtPa7JIxHdhaJraSHXr2pSECu\n-----END PRIVATE KEY-----\n", + Public: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAOT5nGyAPlkxJCD00qGf12YnsHGnfe2Z1j+RxyFkbE5w=\n-----END PUBLIC KEY-----\n", + }, + Scheme: "ed25519", + }, + err: ErrNoPublicKey, + }, + } + for _, table := range invalidTables { + err := validatePublicKey(table.key) + if err != table.err { + t.Errorf("%s returned unexpected error %s, we should got: %s", table.name, err, table.err) + } + } +} From fa94594c5d8a77fbd96d5839efe8d121065e81f8 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 17 Aug 2020 14:51:43 +0200 Subject: [PATCH 83/86] keyIdHashAlgorithms is now optional + fix tests In this commit, we make Key.KeyIdHashAlgorithms optional. We only check the field now, if the field has been initialized. Furthermore this commit fixes a few tests and removes tests, that are not needed anymore. --- in_toto/keylib_test.go | 8 +++--- in_toto/model.go | 10 +++---- in_toto/model_test.go | 60 +++--------------------------------------- 3 files changed, 13 insertions(+), 65 deletions(-) diff --git a/in_toto/keylib_test.go b/in_toto/keylib_test.go index 7e5a684c..d0b9ddc1 100644 --- a/in_toto/keylib_test.go +++ b/in_toto/keylib_test.go @@ -91,10 +91,10 @@ func TestLoadKeyErrors(t *testing.T) { {"EC private key file", "erin", "ecdsa-sha2-nistp256", []string{"sha256", "sha512"}, ErrFailedPEMParsing}, {"valid ed25519 private key, but invalid scheme", "carol", "", []string{"sha256"}, ErrEmptyKeyField}, {"valid ed25519 public key, but invalid scheme", "carol.pub", "", []string{"sha256"}, ErrEmptyKeyField}, - {"valid rsa private key, but invalid hashalgo", "dan", "rsassa-psa-sha256", nil, ErrEmptyKeyField}, - {"valid rsa public key, but invalid hashalgo", "dan.pub", "rsassa-psa-sha256", nil, ErrEmptyKeyField}, - {"valid ecdsa private key, but invalid hashalgo", "frank", "ecdsa-sha2-nistp256", nil, ErrEmptyKeyField}, - {"valid ecdsa public key, but invalid hashalgo", "frank.pub", "ecdsa-sha2-nistp256", nil, ErrEmptyKeyField}, + {"valid rsa private key, but invalid scheme", "dan", "rsassa-psa-sha256", nil, ErrSchemeKeyTypeMismatch}, + {"valid rsa public key, but invalid scheme", "dan.pub", "rsassa-psa-sha256", nil, ErrSchemeKeyTypeMismatch}, + {"valid ecdsa private key, but invalid scheme", "frank", "ecdsa-sha-nistp256", nil, ErrSchemeKeyTypeMismatch}, + {"valid ecdsa public key, but invalid scheme", "frank.pub", "ecdsa-sha-nistp256", nil, ErrSchemeKeyTypeMismatch}, } for _, table := range invalidTables { diff --git a/in_toto/model.go b/in_toto/model.go index 0be6a2d4..5615cdb5 100644 --- a/in_toto/model.go +++ b/in_toto/model.go @@ -233,9 +233,6 @@ func validateKey(key Key) error { if key.KeyVal.Public == "" { return fmt.Errorf("%w: keyval.public", ErrEmptyKeyField) } - if key.KeyIdHashAlgorithms == nil { - return fmt.Errorf("%w: keyid_hash_algorithms", ErrEmptyKeyField) - } if key.Scheme == "" { return fmt.Errorf("%w: scheme", ErrEmptyKeyField) } @@ -243,8 +240,11 @@ func validateKey(key Key) error { if err != nil { return err } - if !subsetCheck(key.KeyIdHashAlgorithms, getSupportedKeyIdHashAlgorithms()) { - return fmt.Errorf("%w: %#v, supported are: %#v", ErrUnsupportedKeyIdHashAlgorithms, key.KeyIdHashAlgorithms, getSupportedKeyIdHashAlgorithms()) + // only check for supported keyIdHashAlgorithms, if the variable has been set + if key.KeyIdHashAlgorithms != nil { + if !subsetCheck(key.KeyIdHashAlgorithms, getSupportedKeyIdHashAlgorithms()) { + return fmt.Errorf("%w: %#v, supported are: %#v", ErrUnsupportedKeyIdHashAlgorithms, key.KeyIdHashAlgorithms, getSupportedKeyIdHashAlgorithms()) + } } return nil } diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 9dea72b7..46ca6559 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -719,8 +719,8 @@ func TestValidatePubKey(t *testing.T) { Scheme: "rsassa-pss-sha256", } - err := validateKey(testKey) - if !errors.Is(err, ErrEmptyKeyField) { + err := validatePublicKey(testKey) + if !errors.Is(err, nil) { t.Errorf("error validating public key: %s", err) } @@ -769,8 +769,8 @@ func TestValidatePubKey(t *testing.T) { Scheme: "rsassa-pss-sha256", } - err = validateKey(testKey) - if !errors.Is(err, ErrEmptyKeyField) { + err = validatePublicKey(testKey) + if !errors.Is(err, ErrNoPublicKey) { t.Error("validateKey error - private key not detected") } @@ -790,58 +790,6 @@ func TestValidatePubKey(t *testing.T) { } } -func TestvalidateKey(t *testing.T) { - key := Key{ - KeyId: "776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5", - KeyType: "invalid", - KeyVal: KeyVal{ - Private: "", - Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAO" + - "CAY8AMIIBigKCAYEAzgLBsMFSgwBiWTBmVsyW\n5KbJwLFSodAzdUhU2Bq6" + - "SdRz/W6UOBGdojZXibxupjRtAaEQW/eXDe+1CbKg6ENZ\nGt2D9HGFCQZgQ" + - "S8ONgNDQGiNxgApMA0T21AaUhru0vEofzdN1DfEF4CAGv5AkcgK\nsalhTy" + - "ONervFIjFEdXGelFZ7dVMV3Pp5WkZPG0jFQWjnmDZhUrtSxEtqbVghc3kK" + - "\nAUj9Ll/3jyi2wS92Z1j5ueN8X62hWX2xBqQ6nViOMzdujkoiYCRSwuMLR" + - "qzW2CbT\nL8hF1+S5KWKFzxl5sCVfpPe7V5HkgEHjwCILXTbCn2fCMKlaSb" + - "J/MG2lW7qSY2Ro\nwVXWkp1wDrsJ6Ii9f2dErv9vJeOVZeO9DsooQ5EuzLC" + - "fQLEU5mn7ul7bU7rFsb8J\nxYOeudkNBatnNCgVMAkmDPiNA7E33bmL5ARR" + - "wU0iZicsqLQR32pmwdap8PjofxqQ\nk7Gtvz/iYzaLrZv33cFWWTsEOqK1g" + - "KqigSqgW9T26wO9AgMBAAE=\n-----END PUBLIC KEY-----", - }, - Scheme: "rsassa-pss-sha256", - } - if err := validateKey(key); err.Error() != "invalid KeyType for key"+ - " '776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5':"+ - " should be 'rsa', got 'invalid'" { - t.Error("validateKey error - invalid type not detected") - } - - key = Key{ - KeyId: "776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5", - KeyType: "rsa", - KeyVal: KeyVal{ - Private: "", - Public: "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAO" + - "CAY8AMIIBigKCAYEAzgLBsMFSgwBiWTBmVsyW\n5KbJwLFSodAzdUhU2Bq6" + - "SdRz/W6UOBGdojZXibxupjRtAaEQW/eXDe+1CbKg6ENZ\nGt2D9HGFCQZgQ" + - "S8ONgNDQGiNxgApMA0T21AaUhru0vEofzdN1DfEF4CAGv5AkcgK\nsalhTy" + - "ONervFIjFEdXGelFZ7dVMV3Pp5WkZPG0jFQWjnmDZhUrtSxEtqbVghc3kK" + - "\nAUj9Ll/3jyi2wS92Z1j5ueN8X62hWX2xBqQ6nViOMzdujkoiYCRSwuMLR" + - "qzW2CbT\nL8hF1+S5KWKFzxl5sCVfpPe7V5HkgEHjwCILXTbCn2fCMKlaSb" + - "J/MG2lW7qSY2Ro\nwVXWkp1wDrsJ6Ii9f2dErv9vJeOVZeO9DsooQ5EuzLC" + - "fQLEU5mn7ul7bU7rFsb8J\nxYOeudkNBatnNCgVMAkmDPiNA7E33bmL5ARR" + - "wU0iZicsqLQR32pmwdap8PjofxqQ\nk7Gtvz/iYzaLrZv33cFWWTsEOqK1g" + - "KqigSqgW9T26wO9AgMBAAE=\n-----END PUBLIC KEY-----", - }, - Scheme: "invalid", - } - if err := validateKey(key); err.Error() != "invalid scheme for key"+ - " '776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5'"+ - ": should be 'rsassa-pss-sha256', got: 'invalid'" { - t.Error("validateKey error - invalid scheme not detected") - } -} - func TestValidateMetablock(t *testing.T) { testMetablock := Metablock{ Signatures: []Signature{ From 1ac47be58ccf237d0b6ede4df2c06148306ecef6 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 17 Aug 2020 15:05:17 +0200 Subject: [PATCH 84/86] add comment about ecdsa interoperability in-toto-golang behaves a little bit different to the securesystemslib. We should mention, that we use ecdsa/ecdsa-sha2-nistp256 pairs instead of ecdsa-sha2-nistp256 for key type and key scheme. --- in_toto/keylib.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/in_toto/keylib.go b/in_toto/keylib.go index 5d30e33e..f356b771 100644 --- a/in_toto/keylib.go +++ b/in_toto/keylib.go @@ -250,6 +250,10 @@ The following schemes are supported: * rsa -> rsassa-pss-sha256 * ecdsa -> ecdsa-sha256-nistp256 +Note that, this behavior is consistent with the securesystemslib, except for +ecdsa. We do not use the scheme string as key type in in-toto-golang. +Instead we are going with a ecdsa/ecdsa-sha2-nistp256 pair. + On success it will return nil. The following errors can happen: * path not found or not readable @@ -333,6 +337,10 @@ return a not initialized signature and an error. Possible errors are: * ErrUnsupportedKeyType Currently supported is only one scheme per key. + +Note that in-toto-golang has different requirements to an ecdsa key. +In in-toto-golang we use the string 'ecdsa' as string for the key type. +In the key scheme we use: ecdsa-sha2-nistp256. */ func GenerateSignature(signable []byte, key Key) (Signature, error) { err := validateKey(key) @@ -423,9 +431,9 @@ func GenerateSignature(signable []byte, key Key) (Signature, error) { VerifySignature will verify unverified byte data via a passed key and signature. Supported key types are: - * RSA - * ED25519 - * ECDSA + * rsa + * ed25519 + * ecdsa When encountering an RSA key, VerifySignature will decode the PEM block in the key and will call rsa.VerifyPSS() for verifying the RSA signature. @@ -435,6 +443,10 @@ When the given key is an ecdsa key, VerifySignature will unmarshall the ASN1 obj and will use the retrieved ecdsa components 'r' and 's' for verifying the signature. On success it will return nil. In case of an unsupported key type or any other error it will return an error. + +Note that in-toto-golang has different requirements to an ecdsa key. +In in-toto-golang we use the string 'ecdsa' as string for the key type. +In the key scheme we use: ecdsa-sha2-nistp256. */ func VerifySignature(key Key, sig Signature, unverified []byte) error { err := validateKey(key) From c25b937d9e9d022e5ecb8e82c6b56fa892dba6c7 Mon Sep 17 00:00:00 2001 From: Christian Rebischke Date: Mon, 17 Aug 2020 15:08:51 +0200 Subject: [PATCH 85/86] fix deadbeef test This adds the missing 'd' to the deadbeef test. We now check for a missing keyfield there. --- in_toto/model_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/in_toto/model_test.go b/in_toto/model_test.go index 46ca6559..1f8f838e 100644 --- a/in_toto/model_test.go +++ b/in_toto/model_test.go @@ -596,10 +596,10 @@ func TestValidateLayout(t *testing.T) { Type: "layout", Expires: "2020-02-27T18:03:43Z", Keys: map[string]Key{ - "deadbeef": Key{KeyId: "deabeef"}, + "deadbeef": Key{KeyId: "deadbeef"}, }, }, - "invalid key found", + "empty field in key: keytype", }, } From 70fdec2c35fa0c35f3482bfdef52bc1f9c03ed0b Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 20 Aug 2020 14:38:06 +0200 Subject: [PATCH 86/86] Remove done items from README todo in-toto-golang now supports all signing methods from the reference implementation and has a fully-fledged runlib, to generate signed link metadata. Big kudos to @shibumi! Signed-off-by: Lukas Puehringer --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index e4fa81a8..5f9b786f 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,8 @@ production use. If any of these features are necessary for your use case please let us know and we will try to provide them as soon as possible! -* [Signature schemes, other than `rsassa-pss-sha256` and `ed25519`](https://github.com/in-toto/in-toto-golang/issues/27) * [GPG keys](https://github.com/in-toto/in-toto-golang/issues/26) * [Layout parameter substitution](https://github.com/in-toto/in-toto-golang/issues/29) -* [in-toto-run functionality](https://github.com/in-toto/in-toto-golang/issues/30) - *Note: A basic `runlib` does exist, however it is only used to execute the - inspection commands in a layout and create the corresponding metadata. It - cannot be used to create signed evidence (link metadata) for steps in a - layout.* * [Hashing algorithms, other than `sha256` (in artifact recording)](https://github.com/in-toto/in-toto-golang/issues/31) * [Symbolic links (in artifact recording)](https://github.com/in-toto/in-toto-golang/issues/32) * [Exclude patterns (in artifact recording)](https://github.com/in-toto/in-toto-golang/issues/33)