Add support for v6 key packets

Compared to v5 keys, v6 keys contain additional length fields to aid in
parsing the key, but omit the secret key material length field.

Additionally, unencrypted v6 secret key packets don't include the count
of the optional fields, as per the updated crypto refresh. Since they
are always absent, the count is not needed.

Finally, unencrypted v6 secret keys do not include the two-byte checksum.
This commit is contained in:
Daniel Huigens 2023-03-15 18:39:19 +01:00 committed by larabr
parent 4521de2bea
commit 31c2a2575d
2 changed files with 42 additions and 16 deletions

View File

@ -106,10 +106,10 @@ class PublicKeyPacket {
*/
async read(bytes) {
let pos = 0;
// A one-octet version number (3, 4 or 5).
// A one-octet version number (4, 5 or 6).
this.version = bytes[pos++];
if (this.version === 4 || this.version === 5) {
if (this.version === 4 || this.version === 5 || this.version === 6) {
// - A four-octet number denoting the time that the key was created.
this.created = util.readDate(bytes.subarray(pos, pos + 4));
pos += 4;
@ -117,7 +117,7 @@ class PublicKeyPacket {
// - A one-octet number denoting the public-key algorithm of this key.
this.algorithm = bytes[pos++];
if (this.version === 5) {
if (this.version >= 5) {
// - A four-octet scalar octet count for the following key material.
pos += 4;
}
@ -147,7 +147,7 @@ class PublicKeyPacket {
arr.push(new Uint8Array([this.algorithm]));
const params = crypto.serializeParams(this.algorithm, this.publicParams);
if (this.version === 5) {
if (this.version >= 5) {
// A four-octet scalar octet count for the following key material
arr.push(util.writeNumber(params.length, 4));
}
@ -163,10 +163,9 @@ class PublicKeyPacket {
writeForHash(version) {
const bytes = this.writePublicKey();
if (version === 5) {
return util.concatUint8Array([new Uint8Array([0x9A]), util.writeNumber(bytes.length, 4), bytes]);
}
return util.concatUint8Array([new Uint8Array([0x99]), util.writeNumber(bytes.length, 2), bytes]);
const versionOctet = 0x95 + version;
const lengthOctets = version >= 5 ? 4 : 2;
return util.concatUint8Array([new Uint8Array([versionOctet]), util.writeNumber(bytes.length, lengthOctets), bytes]);
}
/**
@ -201,7 +200,7 @@ class PublicKeyPacket {
await this.computeFingerprint();
this.keyID = new KeyID();
if (this.version === 5) {
if (this.version >= 5) {
this.keyID.read(this.fingerprint.subarray(0, 8));
} else if (this.version === 4) {
this.keyID.read(this.fingerprint.subarray(12, 20));
@ -216,7 +215,7 @@ class PublicKeyPacket {
async computeFingerprint() {
const toHash = this.writeForHash(this.version);
if (this.version === 5) {
if (this.version >= 5) {
this.fingerprint = await crypto.hash.sha256(toHash);
} else if (this.version === 4) {
this.fingerprint = await crypto.hash.sha1(toHash);

View File

@ -100,6 +100,14 @@ class SecretKeyPacket extends PublicKeyPacket {
i++;
}
// - Only for a version 6 packet where the secret key material is
// encrypted (that is, where the previous octet is not zero), a one-
// octet scalar octet count of the cumulative length of all the
// following optional string-to-key parameter fields.
if (this.version === 6 && this.s2kUsage) {
i++;
}
try {
// - [Optional] If string-to-key usage octet was 255, 254, or 253, a
// one-octet symmetric encryption algorithm.
@ -112,6 +120,12 @@ class SecretKeyPacket extends PublicKeyPacket {
this.aead = bytes[i++];
}
// - [Optional] Only for a version 6 packet, and if string-to-key usage
// octet was 255, 254, or 253, an one-octet count of the following field.
if (this.version === 6) {
i++;
}
// - [Optional] If string-to-key usage octet was 255, 254, or 253, a
// string-to-key specifier. The length of the string-to-key
// specifier is implied by its type, as described above.
@ -157,9 +171,14 @@ class SecretKeyPacket extends PublicKeyPacket {
this.isEncrypted = !!this.s2kUsage;
if (!this.isEncrypted) {
const cleartext = this.keyMaterial.subarray(0, -2);
if (!util.equalsUint8Array(util.writeChecksum(cleartext), this.keyMaterial.subarray(-2))) {
throw new Error('Key checksum mismatch');
let cleartext;
if (this.version === 6) {
cleartext = this.keyMaterial;
} else {
cleartext = this.keyMaterial.subarray(0, -2);
if (!util.equalsUint8Array(util.writeChecksum(cleartext), this.keyMaterial.subarray(-2))) {
throw new Error('Key checksum mismatch');
}
}
try {
const { privateParams } = crypto.parsePrivateKeyParams(this.algorithm, cleartext, this.publicParams);
@ -200,10 +219,18 @@ class SecretKeyPacket extends PublicKeyPacket {
optionalFieldsArr.push(this.aead);
}
const s2k = this.s2k.write();
// - [Optional] Only for a version 6 packet, and if string-to-key usage
// octet was 255, 254, or 253, an one-octet count of the following field.
if (this.version === 6) {
optionalFieldsArr.push(s2k.length);
}
// - [Optional] If string-to-key usage octet was 255, 254, or 253, a
// string-to-key specifier. The length of the string-to-key
// specifier is implied by its type, as described above.
optionalFieldsArr.push(...this.s2k.write());
optionalFieldsArr.push(...s2k);
}
// - [Optional] If secret data is encrypted (string-to-key usage octet
@ -213,7 +240,7 @@ class SecretKeyPacket extends PublicKeyPacket {
optionalFieldsArr.push(...this.iv);
}
if (this.version === 5) {
if (this.version === 5 || (this.version === 6 && this.s2kUsage)) {
arr.push(new Uint8Array([optionalFieldsArr.length]));
}
arr.push(new Uint8Array(optionalFieldsArr));
@ -228,7 +255,7 @@ class SecretKeyPacket extends PublicKeyPacket {
}
arr.push(this.keyMaterial);
if (!this.s2kUsage) {
if (!this.s2kUsage && this.version !== 6) {
arr.push(util.writeChecksum(this.keyMaterial));
}
}