porting crypto functions

This commit is contained in:
Kristin Laemmert 2018-05-23 09:16:42 -07:00 committed by Martin Atkins
parent 1a5299efcb
commit 9aa9b18658
5 changed files with 234 additions and 5 deletions

View File

@ -1,9 +1,16 @@
package funcs
import (
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"fmt"
uuid "github.com/hashicorp/go-uuid"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/gocty"
"golang.org/x/crypto/bcrypt"
)
var UUIDFunc = function.New(&function.Spec{
@ -18,6 +25,82 @@ var UUIDFunc = function.New(&function.Spec{
},
})
// Base64Sha256Func constructs a function that computes the SHA256 hash of a given string and encodes it with
// Base64.
var Base64Sha256Func = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
s := args[0].AsString()
h := sha256.New()
h.Write([]byte(s))
shaSum := h.Sum(nil)
return cty.StringVal(base64.StdEncoding.EncodeToString(shaSum[:])), nil
},
})
// Base64Sha512Func constructs a function that computes the SHA256 hash of a given string and encodes it with
// Base64.
var Base64Sha512Func = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
s := args[0].AsString()
h := sha512.New()
h.Write([]byte(s))
shaSum := h.Sum(nil)
return cty.StringVal(base64.StdEncoding.EncodeToString(shaSum[:])), nil
},
})
// BcryptFunc constructs a function that computes a hash of the given string using the Blowfish cipher.
var BcryptFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
VarParam: &function.Parameter{
Name: "cost",
Type: cty.Number,
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
defaultCost := 10
if len(args) > 1 {
var val int
if err := gocty.FromCtyValue(args[1], &val); err != nil {
return cty.UnknownVal(cty.String), err
}
defaultCost = val
}
if len(args) > 2 {
return cty.UnknownVal(cty.String), fmt.Errorf("bcrypt() takes no more than two arguments")
}
input := args[0].AsString()
out, err := bcrypt.GenerateFromPassword([]byte(input), defaultCost)
if err != nil {
return cty.UnknownVal(cty.String), fmt.Errorf("error occured generating password %s", err.Error())
}
return cty.StringVal(string(out)), nil
},
})
// UUID generates and returns a Type-4 UUID in the standard hexadecimal string
// format.
//
@ -27,3 +110,35 @@ var UUIDFunc = function.New(&function.Spec{
func UUID() (cty.Value, error) {
return UUIDFunc.Call(nil)
}
// Base64sha256 computes the SHA256 hash of a given string and encodes it with
// Base64.
//
// The given string is first encoded as UTF-8 and then the SHA256 algorithm is applied
// as defined in [RFC 4634](https://tools.ietf.org/html/rfc4634). The raw hash is
// then encoded with Base64 before returning. Terraform uses the "standard" Base64
// alphabet as defined in [RFC 4648 section 4](https://tools.ietf.org/html/rfc4648#section-4).
func Base64Sha256(str cty.Value) (cty.Value, error) {
return Base64Sha256Func.Call([]cty.Value{str})
}
// Base64sha512 computes the SHA512 hash of a given string and encodes it with
// Base64.
//
// The given string is first encoded as UTF-8 and then the SHA256 algorithm is applied
// as defined in [RFC 4634](https://tools.ietf.org/html/rfc4634). The raw hash is
// then encoded with Base64 before returning. Terraform uses the "standard" Base64
// alphabet as defined in [RFC 4648 section 4](https://tools.ietf.org/html/rfc4648#section-4).
func Base64Sha512(str cty.Value) (cty.Value, error) {
return Base64Sha512Func.Call([]cty.Value{str})
}
// Bcrypt computes a hash of the given string using the Blowfish cipher,
// returning a string in the Modular Crypt Format(https://passlib.readthedocs.io/en/stable/modular_crypt_format.html)
// usually expected in the shadow password file on many Unix systems.
func Bcrypt(str cty.Value, cost ...cty.Value) (cty.Value, error) {
args := make([]cty.Value, len(cost)+1)
args[0] = str
copy(args[1:], cost)
return BcryptFunc.Call(args)
}

View File

@ -1,7 +1,11 @@
package funcs
import (
"fmt"
"testing"
"github.com/zclconf/go-cty/cty"
"golang.org/x/crypto/bcrypt"
)
func TestUUID(t *testing.T) {
@ -15,3 +19,109 @@ func TestUUID(t *testing.T) {
t.Errorf("wrong result length %d; want %d", got, want)
}
}
func TestBase64Sha256(t *testing.T) {
tests := []struct {
String cty.Value
Want cty.Value
Err bool
}{
{
cty.StringVal("test"),
cty.StringVal("n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="),
false,
},
// This would differ because we're base64-encoding hex represantiation, not raw bytes.
// base64encode(sha256("test")) =
// "OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWEwYzU1YWQwMTVhM2JmNGYxYjJiMGI4MjJjZDE1ZDZjMTViMGYwMGEwOA=="
}
for _, test := range tests {
t.Run(fmt.Sprintf("base64sha256(%#v)", test.String), func(t *testing.T) {
got, err := Base64Sha256(test.String)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else {
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
func TestBase64Sha512(t *testing.T) {
tests := []struct {
String cty.Value
Want cty.Value
Err bool
}{
{
cty.StringVal("test"),
cty.StringVal("7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w=="),
false,
},
// This would differ because we're base64-encoding hex represantiation, not raw bytes
// base64encode(sha512("test")) =
// "OZWUyNmIwZGQ0YWY3ZTc0OWFhMWE4ZWUzYzEwYWU5OTIzZjYxODk4MDc3MmU0NzNmODgxOWE1ZDQ5NDBlMGRiMjdhYzE4NWY4YTBlMWQ1Zjg0Zjg4YmM4ODdmZDY3YjE0MzczMmMzMDRjYzVmYTlhZDhlNmY1N2Y1MDAyOGE4ZmY="
}
for _, test := range tests {
t.Run(fmt.Sprintf("base64sha512(%#v)", test.String), func(t *testing.T) {
got, err := Base64Sha512(test.String)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else {
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
func TestBcrypt(t *testing.T) {
// single variable test
p, err := Bcrypt(cty.StringVal("test"))
if err != nil {
t.Fatalf("err: %s", err)
}
err = bcrypt.CompareHashAndPassword([]byte(p.AsString()), []byte("test"))
if err != nil {
t.Fatalf("Error comparing hash and password: %s", err)
}
// testing with two parameters
p, err = Bcrypt(cty.StringVal("test"), cty.NumberIntVal(5))
if err != nil {
t.Fatalf("err: %s", err)
}
err = bcrypt.CompareHashAndPassword([]byte(p.AsString()), []byte("test"))
if err != nil {
t.Fatalf("Error comparing hash and password: %s", err)
}
// Negative test for more than two parameters
_, err = Bcrypt(cty.StringVal("test"), cty.NumberIntVal(10), cty.NumberIntVal(11))
if err == nil {
t.Fatal("succeeded; want error")
}
}

View File

@ -33,9 +33,9 @@ func (s *Scope) Functions() map[string]function.Function {
"base64decode": funcs.Base64DecodeFunc,
"base64encode": funcs.Base64EncodeFunc,
"base64gzip": funcs.Base64GzipFunc,
"base64sha256": unimplFunc, // TODO
"base64sha512": unimplFunc, // TODO
"bcrypt": unimplFunc, // TODO
"base64sha256": funcs.Base64Sha256Func,
"base64sha512": funcs.Base64Sha512Func,
"bcrypt": funcs.BcryptFunc,
"ceil": unimplFunc, // TODO
"chomp": unimplFunc, // TODO
"cidrhost": unimplFunc, // TODO
@ -63,6 +63,7 @@ func (s *Scope) Functions() map[string]function.Function {
"join": funcs.JoinFunc,
"jsondecode": stdlib.JSONDecodeFunc,
"jsonencode": stdlib.JSONEncodeFunc,
"keys": unimplFunc, // TODO
"length": funcs.LengthFunc,
"list": unimplFunc, // TODO
"log": unimplFunc, // TODO
@ -93,6 +94,7 @@ func (s *Scope) Functions() map[string]function.Function {
"upper": stdlib.UpperFunc,
"urlencode": funcs.UrlEncodeFunc,
"uuid": funcs.UUIDFunc,
"values": unimplFunc, // TODO
"zipmap": unimplFunc, // TODO
}

View File

@ -10,7 +10,8 @@ description: |-
# `base64sha256` Function
`base64sha256` computes the SHA256 hash of a given string and encodes it with
Base64.
Base64. This is not equivalent to base64encode(sha256512("test")) since sha512()
returns hexadecimal representation.
The given string is first encoded as UTF-8 and then the SHA256 algorithm is applied
as defined in [RFC 4634](https://tools.ietf.org/html/rfc4634). The raw hash is

View File

@ -10,7 +10,8 @@ description: |-
# `base64sha512` Function
`base64sha512` computes the SHA512 hash of a given string and encodes it with
Base64.
Base64. This is not equivalent to base64encode(sha512("test")) since sha512()
returns hexadecimal representation.
The given string is first encoded as UTF-8 and then the SHA512 algorithm is applied
as defined in [RFC 4634](https://tools.ietf.org/html/rfc4634). The raw hash is