crypto builtins (#57)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-12-06 07:59:04 -08:00
committed by GitHub
parent ab968c2386
commit 70bf371ebf
14 changed files with 342 additions and 36 deletions

148
src/builtins/crypto.rs Normal file
View File

@@ -0,0 +1,148 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_string};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use anyhow::{bail, Result};
use constant_time_eq::constant_time_eq;
use hmac::{Hmac, Mac};
use md5::{Digest, Md5};
use sha1::Sha1;
use sha2::{Sha256, Sha512};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("crypto.hmac.equal", (hmac_equal_fixed_time, 2));
m.insert("crypto.hmac.md5", (hmac_md5, 2));
m.insert("crypto.hmac.sha1", (hmac_sha1, 2));
m.insert("crypto.hmac.sha256", (hmac_sha256, 2));
m.insert("crypto.hmac.sha512", (hmac_sha512, 2));
m.insert("crypto.md5", (crypto_md5, 1));
m.insert("crypto.sha1", (crypto_sha1, 1));
m.insert("crypto.sha256", (crypto_sha256, 1));
}
fn hmac_equal_fixed_time(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "crypto.hmac.equal";
ensure_args_count(span, name, params, args, 2)?;
let hmac1 = ensure_string(name, &params[0], &args[0])?;
let hmac2 = ensure_string(name, &params[1], &args[1])?;
Ok(Value::Bool(constant_time_eq(
hmac1.as_bytes(),
hmac2.as_bytes(),
)))
}
fn hmac_md5(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "crypto.hmac.md5";
ensure_args_count(span, name, params, args, 2)?;
let x = ensure_string(name, &params[0], &args[0])?;
let key = ensure_string(name, &params[1], &args[1])?;
let mut hmac = Hmac::<Md5>::new_from_slice(key.as_bytes())
.or_else(|_| bail!(span.error("failed to create hmac instance")))?;
hmac.update(x.as_bytes());
let result = hmac.finalize();
Ok(Value::String(hex::encode(result.into_bytes()).into()))
}
fn hmac_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "crypto.hmac.sha1";
ensure_args_count(span, name, params, args, 2)?;
let x = ensure_string(name, &params[0], &args[0])?;
let key = ensure_string(name, &params[1], &args[1])?;
let mut hmac = Hmac::<Sha1>::new_from_slice(key.as_bytes())
.or_else(|_| bail!(span.error("failed to create hmac instance")))?;
hmac.update(x.as_bytes());
let result = hmac.finalize();
Ok(Value::String(hex::encode(result.into_bytes()).into()))
}
fn hmac_sha256(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "crypto.hmac.sha256";
ensure_args_count(span, name, params, args, 2)?;
let x = ensure_string(name, &params[0], &args[0])?;
let key = ensure_string(name, &params[1], &args[1])?;
let mut hmac = Hmac::<Sha256>::new_from_slice(key.as_bytes())
.or_else(|_| bail!(span.error("failed to create hmac instance")))?;
hmac.update(x.as_bytes());
let result = hmac.finalize();
Ok(Value::String(hex::encode(result.into_bytes()).into()))
}
fn hmac_sha512(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "crypto.hmac.sha512";
ensure_args_count(span, name, params, args, 2)?;
let x = ensure_string(name, &params[0], &args[0])?;
let key = ensure_string(name, &params[1], &args[1])?;
let mut hmac = Hmac::<Sha512>::new_from_slice(key.as_bytes())
.or_else(|_| bail!(span.error("failed to create hmac instance")))?;
hmac.update(x.as_bytes());
let result = hmac.finalize();
Ok(Value::String(hex::encode(result.into_bytes()).into()))
}
fn crypto_md5(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "crypto.md5";
ensure_args_count(span, name, params, args, 1)?;
let x = ensure_string(name, &params[0], &args[0])?;
let mut h = Md5::new();
h.update(x.as_bytes());
let result = h.finalize();
Ok(Value::String(hex::encode(result).into()))
}
fn crypto_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "crypto.sha1";
ensure_args_count(span, name, params, args, 1)?;
let x = ensure_string(name, &params[0], &args[0])?;
let mut h = Sha1::new();
h.update(x.as_bytes());
let result = h.finalize();
Ok(Value::String(hex::encode(result).into()))
}
fn crypto_sha256(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "crypto.sha256";
ensure_args_count(span, name, params, args, 1)?;
let x = ensure_string(name, &params[0], &args[0])?;
let mut h = Sha256::new();
h.update(x.as_bytes());
let result = h.finalize();
Ok(Value::String(hex::encode(result).into()))
}

View File

@@ -21,6 +21,8 @@ lazy_static! {
m.insert("all", (all, 1));
m.insert("any", (any, 1));
m.insert("set_diff", (set_diff, 2));
#[cfg(feature = "crypto")]
m.insert("re_match", (regex_match, 2));
m
};

View File

