0

我发现这篇文章Create Google Chrome Crx file with PHP to create a .crx file vith my ZIP archive。但是当我尝试运行它时,我得到了 $signtrue = 0 的长度,但我不知道为什么......我的 .pem 文件已找到且有效,还有我的 .pub 密钥文件,但它仍然返回给我签名中的零长度...

$fp = fopen("/public_html/apps/chrome/nsii.pem", "r");
$priv_key = fread($fp, filesize($fp));
fclose($fp);

$pkeyid = openssl_get_privatekey($priv_key);
openssl_sign(file_get_contents("$name.zip"), $signature, $pkeyid, 'sha1');
openssl_free_key($pk);

# decode the public key

$key = base64_decode(file_get_contents('extension_public_key.pub'));

$fh = fopen("$name.crx", 'wb');
fwrite($fh, 'Cr24');             // extension file magic number
fwrite($fh, pack('V', 2));       // crx format version
fwrite($fh, pack('V', strlen($key)));            // public key length
fwrite($fh, pack('V', strlen($signature)));      // signature length
fwrite($fh, $key);               // public key
fwrite($fh, $signature);         // signature
fwrite($fh, file_get_contents("$name.zip")); // package contents, zipped
fclose($fh);

更新:

原代码如下:

<?php
# make a SHA1 signature using our private key
$pk = openssl_pkey_get_private(file_get_contents('/public_html/apps/chrome/nsii.pem'));
openssl_sign(file_get_contents('/public_html/apps/chrome/extGoogle.zip'), $signature, $pk, 'sha1');
openssl_free_key($pk);

# decode the public key
$key = base64_decode(file_get_contents('/public_html/apps/chrome/extension_public_key.pub'));

# .crx package format:
#
#   magic number               char(4)
#   crx format ver             byte(4)
#   pub key lenth              byte(4)
#   signature length           byte(4)
#   public key                 string
#   signature                  string
#   package contents, zipped   string
#
# see http://code.google.com/chrome/extensions/crx.html
#
$fh = fopen('extension.crx', 'wb');
fwrite($fh, 'Cr24');                             // extension file magic number
fwrite($fh, pack('V', 2));                       // crx format version
fwrite($fh, pack('V', strlen($key)));            // public key length
fwrite($fh, pack('V', strlen($signature)));      // signature length
fwrite($fh, $key);                               // public key
fwrite($fh, $signature);                         // signature
fwrite($fh, file_get_contents('/public_html/apps/chrome/extGoogle.zip')); // package contents, zipped
fclose($fh);

似乎也不能正常工作...问题是为什么?...如果我使用 CLI 编译它,一切正常...

4

1 回答 1

2

您的问题原样不会留下任何迹象,究竟出了什么问题。

但是我可以看到没有太多的错误检查。我建议 - 因为它对于编写良好的代码很常见 - 首先为返回值添加一些测试。此外,您可能需要启用错误报告并跟踪错误日志以查看一些问题。

让我们稍微回顾一下代码并添加注释。它不完整,只是一些指针。从上面的这三行开始:

$fp = fopen("/public_html/apps/chrome/nsii.pem", "r");
$priv_key = fread($fp, filesize($fp));
fclose($fp);

您已经知道(至少您以后会使用它),有方便的功能file_get_contents。它可以替换这三行,我添加了更多行以使代码更具说服力:

$privateKeyPemFile = "/public_html/apps/chrome/nsii.pem";
$privateKey = file_get_contents($privateKeyPemFile);
if (false === $privateKey) {
    throw new UnexpectedValueException(
        sprintf("Unable to read private key file %s", $privateKeyPemFile)
    );
}

接下来使用file_get_contents它实际上是验证打开文件是否成功工作。如果不是,UnexpectedValueException则会抛出一个错误消息,告诉您发生了什么。异常的名称并不重要,对于初学者来说,您可以直接抛出一个Exception并继续。

在这里抛出异常实际上会告诉消息,以防万一失败。

如您所见,您需要验证函数的返回值并检查错误情况。请参阅您在手册中使用的 PHP 函数,并查看记录了哪些返回值以及它们的含义。通常会出现错误情况,例如此处的file_get_contents.

然后你特别关心零长度$signature。让我们看看,对于每个函数,我们是否可以检查返回值并获取一些错误信息:

$keyResource = openssl_pkey_get_private($privateKey);

Theopenssl_get_privatekey实际上是 的别名openssl_pkey_get_private,所以这里已经改变了。我还重命名了变量名,这样更方便,我们有足够的空间,不需要复杂的缩写。

同样,我们在这里有一个返回值,它是false在发生错误的情况下。在非错误条件下,它是关键资源,因此我相应地命名了返回值。让我们再次添加错误检查:

if (false === $keyResource) {
    throw new UnexpectedValueException("Unable to parse and prepare private key");
}

所以到下一行,首先保持不变:

openssl_sign(file_get_contents("$name.zip"), $signature, $pkeyid, 'sha1');

