#request-response #internet-computer #request-http #icp #agent #dfinity

ic-http-certification

互联网计算机的HTTP响应认证

8个稳定版本

2.6.0 2024年7月24日
2.5.0 2024年3月20日
2.4.0 2024年2月19日
2.3.0 2024年1月15日
2.0.1 2023年12月20日

#7 in #request-http

Download history 5177/week @ 2024-05-05 4362/week @ 2024-05-12 3560/week @ 2024-05-19 5201/week @ 2024-05-26 6138/week @ 2024-06-02 4917/week @ 2024-06-09 4495/week @ 2024-06-16 4353/week @ 2024-06-23 5565/week @ 2024-06-30 5576/week @ 2024-07-07 3982/week @ 2024-07-14 4040/week @ 2024-07-21 3636/week @ 2024-07-28 4761/week @ 2024-08-04 4645/week @ 2024-08-11 5585/week @ 2024-08-18

18,807 每月下载量
3 个crate中使用(通过 ic-response-verification

Apache-2.0

325KB
6K SLoC

HTTP认证

概述

HTTP认证是ICP HTTP网关协议的一个子协议。它用于验证HTTP网关从canister接收到的HTTP响应,与相应的HTTP请求进行比较。这使得HTTP网关能够验证它们从canister接收到的响应是真实的且未被篡改的。

使用ic-http-certification crate为在Rust canister中实现HTTP认证协议提供了基础。认证通过以下步骤实现

  1. 定义CEL表达式
  2. 创建认证
  3. 创建HTTP认证树

定义CEL表达式

CEL(通用表达式语言)是一种可移植的表达式语言,可用于不同应用轻松互操作。它可以看作是协议缓冲区的计算或表达式对应物。

CEL表达式是ICP HTTP认证系统的核心。它们用于定义请求和响应对应该被认证的条件。它们还定义了在认证中应包含相应请求和响应对象的内容。

可以通过两种方式创建CEL表达式

将CEL表达式转换为它们的String表示形式

请注意,CelExpression枚举本身不是CEL表达式,而是CEL表达式的Rust表示。要将CelExpression转换为它的String表示形式,请使用CelExpression.to_stringcreate_cel_expr。这适用于通过CEL构建器直接创建的CEL表达式。

use ic_http_certification::cel::{CelExpression, DefaultCelExpression};

let cel_expr = CelExpression::Default(DefaultCelExpression::Skip).to_string();

或者

use ic_http_certification::cel::{CelExpression, DefaultCelExpression, create_cel_expr};

let certification = CelExpression::Default(DefaultCelExpression::Skip);
let cel_expr = create_cel_expr(&certification);

使用CEL构建器

CEL构建器界面提供了一种通过用户友好的界面来简化CEL表达式创建的方法。还可以直接 创建CEL表达式。要定义CEL表达式,从 DefaultCelBuilder 开始。这个结构提供了一组关联函数,可以用来定义如何认证请求和响应对。

当认证请求时

  • 请求正文和方法总是被认证。
  • 要认证请求头和查询参数,分别使用 with_request_headerswith_request_query_parameters。这两个关联函数都接受一个 str 切片作为参数。

当认证响应时

  • 响应正文和状态码总是被认证。
  • 要认证响应头,使用 with_response_certification。这个关联函数接受一个 DefaultResponseCertification 枚举作为参数。
    • 要指定头包含,使用 DefaultResponseCertification 枚举的 certified_response_headers 关联函数。
    • 要认证所有响应头(有某些排除项)使用 DefaultResponseCertification 枚举的 response_header_exclusions 关联函数。这两个函数都接受一个 str 切片作为参数。

完全认证的请求/响应对

要定义完全认证的请求和响应对,包括请求头、查询参数和响应头,请使用 DefaultCelBuilder::full_certification

例如

use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};

let cel_expr = DefaultCelBuilder::full_certification()
    .with_request_headers(vec!["Accept", "Accept-Encoding", "If-None-Match"])
    .with_request_query_parameters(vec!["foo", "bar", "baz"])
    .with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
        "Cache-Control",
        "ETag",
    ]))
    .build();

