openpgpjs/src/crypto/pkcs1.js
larabr 2a8969b437 Internal: improve tree-shaking for crypto modules
Every submodule under the 'crypto' directory was exported-imported
even if a handful of functions where actually needed.
We now only export entire modules behind default exports if it makes
sense for readability and if the different submodules would be
imported together anyway (e.g. `cipherMode` exports are all needed
by the SEIPD class).

We've also dropped exports that are not used outside of the crypto modules,
e.g. pkcs5 helpers.
2024-11-22 14:32:39 +01:00

164 lines
6.2 KiB
JavaScript

// GPG4Browsers - An OpenPGP implementation in javascript
// Copyright (C) 2011 Recurity Labs GmbH
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3.0 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
/**
* @fileoverview Provides EME-PKCS1-v1_5 encoding and decoding and EMSA-PKCS1-v1_5 encoding function
* @see module:crypto/public_key/rsa
* @see module:crypto/public_key/elliptic/ecdh
* @see PublicKeyEncryptedSessionKeyPacket
* @module crypto/pkcs1
*/
import { getRandomBytes } from './random';
import { getHashByteLength } from './hash';
import util from '../util';
/**
* ASN1 object identifiers for hashes
* @see {@link https://tools.ietf.org/html/rfc4880#section-5.2.2}
*/
const hash_headers = [];
hash_headers[1] = [0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04,
0x10];
hash_headers[2] = [0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14];
hash_headers[3] = [0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14];
hash_headers[8] = [0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00,
0x04, 0x20];
hash_headers[9] = [0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00,
0x04, 0x30];
hash_headers[10] = [0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40];
hash_headers[11] = [0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
0x00, 0x04, 0x1C];
/**
* Create padding with secure random data
* @private
* @param {Integer} length - Length of the padding in bytes
* @returns {Uint8Array} Random padding.
*/
function getPKCS1Padding(length) {
const result = new Uint8Array(length);
let count = 0;
while (count < length) {
const randomBytes = getRandomBytes(length - count);
for (let i = 0; i < randomBytes.length; i++) {
if (randomBytes[i] !== 0) {
result[count++] = randomBytes[i];
}
}
}
return result;
}
/**
* Create a EME-PKCS1-v1_5 padded message
* @see {@link https://tools.ietf.org/html/rfc4880#section-13.1.1|RFC 4880 13.1.1}
* @param {Uint8Array} message - Message to be encoded
* @param {Integer} keyLength - The length in octets of the key modulus
* @returns {Uint8Array} EME-PKCS1 padded message.
*/
export function emeEncode(message, keyLength) {
const mLength = message.length;
// length checking
if (mLength > keyLength - 11) {
throw new Error('Message too long');
}
// Generate an octet string PS of length k - mLen - 3 consisting of
// pseudo-randomly generated nonzero octets
const PS = getPKCS1Padding(keyLength - mLength - 3);
// Concatenate PS, the message M, and other padding to form an
// encoded message EM of length k octets as EM = 0x00 || 0x02 || PS || 0x00 || M.
const encoded = new Uint8Array(keyLength);
// 0x00 byte
encoded[1] = 2;
encoded.set(PS, 2);
// 0x00 bytes
encoded.set(message, keyLength - mLength);
return encoded;
}
/**
* Decode a EME-PKCS1-v1_5 padded message
* @see {@link https://tools.ietf.org/html/rfc4880#section-13.1.2|RFC 4880 13.1.2}
* @param {Uint8Array} encoded - Encoded message bytes
* @param {Uint8Array} randomPayload - Data to return in case of decoding error (needed for constant-time processing)
* @returns {Uint8Array} decoded data or `randomPayload` (on error, if given)
* @throws {Error} on decoding failure, unless `randomPayload` is provided
*/
export function emeDecode(encoded, randomPayload) {
// encoded format: 0x00 0x02 <PS> 0x00 <payload>
let offset = 2;
let separatorNotFound = 1;
for (let j = offset; j < encoded.length; j++) {
separatorNotFound &= encoded[j] !== 0;
offset += separatorNotFound;
}
const psLen = offset - 2;
const payload = encoded.subarray(offset + 1); // discard the 0x00 separator
const isValidPadding = encoded[0] === 0 & encoded[1] === 2 & psLen >= 8 & !separatorNotFound;
if (randomPayload) {
return util.selectUint8Array(isValidPadding, payload, randomPayload);
}
if (isValidPadding) {
return payload;
}
throw new Error('Decryption error');
}
/**
* Create a EMSA-PKCS1-v1_5 padded message
* @see {@link https://tools.ietf.org/html/rfc4880#section-13.1.3|RFC 4880 13.1.3}
* @param {Integer} algo - Hash algorithm type used
* @param {Uint8Array} hashed - Message to be encoded
* @param {Integer} emLen - Intended length in octets of the encoded message
* @returns {Uint8Array} Encoded message.
*/
export function emsaEncode(algo, hashed, emLen) {
let i;
if (hashed.length !== getHashByteLength(algo)) {
throw new Error('Invalid hash length');
}
// produce an ASN.1 DER value for the hash function used.
// Let T be the full hash prefix
const hashPrefix = new Uint8Array(hash_headers[algo].length);
for (i = 0; i < hash_headers[algo].length; i++) {
hashPrefix[i] = hash_headers[algo][i];
}
// and let tLen be the length in octets prefix and hashed data
const tLen = hashPrefix.length + hashed.length;
if (emLen < tLen + 11) {
throw new Error('Intended encoded message length too short');
}
// an octet string PS consisting of emLen - tLen - 3 octets with hexadecimal value 0xFF
// The length of PS will be at least 8 octets
const PS = new Uint8Array(emLen - tLen - 3).fill(0xff);
// Concatenate PS, the hash prefix, hashed data, and other padding to form the
// encoded message EM as EM = 0x00 || 0x01 || PS || 0x00 || prefix || hashed
const EM = new Uint8Array(emLen);
EM[1] = 0x01;
EM.set(PS, 2);
EM.set(hashPrefix, emLen - tLen);
EM.set(hashed, emLen - hashed.length);
return EM;
}