立方体符号类型SDK
CubeSigner是一个硬件支持的非拘禁平台 安全 管理加密密钥。 此寄存器是用于 与 CubeSigner 服务进行方案互动.
立方体符号背景
小熊队 构建用于解决密钥的 CubeSigner 安全与关键可用性权衡:现在,许多团队被迫保留 内存中可用的密钥, 并因此暴露给攻击者, 或者试图保留 钥匙安全——通常只在休息时使用——费用高昂. CubeSigner 通过给开发人员低纬度访问来解决这个问题 硬件支持密钥生成和签名. 在所有这些行动中, CubeSigner 在由 MSM 封存的硝基飞地保护用户的密钥- 组合 带有热钱包速度和简便的冷钱包安全.
现在 CubeSigner SDK 支持像 Ethereum 这样的 EVM 链的签名 和雪崩, 以及非EVM链 比特币和索拉纳。 支持更多的链条 是在作品中!
安装 SDK
您可以从 npm 安装 SDK :
npm install --save "@cubist-labs/cubesigner-sdk"
npm install --save "@cubist-labs/cubesigner-sdk-fs-storage" # support for filesystem-backed sessions
登录到立方体符号
在运行下面的“ 启动” 示例( 或稍后的测试) 之前, 您必须
使用 cs 命令行工具,例如,
cs login [email protected] --env '<gamma|prod|...>'
开始
[TIP] 用于从
v0.3.*改为v0.4.*请看 移民.md
在这个节目中,我们要穿过一个简单的立方体符号 设置。 我们会制作一个签名钥匙 然后签署一些EVM 交易,然后添加一项安全政策,以限制 允许 CubeSigner 签名的交易.
开始,我们立即攻克高层 CubeSignerClient 类从一个
已存储在磁盘上的 CubeSigner 管理会话
(记住,你必须已经登录).
我们还假设,下列进口品可供所有 实例如下.
import * as cs from "@cubist-labs/cubesigner-sdk";
import { JsonFileSessionManager, defaultUserSessionManager } from "@cubist-labs/cubesigner-sdk-fs-storage";
import assert from "assert";
证据 CubeSignerClient
企业的第一顺序是创造一种 CubeSignerClient。 。 。 .
我们只要装上会话符号就可以了
磁盘上的默认位置( 这是 cs login 命令保存
目标:
const cubesigner = await cs.CubeSignerClient.create(defaultUserSessionManager());
或者,a CubeSignerClient 实例可以通过明确
提供会话管理器 :
// Create a session manager backed by a JSON file
const fileStorage = new JsonFileSessionManager(
`${process.env.HOME}/.config/cubesigner/management-session.json`,
);
await cs.CubeSignerClient.create(fileStorage);
获取 User 财务报告和审定财务报表 Org 资讯
我们现在可以得到一些关于登录用户和 用户属于:
const me = await cubesigner.user();
console.log(me);
assert(me.user_id); // each user has a globally unique ID
assert(me.org_ids); // IDs of all organizations this user is a member of
assert(me.org_ids.length === 1); // assume that the user is a member of exactly one org
const org = await cubesigner.org();
assert(await org.enabled()); // assume that the org is enabled
与一个组织还有许多关系,比如创建/列名 * 键,创建/列入清单,制定全局安全政策, 包括:.
其余的辅导,我们假设登录的用户是一个 至少一个组织的成员.
创建一个 Key
接下来,让我们创建一个钥匙,我们以后会用它来签署伊特鲁姆
交易。 为此,我们需要一种类型的钥匙 Secp256k1.Evm。 。 。 .
const secpKey = await org.createKey(cs.Secp256k1.Evm);
assert((await secpKey.owner()) == me.user_id);
assert(await secpKey.enabled());
assert(await secpKey.type(), cs.Secp256k1.Evm);
console.log(`Created '${cs.Secp256k1.Evm}' key ${secpKey.id}`);
签名 Ethereum 交易
我们做个假人吧 EvmSignRequest。 。 。 .
const eth1Request: cs.EvmSignRequest = {
chain_id: 1,
tx: {
to: "0xff50ed3d0ec03ac01d4c79aad74928bff48a7b2b",
type: "0x02",
gas: "0x61a80",
maxFeePerGas: "0x2540be400",
maxPriorityFeePerGas: "0x3b9aca00",
nonce: "0",
value: "0x100",
},
};
似乎我们已经准备好一切了 与这个请求签署
键。 但是,试图这样做失败了 403 Forbidden 说些类似的话 Session does not have the required scopes...
try {
console.log("Trying to sign with improper scopes");
await secpKey.signEvm(eth1Request);
assert(false, "Must not be allowed to sign without scopes");
} catch (e) {
assert(`${e}`.includes("Session does not have required scopes"));
}
默认情况下,CLI创建的会话无法执行签名操作. 所有会议都有一系列 范围 哪个 用于确定该会话的用途。 稍后我们再谈这个 但现在只需要知道 我们需要一个新的会议 有合适的范围:
let signingClient = await cs.CubeSignerClient.create(
// declare the "sign:*" scope which allows us to sign using any keys the
// account has access to
await cubesigner.org().createSession("readme signing demo", ["manage:key:get", "sign:*"]));
const signingKey = new cs.Key(signingClient, secpKey.cached);
let sig = await signingKey.signEvm(eth1Request);
console.log(sig.data());
assert(sig.data().rlp_signed_tx);
使用醚.js而不是直接使用 SDK
如果您的应用程序使用 ethers.js, 您可以配置它来使用 CubeSigner 到 签名交易.
import { Signer } from "@cubist-labs/cubesigner-sdk-ethers-v6";
import { ethers } from "ethers";
// Create new Signer
const ethersSigner = new Signer(secpKey.materialId, signingClient);
assert((await ethersSigner.getAddress()) === ethers.getAddress(secpKey.materialId));
// sign transaction as usual:
console.log(
"ethers.js signature:",
await ethersSigner.signTransaction({
chainId: 1,
to: "0xff50ed3d0ec03ac01d4c79aad74928bff48a7b2b",
value: ethers.parseEther("0.0000001"),
}),
);
访问控制 Role
CubeSigner 在多个用户想要时使用角色来控制密钥的访问
使用它们。 你可以把角色当作团体
允许某些用户访问某些密钥。 要开始,让我们
创建一个 Role 然后直接打电话 createSession 上面写着:
// Create a role, implicitly adding ourselves as a member
const role = await org.createRole();
console.log("Adding key to role, then signing an Ethereum transaction");
await role.addKey(secpKey);
// Members of the role can create sessions which can only access keys in the role
const roleClient = await cs.CubeSignerClient.create(
// Role sessions implicitly have the "sign:*" scope
await role.createSession("readme")
);
console.log(`Created client for role '${role.id}'`);
制定安全政策
当我们添加一个 Secp256k1.Evm 关键角色(如上所述),客户端
与这个角色有关,让我们可以签署 任意 以利亚
用那把钥匙进行交易 如果这看起来太宽容, 我们可以附加一个安全
政策限制这一关键要素在这一作用中的允许使用.
例如,将签名限制在事先核准的交易中
接收者,我们可以附加 TxReceiver 我们的关键政策:
console.log("Setting transaction receiver policy");
await secpKey.setPolicy([{ TxReceiver: "0xff50ed3d0ec03ac01d4c79aad74928bff48a7b2b" }]);
console.log("Signing transaction");
const roleKey = new cs.Key(roleClient, secpKey.cached);
sig = await roleKey.signEvm(eth1Request);
assert(sig.data().rlp_signed_tx);
尝试更改交易接收器并验证交易 事实上
console.log("Signing a transaction to a different receiver must be rejected");
try {
await roleKey.signEvm({
chain_id: 1,
tx: {
...eth1Request.tx,
to: "0x0000000000000000000000000000000000000000",
} satisfies any,
});
assert(false, "Must be rejected by policy");
} catch (e) {
assert(`${e}`.includes("Transaction receiver not allowed by policy"));
}
3⁄4 ̄ ̧漯B 制定新的政策,以覆盖以前的政策。 打电话
Key::appendPolicy改为Key::setPolicy添加到 现有政策.
签个字
这个 CubeSignerClient 班级曝光 signBlob 方法,哪个符号
带有给定密钥的任意( raw, 未解释) 字节包。 这个
但默认不允许操作; 永久
已禁用用于 BLS 键,对于其他键类型,可以通过
附有 "AllowRawBlobSigning" 政策:
// Create a new Ed25519 key (e.g., for Cardano) and add it to our roleClient's role
const edKey = await org.createKey(cs.Ed25519.Cardano);
await role.addKey(edKey);
console.log(`Created '${await edKey.type()}' key ${edKey.id} and added it to role ${role.id}`);
// Sign raw blobs with our new ed key and the secp we created before
for (const key of [edKey, secpKey]) {
console.log(`Confirming that raw blob with ${await key.type()} is rejected by default`);
const roleKey = new cs.Key(roleClient, key.cached);
const blobReq: cs.BlobSignRequest = {
message_base64: "L1kE9g59xD3fzYQQSR7340BwU9fGrP6EMfIFcyX/YBc=",
};
try {
await roleKey.signBlob(blobReq);
assert(false, "Must be rejected by policy");
} catch (e) {
assert(`${e}`.includes("Raw blob signing not allowed"));
}
console.log("Signing raw blob after adding 'AllowRawBlobSigning' policy");
await key.appendPolicy(["AllowRawBlobSigning"]);
const blobSig = await roleKey.signBlob(blobReq);
console.log(blobSig.data());
assert(blobSig.data().signature);
}
3⁄4 ̄ ̧漯B 当与 a 签下一个生球时
Secp256k1键,blob 必须 成为SHA-256等安全散列函数的输出,并且必须完全为32字节长. 这是Bitcoin,Ethereum等区块链所使用的ECDSA签名算法的严格要求. 签名任何不是安全散列函数输出的字节字符串,都会导致灾难性的安全故障,包括完全泄露出您的秘密密钥.
试图用分秒键签名无效信件将失败
400 Bad Request 说 Signature scheme: InvalidMessage。 。 。 .
会话管理
在上述例子中,我们使用了三个不同的会议:
- 使用 CLI 创建的仅管理范围的用户会话
- 我们创建的签名会(1)
- 我们创建的角色会议(1)
全体 CubeSignerClients需要有效的会话才能运行。 本节
我们将潜入TypeScript SDK中的会话细节.
从磁盘上装入
如果您已经有一个活动会话在 cs (立方体符号CLI),你可以
用 TypeScript SDK 装入此用户会话如下( 如我们先前所做的) :
await cs.CubeSignerClient.create(defaultUserSessionManager())
或者我们可以使用 CLI 为我们的 JavaScript 客户端创建自己的会话:
cs session create --role-id $ROLE_ID --scope sign-all --output json > session.json
然后我们可以这样装入:
try {
await cs.CubeSignerClient.create(new JsonFileSessionManager("./session.json"));
} catch {
console.error("Unable to find file")
}
从内存装入
如果您已经掌握了会话信息SessionData 在内存中,您可以
直接装入客户端.
// Get the session data we want to use with the client
const sessionData: cs.SessionData = await role.createSession("readme");
await cs.CubeSignerClient.create(new cs.MemorySessionManager(sessionData))
// or, for short
await cs.CubeSignerClient.create(sessionData)
[尖叫声]
这样创建的客户端会自动刷新会话 。 如果您尝试与另一个客户端使用此会话, 他们都会尝试刷新, 导致失败 .
管理人员、储存和刷新
正如我们从上面的例子中看到的那样 CubeSignerClients是使用 create 建筑师。 这个 create
构造器接受原始 SessionData 或一个 SessionManager迄今为止,我们已经看到两个不同的会话经理:
JsonFileSessionManagerMemorySessionManager
这些经理负责保存您的会话符号
并且(可选择地)坚持下去。 当你的会话符号刷新,
JsonFileSessionManager 将新符号写入磁盘.
更复杂的管理人员可以通过执行 SessionManager 接口.
为 OIDC 用户创建会话
立方体符号支持 伊办 用于认证第三方用户的标准.
首先,我们需要一个OIDC的标志。 我们可以从Google或其他方面得到一个
支持 OIDC 发行器 ! 为了这个例子,我们假设
oIDC 令牌存储在 OIDC_TOKEN 环境变量
import * as dotenv from "dotenv"; // npm install [email protected]
dotenv.config();
const oidcToken = process.env["OIDC_TOKEN"];
assert(oidcToken);
在使用 OIDC 符号进行认证之前, 我们必须添加 Org 策略 允许特定的发行人/听众对从符号.
const oldOrgPolicy = await org.policy();
const oidcPayload = JSON.parse(atob(oidcToken.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")));
const oidcAuthSourcesPolicy = {
OidcAuthSources: {
[oidcPayload.iss]: [oidcPayload.aud],
},
};
console.log("Setting org policy", oidcAuthSourcesPolicy);
await org.setPolicy([oidcAuthSourcesPolicy]);
最后,将OIDC令牌换成会话.
const oidcSessionResp = await cs.CubeSignerClient.createOidcSession(
cubesigner.env,
org.id, // org id to log into
oidcToken,
["manage:mfa:*", "sign:*"] // scopes for the session
);
const oidcClient = await cs.CubeSignerClient.create(oidcSessionResp.data());
资讯 关于如何使用 SDK 创建 CubeSigner 的全部细节 账户,然后交换有效的 OIDC 令牌 立方体Signer 会话,检查 OIDC 示例。 。 。 .
为用户设置 TOTP
要管理一个用户 我们需要一个与该用户绑定的会话 。 这个
无论该用户是CubeSigner还是第三方的本地人
OIDC用户. 为此,我们将在本节中使用
以前创建的 oidcCubeSigner 举个例子.
为了建立TOTP,我们首先叫 resetTotpStart 启动方法
TOTP重设程序.
console.log(`Setting up TOTP for user ${me.email}`);
let totpResetResp = await oidcClient.resetTotp();
如果用户已经配置了TOTP(或任何其他形式的MFA),
此响应需要多要素认证 。 这样的话,
例如,电话 totpApprove 并提供现有
TOTP 继续:
import { authenticator } from "otplib"; // npm install [email protected]
let totpSecret = process.env["CS_USER_TOTP_SECRET"]!;
if (totpResetResp.requiresMfa()) {
console.log("Resetting TOTP requires MFA");
const code = authenticator.generate(totpSecret);
totpResetResp = await totpResetResp.totpApprove(oidcClient, code);
assert(!totpResetResp.requiresMfa());
console.log("MFA approved using existing TOTP");
}
反应包含一个TOTP挑战,即一个新的TOTP 标准形式的配置 TOTP 乌尔。 。 。 . 从该url,我们可以生成一个 QR 代码来呈现给用户,或者 创建自动测试的认证器.
const totpChallenge = totpResetResp.data();
assert(totpChallenge.url);
为了完成挑战,我们必须呼吁 resetTotpComplete 财务报告和审定财务报表
从挑战中提供匹配 TOTP 配置的 TOTP 代码 :
totpSecret = new URL(totpChallenge.totp_url).searchParams.get("secret");
assert(totpSecret);
await totpChallenge.answer(authenticator.generate(totpSecret));
TOTP 配置完成后, 我们可以加倍检查验证器
通过调用生成正确的代码 verifyTotp
console.log(`Verifying current TOTP code`);
let code = authenticator.generate(totpSecret);
await oidcClient.verifyTotp(code);
我们还可以检查用户的配置文件是否确实包括 Totp
作为所配置的外交部因素之一.
const mfa = (await oidcClient.user()).mfa;
console.log("Configured MFA types", mfa);
assert(mfa.map((m) => m.type).includes("totp"));
配置用于签名的外交部政策
我们已经讨论过分配一个 警卫 政策 键; 需要多要素 认证是另一种此类政策.
让我们更新我们 secpKey 键,需要通过
TOTP 在任何可能与该密钥签名之前:
console.log(`Require TOTP for key ${secpKey.materialId}`);
await secpKey.appendPolicy([{ RequireMfa: { count: 1, allowed_mfa_types: ["Totp"] } }]);
现在,当我们呼叫任何签署行动 secpKey,我们会
实收 202 Accepted 改为 200 Ok。反应机构包含
外交部身份证,我们可以用来获取和检查相关的外交部
要求,请看需要多少批准,什么是外交部因素
它允许,等等。 相反,因为我们知道 我们的钥匙需要TOTP,我们
可以打电话 totpApprove 并传递当前 TOTP
代码; 如果代码正确, 调用将会成功
并返回签名.
console.log(`Signing a transaction now requires TOTP`);
const oidcKey = new cs.Key(oidcClient, secpKey.cached);
let resp = await oidcKey.signEvm(eth1Request);
assert(resp.requiresMfa());
console.log(`Approving with TOTP code`);
code = authenticator.generate(totpSecret);
resp = await resp.totpApprove(oidcClient, code);
assert(!resp.requiresMfa());
console.log(resp.data());
assert(resp.data().rlp_signed_tx);
清理
完成后,可以取消签名会话并删除角色 我们创造了.
console.log("Cleaning up");
await signingClient.revokeSession();
await roleClient.revokeSession();
await role.delete();
目前不支持删除密钥 .
建造SDK
npm ci
npm run build
运行 SDK 测试
之后 登录,你可以只运行:
npm test
测试将产生一些临时键和角色(在 组织签名用户,然后签署一些信件,以及 终于清理(大部分)了.
许可证
版权(C)2022-2023 Cubist, Inc.
见 通知 用于许可证信息的文件.