你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

声明规则函数(在策略版本 1.2+ 中受支持)

Azure 证明策略语言已更新,现允许查询以 JSON 格式传入的证据。 本文介绍对策略语言做出的所有改进。

此策略版本中引入了一个新的运算符和六个函数。

函数

过去为了生成特定传入声明而处理的证明证据现在将以 JSON 输入的形式提供给策略编写器。 此外,策略语言已更新为使用函数来提取信息。 如 Azure 证明声明规则语法中所述,该语言支持以下三种值类型和一个隐式赋值运算符:

  • Boolean
  • Integer
  • 字符串
  • 声明属性访问

可以使用新的函数调用表达式来处理传入声明集。 将函数调用表达式与声明属性配合使用。 其结构如下:

value=FunctionName((Expression (, Expression)*)?))

该函数调用表达式以函数的名称开头,后接包含零个或多个以逗号分隔的参数的括号。 由于函数的参数是表达式,因此该语言允许将一个函数调用指定为另一个函数的参数。 在函数计算开始之前,参数将从左到右进行计算。

策略语言中实现以下函数。

JmesPath 函数

JmesPath 是一种 JSON 查询语言。 它指定了多个可用于在 JSON 文档中搜索数据的运算符和函数。 搜索始终返回有效的 JSON 作为输出。 JmesPath 函数的结构如下:

JmesPath(Expression, Expression)

参数约束

该函数需要两个(且只能是两个)参数。 第一个参数必须将非空 JSON 计算为字符串。 第二个参数必须计算为非空的有效 JmesPath 查询字符串。

计算

根据第一个参数表示的 JSON 数据计算第二个参数表示的 JmesPath 表达式,并将结果 JSON 作为字符串返回。

只读参数对结果的影响

如果从只读声明中检索到任一输入参数,则生成的 JSON 字符串是只读的。

用例

下面的例子说明了这个概念。

文本参数(布尔值、整数、字符串)

请考虑以下规则:

=>add(type="JmesPathResult", value=JmesPath("{\"foo\": \"bar\"}", "foo"));

在计算此规则期间,将计算 JmesPath 函数。 计算过程无非就是根据 JSON { "foo" : "bar" } 计算 JmesPath 查询 foo。此搜索操作的结果是 JSON 值“bar”。因此,规则的计算会将类型为“JmesPathResult”且字符串值为“bar”的新声明添加到传入的声明集。

反斜杠字符用于转义表示 JSON 数据的文本字符串中的双引号字符。

从声明构造的参数

假设在传入的声明集中提供了以下声明:

Claim1: {type="JsonData", value="{\"values\": [0,1,2,3,4]}"}
Claim2: {type="JmesPathQuery", value="values[2]"}

可按如下所示编写 JmesPath 查询:

c1:[type="JsonData"] && c2:[type=="JmesPathQuery"] =>
add(type="JmesPathResult", value=JmesPath(c1.value, c2.value));

JmesPath 函数的计算过程无非就是根据 JSON {"values":[0,1,2,3,4]} 计算 JmesPath 查询 values[2]。 因此,规则的计算会将类型为“JmesPathResult”且字符串值为“2”的新声明添加到传入的声明集。 因此,传入的集更新为:

Claim1: {type="JsonData", value="{\"values\": [0,1,2,3,4]}"}
Claim2: {type="JmesPathQuery", value="values[2]"}
Claim3: {type="JmesPathQuery", value="2"}

JsonToClaimValue 函数

JSON 指定了六种类型的值:

  • 数字(可以是整数或小数)
  • 布尔
  • 字符串
  • 对象
  • Array
  • Null

策略语言仅支持四种类型的声明值:

  • Integer
  • 布尔
  • 字符串
  • 设置

在策略语言中,JSON 值表示为字符串声明,其值等于该 JSON 值的字符串表示形式。 JsonToClaimValue 函数用于将 JSON 值转换为策略语言支持的声明值。 该函数的结构如下:

JsonToClaimValue(Expression)

参数约束

该函数需要一个(且只能是一个)参数,该参数必须计算为字符串形式的有效 JSON。

计算