部分认证的请求

可以通过 with_request_headerswith_request_query_parameters 分别认证任意数量的请求头或请求查询参数。这两种方法都将接受空数组,这与根本不调用它们相同。同样,对于 with_request_query_parameters,如果它用一个空数组调用,或者根本不调用,则不会认证任何请求查询参数。如果两者都用空数组调用,或者两者都没有调用,那么只有请求正文和方法会被认证。

例如,只认证请求正文和方法

use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};

let cel_expr = DefaultCelBuilder::full_certification()
    .with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
        "Cache-Control",
        "ETag",
    ]))
    .build();

或者,这可以更明确地完成

use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};

let cel_expr = DefaultCelBuilder::full_certification()
    .with_request_headers(vec![])
    .with_request_query_parameters(vec![])
    .with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
        "Cache-Control",
        "ETag",
    ]))
    .build();

跳过请求认证

可以通过使用 DefaultCelBuilder::response_only_certification 而不是 DefaultCelBuilder::full_certification 完全跳过请求认证。

例如

use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};

let cel_expr = DefaultCelBuilder::response_only_certification()
    .with_response_certification(DefaultResponseCertification::response_header_exclusions(vec![
        "Date",
        "Cookie",
        "Set-Cookie",
    ]))
    .build();

部分认证的响应

在调用 with_response_certification 时,可以通过 DefaultResponseCertification 枚举的 certified_response_headers 关联函数提供任意数量的响应头。提供的数组也可以为空。如果数组为空,或者没有调用关联函数,则不会认证任何响应头。

例如,只认证响应正文和状态码

use ic_http_certification::DefaultCelBuilder;

let cel_expr = DefaultCelBuilder::response_only_certification().build();

这也可以更明确地完成

use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};

let cel_expr = DefaultCelBuilder::response_only_certification()
    .with_response_certification(DefaultResponseCertification::certified_response_headers(vec![]))
    .build();

当同时使用 DefaultCelBuilder::response_only_certificationDefaultCelBuilder::full_certification 时,也适用同样的情况

use ic_http_certification::DefaultCelBuilder;

let cel_expr = DefaultCelBuilder::full_certification()
    .with_request_headers(vec!["Accept", "Accept-Encoding", "If-None-Match"])
    .with_request_query_parameters(vec!["foo", "bar", "baz"])
    .build();

要完全跳过响应认证,必须完全跳过认证。认证请求而不认证响应是没有意义的。

跳过认证

要完全跳过认证,请使用 skip_certification,例如

use ic_http_certification::DefaultCelBuilder;

let cel_expr = DefaultCelBuilder::skip_certification();

跳过认证看起来可能有些反直觉,但并非总是可能对请求和响应对进行认证。例如,一个会为每个用户返回不同数据的canister方法就不容易进行认证。

通常,这些请求过去通过raw ICP URL进行路由,但这很危险,因为raw URL允许任何响应副本决定是否需要认证。相比之下,通过使用非raw URL的上述方法跳过认证,副本将不再能够决定是否需要认证,而是由canister本身和共识结果来决定。

创建认证

一旦定义了CEL表达式,就可以将其与HttpRequestHttpResponse结合使用来创建HttpCertification结构体的实例。HttpCertification结构体有三个相关函数

  • 相关的full函数用于在认证中包含HttpRequest和相应的HttpResponse
  • 相关的response_only函数用于在认证中仅包含HttpResponse,并排除相应的HttpRequest
  • 相关的skip函数用于完全跳过认证。

完整认证

执行完整认证需要从DefaultCelBuilder::full_certification创建的CEL表达式,以及一个HttpRequest和一个HttpResponse,以及可选的预计算响应体哈希。

例如

use ic_http_certification::{HttpCertification, HttpRequest, HttpResponse, DefaultCelBuilder, DefaultResponseCertification};

let cel_expr = DefaultCelBuilder::full_certification()
    .with_request_headers(vec!["Accept", "Accept-Encoding", "If-None-Match"])
    .with_request_query_parameters(vec!["foo", "bar", "baz"])
    .with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
        "Cache-Control",
        "ETag",
    ]))
    .build();

