Skip to content

Commit fdfca96

Browse files
stevebioanu3990
authored andcommitted
MLE-24397 - fix reported issue on Linux FIPS around exception caused by default load of FIPS-forbidden MD5 digest algorithm. Incorporate the source from the abandoned www-authenticate project and fix in place.
1 parent 910b2b9 commit fdfca96

File tree

8 files changed

+437
-18
lines changed

8 files changed

+437
-18
lines changed

lib/requester.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright © 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
33
*/
44
'use strict';
5-
var createAuthInitializer = require('www-authenticate');
5+
var createAuthInitializer = require('./www-authenticate/www-authenticate');
66
var Kerberos = require('./optional.js')
77
.libraryProperty('kerberos', 'Kerberos');
88
var Multipart = require('multipart-stream');

lib/www-authenticate/md5.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
var crypto= require('crypto');
2+
3+
function md5(s) {
4+
return crypto.createHash('md5').update(s).digest('hex');
5+
}
6+
7+
module.exports= md5;

lib/www-authenticate/parsers.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
var ParseAuth= /(\w+)\s+(.*)/ // -> scheme, params
2+
, Separators= /([",=])/
3+
;
4+
5+
function parse_params(header) {
6+
// This parser will definitely fail if there is more than one challenge
7+
var tok, last_tok, _i, _len, key, value;
8+
var state= 0; //0: token,
9+
var m= header.split(Separators)
10+
for (_i = 0, _len = m.length; _i < _len; _i++) {
11+
last_tok= tok;
12+
tok = m[_i];
13+
if (!tok.length) continue;
14+
switch (state) {
15+
case 0: // token
16+
key= tok.trim();
17+
state= 1; // expect equals
18+
continue;
19+
case 1: // expect equals
20+
if ('=' != tok) return 'Equal sign was expected after '+key;
21+
state= 2;
22+
continue;
23+
case 2: // expect value
24+
if ('"' == tok) {
25+
value= '';
26+
state= 3; // expect quoted
27+
continue;
28+
}
29+
else {
30+
this.parms[key]= value= tok.trim();
31+
state= 9; // expect comma or end
32+
continue;
33+
}
34+
case 3: // handling quoted string
35+
if ('"' == tok) {
36+
state= 8; // end quoted
37+
continue;
38+
}
39+
else {
40+
value+= tok;
41+
state= 3; // continue accumulating quoted string
42+
continue;
43+
}
44+
case 8: // end quote encountered
45+
if ('"' == tok) {
46+
// double quoted
47+
value+= '"';
48+
state= 3; // back to quoted string
49+
continue;
50+
}
51+
if (',' == tok) {
52+
this.parms[key]= value;
53+
state= 0;
54+
continue;
55+
}
56+
else {
57+
return 'Unexpected token ('+tok+') after '+value+'"';
58+
}
59+
continue;
60+
case 9: // expect commma
61+
if (',' != tok) return 'Comma expected after '+value;
62+
state= 0;
63+
continue;
64+
}
65+
}
66+
switch (state) { // terminal state
67+
case 0: // Empty or ignoring terminal comma
68+
case 9: // Expecting comma or end of header
69+
return;
70+
case 8: // Last token was end quote
71+
this.parms[key]= value;
72+
return;
73+
default:
74+
return 'Unexpected end of www-authenticate value.';
75+
}
76+
}
77+
78+
function Parse_WWW_Authenticate(to_parse)
79+
{
80+
var m= to_parse.match(ParseAuth);
81+
this.scheme= m[1];
82+
this.parms= {};
83+
var err= this.parse_params(m[2]);
84+
if (err) {
85+
this.scheme= '';
86+
this.parms= {};
87+
this.err= err;
88+
}
89+
}
90+
91+
function Parse_Authentication_Info(to_parse)
92+
{
93+
this.scheme= 'Digest';
94+
this.parms= {};
95+
var err= this.parse_params(to_parse);
96+
if (err) {
97+
this.scheme= '';
98+
this.parms= {};
99+
this.err= err;
100+
}
101+
}
102+
103+
Parse_Authentication_Info.prototype.parse_params= parse_params;
104+
Parse_WWW_Authenticate.prototype.parse_params= parse_params;
105+
106+
module.exports = {
107+
WWW_Authenticate: Parse_WWW_Authenticate,
108+
Authentication_Info: Parse_Authentication_Info
109+
};
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
var md5= require('./md5');
2+
3+
/*
4+
* Hide the password. Uses the password to form authorization strings,
5+
* but provides no interface for exporting it.
6+
*/
7+
function user_credentials(username,password,options) {
8+
if (username.is_user_credentials &&
9+
typeof username.basic === 'function' &&
10+
typeof username.digest === 'function'
11+
) {
12+
return username;
13+
}
14+
15+
var basic_string= options && options.hide_basic ?
16+
''
17+
:
18+
(!password && password !== '' ?
19+
Buffer.from(username, "ascii").toString("base64")
20+
:
21+
Buffer.from(username+':'+password, "ascii").toString("base64")
22+
)
23+
function Credentials()
24+
{
25+
this.username= username;
26+
}
27+
Credentials.prototype.basic= function()
28+
{
29+
return basic_string;
30+
}
31+
Credentials.prototype.digest= function(realm)
32+
{
33+
return !password && password !== '' ?
34+
md5(username+':'+realm)
35+
:
36+
md5(username+':'+realm+':'+password)
37+
}
38+
Credentials.prototype.is_user_credentials= function()
39+
{
40+
return true;
41+
}
42+
43+
return new Credentials;
44+
}
45+
46+
module.exports= user_credentials;
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*
2+
* www-authenticate
3+
* https://github.com/randymized/www-authenticate
4+
*
5+
* Copyright (c) 2013 Randy McLaughlin
6+
* Licensed under the MIT license.
7+
*/
8+
9+
/*
10+
* Copyright © 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
11+
*/
12+
13+
'use strict';
14+
15+
var crypto= require('crypto')
16+
, parsers= require('./parsers')
17+
, md5= require('./md5')
18+
, user_credentials= require('./user-credentials')
19+
, basic_challenge= {
20+
statusCode: 401,
21+
headers: {
22+
'www-authenticate': 'Basic realm="sample"'
23+
}
24+
}
25+
;
26+
27+
function hex8(num)
28+
{
29+
return ("00000000" + num.toString(16)).slice(-8);
30+
}
31+
32+
var www_authenticator = function(username,password,options)
33+
{
34+
if (2 == arguments.length && toString.call(password) != '[object String]') {
35+
options= password;
36+
password= null;
37+
}
38+
var credentials= user_credentials(username,password)
39+
var cnonce;
40+
if (options) {
41+
if (toString.call(options.cnonce) == '[object String]')
42+
cnonce= options.cnonce;
43+
}
44+
if (cnonce === void 0) cnonce= crypto.pseudoRandomBytes(8).toString('hex');
45+
46+
/**
47+
* @typedef {Object} Authenticator
48+
* @property {any} [err]
49+
* @property {function(string=, string=): string} [authorize]
50+
* @property {any} [parms]
51+
* @property {string} [cnonce]
52+
*/
53+
54+
/**
55+
* Parses the WWW-Authenticate header.
56+
* @param {string} www_authenticate
57+
* @returns {Authenticator}
58+
*/
59+
var parse_header= function(www_authenticate)
60+
{
61+
function Authenticator()
62+
{
63+
function note_error(err)
64+
{
65+
this.err= err
66+
}
67+
var nc= 0;
68+
69+
var parsed= new parsers.WWW_Authenticate(www_authenticate);
70+
if (parsed.err) return note_error(parsed.err);
71+
var auth_parms= this.parms= parsed.parms;
72+
this.cnonce= cnonce;
73+
74+
switch(parsed.scheme) {
75+
case 'Basic':
76+
var auth_string= 'Basic '+credentials.basic();
77+
this.authorize= function() {
78+
return auth_string;
79+
};
80+
return;
81+
case 'Digest':
82+
var realm= auth_parms.realm;
83+
if (!realm) {
84+
return note_error("Realm not found in www-authenticate header.");
85+
}
86+
87+
var ha1=
88+
credentials.digest(realm);
89+
var nonce= auth_parms.nonce;
90+
if (!nonce) {
91+
return note_error("Nonce not found in www-authenticate header.");
92+
}
93+
94+
var fixed= 'Digest username="'+credentials.username+'",'+
95+
' realm="'+realm+'",'+
96+
' nonce="'+nonce+'",';
97+
var qop= auth_parms.qop;
98+
if (!qop) {
99+
this.authorize= function(method,digestURI) {
100+
var ha2= md5(method+':'+digestURI);
101+
return fixed+
102+
' uri="'+digestURI+'",'+
103+
' response="'+md5(ha1+':'+nonce+':'+ha2)+'",';
104+
};
105+
return;
106+
}
107+
else {
108+
var qopa= qop.split(',');
109+
var q, x, _i, _len;
110+
for (_i = 0, _len = qopa.length; _i < _len; _i++) {
111+
if ('auth' === qopa[_i]) {
112+
var opaque= auth_parms.opaque;
113+
var algorithm= auth_parms.algorithm;
114+
if (algorithm) {
115+
fixed+= ' algorithm="'+algorithm+'",';
116+
}
117+
else {
118+
algorithm= 'MD5';
119+
}
120+
var a1= 'MD5-sess' == algorithm ?
121+
md5(ha1+':'+nonce+':'+cnonce)
122+
:
123+
ha1;
124+
this.authorize= function(method,digestURI) {
125+
var ha2= md5(method+':'+digestURI);
126+
nc= nc+1;
127+
var hexed_nc= hex8(nc);
128+
var s= fixed+
129+
' uri="'+digestURI+'",'+
130+
' qop=auth,'+
131+
' nc='+hexed_nc+','+
132+
' cnonce="'+cnonce+'",'+
133+
' response="'+md5(a1+':'+nonce+':'+hexed_nc+':'+cnonce+':auth:'+ha2)+'"';
134+
if (opaque) {
135+
s+= ', opaque="'+opaque+'"';
136+
}
137+
return s;
138+
};
139+
return;
140+
}
141+
return note_error('Server does not accept any supported quality of protection techniques.');
142+
}
143+
}
144+
break;
145+
default:
146+
return note_error("Unknown scheme");
147+
}
148+
}
149+
150+
return new Authenticator();
151+
};
152+
153+
parse_header.authenticator= new HigherLevel(credentials,options); // deprecated
154+
return parse_header;
155+
};
156+
157+
158+
function HigherLevel(credentials,options)
159+
{
160+
this.credentials= credentials
161+
this.options= options
162+
if (options && options.sendImmediately) {
163+
this.sendImmediately= true;
164+
}
165+
}
166+
HigherLevel.prototype.get_challenge= function(request) {
167+
if (401 == request.statusCode && 'www-authenticate' in request.headers) {
168+
if (!this.parse_header) {
169+
this.parse_header= www_authenticator(this.credentials,this.options)
170+
}
171+
this.challenge= this.parse_header(request.headers['www-authenticate'])
172+
return this.challenge.err;
173+
}
174+
}
175+
HigherLevel.prototype._challenge= function() {
176+
if (!this.challenge) {
177+
if (this.sendImmediately) {
178+
// simulate receipt of a basic challenge
179+
this.get_challenge(basic_challenge)
180+
return this.challenge
181+
}
182+
else return; // simply won't produce an 'Authorization' header
183+
}
184+
return this.challenge;
185+
}
186+
HigherLevel.prototype.authentication_string= function(method,digestURI) {
187+
var challenge= this._challenge();
188+
if (!challenge) return; // simply won't produce an 'Authorization' header
189+
if (challenge.err) return challenge.err;
190+
return challenge.authorize(method,digestURI);
191+
}
192+
HigherLevel.prototype.authenticate_headers= function(headers,method,digestURI) {
193+
var challenge= this._challenge();
194+
if (!challenge) return; // simply won't produce an 'Authorization' header
195+
if (challenge.err) return challenge.err;
196+
headers.authorization= challenge.authorize(method,digestURI);
197+
}
198+
HigherLevel.prototype.authenticate_request_options= function(request_options) {
199+
var challenge= this._challenge();
200+
if (!challenge) return; // simply won't produce an 'Authorization' header
201+
if (challenge.err) return challenge.err;
202+
if (!request_options.headers) request_options.headers= {};
203+
request_options.headers.authorization= challenge.authorize(request_options.method,request_options.path);
204+
}
205+
206+
module.exports = www_authenticator;
207+
module.exports.parsers= parsers;
208+
module.exports.user_credentials= user_credentials;
209+
module.exports.basic_challenge= basic_challenge;
210+
module.exports.authenticator= function(username,password,options)
211+
{
212+
if (2 == arguments.length && toString.call(password) != '[object String]') {
213+
options= password;
214+
password= null;
215+
}
216+
var credentials= user_credentials(username,password)
217+
return new HigherLevel(credentials,options);
218+
}

0 commit comments

Comments
 (0)