From 4d9303dd665105cb5de1971cb6372be73c0b3f2c Mon Sep 17 00:00:00 2001 From: Pontus Freyhult Date: Thu, 22 Feb 2024 13:30:44 +0100 Subject: [PATCH 1/3] Add support for unencrypted private crypt4gh keys --- keys/keys.go | 60 ++++++++++++++++++++++++++++++++--------------- keys/keys_test.go | 13 ++++++++++ 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/keys/keys.go b/keys/keys.go index a2958d5..0c5ddc3 100644 --- a/keys/keys.go +++ b/keys/keys.go @@ -121,28 +121,58 @@ func readCrypt4GHPrivateKey(pemBytes, passPhrase []byte) (privateKey [chacha20po } var rounds uint32 var salt []byte - kdfunction, ok := kdf.KDFS[string(kdfName)] - if !ok { - return privateKey, fmt.Errorf("KDF %v not supported", string(kdfName)) - } - if string(kdfName) != "none" { - if passPhrase == nil { - return privateKey, errors.New("private key is password-protected, need a password for decryption") - } + + if string(kdfName) == none { + // Unencrypted private key + err = binary.Read(buffer, binary.BigEndian, &length) if err != nil { return } - err = binary.Read(buffer, binary.BigEndian, &rounds) + ciphername := make([]byte, length) + err = binary.Read(buffer, binary.BigEndian, &ciphername) if err != nil { return } - salt = make([]byte, length-4) - err = binary.Read(buffer, binary.BigEndian, &salt) + + if string(ciphername) != none { + return privateKey, errors.New("invalid private key: KDF is 'none', but cipher is not 'none'") + } + + err = binary.Read(buffer, binary.BigEndian, &length) if err != nil { return } + payload := make([]byte, length) + err = binary.Read(buffer, binary.BigEndian, &payload) + + copy(privateKey[:], payload) + + return } + if passPhrase == nil { + return privateKey, errors.New("private key is password-protected, need a password for decryption") + } + + kdfunction, ok := kdf.KDFS[string(kdfName)] + if !ok { + return privateKey, fmt.Errorf("KDF %v not supported", string(kdfName)) + } + + err = binary.Read(buffer, binary.BigEndian, &length) + if err != nil { + return + } + err = binary.Read(buffer, binary.BigEndian, &rounds) + if err != nil { + return + } + salt = make([]byte, length-4) + err = binary.Read(buffer, binary.BigEndian, &salt) + if err != nil { + return + } + err = binary.Read(buffer, binary.BigEndian, &length) if err != nil { return @@ -161,14 +191,6 @@ func readCrypt4GHPrivateKey(pemBytes, passPhrase []byte) (privateKey [chacha20po if err != nil { return } - if string(kdfName) == none { - if string(ciphername) != none { - return privateKey, errors.New("invalid private key: KDF is 'none', but cipher is not 'none'") - } - copy(privateKey[:], payload) - - return - } if string(ciphername) != supportedCipherName { return privateKey, fmt.Errorf("unsupported key encryption: %v", string(ciphername)) } diff --git a/keys/keys_test.go b/keys/keys_test.go index bc501cf..004f89a 100644 --- a/keys/keys_test.go +++ b/keys/keys_test.go @@ -46,6 +46,11 @@ ECAwQ= -----END OPENSSH PRIVATE KEY----- ` +const unencryptedCrypt4GH = `-----BEGIN CRYPT4GH PRIVATE KEY----- +YzRnaC12MQAEbm9uZQAEbm9uZQAgCvMeraG2L8NC9rDji46RXESWcXkoV5JeF0IiJdvzyhQ= +-----END CRYPT4GH PRIVATE KEY----- +` + const sshEd25519Pub = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGShUtbgxD70Gj+alwupjPHpTeIHf/s7pWNfx10VvYHV dmytrot@Dmytros-MacBook-Pro.local ` @@ -100,6 +105,14 @@ func TestReadKey(t *testing.T) { passPhrase []byte hash string }{ + { + name: "unencrypted.sec.pem", + content: unencryptedCrypt4GH, + readPrivateKeyFunction: ReadPrivateKey, + readPublicKeyFunction: nil, + passPhrase: nil, + hash: "0af31eada1b62fc342f6b0e38b8e915c449671792857925e17422225dbf3ca14", + }, { name: "crypt4gh-x25519-enc.sec.pem", content: crypt4ghX25519Sec, From 9060c4f390a9d6ae1f0b37802e0006d6febbff95 Mon Sep 17 00:00:00 2001 From: Pontus Freyhult Date: Tue, 27 Feb 2024 08:54:25 +0100 Subject: [PATCH 2/3] Add test for reading using an unencrypted key --- streaming/streaming_test.go | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/streaming/streaming_test.go b/streaming/streaming_test.go index fd009b0..25f51fd 100644 --- a/streaming/streaming_test.go +++ b/streaming/streaming_test.go @@ -43,6 +43,16 @@ YzRnaC12MQAGc2NyeXB0ABQAAAAA2l23+H3w2F3/Zylx5Gs2CwARY2hhY2hhMjBfcG9seTEzMDUAPOdx -----END CRYPT4GH PRIVATE KEY----- ` +const unencryptedSec = `-----BEGIN CRYPT4GH PRIVATE KEY----- +YzRnaC12MQAEbm9uZQAEbm9uZQAgmWET98yM/kaM27VkZGRktSR1q/htHspqBlzEL0FRD3g= +-----END CRYPT4GH PRIVATE KEY----- +` + +const unencryptedPub = `-----BEGIN CRYPT4GH PUBLIC KEY----- +4AOAKwiwvkjF6Wvoh9Aw5gUKjoOoRcA5svJadKzEDhM= +-----END CRYPT4GH PUBLIC KEY----- +` + func readerToReader(seekable bool, source io.Reader) io.Reader { if seekable { return source @@ -1325,3 +1335,65 @@ func TestSomeEOFs(t *testing.T) { } } } + +func TestUnencryptedPrivate(t *testing.T) { + for _, seekable := range []bool{true, false} { + inFile, err := os.Open("../test/sample.txt") + if err != nil { + t.Error(err) + } + + inBytes, err := io.ReadAll(inFile) + if err != nil { + t.Error(err) + } + + if err = inFile.Close(); err != nil { + t.Error(err) + } + + readerPublicKey, err := keys.ReadPublicKey(strings.NewReader(unencryptedPub)) + if err != nil { + t.Error(err) + } + buffer := bytes.Buffer{} + + readerPublicKeyList := [][chacha20poly1305.KeySize]byte{} + readerPublicKeyList = append(readerPublicKeyList, readerPublicKey) + + writer, err := NewCrypt4GHWriterWithoutPrivateKey(&buffer, readerPublicKeyList, nil) + if err != nil { + t.Error(err) + } + + if r, err := writer.Write(inBytes[:20000]); err != nil || r != 20000 { + t.Errorf("Problem when writing to cryptgh writer, r=%d, err=%v", r, err) + } + + err = writer.Close() + if err != nil { + t.Error(err) + } + readerSecretKey, err := keys.ReadPrivateKey(strings.NewReader(unencryptedSec), nil) + if err != nil { + t.Error(err) + } + + bufferReader := bytes.NewReader(buffer.Bytes()) + + reader, err := NewCrypt4GHReader(readerToReader(seekable, bufferReader), readerSecretKey, nil) + if err != nil { + t.Error(err) + } + + buf := make([]byte, 16384) + r, err := reader.Read(buf) + if err != nil || r != len(buf) { + t.Errorf("Read returned unexpected r=%v, err=%v", r, err) + } + + if !bytes.Equal(buf, inBytes[:16384]) { + t.Errorf("Content mismatch when passing segment boundary") + } + } +} From 89e389734138dc8fe7db266c1d778597710cc428 Mon Sep 17 00:00:00 2001 From: Pontus Freyhult Date: Tue, 27 Feb 2024 09:13:21 +0100 Subject: [PATCH 3/3] Bump version --- internal/version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/version/version.go b/internal/version/version.go index ebe5cb7..703f549 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -7,7 +7,7 @@ import ( ) // The version in the current branch -var Version = "1.9.0" +var Version = "1.9.1" // If this is "" (empty string) then it means that it is a final release. // Otherwise, this is a pre-release e.g. "dev", "beta", "rc1", etc.