installed plugin Two Factor
version 0.7.3
This commit is contained in:
748
wp-content/plugins/two-factor/includes/Google/u2f-api.js
Normal file
748
wp-content/plugins/two-factor/includes/Google/u2f-api.js
Normal file
@ -0,0 +1,748 @@
|
||||
//Copyright 2014-2015 Google Inc. All rights reserved.
|
||||
|
||||
//Use of this source code is governed by a BSD-style
|
||||
//license that can be found in the LICENSE file or at
|
||||
//https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
/**
|
||||
* @fileoverview The U2F api.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Namespace for the U2F api.
|
||||
* @type {Object}
|
||||
*/
|
||||
var u2f = u2f || {};
|
||||
|
||||
/**
|
||||
* FIDO U2F Javascript API Version
|
||||
* @number
|
||||
*/
|
||||
var js_api_version;
|
||||
|
||||
/**
|
||||
* The U2F extension id
|
||||
* @const {string}
|
||||
*/
|
||||
// The Chrome packaged app extension ID.
|
||||
// Uncomment this if you want to deploy a server instance that uses
|
||||
// the package Chrome app and does not require installing the U2F Chrome extension.
|
||||
u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
|
||||
// The U2F Chrome extension ID.
|
||||
// Uncomment this if you want to deploy a server instance that uses
|
||||
// the U2F Chrome extension to authenticate.
|
||||
// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
|
||||
|
||||
|
||||
/**
|
||||
* Message types for messages to/from the extension
|
||||
* @const
|
||||
* @enum {string}
|
||||
*/
|
||||
u2f.MessageTypes = {
|
||||
'U2F_REGISTER_REQUEST': 'u2f_register_request',
|
||||
'U2F_REGISTER_RESPONSE': 'u2f_register_response',
|
||||
'U2F_SIGN_REQUEST': 'u2f_sign_request',
|
||||
'U2F_SIGN_RESPONSE': 'u2f_sign_response',
|
||||
'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
|
||||
'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Response status codes
|
||||
* @const
|
||||
* @enum {number}
|
||||
*/
|
||||
u2f.ErrorCodes = {
|
||||
'OK': 0,
|
||||
'OTHER_ERROR': 1,
|
||||
'BAD_REQUEST': 2,
|
||||
'CONFIGURATION_UNSUPPORTED': 3,
|
||||
'DEVICE_INELIGIBLE': 4,
|
||||
'TIMEOUT': 5
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A message for registration requests
|
||||
* @typedef {{
|
||||
* type: u2f.MessageTypes,
|
||||
* appId: ?string,
|
||||
* timeoutSeconds: ?number,
|
||||
* requestId: ?number
|
||||
* }}
|
||||
*/
|
||||
u2f.U2fRequest;
|
||||
|
||||
|
||||
/**
|
||||
* A message for registration responses
|
||||
* @typedef {{
|
||||
* type: u2f.MessageTypes,
|
||||
* responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
|
||||
* requestId: ?number
|
||||
* }}
|
||||
*/
|
||||
u2f.U2fResponse;
|
||||
|
||||
|
||||
/**
|
||||
* An error object for responses
|
||||
* @typedef {{
|
||||
* errorCode: u2f.ErrorCodes,
|
||||
* errorMessage: ?string
|
||||
* }}
|
||||
*/
|
||||
u2f.Error;
|
||||
|
||||
/**
|
||||
* Data object for a single sign request.
|
||||
* @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
|
||||
*/
|
||||
u2f.Transport;
|
||||
|
||||
|
||||
/**
|
||||
* Data object for a single sign request.
|
||||
* @typedef {Array<u2f.Transport>}
|
||||
*/
|
||||
u2f.Transports;
|
||||
|
||||
/**
|
||||
* Data object for a single sign request.
|
||||
* @typedef {{
|
||||
* version: string,
|
||||
* challenge: string,
|
||||
* keyHandle: string,
|
||||
* appId: string
|
||||
* }}
|
||||
*/
|
||||
u2f.SignRequest;
|
||||
|
||||
|
||||
/**
|
||||
* Data object for a sign response.
|
||||
* @typedef {{
|
||||
* keyHandle: string,
|
||||
* signatureData: string,
|
||||
* clientData: string
|
||||
* }}
|
||||
*/
|
||||
u2f.SignResponse;
|
||||
|
||||
|
||||
/**
|
||||
* Data object for a registration request.
|
||||
* @typedef {{
|
||||
* version: string,
|
||||
* challenge: string
|
||||
* }}
|
||||
*/
|
||||
u2f.RegisterRequest;
|
||||
|
||||
|
||||
/**
|
||||
* Data object for a registration response.
|
||||
* @typedef {{
|
||||
* version: string,
|
||||
* keyHandle: string,
|
||||
* transports: Transports,
|
||||
* appId: string
|
||||
* }}
|
||||
*/
|
||||
u2f.RegisterResponse;
|
||||
|
||||
|
||||
/**
|
||||
* Data object for a registered key.
|
||||
* @typedef {{
|
||||
* version: string,
|
||||
* keyHandle: string,
|
||||
* transports: ?Transports,
|
||||
* appId: ?string
|
||||
* }}
|
||||
*/
|
||||
u2f.RegisteredKey;
|
||||
|
||||
|
||||
/**
|
||||
* Data object for a get API register response.
|
||||
* @typedef {{
|
||||
* js_api_version: number
|
||||
* }}
|
||||
*/
|
||||
u2f.GetJsApiVersionResponse;
|
||||
|
||||
|
||||
//Low level MessagePort API support
|
||||
|
||||
/**
|
||||
* Sets up a MessagePort to the U2F extension using the
|
||||
* available mechanisms.
|
||||
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||
*/
|
||||
u2f.getMessagePort = function(callback) {
|
||||
if (typeof chrome != 'undefined' && chrome.runtime) {
|
||||
// The actual message here does not matter, but we need to get a reply
|
||||
// for the callback to run. Thus, send an empty signature request
|
||||
// in order to get a failure response.
|
||||
var msg = {
|
||||
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||
signRequests: []
|
||||
};
|
||||
chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
|
||||
if (!chrome.runtime.lastError) {
|
||||
// We are on a whitelisted origin and can talk directly
|
||||
// with the extension.
|
||||
u2f.getChromeRuntimePort_(callback);
|
||||
} else {
|
||||
// chrome.runtime was available, but we couldn't message
|
||||
// the extension directly, use iframe
|
||||
u2f.getIframePort_(callback);
|
||||
}
|
||||
});
|
||||
} else if (u2f.isAndroidChrome_()) {
|
||||
u2f.getAuthenticatorPort_(callback);
|
||||
} else if (u2f.isIosChrome_()) {
|
||||
u2f.getIosPort_(callback);
|
||||
} else {
|
||||
// chrome.runtime was not available at all, which is normal
|
||||
// when this origin doesn't have access to any extensions.
|
||||
u2f.getIframePort_(callback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect chrome running on android based on the browser's useragent.
|
||||
* @private
|
||||
*/
|
||||
u2f.isAndroidChrome_ = function() {
|
||||
var userAgent = navigator.userAgent;
|
||||
return userAgent.indexOf('Chrome') != -1 &&
|
||||
userAgent.indexOf('Android') != -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect chrome running on iOS based on the browser's platform.
|
||||
* @private
|
||||
*/
|
||||
u2f.isIosChrome_ = function() {
|
||||
return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connects directly to the extension via chrome.runtime.connect.
|
||||
* @param {function(u2f.WrappedChromeRuntimePort_)} callback
|
||||
* @private
|
||||
*/
|
||||
u2f.getChromeRuntimePort_ = function(callback) {
|
||||
var port = chrome.runtime.connect(u2f.EXTENSION_ID,
|
||||
{'includeTlsChannelId': true});
|
||||
setTimeout(function() {
|
||||
callback(new u2f.WrappedChromeRuntimePort_(port));
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a 'port' abstraction to the Authenticator app.
|
||||
* @param {function(u2f.WrappedAuthenticatorPort_)} callback
|
||||
* @private
|
||||
*/
|
||||
u2f.getAuthenticatorPort_ = function(callback) {
|
||||
setTimeout(function() {
|
||||
callback(new u2f.WrappedAuthenticatorPort_());
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a 'port' abstraction to the iOS client app.
|
||||
* @param {function(u2f.WrappedIosPort_)} callback
|
||||
* @private
|
||||
*/
|
||||
u2f.getIosPort_ = function(callback) {
|
||||
setTimeout(function() {
|
||||
callback(new u2f.WrappedIosPort_());
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* A wrapper for chrome.runtime.Port that is compatible with MessagePort.
|
||||
* @param {Port} port
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
u2f.WrappedChromeRuntimePort_ = function(port) {
|
||||
this.port_ = port;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format and return a sign request compliant with the JS API version supported by the extension.
|
||||
* @param {Array<u2f.SignRequest>} signRequests
|
||||
* @param {number} timeoutSeconds
|
||||
* @param {number} reqId
|
||||
* @return {Object}
|
||||
*/
|
||||
u2f.formatSignRequest_ =
|
||||
function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
|
||||
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||
// Adapt request to the 1.0 JS API.
|
||||
var signRequests = [];
|
||||
for (var i = 0; i < registeredKeys.length; i++) {
|
||||
signRequests[i] = {
|
||||
version: registeredKeys[i].version,
|
||||
challenge: challenge,
|
||||
keyHandle: registeredKeys[i].keyHandle,
|
||||
appId: appId
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||
signRequests: signRequests,
|
||||
timeoutSeconds: timeoutSeconds,
|
||||
requestId: reqId
|
||||
};
|
||||
}
|
||||
// JS 1.1 API.
|
||||
return {
|
||||
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||
appId: appId,
|
||||
challenge: challenge,
|
||||
registeredKeys: registeredKeys,
|
||||
timeoutSeconds: timeoutSeconds,
|
||||
requestId: reqId
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Format and return a register request compliant with the JS API version supported by the extension..
|
||||
* @param {Array<u2f.SignRequest>} signRequests
|
||||
* @param {Array<u2f.RegisterRequest>} signRequests
|
||||
* @param {number} timeoutSeconds
|
||||
* @param {number} reqId
|
||||
* @return {Object}
|
||||
*/
|
||||
u2f.formatRegisterRequest_ =
|
||||
function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
|
||||
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||
// Adapt request to the 1.0 JS API.
|
||||
for (var i = 0; i < registerRequests.length; i++) {
|
||||
registerRequests[i].appId = appId;
|
||||
}
|
||||
var signRequests = [];
|
||||
for (var i = 0; i < registeredKeys.length; i++) {
|
||||
signRequests[i] = {
|
||||
version: registeredKeys[i].version,
|
||||
challenge: registerRequests[0],
|
||||
keyHandle: registeredKeys[i].keyHandle,
|
||||
appId: appId
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
||||
signRequests: signRequests,
|
||||
registerRequests: registerRequests,
|
||||
timeoutSeconds: timeoutSeconds,
|
||||
requestId: reqId
|
||||
};
|
||||
}
|
||||
// JS 1.1 API.
|
||||
return {
|
||||
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
||||
appId: appId,
|
||||
registerRequests: registerRequests,
|
||||
registeredKeys: registeredKeys,
|
||||
timeoutSeconds: timeoutSeconds,
|
||||
requestId: reqId
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Posts a message on the underlying channel.
|
||||
* @param {Object} message
|
||||
*/
|
||||
u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
|
||||
this.port_.postMessage(message);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Emulates the HTML 5 addEventListener interface. Works only for the
|
||||
* onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
|
||||
* @param {string} eventName
|
||||
* @param {function({data: Object})} handler
|
||||
*/
|
||||
u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
|
||||
function(eventName, handler) {
|
||||
var name = eventName.toLowerCase();
|
||||
if (name == 'message' || name == 'onmessage') {
|
||||
this.port_.onMessage.addListener(function(message) {
|
||||
// Emulate a minimal MessageEvent object.
|
||||
handler({'data': message});
|
||||
});
|
||||
} else {
|
||||
console.error('WrappedChromeRuntimePort only supports onMessage');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap the Authenticator app with a MessagePort interface.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
u2f.WrappedAuthenticatorPort_ = function() {
|
||||
this.requestId_ = -1;
|
||||
this.requestObject_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the Authenticator intent.
|
||||
* @param {Object} message
|
||||
*/
|
||||
u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
|
||||
var intentUrl =
|
||||
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
|
||||
';S.request=' + encodeURIComponent(JSON.stringify(message)) +
|
||||
';end';
|
||||
document.location = intentUrl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tells what type of port this is.
|
||||
* @return {String} port type
|
||||
*/
|
||||
u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
|
||||
return "WrappedAuthenticatorPort_";
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Emulates the HTML 5 addEventListener interface.
|
||||
* @param {string} eventName
|
||||
* @param {function({data: Object})} handler
|
||||
*/
|
||||
u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
|
||||
var name = eventName.toLowerCase();
|
||||
if (name == 'message') {
|
||||
var self = this;
|
||||
/* Register a callback to that executes when
|
||||
* chrome injects the response. */
|
||||
window.addEventListener(
|
||||
'message', self.onRequestUpdate_.bind(self, handler), false);
|
||||
} else {
|
||||
console.error('WrappedAuthenticatorPort only supports message');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback invoked when a response is received from the Authenticator.
|
||||
* @param function({data: Object}) callback
|
||||
* @param {Object} message message Object
|
||||
*/
|
||||
u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
|
||||
function(callback, message) {
|
||||
var messageObject = JSON.parse(message.data);
|
||||
var intentUrl = messageObject['intentURL'];
|
||||
|
||||
var errorCode = messageObject['errorCode'];
|
||||
var responseObject = null;
|
||||
if (messageObject.hasOwnProperty('data')) {
|
||||
responseObject = /** @type {Object} */ (
|
||||
JSON.parse(messageObject['data']));
|
||||
}
|
||||
|
||||
callback({'data': responseObject});
|
||||
};
|
||||
|
||||
/**
|
||||
* Base URL for intents to Authenticator.
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
|
||||
'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
|
||||
|
||||
/**
|
||||
* Wrap the iOS client app with a MessagePort interface.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
u2f.WrappedIosPort_ = function() {};
|
||||
|
||||
/**
|
||||
* Launch the iOS client app request
|
||||
* @param {Object} message
|
||||
*/
|
||||
u2f.WrappedIosPort_.prototype.postMessage = function(message) {
|
||||
var str = JSON.stringify(message);
|
||||
var url = "u2f://auth?" + encodeURI(str);
|
||||
location.replace(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tells what type of port this is.
|
||||
* @return {String} port type
|
||||
*/
|
||||
u2f.WrappedIosPort_.prototype.getPortType = function() {
|
||||
return "WrappedIosPort_";
|
||||
};
|
||||
|
||||
/**
|
||||
* Emulates the HTML 5 addEventListener interface.
|
||||
* @param {string} eventName
|
||||
* @param {function({data: Object})} handler
|
||||
*/
|
||||
u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
|
||||
var name = eventName.toLowerCase();
|
||||
if (name !== 'message') {
|
||||
console.error('WrappedIosPort only supports message');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up an embedded trampoline iframe, sourced from the extension.
|
||||
* @param {function(MessagePort)} callback
|
||||
* @private
|
||||
*/
|
||||
u2f.getIframePort_ = function(callback) {
|
||||
// Create the iframe
|
||||
var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.src = iframeOrigin + '/u2f-comms.html';
|
||||
iframe.setAttribute('style', 'display:none');
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
var channel = new MessageChannel();
|
||||
var ready = function(message) {
|
||||
if (message.data == 'ready') {
|
||||
channel.port1.removeEventListener('message', ready);
|
||||
callback(channel.port1);
|
||||
} else {
|
||||
console.error('First event on iframe port was not "ready"');
|
||||
}
|
||||
};
|
||||
channel.port1.addEventListener('message', ready);
|
||||
channel.port1.start();
|
||||
|
||||
iframe.addEventListener('load', function() {
|
||||
// Deliver the port to the iframe and initialize
|
||||
iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
//High-level JS API
|
||||
|
||||
/**
|
||||
* Default extension response timeout in seconds.
|
||||
* @const
|
||||
*/
|
||||
u2f.EXTENSION_TIMEOUT_SEC = 30;
|
||||
|
||||
/**
|
||||
* A singleton instance for a MessagePort to the extension.
|
||||
* @type {MessagePort|u2f.WrappedChromeRuntimePort_}
|
||||
* @private
|
||||
*/
|
||||
u2f.port_ = null;
|
||||
|
||||
/**
|
||||
* Callbacks waiting for a port
|
||||
* @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
|
||||
* @private
|
||||
*/
|
||||
u2f.waitingForPort_ = [];
|
||||
|
||||
/**
|
||||
* A counter for requestIds.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
u2f.reqCounter_ = 0;
|
||||
|
||||
/**
|
||||
* A map from requestIds to client callbacks
|
||||
* @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
|
||||
* |function((u2f.Error|u2f.SignResponse)))>}
|
||||
* @private
|
||||
*/
|
||||
u2f.callbackMap_ = {};
|
||||
|
||||
/**
|
||||
* Creates or retrieves the MessagePort singleton to use.
|
||||
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||
* @private
|
||||
*/
|
||||
u2f.getPortSingleton_ = function(callback) {
|
||||
if (u2f.port_) {
|
||||
callback(u2f.port_);
|
||||
} else {
|
||||
if (u2f.waitingForPort_.length == 0) {
|
||||
u2f.getMessagePort(function(port) {
|
||||
u2f.port_ = port;
|
||||
u2f.port_.addEventListener('message',
|
||||
/** @type {function(Event)} */ (u2f.responseHandler_));
|
||||
|
||||
// Careful, here be async callbacks. Maybe.
|
||||
while (u2f.waitingForPort_.length)
|
||||
u2f.waitingForPort_.shift()(u2f.port_);
|
||||
});
|
||||
}
|
||||
u2f.waitingForPort_.push(callback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles response messages from the extension.
|
||||
* @param {MessageEvent.<u2f.Response>} message
|
||||
* @private
|
||||
*/
|
||||
u2f.responseHandler_ = function(message) {
|
||||
var response = message.data;
|
||||
var reqId = response['requestId'];
|
||||
if (!reqId || !u2f.callbackMap_[reqId]) {
|
||||
console.error('Unknown or missing requestId in response.');
|
||||
return;
|
||||
}
|
||||
var cb = u2f.callbackMap_[reqId];
|
||||
delete u2f.callbackMap_[reqId];
|
||||
cb(response['responseData']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches an array of sign requests to available U2F tokens.
|
||||
* If the JS API version supported by the extension is unknown, it first sends a
|
||||
* message to the extension to find out the supported API version and then it sends
|
||||
* the sign request.
|
||||
* @param {string=} appId
|
||||
* @param {string=} challenge
|
||||
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||
* @param {number=} opt_timeoutSeconds
|
||||
*/
|
||||
u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
||||
if (js_api_version === undefined) {
|
||||
// Send a message to get the extension to JS API version, then send the actual sign request.
|
||||
u2f.getApiVersion(
|
||||
function (response) {
|
||||
js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
|
||||
console.log("Extension JS API Version: ", js_api_version);
|
||||
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
||||
});
|
||||
} else {
|
||||
// We know the JS API version. Send the actual sign request in the supported API version.
|
||||
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches an array of sign requests to available U2F tokens.
|
||||
* @param {string=} appId
|
||||
* @param {string=} challenge
|
||||
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||
* @param {number=} opt_timeoutSeconds
|
||||
*/
|
||||
u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
||||
u2f.getPortSingleton_(function(port) {
|
||||
var reqId = ++u2f.reqCounter_;
|
||||
u2f.callbackMap_[reqId] = callback;
|
||||
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
||||
var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
|
||||
port.postMessage(req);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches register requests to available U2F tokens. An array of sign
|
||||
* requests identifies already registered tokens.
|
||||
* If the JS API version supported by the extension is unknown, it first sends a
|
||||
* message to the extension to find out the supported API version and then it sends
|
||||
* the register request.
|
||||
* @param {string=} appId
|
||||
* @param {Array<u2f.RegisterRequest>} registerRequests
|
||||
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||
* @param {number=} opt_timeoutSeconds
|
||||
*/
|
||||
u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
||||
if (js_api_version === undefined) {
|
||||
// Send a message to get the extension to JS API version, then send the actual register request.
|
||||
u2f.getApiVersion(
|
||||
function (response) {
|
||||
js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
|
||||
console.log("Extension JS API Version: ", js_api_version);
|
||||
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
||||
callback, opt_timeoutSeconds);
|
||||
});
|
||||
} else {
|
||||
// We know the JS API version. Send the actual register request in the supported API version.
|
||||
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
||||
callback, opt_timeoutSeconds);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches register requests to available U2F tokens. An array of sign
|
||||
* requests identifies already registered tokens.
|
||||
* @param {string=} appId
|
||||
* @param {Array<u2f.RegisterRequest>} registerRequests
|
||||
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||
* @param {number=} opt_timeoutSeconds
|
||||
*/
|
||||
u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
||||
u2f.getPortSingleton_(function(port) {
|
||||
var reqId = ++u2f.reqCounter_;
|
||||
u2f.callbackMap_[reqId] = callback;
|
||||
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
||||
var req = u2f.formatRegisterRequest_(
|
||||
appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
|
||||
port.postMessage(req);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Dispatches a message to the extension to find out the supported
|
||||
* JS API version.
|
||||
* If the user is on a mobile phone and is thus using Google Authenticator instead
|
||||
* of the Chrome extension, don't send the request and simply return 0.
|
||||
* @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
|
||||
* @param {number=} opt_timeoutSeconds
|
||||
*/
|
||||
u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
|
||||
u2f.getPortSingleton_(function(port) {
|
||||
// If we are using Android Google Authenticator or iOS client app,
|
||||
// do not fire an intent to ask which JS API version to use.
|
||||
if (port.getPortType) {
|
||||
var apiVersion;
|
||||
switch (port.getPortType()) {
|
||||
case 'WrappedIosPort_':
|
||||
case 'WrappedAuthenticatorPort_':
|
||||
apiVersion = 1.1;
|
||||
break;
|
||||
|
||||
default:
|
||||
apiVersion = 0;
|
||||
break;
|
||||
}
|
||||
callback({ 'js_api_version': apiVersion });
|
||||
return;
|
||||
}
|
||||
var reqId = ++u2f.reqCounter_;
|
||||
u2f.callbackMap_[reqId] = callback;
|
||||
var req = {
|
||||
type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
|
||||
timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
|
||||
requestId: reqId
|
||||
};
|
||||
port.postMessage(req);
|
||||
});
|
||||
};
|
507
wp-content/plugins/two-factor/includes/Yubico/U2F.php
Normal file
507
wp-content/plugins/two-factor/includes/Yubico/U2F.php
Normal file
@ -0,0 +1,507 @@
|
||||
<?php
|
||||
/* Copyright (c) 2014 Yubico AB
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
namespace u2flib_server;
|
||||
|
||||
/** Constant for the version of the u2f protocol */
|
||||
const U2F_VERSION = "U2F_V2";
|
||||
|
||||
/** Error for the authentication message not matching any outstanding
|
||||
* authentication request */
|
||||
const ERR_NO_MATCHING_REQUEST = 1;
|
||||
|
||||
/** Error for the authentication message not matching any registration */
|
||||
const ERR_NO_MATCHING_REGISTRATION = 2;
|
||||
|
||||
/** Error for the signature on the authentication message not verifying with
|
||||
* the correct key */
|
||||
const ERR_AUTHENTICATION_FAILURE = 3;
|
||||
|
||||
/** Error for the challenge in the registration message not matching the
|
||||
* registration challenge */
|
||||
const ERR_UNMATCHED_CHALLENGE = 4;
|
||||
|
||||
/** Error for the attestation signature on the registration message not
|
||||
* verifying */
|
||||
const ERR_ATTESTATION_SIGNATURE = 5;
|
||||
|
||||
/** Error for the attestation verification not verifying */
|
||||
const ERR_ATTESTATION_VERIFICATION = 6;
|
||||
|
||||
/** Error for not getting good random from the system */
|
||||
const ERR_BAD_RANDOM = 7;
|
||||
|
||||
/** Error when the counter is lower than expected */
|
||||
const ERR_COUNTER_TOO_LOW = 8;
|
||||
|
||||
/** Error decoding public key */
|
||||
const ERR_PUBKEY_DECODE = 9;
|
||||
|
||||
/** Error user-agent returned error */
|
||||
const ERR_BAD_UA_RETURNING = 10;
|
||||
|
||||
/** Error old OpenSSL version */
|
||||
const ERR_OLD_OPENSSL = 11;
|
||||
|
||||
/** @internal */
|
||||
const PUBKEY_LEN = 65;
|
||||
|
||||
class U2F
|
||||
{
|
||||
/** @var string */
|
||||
private $appId;
|
||||
|
||||
/** @var null|string */
|
||||
private $attestDir;
|
||||
|
||||
/** @internal */
|
||||
private $FIXCERTS = array(
|
||||
'349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
|
||||
'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
|
||||
'1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
|
||||
'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
|
||||
'6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
|
||||
'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511'
|
||||
);
|
||||
|
||||
/**
|
||||
* @param string $appId Application id for the running application
|
||||
* @param string|null $attestDir Directory where trusted attestation roots may be found
|
||||
* @throws Error If OpenSSL older than 1.0.0 is used
|
||||
*/
|
||||
public function __construct($appId, $attestDir = null)
|
||||
{
|
||||
if(OPENSSL_VERSION_NUMBER < 0x10000000) {
|
||||
throw new Error('OpenSSL has to be at least version 1.0.0, this is ' . OPENSSL_VERSION_TEXT, ERR_OLD_OPENSSL);
|
||||
}
|
||||
$this->appId = $appId;
|
||||
$this->attestDir = $attestDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to get a registration request to send to a user.
|
||||
* Returns an array of one registration request and a array of sign requests.
|
||||
*
|
||||
* @param array $registrations List of current registrations for this
|
||||
* user, to prevent the user from registering the same authenticator several
|
||||
* times.
|
||||
* @return array An array of two elements, the first containing a
|
||||
* RegisterRequest the second being an array of SignRequest
|
||||
* @throws Error
|
||||
*/
|
||||
public function getRegisterData(array $registrations = array())
|
||||
{
|
||||
$challenge = $this->createChallenge();
|
||||
$request = new RegisterRequest($challenge, $this->appId);
|
||||
$signs = $this->getAuthenticateData($registrations);
|
||||
return array($request, $signs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to verify and unpack a registration message.
|
||||
*
|
||||
* @param RegisterRequest $request this is a reply to
|
||||
* @param object $response response from a user
|
||||
* @param bool $includeCert set to true if the attestation certificate should be
|
||||
* included in the returned Registration object
|
||||
* @return Registration
|
||||
* @throws Error
|
||||
*/
|
||||
public function doRegister($request, $response, $includeCert = true)
|
||||
{
|
||||
if( !is_object( $request ) ) {
|
||||
throw new \InvalidArgumentException('$request of doRegister() method only accepts object.');
|
||||
}
|
||||
|
||||
if( !is_object( $response ) ) {
|
||||
throw new \InvalidArgumentException('$response of doRegister() method only accepts object.');
|
||||
}
|
||||
|
||||
if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
|
||||
throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
|
||||
}
|
||||
|
||||
if( !is_bool( $includeCert ) ) {
|
||||
throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.');
|
||||
}
|
||||
|
||||
$rawReg = $this->base64u_decode($response->registrationData);
|
||||
$regData = array_values(unpack('C*', $rawReg));
|
||||
$clientData = $this->base64u_decode($response->clientData);
|
||||
$cli = json_decode($clientData);
|
||||
|
||||
if($cli->challenge !== $request->challenge) {
|
||||
throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE );
|
||||
}
|
||||
|
||||
$registration = new Registration();
|
||||
$offs = 1;
|
||||
$pubKey = substr($rawReg, $offs, PUBKEY_LEN);
|
||||
$offs += PUBKEY_LEN;
|
||||
// Decode the pubKey to make sure it's good.
|
||||
$tmpKey = $this->pubkey_to_pem($pubKey);
|
||||
if($tmpKey === null) {
|
||||
throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
|
||||
}
|
||||
$registration->publicKey = base64_encode($pubKey);
|
||||
$khLen = $regData[$offs++];
|
||||
$kh = substr($rawReg, $offs, $khLen);
|
||||
$offs += $khLen;
|
||||
$registration->keyHandle = $this->base64u_encode($kh);
|
||||
|
||||
// length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes).
|
||||
$certLen = 4;
|
||||
$certLen += ($regData[$offs + 2] << 8);
|
||||
$certLen += $regData[$offs + 3];
|
||||
|
||||
$rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen));
|
||||
$offs += $certLen;
|
||||
$pemCert = "-----BEGIN CERTIFICATE-----\r\n";
|
||||
$pemCert .= chunk_split(base64_encode($rawCert), 64);
|
||||
$pemCert .= "-----END CERTIFICATE-----";
|
||||
if($includeCert) {
|
||||
$registration->certificate = base64_encode($rawCert);
|
||||
}
|
||||
if($this->attestDir) {
|
||||
if(openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) {
|
||||
throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION );
|
||||
}
|
||||
}
|
||||
|
||||
if(!openssl_pkey_get_public($pemCert)) {
|
||||
throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
|
||||
}
|
||||
$signature = substr($rawReg, $offs);
|
||||
|
||||
$dataToVerify = chr(0);
|
||||
$dataToVerify .= hash('sha256', $request->appId, true);
|
||||
$dataToVerify .= hash('sha256', $clientData, true);
|
||||
$dataToVerify .= $kh;
|
||||
$dataToVerify .= $pubKey;
|
||||
|
||||
if(openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) {
|
||||
return $registration;
|
||||
} else {
|
||||
throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to get an authentication request.
|
||||
*
|
||||
* @param array $registrations An array of the registrations to create authentication requests for.
|
||||
* @return array An array of SignRequest
|
||||
* @throws Error
|
||||
*/
|
||||
public function getAuthenticateData(array $registrations)
|
||||
{
|
||||
$sigs = array();
|
||||
$challenge = $this->createChallenge();
|
||||
foreach ($registrations as $reg) {
|
||||
if( !is_object( $reg ) ) {
|
||||
throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.');
|
||||
}
|
||||
|
||||
$sig = new SignRequest();
|
||||
$sig->appId = $this->appId;
|
||||
$sig->keyHandle = $reg->keyHandle;
|
||||
$sig->challenge = $challenge;
|
||||
$sigs[] = $sig;
|
||||
}
|
||||
return $sigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to verify an authentication response
|
||||
*
|
||||
* @param array $requests An array of outstanding authentication requests
|
||||
* @param array $registrations An array of current registrations
|
||||
* @param object $response A response from the authenticator
|
||||
* @return Registration
|
||||
* @throws Error
|
||||
*
|
||||
* The Registration object returned on success contains an updated counter
|
||||
* that should be saved for future authentications.
|
||||
* If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of
|
||||
* token cloning or similar and appropriate action should be taken.
|
||||
*/
|
||||
public function doAuthenticate(array $requests, array $registrations, $response)
|
||||
{
|
||||
if( !is_object( $response ) ) {
|
||||
throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.');
|
||||
}
|
||||
|
||||
if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
|
||||
throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
|
||||
}
|
||||
|
||||
/** @var object|null $req */
|
||||
$req = null;
|
||||
|
||||
/** @var object|null $reg */
|
||||
$reg = null;
|
||||
|
||||
$clientData = $this->base64u_decode($response->clientData);
|
||||
$decodedClient = json_decode($clientData);
|
||||
foreach ($requests as $req) {
|
||||
if( !is_object( $req ) ) {
|
||||
throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.');
|
||||
}
|
||||
|
||||
if($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) {
|
||||
break;
|
||||
}
|
||||
|
||||
$req = null;
|
||||
}
|
||||
if($req === null) {
|
||||
throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST );
|
||||
}
|
||||
foreach ($registrations as $reg) {
|
||||
if( !is_object( $reg ) ) {
|
||||
throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.');
|
||||
}
|
||||
|
||||
if($reg->keyHandle === $response->keyHandle) {
|
||||
break;
|
||||
}
|
||||
$reg = null;
|
||||
}
|
||||
if($reg === null) {
|
||||
throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION );
|
||||
}
|
||||
$pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey));
|
||||
if($pemKey === null) {
|
||||
throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
|
||||
}
|
||||
|
||||
$signData = $this->base64u_decode($response->signatureData);
|
||||
$dataToVerify = hash('sha256', $req->appId, true);
|
||||
$dataToVerify .= substr($signData, 0, 5);
|
||||
$dataToVerify .= hash('sha256', $clientData, true);
|
||||
$signature = substr($signData, 5);
|
||||
|
||||
if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) {
|
||||
$ctr = unpack("Nctr", substr($signData, 1, 4));
|
||||
$counter = $ctr['ctr'];
|
||||
/* TODO: wrap-around should be handled somehow.. */
|
||||
if($counter > $reg->counter) {
|
||||
$reg->counter = $counter;
|
||||
return $reg;
|
||||
} else {
|
||||
throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW );
|
||||
}
|
||||
} else {
|
||||
throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function get_certs()
|
||||
{
|
||||
$files = array();
|
||||
$dir = $this->attestDir;
|
||||
if($dir && $handle = opendir($dir)) {
|
||||
while(false !== ($entry = readdir($handle))) {
|
||||
if(is_file("$dir/$entry")) {
|
||||
$files[] = "$dir/$entry";
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
private function base64u_encode($data)
|
||||
{
|
||||
return trim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
private function base64u_decode($data)
|
||||
{
|
||||
return base64_decode(strtr($data, '-_', '+/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return null|string
|
||||
*/
|
||||
private function pubkey_to_pem($key)
|
||||
{
|
||||
if(strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert the public key to binary DER format first
|
||||
* Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480
|
||||
*
|
||||
* SEQUENCE(2 elem) 30 59
|
||||
* SEQUENCE(2 elem) 30 13
|
||||
* OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01
|
||||
* OID1.2.840.10045.3.1.7 (secp256r1) 06 08 2a 86 48 ce 3d 03 01 07
|
||||
* BIT STRING(520 bit) 03 42 ..key..
|
||||
*/
|
||||
$der = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
|
||||
$der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
|
||||
$der .= "\0".$key;
|
||||
|
||||
$pem = "-----BEGIN PUBLIC KEY-----\r\n";
|
||||
$pem .= chunk_split(base64_encode($der), 64);
|
||||
$pem .= "-----END PUBLIC KEY-----";
|
||||
|
||||
return $pem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws Error
|
||||
*/
|
||||
private function createChallenge()
|
||||
{
|
||||
$challenge = openssl_random_pseudo_bytes(32, $crypto_strong );
|
||||
if( $crypto_strong !== true ) {
|
||||
throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM);
|
||||
}
|
||||
|
||||
$challenge = $this->base64u_encode( $challenge );
|
||||
|
||||
return $challenge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes a certificate where the signature contains unused bits.
|
||||
*
|
||||
* @param string $cert
|
||||
* @return mixed
|
||||
*/
|
||||
private function fixSignatureUnusedBits($cert)
|
||||
{
|
||||
if(in_array(hash('sha256', $cert), $this->FIXCERTS)) {
|
||||
$cert[strlen($cert) - 257] = "\0";
|
||||
}
|
||||
return $cert;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for building a registration request
|
||||
*
|
||||
* @package u2flib_server
|
||||
*/
|
||||
class RegisterRequest
|
||||
{
|
||||
/** Protocol version */
|
||||
public $version = U2F_VERSION;
|
||||
|
||||
/** Registration challenge */
|
||||
public $challenge;
|
||||
|
||||
/** Application id */
|
||||
public $appId;
|
||||
|
||||
/**
|
||||
* @param string $challenge
|
||||
* @param string $appId
|
||||
* @internal
|
||||
*/
|
||||
public function __construct($challenge, $appId)
|
||||
{
|
||||
$this->challenge = $challenge;
|
||||
$this->appId = $appId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for building up an authentication request
|
||||
*
|
||||
* @package u2flib_server
|
||||
*/
|
||||
class SignRequest
|
||||
{
|
||||
/** Protocol version */
|
||||
public $version = U2F_VERSION;
|
||||
|
||||
/** Authentication challenge */
|
||||
public $challenge;
|
||||
|
||||
/** Key handle of a registered authenticator */
|
||||
public $keyHandle;
|
||||
|
||||
/** Application id */
|
||||
public $appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class returned for successful registrations
|
||||
*
|
||||
* @package u2flib_server
|
||||
*/
|
||||
class Registration
|
||||
{
|
||||
/** The key handle of the registered authenticator */
|
||||
public $keyHandle;
|
||||
|
||||
/** The public key of the registered authenticator */
|
||||
public $publicKey;
|
||||
|
||||
/** The attestation certificate of the registered authenticator */
|
||||
public $certificate;
|
||||
|
||||
/** The counter associated with this registration */
|
||||
public $counter = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class, returned on errors
|
||||
*
|
||||
* @package u2flib_server
|
||||
*/
|
||||
class Error extends \Exception
|
||||
{
|
||||
/**
|
||||
* Override constructor and make message and code mandatory
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param \Exception|null $previous
|
||||
*/
|
||||
public function __construct($message, $code, \Exception $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* Extracted from wp-login.php since that file also loads WP core which we already have.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Outputs the footer for the login page.
|
||||
*
|
||||
* @since 3.1.0
|
||||
*
|
||||
* @global bool|string $interim_login Whether interim login modal is being displayed. String 'success'
|
||||
* upon successful login.
|
||||
*
|
||||
* @param string $input_id Which input to auto-focus.
|
||||
*/
|
||||
function login_footer( $input_id = '' ) {
|
||||
global $interim_login;
|
||||
|
||||
// Don't allow interim logins to navigate away from the page.
|
||||
if ( ! $interim_login ) {
|
||||
?>
|
||||
<p id="backtoblog">
|
||||
<?php
|
||||
$html_link = sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( home_url( '/' ) ),
|
||||
sprintf(
|
||||
/* translators: %s: Site title. */
|
||||
_x( '← Go to %s', 'site' ),
|
||||
get_bloginfo( 'title', 'display' )
|
||||
)
|
||||
);
|
||||
/**
|
||||
* Filter the "Go to site" link displayed in the login page footer.
|
||||
*
|
||||
* @since 5.7.0
|
||||
*
|
||||
* @param string $link HTML link to the home URL of the current site.
|
||||
*/
|
||||
echo apply_filters( 'login_site_html_link', $html_link );
|
||||
?>
|
||||
</p>
|
||||
<?php
|
||||
|
||||
the_privacy_policy_link( '<div class="privacy-policy-page-link">', '</div>' );
|
||||
}
|
||||
|
||||
?>
|
||||
</div><?php // End of <div id="login">. ?>
|
||||
|
||||
<?php
|
||||
|
||||
if ( ! empty( $input_id ) ) {
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
try{document.getElementById('<?php echo $input_id; ?>').focus();}catch(e){}
|
||||
if(typeof wpOnload==='function')wpOnload();
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires in the login page footer.
|
||||
*
|
||||
* @since 3.1.0
|
||||
*/
|
||||
do_action( 'login_footer' );
|
||||
|
||||
?>
|
||||
<div class="clear"></div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the JavaScript to handle the form shaking on the login page.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
function wp_shake_js() {
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
document.querySelector('form').classList.add('shake');
|
||||
</script>
|
||||
<?php
|
||||
}
|
259
wp-content/plugins/two-factor/includes/function.login-header.php
Normal file
259
wp-content/plugins/two-factor/includes/function.login-header.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* Extracted from wp-login.php since that file also loads WP core which we already have.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Output the login page header.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*
|
||||
* @global string $error Login error message set by deprecated pluggable wp_login() function
|
||||
* or plugins replacing it.
|
||||
* @global bool|string $interim_login Whether interim login modal is being displayed. String 'success'
|
||||
* upon successful login.
|
||||
* @global string $action The action that brought the visitor to the login page.
|
||||
*
|
||||
* @param string $title Optional. WordPress login Page title to display in the `<title>` element.
|
||||
* Default 'Log In'.
|
||||
* @param string $message Optional. Message to display in header. Default empty.
|
||||
* @param WP_Error $wp_error Optional. The error to pass. Default is a WP_Error instance.
|
||||
*/
|
||||
function login_header( $title = 'Log In', $message = '', $wp_error = null ) {
|
||||
global $error, $interim_login, $action;
|
||||
|
||||
// Don't index any of these forms.
|
||||
add_filter( 'wp_robots', 'wp_robots_sensitive_page' );
|
||||
add_action( 'login_head', 'wp_strict_cross_origin_referrer' );
|
||||
|
||||
add_action( 'login_head', 'wp_login_viewport_meta' );
|
||||
|
||||
if ( ! is_wp_error( $wp_error ) ) {
|
||||
$wp_error = new WP_Error();
|
||||
}
|
||||
|
||||
// Shake it!
|
||||
$shake_error_codes = array( 'empty_password', 'empty_email', 'invalid_email', 'invalidcombo', 'empty_username', 'invalid_username', 'incorrect_password', 'retrieve_password_email_failure' );
|
||||
/**
|
||||
* Filters the error codes array for shaking the login form.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param array $shake_error_codes Error codes that shake the login form.
|
||||
*/
|
||||
$shake_error_codes = apply_filters( 'shake_error_codes', $shake_error_codes );
|
||||
|
||||
if ( $shake_error_codes && $wp_error->has_errors() && in_array( $wp_error->get_error_code(), $shake_error_codes, true ) ) {
|
||||
add_action( 'login_footer', 'wp_shake_js', 12 );
|
||||
}
|
||||
|
||||
$login_title = get_bloginfo( 'name', 'display' );
|
||||
|
||||
/* translators: Login screen title. 1: Login screen name, 2: Network or site name. */
|
||||
$login_title = sprintf( __( '%1$s ‹ %2$s — WordPress' ), $title, $login_title );
|
||||
|
||||
if ( wp_is_recovery_mode() ) {
|
||||
/* translators: %s: Login screen title. */
|
||||
$login_title = sprintf( __( 'Recovery Mode — %s' ), $login_title );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the title tag content for login page.
|
||||
*
|
||||
* @since 4.9.0
|
||||
*
|
||||
* @param string $login_title The page title, with extra context added.
|
||||
* @param string $title The original page title.
|
||||
*/
|
||||
$login_title = apply_filters( 'login_title', $login_title, $title );
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="<?php bloginfo( 'html_type' ); ?>; charset=<?php bloginfo( 'charset' ); ?>" />
|
||||
<title><?php echo $login_title; ?></title>
|
||||
<?php
|
||||
|
||||
wp_enqueue_style( 'login' );
|
||||
|
||||
/*
|
||||
* Remove all stored post data on logging out.
|
||||
* This could be added by add_action('login_head'...) like wp_shake_js(),
|
||||
* but maybe better if it's not removable by plugins.
|
||||
*/
|
||||
if ( 'loggedout' === $wp_error->get_error_code() ) {
|
||||
?>
|
||||
<script>if("sessionStorage" in window){try{for(var key in sessionStorage){if(key.indexOf("wp-autosave-")!=-1){sessionStorage.removeItem(key)}}}catch(e){}};</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts and styles for the login page.
|
||||
*
|
||||
* @since 3.1.0
|
||||
*/
|
||||
do_action( 'login_enqueue_scripts' );
|
||||
|
||||
/**
|
||||
* Fires in the login page header after scripts are enqueued.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
do_action( 'login_head' );
|
||||
|
||||
$login_header_url = __( 'https://wordpress.org/' );
|
||||
|
||||
/**
|
||||
* Filters link URL of the header logo above login form.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*
|
||||
* @param string $login_header_url Login header logo URL.
|
||||
*/
|
||||
$login_header_url = apply_filters( 'login_headerurl', $login_header_url );
|
||||
|
||||
$login_header_title = '';
|
||||
|
||||
/**
|
||||
* Filters the title attribute of the header logo above login form.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @deprecated 5.2.0 Use {@see 'login_headertext'} instead.
|
||||
*
|
||||
* @param string $login_header_title Login header logo title attribute.
|
||||
*/
|
||||
$login_header_title = apply_filters_deprecated(
|
||||
'login_headertitle',
|
||||
array( $login_header_title ),
|
||||
'5.2.0',
|
||||
'login_headertext',
|
||||
__( 'Usage of the title attribute on the login logo is not recommended for accessibility reasons. Use the link text instead.' )
|
||||
);
|
||||
|
||||
$login_header_text = empty( $login_header_title ) ? __( 'Powered by WordPress' ) : $login_header_title;
|
||||
|
||||
/**
|
||||
* Filters the link text of the header logo above the login form.
|
||||
*
|
||||
* @since 5.2.0
|
||||
*
|
||||
* @param string $login_header_text The login header logo link text.
|
||||
*/
|
||||
$login_header_text = apply_filters( 'login_headertext', $login_header_text );
|
||||
|
||||
$classes = array( 'login-action-' . $action, 'wp-core-ui' );
|
||||
|
||||
if ( is_rtl() ) {
|
||||
$classes[] = 'rtl';
|
||||
}
|
||||
|
||||
if ( $interim_login ) {
|
||||
$classes[] = 'interim-login';
|
||||
|
||||
?>
|
||||
<style type="text/css">html{background-color: transparent;}</style>
|
||||
<?php
|
||||
|
||||
if ( 'success' === $interim_login ) {
|
||||
$classes[] = 'interim-login-success';
|
||||
}
|
||||
}
|
||||
|
||||
$classes[] = ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_locale() ) ) );
|
||||
|
||||
/**
|
||||
* Filters the login page body classes.
|
||||
*
|
||||
* @since 3.5.0
|
||||
*
|
||||
* @param array $classes An array of body classes.
|
||||
* @param string $action The action that brought the visitor to the login page.
|
||||
*/
|
||||
$classes = apply_filters( 'login_body_class', $classes, $action );
|
||||
|
||||
?>
|
||||
</head>
|
||||
<body class="login no-js <?php echo esc_attr( implode( ' ', $classes ) ); ?>">
|
||||
<script type="text/javascript">
|
||||
document.body.className = document.body.className.replace('no-js','js');
|
||||
</script>
|
||||
<?php
|
||||
/**
|
||||
* Fires in the login page header after the body tag is opened.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*/
|
||||
do_action( 'login_header' );
|
||||
|
||||
?>
|
||||
<div id="login">
|
||||
<h1><a href="<?php echo esc_url( $login_header_url ); ?>"><?php echo $login_header_text; ?></a></h1>
|
||||
<?php
|
||||
/**
|
||||
* Filters the message to display above the login form.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*
|
||||
* @param string $message Login message text.
|
||||
*/
|
||||
$message = apply_filters( 'login_message', $message );
|
||||
|
||||
if ( ! empty( $message ) ) {
|
||||
echo $message . "\n";
|
||||
}
|
||||
|
||||
// In case a plugin uses $error rather than the $wp_errors object.
|
||||
if ( ! empty( $error ) ) {
|
||||
$wp_error->add( 'error', $error );
|
||||
unset( $error );
|
||||
}
|
||||
|
||||
if ( $wp_error->has_errors() ) {
|
||||
$errors = '';
|
||||
$messages = '';
|
||||
|
||||
foreach ( $wp_error->get_error_codes() as $code ) {
|
||||
$severity = $wp_error->get_error_data( $code );
|
||||
foreach ( $wp_error->get_error_messages( $code ) as $error_message ) {
|
||||
if ( 'message' === $severity ) {
|
||||
$messages .= ' ' . $error_message . "<br />\n";
|
||||
} else {
|
||||
$errors .= ' ' . $error_message . "<br />\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $errors ) ) {
|
||||
/**
|
||||
* Filters the error messages displayed above the login form.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*
|
||||
* @param string $errors Login error message.
|
||||
*/
|
||||
echo '<div id="login_error">' . apply_filters( 'login_errors', $errors ) . "</div>\n";
|
||||
}
|
||||
|
||||
if ( ! empty( $messages ) ) {
|
||||
/**
|
||||
* Filters instructional messages displayed above the login form.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*
|
||||
* @param string $messages Login messages.
|
||||
*/
|
||||
echo '<p class="message">' . apply_filters( 'login_messages', $messages ) . "</p>\n";
|
||||
}
|
||||
}
|
||||
} // End of login_header().
|
||||
|
||||
/**
|
||||
* Outputs the viewport meta tag for the login page.
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
function wp_login_viewport_meta() {
|
||||
?>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<?php
|
||||
}
|
Reference in New Issue
Block a user