let request = HttpRequest {
    method: "GET".to_string(),
    url: "/index.html?foo=a&bar=b&baz=c".to_string(),
    headers: vec![
        ("Accept".to_string(), "application/json".to_string()),
        ("Accept-Encoding".to_string(), "gzip".to_string()),
        ("If-None-Match".to_string(), "987654321".to_string()),
    ],
    body: vec![],
};

let response = HttpResponse {
    status_code: 200,
    headers: vec![
        ("Cache-Control".to_string(), "no-cache".to_string()),
        ("ETag".to_string(), "123456789".to_string()),
        ("IC-CertificateExpression".to_string(), cel_expr.to_string()),
    ],
    body: vec![1, 2, 3, 4, 5, 6],
    upgrade: None,
};

let certification = HttpCertification::full(&cel_expr, &request, &response, None);

仅响应认证

执行仅响应认证需要从DefaultCelBuilder::response_only_certification创建的CEL表达式,以及一个HttpResponse,以及可选的预计算响应体哈希。

例如

use ic_http_certification::{HttpCertification, HttpResponse, DefaultCelBuilder, DefaultResponseCertification};

let cel_expr = DefaultCelBuilder::response_only_certification()
    .with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
        "Cache-Control",
        "ETag",
    ]))
    .build();

let response = HttpResponse {
    status_code: 200,
    headers: vec![
        ("Cache-Control".to_string(), "no-cache".to_string()),
        ("ETag".to_string(), "123456789".to_string()),
        ("IC-CertificateExpression".to_string(), cel_expr.to_string()),
    ],
    body: vec![1, 2, 3, 4, 5, 6],
    upgrade: None,
};

let certification = HttpCertification::response_only(&cel_expr, &response, None).unwrap();

跳过认证

跳过认证不需要定义显式的CEL表达式,因为它始终是相同的。

例如

use ic_http_certification::HttpCertification;

let certification = HttpCertification::skip();

创建HTTP认证树

定义树路径

可以使用HttpCertificationPath结构体定义树路径,并分为两种类型:wildcard(通配符)和exact(精确)。这两种类型的路径可以以或不以反斜杠结尾,但请注意,以反斜杠结尾的路径与不以反斜杠结尾的路径是不同的路径,并且它们将由树以这种方式处理。

通配符路径可用于匹配请求URL的子路径。这对于404响应、回退或重写可能很有用。它们使用wildcard相关函数定义。

在这个例子中,使用此路径进入树的认证将适用于以/js开头的任何请求URL,除非在树中存在更具体的路径(例如/js/example.js)。

use ic_http_certification::HttpCertificationPath;

let path = HttpCertificationPath::wildcard("/js");

精确路径用于匹配整个请求URL。以反斜杠结尾的精确路径指向文件系统目录,而不以反斜杠结尾的路径指向单个文件。两者都是认证树中的单独路径,并将完全独立处理。

在这个例子中,具有此路径的证书将仅对精确的请求URL /js/example.js 有效。

use ic_http_certification::HttpCertificationPath;

let path = HttpCertificationPath::exact("/js/example.js");

使用HTTP证书树

HttpCertificationTree 可以很容易地通过 Default 特性进行初始化,并且可以使用 HttpCertificationTreeEntry 结构体向树中添加条目、从树中删除条目或由树生成见证。 HttpCertificationTreeEntry 需要一个 HttpCertification 和一个 HttpCertificationPath

例如

use ic_http_certification::{HttpCertification, HttpRequest, HttpResponse, DefaultCelBuilder, DefaultResponseCertification, HttpCertificationTree, HttpCertificationTreeEntry, HttpCertificationPath};

let cel_expr = DefaultCelBuilder::full_certification()
    .with_request_headers(vec!["Accept", "Accept-Encoding", "If-None-Match"])
    .with_request_query_parameters(vec!["foo", "bar", "baz"])
    .with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
        "Cache-Control",
        "ETag",
    ]))
    .build();

