pkg/crypto/hmac: add crypto/hmac support

Adds interface for the standard library crypto/hmac package, along with a
way to select from a list of supported hash functions.

Fixes #788

Change-Id: Ifdbe031a6da4a8690448ae66d562079068539449
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8861
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/pkg/crypto/hmac/hmac.go b/pkg/crypto/hmac/hmac.go
new file mode 100644
index 0000000..851d356
--- /dev/null
+++ b/pkg/crypto/hmac/hmac.go
@@ -0,0 +1,71 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package hmac
+
+import (
+	"crypto/hmac"
+	"crypto/md5"
+	"crypto/sha1"
+	"crypto/sha256"
+	"crypto/sha512"
+	"fmt"
+	"hash"
+)
+
+const (
+	MD5        = "MD5"
+	SHA1       = "SHA1"
+	SHA224     = "SHA224"
+	SHA256     = "SHA256"
+	SHA384     = "SHA384"
+	SHA512     = "SHA512"
+	SHA512_224 = "SHA512_224"
+	SHA512_256 = "SHA512_256"
+)
+
+// Sign returns the HMAC signature of the data, using the provided key and hash function.
+//
+// Supported hash functions: "MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SHA512_224",
+// and "SHA512_256".
+func Sign(hashName string, key []byte, data []byte) ([]byte, error) {
+	hash, err := hashFromName(hashName)
+	if err != nil {
+		return nil, err
+	}
+	mac := hmac.New(hash, key)
+	mac.Write(data)
+	return mac.Sum(nil), nil
+}
+
+func hashFromName(hash string) (func() hash.Hash, error) {
+	switch hash {
+	case MD5:
+		return md5.New, nil
+	case SHA1:
+		return sha1.New, nil
+	case SHA224:
+		return sha256.New224, nil
+	case SHA256:
+		return sha256.New, nil
+	case SHA384:
+		return sha512.New384, nil
+	case SHA512:
+		return sha512.New, nil
+	case SHA512_224:
+		return sha512.New512_224, nil
+	case SHA512_256:
+		return sha512.New512_256, nil
+	}
+	return nil, fmt.Errorf("unsupported hash function")
+}
diff --git a/pkg/crypto/hmac/hmac_test.go b/pkg/crypto/hmac/hmac_test.go
new file mode 100644
index 0000000..27326e1
--- /dev/null
+++ b/pkg/crypto/hmac/hmac_test.go
@@ -0,0 +1,24 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package hmac_test
+
+import (
+	"testing"
+
+	"cuelang.org/go/pkg/internal/builtintest"
+)
+
+func TestBuiltin(t *testing.T) {
+	builtintest.Run("hmac", t)
+}
diff --git a/pkg/crypto/hmac/pkg.go b/pkg/crypto/hmac/pkg.go
new file mode 100644
index 0000000..98d72c9
--- /dev/null
+++ b/pkg/crypto/hmac/pkg.go
@@ -0,0 +1,59 @@
+// Code generated by go generate. DO NOT EDIT.
+
+//go:generate rm pkg.go
+//go:generate go run ../../gen/gen.go
+
+package hmac
+
+import (
+	"cuelang.org/go/internal/core/adt"
+	"cuelang.org/go/pkg/internal"
+)
+
+func init() {
+	internal.Register("crypto/hmac", pkg)
+}
+
+var _ = adt.TopKind // in case the adt package isn't used
+
+var pkg = &internal.Package{
+	Native: []*internal.Builtin{{
+		Name:  "MD5",
+		Const: "\"MD5\"",
+	}, {
+		Name:  "SHA1",
+		Const: "\"SHA1\"",
+	}, {
+		Name:  "SHA224",
+		Const: "\"SHA224\"",
+	}, {
+		Name:  "SHA256",
+		Const: "\"SHA256\"",
+	}, {
+		Name:  "SHA384",
+		Const: "\"SHA384\"",
+	}, {
+		Name:  "SHA512",
+		Const: "\"SHA512\"",
+	}, {
+		Name:  "SHA512_224",
+		Const: "\"SHA512_224\"",
+	}, {
+		Name:  "SHA512_256",
+		Const: "\"SHA512_256\"",
+	}, {
+		Name: "Sign",
+		Params: []internal.Param{
+			{Kind: adt.StringKind},
+			{Kind: adt.BytesKind | adt.StringKind},
+			{Kind: adt.BytesKind | adt.StringKind},
+		},
+		Result: adt.BytesKind | adt.StringKind,
+		Func: func(c *internal.CallCtxt) {
+			hashName, key, data := c.String(0), c.Bytes(1), c.Bytes(2)
+			if c.Do() {
+				c.Ret, c.Err = Sign(hashName, key, data)
+			}
+		},
+	}},
+}
diff --git a/pkg/crypto/hmac/testdata/hmac.txtar b/pkg/crypto/hmac/testdata/hmac.txtar
new file mode 100644
index 0000000..cbfa73b
--- /dev/null
+++ b/pkg/crypto/hmac/testdata/hmac.txtar
@@ -0,0 +1,16 @@
+-- in.cue --
+import "crypto/hmac"
+import "encoding/hex"
+
+t1: hex.Encode(hmac.Sign(hmac.SHA1, hex.Decode("303132333435363738393a3b3c3d3e3f40414243"), "Sample #2"))
+t2: hex.Encode(hmac.Sign(hmac.MD5, "Jefe", "what do ya want for nothing?"))
+t3: hex.Encode(hmac.Sign(hmac.SHA256, hex.Decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), "Hi There"))
+t4: hex.Encode(hmac.Sign(hmac.SHA224, hex.Decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b"), "Sample message for keylen<blocklen"))
+t5: hex.Encode(hmac.Sign(hmac.SHA384, hex.Decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"), "Sample message for keylen<blocklen"))
+-- out/hmac --
+t1: "0922d3405faa3d194f82a45830737d5cc6c75d24"
+t2: "750c783e6ab0b503eaa86e310a5db738"
+t3: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
+t4: "e3d249a8cfb67ef8b7a169e9a0a599714a2cecba65999a51beb8fbbe"
+t5: "6eb242bdbb582ca17bebfa481b1e23211464d2b7f8c20b9ff2201637b93646af5ae9ac316e98db45d9cae773675eeed0"
+
diff --git a/pkg/register.go b/pkg/register.go
index b7696b9..69bdb0c 100644
--- a/pkg/register.go
+++ b/pkg/register.go
@@ -15,6 +15,7 @@
 package pkg
 
 import (
+	_ "cuelang.org/go/pkg/crypto/hmac"
 	_ "cuelang.org/go/pkg/crypto/md5"
 	_ "cuelang.org/go/pkg/crypto/sha1"
 	_ "cuelang.org/go/pkg/crypto/sha256"