Add support for v6 signatures

Compared to v5 signatures, v6 signatures include a salt, and the
subpacket lengths are increased from 2 to 4 bytes.
This commit is contained in:
Daniel Huigens 2023-03-15 19:28:52 +01:00 committed by larabr
parent 8816bd7541
commit a5f1ab8a1c

View File

@ -60,6 +60,7 @@ class SignaturePacket {
this.signatureData = null; this.signatureData = null;
this.unhashedSubpackets = []; this.unhashedSubpackets = [];
this.signedHashValue = null; this.signedHashValue = null;
this.salt = null;
this.created = null; this.created = null;
this.signatureExpirationTime = null; this.signatureExpirationTime = null;
@ -110,7 +111,7 @@ class SignaturePacket {
let i = 0; let i = 0;
this.version = bytes[i++]; this.version = bytes[i++];
if (this.version !== 4 && this.version !== 5) { if (this.version !== 4 && this.version !== 5 && this.version !== 6) {
throw new UnsupportedError(`Version ${this.version} of the signature packet is unsupported.`); throw new UnsupportedError(`Version ${this.version} of the signature packet is unsupported.`);
} }
@ -139,6 +140,20 @@ class SignaturePacket {
this.signedHashValue = bytes.subarray(i, i + 2); this.signedHashValue = bytes.subarray(i, i + 2);
i += 2; i += 2;
// Only for v6 signatures, a variable-length field containing:
if (this.version === 6) {
// A one-octet salt size. The value MUST match the value defined
// for the hash algorithm as specified in Table 23 (Hash algorithm registry).
const saltLength = bytes[i++];
if (saltLength !== saltLengthForHash(this.hashAlgorithm)) {
throw new Error('Unexpected salt size for the hash algorithm');
}
// The salt; a random value value of the specified size.
this.salt = bytes.subarray(i, i + saltLength);
i += saltLength;
}
this.params = crypto.signature.parseSignatureParams(this.publicKeyAlgorithm, bytes.subarray(i, bytes.length)); this.params = crypto.signature.parseSignatureParams(this.publicKeyAlgorithm, bytes.subarray(i, bytes.length));
} }
@ -159,6 +174,10 @@ class SignaturePacket {
arr.push(this.signatureData); arr.push(this.signatureData);
arr.push(this.writeUnhashedSubPackets()); arr.push(this.writeUnhashedSubPackets());
arr.push(this.signedHashValue); arr.push(this.signedHashValue);
if (this.version === 6) {
arr.push(new Uint8Array([this.salt.length]));
arr.push(this.salt);
}
arr.push(this.writeParams()); arr.push(this.writeParams());
return util.concat(arr); return util.concat(arr);
} }
@ -173,18 +192,15 @@ class SignaturePacket {
* @async * @async
*/ */
async sign(key, data, date = new Date(), detached = false) { async sign(key, data, date = new Date(), detached = false) {
if (key.version === 5) { this.version = key.version;
this.version = 5;
} else {
this.version = 4;
}
const arr = [new Uint8Array([this.version, this.signatureType, this.publicKeyAlgorithm, this.hashAlgorithm])];
this.created = util.normalizeDate(date); this.created = util.normalizeDate(date);
this.issuerKeyVersion = key.version; this.issuerKeyVersion = key.version;
this.issuerFingerprint = key.getFingerprintBytes(); this.issuerFingerprint = key.getFingerprintBytes();
this.issuerKeyID = key.getKeyID(); this.issuerKeyID = key.getKeyID();
const arr = [new Uint8Array([this.version, this.signatureType, this.publicKeyAlgorithm, this.hashAlgorithm])];
// Add hashed subpackets // Add hashed subpackets
arr.push(this.writeHashedSubPackets()); arr.push(this.writeHashedSubPackets());
@ -195,6 +211,10 @@ class SignaturePacket {
this.signatureData = util.concat(arr); this.signatureData = util.concat(arr);
if (this.version === 6) {
const saltLength = saltLengthForHash(this.hashAlgorithm);
this.salt = await crypto.random.getRandomBytes(saltLength);
}
const toHash = this.toHash(this.signatureType, data, detached); const toHash = this.toHash(this.signatureType, data, detached);
const hash = await this.hash(this.signatureType, data, toHash, detached); const hash = await this.hash(this.signatureType, data, toHash, detached);
@ -255,7 +275,7 @@ class SignaturePacket {
bytes = util.concat([bytes, this.revocationKeyFingerprint]); bytes = util.concat([bytes, this.revocationKeyFingerprint]);
arr.push(writeSubPacket(sub.revocationKey, false, bytes)); arr.push(writeSubPacket(sub.revocationKey, false, bytes));
} }
if (!this.issuerKeyID.isNull() && this.issuerKeyVersion !== 5) { if (!this.issuerKeyID.isNull() && this.issuerKeyVersion < 5) {
// If the version of [the] key is greater than 4, this subpacket // If the version of [the] key is greater than 4, this subpacket
// MUST NOT be included in the signature. // MUST NOT be included in the signature.
arr.push(writeSubPacket(sub.issuer, true, this.issuerKeyID.write())); arr.push(writeSubPacket(sub.issuer, true, this.issuerKeyID.write()));
@ -320,7 +340,7 @@ class SignaturePacket {
if (this.issuerFingerprint !== null) { if (this.issuerFingerprint !== null) {
bytes = [new Uint8Array([this.issuerKeyVersion]), this.issuerFingerprint]; bytes = [new Uint8Array([this.issuerKeyVersion]), this.issuerFingerprint];
bytes = util.concat(bytes); bytes = util.concat(bytes);
arr.push(writeSubPacket(sub.issuerFingerprint, this.version === 5, bytes)); arr.push(writeSubPacket(sub.issuerFingerprint, this.version >= 5, bytes));
} }
if (this.preferredAEADAlgorithms !== null) { if (this.preferredAEADAlgorithms !== null) {
bytes = util.stringToUint8Array(util.uint8ArrayToString(this.preferredAEADAlgorithms)); bytes = util.stringToUint8Array(util.uint8ArrayToString(this.preferredAEADAlgorithms));
@ -328,7 +348,7 @@ class SignaturePacket {
} }
const result = util.concat(arr); const result = util.concat(arr);
const length = util.writeNumber(result.length, 2); const length = util.writeNumber(result.length, this.version === 6 ? 4 : 2);
return util.concat([length, result]); return util.concat([length, result]);
} }
@ -345,7 +365,7 @@ class SignaturePacket {
}); });
const result = util.concat(arr); const result = util.concat(arr);
const length = util.writeNumber(result.length, 2); const length = util.writeNumber(result.length, this.version === 6 ? 4 : 2);
return util.concat([length, result]); return util.concat([length, result]);
} }
@ -509,7 +529,7 @@ class SignaturePacket {
// Issuer Fingerprint // Issuer Fingerprint
this.issuerKeyVersion = bytes[mypos++]; this.issuerKeyVersion = bytes[mypos++];
this.issuerFingerprint = bytes.subarray(mypos, bytes.length); this.issuerFingerprint = bytes.subarray(mypos, bytes.length);
if (this.issuerKeyVersion === 5) { if (this.issuerKeyVersion >= 5) {
this.issuerKeyID.read(this.issuerFingerprint); this.issuerKeyID.read(this.issuerFingerprint);
} else { } else {
this.issuerKeyID.read(this.issuerFingerprint.subarray(-8)); this.issuerKeyID.read(this.issuerFingerprint.subarray(-8));
@ -531,10 +551,12 @@ class SignaturePacket {
} }
readSubPackets(bytes, trusted = true, config) { readSubPackets(bytes, trusted = true, config) {
// Two-octet scalar octet count for following subpacket data. const subpacketLengthBytes = this.version === 6 ? 4 : 2;
const subpacketLength = util.readNumber(bytes.subarray(0, 2));
let i = 2; // Two-octet scalar octet count for following subpacket data.
const subpacketLength = util.readNumber(bytes.subarray(0, subpacketLengthBytes));
let i = subpacketLengthBytes;
// subpacket data set (zero or more subpackets) // subpacket data set (zero or more subpackets)
while (i < 2 + subpacketLength) { while (i < 2 + subpacketLength) {
@ -645,7 +667,7 @@ class SignaturePacket {
toHash(signatureType, data, detached = false) { toHash(signatureType, data, detached = false) {
const bytes = this.toSign(signatureType, data); const bytes = this.toSign(signatureType, data);
return util.concat([bytes, this.signatureData, this.calculateTrailer(data, detached)]); return util.concat([this.salt || new Uint8Array(), bytes, this.signatureData, this.calculateTrailer(data, detached)]);
} }
async hash(signatureType, data, toHash, detached = false) { async hash(signatureType, data, toHash, detached = false) {
@ -769,3 +791,20 @@ function writeSubPacket(type, critical, data) {
arr.push(data); arr.push(data);
return util.concat(arr); return util.concat(arr);
} }
/**
* Select the required salt length for the given hash algorithm, as per Table 23 (Hash algorithm registry) of the crypto refresh.
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#section-9.5|Crypto Refresh Section 9.5}
* @param {enums.hash} hashAlgorithm - Hash algorithm.
* @returns {Integer} Salt length.
* @private
*/
function saltLengthForHash(hashAlgorithm) {
switch (hashAlgorithm) {
case enums.hash.sha256: return 16;
case enums.hash.sha384: return 24;
case enums.hash.sha512: return 32;
case enums.hash.sha224: return 16;
default: throw new Error('Unsupported hash function for V6 signatures');
}
}