let request = HttpRequest {
    method: "GET".to_string(),
    url: "/index.html?foo=a&bar=b&baz=c".to_string(),
    headers: vec![
        ("Accept".to_string(), "application/json".to_string()),
        ("Accept-Encoding".to_string(), "gzip".to_string()),
        ("If-None-Match".to_string(), "987654321".to_string()),
    ],
    body: vec![],
};

let response = HttpResponse {
    status_code: 200,
    headers: vec![
        ("Cache-Control".to_string(), "no-cache".to_string()),
        ("ETag".to_string(), "123456789".to_string()),
        ("IC-CertificateExpression".to_string(), cel_expr.to_string()),
    ],
    body: vec![1, 2, 3, 4, 5, 6],
    upgrade: None,
};

let request_url = "/example.json";
let path = HttpCertificationPath::exact(request_url);
let certification = HttpCertification::full(&cel_expr, &request, &response, None).unwrap();

let mut http_certification_tree = HttpCertificationTree::default();

let entry = HttpCertificationTreeEntry::new(&path, &certification);

// insert the entry into the tree
http_certification_tree.insert(&entry);

// generate a witness for this entry in the tree
let witness = http_certification_tree.witness(&entry, request_url);

// delete the entry from the tree
http_certification_tree.delete(&entry);

直接创建CEL表达式

要定义一个CEL表达式,从 CelExpression 枚举开始。此枚举提供了一组变体,可以用于定义ICP HTTP网关支持的CEL表达式类型。目前仅支持一个变体,称为“默认”证书表达式,但随着HTTP证书协议的演变,未来可能还会添加更多。

当认证请求时

  • 请求正文和方法总是被认证。

  • 要证书请求头和查询参数,请使用 DefaultRequestCertification 结构体的 headersquery_paramters 字段。这两个字段都接受一个 str 切片作为参数。

当认证响应时

  • 响应正文和状态码总是被认证。

  • 要证书响应头,请使用 DefaultResponseCertification 枚举的 certified_response_headers 相关函数。或者要证书所有响应头(带有一些排除项),请使用 DefaultResponseCertification 枚举的 response_header_exclusions 相关函数。这两个相关函数都接受一个 str 切片作为参数。

注意,以下提供的示例CEL表达式是为了可读性而格式化的。由 CelExpression::to_stringcreate_cel_expr 产生的实际CEL表达式是压缩的。压缩的CEL表达式更受欢迎,因为它更紧凑,从而减少了HTTP网关验证证书时的有效负载,并加快了评估时间,但格式化的版本也可以接受。

完全认证的请求/响应对

要定义一个完全证书的请求和响应对,包括请求头、查询参数和响应头

use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCelExpression, DefaultFullCelExpression, DefaultRequestCertification, DefaultResponseCertification};

let cel_expr = CelExpression::Default(DefaultCelExpression::Full(
  DefaultFullCelExpression {
    request: DefaultRequestCertification::new(
      vec!["Accept", "Accept-Encoding", "If-None-Match"],
      vec!["foo", "bar", "baz"],
    ),
    response: DefaultResponseCertification::certified_response_headers(vec![
      "ETag",
      "Cache-Control",
    ]),
  }));

这将产生以下CEL表达式

default_certification (
  ValidationArgs {
    request_certification: RequestCertification {
      certified_request_headers: ["Accept", "Accept-Encoding", "If-None-Match"],
      certified_query_parameters: ["foo", "bar", "baz"]
    },
    response_certification: ResponseCertification {
      certified_response_headers: ResponseHeaderList {
        headers: [
          "ETag",
          "Cache-Control"
        ]
      }
    }
  }
)

部分认证的请求

可以通过 DefaultRequestCertification 结构体的 headersquery_parameters 字段提供任意数量的请求头或查询参数,并且两者都可以是空数组。如果 headers 字段为空,则不会证书任何请求头。同样对于 query_parameters 字段,如果它为空,则不会证书任何查询参数。如果两者都为空,则只证书请求体和方法。

例如,只认证请求正文和方法

