Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trail of bits 018 #9674

Merged
merged 11 commits into from
Aug 17, 2020
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
1 change: 1 addition & 0 deletions command/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ func (c *AgentCommand) Run(args []string) int {
Client: client,
WrapTTL: sc.WrapTTL,
DHType: sc.DHType,
DeriveKey: sc.DeriveKey,
DHPath: sc.DHPath,
AAD: sc.AAD,
}
Expand Down
14 changes: 9 additions & 5 deletions command/agent/cert_with_name_end_to_end_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import (
hclog "github.com/hashicorp/go-hclog"

"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/logical"
vaultcert "github.com/hashicorp/vault/builtin/credential/cert"
"github.com/hashicorp/vault/command/agent/auth"
agentcert "github.com/hashicorp/vault/command/agent/auth/cert"
"github.com/hashicorp/vault/command/agent/sink"
"github.com/hashicorp/vault/command/agent/sink/file"
"github.com/hashicorp/vault/helper/dhutil"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
)

Expand Down Expand Up @@ -137,6 +137,7 @@ func testCertWithNameEndToEnd(t *testing.T, ahWrapping bool) {
AAD: "foobar",
DHType: "curve25519",
DHPath: dhpath,
DeriveKey: true,
Config: map[string]interface{}{
"path": out,
},
Expand Down Expand Up @@ -186,14 +187,17 @@ func testCertWithNameEndToEnd(t *testing.T, ahWrapping bool) {
continue
}

aesKey, err := dhutil.GenerateSharedKey(pri, resp.Curve25519PublicKey)
shared, err := dhutil.GenerateSharedSecret(pri, resp.Curve25519PublicKey)
if err != nil {
t.Fatal(err)
}
aesKey, err := dhutil.DeriveSharedKey(shared, pub, resp.Curve25519PublicKey)
if err != nil {
t.Fatal(err)
}
if len(aesKey) == 0 {
t.Fatal("got empty aes key")
}

val, err = dhutil.DecryptAES(aesKey, resp.EncryptedPayload, resp.Nonce, []byte("foobar"))
if err != nil {
t.Fatalf("error: %v\nresp: %v", err, string(val))
Expand Down
2 changes: 1 addition & 1 deletion command/agent/cert_with_no_name_end_to_end_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func testCertWithNoNAmeEndToEnd(t *testing.T, ahWrapping bool) {
continue
}

aesKey, err := dhutil.GenerateSharedKey(pri, resp.Curve25519PublicKey)
aesKey, err := dhutil.GenerateSharedSecret(pri, resp.Curve25519PublicKey)
if err != nil {
t.Fatal(err)
}
Expand Down
4 changes: 4 additions & 0 deletions command/agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ type Sink struct {
WrapTTLRaw interface{} `hcl:"wrap_ttl"`
WrapTTL time.Duration `hcl:"-"`
DHType string `hcl:"dh_type"`
DeriveKey bool `hcl:"derive_key"`
DHPath string `hcl:"dh_path"`
AAD string `hcl:"aad"`
AADEnvVar string `hcl:"aad_env_var"`
Expand Down Expand Up @@ -395,6 +396,9 @@ func parseSinks(result *Config, list *ast.ObjectList) error {
if s.AAD != "" {
return multierror.Prefix(errors.New("specifying AAD data without 'dh_type' does not make sense"), fmt.Sprintf("sink.%s", s.Type))
}
if s.DeriveKey {
return multierror.Prefix(errors.New("specifying 'derive_key' data without 'dh_type' does not make sense"), fmt.Sprintf("sink.%s", s.Type))
}
case s.DHPath != "" && s.DHType != "":
default:
return multierror.Prefix(errors.New("'dh_type' and 'dh_path' must be specified together"), fmt.Sprintf("sink.%s", s.Type))
Expand Down
1 change: 1 addition & 0 deletions command/agent/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func TestLoadConfigFile(t *testing.T) {
DHType: "curve25519",
DHPath: "/tmp/file-foo-dhpath2",
AAD: "aad",
DeriveKey: true,
Config: map[string]interface{}{
"path": "/tmp/file-bar",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ auto_auth {
aad_env_var = "TEST_AAD_ENV"
dh_type = "curve25519"
dh_path = "/tmp/file-foo-dhpath2"
derive_key = true
config = {
path = "/tmp/file-bar"
}
Expand Down
1 change: 1 addition & 0 deletions command/agent/config/test-fixtures/config.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ auto_auth {
aad_env_var = "TEST_AAD_ENV"
dh_type = "curve25519"
dh_path = "/tmp/file-foo-dhpath2"
derive_key = true
config = {
path = "/tmp/file-bar"
}
Expand Down
7 changes: 6 additions & 1 deletion command/agent/jwt_end_to_end_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ func testJWTEndToEnd(t *testing.T, ahWrapping bool) {
AAD: "foobar",
DHType: "curve25519",
DHPath: dhpath,
DeriveKey: true,
Config: map[string]interface{}{
"path": out,
},
Expand Down Expand Up @@ -231,7 +232,11 @@ func testJWTEndToEnd(t *testing.T, ahWrapping bool) {
continue
}

aesKey, err := dhutil.GenerateSharedKey(pri, resp.Curve25519PublicKey)
shared, err := dhutil.GenerateSharedSecret(pri, resp.Curve25519PublicKey)
if err != nil {
t.Fatal(err)
}
aesKey, err := dhutil.DeriveSharedKey(shared, pub, resp.Curve25519PublicKey)
if err != nil {
t.Fatal(err)
}
Expand Down
12 changes: 11 additions & 1 deletion command/agent/sink/sink.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type SinkConfig struct {
WrapTTL time.Duration
DHType string
DHPath string
DeriveKey bool
AAD string
cachedRemotePubKey []byte
cachedPubKey []byte
Expand Down Expand Up @@ -205,7 +206,16 @@ func (s *SinkConfig) encryptToken(token string) (string, error) {
resp.Curve25519PublicKey = s.cachedPubKey
}

aesKey, err = dhutil.GenerateSharedKey(s.cachedPriKey, s.cachedRemotePubKey)
secret, err := dhutil.GenerateSharedSecret(s.cachedPriKey, s.cachedRemotePubKey)
if err != nil {
return "", errwrap.Wrapf("error calculating shared key: {{err}}", err)
}
if s.DeriveKey {
aesKey, err = dhutil.DeriveSharedKey(secret, s.cachedPubKey, s.cachedRemotePubKey)
} else {
aesKey = secret
}

if err != nil {
return "", errwrap.Wrapf("error deriving shared key: {{err}}", err)
}
Expand Down
55 changes: 48 additions & 7 deletions helper/dhutil/dhutil.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package dhutil

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"golang.org/x/crypto/hkdf"
"io"

"golang.org/x/crypto/curve25519"
Expand Down Expand Up @@ -34,23 +38,60 @@ func GeneratePublicPrivateKey() ([]byte, []byte, error) {
return public[:], scalar[:], nil
}

// generateSharedKey uses the private key and the other party's public key to
// GenerateSharedSecret uses the private key and the other party's public key to
// generate the shared secret.
func GenerateSharedKey(ourPrivate, theirPublic []byte) ([]byte, error) {
func GenerateSharedSecret(ourPrivate, theirPublic []byte) ([]byte, error) {
if len(ourPrivate) != 32 {
return nil, fmt.Errorf("invalid private key length: %d", len(ourPrivate))
}
if len(theirPublic) != 32 {
return nil, fmt.Errorf("invalid public key length: %d", len(theirPublic))
}

var scalar, pub, secret [32]byte
copy(scalar[:], ourPrivate)
copy(pub[:], theirPublic)
return curve25519.X25519(ourPrivate, theirPublic)
}

// DeriveSharedKey uses HKDF to derive a key from a shared secret and public keys
func DeriveSharedKey(secret, ourPublic, theirPublic []byte) ([]byte, error) {
// Derive the final key from the HKDF of the secret and public keys.

/*
Internally, HKDF hashes the secret and two public keys. If Alice and Bob are doing DH key exchange, Alice calculates:

HKDF(secret, A, B) since ourPublic is A.

Bob calculates HKDF(secret, B, A), since Bob's ours is B. That produces a different value. Now we only care
that both public keys participate in the derivation, so simply sorting them so they are in a consistent
numerical order (either one would do) arrives at an agreed value.
*/

var pub1 []byte
var pub2 []byte
switch bytes.Compare(ourPublic, theirPublic) {
ncabatoff marked this conversation as resolved.
Show resolved Hide resolved
case 0:
return nil, errors.New("same public key supplied for both participants")
case -1:
pub1 = ourPublic
pub2 = theirPublic
case 1:
pub1 = theirPublic
pub2 = ourPublic
}

kio := hkdf.New(sha256.New, secret, pub1, pub2)

curve25519.ScalarMult(&secret, &scalar, &pub)
var key [32]byte
n, err := io.ReadFull(kio, key[:])
if err != nil {
ncabatoff marked this conversation as resolved.
Show resolved Hide resolved
// Don't return the key along with the error to prevent misuse
return nil, err
}
if n != 32 {
return nil, errors.New("short read from hkdf")
}
fmt.Printf("Key: %s\n", hex.EncodeToString(key[:]))

return secret[:], nil
return key[:], nil
}

// Use AES256-GCM to encrypt some plaintext with a provided key. The returned values are
Expand Down
1 change: 1 addition & 0 deletions helper/dhutil/dhutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package dhutil
5 changes: 5 additions & 0 deletions website/pages/docs/agent/autoauth/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ These configuration values are common to all Sinks:
agent should read the client's initial parameters (e.g. curve25519 public
key).

- `derive_key` `(bool: false)` - If specified, the final encryption key is
calculated by using HKDF-SHA256 to derive a key from the calculated shared
secret and the two public keys for enhanced security. This is recommended
if backward compatibility isn't a concern.

- `aad` `(string: optional)` - If specified, additional authenticated data to
use with the AES-GCM encryption of the token. Can be any string, including
serialized data.
Expand Down