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

Implement JWS-compatible signature marshaling #6077

Merged
merged 2 commits into from
Jan 23, 2019
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
5 changes: 1 addition & 4 deletions builtin/logical/transit/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,9 @@ func testTransit_RSA(t *testing.T, keyType string) {
"hash_algorithm": "invalid",
}
resp, err = b.HandleRequest(context.Background(), signReq)
if err != nil {
if err == nil {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatal("expected an error response")
}

signReq.Data = map[string]interface{}{
"input": plaintext,
Expand Down
133 changes: 71 additions & 62 deletions builtin/logical/transit/path_sign_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ package transit

import (
"context"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"fmt"
"hash"

"github.com/hashicorp/vault/helper/errutil"
"github.com/hashicorp/vault/helper/keysutil"
Expand All @@ -18,23 +15,23 @@ func (b *backend) pathSign() *framework.Path {
return &framework.Path{
Pattern: "sign/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("urlalgorithm"),
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "The key to use",
},

"input": &framework.FieldSchema{
"input": {
Type: framework.TypeString,
Description: "The base64-encoded input data",
},

"context": &framework.FieldSchema{
"context": {
Type: framework.TypeString,
Description: `Base64 encoded context for key derivation. Required if key
derivation is enabled; currently only available with ed25519 keys.`,
},

"hash_algorithm": &framework.FieldSchema{
"hash_algorithm": {
Type: framework.TypeString,
Default: "sha2-256",
Description: `Hash algorithm to use (POST body parameter). Valid values are:
Expand All @@ -48,33 +45,40 @@ Defaults to "sha2-256". Not valid for all key types,
including ed25519.`,
},

"algorithm": &framework.FieldSchema{
"algorithm": {
Type: framework.TypeString,
Default: "sha2-256",
Description: `Deprecated: use "hash_algorithm" instead.`,
},

"urlalgorithm": &framework.FieldSchema{
"urlalgorithm": {
Type: framework.TypeString,
Description: `Hash algorithm to use (POST URL parameter)`,
},

"key_version": &framework.FieldSchema{
"key_version": {
Type: framework.TypeInt,
Description: `The version of the key to use for signing.
Must be 0 (for latest) or a value greater than or equal
to the min_encryption_version configured on the key.`,
},

"prehashed": &framework.FieldSchema{
"prehashed": {
Type: framework.TypeBool,
Description: `Set to 'true' when the input is already hashed. If the key type is 'rsa-2048' or 'rsa-4096', then the algorithm used to hash the input should be indicated by the 'algorithm' parameter.`,
},
"signature_algorithm": &framework.FieldSchema{

"signature_algorithm": {
Type: framework.TypeString,
Description: `The signature algorithm to use for signing. Currently only applies to RSA key types.
Options are 'pss' or 'pkcs1v15'. Defaults to 'pss'`,
},

"marshaling_algorithm": {
Type: framework.TypeString,
Default: "asn1",
Description: `The method by which to marshal the signature. The default is 'asn1' which is used by openssl and X.509. It can also be set to 'jws' which is used for JWT signatures; setting it to this will also cause the encoding of the signature to be url-safe base64 instead of using standard base64 encoding. Currently only valid for ECDSA P-256 key types".`,
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
Expand All @@ -95,33 +99,33 @@ func (b *backend) pathVerify() *framework.Path {
Description: "The key to use",
},

"context": &framework.FieldSchema{
"context": {
Type: framework.TypeString,
Description: `Base64 encoded context for key derivation. Required if key
derivation is enabled; currently only available with ed25519 keys.`,
},

"signature": &framework.FieldSchema{
"signature": {
Type: framework.TypeString,
Description: "The signature, including vault header/key version",
},

"hmac": &framework.FieldSchema{
"hmac": {
Type: framework.TypeString,
Description: "The HMAC, including vault header/key version",
},

"input": &framework.FieldSchema{
"input": {
Type: framework.TypeString,
Description: "The base64-encoded input data to verify",
},

"urlalgorithm": &framework.FieldSchema{
"urlalgorithm": {
Type: framework.TypeString,
Description: `Hash algorithm to use (POST URL parameter)`,
},

"hash_algorithm": &framework.FieldSchema{
"hash_algorithm": {
Type: framework.TypeString,
Default: "sha2-256",
Description: `Hash algorithm to use (POST body parameter). Valid values are:
Expand All @@ -133,21 +137,29 @@ derivation is enabled; currently only available with ed25519 keys.`,

Defaults to "sha2-256". Not valid for all key types.`,
},
"algorithm": &framework.FieldSchema{

"algorithm": {
Type: framework.TypeString,
Default: "sha2-256",
Description: `Deprecated: use "hash_algorithm" instead.`,
},

"prehashed": &framework.FieldSchema{
"prehashed": {
Type: framework.TypeBool,
Description: `Set to 'true' when the input is already hashed. If the key type is 'rsa-2048' or 'rsa-4096', then the algorithm used to hash the input should be indicated by the 'algorithm' parameter.`,
},
"signature_algorithm": &framework.FieldSchema{

"signature_algorithm": {
Type: framework.TypeString,
Description: `The signature algorithm to use for signature verification. Currently only applies to RSA key types.
Options are 'pss' or 'pkcs1v15'. Defaults to 'pss'`,
},

"marshaling_algorithm": {
Type: framework.TypeString,
Default: "asn1",
Description: `The method by which to unmarshal the signature when verifying. The default is 'asn1' which is used by openssl and X.509; can also be set to 'jws' which is used for JWT signatures in which case the signature is also expected to be url-safe base64 encoding instead of standard base64 encoding. Currently only valid for ECDSA P-256 key types".`,
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
Expand All @@ -163,13 +175,25 @@ func (b *backend) pathSignWrite(ctx context.Context, req *logical.Request, d *fr
name := d.Get("name").(string)
ver := d.Get("key_version").(int)
inputB64 := d.Get("input").(string)
hashAlgorithm := d.Get("urlalgorithm").(string)
if hashAlgorithm == "" {
hashAlgorithm = d.Get("hash_algorithm").(string)
if hashAlgorithm == "" {
hashAlgorithm = d.Get("algorithm").(string)
hashAlgorithmStr := d.Get("urlalgorithm").(string)
if hashAlgorithmStr == "" {
hashAlgorithmStr = d.Get("hash_algorithm").(string)
if hashAlgorithmStr == "" {
hashAlgorithmStr = d.Get("algorithm").(string)
}
}

hashAlgorithm, ok := keysutil.HashTypeMap[hashAlgorithmStr]
if !ok {
return logical.ErrorResponse(fmt.Sprintf("invalid hash algorithm %q", hashAlgorithmStr)), logical.ErrInvalidRequest
}

marshalingStr := d.Get("marshaling_algorithm").(string)
marshaling, ok := keysutil.MarshalingTypeMap[marshalingStr]
if !ok {
return logical.ErrorResponse(fmt.Sprintf("invalid marshaling type %q", marshalingStr)), logical.ErrInvalidRequest
}

prehashed := d.Get("prehashed").(bool)
sigAlgorithm := d.Get("signature_algorithm").(string)

Expand Down Expand Up @@ -209,25 +233,12 @@ func (b *backend) pathSignWrite(ctx context.Context, req *logical.Request, d *fr
}

if p.Type.HashSignatureInput() && !prehashed {
var hf hash.Hash
switch hashAlgorithm {
case "sha2-224":
hf = sha256.New224()
case "sha2-256":
hf = sha256.New()
case "sha2-384":
hf = sha512.New384()
case "sha2-512":
hf = sha512.New()
default:
p.Unlock()
return logical.ErrorResponse(fmt.Sprintf("unsupported hash algorithm %s", hashAlgorithm)), nil
}
hf := keysutil.HashFuncMap[hashAlgorithm]()
hf.Write(input)
input = hf.Sum(nil)
}

sig, err := p.Sign(ver, context, input, hashAlgorithm, sigAlgorithm)
sig, err := p.Sign(ver, context, input, hashAlgorithm, sigAlgorithm, marshaling)
if err != nil {
p.Unlock()
return nil, err
Expand All @@ -253,7 +264,6 @@ func (b *backend) pathSignWrite(ctx context.Context, req *logical.Request, d *fr
}

func (b *backend) pathVerifyWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {

sig := d.Get("signature").(string)
hmac := d.Get("hmac").(string)
switch {
Expand All @@ -269,13 +279,25 @@ func (b *backend) pathVerifyWrite(ctx context.Context, req *logical.Request, d *

name := d.Get("name").(string)
inputB64 := d.Get("input").(string)
hashAlgorithm := d.Get("urlalgorithm").(string)
if hashAlgorithm == "" {
hashAlgorithm = d.Get("hash_algorithm").(string)
if hashAlgorithm == "" {
hashAlgorithm = d.Get("algorithm").(string)
hashAlgorithmStr := d.Get("urlalgorithm").(string)
if hashAlgorithmStr == "" {
hashAlgorithmStr = d.Get("hash_algorithm").(string)
if hashAlgorithmStr == "" {
hashAlgorithmStr = d.Get("algorithm").(string)
}
}

hashAlgorithm, ok := keysutil.HashTypeMap[hashAlgorithmStr]
if !ok {
return logical.ErrorResponse(fmt.Sprintf("invalid hash algorithm %q", hashAlgorithmStr)), logical.ErrInvalidRequest
}

marshalingStr := d.Get("marshaling_algorithm").(string)
marshaling, ok := keysutil.MarshalingTypeMap[marshalingStr]
if !ok {
return logical.ErrorResponse(fmt.Sprintf("invalid marshaling type %q", marshalingStr)), logical.ErrInvalidRequest
}

prehashed := d.Get("prehashed").(bool)
sigAlgorithm := d.Get("signature_algorithm").(string)

Expand Down Expand Up @@ -315,25 +337,12 @@ func (b *backend) pathVerifyWrite(ctx context.Context, req *logical.Request, d *
}

if p.Type.HashSignatureInput() && !prehashed {
var hf hash.Hash
switch hashAlgorithm {
case "sha2-224":
hf = sha256.New224()
case "sha2-256":
hf = sha256.New()
case "sha2-384":
hf = sha512.New384()
case "sha2-512":
hf = sha512.New()
default:
p.Unlock()
return logical.ErrorResponse(fmt.Sprintf("unsupported hash algorithm %s", hashAlgorithm)), nil
}
hf := keysutil.HashFuncMap[hashAlgorithm]()
hf.Write(input)
input = hf.Sum(nil)
}

valid, err := p.VerifySignature(context, input, sig, hashAlgorithm, sigAlgorithm)
valid, err := p.VerifySignature(context, input, hashAlgorithm, sigAlgorithm, marshaling, sig)
if err != nil {
switch err.(type) {
case errutil.UserError:
Expand Down
18 changes: 18 additions & 0 deletions builtin/logical/transit/path_sign_verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func TestTransit_SignVerify_P256(t *testing.T) {
}

signRequest := func(req *logical.Request, errExpected bool, postpath string) string {
t.Helper()
req.Path = "sign/foo" + postpath
resp, err := b.HandleRequest(context.Background(), req)
if err != nil && !errExpected {
Expand All @@ -108,6 +109,7 @@ func TestTransit_SignVerify_P256(t *testing.T) {
}

verifyRequest := func(req *logical.Request, errExpected bool, postpath, sig string) {
t.Helper()
req.Path = "verify/foo" + postpath
req.Data["signature"] = sig
resp, err := b.HandleRequest(context.Background(), req)
Expand Down Expand Up @@ -166,6 +168,22 @@ func TestTransit_SignVerify_P256(t *testing.T) {
verifyRequest(req, false, "", sig)
delete(req.Data, "prehashed")

// Test marshaling selection
// Bad value
req.Data["marshaling_algorithm"] = "asn2"
sig = signRequest(req, true, "")
// Use the default, verify we can't validate with jws
req.Data["marshaling_algorithm"] = "asn1"
sig = signRequest(req, false, "")
req.Data["marshaling_algorithm"] = "jws"
verifyRequest(req, true, "", sig)
// Sign with jws, verify we can validate
sig = signRequest(req, false, "")
verifyRequest(req, false, "", sig)
// If we change marshaling back to asn1 we shouldn't be able to verify
delete(req.Data, "marshaling_algorithm")
verifyRequest(req, true, "", sig)

// Test 512 and save sig for later to ensure we can't validate once min
// decryption version is set
req.Data["hash_algorithm"] = "sha2-512"
Expand Down
46 changes: 46 additions & 0 deletions helper/keysutil/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package keysutil

import (
"crypto/sha256"
"crypto/sha512"
"hash"
)

type HashType uint32

const (
_ = iota
HashTypeSHA2224 HashType = iota
HashTypeSHA2256
HashTypeSHA2384
HashTypeSHA2512
)

type MarshalingType uint32

const (
_ = iota
MarshalingTypeASN1 MarshalingType = iota
MarshalingTypeJWS
)

var (
HashTypeMap = map[string]HashType{
"sha2-224": HashTypeSHA2224,
"sha2-256": HashTypeSHA2256,
"sha2-384": HashTypeSHA2384,
"sha2-512": HashTypeSHA2512,
}

HashFuncMap = map[HashType]func() hash.Hash{
HashTypeSHA2224: sha256.New224,
HashTypeSHA2256: sha256.New,
HashTypeSHA2384: sha512.New384,
HashTypeSHA2512: sha512.New,
}

MarshalingTypeMap = map[string]MarshalingType{
"asn1": MarshalingTypeASN1,
"jws": MarshalingTypeJWS,
}
)
Loading