file_get_contents显然,对 zip 文件没有错误检查。相反,我们需要更多的代码行来保证这个安全:

$zipFile     = "$name.zip";
$zipContents = file_get_contents($zipFile);
if (false === $zipContents) { 
    throw new ... 
}

由于这只是为了加载数据,所以openssl_sign需要采用调用:

$signatureAlgorithm = OPENSSL_ALGO_SHA1;
$result = openssl_sign($zipContents, $signature, $keyResource, $signatureAlgorithm);
if (!$result) {
    throw new ...
}

也许第一个区别是您使用了字符串'SHA1',而手册告诉我正在使用常量。也许它具有相同的值,我不能告诉你,我在这里使用了记录的常量(可能不是文档总是正确的,但是如果你不知道发生了什么,我首先建议遵循文档,然后查看)。

同样,正在检查函数的返回值是否存在错误情况。假设您的代码可能在该功能上失败了,我们只会知道它失败了,而不是出了什么问题。

PHP 手册显示有一个被调用的函数openssl_error_string可以返回 (peu-a-peu) 错误字符串。因为它一个接一个地返回,所以你需要循环来获取所有(最近的第一个),这可以通过一个公共for循环来完成:

for ($buffer = ''; $string = openssl_error_string(); $buffer .= $string, "\n");

所以我们可以将它添加到异常中。可能在某种函数中使用它也很有用,做你需要的。

if (!$result) {
    $buffer = sprintf(
        "Failed to sign zip contents (%d); Openssl error(s):\n", strlen($zipContents)
    );
    for (; $string = openssl_error_string(); $buffer .= $string, "\n");
    throw new UnexpectedValueException($buffer);
}

因此,您的代码是检查错误条件并显示不同函数/库有不同方式发出故障信号以及如何获取有关信息的地方的完美示例。

如果您以安全的方式编写代码,它具有所有前置条件和后置条件检查,这意味着您不仅要检查返回值,还要在使用值之前检查它们。一个例子是 zipfile 的文件名。在打开文件之前检查文件是否存在并且可读:

$zipFile = "$name.zip";
if (!is_readable($zipFile)) {
    throw new Exception(sprintf("Zipfile is not readable %s", $zipFile));
}
$zipContents = file_get_contents($zipFile);
...

这只是一个示例,为失败而设计(在这种情况下,它可能有点过头了,因为它file_get_contents也会告诉我们我们无法读取文件以防万一,但有时这些前置条件检查更有价值。

到目前为止的代码立即进行审查,将参数变量移到顶部:

$privateKeyPemFile  = "/public_html/apps/chrome/nsii.pem";
$zipFile            = "$name.zip";
$signatureAlgorithm = OPENSSL_ALGO_SHA1;

$privateKey = file_get_contents($privateKeyPemFile);
if (false === $privateKey) {
    throw new UnexpectedValueException(
        sprintf("Unable to read private key file %s", $privateKeyPemFile)
    );
}

$keyResource = openssl_pkey_get_private($privateKey);
if (false === $keyResource) {
    $buffer = sprintf(
        "Unable to parse and prepare private key (%d) %s; Openssl error(s):\n"
        , strlen($privateKey), $privateKeyPemFile
    );
    for (; $string = openssl_error_string(); $buffer .= $string, "\n") ;
    throw new UnexpectedValueException($buffer);
}

if (!is_readable($zipFile)) {
    throw new Exception(sprintf("Zipfile is not readable %s", $zipFile));
}
$zipContents = file_get_contents($zipFile);
if (false === $zipContents) {
    throw new UnexpectedValueException(
        sprintf("Failed to open zipfile %s", $zipFile)
    );
}

$result = openssl_sign($zipContents, $signature, $keyResource, $signatureAlgorithm);
if (!$result) {
    $buffer = sprintf(
        "Failed to sign zip contents (%d); Openssl error(s):\n"
        , strlen($zipContents)
    );
    for (; $string = openssl_error_string(); $buffer .= $string, "\n") ;
    throw new UnexpectedValueException($buffer);
}

将变量放在顶部可以让您预先查看随后代码的输入参数,并为您提供更好的概览。

在处理过程中检查返回值可以让您尽早发现问题。

读出 openssll 错误将为您提供有用的信息来解决问题。

您可以在 PHP 手册中找到几乎所有这方面的信息。此答案可能无法解决您的具体问题,但应该为您提供更好地解决问题本身以及您在代码中可能遇到的未来问题所需的工具。

它只花费你几行代码,但你的代码会更好地工作。想象一下,您将您的脚本复制到一个新的服务器上,并且情况可能有所不同,您不想盲目飞行。

调试愉快。如您所见,我只介绍了您的代码的前半部分,您应该知道我没有运行您的代码。因此,此示例是出于最佳意图完成的,但不能保证它立即适用于您的场景。但我认为我想展示的东西是可见的。如果您对其余代码应用类似的检查,您应该能够了解到底出了什么问题,这是解决方案的一半。

于 2012-09-29T22:01:59.217 回答