@@ -17,9 +17,13 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("json.is_valid", (json_is_valid, 1));
m.insert("json.marshal", (json_marshal, 1));
m.insert("jsonunmarshal", (json_unmarshal, 1));
m.insert("yaml.is_valid", (yaml_is_valid, 1));
m.insert("yaml.marshal", (yaml_marshal, 1));
m.insert("yaml.unmarshal", (yaml_unmarshal, 1));
#[cfg(feature = "yaml")]
{
m.insert("yaml.is_valid", (yaml_is_valid, 1));
m.insert("yaml.marshal", (yaml_marshal, 1));
m.insert("yaml.unmarshal", (yaml_unmarshal, 1));
}
}
fn base64_decode(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
@@ -33,6 +37,7 @@ fn base64_decode(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Va
))
}
#[cfg(feature = "yaml")]
fn yaml_is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "yaml.is_valid";
ensure_args_count(span, name, params, args, 1)?;
@@ -41,6 +46,7 @@ fn yaml_is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Va
Ok(Value::Bool(Value::from_yaml_str(&yaml_str).is_ok()))
}
#[cfg(feature = "yaml")]
fn yaml_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "yaml.marshal";
ensure_args_count(span, name, params, args, 1)?;
@@ -51,6 +57,7 @@ fn yaml_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Val
))
}
#[cfg(feature = "yaml")]
fn yaml_unmarshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "yaml.unmarshal";
ensure_args_count(span, name, params, args, 1)?;

View File

