forked from openpgpjs/openpgpjs
/
pkcs1.js
166 lines (154 loc) · 6.31 KB
/
pkcs1.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// 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
* @private
*/
import { getRandomBytes } from './random';
import hash 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 {Promise<Uint8Array>} Random padding.
* @async
*/
async function getPKCS1Padding(length) {
const result = new Uint8Array(length);
let count = 0;
while (count < length) {
const randomBytes = await 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 {Promise<Uint8Array>} EME-PKCS1 padded message.
* @async
*/
export async 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 = await 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); // discar 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 async function emsaEncode(algo, hashed, emLen) {
let i;
if (hashed.length !== hash.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;
}