From 0f38879de78ca62158dedafb22af9a6186067438 Mon Sep 17 00:00:00 2001 From: Gabriel Handford Date: Sun, 26 Jul 2020 17:27:38 -0700 Subject: [PATCH 1/4] saltpack: Autodetect decrypt/verify input --- edx25519.go | 2 +- errors.go | 3 + saltpack/README.md | 171 +---------------- saltpack/detect.go | 61 ++++++ saltpack/detect_test.go | 48 +++++ saltpack/encrypt.go | 168 ++++++++++------- saltpack/encrypt_examples_test.go | 14 +- saltpack/encrypt_test.go | 100 +++++++--- saltpack/saltpack.go | 26 ++- saltpack/sign.go | 296 +++++++++++++++++++++--------- saltpack/sign_examples_test.go | 10 +- saltpack/sign_test.go | 105 +++++++++-- saltpack/signcrypt.go | 91 ++++----- saltpack/signcrypt_test.go | 67 ++++--- 14 files changed, 706 insertions(+), 456 deletions(-) create mode 100644 saltpack/detect.go create mode 100644 saltpack/detect_test.go diff --git a/edx25519.go b/edx25519.go index c61dd47..5abb784 100644 --- a/edx25519.go +++ b/edx25519.go @@ -228,7 +228,7 @@ func (s *EdX25519PublicKey) Verify(b []byte) ([]byte, error) { } _, ok := sign.Open(nil, b, s.publicKey) if !ok { - return nil, errors.Errorf("verify failed") + return nil, ErrVerifyFailed } return b[sign.Overhead:], nil } diff --git a/errors.go b/errors.go index 2a7442a..0780b3b 100644 --- a/errors.go +++ b/errors.go @@ -6,6 +6,9 @@ import ( "github.com/pkg/errors" ) +// ErrVerifyFailed if key verify failed. +var ErrVerifyFailed = errors.New("verify failed") + // ErrNotFound describes a key not found error when a key is required. type ErrNotFound struct { ID string diff --git a/saltpack/README.md b/saltpack/README.md index e2abb2c..304b493 100644 --- a/saltpack/README.md +++ b/saltpack/README.md @@ -1,171 +1,10 @@ # Saltpack -This package allows you to encrypt/decrypt and sign/verify using Saltpack and -EdX25519 keys. +See [saltpack.org](https://saltpack.org) for more details. -For more details visit **[keys.pub](https://keys.pub)**. +This [github.com/keys-pub/keys/saltpack](https://github.com/keys-pub/keys/tree/master/saltpack) package allows you to encrypt/decrypt, sign/verify using Saltpack. -## Encrypt +## Examples -The following example describes how to: - -- Generate an EdX25519 key -- Encrypt to recipients (Alice and Bob) using Saltpack - -```go -import ( - "github.com/keys-pub/keys" - "github.com/keys-pub/keys/saltpack" -) - -... -// Alice -alice := keys.GenerateEdX25519Key() - -// Bob -bobID := keys.ID("kex1yy7amjzd5ld3k0uphvyetlz2vd8yy3fky64dut9jdf9qh852f0nsxjgv0m") - -message := []byte("hi bob") - -// Encrypt using Saltpack from alice to bob (include alice as a recipient too). -sp := saltpack.New(nil) -encrypted, err := sp.EncryptArmored(message, alice.X25519Key(), bobID, alice.ID()) -if err != nil { - log.Fatal(err) -} - -fmt.Printf("Encrypted: %s\n", string(encrypted)) -``` - -## Decrypt - -The following example decrypts the message from the Encrypt example: - -- Initialize and setup/unlock a Keyring -- Create a Store -- Import a EdX25519 key -- Decrypt and verify a Saltpack message - -```go -import ( - "github.com/keys-pub/keys" - "github.com/keys-pub/keys/keyring" - "github.com/keys-pub/keys/saltpack" -) - -... - -// Message from Alice -aliceID := keys.ID("kex1vrpxw9rqmf49kygc7ujjrdlx8lkzaarjc3s24j73xlqxhwvsyx2sw06r82") -encrypted := `BEGIN SALTPACK ENCRYPTED MESSAGE. -kcJn5brvybfNjz6 D5ll2Nk0YusOJBf 9x1CB6V3o7cdMOV ZPenXvEVhLpMBj0 8rJiM2GJTyXbhDn -cGIoczvWtRoxL5r 3EIPrfVqpwhLDke LfCV6YykdYdGwY1 lUfrzkOIUGdeURb HDSwgrTSrcexwj3 -ix9Mw1FVXQGBwBV yil8lLyD1q0VFGv KmgJYyARppqQEIF HgAsZq0BJL6Dosz WGrFalmG90QA6PO -avDlwRXMDbjKFvE wQtaBDKXVSBaM9k 0Xu0CfdGUkEICbN vZNV67cGqEz2IiH kr8. -END SALTPACK ENCRYPTED MESSAGE.` - -// Bob creates a Keyring and Store -kr, err := keyring.New("BobKeyring") -if err != nil { - log.Fatal(err) -} -if err := kr.UnlockWithPassword("bobpassword", true); err != nil { - log.Fatal(err) -} -ks := keys.NewStore(kr) - -// Import edx25519 key to bob's Store -kmsg := `BEGIN EDX25519 KEY MESSAGE. -E9zL57KzBY1CIdJ d5tlpnyCIX8R5DB oLswy2g17kbfK4s CwryRUoII3ZNk3l -scLQrPmgNuKi9OK 7ugGoVWBY2n5xbK 7w500Vp2iXo6LAe XZiB06UjUdCoYJv -YjKbul2B61uxTZc waeBgRV91RZoKCn xLQnRhLXE2KC. -END EDX25519 KEY MESSAGE.` -bob, err := keys.DecodeKeyFromSaltpack(kmsg, "password", false) -if err != nil { - log.Fatal(err) -} -if err := ks.SaveKey(bob); err != nil { - log.Fatal(err) -} - -// Bob decrypts the saltpack message. -sp := saltpack.New(ks) -out, sender, err := sp.DecryptArmored(encrypted) -if err != nil { - log.Fatal(err) -} - -// The sender from Saltpack Decrypt is a X25519 public key, so find the -// corresponding EdX25519 public key. -if sender != nil { - pk, err := ks.FindEdX25519PublicKey(sender.ID()) - if err != nil { - log.Fatal(err) - } - if pk != nil && pk.ID() == aliceID { - fmt.Printf("signer is alice\n") - } -} - -fmt.Printf("%s\n", string(out)) -``` - -## Sign - -Sign bytes to an armored saltpack message. - -```go -import ( - "github.com/keys-pub/keys" - "github.com/keys-pub/keys/saltpack" -) -... - -sp := saltpack.New(nil) - -alice := keys.GenerateEdX25519Key() - -message := []byte("hi from alice") - -sig, err := sp.SignArmored(message, alice) -if err != nil { - log.Fatal(err) -} -fmt.Printf("%s\n", alice.ID()) -fmt.Printf("%s\n", sig) -``` - -## Verify - -Verify a signed saltpack message. - -```go -import ( - "github.com/keys-pub/keys" - "github.com/keys-pub/keys/saltpack" -) -... - -sp := saltpack.New(nil) - -aliceID := keys.ID("kex1w2jep8dkr2s0g9kx5g6xe3387jslnlj08yactvn8xdtrx4cnypjq9rpnux") -signed := `BEGIN SALTPACK SIGNED MESSAGE. -kXR7VktZdyH7rvq v5wcIkHbs7mPCSd NhKLR9E0K47y29T QkuYinHym6EfZwL -1TwgxI3RQ52fHg5 1FzmLOMghcYLcV7 i0l0ovabGhxGrEl z7WuI4O3xMU5saq -U28RqUnKNroATPO 5rn2YyQcut2SeMn lXJBlDqRv9WyxjG M0PcKvsAsvmid1m -cqA4TCjz5V9VpuO zuIQ55lRQLeP5kU aWFxq5Nl8WsPqlR RdX86OuTbaKUvKI -wdNd6ISacrT0I82 qZ71sc7sTxiMxoI P43uCGaAZZ3Ab62 vR8N6WQPE8. -END SALTPACK SIGNED MESSAGE.` - -out, signer, err := sp.VerifyArmored(signed) -if err != nil { - log.Fatal(err) -} -if signer == aliceID { - fmt.Printf("signer is alice\n") -} -fmt.Printf("%s\n", string(out)) -// Output: -// signer is alice -// hi from alice -``` +- [Encrypt + Decrypt](https://github.com/keys-pub/keys/blob/master/saltpack/encrypt_examples_test.go) +- [Sign + Verify](https://github.com/keys-pub/keys/blob/master/saltpack/sign_examples_test.go) diff --git a/saltpack/detect.go b/saltpack/detect.go new file mode 100644 index 0000000..035e015 --- /dev/null +++ b/saltpack/detect.go @@ -0,0 +1,61 @@ +package saltpack + +import ( + "bytes" + + ksaltpack "github.com/keybase/saltpack" + "github.com/keys-pub/keys" +) + +// Encoding for saltpack (armored vs binary, encrypt vs signcrypt). +type Encoding string + +const ( + // UnknownEncoding is unknown. + UnknownEncoding Encoding = "" + // EncryptEncoding used saltpack.Encrypt + EncryptEncoding Encoding = "encrypt" + // SigncryptEncoding used saltpack.Signcrypt + SigncryptEncoding Encoding = "signcrypt" + // SignEncoding used saltpack.Sign + SignEncoding Encoding = "sign" +) + +func detectEncrypt(b []byte) (Encoding, bool) { + if _, _, err := NewDecryptStream(bytes.NewReader(b), false, nil); err == ksaltpack.ErrNoDecryptionKey { + return EncryptEncoding, false + } + if _, _, err := NewDecryptStream(bytes.NewReader(b), true, nil); err == ksaltpack.ErrNoDecryptionKey { + return EncryptEncoding, true + } + if _, _, err := NewSigncryptOpenStream(bytes.NewReader(b), false, nil); err == ksaltpack.ErrNoDecryptionKey { + return SigncryptEncoding, false + } + if _, _, err := NewSigncryptOpenStream(bytes.NewReader(b), true, nil); err == ksaltpack.ErrNoDecryptionKey { + return SigncryptEncoding, true + } + return UnknownEncoding, false +} + +func detectSign(b []byte) (Encoding, bool) { + kr := &saltpack{} + if _, _, _, err := ksaltpack.NewDearmor62VerifyStream(signVersionValidator, bytes.NewReader(b), kr); err == nil { + return SignEncoding, true + } + if _, _, err := ksaltpack.NewVerifyStream(signVersionValidator, bytes.NewReader(b), kr); err == nil { + return SignEncoding, false + } + return UnknownEncoding, false +} + +func detectSignDetached(b []byte) (Encoding, bool) { + kr := &saltpack{} + var buf bytes.Buffer + if _, _, err := ksaltpack.Dearmor62VerifyDetachedReader(signVersionValidator, &buf, string(b), kr); err == keys.ErrVerifyFailed { + return SignEncoding, true + } + if _, err := ksaltpack.VerifyDetachedReader(signVersionValidator, &buf, b, kr); err == keys.ErrVerifyFailed { + return SignEncoding, false + } + return UnknownEncoding, false +} diff --git a/saltpack/detect_test.go b/saltpack/detect_test.go new file mode 100644 index 0000000..228cb3d --- /dev/null +++ b/saltpack/detect_test.go @@ -0,0 +1,48 @@ +package saltpack + +import ( + "bytes" + "testing" + + "github.com/keys-pub/keys" + "github.com/stretchr/testify/require" +) + +func TestDetectEncrypt(t *testing.T) { + alice := keys.NewEdX25519KeyFromSeed(testSeed(0x01)) + bob := keys.NewEdX25519KeyFromSeed(testSeed(0x02)) + message := []byte("hi bob") + xalice := alice.X25519Key() + xbob := bob.X25519Key() + + encrypted, err := Encrypt(message, false, xalice, xbob.ID()) + require.NoError(t, err) + enc, armored := detectEncrypt(encrypted) + require.Equal(t, EncryptEncoding, enc) + require.False(t, armored) + + out, err := Encrypt(message, true, xalice, xbob.ID()) + require.NoError(t, err) + enc, armored = detectEncrypt([]byte(out)) + require.Equal(t, EncryptEncoding, enc) + require.True(t, armored) + + signcrypted, err := Signcrypt(message, false, alice, bob.ID()) + require.NoError(t, err) + enc, armored = detectEncrypt([]byte(signcrypted)) + require.Equal(t, SigncryptEncoding, enc) + require.True(t, armored) + + out, err = Signcrypt(message, true, alice, bob.ID()) + require.NoError(t, err) + enc, armored = detectEncrypt([]byte(out)) + require.Equal(t, SigncryptEncoding, enc) + require.True(t, armored) + + enc, _ = detectEncrypt([]byte{0x01}) + require.Equal(t, UnknownEncoding, enc) +} + +func testSeed(b byte) *[32]byte { + return keys.Bytes32(bytes.Repeat([]byte{b}, 32)) +} diff --git a/saltpack/encrypt.go b/saltpack/encrypt.go index f001977..0f9181a 100644 --- a/saltpack/encrypt.go +++ b/saltpack/encrypt.go @@ -1,7 +1,9 @@ package saltpack import ( + "bufio" "io" + "reflect" "github.com/keys-pub/keys" "github.com/pkg/errors" @@ -12,7 +14,7 @@ import ( // Encrypt to recipients. // Sender can be nil, if you want it to be anonymous. // https://saltpack.org/encryption-format-v2 -func Encrypt(b []byte, sender *keys.X25519Key, recipients ...keys.ID) ([]byte, error) { +func Encrypt(b []byte, armored bool, sender *keys.X25519Key, recipients ...keys.ID) ([]byte, error) { recs, err := boxPublicKeys(recipients) if err != nil { return nil, err @@ -21,22 +23,14 @@ func Encrypt(b []byte, sender *keys.X25519Key, recipients ...keys.ID) ([]byte, e if sender != nil { sbk = newBoxKey(sender) } - return ksaltpack.Seal(ksaltpack.Version2(), b, sbk, recs) -} - -// EncryptArmored to recipients. -// Sender can be nil, if you want it to be anonymous. -// https://saltpack.org/encryption-format-v2 -func EncryptArmored(b []byte, sender *keys.X25519Key, recipients ...keys.ID) (string, error) { - recs, err := boxPublicKeys(recipients) - if err != nil { - return "", err - } - var sbk ksaltpack.BoxSecretKey - if sender != nil { - sbk = newBoxKey(sender) + if armored { + s, err := ksaltpack.EncryptArmor62Seal(ksaltpack.Version2(), b, sbk, recs, "") + if err != nil { + return nil, err + } + return []byte(s), nil } - return ksaltpack.EncryptArmor62Seal(ksaltpack.Version2(), b, sbk, recs, "") + return ksaltpack.Seal(ksaltpack.Version2(), b, sbk, recs) } func x25519SenderKey(info *ksaltpack.MessageKeyInfo) (*keys.X25519PublicKey, error) { @@ -44,34 +38,42 @@ func x25519SenderKey(info *ksaltpack.MessageKeyInfo) (*keys.X25519PublicKey, err if !info.SenderIsAnon { b := info.SenderKey.ToKID() if len(b) != 32 { - return nil, errors.Errorf("invalid (x25519) sender key") + return nil, errors.Errorf("invalid x25519 sender key") } sender = keys.NewX25519PublicKey(keys.Bytes32(b)) } return sender, nil } -// Decrypt bytes (using the specified keys). -// If there was a sender, will return a X25519 key ID. -func Decrypt(b []byte, ks KeyStore) ([]byte, *keys.X25519PublicKey, error) { - s := newSaltpack(ks) - info, out, err := ksaltpack.Open(encryptVersionValidator, b, s) - if err != nil { - return nil, nil, convertBoxKeyErr(err) - } - sender, err := x25519SenderKey(info) - if err != nil { - return nil, nil, err - } - return out, sender, nil +// Open decrypts bytes after attempting to auto detect the encoding. +func Open(b []byte, kr Keyring) (out []byte, key keys.Key, enc Encoding, err error) { + enc, armored := detectEncrypt(b) + switch enc { + case EncryptEncoding: + out, key, err = Decrypt(b, armored, kr) + case SigncryptEncoding: + out, key, err = SigncryptOpen(b, armored, kr) + default: + err = errors.Errorf("invalid data") + } + if isNil(key) { + key = nil + } + return } -// DecryptArmored text (using the specified keys). -// If there was a sender, will return a X25519 key ID. -func DecryptArmored(str string, ks KeyStore) ([]byte, *keys.X25519PublicKey, error) { - s := newSaltpack(ks) - // TODO: Casting to string could be a performance issue - info, out, _, err := ksaltpack.Dearmor62DecryptOpen(encryptVersionValidator, str, s) +// Decrypt bytes. +// If there was a sender, will return the X25519 public key. +func Decrypt(b []byte, armored bool, kr Keyring) ([]byte, *keys.X25519PublicKey, error) { + s := newSaltpack(kr) + var info *ksaltpack.MessageKeyInfo + var out []byte + var err error + if armored { + info, out, _, err = ksaltpack.Dearmor62DecryptOpen(encryptVersionValidator, string(b), s) + } else { + info, out, err = ksaltpack.Open(encryptVersionValidator, b, s) + } if err != nil { return nil, nil, convertBoxKeyErr(err) } @@ -82,9 +84,9 @@ func DecryptArmored(str string, ks KeyStore) ([]byte, *keys.X25519PublicKey, err return out, sender, nil } -// NewEncryptStream creates an encrypted io.WriteCloser. +// NewEncryptStream creates an encrypted armored io.WriteCloser. // Sender can be nil, if you want it to be anonymous. -func NewEncryptStream(w io.Writer, sender *keys.X25519Key, recipients ...keys.ID) (io.WriteCloser, error) { +func NewEncryptStream(w io.Writer, armored bool, sender *keys.X25519Key, recipients ...keys.ID) (io.WriteCloser, error) { recs, err := boxPublicKeys(recipients) if err != nil { return nil, err @@ -93,28 +95,51 @@ func NewEncryptStream(w io.Writer, sender *keys.X25519Key, recipients ...keys.ID if sender != nil { sbk = newBoxKey(sender) } + if armored { + return ksaltpack.NewEncryptArmor62Stream(ksaltpack.Version2(), w, sbk, recs, "") + } return ksaltpack.NewEncryptStream(ksaltpack.Version2(), w, sbk, recs) } -// NewEncryptArmoredStream creates an encrypted armored io.WriteCloser. -// Sender can be nil, if you want it to be anonymous. -func NewEncryptArmoredStream(w io.Writer, sender *keys.X25519Key, recipients ...keys.ID) (io.WriteCloser, error) { - recs, err := boxPublicKeys(recipients) +// NewReader creates io.Reader for decryption after trying to detect the encoding. +// We peek up to 512 bytes from the reader, detect the encoding and return that stream. +func NewReader(r io.Reader, kr Keyring) (out io.Reader, key keys.Key, enc Encoding, err error) { + buf := bufio.NewReader(r) + var peek []byte + peek, err = buf.Peek(512) if err != nil { - return nil, err + if err != io.EOF { + return + } } - var sbk ksaltpack.BoxSecretKey - if sender != nil { - sbk = newBoxKey(sender) + enc, armored := detectEncrypt(peek) + switch enc { + case EncryptEncoding: + out, key, err = NewDecryptStream(buf, armored, kr) + case SigncryptEncoding: + out, key, err = NewSigncryptOpenStream(buf, armored, kr) + default: + err = errors.Errorf("invalid data") } - return ksaltpack.NewEncryptArmor62Stream(ksaltpack.Version2(), w, sbk, recs, "") + if isNil(key) { + key = nil + } + return } -// NewDecryptStream creates a decryption stream (using the specified keys). +// NewDecryptStream creates a decrypt stream. // If there was a sender, will return a X25519 key ID. -func NewDecryptStream(r io.Reader, ks KeyStore) (io.Reader, *keys.X25519PublicKey, error) { - s := newSaltpack(ks) - info, stream, err := ksaltpack.NewDecryptStream(encryptVersionValidator, r, s) +func NewDecryptStream(r io.Reader, armored bool, kr Keyring) (io.Reader, *keys.X25519PublicKey, error) { + s := newSaltpack(kr) + + var info *ksaltpack.MessageKeyInfo + var stream io.Reader + var err error + if armored { + info, stream, _, err = ksaltpack.NewDearmor62DecryptStream(encryptVersionValidator, r, s) + } else { + info, stream, err = ksaltpack.NewDecryptStream(encryptVersionValidator, r, s) + } if err != nil { return nil, nil, convertBoxKeyErr(err) } @@ -125,18 +150,35 @@ func NewDecryptStream(r io.Reader, ks KeyStore) (io.Reader, *keys.X25519PublicKe return stream, sender, nil } -// NewDecryptArmoredStream creates a decryption stream (using the specified keys). -// If there was a sender, will return a X25519 key ID. -func NewDecryptArmoredStream(r io.Reader, ks KeyStore) (io.Reader, *keys.X25519PublicKey, error) { - s := newSaltpack(ks) - // TODO: Specifying nil for resolver will panic if box keys not found - info, stream, _, err := ksaltpack.NewDearmor62DecryptStream(encryptVersionValidator, r, s) - if err != nil { - return nil, nil, convertBoxKeyErr(err) +// isNil checks if a specified object is nil. +func isNil(object interface{}) bool { + if object == nil { + return true } - sender, err := x25519SenderKey(info) - if err != nil { - return nil, nil, err + + value := reflect.ValueOf(object) + kind := value.Kind() + isNilableKind := containsKind( + []reflect.Kind{ + reflect.Chan, reflect.Func, + reflect.Interface, reflect.Map, + reflect.Ptr, reflect.Slice}, + kind) + + if isNilableKind && value.IsNil() { + return true } - return stream, sender, nil + + return false +} + +// containsKind checks if a specified kind in the slice of kinds. +func containsKind(kinds []reflect.Kind, kind reflect.Kind) bool { + for i := 0; i < len(kinds); i++ { + if kind == kinds[i] { + return true + } + } + + return false } diff --git a/saltpack/encrypt_examples_test.go b/saltpack/encrypt_examples_test.go index 4daa80f..a3f6b88 100644 --- a/saltpack/encrypt_examples_test.go +++ b/saltpack/encrypt_examples_test.go @@ -8,14 +8,14 @@ import ( "github.com/keys-pub/keys/saltpack" ) -func ExampleEncryptArmored() { +func ExampleEncrypt() { alice := keys.GenerateX25519Key() bob := keys.GenerateX25519Key() message := []byte("hi bob") // Encrypt from alice to bob - encrypted, err := saltpack.EncryptArmored(message, alice, bob.ID()) + encrypted, err := saltpack.Encrypt(message, true, alice, bob.ID()) if err != nil { log.Fatal(err) } @@ -23,16 +23,16 @@ func ExampleEncryptArmored() { // Output: 375 } -func ExampleDecryptArmored() { - // Message from ExampleEncryptArmored +func ExampleDecrypt() { + // Message from ExampleEncrypt aliceID := keys.ID("kbx17jhqvdrgdyruyseuaat0rerj7ep4n63n4klufzxtzmk9p3d944gs4fg39g") - encrypted := `BEGIN SALTPACK ENCRYPTED MESSAGE. + encrypted := []byte(`BEGIN SALTPACK ENCRYPTED MESSAGE. kcJn5brvybfNjz6 D5ll2Nk0YiiGFCz I2xgcLXuPzYNBe3 OboFDA8Gh0TxosH yo82IRf2OZzteqO GaPWlm7t0lC0M0U vNnOvsussLf1nMw Oo1Llf9oAbA7qxa GjupUEWnTgyjjUn GKb3WvtjSgRsJS2 Y92GMEx8cHvbGrJ zvLGlbqAhEIDNZ2 SE15aoV6ahVxeVH 1inHyghv3H1oTAC K86mBl4fg9FY1QK 4n0gLOmSHbD8UYH V3HAPS0yaBC4xJB g3y04Xcqiij36Nb WmJgvSFdGugXd7O yfU. END SALTPACK ENCRYPTED MESSAGE. - ` + `) // Bob is keys.ID("kbx18x22l7nemmxcj76f9l3aaflc5487lp5u5q778gpe3t3wzhlqvu8qxa9z07") key := `BEGIN X25519 KEY MESSAGE. @@ -45,7 +45,7 @@ func ExampleDecryptArmored() { } // Bob decrypts - out, sender, err := saltpack.DecryptArmored(encrypted, saltpack.NewKeyStore(bob)) + out, sender, err := saltpack.Decrypt(encrypted, true, saltpack.NewKeyring(bob)) if err != nil { log.Fatal(err) } diff --git a/saltpack/encrypt_test.go b/saltpack/encrypt_test.go index eb983a9..d3649e2 100644 --- a/saltpack/encrypt_test.go +++ b/saltpack/encrypt_test.go @@ -16,19 +16,19 @@ func TestEncrypt(t *testing.T) { message := []byte("hi bob") - encrypted, err := saltpack.Encrypt(message, alice, bob.ID()) + encrypted, err := saltpack.Encrypt(message, false, alice, bob.ID()) require.NoError(t, err) - out, sender, err := saltpack.Decrypt(encrypted, saltpack.NewKeyStore(bob)) + out, sender, err := saltpack.Decrypt(encrypted, false, saltpack.NewKeyring(bob)) require.NoError(t, err) require.Equal(t, message, out) require.NotNil(t, sender) require.Equal(t, alice.PublicKey().ID(), sender.ID()) - encrypted2, err := saltpack.EncryptArmored(message, alice, bob.ID()) + encrypted2, err := saltpack.Encrypt(message, true, alice, bob.ID()) require.NoError(t, err) - out, sender, err = saltpack.DecryptArmored(encrypted2, saltpack.NewKeyStore(bob)) + out, sender, err = saltpack.Decrypt([]byte(encrypted2), true, saltpack.NewKeyring(bob)) require.NoError(t, err) require.Equal(t, message, out) require.NotNil(t, sender) @@ -43,11 +43,11 @@ func TestEncrypt(t *testing.T) { // require.NotNil(t, sender) // require.Equal(t, alice.PublicKey().ID(), sender.ID()) - _, err = saltpack.Encrypt(message, alice, keys.ID("")) + _, err = saltpack.Encrypt(message, false, alice, keys.ID("")) require.EqualError(t, err, "invalid recipient: empty id") // Duplicate recipient - _, err = saltpack.Encrypt(message, alice, bob.ID(), bob.ID()) + _, err = saltpack.Encrypt(message, false, alice, bob.ID(), bob.ID()) require.NoError(t, err) } @@ -55,48 +55,80 @@ func TestEncryptAnon(t *testing.T) { bob := keys.NewX25519KeyFromSeed(testSeed(0x02)) message := []byte("hi bob") // Anon sender - encrypted, err := saltpack.Encrypt(message, nil, bob.ID()) + encrypted, err := saltpack.Encrypt(message, false, nil, bob.ID()) require.NoError(t, err) - out, sender, err := saltpack.Decrypt(encrypted, saltpack.NewKeyStore(bob)) + out, sender, err := saltpack.Decrypt(encrypted, false, saltpack.NewKeyring(bob)) require.NoError(t, err) require.Equal(t, message, out) require.Nil(t, sender) } +func copyBytes(source []byte) []byte { + dest := make([]byte, len(source)) + copy(dest, source) + return dest +} + func TestEncryptStream(t *testing.T) { alice := keys.GenerateX25519Key() bob := keys.GenerateX25519Key() message := []byte("hi bob") var buf bytes.Buffer - encrypted, err := saltpack.NewEncryptStream(&buf, alice, bob.ID()) + stream, err := saltpack.NewEncryptStream(&buf, false, alice, bob.ID()) require.NoError(t, err) - n, err := encrypted.Write(message) + n, err := stream.Write(message) require.NoError(t, err) require.Equal(t, len(message), n) - encrypted.Close() + stream.Close() + encrypted := copyBytes(buf.Bytes()) - stream, sender, err := saltpack.NewDecryptStream(&buf, saltpack.NewKeyStore(bob)) + dstream, sender, err := saltpack.NewDecryptStream(bytes.NewReader(encrypted), false, saltpack.NewKeyring(bob)) require.NoError(t, err) require.NotNil(t, sender) require.Equal(t, alice.PublicKey().ID(), sender.ID()) - out, err := ioutil.ReadAll(stream) + out, err := ioutil.ReadAll(dstream) + require.NoError(t, err) + require.Equal(t, message, out) + + dstream, key, enc, err := saltpack.NewReader(bytes.NewReader(encrypted), saltpack.NewKeyring(bob)) + require.NoError(t, err) + require.NotNil(t, key) + require.Equal(t, saltpack.EncryptEncoding, enc) + require.Equal(t, alice.PublicKey().ID(), key.ID()) + out, err = ioutil.ReadAll(dstream) require.NoError(t, err) require.Equal(t, message, out) +} + +func TestEncryptArmoredStream(t *testing.T) { + alice := keys.GenerateX25519Key() + bob := keys.GenerateX25519Key() + message := []byte("hi bob") - var buf2 bytes.Buffer - encrypted2, err := saltpack.NewEncryptArmoredStream(&buf2, alice, bob.ID()) + var buf bytes.Buffer + stream, err := saltpack.NewEncryptStream(&buf, true, alice, bob.ID()) require.NoError(t, err) - n, err = encrypted2.Write(message) + n, err := stream.Write(message) require.NoError(t, err) require.Equal(t, len(message), n) - encrypted2.Close() + stream.Close() + encrypted := copyBytes(buf.Bytes()) - stream, sender, err = saltpack.NewDecryptArmoredStream(&buf2, saltpack.NewKeyStore(bob)) + dstream, sender, err := saltpack.NewDecryptStream(bytes.NewReader(encrypted), true, saltpack.NewKeyring(bob)) require.NoError(t, err) require.NotNil(t, sender) require.Equal(t, alice.PublicKey().ID(), sender.ID()) - out, err = ioutil.ReadAll(stream) + out, err := ioutil.ReadAll(dstream) + require.NoError(t, err) + require.Equal(t, message, out) + + dstream, key, enc, err := saltpack.NewReader(bytes.NewReader(encrypted), saltpack.NewKeyring(bob)) + require.NoError(t, err) + require.NotNil(t, key) + require.Equal(t, saltpack.EncryptEncoding, enc) + require.Equal(t, alice.PublicKey().ID(), key.ID()) + out, err = ioutil.ReadAll(dstream) require.NoError(t, err) require.Equal(t, message, out) } @@ -107,17 +139,29 @@ func TestEncryptStreamAnon(t *testing.T) { // Anon sender var buf bytes.Buffer - encrypted, err := saltpack.NewEncryptStream(&buf, nil, bob.ID()) + stream, err := saltpack.NewEncryptStream(&buf, false, nil, bob.ID()) require.NoError(t, err) - n, err := encrypted.Write(message) + n, err := stream.Write(message) require.NoError(t, err) require.Equal(t, len(message), n) - encrypted.Close() + stream.Close() + encrypted := copyBytes(buf.Bytes()) - stream, sender, err := saltpack.NewDecryptStream(&buf, saltpack.NewKeyStore(bob)) + dstream, sender, err := saltpack.NewDecryptStream(bytes.NewReader(encrypted), false, saltpack.NewKeyring(bob)) require.NoError(t, err) require.Nil(t, sender) - out, err := ioutil.ReadAll(stream) + out, err := ioutil.ReadAll(dstream) + require.NoError(t, err) + require.Equal(t, message, out) + + dstream, key, enc, err := saltpack.NewReader(bytes.NewReader(encrypted), saltpack.NewKeyring(bob)) + require.NoError(t, err) + require.Nil(t, key) + if key != nil { + t.Fatal("not nil") + } + require.Equal(t, saltpack.EncryptEncoding, enc) + out, err = ioutil.ReadAll(dstream) require.NoError(t, err) require.Equal(t, message, out) } @@ -126,10 +170,10 @@ func TestEncryptOpenError(t *testing.T) { alice := keys.GenerateX25519Key() bob := keys.GenerateX25519Key() - encrypted, err := saltpack.Encrypt([]byte("alice's message"), alice, bob.ID()) + encrypted, err := saltpack.Encrypt([]byte("alice's message"), false, alice, bob.ID()) require.NoError(t, err) - _, _, err = saltpack.Decrypt(encrypted, saltpack.NewKeyStore()) + _, _, err = saltpack.Decrypt(encrypted, false, saltpack.NewKeyring()) require.EqualError(t, err, "no decryption key found for message") } @@ -139,10 +183,10 @@ func TestEncryptWithEdX25519Key(t *testing.T) { message := []byte("hi bob") - encrypted, err := saltpack.Encrypt(message, alice.X25519Key(), bob.ID()) + encrypted, err := saltpack.Encrypt(message, false, alice.X25519Key(), bob.ID()) require.NoError(t, err) - out, sender, err := saltpack.Decrypt(encrypted, saltpack.NewKeyStore(bob)) + out, sender, err := saltpack.Decrypt(encrypted, false, saltpack.NewKeyring(bob)) require.NoError(t, err) require.Equal(t, message, out) require.NotNil(t, sender) diff --git a/saltpack/saltpack.go b/saltpack/saltpack.go index ca93f50..d78cf41 100644 --- a/saltpack/saltpack.go +++ b/saltpack/saltpack.go @@ -10,21 +10,21 @@ import ( "github.com/pkg/errors" ) -// KeyStore for Saltpack keys. -type KeyStore interface { +// Keyring for Saltpack keys. +type Keyring interface { X25519Keys() ([]*keys.X25519Key, error) } type saltpack struct { - ks KeyStore + kr Keyring } func (s *saltpack) X25519Keys() ([]*keys.X25519Key, error) { - return s.ks.X25519Keys() + return s.kr.X25519Keys() } -func newSaltpack(ks KeyStore) *saltpack { - return &saltpack{ks: ks} +func newSaltpack(kr Keyring) *saltpack { + return &saltpack{kr: kr} } type store struct { @@ -35,8 +35,8 @@ func (s *store) X25519Keys() ([]*keys.X25519Key, error) { return s.keys, nil } -// NewKeyStore creates store for keys. -func NewKeyStore(keys ...keys.Key) KeyStore { +// NewKeyring creates keyring for keys. +func NewKeyring(keys ...keys.Key) Keyring { return &store{keys: x25519Keys(keys)} } @@ -76,7 +76,10 @@ func (s *saltpack) CreateEphemeralKey() (ksaltpack.BoxSecretKey, error) { // to one of the given Key IDs. Returns the index and the key on success, // or -1 and nil on failure. func (s *saltpack) LookupBoxSecretKey(kids [][]byte) (int, ksaltpack.BoxSecretKey) { - keys, err := s.ks.X25519Keys() + if s.kr == nil { + return -1, nil + } + keys, err := s.kr.X25519Keys() if err != nil { logger.Warningf("Failed to get x25519 keys: %v", err) return -1, nil @@ -105,7 +108,10 @@ func (s *saltpack) LookupBoxPublicKey(kid []byte) ksaltpack.BoxPublicKey { // receivers via trial and error. func (s *saltpack) GetAllBoxSecretKeys() []ksaltpack.BoxSecretKey { logger.Infof("List x25519 keys...") - keys, err := s.ks.X25519Keys() + if s.kr == nil { + return []ksaltpack.BoxSecretKey{} + } + keys, err := s.kr.X25519Keys() if err != nil { logger.Warningf("Failed to get x25519 keys: %v", err) return []ksaltpack.BoxSecretKey{} diff --git a/saltpack/sign.go b/saltpack/sign.go index 292a997..926c3a5 100644 --- a/saltpack/sign.go +++ b/saltpack/sign.go @@ -1,7 +1,9 @@ package saltpack import ( + "bufio" "io" + "os" "strings" ksaltpack "github.com/keybase/saltpack" @@ -10,43 +12,46 @@ import ( ) // Sign ... -func Sign(b []byte, key *keys.EdX25519Key) ([]byte, error) { +func Sign(b []byte, armored bool, key *keys.EdX25519Key) ([]byte, error) { + if armored { + s, err := ksaltpack.SignArmor62(ksaltpack.Version2(), b, newSignKey(key), "") + if err != nil { + return nil, err + } + return []byte(s), nil + } return ksaltpack.Sign(ksaltpack.Version2(), b, newSignKey(key)) } -// SignArmored ... -func SignArmored(b []byte, key *keys.EdX25519Key) (string, error) { - return ksaltpack.SignArmor62(ksaltpack.Version2(), b, newSignKey(key), "") -} - // SignDetached ... -func SignDetached(b []byte, key *keys.EdX25519Key) ([]byte, error) { +func SignDetached(b []byte, armored bool, key *keys.EdX25519Key) ([]byte, error) { + if armored { + s, err := ksaltpack.SignDetachedArmor62(ksaltpack.Version2(), b, newSignKey(key), "") + if err != nil { + return nil, err + } + return []byte(s), nil + } return ksaltpack.SignDetached(ksaltpack.Version2(), b, newSignKey(key)) } -// SignArmoredDetached ... -func SignArmoredDetached(b []byte, key *keys.EdX25519Key) (string, error) { - return ksaltpack.SignDetachedArmor62(ksaltpack.Version2(), b, newSignKey(key), "") -} - // Verify ... func Verify(b []byte) ([]byte, keys.ID, error) { s := &saltpack{} - spk, out, err := ksaltpack.Verify(signVersionValidator, b, s) - if err != nil { - return nil, "", convertSignKeyErr(err) + var spk ksaltpack.SigningPublicKey + var out []byte + var err error + enc, armored := detectSign(b) + switch enc { + case SignEncoding: + if armored { + spk, out, _, err = ksaltpack.Dearmor62Verify(signVersionValidator, string(b), s) + } else { + spk, out, err = ksaltpack.Verify(signVersionValidator, b, s) + } + default: + return nil, "", errors.Errorf("invalid data") } - signer, err := edX25519KeyID(spk.ToKID()) - if err != nil { - return nil, "", errors.Wrapf(err, "failed to verify") - } - return out, signer, nil -} - -// VerifyArmored ... -func VerifyArmored(msg string) ([]byte, keys.ID, error) { - s := &saltpack{} - spk, out, _, err := ksaltpack.Dearmor62Verify(signVersionValidator, msg, s) if err != nil { return nil, "", convertSignKeyErr(err) } @@ -60,21 +65,19 @@ func VerifyArmored(msg string) ([]byte, keys.ID, error) { // VerifyDetached ... func VerifyDetached(sig []byte, b []byte) (keys.ID, error) { s := &saltpack{} - spk, err := ksaltpack.VerifyDetached(signVersionValidator, b, sig, s) - if err != nil { - return "", convertSignKeyErr(err) + var spk ksaltpack.SigningPublicKey + var err error + enc, armored := detectSignDetached(sig) + switch enc { + case SignEncoding: + if armored { + spk, _, err = ksaltpack.Dearmor62VerifyDetached(signVersionValidator, b, string(sig), s) + } else { + spk, err = ksaltpack.VerifyDetached(signVersionValidator, b, sig, s) + } + default: + return "", errors.Errorf("invalid data") } - signer, err := edX25519KeyID(spk.ToKID()) - if err != nil { - return "", errors.Wrapf(err, "failed to verify") - } - return signer, nil -} - -// VerifyArmoredDetached ... -func VerifyArmoredDetached(sig string, b []byte) (keys.ID, error) { - s := &saltpack{} - spk, _, err := ksaltpack.Dearmor62VerifyDetached(signVersionValidator, b, sig, s) if err != nil { return "", convertSignKeyErr(err) } @@ -86,43 +89,42 @@ func VerifyArmoredDetached(sig string, b []byte) (keys.ID, error) { } // NewSignStream ... -func NewSignStream(w io.Writer, key *keys.EdX25519Key) (io.WriteCloser, error) { +func NewSignStream(w io.Writer, armored bool, detached bool, key *keys.EdX25519Key) (io.WriteCloser, error) { + if armored && detached { + return ksaltpack.NewSignDetachedArmor62Stream(ksaltpack.Version1(), w, newSignKey(key), "") + } + if armored { + return ksaltpack.NewSignArmor62Stream(ksaltpack.Version1(), w, newSignKey(key), "") + } + if detached { + return ksaltpack.NewSignDetachedStream(ksaltpack.Version1(), w, newSignKey(key)) + } return ksaltpack.NewSignStream(ksaltpack.Version1(), w, newSignKey(key)) } -// NewSignArmoredDetachedStream ... -func NewSignArmoredDetachedStream(w io.Writer, key *keys.EdX25519Key) (io.WriteCloser, error) { - return ksaltpack.NewSignDetachedArmor62Stream(ksaltpack.Version1(), w, newSignKey(key), "") -} - -// NewSignArmoredStream ... -func NewSignArmoredStream(w io.Writer, key *keys.EdX25519Key) (io.WriteCloser, error) { - return ksaltpack.NewSignArmor62Stream(ksaltpack.Version1(), w, newSignKey(key), "") -} - -// NewSignDetachedStream ... -func NewSignDetachedStream(w io.Writer, key *keys.EdX25519Key) (io.WriteCloser, error) { - return ksaltpack.NewSignDetachedStream(ksaltpack.Version1(), w, newSignKey(key)) -} - // NewVerifyStream ... func NewVerifyStream(r io.Reader) (io.Reader, keys.ID, error) { s := &saltpack{} - spk, reader, err := ksaltpack.NewVerifyStream(signVersionValidator, r, s) + buf := bufio.NewReader(r) + peek, err := buf.Peek(512) if err != nil { - return nil, "", convertSignKeyErr(err) + if err != io.EOF { + return nil, "", err + } } - signer, err := edX25519KeyID(spk.ToKID()) - if err != nil { - return nil, "", errors.Wrapf(err, "failed to verify") + var spk ksaltpack.SigningPublicKey + var reader io.Reader + enc, armored := detectSign(peek) + switch enc { + case SignEncoding: + if armored { + spk, reader, _, err = ksaltpack.NewDearmor62VerifyStream(signVersionValidator, buf, s) + } else { + spk, reader, err = ksaltpack.NewVerifyStream(signVersionValidator, buf, s) + } + default: + err = errors.Errorf("invalid data") } - return reader, signer, nil -} - -// NewVerifyArmoredStream ... -func NewVerifyArmoredStream(r io.Reader) (io.Reader, keys.ID, error) { - s := &saltpack{} - spk, reader, _, err := ksaltpack.NewDearmor62VerifyStream(signVersionValidator, r, s) if err != nil { return nil, "", convertSignKeyErr(err) } @@ -136,23 +138,18 @@ func NewVerifyArmoredStream(r io.Reader) (io.Reader, keys.ID, error) { // VerifyDetachedReader ... func VerifyDetachedReader(sig []byte, r io.Reader) (keys.ID, error) { s := &saltpack{} - spk, err := ksaltpack.VerifyDetachedReader(signVersionValidator, r, sig, s) - if err != nil { - return "", convertSignKeyErr(err) - } - signer, err := edX25519KeyID(spk.ToKID()) - if err != nil { - return "", errors.Wrapf(err, "failed to verify") - } - return signer, nil -} - -// VerifyArmoredDetachedReader ... -func VerifyArmoredDetachedReader(sig string, r io.Reader) (keys.ID, error) { - s := &saltpack{} - spk, _, err := ksaltpack.Dearmor62VerifyDetachedReader(signVersionValidator, r, sig, s) - if err != nil { - return "", convertSignKeyErr(err) + var spk ksaltpack.SigningPublicKey + var err error + enc, armored := detectSignDetached(sig) + switch enc { + case SignEncoding: + if armored { + spk, _, err = ksaltpack.Dearmor62VerifyDetachedReader(signVersionValidator, r, string(sig), s) + } else { + spk, err = ksaltpack.VerifyDetachedReader(signVersionValidator, r, sig, s) + } + default: + return "", errors.Errorf("invalid data") } signer, err := edX25519KeyID(spk.ToKID()) if err != nil { @@ -169,3 +166,132 @@ func StripBefore(message string) string { } return message[n:] } + +// SignFile signs a file. +func SignFile(in string, out string, key *keys.EdX25519Key, armored bool, detached bool) error { + logger.Infof("Signing %s to %s", in, out) + + if in == "" { + return errors.Errorf("in not specified") + } + if out == "" { + return errors.Errorf("out not specified") + } + + outTmp := out + ".tmp" + outFile, err := os.Create(outTmp) + if err != nil { + return err + } + defer func() { + _ = outFile.Close() + _ = os.Remove(outTmp) + }() + writer := bufio.NewWriter(outFile) + + logger.Debugf("Sign armored=%t detached=%t", armored, detached) + stream, err := NewSignStream(writer, armored, detached, key) + if err != nil { + return err + } + + inFile, err := os.Open(in) // #nosec + if err != nil { + return err + } + defer func() { + _ = inFile.Close() + }() + reader := bufio.NewReader(inFile) + if _, err := reader.WriteTo(stream); err != nil { + return err + } + + if err := stream.Close(); err != nil { + return err + } + if err := writer.Flush(); err != nil { + return err + } + if err := inFile.Close(); err != nil { + return err + } + if err := outFile.Close(); err != nil { + return err + } + + if err := os.Rename(outTmp, out); err != nil { + return err + } + + return nil +} + +// VerifyFile outputs verified file from in path. +func VerifyFile(in string, out string) (keys.ID, error) { + logger.Infof("Verify %s to %s", in, out) + if in == "" { + return "", errors.Errorf("in not specified") + } + if out == "" { + return "", errors.Errorf("out not specified") + } + + inFile, err := os.Open(in) // #nosec + if err != nil { + return "", err + } + defer func() { + _ = inFile.Close() + }() + reader := bufio.NewReader(inFile) + + verifyReader, kid, err := NewVerifyStream(reader) + if err != nil { + return "", errors.Wrapf(err, "failed to verify file") + } + + outTmp := out + ".tmp" + outFile, err := os.Create(outTmp) + if err != nil { + return "", err + } + defer func() { + _ = outFile.Close() + _ = os.Remove(outTmp) + }() + + writer := bufio.NewWriter(outFile) + + if _, err := writer.ReadFrom(verifyReader); err != nil { + return "", err + } + if err := writer.Flush(); err != nil { + return "", err + } + if err := inFile.Close(); err != nil { + return "", err + } + if err := outFile.Close(); err != nil { + return "", err + } + + if err := os.Rename(outTmp, out); err != nil { + return "", err + } + + return kid, nil +} + +// VerifyFileDetached verifies file at path with signature. +func VerifyFileDetached(sig []byte, path string) (keys.ID, error) { + file, err := os.Open(path) // #nosec + if err != nil { + return "", err + } + defer func() { + _ = file.Close() + }() + reader := bufio.NewReader(file) + return VerifyDetachedReader(sig, reader) +} diff --git a/saltpack/sign_examples_test.go b/saltpack/sign_examples_test.go index be4279d..63fb922 100644 --- a/saltpack/sign_examples_test.go +++ b/saltpack/sign_examples_test.go @@ -13,7 +13,7 @@ func ExampleSign() { message := []byte("hi from alice") - sig, err := saltpack.SignArmored(message, alice) + sig, err := saltpack.Sign(message, true, alice) if err != nil { log.Fatal(err) } @@ -26,7 +26,7 @@ func ExampleSignDetached() { message := []byte("hi from alice") - sig, err := saltpack.SignArmoredDetached(message, alice) + sig, err := saltpack.SignDetached(message, true, alice) if err != nil { log.Fatal(err) } @@ -35,15 +35,15 @@ func ExampleSignDetached() { func ExampleVerify() { aliceID := keys.ID("kex1w2jep8dkr2s0g9kx5g6xe3387jslnlj08yactvn8xdtrx4cnypjq9rpnux") - signed := `BEGIN SALTPACK SIGNED MESSAGE. + signed := []byte(`BEGIN SALTPACK SIGNED MESSAGE. kXR7VktZdyH7rvq v5wcIkHbs7mPCSd NhKLR9E0K47y29T QkuYinHym6EfZwL 1TwgxI3RQ52fHg5 1FzmLOMghcYLcV7 i0l0ovabGhxGrEl z7WuI4O3xMU5saq U28RqUnKNroATPO 5rn2YyQcut2SeMn lXJBlDqRv9WyxjG M0PcKvsAsvmid1m cqA4TCjz5V9VpuO zuIQ55lRQLeP5kU aWFxq5Nl8WsPqlR RdX86OuTbaKUvKI wdNd6ISacrT0I82 qZ71sc7sTxiMxoI P43uCGaAZZ3Ab62 vR8N6WQPE8. - END SALTPACK SIGNED MESSAGE.` + END SALTPACK SIGNED MESSAGE.`) - out, signer, err := saltpack.VerifyArmored(signed) + out, signer, err := saltpack.Verify(signed) if err != nil { log.Fatal(err) } diff --git a/saltpack/sign_test.go b/saltpack/sign_test.go index 6aafcd8..2e2842c 100644 --- a/saltpack/sign_test.go +++ b/saltpack/sign_test.go @@ -3,6 +3,8 @@ package saltpack_test import ( "bytes" "io/ioutil" + "os" + "path/filepath" "testing" "github.com/keys-pub/keys" @@ -15,7 +17,7 @@ func TestSignVerify(t *testing.T) { message := []byte("hi") - sig, err := saltpack.Sign(message, alice) + sig, err := saltpack.Sign(message, false, alice) require.NoError(t, err) out, signer, err := saltpack.Verify(sig) @@ -23,7 +25,7 @@ func TestSignVerify(t *testing.T) { require.Equal(t, message, out) require.Equal(t, alice.PublicKey().ID(), signer) - sig, err = saltpack.SignDetached(message, alice) + sig, err = saltpack.SignDetached(message, false, alice) require.NoError(t, err) signer, err = saltpack.VerifyDetached(sig, message) @@ -37,12 +39,20 @@ func TestSignVerifyArmored(t *testing.T) { message := []byte("hi") - sig, err := saltpack.SignArmored(message, alice) + sig, err := saltpack.Sign(message, true, alice) require.NoError(t, err) - messageOut, signer, err := saltpack.VerifyArmored(sig) + out, signer, err := saltpack.Verify(sig) + require.NoError(t, err) + require.Equal(t, message, out) + require.Equal(t, alice.PublicKey().ID(), signer) + + sig, err = saltpack.SignDetached(message, true, alice) require.NoError(t, err) - require.Equal(t, message, messageOut) + + signer, err = saltpack.VerifyDetached(sig, message) + require.NoError(t, err) + require.Equal(t, message, out) require.Equal(t, alice.PublicKey().ID(), signer) } @@ -52,7 +62,7 @@ func TestSignVerifyStream(t *testing.T) { message := []byte("I'm alice") var buf bytes.Buffer - signed, err := saltpack.NewSignStream(&buf, alice) + signed, err := saltpack.NewSignStream(&buf, false, false, alice) require.NoError(t, err) n, err := signed.Write(message) require.NoError(t, err) @@ -68,7 +78,7 @@ func TestSignVerifyStream(t *testing.T) { require.Equal(t, message, out) var buf2 bytes.Buffer - signed2, err := saltpack.NewSignArmoredStream(&buf2, alice) + signed2, err := saltpack.NewSignStream(&buf2, true, false, alice) require.NoError(t, err) n, err = signed2.Write(message) require.NoError(t, err) @@ -76,7 +86,7 @@ func TestSignVerifyStream(t *testing.T) { signed2.Close() reader = bytes.NewReader(buf2.Bytes()) - stream, signer, err = saltpack.NewVerifyArmoredStream(reader) + stream, signer, err = saltpack.NewVerifyStream(reader) require.NoError(t, err) require.Equal(t, alice.PublicKey().ID(), signer) out, err = ioutil.ReadAll(stream) @@ -85,7 +95,7 @@ func TestSignVerifyStream(t *testing.T) { // Sign detached var buf3 bytes.Buffer - signed3, err := saltpack.NewSignDetachedStream(&buf3, alice) + signed3, err := saltpack.NewSignStream(&buf3, false, true, alice) require.NoError(t, err) _, err = signed3.Write(message) require.NoError(t, err) @@ -98,18 +108,91 @@ func TestSignVerifyStream(t *testing.T) { // Sign armored/detached var buf4 bytes.Buffer - signed4, err := saltpack.NewSignArmoredDetachedStream(&buf4, alice) + signed4, err := saltpack.NewSignStream(&buf4, true, true, alice) require.NoError(t, err) _, err = signed4.Write(message) require.NoError(t, err) require.Equal(t, len(message), n) signed4.Close() - signer, err = saltpack.VerifyArmoredDetachedReader(buf4.String(), bytes.NewBuffer(message)) + signer, err = saltpack.VerifyDetachedReader(buf4.Bytes(), bytes.NewBuffer(message)) require.NoError(t, err) require.Equal(t, alice.PublicKey().ID(), signer) } +func TestSignVerifyFile(t *testing.T) { + testSignVerifyFile(t, false) + testSignVerifyFile(t, true) +} + +func testSignVerifyFile(t *testing.T, armored bool) { + var err error + alice := keys.GenerateEdX25519Key() + in := filepath.Join(os.TempDir(), keys.RandFileName()) + data := bytes.Repeat([]byte{0x01}, 16*1024) + err = ioutil.WriteFile(in, data, 0600) + require.NoError(t, err) + out := in + ".signed" + outVerified := in + ".verified" + defer func() { + _ = os.Remove(in) + _ = os.Remove(out) + _ = os.Remove(outVerified) + }() + + err = saltpack.SignFile(in, out, alice, armored, false) + require.NoError(t, err) + + kid, err := saltpack.VerifyFile(out, outVerified) + require.NoError(t, err) + require.Equal(t, alice.ID(), kid) + b, err := ioutil.ReadFile(outVerified) + require.NoError(t, err) + require.Equal(t, data, b) +} + +func TestSignFileErrors(t *testing.T) { + var err error + alice := keys.GenerateEdX25519Key() + err = saltpack.SignFile("notfound", "notfound.sig", alice, true, true) + require.EqualError(t, err, "open notfound: no such file or directory") +} + +func TestVerifyFileErrors(t *testing.T) { + var err error + _, err = saltpack.VerifyFile("notfound.signed", "notfound") + require.EqualError(t, err, "open notfound.signed: no such file or directory") +} + +func TestSignVerifyFileDetached(t *testing.T) { + testSignVerifyFileDetached(t, false) + testSignVerifyFileDetached(t, true) +} + +func testSignVerifyFileDetached(t *testing.T, armored bool) { + var err error + alice := keys.GenerateEdX25519Key() + in := filepath.Join(os.TempDir(), keys.RandFileName()) + data := bytes.Repeat([]byte{0x01}, 16*1024) + err = ioutil.WriteFile(in, data, 0600) + require.NoError(t, err) + out := in + ".sig" + defer func() { + _ = os.Remove(in) + _ = os.Remove(out) + }() + + err = saltpack.SignFile(in, out, alice, armored, true) + require.NoError(t, err) + + sig, err := ioutil.ReadFile(out) + require.NoError(t, err) + + kid, err := saltpack.VerifyFileDetached(sig, in) + require.NoError(t, err) + require.Equal(t, alice.ID(), kid) +} + func TestStripBefore(t *testing.T) { sig := "BEGIN SALTPACK SIGNED MESSAGE. XXXXXXXX END SALTPACK SIGNED MESSAGE." message := saltpack.StripBefore("Some text in the beginning to ignore: " + sig) diff --git a/saltpack/signcrypt.go b/saltpack/signcrypt.go index 3e25f28..11ee995 100644 --- a/saltpack/signcrypt.go +++ b/saltpack/signcrypt.go @@ -11,7 +11,7 @@ import ( // Signcrypt to recipients. // https://saltpack.org/signcryption-format -func Signcrypt(b []byte, sender *keys.EdX25519Key, recipients ...keys.ID) ([]byte, error) { +func Signcrypt(b []byte, armored bool, sender *keys.EdX25519Key, recipients ...keys.ID) ([]byte, error) { recs, err := boxPublicKeys(recipients) if err != nil { return nil, err @@ -20,20 +20,14 @@ func Signcrypt(b []byte, sender *keys.EdX25519Key, recipients ...keys.ID) ([]byt return nil, errors.Errorf("no sender specified") } sk := newSignKey(sender) - return ksaltpack.SigncryptSeal(b, ephemeralKeyCreator{}, sk, recs, nil) -} - -// SigncryptArmored to recipients. -func SigncryptArmored(b []byte, sender *keys.EdX25519Key, recipients ...keys.ID) (string, error) { - recs, err := boxPublicKeys(recipients) - if err != nil { - return "", err + if armored { + s, err := ksaltpack.SigncryptArmor62Seal(b, ephemeralKeyCreator{}, sk, recs, nil, "") + if err != nil { + return nil, err + } + return []byte(s), nil } - if sender == nil { - return "", errors.Errorf("no sender specified") - } - sk := newSignKey(sender) - return ksaltpack.SigncryptArmor62Seal(b, ephemeralKeyCreator{}, sk, recs, nil, "") + return ksaltpack.SigncryptSeal(b, ephemeralKeyCreator{}, sk, recs, nil) } func edx25519SenderKey(senderPub ksaltpack.SigningPublicKey) (*keys.EdX25519PublicKey, error) { @@ -42,31 +36,23 @@ func edx25519SenderKey(senderPub ksaltpack.SigningPublicKey) (*keys.EdX25519Publ } b := senderPub.ToKID() if len(b) != 32 { - return nil, errors.Errorf("invalid (edx25519) sender key") + return nil, errors.Errorf("invalid edx25519 sender key") } return keys.NewEdX25519PublicKey(keys.Bytes32(b)), nil } // SigncryptOpen ... -func SigncryptOpen(b []byte, ks KeyStore) ([]byte, *keys.EdX25519PublicKey, error) { - s := newSaltpack(ks) - spk, out, err := ksaltpack.SigncryptOpen(b, s, nil) - if err != nil { - return nil, nil, convertSignKeyErr(err) +func SigncryptOpen(b []byte, armored bool, kr Keyring) ([]byte, *keys.EdX25519PublicKey, error) { + s := newSaltpack(kr) + var spk ksaltpack.SigningPublicKey + var out []byte + var err error + if armored { + spk, out, _, err = ksaltpack.Dearmor62SigncryptOpen(string(b), s, nil) + } else { + spk, out, err = ksaltpack.SigncryptOpen(b, s, nil) } - sender, err := edx25519SenderKey(spk) - if err != nil { - return nil, nil, errors.Wrapf(err, "failed to signcrypt open") - } - return out, sender, nil -} - -// SigncryptArmoredOpen ... -func SigncryptArmoredOpen(str string, ks KeyStore) ([]byte, *keys.EdX25519PublicKey, error) { - s := newSaltpack(ks) - // TODO: Casting to string could be a performance issue - spk, out, _, err := ksaltpack.Dearmor62SigncryptOpen(str, s, nil) if err != nil { return nil, nil, convertSignKeyErr(err) } @@ -78,42 +64,29 @@ func SigncryptArmoredOpen(str string, ks KeyStore) ([]byte, *keys.EdX25519Public } // NewSigncryptStream creates a signcrypt stream. -func NewSigncryptStream(w io.Writer, sender *keys.EdX25519Key, recipients ...keys.ID) (io.WriteCloser, error) { +func NewSigncryptStream(w io.Writer, armored bool, sender *keys.EdX25519Key, recipients ...keys.ID) (io.WriteCloser, error) { recs, err := boxPublicKeys(recipients) if err != nil { return nil, err } - return ksaltpack.NewSigncryptSealStream(w, ephemeralKeyCreator{}, newSignKey(sender), recs, nil) -} - -// NewSigncryptArmoredStream creates a signcrypt stream. -func NewSigncryptArmoredStream(w io.Writer, sender *keys.EdX25519Key, recipients ...keys.ID) (io.WriteCloser, error) { - recs, err := boxPublicKeys(recipients) - if err != nil { - return nil, err + if armored { + return ksaltpack.NewSigncryptArmor62SealStream(w, ephemeralKeyCreator{}, newSignKey(sender), recs, nil, "") } - return ksaltpack.NewSigncryptArmor62SealStream(w, ephemeralKeyCreator{}, newSignKey(sender), recs, nil, "") + return ksaltpack.NewSigncryptSealStream(w, ephemeralKeyCreator{}, newSignKey(sender), recs, nil) } // NewSigncryptOpenStream creates a signcrypt open stream. -func NewSigncryptOpenStream(r io.Reader, ks KeyStore) (io.Reader, *keys.EdX25519PublicKey, error) { - s := newSaltpack(ks) - spk, stream, err := ksaltpack.NewSigncryptOpenStream(r, s, nil) - if err != nil { - return nil, nil, convertSignKeyErr(err) - } - sender, err := edx25519SenderKey(spk) - if err != nil { - return nil, nil, errors.Wrapf(err, "failed to signcrypt open") - } - return stream, sender, nil -} +func NewSigncryptOpenStream(r io.Reader, armored bool, kr Keyring) (io.Reader, *keys.EdX25519PublicKey, error) { + s := newSaltpack(kr) -// NewSigncryptArmoredOpenStream ... -func NewSigncryptArmoredOpenStream(r io.Reader, ks KeyStore) (io.Reader, *keys.EdX25519PublicKey, error) { - s := newSaltpack(ks) - // TODO: Specifying nil for resolver will panic if box keys not found - spk, stream, _, err := ksaltpack.NewDearmor62SigncryptOpenStream(r, s, nil) + var spk ksaltpack.SigningPublicKey + var stream io.Reader + var err error + if armored { + spk, stream, _, err = ksaltpack.NewDearmor62SigncryptOpenStream(r, s, nil) + } else { + spk, stream, err = ksaltpack.NewSigncryptOpenStream(r, s, nil) + } if err != nil { return nil, nil, convertSignKeyErr(err) } diff --git a/saltpack/signcrypt_test.go b/saltpack/signcrypt_test.go index 16b5051..4f051a0 100644 --- a/saltpack/signcrypt_test.go +++ b/saltpack/signcrypt_test.go @@ -16,70 +16,95 @@ func TestSigncrypt(t *testing.T) { message := []byte("hi bob") - encrypted, err := saltpack.Signcrypt(message, alice, bob.ID()) + encrypted, err := saltpack.Signcrypt(message, false, alice, bob.ID()) require.NoError(t, err) - out, sender, err := saltpack.SigncryptOpen(encrypted, saltpack.NewKeyStore(bob)) + out, sender, err := saltpack.SigncryptOpen(encrypted, false, saltpack.NewKeyring(bob)) require.NoError(t, err) require.Equal(t, message, out) require.NotNil(t, sender) require.Equal(t, alice.PublicKey().ID(), sender.ID()) - encrypted2, err := saltpack.SigncryptArmored(message, alice, bob.ID()) + encrypted2, err := saltpack.Signcrypt(message, true, alice, bob.ID()) require.NoError(t, err) - out, sender, err = saltpack.SigncryptArmoredOpen(encrypted2, saltpack.NewKeyStore(bob)) + out, sender, err = saltpack.SigncryptOpen(encrypted2, true, saltpack.NewKeyring(bob)) require.NoError(t, err) require.Equal(t, message, out) require.NotNil(t, sender) require.Equal(t, alice.PublicKey().ID(), sender.ID()) - _, err = saltpack.Signcrypt(message, alice, keys.ID("")) + _, err = saltpack.Signcrypt(message, false, alice, keys.ID("")) require.EqualError(t, err, "invalid recipient: empty id") - _, err = saltpack.Signcrypt(message, nil, bob.ID()) + _, err = saltpack.Signcrypt(message, false, nil, bob.ID()) require.EqualError(t, err, "no sender specified") // Duplicate recipient - _, err = saltpack.Signcrypt(message, alice, bob.ID(), bob.ID()) + _, err = saltpack.Signcrypt(message, false, alice, bob.ID(), bob.ID()) require.NoError(t, err) } func TestSigncryptStream(t *testing.T) { alice := keys.GenerateEdX25519Key() bob := keys.GenerateEdX25519Key() - message := []byte("hi bob") var buf bytes.Buffer - encrypted, err := saltpack.NewSigncryptStream(&buf, alice, bob.ID()) + stream, err := saltpack.NewSigncryptStream(&buf, false, alice, bob.ID()) require.NoError(t, err) - n, err := encrypted.Write(message) + n, err := stream.Write(message) require.NoError(t, err) require.Equal(t, len(message), n) - encrypted.Close() + stream.Close() + encrypted := copyBytes(buf.Bytes()) - stream, sender, err := saltpack.NewSigncryptOpenStream(&buf, saltpack.NewKeyStore(bob)) + dstream, sender, err := saltpack.NewSigncryptOpenStream(bytes.NewReader(encrypted), false, saltpack.NewKeyring(bob)) require.NoError(t, err) require.NotNil(t, sender) require.Equal(t, alice.PublicKey().ID(), sender.ID()) - out, err := ioutil.ReadAll(stream) + out, err := ioutil.ReadAll(dstream) require.NoError(t, err) require.Equal(t, message, out) - var buf2 bytes.Buffer - encrypted2, err := saltpack.NewSigncryptArmoredStream(&buf2, alice, bob.ID()) + dstream, key, enc, err := saltpack.NewReader(bytes.NewReader(encrypted), saltpack.NewKeyring(bob)) require.NoError(t, err) - n, err = encrypted2.Write(message) + require.NotNil(t, key) + require.Equal(t, saltpack.SigncryptEncoding, enc) + require.Equal(t, alice.PublicKey().ID(), key.ID()) + out, err = ioutil.ReadAll(dstream) + require.NoError(t, err) + require.Equal(t, message, out) +} + +func TestSigncryptArmoredStream(t *testing.T) { + alice := keys.GenerateEdX25519Key() + bob := keys.GenerateEdX25519Key() + message := []byte("hi bob") + + var buf bytes.Buffer + stream, err := saltpack.NewSigncryptStream(&buf, true, alice, bob.ID()) + require.NoError(t, err) + n, err := stream.Write(message) require.NoError(t, err) require.Equal(t, len(message), n) - encrypted2.Close() + stream.Close() + encrypted := copyBytes(buf.Bytes()) - stream, sender, err = saltpack.NewSigncryptArmoredOpenStream(&buf2, saltpack.NewKeyStore(bob)) + dstream, sender, err := saltpack.NewSigncryptOpenStream(bytes.NewReader(encrypted), true, saltpack.NewKeyring(bob)) require.NoError(t, err) require.NotNil(t, sender) require.Equal(t, alice.PublicKey().ID(), sender.ID()) - out, err = ioutil.ReadAll(stream) + out, err := ioutil.ReadAll(dstream) + require.NoError(t, err) + require.Equal(t, message, out) + + dstream, key, enc, err := saltpack.NewReader(bytes.NewReader(encrypted), saltpack.NewKeyring(bob)) + require.NoError(t, err) + require.NotNil(t, key) + require.Equal(t, saltpack.SigncryptEncoding, enc) + require.Equal(t, alice.PublicKey().ID(), key.ID()) + out, err = ioutil.ReadAll(dstream) require.NoError(t, err) require.Equal(t, message, out) } @@ -88,9 +113,9 @@ func TestSigncryptOpenError(t *testing.T) { alice := keys.GenerateEdX25519Key() bob := keys.GenerateEdX25519Key() - encrypted, err := saltpack.Signcrypt([]byte("alice's message"), alice, bob.ID()) + encrypted, err := saltpack.Signcrypt([]byte("alice's message"), false, alice, bob.ID()) require.NoError(t, err) - _, _, err = saltpack.SigncryptOpen(encrypted, saltpack.NewKeyStore()) + _, _, err = saltpack.SigncryptOpen(encrypted, false, saltpack.NewKeyring()) require.EqualError(t, err, "no decryption key found for message") } From 4ddc8c1321d97bed13492a2a6b1ab570c846443b Mon Sep 17 00:00:00 2001 From: Gabriel Handford Date: Sun, 26 Jul 2020 17:29:57 -0700 Subject: [PATCH 2/4] Update detect_test.go --- saltpack/detect_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saltpack/detect_test.go b/saltpack/detect_test.go index 228cb3d..e545464 100644 --- a/saltpack/detect_test.go +++ b/saltpack/detect_test.go @@ -31,7 +31,7 @@ func TestDetectEncrypt(t *testing.T) { require.NoError(t, err) enc, armored = detectEncrypt([]byte(signcrypted)) require.Equal(t, SigncryptEncoding, enc) - require.True(t, armored) + require.False(t, armored) out, err = Signcrypt(message, true, alice, bob.ID()) require.NoError(t, err) From 7a5a5080996cdee204a5b4405ac95a29a2ccb83f Mon Sep 17 00:00:00 2001 From: Gabriel Handford Date: Sun, 26 Jul 2020 17:34:32 -0700 Subject: [PATCH 3/4] Update sign.go --- saltpack/sign.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/saltpack/sign.go b/saltpack/sign.go index 926c3a5..8a0ee64 100644 --- a/saltpack/sign.go +++ b/saltpack/sign.go @@ -151,6 +151,9 @@ func VerifyDetachedReader(sig []byte, r io.Reader) (keys.ID, error) { default: return "", errors.Errorf("invalid data") } + if err != nil { + return "", convertSignKeyErr(err) + } signer, err := edX25519KeyID(spk.ToKID()) if err != nil { return "", errors.Wrapf(err, "failed to verify") From 259df6599ebc15983d306c5390b4be365f7f0d0f Mon Sep 17 00:00:00 2001 From: Gabriel Handford Date: Sun, 26 Jul 2020 17:41:41 -0700 Subject: [PATCH 4/4] Update sign_test.go --- saltpack/sign_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/saltpack/sign_test.go b/saltpack/sign_test.go index 2e2842c..ee7a0a0 100644 --- a/saltpack/sign_test.go +++ b/saltpack/sign_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "testing" "github.com/keys-pub/keys" @@ -152,6 +153,9 @@ func testSignVerifyFile(t *testing.T, armored bool) { } func TestSignFileErrors(t *testing.T) { + if runtime.GOOS != "darwin" { + t.Skip() + } var err error alice := keys.GenerateEdX25519Key() err = saltpack.SignFile("notfound", "notfound.sig", alice, true, true) @@ -159,6 +163,9 @@ func TestSignFileErrors(t *testing.T) { } func TestVerifyFileErrors(t *testing.T) { + if runtime.GOOS != "darwin" { + t.Skip() + } var err error _, err = saltpack.VerifyFile("notfound.signed", "notfound") require.EqualError(t, err, "open notfound.signed: no such file or directory")