@@ -6,14 +6,20 @@ mod arrays;
mod bitwise;
pub mod comparison;
mod conversions;
mod debugging;
pub mod deprecated;
#[cfg(feature = "crypto")]
mod crypto;
mod debugging;
#[cfg(feature = "deprecated")]
pub mod deprecated;
mod encoding;
#[cfg(feature = "glob")]
mod glob;
pub mod numbers;
mod objects;
#[cfg(feature = "regex")]
mod regex;
#[cfg(feature = "semver")]
mod semver;
pub mod sets;
mod strings;
@@ -34,6 +40,7 @@ use lazy_static::lazy_static;
pub type BuiltinFcn = (fn(&Span, &[Ref<Expr>], &[Value]) -> Result<Value>, u8);
#[cfg(feature = "deprecated")]
pub use deprecated::DEPRECATED;
#[rustfmt::skip]
@@ -48,8 +55,13 @@ lazy_static! {
sets::register(&mut m);
objects::register(&mut m);
strings::register(&mut m);
#[cfg(feature = "regex")]
regex::register(&mut m);
#[cfg(feature = "glob")]
glob::register(&mut m);
bitwise::register(&mut m);
conversions::register(&mut m);
//units::register(&mut m);
@@ -58,12 +70,15 @@ lazy_static! {
//token_signing::register(&mut m);
//token_verification::register(&mut m);
time::register(&mut m);
//cryptography::register(&mut m);
#[cfg(feature = "crypto")]
crypto::register(&mut m);
//graphs::register(&mut m);
//graphql::register(&mut m);
//http::register(&mut m);
//net::register(&mut m);
//uuid::register(&mut m);
#[cfg(feature = "semver")]
semver::register(&mut m);
//rego::register(&mut m);
//opa::register(&mut m);

View File

@@ -100,7 +100,7 @@ impl Interpreter {
module: None,
schedule: None,
current_module_path: String::default(),
input: Value::new_object(),
input: Value::Null,
data: Value::new_object(),
init_data: Value::new_object(),
with_document: Value::new_object(),
@@ -499,9 +499,35 @@ impl Interpreter {
}
return Ok(Value::Bool(true));
}
(Expr::Object { .. }, Expr::Object { .. }) => {
// TODO: destructure
return self.eval_bool_expr(&BoolOp::Eq, lhs, rhs);
(
Expr::Object {
fields: lhs_fields, ..
},
Expr::Object {
fields: rhs_fields,
span: rhs_span,
},
) => {
if lhs_fields.len() != rhs_fields.len() {
bail!(rhs_span.error("mismatch in number of object keysin lhs and rhs"));
}
for ((_, lhs_key, lhs_value), (_, rhs_key, rhs_value)) in
std::iter::zip(lhs_fields.iter(), rhs_fields.iter())
{
if self.eval_bool_expr(&BoolOp::Eq, lhs_key, rhs_key)?
!= Value::Bool(true)
{
return Ok(Value::Bool(false));
}
if self.eval_assign_expr(&AssignOp::Eq, lhs_value, rhs_value)?
!= Value::Bool(true)
{
return Ok(Value::Bool(false));
}
}
return Ok(Value::Bool(true));
}
(Expr::Array { .. }, _) => {
let value = self.eval_expr(rhs)?;
@@ -645,6 +671,7 @@ impl Interpreter {
}
}
}
Value::Undefined | Value::Null => r = false,
// Other types cause every to evaluate to true even though
// it is supposed to happen only for empty domain.
_ => (),
@@ -715,7 +742,6 @@ impl Interpreter {
Ok(true)
}
// Destructure objects
(Expr::Object { fields, .. }, Value::Object(_)) => {
let mut r = true;
@@ -746,6 +772,10 @@ impl Interpreter {
Ok(r)
}
// TODO: This suppresses errors in case of type mismatches.
// OPA raises the error sometimes in static scenarios, but doesn't
// raise in scenarios due to data/input
(Expr::Array { .. }, _) | (Expr::Object { .. }, _) => Ok(false),
_ => {
let expr_value = self.lookup_or_eval_expr(cache, expr)?;
if expr_value == Value::Undefined {
@@ -1622,16 +1652,19 @@ impl Interpreter {
}
fn lookup_builtin(&self, span: &Span, path: &str) -> Result<Option<&BuiltinFcn>> {
Ok(if let Some(builtin) = builtins::BUILTINS.get(path) {
Some(builtin)
} else if let Some(builtin) = builtins::DEPRECATED.get(path) {
if let Some(builtin) = builtins::BUILTINS.get(path) {
return Ok(Some(builtin));
}
#[cfg(feature = "deprecated")]
if let Some(builtin) = builtins::DEPRECATED.get(path) {
if !self.allow_deprecated {
bail!(span.error(format!("{path} is deprecated").as_str()))
}
Some(builtin)
} else {
None
})
return Ok(Some(builtin));
}
Ok(None)
}
fn eval_call_impl(&mut self, span: &Span, fcn: &ExprRef, params: &[ExprRef]) -> Result<Value> {

View File

@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use core::fmt::{Debug, Formatter};
use std::cmp::{Ord, Ordering};
use std::ops::{AddAssign, Div, MulAssign, Rem, SubAssign};
use std::rc::Rc;
@@ -56,13 +57,47 @@ impl Serialize for BigDecimal {
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
#[derive(Clone)]
pub enum Number {
// TODO: maybe specialize for u64, i64, f64
Big(Rc<BigDecimal>),
}
impl Debug for Number {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Number::Big(b) => b.d.fmt(f),
}
}
}
impl Serialize for Number {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Big(b) => {
if let Some(n) = self.as_u64() {
n.serialize(serializer)
} else if let Some(n) = self.as_i64() {
n.serialize(serializer)
} else {
if let Some(f) = self.as_f64() {
if &Number::from(f) == self {
return f.serialize(serializer);
}
}
let s = b.d.to_string();
let v = serde_json::Number::from_str(&s)
.map_err(|_| serde::ser::Error::custom("could not serialize big number"))?;
v.serialize(serializer)
}
}
}
}
}
use Number::*;
impl From<u64> for Number {

View File

@@ -76,7 +76,7 @@ impl<'source> Parser<'source> {
pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> {
match &self.future_keywords.get(kw) {
Some(s) => Err(self.source.error(
Some(s) if false => Err(self.source.error(
span.line,
span.col,
format!(
@@ -86,8 +86,11 @@ impl<'source> Parser<'source> {
)
.as_str(),
)),
None => {
_ => {
self.future_keywords.insert(kw.to_string(), span.clone());
if kw == "every" {
self.future_keywords.insert("in".to_string(), span.clone());
}
Ok(())
}
}
@@ -1475,6 +1478,11 @@ impl<'source> Parser<'source> {
let ref_comps = Self::get_path_ref_components(&import.refr)?;
let comps: Vec<std::rc::Rc<&str>> = ref_comps.iter().map(|s| s.text()).collect();
if comps.len() >= 2 && comps[0].as_ref() == &"future" && comps[1].as_ref() == &"keywords" {
imports.push(import);
return Ok(());
}
for imp in imports.iter() {
let imp_comps = Self::get_path_ref_components(&imp.refr)?;
let imp_comps: Vec<std::rc::Rc<&str>> = imp_comps.iter().map(|s| s.text()).collect();

View File

@@ -51,12 +51,17 @@ fn get_extra_arg_impl(
*n_args
} else {
let path = get_path_string(fcn, None)?;
if let Some((_, n_args)) = BUILTINS.get(path.as_str()) {
if let Some((_, n_args)) = functions.get(&path) {
*n_args
} else if let Some((_, n_args)) = DEPRECATED.get(path.as_str()) {
} else if let Some((_, n_args)) = BUILTINS.get(path.as_str()) {
*n_args
} else {
return Ok(None);
#[cfg(feature = "deprecated")]
if let Some((_, n_args)) = DEPRECATED.get(path.as_str()) {
*n_args
} else {
return Ok(None);
}
}
};
if (n_args as usize) + 1 == params.len() {

View File

@@ -223,10 +223,12 @@ impl Value {
}
}
#[cfg(feature = "yaml")]
pub fn from_yaml_str(yaml: &str) -> Result<Value> {
Ok(serde_yaml::from_str(yaml)?)
}
#[cfg(feature = "yaml")]
pub fn from_yaml_file(path: &String) -> Result<Value> {
match std::fs::read_to_string(path) {
Ok(c) => Self::from_yaml_str(c.as_str()),