use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCelExpression, DefaultFullCelExpression, DefaultRequestCertification, DefaultResponseCertification};

let cel_expr = CelExpression::Default(DefaultCelExpression::Full(
  DefaultFullCelExpression {
    request: DefaultRequestCertification::new(
      vec![],
      vec![],
    ),
    response: DefaultResponseCertification::certified_response_headers(vec![
      "ETag",
      "Cache-Control",
    ]),
  }));

这将产生以下CEL表达式

default_certification (
  ValidationArgs {
    request_certification: RequestCertification {
      certified_request_headers: [],
      certified_query_parameters: []
    },
    response_certification: ResponseCertification {
      certified_response_headers: ResponseHeaderList {
        headers: [
          "ETag",
          "Cache-Control"
        ]
      }
    }
  }
)

跳过请求认证

可以通过使用 DefaultCelExpression 结构体的 ResponseOnly 变体来完全跳过请求证书。

例如

use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCelExpression, DefaultResponseOnlyCelExpression, DefaultResponseCertification};

let cel_expr = CelExpression::Default(DefaultCelExpression::ResponseOnly(
  DefaultResponseOnlyCelExpression {
    response: DefaultResponseCertification::certified_response_headers(vec![
      "ETag",
      "Cache-Control",
    ]),
  }));

这将产生以下CEL表达式

default_certification (
  ValidationArgs {
    no_request_certification: Empty {},
    response_certification: ResponseCertification {
      certified_response_headers: ResponseHeaderList {
        headers: [
          "ETag",
          "Cache-Control"
        ]
      }
    }
  }
)

部分认证的响应

与请求证书类似,可以通过 DefaultResponseCertification 枚举的 certified_response_headers 相关函数提供任意数量的响应头,也可以是空数组。如果数组为空,则不会证书任何响应头。

例如

use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCertification, DefaultRequestCertification, DefaultResponseCertification};

let cel_expr = CelExpression::DefaultCertification(Some(DefaultCertification {
  request: DefaultRequestCertification::new(
    vec!["Accept", "Accept-Encoding", "If-None-Match"],
    vec!["foo", "bar", "baz"],
  ),
  response_certification: DefaultResponseCertification::certified_response_headers(vec![]),
}));

这将产生以下CEL表达式

default_certification (
  ValidationArgs {
    request_certification: RequestCertification {
      certified_request_headers: ["Accept", "Accept-Encoding", "If-None-Match"],
      certified_query_parameters: ["foo", "bar", "baz"]
    },
    response_certification: ResponseCertification {
      certified_response_headers: ResponseHeaderList {
        headers: []
      }
    }
  }
)

如果使用 response_header_exclusions 相关函数,则空数组将证书 所有 响应头。例如

use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCelExpression, DefaultFullCelExpression, DefaultRequestCertification, DefaultResponseCertification};

let cel_expr = CelExpression::Default(DefaultCelExpression::Full(
  DefaultFullCelExpression {
    request: DefaultRequestCertification::new(
      vec!["Accept", "Accept-Encoding", "If-None-Match"],
      vec!["foo", "bar", "baz"],
    ),
    response: DefaultResponseCertification::response_header_exclusions(vec![]),
  }));

这将产生以下CEL表达式

default_certification (
  ValidationArgs {
    request_certification: RequestCertification {
      certified_request_headers: ["Accept", "Accept-Encoding", "If-None-Match"],
      certified_query_parameters: ["foo", "bar", "baz"]
    },
    response_certification: ResponseCertification {
      response_header_exclusions: ResponseHeaderList {
        headers: []
      }
    }
  }
)

要完全跳过响应证书,则必须完全跳过证书。证书请求而不证书响应是没有用的。

跳过认证

要完全跳过证书

use ic_http_certification::cel::{CelExpression, DefaultCelExpression};

let cel_expr = CelExpression::Default(DefaultCelExpression::Skip);

这将产生以下CEL表达式

default_certification (
  ValidationArgs {
    no_certification: Empty {}
  }
)

依赖

~2–12MB
~101K SLoC