Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion edx25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
3 changes: 3 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
171 changes: 5 additions & 166 deletions saltpack/README.md
Original file line number Diff line number Diff line change
@@ -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)
61 changes: 61 additions & 0 deletions saltpack/detect.go
Original file line number Diff line number Diff line change
@@ -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
}
48 changes: 48 additions & 0 deletions saltpack/detect_test.go
Original file line number Diff line number Diff line change
@@ -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.False(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))
}
Loading