Skip to content

Commit 7322a0f

Browse files
committed
chore: wip
1 parent efd614e commit 7322a0f

2 files changed

Lines changed: 157 additions & 72 deletions

File tree

src/pkcs1.ts

Lines changed: 97 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,55 @@
4545
*/
4646

4747
import { sha1 } from './sha1'
48+
import { ByteStringBuffer } from './utils'
49+
50+
// Extended Error interface for PKCS1 specific errors
51+
interface PKCS1Error extends Error {
52+
length?: number
53+
maxLength?: number
54+
seedLength?: number
55+
digestLength?: number
56+
expectedLength?: number
57+
}
58+
59+
// Type definitions for key and options
60+
interface RSAKey {
61+
n: {
62+
bitLength: () => number
63+
}
64+
}
65+
66+
interface MessageDigest {
67+
start: () => void
68+
update: (msg: string, encoding?: string) => void
69+
digest: () => ByteStringBuffer
70+
digestLength: number
71+
}
72+
73+
interface PKCS1Options {
74+
label?: string
75+
seed?: string
76+
md?: MessageDigest
77+
mgf1?: {
78+
md?: MessageDigest
79+
}
80+
}
81+
82+
// Utility functions that were previously from forge
83+
function getRandomBytes(count: number): string {
84+
const bytes = new Uint8Array(count)
85+
crypto.getRandomValues(bytes)
86+
return Array.from(bytes).map(b => String.fromCharCode(b)).join('')
87+
}
88+
89+
function xorBytes(a: string, b: string, length: number): string {
90+
let result = ''
91+
for (let i = 0; i < length; i++) {
92+
result += String.fromCharCode(a.charCodeAt(i) ^ b.charCodeAt(i))
93+
}
94+
return result
95+
}
96+
4897
/**
4998
* Encode the given RSAES-OAEP message (M) using key, with optional label (L)
5099
* and seed.
@@ -63,25 +112,24 @@ import { sha1 } from './sha1'
63112
*
64113
* @return the encoded message bytes.
65114
*/
66-
export function encode_rsa_oaep(key: any, message: any, options: any): string {
115+
export function encode_rsa_oaep(key: RSAKey, message: string, options: PKCS1Options | string): string {
67116
// parse arguments
68-
let label
69-
let seed
70-
let md
71-
let mgf1Md
117+
let label: string | undefined
118+
let seed: string | undefined
119+
let md: MessageDigest | undefined
120+
let mgf1Md: MessageDigest | undefined
72121

73122
// legacy args (label, seed, md)
74123
if (typeof options === 'string') {
75124
label = options
76-
seed = arguments[3] || undefined
77-
md = arguments[4] || undefined
125+
seed = arguments[3] as string
126+
md = arguments[4] as MessageDigest
78127
}
79128
else if (options) {
80-
label = options.label || undefined
81-
seed = options.seed || undefined
82-
md = options.md || undefined
83-
84-
if (options.mgf1 && options.mgf1.md) {
129+
label = options.label
130+
seed = options.seed
131+
md = options.md
132+
if (options.mgf1?.md) {
85133
mgf1Md = options.mgf1.md
86134
}
87135
}
@@ -103,7 +151,7 @@ export function encode_rsa_oaep(key: any, message: any, options: any): string {
103151
const keyLength = Math.ceil(key.n.bitLength() / 8)
104152
const maxLength = keyLength - 2 * md.digestLength - 2
105153
if (message.length > maxLength) {
106-
var error = new Error('RSAES-OAEP input message length is too long.')
154+
const error = new Error('RSAES-OAEP input message length is too long.') as PKCS1Error
107155
error.length = message.length
108156
error.maxLength = maxLength
109157
throw error
@@ -124,25 +172,24 @@ export function encode_rsa_oaep(key: any, message: any, options: any): string {
124172
const DB = `${lHash.getBytes() + PS}\x01${message}`
125173

126174
if (!seed) {
127-
seed = forge.random.getBytes(md.digestLength)
175+
seed = getRandomBytes(md.digestLength)
128176
}
129177
else if (seed.length !== md.digestLength) {
130-
var error = new Error('Invalid RSAES-OAEP seed. The seed length must '
131-
+ 'match the digest length.')
178+
const error = new Error('Invalid RSAES-OAEP seed. The seed length must match the digest length.') as PKCS1Error
132179
error.seedLength = seed.length
133180
error.digestLength = md.digestLength
134181
throw error
135182
}
136183

137184
const dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md)
138-
const maskedDB = forge.util.xorBytes(DB, dbMask, DB.length)
185+
const maskedDB = xorBytes(DB, dbMask, DB.length)
139186

140187
const seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md)
141-
const maskedSeed = forge.util.xorBytes(seed, seedMask, seed.length)
188+
const maskedSeed = xorBytes(seed, seedMask, seed.length)
142189

143190
// return encoded message
144191
return `\x00${maskedSeed}${maskedDB}`
145-
};
192+
}
146193

147194
/**
148195
* Decode the given RSAES-OAEP encoded message (EM) using key, with optional
@@ -161,20 +208,21 @@ export function encode_rsa_oaep(key: any, message: any, options: any): string {
161208
*
162209
* @return the decoded message bytes.
163210
*/
164-
pkcs1.decode_rsa_oaep = function (key, em, options) {
211+
export function decode_rsa_oaep(key: RSAKey, em: string, options: PKCS1Options | string): string {
165212
// parse args
166-
let label
167-
let md
168-
let mgf1Md
213+
let label: string | undefined
214+
let md: MessageDigest | undefined
215+
let mgf1Md: MessageDigest | undefined
216+
169217
// legacy args
170218
if (typeof options === 'string') {
171219
label = options
172-
md = arguments[3] || undefined
220+
md = arguments[3] as MessageDigest
173221
}
174222
else if (options) {
175-
label = options.label || undefined
176-
md = options.md || undefined
177-
if (options.mgf1 && options.mgf1.md) {
223+
label = options.label
224+
md = options.md
225+
if (options.mgf1?.md) {
178226
mgf1Md = options.mgf1.md
179227
}
180228
}
@@ -183,7 +231,7 @@ pkcs1.decode_rsa_oaep = function (key, em, options) {
183231
const keyLength = Math.ceil(key.n.bitLength() / 8)
184232

185233
if (em.length !== keyLength) {
186-
var error = new Error('RSAES-OAEP encoded message length is invalid.')
234+
const error = new Error('RSAES-OAEP encoded message length is invalid.') as PKCS1Error
187235
error.length = em.length
188236
error.expectedLength = keyLength
189237
throw error
@@ -209,6 +257,7 @@ pkcs1.decode_rsa_oaep = function (key, em, options) {
209257
if (!label) {
210258
label = ''
211259
}
260+
212261
md.update(label, 'raw')
213262
const lHash = md.digest().getBytes()
214263

@@ -218,36 +267,31 @@ pkcs1.decode_rsa_oaep = function (key, em, options) {
218267
const maskedDB = em.substring(1 + md.digestLength)
219268

220269
const seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md)
221-
const seed = forge.util.xorBytes(maskedSeed, seedMask, maskedSeed.length)
270+
const seed = xorBytes(maskedSeed, seedMask, maskedSeed.length)
222271

223272
const dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md)
224-
const db = forge.util.xorBytes(maskedDB, dbMask, maskedDB.length)
273+
const db = xorBytes(maskedDB, dbMask, maskedDB.length)
225274

226275
const lHashPrime = db.substring(0, md.digestLength)
227276

228277
// constant time check that all values match what is expected
229-
var error = (y !== '\x00')
278+
let error = 0
279+
280+
// constant time check y is 0
281+
error |= y.charCodeAt(0)
230282

231283
// constant time check lHash vs lHashPrime
232284
for (let i = 0; i < md.digestLength; ++i) {
233-
error |= (lHash.charAt(i) !== lHashPrime.charAt(i))
285+
error |= lHash.charCodeAt(i) ^ lHashPrime.charCodeAt(i)
234286
}
235287

236-
// "constant time" find the 0x1 byte separating the padding (zeros) from the
237-
// message
238-
// TODO: It must be possible to do this in a better/smarter way?
288+
// "constant time" find the 0x1 byte separating the padding (zeros) from the message
239289
let in_ps = 1
240290
let index = md.digestLength
241291
for (let j = md.digestLength; j < db.length; j++) {
242292
const code = db.charCodeAt(j)
243-
244293
const is_0 = (code & 0x1) ^ 0x1
245-
246-
// non-zero if not 0 or 1 in the ps section
247-
const error_mask = in_ps ? 0xFFFE : 0x0000
248-
error |= (code & error_mask)
249-
250-
// latch in_ps to zero after we find 0x1
294+
error |= in_ps & (code & 0xFFFE)
251295
in_ps = in_ps & is_0
252296
index += in_ps
253297
}
@@ -259,7 +303,10 @@ pkcs1.decode_rsa_oaep = function (key, em, options) {
259303
return db.substring(index + 1)
260304
}
261305

262-
function rsa_mgf1(seed, maskLength, hash) {
306+
/**
307+
* MGF1 using the given hash function to generate a mask of the specified length.
308+
*/
309+
function rsa_mgf1(seed: string, maskLength: number, hash: MessageDigest): string {
263310
// default to SHA-1 message digest
264311
if (!hash) {
265312
hash = sha1.create()
@@ -280,7 +327,13 @@ function rsa_mgf1(seed, maskLength, hash) {
280327
return t.substring(0, maskLength)
281328
}
282329

283-
export const pkcs1 = {
330+
export interface PKCS1 {
331+
encode_rsa_oaep: (key: RSAKey, message: string, options: PKCS1Options | string) => string
332+
decode_rsa_oaep: (key: RSAKey, em: string, options: PKCS1Options | string) => string
333+
rsa_mgf1: (seed: string, maskLength: number, hash: MessageDigest) => string
334+
}
335+
336+
export const pkcs1: PKCS1 = {
284337
encode_rsa_oaep,
285338
decode_rsa_oaep,
286339
rsa_mgf1,

0 commit comments

Comments
 (0)