ONVIF WS-UsernameToken 密码验证
ONVIF WS-UsernameToken password validation
我正在尝试发送一条 ONVIF PTZ soap 消息以获取摄像机的状态作为简单测试。我也在努力保持这种纯粹JavaScript。我不能使用 Node.js 因为应用程序的其余部分是用不同的语言编写的,我需要它作为客户端。我尝试做的测试之一是复制 ONVIF TM 应用程序程序员指南中的结果。我可以发送 soap 消息以从 SoapUI 获取状态,但 SoapUI 不使用 WS-UsernameToken。
这是一个简单的 HTML 文件:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<!-- This folder is for asking the question of how to access a module from JQuery -->
<head>
<title>My Test Page</title>
<!-- sha.js is from jsSHA library (https://github.com/Caligatio/jsSHA) -->
<script src="./crypto/sha1.js"></script>
<script src="./soap.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
</head>
<body>
My page.
<h1>Camera Status:</h1>
<textarea class="statusArea" rows="20" cols="40" style="border:none;">
</textarea>
<script>
$(document).ready(function() {
testSoap();
});
</script>
</body>
</html>
这是一个 JavaScript 文件:
const testPW = "testPassword";
const textHash = new jsSHA( "SHA-1", "TEXT");
const PasswordType = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest";
const WSSE = 'xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"';
const WSU = 'xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"';
const testData = {
nonce: 'LKqI6G/AikKCQrN0zqZFlg==',
date: '2010-09-16T07:50:45Z',
password: 'userpassword',
result: 'tuOSpGlFlIXsozq4HFNeeGeFLEI='
};
const pwDigestFormula = (nonce_, date_, pw_) => {
let temp = nonce_ + date_ + pw_;
textHash.update(temp);
return textHash.getHash("B64");
}
const getNonce = (length = 24) => {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for(var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
const getIsoTimestamp = () => {
let d = (new Date()).toISOString();
console.log(d);
return d;
};
const getPasswordDigest = (password_) => {
let result = {
passwordType: PasswordType,
nonce: getNonce(),
created: getIsoTimestamp(),
digestPassword: null
};
result.digestPassword = pwDigestFormula(atob(result['nonce']), result['created'], password_);
return result;
}
const TEST_ONVIF_PTZ_SERVICE_URL = "http://###.###.###.###/onvif/ptz";
const getObjectTypeName = (object_) => {
return (object_?.constructor?.name ?? null);
};
/*
Parts of this class were from
*/
class SoapMessageObj {
#mediaProfile = 'test!';
commands = {
SECURE_HEADER: (username_, password_, nonce_, isoTimestamp_) =>
`<soap:Header>
<wsse:Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>${username_}</wsse:Username>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">${nonce_}</wsse:Nonce>
<wsu:Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">${isoTimestamp_}</wsu:Created>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">${password_}</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>`,
STATUS: (profileToken_ = 'media_profile1', header_ = '<soap:Header/>', attributes_ = null) => {
if (null!== attributes_) {
attributes_ = ` ${attributes_.join(' ')}`;
} else {
attributes_ = '';
}
return `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:wsdl="http://www.onvif.org/ver20/ptz/wsdl" ${attributes_}>
${header_}
<soap:Body>
<wsdl:GetStatus>
<wsdl:ProfileToken>${profileToken_}</wsdl:ProfileToken>
</wsdl:GetStatus>
</soap:Body>
</soap:Envelope>`
}
};
xmlSerializer = new XMLSerializer();
// String containing the soap message
#soapMessage = null;
// URL object
#url = null;
constructor(soapUrl_) {
let objectType = getObjectTypeName(soapUrl_);
switch(objectType) {
case 'String':
this.#url = new URL(soapUrl_);
break;
case 'URL':
this.#url = soapUrl_;
break;
default:
throw new Error(`Error: unknown object in SoapMessageObj call: ${objectType}`);
}
};
/*
* Getters and Setters
*/
get soapMessage() {
return this.#soapMessage;
};
set soapMessage(value) {
this.#soapMessage = value;
};
get url() {
return this.#url;
};
set url(url_) {
this.#url = url_;
};
get mediaProfile() {
return this.#mediaProfile;
}
set mediaProfile(mediaProfile_) {
this.#mediaProfile = mediaProfile_;
}
/*
Default processing for Success
*/
async processSuccess(data_, status_, req_) {
let dataType = getObjectTypeName(data_);
console.log('Successfully Sent command');
console.debug( `SUCCESS. Status: ${status_}` );
console.debug('Data object: ' + dataType);
console.debug('req object: ' + getObjectTypeName(req_));
this.response = data_;
if (dataType === "XMLDocument") {
console.debug(this.xmlSerializer.serializeToString(data_));
} else {
for (let o in data_) {
console.debug(`${o}: ${data_[o]}`);
}
}
};
/*
Default processing for failure.
*/
async processError(data_, status_, req_) {
console.debug( `ERROR. Status: ${status_}` );
let dataType = getObjectTypeName(data_);
console.debug('Data object: ' + dataType);
if (dataType === "XMLDocument") {
console.debug(this.xmlSerializer.serializeToString(data_));
} else {
dataType = getObjectTypeName(data_.responseXML);
if (dataType === "XMLDocument" ) {
this.response = data_.responseXML;
console.clear();
console.debug('responseXML property object: ' + dataType);
console.debug(this.xmlSerializer.serializeToString(data_.responseXML));
} else {
this.response = data_;
for (let o in data_) {
console.debug(`${o}: ${data_[o]}`);
}
}
}
};
/*
Pass in JavaScript SoapMessageObj object
The bind is needed to insure the right class/object for the "this" variable.
*/
async sendSoapMessage(soapMessage_, success_ = this.processSuccess.bind(this), failure_ = this.processError.bind(this), context_ = this) {
jQuery.support.cors = true;
$.ajax({
type: "POST",
url: context_.url.href,
crossDomain: true,
processData: false,
data: soapMessage_,
success: success_,
error: failure_
});
};
};
/*
Test function
*/
function testSoap() {
//try to replicate the example from ONVIF_WG-APG-Application_Programmers_Guide-1.pdf
let test = btoa(pwDigestFormula( atob(testData.nonce), testData.date, testData.password ) )
console.debug(`atob(btoa): ${test} testData equal: ${test==testData.result}`);
test = atob(pwDigestFormula( btoa(testData.nonce), testData.date, testData.password ) );
console.debug(`atob(btoa): ${test} testData equal: ${test==testData.result}`);
};
更新 2022 年 3 月 9 日
从 testSoap 中删除了额外的代码。
我将此张贴在这里,以便任何其他寻找答案的人都能得到它。我通过谷歌搜索、来自同事的 link 以及反复试验找到了答案。我能够使用两个 JavaScript 代码文件复制示例。为了方便起见,我把它们合并成一个。
/**
* base64.js
* Original author: Chris Veness
* Repository: https://gist.github.com/chrisveness/e121cffb51481bd1c142
* License: MIT (According to a comment).
*
* 03/09/2022 JLM Updated to ES6 and use strict.
*/
/**
* Encode string into Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648].
* As per RFC 4648, no newlines are added.
*
* Characters in str must be within ISO-8859-1 with Unicode code point <= 256.
*
* Can be achieved JavaScript with btoa(), but this approach may be useful in other languages.
*
* @param {string} str_ ASCII/ISO-8859-1 string to be encoded as base-64.
* @returns {string} Base64-encoded string.
*/
"use strict";
const B64_CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
//function base64Encode(str) {
const base64Encode = (str_) => {
if (/([^\u0000-\u00ff])/.test(str_)) throw Error("String must be ASCII");
let b64 = B64_CHARS;
let o1,
o2,
o3,
bits,
h1,
h2,
h3,
h4,
e = [],
pad = "",
c;
c = str_.length % 3; // pad string to length of multiple of 3
if (c > 0) {
while (c++ < 3) {
pad += "=";
str_ += "[=10=]";
}
}
// note: doing padding here saves us doing special-case packing for trailing 1 or 2 chars
for (c = 0; c < str_.length; c += 3) {
// pack three octets into four hexets
o1 = str_.charCodeAt(c);
o2 = str_.charCodeAt(c + 1);
o3 = str_.charCodeAt(c + 2);
bits = (o1 << 16) | (o2 << 8) | o3;
h1 = (bits >> 18) & 0x3f;
h2 = (bits >> 12) & 0x3f;
h3 = (bits >> 6) & 0x3f;
h4 = bits & 0x3f;
// use hextets to index into code string
e[c / 3] =
b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
}
str_ = e.join(""); // use Array.join() for better performance than repeated string appends
// replace 'A's from padded nulls with '='s
str_ = str_.slice(0, str_.length - pad.length) + pad;
return str_;
};
/**
* Decode string from Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648].
* As per RFC 4648, newlines are not catered for.
*
* Can be achieved JavaScript with atob(), but this approach may be useful in other languages.
*
* @param {string} str_ Base64-encoded string.
* @returns {string} Decoded ASCII/ISO-8859-1 string.
*/
//function Base64Decode(str) {
const base64Decode = (str_) => {
if (!/^[a-z0-9+/]+={0,2}$/i.test(str_) || str_.length % 4 != 0)
throw Error("Not base64 string");
let b64 = B64_CHARS;
let o1,
o2,
o3,
h1,
h2,
h3,
h4,
bits,
d = [];
for (let c = 0; c < str_.length; c += 4) {
// unpack four hexets into three octets
h1 = b64.indexOf(str_.charAt(c));
h2 = b64.indexOf(str_.charAt(c + 1));
h3 = b64.indexOf(str_.charAt(c + 2));
h4 = b64.indexOf(str_.charAt(c + 3));
bits = (h1 << 18) | (h2 << 12) | (h3 << 6) | h4;
o1 = (bits >>> 16) & 0xff;
o2 = (bits >>> 8) & 0xff;
o3 = bits & 0xff;
d[c / 4] = String.fromCharCode(o1, o2, o3);
// check for padding
if (h4 == 0x40) d[c / 4] = String.fromCharCode(o1, o2);
if (h3 == 0x40) d[c / 4] = String.fromCharCode(o1);
}
str_ = d.join(""); // use Array.join() for better performance than repeated string appends
return str_;
};
/*************************************************************************************
* wsse.js - Generate WSSE authentication header in JavaScript
*/
//
// wsse.js - Generate WSSE authentication header in JavaScript
// (C) 2005 Victor R. Ruiz <victor*sixapart.com> - http://rvr.typepad.com/
//
// Parts:
// SHA-1 library (C) 2000-2002 Paul Johnston - BSD license
// ISO 8601 function (C) 2000 JF Walker All Rights
// Base64 function (C) aardwulf systems - Creative Commons
//
// Example call:
//
// let w = wsseHeader(Username, Password);
// alert('X-WSSE: ' + w);
//
// Changelog:
// 2005.07.21 - Release 1.0
//
/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS PUB 180-1
* Version 2.1a Copyright Paul Johnston 2000 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for details.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
let hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
let b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */
let chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
const VALID_CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
const hex_sha1 = (s_) => {
return binb2hex(core_sha1(str2binb(s_), s_.length * chrsz));
};
const b64_sha1 = (s_) => {
return binb2b64(core_sha1(str2binb(s_), s_.length * chrsz));
};
const str_sha1 = (s_) => {
return binb2str(core_sha1(str2binb(s_), s_.length * chrsz));
};
const hex_hmac_sha1 = (key_, data_) => {
return binb2hex(core_hmac_sha1(key_, data_));
};
const b64_hmac_sha1 = (key_, data_) => {
return binb2b64(core_hmac_sha1(key_, data_));
};
const str_hmac_sha1 = (key_, data_) => {
return binb2str(core_hmac_sha1(key_, data_));
};
/*
* Perform a simple self-test to see if the VM is working
*/
function sha1_vm_test() {
return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
}
/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function core_sha1(x_, len_) {
/* append padding */
x_[len_ >> 5] |= 0x80 << (24 - (len_ % 32));
x_[(((len_ + 64) >> 9) << 4) + 15] = len_;
let w = Array(80);
let a = 1732584193;
let b = -271733879;
let c = -1732584194;
let d = 271733878;
let e = -1009589776;
for (let i = 0; i < x_.length; i += 16) {
let olda = a;
let oldb = b;
let oldc = c;
let oldd = d;
let olde = e;
for (let j = 0; j < 80; j++) {
if (j < 16) w[j] = x_[i + j];
else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
let t = safe_add(
safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
safe_add(safe_add(e, w[j]), sha1_kt(j))
);
e = d;
d = c;
c = rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return Array(a, b, c, d, e);
}
/*
* Perform the appropriate triplet combination function for the current
* iteration
*/
function sha1_ft(t_, b_, c_, d_) {
if (t_ < 20) return (b_ & c_) | (~b_ & d_);
if (t_ < 40) return b_ ^ c_ ^ d_;
if (t_ < 60) return (b_ & c_) | (b_ & d_) | (c_ & d_);
return b_ ^ c_ ^ d_;
}
/*
* Determine the appropriate additive constant for the current iteration
*/
function sha1_kt(t_) {
return t_ < 20
? 1518500249
: t_ < 40
? 1859775393
: t_ < 60
? -1894007588
: -899497514;
}
/*
* Calculate the HMAC-SHA1 of a key and some data
*/
function core_hmac_sha1(key_, data_) {
let bkey = str2binb(key_);
if (bkey.length > 16) bkey = core_sha1(bkey, key_.length * chrsz);
let ipad = Array(16),
opad = Array(16);
for (let i = 0; i < 16; i++) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5c5c5c5c;
}
let hash = core_sha1(
ipad.concat(str2binb(data_)),
512 + data.length * chrsz
);
return core_sha1(opad.concat(hash), 512 + 160);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x_, y_) {
let lsw = (x_ & 0xffff) + (y_ & 0xffff);
let msw = (x_ >> 16) + (y_ >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function rol(num_, cnt_) {
return (num_ << cnt_) | (num_ >>> (32 - cnt_));
}
/*
* Convert an 8-bit or 16-bit string to an array of big-endian words
* In 8-bit function, characters >255 have their hi-byte silently ignored.
*/
function str2binb(str_) {
let bin = Array();
let mask = (1 << chrsz) - 1;
for (let i = 0; i < str_.length * chrsz; i += chrsz)
bin[i >> 5] |=
(str_.charCodeAt(i / chrsz) & mask) << (32 - chrsz - (i % 32));
return bin;
}
/*
* Convert an array of big-endian words to a string
*/
function binb2str(bin_) {
let str = "";
let mask = (1 << chrsz) - 1;
for (let i = 0; i < bin_.length * 32; i += chrsz)
str += String.fromCharCode(
(bin_[i >> 5] >>> (32 - chrsz - (i % 32))) & mask
);
return str;
}
/*
* Convert an array of big-endian words to a hex string.
*/
function binb2hex(binarray_) {
let hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
let str = "";
for (let i = 0; i < binarray_.length * 4; i++) {
str +=
hex_tab.charAt(
(binarray_[i >> 2] >> ((3 - (i % 4)) * 8 + 4)) & 0xf
) +
hex_tab.charAt((binarray_[i >> 2] >> ((3 - (i % 4)) * 8)) & 0xf);
}
return str;
}
/*
* Convert an array of big-endian words to a base-64 string
*/
function binb2b64(binarray_) {
// let tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let tab = VALID_CHARS;
let str = "";
for (let i = 0; i < binarray_.length * 4; i += 3) {
let triplet =
(((binarray_[i >> 2] >> (8 * (3 - (i % 4)))) & 0xff) << 16) |
(((binarray_[(i + 1) >> 2] >> (8 * (3 - ((i + 1) % 4)))) & 0xff) <<
8) |
((binarray_[(i + 2) >> 2] >> (8 * (3 - ((i + 2) % 4)))) & 0xff);
for (let j = 0; j < 4; j++) {
if (i * 8 + j * 6 > binarray_.length * 32) str += b64pad;
else str += tab.charAt((triplet >> (6 * (3 - j))) & 0x3f);
}
}
return str;
}
// aardwulf systems
// This work is licensed under a Creative Commons License.
// http://www.aardwulf.com/tutor/base64/
function encode64(input_) {
let keyStr = `${VALID_CHARS}=`;
/*
let keyStr = "ABCDEFGHIJKLMNOP" +
"QRSTUVWXYZabcdef" +
"ghijklmnopqrstuv" +
"wxyz0123456789+/" +
"=";
*/
let output = "";
let chr1,
chr2,
chr3 = "";
let enc1,
enc2,
enc3,
enc4 = "";
let i = 0;
do {
chr1 = input_.charCodeAt(i++);
chr2 = input_.charCodeAt(i++);
chr3 = input_.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output =
output +
keyStr.charAt(enc1) +
keyStr.charAt(enc2) +
keyStr.charAt(enc3) +
keyStr.charAt(enc4);
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
} while (i < input_.length);
return output;
}
// TITLE
// TempersFewGit v 2.1 (ISO 8601 Time/Date script)
//
// OBJECTIVE
// Javascript script to detect the time zone where a browser
// is and display the date and time in accordance with the
// ISO 8601 standard.
//
// AUTHOR
// John Walker
// http://321WebLiftOff.net
// jfwalker@ureach.com
//
// ENCOMIUM
// Thanks to Stephen Pugh for his help.
//
// CREATED
// 2000-09-15T09:42:53+01:00
//
// UPDATED
// 2022-03-11 JLM Updated to ES6 and to use less strings.
//
// REFERENCES
// For more about ISO 8601 see:
// http://www.w3.org/TR/NOTE-datetime
// http://www.cl.cam.ac.uk/~mgk25/iso-time.html
//
// COPYRIGHT
// This script is Copyright 2000 JF Walker All Rights
// Reserved but may be freely used provided this colophon is
// included in full.
//
function isodatetime() {
let today = new Date();
let year = today.getFullYear();
if (year < 2000) {
// this should not be needed now
// Y2K Fix, Isaac Powell
year = year + 1900; // http://onyx.idbsu.edu/~ipowell
}
let month = today.getMonth() + 1;
let day = today.getDate();
let hour = today.getHours();
let hourUTC = today.getUTCHours();
let diff = hour - hourUTC;
if (diff > 12) diff -= 24; // Fix the problem for town with real negative diff
if (diff <= -12) diff += 24; // Fix the problem for town with real positive diff
let hourdifference = Math.abs(diff);
let minute = today.getMinutes();
let minuteUTC = today.getUTCMinutes();
let minutedifference;
let second = today.getSeconds();
let timezone;
if (minute != minuteUTC && minuteUTC < 30 && diff < 0) {
hourdifference--;
}
if (minute != minuteUTC && minuteUTC > 30 && diff > 0) {
hourdifference--;
}
minutedifference = (minute != minuteUTC)? ":30" : ":00";
timezone = `${diff < 0 ? "-" : "+"}${(hourdifference < 10)?"0":""}${hourdifference}${minutedifference}`;
if (month <= 9) month = `0${month}`; //"0" + month;
if (day <= 9) day = `0${day}`; //"0" + day;
if (hour <= 9) hour = `0${hour}`; //"0" + hour;
if (minute <= 9) minute = `0${minute}`; //"0" + minute;
if (second <= 9) second = `0${second}`; //"0" + second;
let time = `${year}-${month}-${day}T${hour}:${minute}:${second}${timezone}`;
return time;
}
// (C) 2005 Victor R. Ruiz <victor*sixapart.com>
// Code to generate WSSE authentication header
//
// http://www.sixapart.com/pronet/docs/typepad_atom_api
//
// X-WSSE: UsernameToken Username="name", PasswordDigest="digest", Created="timestamp", Nonce="nonce"
//
// * Username- The username that the user enters (the TypePad username).
// * Nonce. A secure token generated anew for each HTTP request.
// * Created. The ISO-8601 timestamp marking when Nonce was created.
// * PasswordDigest. A SHA-1 digest of the Nonce, Created timestamp, and the password
// that the user supplies, base64-encoded. In other words, this should be calculated
// as: base64(sha1(Nonce . Created . Password))
//
function wsse(password_) {
let passwordDigest, nonce, created;
let r = new Array();
// Nonce = b64_sha1(isodatetime() + 'There is more than words');
nonce = b64_sha1(`${isodatetime()}There is more than words`);
let nonceEncoded = encode64(nonce);
created = isodatetime();
passwordDigest = b64_sha1(nonce + created + password_);
r[0] = nonceEncoded;
r[1] = created;
r[2] = passwordDigest;
return r;
}
function wsseHeader(username_, password_) {
let w = wsse(password_);
// let header = 'UsernameToken Username="' + Username + '", PasswordDigest="' + w[2] + '", Created="' + w[1] + '", Nonce="' + w[0] + '"';
let header = `UsernameToken Username="${username_}", PasswordDigest="${w[2]}", Created="${w[1]}", Nonce="${w[0]}"`;
return header;
}
这里是测试代码函数:
/*
Test function
*/
function testSoap() {
let n1 = base64Decode(testData.nonce);
let result = wsseTesting(testData.password, n1, testData.date);
console.debug(`password digest: ${result[2]} equal: ${result[2]==testData.result}`);
enter code here
}
希望这对其他人也有帮助。
我正在尝试发送一条 ONVIF PTZ soap 消息以获取摄像机的状态作为简单测试。我也在努力保持这种纯粹JavaScript。我不能使用 Node.js 因为应用程序的其余部分是用不同的语言编写的,我需要它作为客户端。我尝试做的测试之一是复制 ONVIF TM 应用程序程序员指南中的结果。我可以发送 soap 消息以从 SoapUI 获取状态,但 SoapUI 不使用 WS-UsernameToken。
这是一个简单的 HTML 文件:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<!-- This folder is for asking the question of how to access a module from JQuery -->
<head>
<title>My Test Page</title>
<!-- sha.js is from jsSHA library (https://github.com/Caligatio/jsSHA) -->
<script src="./crypto/sha1.js"></script>
<script src="./soap.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
</head>
<body>
My page.
<h1>Camera Status:</h1>
<textarea class="statusArea" rows="20" cols="40" style="border:none;">
</textarea>
<script>
$(document).ready(function() {
testSoap();
});
</script>
</body>
</html>
这是一个 JavaScript 文件:
const testPW = "testPassword";
const textHash = new jsSHA( "SHA-1", "TEXT");
const PasswordType = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest";
const WSSE = 'xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"';
const WSU = 'xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"';
const testData = {
nonce: 'LKqI6G/AikKCQrN0zqZFlg==',
date: '2010-09-16T07:50:45Z',
password: 'userpassword',
result: 'tuOSpGlFlIXsozq4HFNeeGeFLEI='
};
const pwDigestFormula = (nonce_, date_, pw_) => {
let temp = nonce_ + date_ + pw_;
textHash.update(temp);
return textHash.getHash("B64");
}
const getNonce = (length = 24) => {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for(var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
const getIsoTimestamp = () => {
let d = (new Date()).toISOString();
console.log(d);
return d;
};
const getPasswordDigest = (password_) => {
let result = {
passwordType: PasswordType,
nonce: getNonce(),
created: getIsoTimestamp(),
digestPassword: null
};
result.digestPassword = pwDigestFormula(atob(result['nonce']), result['created'], password_);
return result;
}
const TEST_ONVIF_PTZ_SERVICE_URL = "http://###.###.###.###/onvif/ptz";
const getObjectTypeName = (object_) => {
return (object_?.constructor?.name ?? null);
};
/*
Parts of this class were from
*/
class SoapMessageObj {
#mediaProfile = 'test!';
commands = {
SECURE_HEADER: (username_, password_, nonce_, isoTimestamp_) =>
`<soap:Header>
<wsse:Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>${username_}</wsse:Username>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">${nonce_}</wsse:Nonce>
<wsu:Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">${isoTimestamp_}</wsu:Created>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">${password_}</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>`,
STATUS: (profileToken_ = 'media_profile1', header_ = '<soap:Header/>', attributes_ = null) => {
if (null!== attributes_) {
attributes_ = ` ${attributes_.join(' ')}`;
} else {
attributes_ = '';
}
return `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:wsdl="http://www.onvif.org/ver20/ptz/wsdl" ${attributes_}>
${header_}
<soap:Body>
<wsdl:GetStatus>
<wsdl:ProfileToken>${profileToken_}</wsdl:ProfileToken>
</wsdl:GetStatus>
</soap:Body>
</soap:Envelope>`
}
};
xmlSerializer = new XMLSerializer();
// String containing the soap message
#soapMessage = null;
// URL object
#url = null;
constructor(soapUrl_) {
let objectType = getObjectTypeName(soapUrl_);
switch(objectType) {
case 'String':
this.#url = new URL(soapUrl_);
break;
case 'URL':
this.#url = soapUrl_;
break;
default:
throw new Error(`Error: unknown object in SoapMessageObj call: ${objectType}`);
}
};
/*
* Getters and Setters
*/
get soapMessage() {
return this.#soapMessage;
};
set soapMessage(value) {
this.#soapMessage = value;
};
get url() {
return this.#url;
};
set url(url_) {
this.#url = url_;
};
get mediaProfile() {
return this.#mediaProfile;
}
set mediaProfile(mediaProfile_) {
this.#mediaProfile = mediaProfile_;
}
/*
Default processing for Success
*/
async processSuccess(data_, status_, req_) {
let dataType = getObjectTypeName(data_);
console.log('Successfully Sent command');
console.debug( `SUCCESS. Status: ${status_}` );
console.debug('Data object: ' + dataType);
console.debug('req object: ' + getObjectTypeName(req_));
this.response = data_;
if (dataType === "XMLDocument") {
console.debug(this.xmlSerializer.serializeToString(data_));
} else {
for (let o in data_) {
console.debug(`${o}: ${data_[o]}`);
}
}
};
/*
Default processing for failure.
*/
async processError(data_, status_, req_) {
console.debug( `ERROR. Status: ${status_}` );
let dataType = getObjectTypeName(data_);
console.debug('Data object: ' + dataType);
if (dataType === "XMLDocument") {
console.debug(this.xmlSerializer.serializeToString(data_));
} else {
dataType = getObjectTypeName(data_.responseXML);
if (dataType === "XMLDocument" ) {
this.response = data_.responseXML;
console.clear();
console.debug('responseXML property object: ' + dataType);
console.debug(this.xmlSerializer.serializeToString(data_.responseXML));
} else {
this.response = data_;
for (let o in data_) {
console.debug(`${o}: ${data_[o]}`);
}
}
}
};
/*
Pass in JavaScript SoapMessageObj object
The bind is needed to insure the right class/object for the "this" variable.
*/
async sendSoapMessage(soapMessage_, success_ = this.processSuccess.bind(this), failure_ = this.processError.bind(this), context_ = this) {
jQuery.support.cors = true;
$.ajax({
type: "POST",
url: context_.url.href,
crossDomain: true,
processData: false,
data: soapMessage_,
success: success_,
error: failure_
});
};
};
/*
Test function
*/
function testSoap() {
//try to replicate the example from ONVIF_WG-APG-Application_Programmers_Guide-1.pdf
let test = btoa(pwDigestFormula( atob(testData.nonce), testData.date, testData.password ) )
console.debug(`atob(btoa): ${test} testData equal: ${test==testData.result}`);
test = atob(pwDigestFormula( btoa(testData.nonce), testData.date, testData.password ) );
console.debug(`atob(btoa): ${test} testData equal: ${test==testData.result}`);
};
更新 2022 年 3 月 9 日 从 testSoap 中删除了额外的代码。
我将此张贴在这里,以便任何其他寻找答案的人都能得到它。我通过谷歌搜索、来自同事的 link 以及反复试验找到了答案。我能够使用两个 JavaScript 代码文件复制示例。为了方便起见,我把它们合并成一个。
/**
* base64.js
* Original author: Chris Veness
* Repository: https://gist.github.com/chrisveness/e121cffb51481bd1c142
* License: MIT (According to a comment).
*
* 03/09/2022 JLM Updated to ES6 and use strict.
*/
/**
* Encode string into Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648].
* As per RFC 4648, no newlines are added.
*
* Characters in str must be within ISO-8859-1 with Unicode code point <= 256.
*
* Can be achieved JavaScript with btoa(), but this approach may be useful in other languages.
*
* @param {string} str_ ASCII/ISO-8859-1 string to be encoded as base-64.
* @returns {string} Base64-encoded string.
*/
"use strict";
const B64_CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
//function base64Encode(str) {
const base64Encode = (str_) => {
if (/([^\u0000-\u00ff])/.test(str_)) throw Error("String must be ASCII");
let b64 = B64_CHARS;
let o1,
o2,
o3,
bits,
h1,
h2,
h3,
h4,
e = [],
pad = "",
c;
c = str_.length % 3; // pad string to length of multiple of 3
if (c > 0) {
while (c++ < 3) {
pad += "=";
str_ += "[=10=]";
}
}
// note: doing padding here saves us doing special-case packing for trailing 1 or 2 chars
for (c = 0; c < str_.length; c += 3) {
// pack three octets into four hexets
o1 = str_.charCodeAt(c);
o2 = str_.charCodeAt(c + 1);
o3 = str_.charCodeAt(c + 2);
bits = (o1 << 16) | (o2 << 8) | o3;
h1 = (bits >> 18) & 0x3f;
h2 = (bits >> 12) & 0x3f;
h3 = (bits >> 6) & 0x3f;
h4 = bits & 0x3f;
// use hextets to index into code string
e[c / 3] =
b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
}
str_ = e.join(""); // use Array.join() for better performance than repeated string appends
// replace 'A's from padded nulls with '='s
str_ = str_.slice(0, str_.length - pad.length) + pad;
return str_;
};
/**
* Decode string from Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648].
* As per RFC 4648, newlines are not catered for.
*
* Can be achieved JavaScript with atob(), but this approach may be useful in other languages.
*
* @param {string} str_ Base64-encoded string.
* @returns {string} Decoded ASCII/ISO-8859-1 string.
*/
//function Base64Decode(str) {
const base64Decode = (str_) => {
if (!/^[a-z0-9+/]+={0,2}$/i.test(str_) || str_.length % 4 != 0)
throw Error("Not base64 string");
let b64 = B64_CHARS;
let o1,
o2,
o3,
h1,
h2,
h3,
h4,
bits,
d = [];
for (let c = 0; c < str_.length; c += 4) {
// unpack four hexets into three octets
h1 = b64.indexOf(str_.charAt(c));
h2 = b64.indexOf(str_.charAt(c + 1));
h3 = b64.indexOf(str_.charAt(c + 2));
h4 = b64.indexOf(str_.charAt(c + 3));
bits = (h1 << 18) | (h2 << 12) | (h3 << 6) | h4;
o1 = (bits >>> 16) & 0xff;
o2 = (bits >>> 8) & 0xff;
o3 = bits & 0xff;
d[c / 4] = String.fromCharCode(o1, o2, o3);
// check for padding
if (h4 == 0x40) d[c / 4] = String.fromCharCode(o1, o2);
if (h3 == 0x40) d[c / 4] = String.fromCharCode(o1);
}
str_ = d.join(""); // use Array.join() for better performance than repeated string appends
return str_;
};
/*************************************************************************************
* wsse.js - Generate WSSE authentication header in JavaScript
*/
//
// wsse.js - Generate WSSE authentication header in JavaScript
// (C) 2005 Victor R. Ruiz <victor*sixapart.com> - http://rvr.typepad.com/
//
// Parts:
// SHA-1 library (C) 2000-2002 Paul Johnston - BSD license
// ISO 8601 function (C) 2000 JF Walker All Rights
// Base64 function (C) aardwulf systems - Creative Commons
//
// Example call:
//
// let w = wsseHeader(Username, Password);
// alert('X-WSSE: ' + w);
//
// Changelog:
// 2005.07.21 - Release 1.0
//
/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS PUB 180-1
* Version 2.1a Copyright Paul Johnston 2000 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for details.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
let hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
let b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */
let chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
const VALID_CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
const hex_sha1 = (s_) => {
return binb2hex(core_sha1(str2binb(s_), s_.length * chrsz));
};
const b64_sha1 = (s_) => {
return binb2b64(core_sha1(str2binb(s_), s_.length * chrsz));
};
const str_sha1 = (s_) => {
return binb2str(core_sha1(str2binb(s_), s_.length * chrsz));
};
const hex_hmac_sha1 = (key_, data_) => {
return binb2hex(core_hmac_sha1(key_, data_));
};
const b64_hmac_sha1 = (key_, data_) => {
return binb2b64(core_hmac_sha1(key_, data_));
};
const str_hmac_sha1 = (key_, data_) => {
return binb2str(core_hmac_sha1(key_, data_));
};
/*
* Perform a simple self-test to see if the VM is working
*/
function sha1_vm_test() {
return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
}
/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function core_sha1(x_, len_) {
/* append padding */
x_[len_ >> 5] |= 0x80 << (24 - (len_ % 32));
x_[(((len_ + 64) >> 9) << 4) + 15] = len_;
let w = Array(80);
let a = 1732584193;
let b = -271733879;
let c = -1732584194;
let d = 271733878;
let e = -1009589776;
for (let i = 0; i < x_.length; i += 16) {
let olda = a;
let oldb = b;
let oldc = c;
let oldd = d;
let olde = e;
for (let j = 0; j < 80; j++) {
if (j < 16) w[j] = x_[i + j];
else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
let t = safe_add(
safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
safe_add(safe_add(e, w[j]), sha1_kt(j))
);
e = d;
d = c;
c = rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return Array(a, b, c, d, e);
}
/*
* Perform the appropriate triplet combination function for the current
* iteration
*/
function sha1_ft(t_, b_, c_, d_) {
if (t_ < 20) return (b_ & c_) | (~b_ & d_);
if (t_ < 40) return b_ ^ c_ ^ d_;
if (t_ < 60) return (b_ & c_) | (b_ & d_) | (c_ & d_);
return b_ ^ c_ ^ d_;
}
/*
* Determine the appropriate additive constant for the current iteration
*/
function sha1_kt(t_) {
return t_ < 20
? 1518500249
: t_ < 40
? 1859775393
: t_ < 60
? -1894007588
: -899497514;
}
/*
* Calculate the HMAC-SHA1 of a key and some data
*/
function core_hmac_sha1(key_, data_) {
let bkey = str2binb(key_);
if (bkey.length > 16) bkey = core_sha1(bkey, key_.length * chrsz);
let ipad = Array(16),
opad = Array(16);
for (let i = 0; i < 16; i++) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5c5c5c5c;
}
let hash = core_sha1(
ipad.concat(str2binb(data_)),
512 + data.length * chrsz
);
return core_sha1(opad.concat(hash), 512 + 160);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x_, y_) {
let lsw = (x_ & 0xffff) + (y_ & 0xffff);
let msw = (x_ >> 16) + (y_ >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function rol(num_, cnt_) {
return (num_ << cnt_) | (num_ >>> (32 - cnt_));
}
/*
* Convert an 8-bit or 16-bit string to an array of big-endian words
* In 8-bit function, characters >255 have their hi-byte silently ignored.
*/
function str2binb(str_) {
let bin = Array();
let mask = (1 << chrsz) - 1;
for (let i = 0; i < str_.length * chrsz; i += chrsz)
bin[i >> 5] |=
(str_.charCodeAt(i / chrsz) & mask) << (32 - chrsz - (i % 32));
return bin;
}
/*
* Convert an array of big-endian words to a string
*/
function binb2str(bin_) {
let str = "";
let mask = (1 << chrsz) - 1;
for (let i = 0; i < bin_.length * 32; i += chrsz)
str += String.fromCharCode(
(bin_[i >> 5] >>> (32 - chrsz - (i % 32))) & mask
);
return str;
}
/*
* Convert an array of big-endian words to a hex string.
*/
function binb2hex(binarray_) {
let hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
let str = "";
for (let i = 0; i < binarray_.length * 4; i++) {
str +=
hex_tab.charAt(
(binarray_[i >> 2] >> ((3 - (i % 4)) * 8 + 4)) & 0xf
) +
hex_tab.charAt((binarray_[i >> 2] >> ((3 - (i % 4)) * 8)) & 0xf);
}
return str;
}
/*
* Convert an array of big-endian words to a base-64 string
*/
function binb2b64(binarray_) {
// let tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let tab = VALID_CHARS;
let str = "";
for (let i = 0; i < binarray_.length * 4; i += 3) {
let triplet =
(((binarray_[i >> 2] >> (8 * (3 - (i % 4)))) & 0xff) << 16) |
(((binarray_[(i + 1) >> 2] >> (8 * (3 - ((i + 1) % 4)))) & 0xff) <<
8) |
((binarray_[(i + 2) >> 2] >> (8 * (3 - ((i + 2) % 4)))) & 0xff);
for (let j = 0; j < 4; j++) {
if (i * 8 + j * 6 > binarray_.length * 32) str += b64pad;
else str += tab.charAt((triplet >> (6 * (3 - j))) & 0x3f);
}
}
return str;
}
// aardwulf systems
// This work is licensed under a Creative Commons License.
// http://www.aardwulf.com/tutor/base64/
function encode64(input_) {
let keyStr = `${VALID_CHARS}=`;
/*
let keyStr = "ABCDEFGHIJKLMNOP" +
"QRSTUVWXYZabcdef" +
"ghijklmnopqrstuv" +
"wxyz0123456789+/" +
"=";
*/
let output = "";
let chr1,
chr2,
chr3 = "";
let enc1,
enc2,
enc3,
enc4 = "";
let i = 0;
do {
chr1 = input_.charCodeAt(i++);
chr2 = input_.charCodeAt(i++);
chr3 = input_.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output =
output +
keyStr.charAt(enc1) +
keyStr.charAt(enc2) +
keyStr.charAt(enc3) +
keyStr.charAt(enc4);
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
} while (i < input_.length);
return output;
}
// TITLE
// TempersFewGit v 2.1 (ISO 8601 Time/Date script)
//
// OBJECTIVE
// Javascript script to detect the time zone where a browser
// is and display the date and time in accordance with the
// ISO 8601 standard.
//
// AUTHOR
// John Walker
// http://321WebLiftOff.net
// jfwalker@ureach.com
//
// ENCOMIUM
// Thanks to Stephen Pugh for his help.
//
// CREATED
// 2000-09-15T09:42:53+01:00
//
// UPDATED
// 2022-03-11 JLM Updated to ES6 and to use less strings.
//
// REFERENCES
// For more about ISO 8601 see:
// http://www.w3.org/TR/NOTE-datetime
// http://www.cl.cam.ac.uk/~mgk25/iso-time.html
//
// COPYRIGHT
// This script is Copyright 2000 JF Walker All Rights
// Reserved but may be freely used provided this colophon is
// included in full.
//
function isodatetime() {
let today = new Date();
let year = today.getFullYear();
if (year < 2000) {
// this should not be needed now
// Y2K Fix, Isaac Powell
year = year + 1900; // http://onyx.idbsu.edu/~ipowell
}
let month = today.getMonth() + 1;
let day = today.getDate();
let hour = today.getHours();
let hourUTC = today.getUTCHours();
let diff = hour - hourUTC;
if (diff > 12) diff -= 24; // Fix the problem for town with real negative diff
if (diff <= -12) diff += 24; // Fix the problem for town with real positive diff
let hourdifference = Math.abs(diff);
let minute = today.getMinutes();
let minuteUTC = today.getUTCMinutes();
let minutedifference;
let second = today.getSeconds();
let timezone;
if (minute != minuteUTC && minuteUTC < 30 && diff < 0) {
hourdifference--;
}
if (minute != minuteUTC && minuteUTC > 30 && diff > 0) {
hourdifference--;
}
minutedifference = (minute != minuteUTC)? ":30" : ":00";
timezone = `${diff < 0 ? "-" : "+"}${(hourdifference < 10)?"0":""}${hourdifference}${minutedifference}`;
if (month <= 9) month = `0${month}`; //"0" + month;
if (day <= 9) day = `0${day}`; //"0" + day;
if (hour <= 9) hour = `0${hour}`; //"0" + hour;
if (minute <= 9) minute = `0${minute}`; //"0" + minute;
if (second <= 9) second = `0${second}`; //"0" + second;
let time = `${year}-${month}-${day}T${hour}:${minute}:${second}${timezone}`;
return time;
}
// (C) 2005 Victor R. Ruiz <victor*sixapart.com>
// Code to generate WSSE authentication header
//
// http://www.sixapart.com/pronet/docs/typepad_atom_api
//
// X-WSSE: UsernameToken Username="name", PasswordDigest="digest", Created="timestamp", Nonce="nonce"
//
// * Username- The username that the user enters (the TypePad username).
// * Nonce. A secure token generated anew for each HTTP request.
// * Created. The ISO-8601 timestamp marking when Nonce was created.
// * PasswordDigest. A SHA-1 digest of the Nonce, Created timestamp, and the password
// that the user supplies, base64-encoded. In other words, this should be calculated
// as: base64(sha1(Nonce . Created . Password))
//
function wsse(password_) {
let passwordDigest, nonce, created;
let r = new Array();
// Nonce = b64_sha1(isodatetime() + 'There is more than words');
nonce = b64_sha1(`${isodatetime()}There is more than words`);
let nonceEncoded = encode64(nonce);
created = isodatetime();
passwordDigest = b64_sha1(nonce + created + password_);
r[0] = nonceEncoded;
r[1] = created;
r[2] = passwordDigest;
return r;
}
function wsseHeader(username_, password_) {
let w = wsse(password_);
// let header = 'UsernameToken Username="' + Username + '", PasswordDigest="' + w[2] + '", Created="' + w[1] + '", Nonce="' + w[0] + '"';
let header = `UsernameToken Username="${username_}", PasswordDigest="${w[2]}", Created="${w[1]}", Nonce="${w[0]}"`;
return header;
}
这里是测试代码函数:
/*
Test function
*/
function testSoap() {
let n1 = base64Decode(testData.nonce);
let result = wsseTesting(testData.password, n1, testData.date);
console.debug(`password digest: ${result[2]} equal: ${result[2]==testData.result}`);
enter code here
}
希望这对其他人也有帮助。