lib/crypto.go (403 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 lib
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"hash"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/uuid"
)
// Crypto returns a cel.EnvOption to configure extended functions for
// cryptographic hash functions and encoding.
//
// # Base64
//
// Returns a string of the base64 encoding of a string or bytes:
//
// base64(<bytes>) -> <string>
// base64(<string>) -> <string>
// <bytes>.base64() -> <string>
// <string>.base64() -> <string>
//
// Examples:
//
// "hello world".base64() // return "aGVsbG8gd29ybGQ="
//
// # Base64 Decode
//
// Returns a bytes from the base64 encoding in a string:
//
// base64_decode(<string>) -> <bytes>
// <string>.base64_decode() -> <bytes>
//
// Examples:
//
// "aGVsbG8gd29ybGQ=".base64_decode() // return b"hello world"
//
// # Base64 Raw
//
// Returns a string of the raw unpadded base64 encoding of a string or bytes:
//
// base64_raw(<bytes>) -> <string>
// base64_raw(<string>) -> <string>
// <bytes>.base64_raw() -> <string>
// <string>.base64_raw() -> <string>
//
// Examples:
//
// "hello world".base64_raw() // return "aGVsbG8gd29ybGQ"
//
// # Base64 Raw Decode
//
// Returns a bytes from the raw base64 encoding in a string:
//
// base64_raw_decode(<string>) -> <bytes>
// <string>.base64_raw_decode() -> <bytes>
//
// Examples:
//
// "aGVsbG8gd29ybGQ".base64_raw_decode() // return b"hello world"
//
// # Hex
//
// Returns a string of the hexadecimal representation of a string or bytes:
//
// hex(<bytes>) -> <string>
// hex(<string>) -> <string>
// <bytes>.hex() -> <string>
// <string>.hex() -> <string>
//
// Examples:
//
// "hello world".hex() // return "68656c6c6f20776f726c64"
//
// # Hex Decode
//
// Returns a bytes from the hexadecimal representation in a string:
//
// hex_decode(<string>) -> <bytes>
// <string>.hex_decode() -> <bytes>
//
// Examples:
//
// "68656c6c6f20776f726c64".hex_decode() // return b"hello world"
//
// # MD5
//
// Returns a bytes of the md5 hash of a string or bytes:
//
// md5(<bytes>) -> <bytes>
// md5(<string>) -> <bytes>
// <bytes>.md5() -> <bytes>
// <string>.md5() -> <bytes>
//
// Examples:
//
// "hello world".md5() // return "XrY7u+Ae7tCTyyK7j1rNww=="
// "hello world".md5().hex() // return "5eb63bbbe01eeed093cb22bb8f5acdc3"
//
// # SHA-1
//
// Returns a bytes of the sha-1 hash of a string or bytes:
//
// sha1(<bytes>) -> <bytes>
// sha1(<string>) -> <bytes>
// <bytes>.sha1() -> <bytes>
// <string>.sha1() -> <bytes>
//
// Examples:
//
// "hello world".sha1() // return "Kq5sNclPz7QV2+lfQIuc6R7oRu0="
// "hello world".sha1().hex() // return "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"
//
// # SHA-256
//
// Returns a bytes of the sha-256 cryptographic hash of a string or bytes:
//
// sha256(<bytes>) -> <bytes>
// sha256(<string>) -> <bytes>
// <bytes>.sha256() -> <bytes>
// <string>.sha256() -> <bytes>
//
// Examples:
//
// "hello world".sha1() // return "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
// "hello world".sha1().hex() // return "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
//
// # HMAC
//
// Returns a bytes of the HMAC keyed MAC of a string or bytes using either
// the sha-1 or sha-256 hash function depending on the the second parameter:
//
// hmac(<bytes>, <string>, <bytes>) -> <bytes>
// hmac(<string>, <string>, <bytes>) -> <bytes>
// <bytes>.hmac(<string>, <bytes>) -> <bytes>
// <string>.hmac(<string>, <bytes>) -> <bytes>
//
// Examples:
//
// "hello world".hmac("sha256", b"key") // return "C6BvH5pjAEYeQ0VFNdw8QiPkex01cHPXU26ukOwJW+E="
// "hello world".hmac("sha256", b"key").hex() // return "0ba06f1f9a6300461e43454535dc3c4223e47b1d357073d7536eae90ec095be1"
//
// # UUID
//
// Returns a string of a random (Version 4) UUID based on the the Go crypto/rand
// source:
//
// uuid() -> <string>
//
// Examples:
//
// uuid() // return "582fc58b-f983-4c35-abb1-65c507c1dc0c"
func Crypto() cel.EnvOption {
return cel.Lib(cryptoLib{})
}
type cryptoLib struct{}
func (cryptoLib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
cel.Function("base64",
cel.MemberOverload(
"bytes_base64",
[]*cel.Type{cel.BytesType},
cel.StringType,
cel.UnaryBinding(base64Encode),
),
cel.Overload(
"base64_bytes",
[]*cel.Type{cel.BytesType},
cel.StringType,
cel.UnaryBinding(base64Encode),
),
cel.MemberOverload(
"string_base64",
[]*cel.Type{cel.StringType},
cel.StringType,
cel.UnaryBinding(base64Encode),
),
cel.Overload(
"base64_string",
[]*cel.Type{cel.StringType},
cel.StringType,
cel.UnaryBinding(base64Encode),
),
),
cel.Function("base64_decode",
cel.MemberOverload(
"string_base64_decode",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(base64Decode),
),
cel.Overload(
"base64_decode_string",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(base64Decode),
),
),
cel.Function("base64_raw",
cel.MemberOverload(
"bytes_base64_raw",
[]*cel.Type{cel.BytesType},
cel.StringType,
cel.UnaryBinding(base64RawEncode),
),
cel.Overload(
"base64_raw_bytes",
[]*cel.Type{cel.BytesType},
cel.StringType,
cel.UnaryBinding(base64RawEncode),
),
cel.MemberOverload(
"string_base64_raw",
[]*cel.Type{cel.StringType},
cel.StringType,
cel.UnaryBinding(base64RawEncode),
),
cel.Overload(
"base64_raw_string",
[]*cel.Type{cel.StringType},
cel.StringType,
cel.UnaryBinding(base64RawEncode),
),
),
cel.Function("base64_raw_decode",
cel.MemberOverload(
"string_base64_raw_decode",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(base64RawDecode),
),
cel.Overload(
"base64_raw_decode_string",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(base64RawDecode),
),
),
cel.Function("hex",
cel.MemberOverload(
"bytes_hex",
[]*cel.Type{cel.BytesType},
cel.StringType,
cel.UnaryBinding(hexEncode),
),
cel.Overload(
"hex_bytes",
[]*cel.Type{cel.BytesType},
cel.StringType,
cel.UnaryBinding(hexEncode),
),
cel.MemberOverload(
"string_hex",
[]*cel.Type{cel.StringType},
cel.StringType,
cel.UnaryBinding(hexEncode),
),
cel.Overload(
"hex_string",
[]*cel.Type{cel.StringType},
cel.StringType,
cel.UnaryBinding(hexEncode),
),
),
cel.Function("hex_decode",
cel.MemberOverload(
"string_hex_decode",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(hexDecode),
),
cel.Overload(
"hex_decode_string",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(hexDecode),
),
),
cel.Function("md5",
cel.MemberOverload(
"bytes_md5",
[]*cel.Type{cel.BytesType},
cel.BytesType,
cel.UnaryBinding(md5Hash),
),
cel.Overload(
"md5_bytes",
[]*cel.Type{cel.BytesType},
cel.BytesType,
cel.UnaryBinding(md5Hash),
),
cel.MemberOverload(
"string_md5",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(md5Hash),
),
cel.Overload(
"md5_string",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(md5Hash),
),
),
cel.Function("sha1",
cel.MemberOverload(
"bytes_sha1",
[]*cel.Type{cel.BytesType},
cel.BytesType,
cel.UnaryBinding(sha1Hash),
),
cel.Overload(
"sha1_bytes",
[]*cel.Type{cel.BytesType},
cel.BytesType,
cel.UnaryBinding(sha1Hash),
),
cel.MemberOverload(
"string_sha1",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(sha1Hash),
),
cel.Overload(
"sha1_string",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(sha1Hash),
),
),
cel.Function("sha256",
cel.MemberOverload(
"bytes_sha256",
[]*cel.Type{cel.BytesType},
cel.BytesType,
cel.UnaryBinding(sha256Hash),
),
cel.Overload(
"sha256_bytes",
[]*cel.Type{cel.BytesType},
cel.BytesType,
cel.UnaryBinding(sha256Hash),
),
cel.MemberOverload(
"string_sha256",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(sha256Hash),
),
cel.Overload(
"sha256_string",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(sha256Hash),
),
),
cel.Function("hmac",
cel.MemberOverload(
"bytes_hmac_string_bytes",
[]*cel.Type{cel.BytesType, cel.StringType, cel.BytesType},
cel.BytesType,
cel.FunctionBinding(hmacHash),
),
cel.Overload(
"hmac_bytes_string_bytes",
[]*cel.Type{cel.BytesType, cel.StringType, cel.BytesType},
cel.BytesType,
cel.FunctionBinding(hmacHash),
),
cel.MemberOverload(
"string_hmac_string_bytes",
[]*cel.Type{cel.StringType, cel.StringType, cel.BytesType},
cel.BytesType,
cel.FunctionBinding(hmacHash),
),
cel.Overload(
"hmac_string_string_bytes",
[]*cel.Type{cel.StringType, cel.StringType, cel.BytesType},
cel.BytesType,
cel.FunctionBinding(hmacHash),
),
),
cel.Function("uuid",
cel.Overload(
"uuid_string",
nil,
cel.StringType,
cel.FunctionBinding(uuidString),
),
),
}
}
func (cryptoLib) ProgramOptions() []cel.ProgramOption { return nil }
func base64Encode(val ref.Val) ref.Val {
switch val := val.(type) {
case types.Bytes:
return types.String(base64.StdEncoding.EncodeToString(val))
case types.String:
return types.String(base64.StdEncoding.EncodeToString([]byte(val)))
default:
return types.NewErr("invalid type for base64: %s", val.Type())
}
}
func base64Decode(val ref.Val) ref.Val {
switch val := val.(type) {
case types.String:
b, err := base64.StdEncoding.DecodeString(string(val))
if err != nil {
return types.NewErr("invalid base64 encoding: %w", err)
}
return types.Bytes(b)
default:
return types.NewErr("invalid type for base64_decode: %s", val.Type())
}
}
func base64RawEncode(val ref.Val) ref.Val {
switch val := val.(type) {
case types.Bytes:
return types.String(base64.RawStdEncoding.EncodeToString(val))
case types.String:
return types.String(base64.RawStdEncoding.EncodeToString([]byte(val)))
default:
return types.NewErr("invalid type for base64_raw: %s", val.Type())
}
}
func base64RawDecode(val ref.Val) ref.Val {
switch val := val.(type) {
case types.String:
b, err := base64.RawStdEncoding.DecodeString(string(val))
if err != nil {
return types.NewErr("invalid raw base64 encoding: %w", err)
}
return types.Bytes(b)
default:
return types.NewErr("invalid type for base64_raw_decode: %s", val.Type())
}
}
func hexEncode(val ref.Val) ref.Val {
switch val := val.(type) {
case types.Bytes:
return types.String(hex.EncodeToString(val))
case types.String:
return types.String(hex.EncodeToString([]byte(val)))
default:
return types.NewErr("invalid type for hex: %s", val.Type())
}
}
func hexDecode(val ref.Val) ref.Val {
switch val := val.(type) {
case types.String:
b, err := hex.DecodeString(string(val))
if err != nil {
return types.NewErr("invalid hex encoding: %w", err)
}
return types.Bytes(b)
default:
return types.NewErr("invalid type for hex: %s", val.Type())
}
}
func md5Hash(val ref.Val) ref.Val {
switch val := val.(type) {
case types.Bytes:
h := md5.New()
h.Write(val)
return types.Bytes(h.Sum(nil))
case types.String:
h := md5.New()
h.Write([]byte(val))
return types.Bytes(h.Sum(nil))
default:
return types.NewErr("invalid type for md5: %s", val.Type())
}
}
func sha1Hash(val ref.Val) ref.Val {
switch val := val.(type) {
case types.Bytes:
h := sha1.New()
h.Write(val)
return types.Bytes(h.Sum(nil))
case types.String:
h := sha1.New()
h.Write([]byte(val))
return types.Bytes(h.Sum(nil))
default:
return types.NewErr("invalid type for sha1: %s", val.Type())
}
}
func sha256Hash(val ref.Val) ref.Val {
switch val := val.(type) {
case types.Bytes:
h := sha256.New()
h.Write(val)
return types.Bytes(h.Sum(nil))
case types.String:
h := sha256.New()
h.Write([]byte(val))
return types.Bytes(h.Sum(nil))
default:
return types.NewErr("invalid type for sha256: %s", val.Type())
}
}
func hmacHash(args ...ref.Val) ref.Val {
if len(args) != 3 {
return types.NewErr("no such overload for hmac")
}
var val []byte
switch arg := args[0].(type) {
case types.Bytes:
val = []byte(arg)
case types.String:
val = []byte(arg)
default:
return types.NoSuchOverloadErr()
}
hashName, ok := args[1].(types.String)
if !ok {
return types.ValOrErr(args[1], "no such overload")
}
key, ok := args[2].(types.Bytes)
if !ok {
return types.ValOrErr(args[2], "no such overload")
}
var mac hash.Hash
switch hashName {
case "sha1":
mac = hmac.New(sha1.New, key)
case "sha256":
mac = hmac.New(sha256.New, key)
default:
return types.NewErr("invalid hash for hmac: %s", hashName)
}
mac.Write([]byte(val))
return types.Bytes(mac.Sum(nil))
}
func uuidString(args ...ref.Val) ref.Val {
id, err := uuid.NewRandom()
if err != nil {
return types.NewErr("failed to create uuid: %v", err)
}
return types.String(id.String())
}