下面介绍了六种类型的 JSON 值转换为声明值的方式:

  • 数字:如果数字是整数,则该函数将返回声明值和相同的整数值。 如果数字是小数,则会生成错误。
  • 布尔值:该函数返回声明值和相同的布尔值。
  • 字符串:该函数返回声明值和相同的字符串值。
  • 对象:该函数不支持 JSON 对象。 如果参数是 JSON 对象,则会生成错误。
  • 数组:该函数仅支持基元(数字、布尔值、字符串和 null)类型的数组。 此类数组将转换为集,其中包含具有相同类型的声明,但该声明具有通过转换数组中的 JSON 值创建的值。 如果该函数的参数是非基元(对象和数组)类型的数组,则会生成错误。
  • Null:如果输入为 JSON null,则该函数将返回空声明值。 如果使用此类声明值来构造声明,则该声明是空声明。 如果规则尝试添加或发出空声明,则不会将任何声明添加到传入或传出的声明集。 换言之,尝试添加或发出空声明的规则会导致 no-op。

只读参数对结果的影响

如果输入参数是只读的,那么结果声明值就是只读的。

用例

下面的例子说明了这个概念。

JSON 数字/布尔值/字符串

假设在传入的声明集中提供了以下声明:

Claim1: { type="JsonIntegerData", value="100" }
Claim2: { type="JsonBooleanData", value="true" }
Claim1: { type="JsonStringData", value="abc" }

计算规则:

c:[type=="JsonIntegerData"] => add(type="IntegerResult", value=JsonToClaimValue(c.value));
c:[type=="JsonBooleanData"] => add(type="BooleanResult", value=JsonToClaimValue(c.value));
c:[type=="JsonStringData"] => add(type="StringResult", value=JsonToClaimValue(c.value));

已更新传入的声明集:

Claim1: { type = "JsonIntegerData", value="100" } 
Claim2: { type = "IntegerResult", value="100" } 
Claim3: { type = "JsonBooleanData", value="true" } 
Claim4: { type = "BooleanResult", value="true" } 
Claim5: { type = "JsonStringData", value="abc" } 
Claim6: { type = "StringResult", value="abc" } 

JSON 数组

假设在传入的声明集中提供了以下声明:

Claim1: { type="JsonData", value="[0, \"abc\", true]" }

计算规则:

c:[type=="JsonData"] => add(type="Result", value=JsonToClaimValue(c.value));

已更新传入的声明集:

Claim1: { type="JsonData", value="[0, \"abc\", true]" }
Claim2: { type="Result", value=0 }
Claim3: { type="Result", value="abc" }
Claim4: { type="Result", value=true}

声明中的类型相同,只有值不同。 如果数组中存在多个具有相同值的条目,则会创建多个声明。

JSON Null

假设在传入的声明集中提供了以下声明:

Claim1: { type="JsonData", value="null" }

计算规则:

c:[type=="JsonData"] => add(type="Result", value=JsonToClaimValue(c.value));

已更新传入的声明集:

Claim1: { type="JsonData", value="null" }

规则尝试添加具有 Result 类型和空值的声明。 由于不允许,因此不会创建任何声明,并且传入声明集保持不变。

IsSubsetOf 函数

此函数用于检查某个声明集是否为另一个声明集的子集。 该函数的结构如下:

IsSubsetOf(Expression, Expression)

参数约束

此函数需要两个(且只能是两个)参数。 这两个参数都可以是任意基数的集。 策略语言中的集本质上是异构的,因此参数集中可以存在哪种类型的值没有限制。

计算

该函数检查第一个参数表示的集是否为第二个参数表示的集的子集。 如果是,则返回 true。 否则返回 false。

只读参数对结果的影响

由于该函数只是创建并返回布尔值,因此返回的声明值始终为非只读的。

用例

假设在传入的声明集中提供了以下声明:

Claim1: { type="Subset", value="abc" }
Claim2: { type="Subset", value=100 }
Claim3: { type="Superset", value=true }
Claim4: { type="Superset", value="abc" }
Claim5: { type="Superset", value=100 }

计算规则:

c1:[type == "Subset"] && c2:[type=="Superset"] => add(type="IsSubset", value=IsSubsetOf(c1.value, c2.value));

已更新传入的声明集:

Claim1: { type="Subset", value="abc" }
Claim2: { type="Subset", value=100 }
Claim3: { type="Superset", value=true }
Claim4: { type="Superset", value="abc" }
Claim5: { type="Superset", value=100 }
Claim6: { type="IsSubset", value=true }

AppendString 函数

此函数用于追加两个字符串值。 该函数的结构如下:

AppendString(Expression, Expression)

参数约束

此函数需要两个(且只能是两个)参数。 这两个参数都必须计算为字符串值。 允许空字符串参数。

计算

此函数将第二个参数的字符串值追加到第一个参数的字符串值,并返回结果字符串值。

