我正在使用 NodeJS 20.11.1 和 Express 创建一个具有路由的 HTTPS 服务器
/cert
。这个想法是,此路由将请求将用于签署文档的客户端证书。这个想法是我们的国家(保加利亚)使用合格的数字签名。它基本上是一张智能卡,其中包含用于在政府网站上识别您的电子请求的证书。我正在为想要利用这些证书并向国家卫生研究所提交一些数据的牙医开发一个网络应用程序。所以我的想法是这样的 - 客户(医生)将他们的智能卡插入电脑,打开这条路线并提交他们的证书。
要通过此健康系统进行身份验证,我必须打开一个 URL
https://ptest-auth.his.bg/token
(测试环境),它将为我提供一个 XML 文档。我必须使用客户端的证书对其进行签名,并将签名的数据发送到相同的 URL,它将为我提供 accessToken,我可以在稍后的请求中将其用作授权标头。
所以我现在的代码是这样的:
import { SignedXml } from "xml-crypto";
const keyPath = path.join(__dirname, "..", "..", "selfsigned.key");
const certPath = path.join(__dirname, "..", "..", "selfsigned.crt");
const key = fs.readFileSync(keyPath);
const cert = fs.readFileSync(certPath);
const options = {
key: key,
cert: cert,
requestCert: true,
rejectUnauthorized: false,
};
app.get("/cert", async (req: Request, res: Response) => {
// Get the digital signature of the doctor
const clientCert = (req.socket as TLSSocket).getPeerCertificate(true).raw.toString("base64");
// build a PEM certificate
const match = clientCert.match(/.{0,64}/g);
const prefix = "-----BEGIN CERTIFICATE-----\n";
const postfix = "-----END CERTIFICATE-----";
if (!match) {
return res.status(400).json({
error: "No client certificate provided",
});
}
const pemText = prefix + match.join("\n") + postfix;
// Get the XML document
axios.get("https://ptest-auth.his.bg/token")
.then((response) => {
// This is a normal response
})
.catch((error) => {
// Doing the processing here because the API returns 401 if no certificate is provided
const data = error.response.data;
// Sign the document
const signature = new SignedXml({ privateKey: pemText });
signature.addReference({
xpath: "/*",
digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha512",
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
});
signature.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
signature.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
signature.computeSignature(data);
const signedData = signature.getSignedXml();
console.log(signedData);
});
res.status(200).json({
success: true,
message: "Client certificate",
clientCert,
});
});
https.createServer(options, app).listen(8002, () => {
console.log("listening on port 8002");
});
不幸的是,这给了我一个错误:
Error: error:1E08010C:DECODER routines::unsupported
at Sign.sign (node:internal/crypto/sig:128:29)
at D:\GitHub\dentrino-gql\node_modules\xml-crypto\lib\signature-algorithms.js:51:32
at RsaSha512.getSignature (D:\GitHub\dentrino-gql\node_modules\xml-crypto\lib\types.js:52:20)
at SignedXml.calculateSignatureValue (D:\GitHub\dentrino-gql\node_modules\xml-crypto\lib\signed-xml.js:244:42)
at SignedXml.computeSignature (D:\GitHub\dentrino-gql\node_modules\xml-crypto\lib\signed-xml.js:634:18)
at D:\GitHub\dentrino-gql\dist\microservices\app.js:118:19
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
library: 'DECODER routines',
reason: 'unsupported',
code: 'ERR_OSSL_UNSUPPORTED'
}
我已经阅读了很多这方面的内容,对于 NodeJS 中的加密,我是一个菜鸟。仍然存在的主要问号是 - 我获得的证书是否足以签署 XML?如果是这样,为什么它不起作用?
任何想法表示赞赏!
...是我获得的足以签署 XML 的证书
不。签署某些东西需要私钥,证书(仅包含公钥)是不够的。私钥必须由证书所有者保密(因此得名)。因此,在您的特定情况下,智能卡上的私钥位于客户端,签名也只能在客户端完成。
想象一下,如果您可以仅使用(公共)证书而不使用私钥进行签名,会发生什么:您可以以客户的名义签署任意文档,而客户将无法控制和了解您在他们的签名中签署的内容名字。