Closed GMBrian closed 2 months ago
@singpolyma I wanted to follow up on this issue to see if there might be any updates or if there's anything I can do to help move it forward. Thanks
Both security.txt as the PGP cleartext signature use the Internet standard newline <CR><LF>
/ \r\n
.
However security.txt allows 'some' LF's (see Line Separator), however cleartext signatures are still over the <CR><LF>
:
a cleartext signature is calculated on the text using canonical
<CR><LF>
line endings
Source: RFC 4880 - OpenPGP Message Format - 7.1. Dash-Escaped Text.
So it's to be expected if openpgp-php is cleartext unaware (only OpenPGP_LiteralDataPacket
normalize($clearsign=false)
is), this will result in a different signature than a cleartext signature.
Also see:
It took me a while to get your sample working, because of another <CR><LF>
:
The line ending (i.e., the
<CR><LF>
) before the-----BEGIN PGP SIGNATURE-----
line that terminates the signed text is not considered part of the signed text.
and
An implementation SHOULD add a line break after the cleartext, but MAY omit it if the cleartext ends with a line break. This is for visual clarity.
It results in this modified example that works for me (which I placed in the example directory of openpgp-php-0.6.0):
<?php
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\RSA\Formats\Keys\PKCS1;
include_once dirname(__FILE__).'/../vendor/autoload.php';
require_once dirname(__FILE__).'/../lib/openpgp.php';
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
$name = "dummy";
$email = "security@example.org";
$passphrase = "high-entropy-passphrase";
$key_length = 2048;
// Generate a key pair
$privateKey = RSA::createKey($key_length);
$privateKeyComponents = PKCS1::load($privateKey->toString('PKCS1'));
$secretKeyPacket = new OpenPGP_SecretKeyPacket(array(
'n' => $privateKeyComponents["modulus"]->toBytes(),
'e' => $privateKeyComponents["publicExponent"]->toBytes(),
'd' => $privateKeyComponents["privateExponent"]->toBytes(),
'p' => $privateKeyComponents["primes"][1]->toBytes(),
'q' => $privateKeyComponents["primes"][2]->toBytes(),
'u' => $privateKeyComponents["coefficients"][2]->toBytes()
));
// Assemble packets for the private key
$packets = array($secretKeyPacket);
$wkey = new OpenPGP_Crypt_RSA($secretKeyPacket);
$fingerprint = $wkey->key()->fingerprint;
$key = $wkey->private_key();
$key = $key->withHash('sha256');
$keyid = substr($fingerprint, -16);
// Add a user ID packet
$uid = new OpenPGP_UserIDPacket("$name <$email>");
$packets[] = $uid;
// Add a signature packet to certify the binding between the user ID and the key
$sig = new OpenPGP_SignaturePacket(new OpenPGP_Message(array($secretKeyPacket, $uid)), 'RSA', 'SHA256');
$sig->signature_type = 0x13;
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02 | 0x04)); // Certify + sign + encrypt bits
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);
$m = $wkey->sign_key_userid(array($secretKeyPacket, $uid, $sig));
// Append the signature to the private key packets
$packets[] = $m->packets[2];
// Assemble packets for the public key
$publicPackets = array(new OpenPGP_PublicKeyPacket($secretKeyPacket));
$publicPackets[] = $uid;
$publicPackets[] = $sig;
// Encrypt the private key with a passphrase
$encryptedSecretKeyPacket = OpenPGP_Crypt_Symmetric::encryptSecretKey($passphrase, $secretKeyPacket);
// Assemble the private key message
$privateMessage = new OpenPGP_Message($packets);
$privateMessage[0] = $encryptedSecretKeyPacket;
// Enarmor the private key message
$privateEnarmorKey = OpenPGP::enarmor($privateMessage->to_bytes(), "PGP PRIVATE KEY BLOCK");
// Assemble the public key message
$publicMessage = new OpenPGP_Message($publicPackets);
// Enarmor the public key message
$publicEnarmorKey = OpenPGP::enarmor($publicMessage->to_bytes(), "PGP PUBLIC KEY BLOCK");
$string = "Contact: mailto:$email\r\nExpires: 2025-04-29T00:00:00Z\r\nPreferred-Languages: en\r\nEncryption: https://www.example.org/publickey.txt\r\nCanonical: https://www.example.org/.well-known/security.txt";
$m = $wkey->sign($string);
/* Generate clearsigned data */
$packets = $m->signatures()[0];
$file_contents = "-----BEGIN PGP SIGNED MESSAGE-----\r\nHash: SHA256\r\n\r\n";
// Output normalised data. You could convert line endings here
// or add trailing whitespace (spaces and tabs) to lines without breaking the signature
$file_contents .= preg_replace("/^-/", "- -", $packets[0]->data) . "\r\n";
$file_contents .= OpenPGP::enarmor($packets[1][0]->to_bytes(), "PGP SIGNATURE");
$parsed_pubkey = OpenPGP_Message::parse($publicEnarmorKey);
/* Parse signed message from file named "t" */
$m = OpenPGP_Message::parse($file_contents);
/* Create a verifier for the key */
$verify = new OpenPGP_Crypt_RSA($parsed_pubkey);
// We verify
$verified = (bool)$verify->verify($m);
echo var_export(array(
'signed_message' => $file_contents,
'public_key' => $publicEnarmorKey,
'private_key' => $privateEnarmorKey,
'verified' => $verified
));
Which will result in a signed security.txt:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Contact: mailto:security@example.org
Expires: 2025-04-29T00:00:00Z
Preferred-Languages: en
Encryption: https://www.example.org/publickey.txt
Canonical: https://www.example.org/.well-known/security.txt
-----BEGIN PGP SIGNATURE-----
wv8AAAEkBAABCAAY/wAAAAUCZmi0d/8AAAAJEBzceu7GBetzAABTKwf/UyuI0a7vrh8A3ZBLlCEt
L5ngWDiahiUtwNEf+G/wwylO5ERlzYfBrotHueF4N9NX/2g9QlkCtdHgII7FXni+B9Tp4svydi8K
NZx48qdqEIB9LuUGfBaZ6imm7+2WQY3BvywZU03BTCXFS1bclvFwEzOUJJqqFinbRos5ACzFrRQ5
XIritbFtyql8H616nPhd5/8p1VDo3dseVK6eq6X7AAkoEPcKqd3GQ36aEWpwPpKUXnOnGPywNDCz
pNr+nRX7XoVVhsqfkQhiMTYQKzbk0i+IeWvii0iGejSAHH3afQlKacmTcBjbLrqG9F9k/98JWP/y
F59oKa3ULQQY5bYmUQ==
=ZDI9
-----END PGP SIGNATURE-----
with the publickey.txt:
-----BEGIN PGP PUBLIC KEY BLOCK-----
xv8AAAENBGZotHcBCAC/zCEyheqrkWrqqeMjMxhuav6g1LL3KIqmcV6zRNXzdP3SSWtrM7xSRQCv
C7HjvcGXbmT75eaBUhIWi28QX6exGR/kQURQK8MJg9e1GtU2hF/lpN6wG+QJo+jIEntuFpCfNxPD
s6/KQAltdrOFPSO0VYHrl9GvWNBYKBhtMUOc8cole3CwhvupGCExlgNqgza9R6d1bj1ZrqxleJ9v
u37IVzO0A5etj+sgjeV/i6hh0X3dxNJIgKc88mLkNqrnbzGSz5KM4JYAdRA4Uf9wwHjbWBGRg3iL
KcaSMyQ+kjesNsm6jl/KmEMUOU5jzdHgmocxSWZaE7DruMl9OpcsLA4zABEBAAHN/wAAABxkdW1t
eSA8c2VjdXJpdHlAZXhhbXBsZS5vcmc+wv8AAAErBBMBCAAf/wAAAAUCZmi0d/8AAAACGwf/AAAA
CRAc3HruxgXrcwAABhIH+wYSEUHAjLkwdBs/HwttMHvifBsKEleUgqQJh7odfhPWiAeOCzYsilVG
nLRVOcP7oBGjFXPPTK5of5ydLgtB+SYd+CKD/HvssBmvgeCYCYrzZB+L2i8QZ6w79eGkLqRNl0kA
cgIlHV5DjZIlY1wCNjp72N6oCUetr43i8rSmE9Oj1pj0FzQNaXgQQ632a0ClWPn7EIcw2/EEOj7n
jpTtNmgXWJBqygraAmYfxC6E5JDhPdzKPG1ZTqG2K41owKej+spDn7LU3jV8OF/2NUN7y60J+dZ3
+9Mr9fnqapYiLAdWhHBh6sMNN7QeYW1T6hMZfUNHh8Jm+kcoN3YCpsGpTOw=
=K6ye
-----END PGP PUBLIC KEY BLOCK-----
which validates in gpg:
$ gpg --no-default-keyring --keyring tmp.gpg --import publickey.txt && gpg --keyring tmp.gpg --verify security.txt
gpg: key 1CDC7AEEC605EB73: public key "dummy <security@example.org>" imported
gpg: Total number processed: 1
gpg: imported: 1
gpg: Signature made Tue 11 Jun 2024 10:32:55 PM CEST
gpg: using RSA key 1CDC7AEEC605EB73
gpg: Good signature from "dummy <security@example.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 4BA6 5740 4364 D93F 1F87 3681 1CDC 7AEE C605 EB73
Also https://pgptool.org/ validates.
@bwbroersma I totally missed the <CR><LF>
discrepancy. Thanks for your elaborate answer. My first tests are validating just fine as well. Great help
I'm trying to clearsign a message, which works fine until I add \n linebreaks into the message. After that the signature can't be verified anymore.
Most of my function is simply taken from the keygen, clearsign and verify examples. My goal is generate and return new private and public keys and clearsign a file with a valid signature.
Contents from file $SecurityTxtAdmin->get_securitytxt_file_contents();
After clearsign
The above signature can be verified correctly with a tool such as GPG Suite or https://pgptool.org/. However if I enter any \n linebreaks into this file, it will break the signature and these tools give a bad result. Linebreaks are essential for my implementation. The public key for this file is;
After introducing linebreaks:
The above signature is not valid according to the earlier mentioned GPG tools. The public key for this file is;
Please let me know if you need more information.