只读参数对结果的影响

如果从只读声明中检索到任一参数,则将结果字符串值视为只读。

用例

假设在传入的声明集中提供了以下声明:

Claim1: { type="String1", value="abc" }
Claim2: { type="String2", value="xyz" }

计算规则:

c:[type=="String1"] && c2:[type=="String2"] => add(type="Result", value=AppendString(c1.value, c2.value));

已更新传入的声明集:

Claim1: { type="String1", value="abc" }
Claim2: { type="String2", value="xyz" }
Claim3: { type="Result", value="abcxyz" }

NegateBool 函数

此函数用于对布尔声明值取反。 该函数的结构如下:

NegateBool(Expression)

参数约束

该函数需要一个(且只能是一个)参数,该参数必须计算为布尔值。

计算

此函数对参数表示的布尔值取反,并返回取反的值。

只读参数对结果的影响

如果从只读声明中检索到参数,则将结果布尔值视为只读。

用例

假设在传入的声明集中提供了以下声明:

Claim1: { type="Input", value=true }

计算规则:

c:[type=="Input"] => add(type="Result", value=NegateBol(c.value));

已更新传入的声明集:

Claim1: { type="Input", value=true }
Claim2: { type="Result", value=false }

ContainsOnlyValue 函数

此函数用于检查声明集是否仅包含特定的声明值。 该函数的结构如下:

ContainsOnlyValue(Expression, Expression)

参数约束

此函数需要两个(且只能是两个)参数。 第一个参数可以计算任意基数的异构集。 第二个参数必须计算策略语言支持的任何类型(布尔值、字符串或整数)的单个值。

计算

如果第一个参数表示的集不为空并且仅包含第二个参数表示的值,则该函数将返回 true。 如果第一个参数表示的集为空或包含的任何值不是第二个参数表示的值,则该函数将返回 false。

只读参数对结果的影响

由于该函数只是创建并返回布尔值,因此返回的声明值始终为非只读的。

用例

假设在传入的声明集中提供了以下声明:

Claim1: {type="Set", value=100}
Claim2: {type="Set", value=101}

计算规则:

c:[type=="Set"] => add(type="Result", value=ContainsOnlyValue(100));

已更新传入的声明集:

Claim1: {type="Set", value=100}
Claim2: {type="Set", value=101}
Claim3: {type="Result", value=false}

Not 条件运算符

策略语言中的规则从可选的条件列表开始,这些条件充当传入声明集的筛选条件。 这些条件可用于识别传入的声明集中是否存在某个声明。 但是,无法检查某个声明是否不存在。 因此,引入了一个新的运算符 (!),可将它应用于条件列表中的各个条件。 此运算符改变了条件的计算行为,现在不是检查某个声明是否存在,而是检查某个声明是否不存在。

用例

假设在传入的声明集中提供了以下声明:

Claim1: {type="Claim1", value=100}
Claim2: {type="Claim2", value=200}

计算规则:

![type=="Claim3"] => add(type="Claim3", value=300)

此规则实际上可解读为:如果传入的声明集中不存在类型为“Claim3”的声明,则将类型为“Claim3”且值为 300 的新声明添加到传入的声明集中。

已更新传入的声明集:

Claim1: {type="Claim1", value=100}
Claim2: {type="Claim2", value=200}
Claim3: {type="Claim2", value=300}

使用策略版本 1.2 的示例策略

Windows 实现测量的启动,并且伴随着证明,所提供的保护得到了极大的增强。 你可以使用它安全可靠地检测并防范易受攻击的以及恶意的启动组件。 现在,你可以用这些度量生成证明策略。

version=1.2;

authorizationrules { 
  => permit();
};


issuancerules
{

c:[type == "events", issuer=="AttestationService"] => add(type = "efiConfigVariables", value = JmesPath(c.value, "Events[?EventTypeString == 'EV_EFI_VARIABLE_DRIVER_CONFIG' && ProcessedData.VariableGuid == '8BE4DF61-93CA-11D2-AA0D-00E098032B8C']"));

c:[type=="efiConfigVariables", issuer="AttestationPolicy"]=> issue(type = "secureBootEnabled", value = JsonToClaimValue(JmesPath(c.value, "[?ProcessedData.UnicodeName == 'SecureBoot'] | length(@) == `1` && @[0].ProcessedData.VariableData == 'AQ'")));
![type=="secureBootEnabled", issuer=="AttestationPolicy"] => issue(type="secureBootEnabled", value=false);

};