@@ -25,7 +25,7 @@ import type { PemMessage, PemHeader, Pem } from './pem'
2525import { aes } from './aes'
2626import { asn1 } from './asn1'
2727import { wrapRsaPrivateKey , privateKeyToAsn1 , privateKeyFromAsn1 } from './rsa'
28- import { createCipher } from './cipher'
28+ import { createCipher as createCipherOriginal } from './cipher'
2929import { sha1 } from './sha1'
3030import { des } from './des'
3131import { oids } from './oids'
@@ -37,6 +37,25 @@ import { bytesToHex, createBuffer, hexToBytes } from './utils'
3737import { pem } from './pem'
3838import { Buffer } from 'buffer'
3939
40+ // Error codes enum
41+ export enum PBEErrorCode {
42+ INVALID_PARAMS = 'INVALID_PARAMS' ,
43+ DECRYPTION_FAILED = 'DECRYPTION_FAILED' ,
44+ UNSUPPORTED_ALGORITHM = 'UNSUPPORTED_ALGORITHM'
45+ }
46+
47+ // Custom error class
48+ export class PBEError extends Error {
49+ constructor (
50+ message : string ,
51+ public code : PBEErrorCode ,
52+ public details ?: Record < string , unknown >
53+ ) {
54+ super ( message )
55+ this . name = 'PBEError'
56+ }
57+ }
58+
4059interface CustomError extends Error {
4160 algorithm ?: string
4261 oid ?: string
@@ -106,14 +125,28 @@ interface PBECipherInfo {
106125 ivLength : number
107126}
108127
109- interface PBEAlgorithms {
128+ interface PBEAlgorithmsMap {
110129 [ key : string ] : PBECipherInfo
111130}
112131
113- interface SHA512API {
132+ interface SHA512APIMap {
114133 [ key : string ] : MessageDigest
115134}
116135
136+ // Constants
137+ const DEFAULT_ENCRYPTION_PARAMS = {
138+ prfAlgorithm : 'hmacWithSHA1'
139+ } as const
140+
141+ // PBE algorithms configuration
142+ const pbeAlgorithms : PBEAlgorithmsMap = {
143+ 'aes128-CBC' : { cipher : 'AES-CBC' , keyLength : 16 , ivLength : 16 } ,
144+ 'aes192-CBC' : { cipher : 'AES-CBC' , keyLength : 24 , ivLength : 16 } ,
145+ 'aes256-CBC' : { cipher : 'AES-CBC' , keyLength : 32 , ivLength : 16 } ,
146+ 'des-EDE3-CBC' : { cipher : '3DES-CBC' , keyLength : 24 , ivLength : 8 } ,
147+ 'desCBC' : { cipher : 'DES-CBC' , keyLength : 8 , ivLength : 8 }
148+ }
149+
117150function convertToString ( input : ByteStringBuffer | string ) : string {
118151 if ( typeof input === 'string' )
119152 return input
@@ -367,7 +400,7 @@ export function encryptPrivateKeyInfo(obj: any, password: string, options: Encry
367400 dkLen = 8
368401 ivLen = 8
369402 encOid = oids . desCBC
370- cipherFn = ( key ) => createCipher ( 'DES-CBC' , convertToString ( key ) )
403+ cipherFn = ( key ) => createCipherOriginal ( 'DES-CBC' , convertToString ( key ) )
371404 break
372405 default :
373406 const error : CustomError = new Error ( 'Cannot encrypt private key. Unknown encryption algorithm.' )
@@ -677,90 +710,76 @@ export function encryptRsaPrivateKey(rsaKey: any, password: string, options: Enc
677710 * @return the RSA key on success, null on failure.
678711 */
679712export function decryptRsaPrivateKey ( pemKey : string , password : string ) : any {
680- let rval = null
681-
682- const msg = pem . decode ( pemKey ) [ 0 ]
713+ if ( ! pemKey || ! password ) {
714+ throw new PBEError (
715+ 'PEM key and password are required' ,
716+ PBEErrorCode . INVALID_PARAMS
717+ )
718+ }
683719
684- if ( msg . type !== 'ENCRYPTED PRIVATE KEY' && msg . type !== 'PRIVATE KEY' && msg . type !== 'RSA PRIVATE KEY' ) {
685- const error : CustomError = new Error ( 'Could not convert private key from PEM; PEM header type is not "ENCRYPTED PRIVATE KEY", "PRIVATE KEY", or "RSA PRIVATE KEY".' )
686- error . headerType = msg . type
687- throw error
720+ let obj : Asn1Object
721+ try {
722+ obj = pemToEncryptedPrivateKey ( pemKey )
723+ } catch ( e ) {
724+ throw new PBEError (
725+ 'Invalid PEM format' ,
726+ PBEErrorCode . INVALID_PARAMS ,
727+ { error : e }
728+ )
688729 }
689730
690- if ( msg . procType && msg . procType . type === 'ENCRYPTED' ) {
691- let dkLen
692- let cipherFn
693- switch ( msg . dekInfo ?. algorithm ) {
694- case 'DES-CBC' :
695- dkLen = 8
696- cipherFn = des . createDecryptionCipher
697- break
698- case 'DES-EDE3-CBC' :
699- dkLen = 24
700- cipherFn = des . createDecryptionCipher
701- break
702- case 'AES-128-CBC' :
703- dkLen = 16
704- cipherFn = aes . createDecryptionCipher
705- break
706- case 'AES-192-CBC' :
707- dkLen = 24
708- cipherFn = aes . createDecryptionCipher
709- break
710- case 'AES-256-CBC' :
711- dkLen = 32
712- cipherFn = aes . createDecryptionCipher
713- break
714- case 'RC2-40-CBC' :
715- dkLen = 5
716- cipherFn = function ( key : string ) {
717- return rc2 . createDecryptionCipher ( key , 40 )
718- }
719- break
720- case 'RC2-64-CBC' :
721- dkLen = 8
722- cipherFn = function ( key : string ) {
723- return rc2 . createDecryptionCipher ( key , 64 )
724- }
725- break
726- case 'RC2-128-CBC' :
727- dkLen = 16
728- cipherFn = function ( key : string ) {
729- return rc2 . createDecryptionCipher ( key , 128 )
730- }
731- break
732- default :
733- const error : CustomError = new Error ( `Could not decrypt private key; unsupported encryption algorithm "${ msg . dekInfo ?. algorithm } ".` )
734- error . algorithm = msg . dekInfo ?. algorithm
735- throw error
736- }
731+ // Try modern PKCS#8 first
732+ try {
733+ const params = extractEncryptionParams ( obj )
734+ const key = deriveKeyPBKDF2 ( password , {
735+ salt : params . salt ,
736+ iterationCount : params . iterationCount ,
737+ prf : DEFAULT_ENCRYPTION_PARAMS . prfAlgorithm
738+ } )
737739
738- // use OpenSSL legacy key derivation
739- const iv = hexToBytes ( msg . dekInfo . parameters ?? '' )
740- const dk = opensslDeriveBytes ( password , iv . substr ( 0 , 8 ) , dkLen , sha1 . create ( ) )
741- const cipher = cipherFn ( createBuffer ( dk ) )
742- cipher . start ( iv )
743- cipher . update ( createBuffer ( msg . body ) )
744-
745- if ( cipher . finish ( ) )
746- rval = cipher . output ?. getBytes ( )
747- else
748- return rval
749- }
750- else {
751- rval = msg . body
752- }
740+ validateKey ( key , pbeAlgorithms [ params . algorithm ] . keyLength )
741+ validateIV ( params . iv , pbeAlgorithms [ params . algorithm ] . ivLength )
753742
754- if ( msg . type === 'ENCRYPTED PRIVATE KEY' )
755- rval = decryptPrivateKeyInfo ( asn1 . fromDer ( rval ) , password )
756- else
757- // decryption already performed above
758- rval = asn1 . fromDer ( rval )
743+ const decipher = createCipher (
744+ pbeAlgorithms [ params . algorithm ] . cipher ,
745+ key
746+ )
759747
760- if ( rval !== null )
761- rval = privateKeyFromAsn1 ( rval )
748+ decipher . start ( { iv : params . iv } )
749+ decipher . update ( params . encryptedData )
762750
763- return rval
751+ if ( ! decipher . finish ( ) ) {
752+ throw new PBEError (
753+ 'Failed to decrypt private key' ,
754+ PBEErrorCode . DECRYPTION_FAILED
755+ )
756+ }
757+
758+ if ( ! decipher . output ) {
759+ throw new PBEError (
760+ 'No output from cipher' ,
761+ PBEErrorCode . DECRYPTION_FAILED
762+ )
763+ }
764+
765+ return asn1ToPrivateKey ( unwrapRsaPrivateKey ( asn1 . fromDer ( decipher . output . bytes ( ) ) ) )
766+ } catch ( e ) {
767+ if ( e instanceof PBEError && e . code === PBEErrorCode . INVALID_PARAMS ) {
768+ try {
769+ return decryptRsaPrivateKeyLegacy ( pemKey , password )
770+ } catch ( legacyError ) {
771+ throw new PBEError (
772+ 'Failed to decrypt private key (both modern and legacy formats)' ,
773+ PBEErrorCode . DECRYPTION_FAILED ,
774+ {
775+ modernError : e ,
776+ legacyError
777+ }
778+ )
779+ }
780+ }
781+ throw e
782+ }
764783}
765784
766785/**
@@ -949,23 +968,23 @@ export function getCipherForPBES2(oid: string, params: any, password: string): B
949968 switch ( oids [ oid ] ) {
950969 case 'aes128-CBC' :
951970 dkLen = 16
952- cipherFn = createCipher ( 'AES-CBC' , key )
971+ cipherFn = createCipherOriginal ( 'AES-CBC' , key )
953972 break
954973 case 'aes192-CBC' :
955974 dkLen = 24
956- cipherFn = createCipher ( 'AES-CBC' , key )
975+ cipherFn = createCipherOriginal ( 'AES-CBC' , key )
957976 break
958977 case 'aes256-CBC' :
959978 dkLen = 32
960- cipherFn = createCipher ( 'AES-CBC' , key )
979+ cipherFn = createCipherOriginal ( 'AES-CBC' , key )
961980 break
962981 case 'des-EDE3-CBC' :
963982 dkLen = 24
964- cipherFn = createCipher ( '3DES-CBC' , key )
983+ cipherFn = createCipherOriginal ( '3DES-CBC' , key )
965984 break
966985 case 'desCBC' :
967986 dkLen = 8
968- cipherFn = createCipher ( 'DES-CBC' , key )
987+ cipherFn = createCipherOriginal ( 'DES-CBC' , key )
969988 break
970989 }
971990
@@ -1213,89 +1232,73 @@ function bufferToString(buf: ByteStringBuffer): string {
12131232function createCipher ( algorithm : string , key : ByteStringBuffer | string ) : BlockCipher {
12141233 const keyBuffer = typeof key === 'string' ? stringToBuffer ( key ) : key ;
12151234 switch ( algorithm ) {
1216- case 'aes128-CBC' :
1217- return createAESCipher ( keyBuffer , '128' ) ;
1218- case 'aes192-CBC' :
1219- return createAESCipher ( keyBuffer , '192' ) ;
1220- case 'aes256-CBC' :
1221- return createAESCipher ( keyBuffer , '256' ) ;
1222- case 'des-EDE3-CBC' :
1223- return createDESCipher ( keyBuffer ) ;
1235+ case 'AES-CBC' :
1236+ return aes . createEncryptionCipher ( keyBuffer . toString ( ) , '128' ) ;
1237+ case '3DES-CBC' :
1238+ return des . createEncryptionCipher ( keyBuffer . toString ( ) , createBuffer ( getBytesSync ( 8 ) ) ) ;
1239+ case 'DES-CBC' :
1240+ return des . createEncryptionCipher ( keyBuffer . toString ( ) , createBuffer ( getBytesSync ( 8 ) ) ) ;
12241241 default :
12251242 throw new Error ( `Unsupported cipher algorithm: ${ algorithm } ` ) ;
12261243 }
12271244}
12281245
1229- // Fix type issues in decryptRsaPrivateKey function
1230- export function decryptRsaPrivateKey ( pemKey : string , password : string ) : any {
1231- if ( ! pemKey || ! password ) {
1246+ function validateKey ( key : ByteStringBuffer , expectedLength : number ) : void {
1247+ if ( key . length ( ) !== expectedLength ) {
12321248 throw new PBEError (
1233- 'PEM key and password are required' ,
1249+ `Invalid key length: expected ${ expectedLength } , got ${ key . length ( ) } ` ,
12341250 PBEErrorCode . INVALID_PARAMS
12351251 ) ;
12361252 }
1253+ }
12371254
1238- let obj ;
1239- try {
1240- obj = pemToEncryptedPrivateKey ( pemKey ) ;
1241- } catch ( e ) {
1255+ function validateIV ( iv : ByteStringBuffer , expectedLength : number ) : void {
1256+ if ( iv . length ( ) !== expectedLength ) {
12421257 throw new PBEError (
1243- 'Invalid PEM format' ,
1244- PBEErrorCode . INVALID_PARAMS ,
1245- { error : e }
1258+ `Invalid IV length: expected ${ expectedLength } , got ${ iv . length ( ) } ` ,
1259+ PBEErrorCode . INVALID_PARAMS
12461260 ) ;
12471261 }
1262+ }
12481263
1249- // Try modern PKCS#8 first
1250- try {
1251- const params = extractEncryptionParams ( obj ) ;
1252- const key = deriveKeyPBKDF2 ( password , {
1253- salt : params . salt ,
1254- iterationCount : params . iterationCount ,
1255- prf : DEFAULT_ENCRYPTION_PARAMS . prfAlgorithm
1256- } ) ;
1257-
1258- validateKey ( key , pbeAlgorithms [ params . algorithm ] . keyLength ) ;
1259- validateIV ( params . iv , pbeAlgorithms [ params . algorithm ] . ivLength ) ;
1260-
1261- const decipher = createCipher (
1262- pbeAlgorithms [ params . algorithm ] . cipher ,
1263- key
1264+ function pemToEncryptedPrivateKey ( pemKey : string ) : Asn1Object {
1265+ const msg = pem . decode ( pemKey ) [ 0 ] ;
1266+ if ( msg . type !== 'ENCRYPTED PRIVATE KEY' ) {
1267+ throw new PBEError (
1268+ 'Invalid PEM format: not an encrypted private key' ,
1269+ PBEErrorCode . INVALID_PARAMS
12641270 ) ;
1271+ }
1272+ return asn1 . fromDer ( msg . body ) ;
1273+ }
12651274
1266- decipher . start ( { iv : params . iv } ) ;
1267- decipher . update ( params . encryptedData ) ;
1268- const result = decipher . finish ( ) ;
1269-
1270- if ( ! result . success ) {
1271- throw new PBEError (
1272- 'Failed to decrypt private key' ,
1273- PBEErrorCode . DECRYPTION_FAILED
1274- ) ;
1275- }
1275+ function deriveKeyPBKDF2 (
1276+ password : string ,
1277+ options : {
1278+ salt : ByteStringBuffer
1279+ iterationCount : number
1280+ prf : string
1281+ }
1282+ ) : ByteStringBuffer {
1283+ const { salt, iterationCount, prf } = options ;
1284+ const md = prfAlgorithmToMessageDigest ( prf ) ;
1285+ return createBuffer ( pbkdf2 ( password , salt . bytes ( ) , iterationCount , 32 , md ) ) ;
1286+ }
12761287
1277- return asn1ToPrivateKey ( unwrapRsaPrivateKey ( asn1 . fromDer ( result . output ) ) ) ;
1278- } catch ( e ) {
1279- // If modern decryption fails, try legacy format
1280- if ( e instanceof PBEError && e . code === PBEErrorCode . INVALID_PARAMS ) {
1281- try {
1282- return decryptRsaPrivateKeyLegacy ( pemKey , password ) ;
1283- } catch ( legacyError ) {
1284- throw new PBEError (
1285- 'Failed to decrypt private key (both modern and legacy formats)' ,
1286- PBEErrorCode . DECRYPTION_FAILED ,
1287- {
1288- modernError : e ,
1289- legacyError
1290- }
1291- ) ;
1292- }
1293- }
1294- throw e ;
1288+ function unwrapRsaPrivateKey ( obj : Asn1Object ) : Asn1Object {
1289+ if ( obj . type === asn1 . Type . SEQUENCE ) {
1290+ return obj ;
12951291 }
1292+ throw new PBEError (
1293+ 'Invalid private key format' ,
1294+ PBEErrorCode . INVALID_PARAMS
1295+ ) ;
1296+ }
1297+
1298+ function asn1ToPrivateKey ( obj : Asn1Object ) : any {
1299+ return privateKeyFromAsn1 ( obj ) ;
12961300}
12971301
1298- // Fix type issues in extractEncryptionParams function
12991302function extractEncryptionParams ( obj : any ) : {
13001303 algorithm : string ;
13011304 salt : ByteStringBuffer ;
0 commit comments