diff --git a/ast/builtins.go b/ast/builtins.go index 321012d444..c696eb1053 100644 --- a/ast/builtins.go +++ b/ast/builtins.go @@ -214,6 +214,7 @@ var DefaultBuiltins = [...]*Builtin{ CryptoHmacSha1, CryptoHmacSha256, CryptoHmacSha512, + CryptoHmacEqual, // Graphs WalkBuiltin, @@ -2380,6 +2381,18 @@ var CryptoHmacSha512 = &Builtin{ ), } +var CryptoHmacEqual = &Builtin{ + Name: "crypto.hmac.equal", + Description: "Returns a boolean representing the result of comparing two MACs for equality without leaking timing information.", + Decl: types.NewFunction( + types.Args( + types.Named("mac1", types.S).Description("mac1 to compare"), + types.Named("mac2", types.S).Description("mac2 to compare"), + ), + types.Named("result", types.B).Description("`true` if the MACs are equals, `false` otherwise"), + ), +} + /** * Graphs. */ diff --git a/builtin_metadata.json b/builtin_metadata.json index e15d42980d..32d2b9b5a5 100644 --- a/builtin_metadata.json +++ b/builtin_metadata.json @@ -33,6 +33,7 @@ "to_number" ], "crypto": [ + "crypto.hmac.equal", "crypto.hmac.md5", "crypto.hmac.sha1", "crypto.hmac.sha256", @@ -3202,6 +3203,31 @@ }, "wasm": true }, + "crypto.hmac.equal": { + "args": [ + { + "description": "mac1 to compare", + "name": "mac1", + "type": "string" + }, + { + "description": "mac2 to compare", + "name": "mac2", + "type": "string" + } + ], + "available": [ + "edge" + ], + "description": "Returns a boolean representing the result of comparing two MACs for equality without leaking timing information.", + "introduced": "edge", + "result": { + "description": "`true` if the MACs are equals, `false` otherwise", + "name": "result", + "type": "boolean" + }, + "wasm": false + }, "crypto.hmac.md5": { "args": [ { diff --git a/capabilities.json b/capabilities.json index 4fe0c2b7fe..bfa43f4a59 100644 --- a/capabilities.json +++ b/capabilities.json @@ -572,6 +572,23 @@ "type": "function" } }, + { + "name": "crypto.hmac.equal", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, { "name": "crypto.hmac.md5", "decl": { diff --git a/test/cases/testdata/cryptohmacequal/test-cryptohmacequal.yaml b/test/cases/testdata/cryptohmacequal/test-cryptohmacequal.yaml new file mode 100644 index 0000000000..0bb37b016f --- /dev/null +++ b/test/cases/testdata/cryptohmacequal/test-cryptohmacequal.yaml @@ -0,0 +1,66 @@ +cases: + - note: cryptohmacequal/crypto.hmac.equal_md5 + query: data.test.p = x + modules: + - | + package test + + p[res] { + res := crypto.hmac.equal(input.mac1, input.mac2) + } + input: {"mac1": "31b6db9e5eb4addb42f1a6ca07367adc", "mac2": "31b6db9e5eb4addb42f1a6ca07367adc"} + want_result: + - x: + - true + - note: cryptohmacequal/crypto.hmac.equal_sha1 + query: data.test.p = x + modules: + - | + package test + + p[res] { + res := crypto.hmac.equal(input.mac1, input.mac2) + } + input: {"mac1": "85d155c55ed286a300bd1cf124de08d87e914f3a", "mac2": "85d155c55ed286a300bd1cf124de08d87e914f3a"} + want_result: + - x: + - true + - note: cryptohmacequal/crypto.hmac.equal_sha256 + query: data.test.p = x + modules: + - | + package test + + p[res] { + res := crypto.hmac.equal(input.mac1, input.mac2) + } + input: {"mac1": "147933218aaabc0b8b10a2b3a5c34684c8d94341bcf10a4736dc7270f7741851", "mac2": "147933218aaabc0b8b10a2b3a5c34684c8d94341bcf10a4736dc7270f7741851"} + want_result: + - x: + - true + - note: cryptohmacequal/crypto.hmac.equal_sha512 + query: data.test.p = x + modules: + - | + package test + + p[res] { + res := crypto.hmac.equal(input.mac1, input.mac2) + } + input: {"mac1": "24257d7210582a65c731ec55159c8184cc24c02489453e58587f71f44c23a2d61b4b72154a89d17b2d49448a8452ea066f4fc56a2bcead45c088572ffccdb3d8", "mac2": "24257d7210582a65c731ec55159c8184cc24c02489453e58587f71f44c23a2d61b4b72154a89d17b2d49448a8452ea066f4fc56a2bcead45c088572ffccdb3d8"} + want_result: + - x: + - true + - note: cryptohmacequal/crypto.hmac.equal_false + query: data.test.p = x + modules: + - | + package test + + p[res] { + res := crypto.hmac.equal(input.mac1, input.mac2) + } + input: {"mac1": "31b6db9e5eb4addb42f1a6ca07367adc", "mac2": "31b6db9e5eb4addb"} + want_result: + - x: + - false \ No newline at end of file diff --git a/topdown/crypto.go b/topdown/crypto.go index 3181ab89b7..03dd7770be 100644 --- a/topdown/crypto.go +++ b/topdown/crypto.go @@ -249,6 +249,24 @@ func builtinCryptoHmacSha512(_ BuiltinContext, operands []*ast.Term, iter func(* return hmacHelper(operands, iter, sha512.New) } +func builtinCryptoHmacEqual(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + a1 := operands[0].Value + mac1, err := builtins.StringOperand(a1, 1) + if err != nil { + return err + } + + a2 := operands[1].Value + mac2, err := builtins.StringOperand(a2, 2) + if err != nil { + return err + } + + res := hmac.Equal([]byte(mac1), []byte(mac2)) + + return iter(ast.BooleanTerm(res)) +} + func init() { RegisterBuiltinFunc(ast.CryptoX509ParseCertificates.Name, builtinCryptoX509ParseCertificates) RegisterBuiltinFunc(ast.CryptoX509ParseAndVerifyCertificates.Name, builtinCryptoX509ParseAndVerifyCertificates) @@ -261,6 +279,7 @@ func init() { RegisterBuiltinFunc(ast.CryptoHmacSha1.Name, builtinCryptoHmacSha1) RegisterBuiltinFunc(ast.CryptoHmacSha256.Name, builtinCryptoHmacSha256) RegisterBuiltinFunc(ast.CryptoHmacSha512.Name, builtinCryptoHmacSha512) + RegisterBuiltinFunc(ast.CryptoHmacEqual.Name, builtinCryptoHmacEqual) } func verifyX509CertificateChain(certs []*x509.Certificate) ([]*x509.Certificate, error) {