Initial Commit
This commit is contained in:
commit
8ca36409fd
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Composer
|
||||||
|
composer.phar
|
||||||
|
/vendor/
|
||||||
|
# VS Code
|
||||||
|
/.vscode/
|
||||||
|
settings.json
|
||||||
|
# Certificate Authority
|
||||||
|
/ca/
|
||||||
|
# User Data
|
||||||
|
/data/
|
||||||
|
# Twig Compilation Cache
|
||||||
|
/compilation_cache/
|
7
.htaccess
Normal file
7
.htaccess
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
RewriteBase /
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
|
||||||
|
RewriteRule ^(.+)$ index.php [QSA,L]
|
29
composer.json
Normal file
29
composer.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "161sh/gatekeeper",
|
||||||
|
"description": "SSO Identity Management for 161.sh",
|
||||||
|
"license": "AGPL-3.0-only",
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"version": "1.0",
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.4",
|
||||||
|
"gac/routing": "dev-main",
|
||||||
|
"twig/twig": "^3.0",
|
||||||
|
"ext-json": "*",
|
||||||
|
"161sh/seriousjson": "@dev"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^8"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"Tor": "src/",
|
||||||
|
"Cave": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "path",
|
||||||
|
"url": "../SeriousJSON"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1794
composer.lock
generated
Normal file
1794
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
0
docs/API/README.md
Normal file
0
docs/API/README.md
Normal file
62
files/css/clippy.css
Executable file
62
files/css/clippy.css
Executable file
@ -0,0 +1,62 @@
|
|||||||
|
.clippy, .clippy-balloon {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clippy-balloon {
|
||||||
|
|
||||||
|
background: #FFC;
|
||||||
|
color: black;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.clippy-content {
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 120px;
|
||||||
|
font-family: "Microsoft Sans", sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clippy-tip {
|
||||||
|
width: 10px;
|
||||||
|
height: 16px;
|
||||||
|
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAgCAMAAAAlvKiEAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRF///MAAAA////52QwgAAAAAN0Uk5T//8A18oNQQAAAGxJREFUeNqs0kEOwCAIRFHn3//QTUU6xMyyxii+jQosrTPkyPEM6IN3FtzIRk1U4dFeKWQiH6pRRowMVKEmvronEynkwj0uZJgR22+YLopPSo9P34wJSamLSU7lSIWLJU7NkNomNlhqxUeAAQC+TQLZyEuJBwAAAABJRU5ErkJggg==) no-repeat;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clippy-top-left .clippy-tip {
|
||||||
|
top: 100%;
|
||||||
|
margin-top: 0px;
|
||||||
|
left: 100%;
|
||||||
|
margin-left: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clippy-top-right .clippy-tip {
|
||||||
|
top: 100%;
|
||||||
|
margin-top: 0px;
|
||||||
|
left: 0;
|
||||||
|
margin-left: 50px;
|
||||||
|
background-position: -10px 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.clippy-bottom-right .clippy-tip {
|
||||||
|
top: 0;
|
||||||
|
margin-top: -16px;
|
||||||
|
left: 0;
|
||||||
|
margin-left: 50px;
|
||||||
|
background-position: -10px -16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clippy-bottom-left .clippy-tip {
|
||||||
|
top: 0;
|
||||||
|
margin-top: -16px;
|
||||||
|
left: 100%;
|
||||||
|
margin-left: -50px;
|
||||||
|
background-position: 0px -16px;
|
||||||
|
}
|
||||||
|
|
137
files/css/style.css
Executable file
137
files/css/style.css
Executable file
@ -0,0 +1,137 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "Merriweather";
|
||||||
|
src: url("/files/media/font.ttf");
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: 'Merriweather';
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: url('/files/media/cursor.cur'), auto;
|
||||||
|
cursor: -webkit-image-set(
|
||||||
|
url('/files/media/cursor.png') 1x,
|
||||||
|
url('/files/media/cursor2x.png') 2x
|
||||||
|
), auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #008080;
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
/*display: grid;
|
||||||
|
place-items: center;*/
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padring {
|
||||||
|
padding: 3%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width:650px;
|
||||||
|
background:#C0C0C0;
|
||||||
|
border-width:1px;
|
||||||
|
border-color:#FFFFFF #808080 #808080 #FFFFFF;
|
||||||
|
border-style:solid;
|
||||||
|
position:absolute;
|
||||||
|
top:50%;
|
||||||
|
left:50%;
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
|
padding:1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
width: 394px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 80%;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
width: 248px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 80%;
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus{
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
div.title{
|
||||||
|
height:18px;
|
||||||
|
width:100%;
|
||||||
|
background:#000080;
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
img.title{
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
p.title{
|
||||||
|
margin:2px 0 0 1px;
|
||||||
|
float:left;
|
||||||
|
font-weight:bold;
|
||||||
|
font-size:11px;
|
||||||
|
}
|
||||||
|
.title button{
|
||||||
|
/*position:absolute;
|
||||||
|
right:3px;
|
||||||
|
top:3px;*/
|
||||||
|
margin:2px 2px 2px 0;
|
||||||
|
float:right;
|
||||||
|
width:14px;
|
||||||
|
height:13.5px;
|
||||||
|
background:#C0C0C0;
|
||||||
|
border-width:1px;
|
||||||
|
border-color:#FFFFFF #808080 #808080 #FFFFFF;
|
||||||
|
padding:0;
|
||||||
|
font-size:9px;
|
||||||
|
font-weight:bold;
|
||||||
|
text-align:center;
|
||||||
|
focus:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
background:#EEE;
|
||||||
|
border-width:1px;
|
||||||
|
border-color:#FFFFFF #808080 #808080 #FFFFFF;
|
||||||
|
border-style:solid;
|
||||||
|
padding:5px;
|
||||||
|
margin:3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=submit], .button, .keygen-link {
|
||||||
|
font-size:14px;
|
||||||
|
outline:1px solid #000000;
|
||||||
|
background:#C0C0C0;
|
||||||
|
border-width:1px;
|
||||||
|
border-style:solid;
|
||||||
|
border-color:#FFFFFF #808080 #808080 #FFFFFF;
|
||||||
|
padding:4px 10px;
|
||||||
|
margin:20px 3px;
|
||||||
|
display:inline;
|
||||||
|
position:relative;
|
||||||
|
bottom:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keygen-algorithm { /* algorithm dropdown (select) */ }
|
||||||
|
.keygen-pkeyopts { display: none; /* additional key options (text input) */ }
|
||||||
|
.keygen-status { /* status/progress (span) */ }
|
||||||
|
.keygen-link { /* generated certificate download link (a) */ }
|
||||||
|
|
||||||
|
.powered {
|
||||||
|
color: #444;
|
||||||
|
text-shadow: #DDD 1px 1px 0;
|
||||||
|
border-top: 2px groove #DDD;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: orange;
|
||||||
|
}
|
341
files/js/browser.openssl.js
Executable file
341
files/js/browser.openssl.js
Executable file
File diff suppressed because one or more lines are too long
1
files/js/clippy.min.js
vendored
Executable file
1
files/js/clippy.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
4
files/js/jquery.min.js
vendored
Executable file
4
files/js/jquery.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
404
files/js/keygen.js
Executable file
404
files/js/keygen.js
Executable file
@ -0,0 +1,404 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Vladimir Panteleev
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* jshint esversion : 8, laxbreak:true */
|
||||||
|
|
||||||
|
function isKeygenNativelySupported() {
|
||||||
|
// TODO: iOS - https://github.com/Modernizr/Modernizr/issues/1075
|
||||||
|
var keygen = document.createElement('keygen');
|
||||||
|
if ('challenge' in keygen)
|
||||||
|
return 'blink';
|
||||||
|
|
||||||
|
var template = document.createElement('div');
|
||||||
|
template.innerHTML = '<form><keygen class="keygen"></form>';
|
||||||
|
keygen = template.getElementsByClassName('keygen')[0];
|
||||||
|
if (keygen.tagName.toLowerCase() === 'select')
|
||||||
|
return 'gecko';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function keygenJS(OpenSSL) {
|
||||||
|
if (isKeygenNativelySupported())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const supportedAlgorithms = [ 'RSA', 'RSA-PSS', 'X25519',
|
||||||
|
'X448', 'ED25519', 'ED448' ];
|
||||||
|
function wipe(arr) {
|
||||||
|
for (var i = 0; i < arr.length; i++)
|
||||||
|
arr[i] = 0xFE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextPowerOfTwo(v) {
|
||||||
|
var p = 2;
|
||||||
|
while ((v >>= 1)) {
|
||||||
|
p <<= 1;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newSession() {
|
||||||
|
// openssl.js suggests using wasmfs, however, it adds a
|
||||||
|
// lot of dependencies and is difficult to package, so
|
||||||
|
// just implement a basic virtual FS here.
|
||||||
|
|
||||||
|
let files = { };
|
||||||
|
let lineBuf = '';
|
||||||
|
let tty = {
|
||||||
|
write : function(arr) {
|
||||||
|
for (var c of arr)
|
||||||
|
if (c == 10) {
|
||||||
|
console.log(lineBuf); lineBuf = '';
|
||||||
|
} else {
|
||||||
|
lineBuf += String.fromCharCode(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let fds = [ { }, tty, tty ];
|
||||||
|
let impl = {
|
||||||
|
existsSync : function(fn) {
|
||||||
|
// console.log('existsSync', fn);
|
||||||
|
return files.hasOwnProperty(fn);
|
||||||
|
},
|
||||||
|
realpathSync : function(n) {
|
||||||
|
return n;
|
||||||
|
},
|
||||||
|
mkdirSync : function() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
openSync : function(fn, flags) {
|
||||||
|
// console.log('openSync', fn, flags);
|
||||||
|
var ro = flags & 1;
|
||||||
|
if (!ro)
|
||||||
|
files[fn] = new Uint8Array(0);
|
||||||
|
else
|
||||||
|
if (!files.hasOwnProperty(fn))
|
||||||
|
return -1;
|
||||||
|
var pos = 0;
|
||||||
|
fds.push({
|
||||||
|
read : function(length) {
|
||||||
|
var arr = files[fn].subarray(pos, pos + length);
|
||||||
|
pos += arr.length;
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
write : ro ? null : function(arr) {
|
||||||
|
// console.log('write', arr.length, '@', pos, '/', files[fn].buffer.byteLength);
|
||||||
|
if (files[fn].buffer.byteLength < pos + arr.length) {
|
||||||
|
var newSize = nextPowerOfTwo(pos + arr.length);
|
||||||
|
var newFile = new Uint8Array(newSize);
|
||||||
|
newFile.set(files[fn]);
|
||||||
|
wipe(new Uint8Array(files[fn].buffer));
|
||||||
|
files[fn] = newFile.subarray(0, pos);
|
||||||
|
}
|
||||||
|
var target = new Uint8Array(files[fn].buffer, pos, arr.length);
|
||||||
|
target.set(arr);
|
||||||
|
pos += arr.length;
|
||||||
|
files[fn] = new Uint8Array(files[fn].buffer, 0, pos);
|
||||||
|
wipe(arr);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return fds.length - 1;
|
||||||
|
},
|
||||||
|
readSync : function(fd, buffer, offset, length, position) {
|
||||||
|
// console.log(fd, buffer, offset, length);
|
||||||
|
if (fd < 0) return 0;
|
||||||
|
var arr = fds[fd].read(length);
|
||||||
|
buffer.subarray(offset, offset + arr.length).set(arr);
|
||||||
|
return arr.length;
|
||||||
|
},
|
||||||
|
writeSync : function(fd, buffer) {
|
||||||
|
if (fd < 0) return 0;
|
||||||
|
fds[fd].write(buffer);
|
||||||
|
return buffer.length;
|
||||||
|
},
|
||||||
|
closeSync : function(fd) {
|
||||||
|
if (fd < 0) return;
|
||||||
|
fds[fd] = null;
|
||||||
|
},
|
||||||
|
fstatSync : function() {
|
||||||
|
return {
|
||||||
|
isBlockDevice : () => false,
|
||||||
|
isCharacterDevice : () => false,
|
||||||
|
isDirectory : () => false,
|
||||||
|
isFIFO : () => false,
|
||||||
|
isFile : () => true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
constants : {
|
||||||
|
O_RDONLY : 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let openSSL = new OpenSSL({
|
||||||
|
fs: impl,
|
||||||
|
rootDir: '/'
|
||||||
|
});
|
||||||
|
|
||||||
|
return { files, openSSL };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genSpkac(algorithm, pkeyopts, challenge) {
|
||||||
|
let s = newSession();
|
||||||
|
if (algorithm === undefined)
|
||||||
|
algorithm = 'RSA';
|
||||||
|
if (!algorithm.length || algorithm.match(/[\s]/))
|
||||||
|
throw 'Invalid algorithm';
|
||||||
|
if (challenge !== undefined && (!challenge.length || challenge.match(/[\s]/))) {
|
||||||
|
// https://github.com/DigitalArsenal/openssl.js/issues/3
|
||||||
|
throw 'Invalid challenge string';
|
||||||
|
}
|
||||||
|
pkeyopts = pkeyopts.split(/[\s]{1,}/g).filter(Boolean);
|
||||||
|
|
||||||
|
await s.openSSL.runCommand(
|
||||||
|
"genpkey" +
|
||||||
|
" -algorithm " + algorithm +
|
||||||
|
pkeyopts.map(opt => " -pkeyopt " + opt) +
|
||||||
|
" -out /private.pem");
|
||||||
|
if (!s.files.hasOwnProperty('/private.pem') || !s.files['/private.pem'].length)
|
||||||
|
throw 'Private key generation failed';
|
||||||
|
|
||||||
|
await s.openSSL.runCommand(
|
||||||
|
"spkac" +
|
||||||
|
" -key /private.pem" +
|
||||||
|
" -out /spkac" +
|
||||||
|
(challenge === undefined ? "" : " -challenge " + challenge)
|
||||||
|
);
|
||||||
|
if (!s.files.hasOwnProperty('/spkac') || !s.files['/spkac'].length)
|
||||||
|
throw 'Private key generation failed';
|
||||||
|
var privateKey = s.files['/private.pem'];
|
||||||
|
|
||||||
|
var spkac = s.files['/spkac'];
|
||||||
|
spkac = spkac.subarray(6, spkac.length - 1); // Skip "SKPAC=" and trim line ending
|
||||||
|
spkac = String.fromCharCode.apply(null, spkac);
|
||||||
|
return { spkac, privateKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function convertDerToPem(der) {
|
||||||
|
let s = newSession();
|
||||||
|
s.files['/cert.der'] = der;
|
||||||
|
await s.openSSL.runCommand(
|
||||||
|
"x509" +
|
||||||
|
" -inform der" +
|
||||||
|
" -in /cert.der" +
|
||||||
|
" -outform pem" +
|
||||||
|
" -out /cert.pem");
|
||||||
|
if (!s.files.hasOwnProperty('/cert.pem') || !s.files['/cert.pem'].length)
|
||||||
|
throw 'Certificate conversion failed';
|
||||||
|
return s.files['/cert.pem'];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function convertToPkcs12(key, pem) {
|
||||||
|
let s = newSession();
|
||||||
|
s.files['/private.pem'] = key;
|
||||||
|
s.files['/cert.pem'] = pem;
|
||||||
|
await s.openSSL.runCommand(
|
||||||
|
"pkcs12" +
|
||||||
|
" -export" +
|
||||||
|
" -in /cert.pem" +
|
||||||
|
" -inkey /private.pem" +
|
||||||
|
" -out /cert.p12" +
|
||||||
|
" -passout pass:mac");
|
||||||
|
if (!s.files.hasOwnProperty('/cert.p12') || !s.files['/cert.p12'].length)
|
||||||
|
throw 'Certificate conversion failed';
|
||||||
|
return s.files['/cert.p12'];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function base64Encode(data) {
|
||||||
|
let s = newSession();
|
||||||
|
s.files['/data.bin'] = data;
|
||||||
|
await s.openSSL.runCommand(
|
||||||
|
"base64" +
|
||||||
|
" -e" +
|
||||||
|
" -in /data.bin" +
|
||||||
|
" -out /data.txt");
|
||||||
|
if (!s.files.hasOwnProperty('/data.txt'))
|
||||||
|
throw 'Base64 encoding failed';
|
||||||
|
return s.files['/data.txt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function polyfillKeygen(keygen) {
|
||||||
|
var name = keygen.getAttribute('name');
|
||||||
|
if (!name) throw '<keygen> element has no name';
|
||||||
|
|
||||||
|
var autofocus = keygen.hasAttribute('autofocus');
|
||||||
|
if (autofocus)
|
||||||
|
keygen.removeAttribute('autofocus');
|
||||||
|
|
||||||
|
var disabled = keygen.hasAttribute('disabled');
|
||||||
|
|
||||||
|
if (keygen.hasAttribute('keyparams')) // Don't do the wrong thing
|
||||||
|
throw 'Sorry, the keyparams <keygen> attribute is not supported';
|
||||||
|
|
||||||
|
var algoSelect;
|
||||||
|
if (!keygen.hasAttribute('keytype')) {
|
||||||
|
algoSelect = document.createElement('select');
|
||||||
|
algoSelect.setAttribute('title', 'Private key algorithm');
|
||||||
|
algoSelect.setAttribute('class', 'keygen-algorithm');
|
||||||
|
for (var algoName of supportedAlgorithms) {
|
||||||
|
var algoOption = document.createElement('option');
|
||||||
|
algoOption.setAttribute('value', algoName);
|
||||||
|
algoOption.textContent = algoName;
|
||||||
|
algoSelect.appendChild(algoOption);
|
||||||
|
}
|
||||||
|
if (autofocus) {
|
||||||
|
algoSelect.setAttribute('autofocus', '');
|
||||||
|
autofocus = false;
|
||||||
|
}
|
||||||
|
if (disabled)
|
||||||
|
algoSelect.setAttribute('disabled', '');
|
||||||
|
keygen.appendChild(algoSelect);
|
||||||
|
}
|
||||||
|
|
||||||
|
var optInput = document.createElement('input');
|
||||||
|
optInput.setAttribute('title', 'Space-separated public key generation algorithm options\n' +
|
||||||
|
'(e.g. "rsa_keygen_bits:4096" for a 4096-bit RSA key)');
|
||||||
|
optInput.setAttribute('class', 'keygen-pkeyopts');
|
||||||
|
optInput.setAttribute('placeholder', 'optional private key options');
|
||||||
|
if (autofocus) {
|
||||||
|
optInput.setAttribute('autofocus', '');
|
||||||
|
autofocus = false;
|
||||||
|
}
|
||||||
|
if (disabled)
|
||||||
|
optInput.setAttribute('disabled', '');
|
||||||
|
keygen.appendChild(optInput);
|
||||||
|
|
||||||
|
var status = document.createElement('span');
|
||||||
|
status.setAttribute('class', 'keygen-status');
|
||||||
|
status.textContent = 'Ready!';
|
||||||
|
keygen.appendChild(status);
|
||||||
|
|
||||||
|
var resultLink = document.createElement('a');
|
||||||
|
resultLink.setAttribute('class', 'keygen-link-inv');
|
||||||
|
keygen.appendChild(resultLink);
|
||||||
|
|
||||||
|
var form;
|
||||||
|
if (keygen.hasAttribute('form')) {
|
||||||
|
form = document.getElementById(keygen.getAttribute('form'));
|
||||||
|
if (!form) throw '<keygen> has invalid form attribute';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
form = optInput.form;
|
||||||
|
if (!form) throw '<keygen> is not in a <form> and has no form attribute';
|
||||||
|
}
|
||||||
|
|
||||||
|
var spkacInput = document.createElement('input');
|
||||||
|
spkacInput.setAttribute('type', 'hidden');
|
||||||
|
keygen.removeAttribute('name');
|
||||||
|
spkacInput.setAttribute('name', name);
|
||||||
|
if (disabled)
|
||||||
|
spkacInput.setAttribute('disabled', '');
|
||||||
|
form.appendChild(spkacInput);
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
(async function() {
|
||||||
|
var algorithm = keygen.hasAttribute('keytype')
|
||||||
|
? keygen.getAttribute('keytype')
|
||||||
|
: algoSelect.value;
|
||||||
|
var challenge;
|
||||||
|
if (keygen.hasAttribute('challenge'))
|
||||||
|
challenge = keygen.getAttribute('challenge');
|
||||||
|
var pkeyopts = optInput.value;
|
||||||
|
if (keygen.hasAttribute('-keygenjs-pkeyopts'))
|
||||||
|
pkeyopts += ' ' + keygen.getAttribute('-keygenjs-pkeyopts');
|
||||||
|
|
||||||
|
resultLink.setAttribute('class', 'keygen-link-inv');
|
||||||
|
resultLink.setAttribute('href', '');
|
||||||
|
resultLink.textContent = '';
|
||||||
|
|
||||||
|
var spkac;
|
||||||
|
status.textContent = 'Generating key pair...';
|
||||||
|
try {
|
||||||
|
spkac = await genSpkac(algorithm, pkeyopts, challenge);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('keygen.js key generation error:', e);
|
||||||
|
status.textContent = 'Error!';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
spkacInput.setAttribute('value', spkac.spkac);
|
||||||
|
status.textContent = 'Submitting SPKAC request...';
|
||||||
|
var data = new FormData(form);
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open(form.method || 'GET', form.action || '', true);
|
||||||
|
xhr.responseType = 'arraybuffer';
|
||||||
|
xhr.onload = function() {
|
||||||
|
status.textContent = 'Processing response...';
|
||||||
|
try {
|
||||||
|
if (!xhr.response) throw 'No response';
|
||||||
|
var der = new Uint8Array(xhr.response);
|
||||||
|
if (!der.length) throw 'Empty response';
|
||||||
|
|
||||||
|
(async function() {
|
||||||
|
var pem;
|
||||||
|
status.textContent = 'Converting certificate...';
|
||||||
|
try {
|
||||||
|
pem = await convertDerToPem(der);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('keygen.js certificate conversion error:', e);
|
||||||
|
status.textContent = 'Error!';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var p12;
|
||||||
|
status.textContent = 'Combining certificate...';
|
||||||
|
try {
|
||||||
|
p12 = await convertToPkcs12(spkac.privateKey, pem);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('keygen.js certificate combining error:', e);
|
||||||
|
status.textContent = 'Error!';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var b64 = await base64Encode(p12);
|
||||||
|
var url = 'data:application/x-pkcs12;base64,' +
|
||||||
|
String.fromCharCode.apply(null, b64);
|
||||||
|
|
||||||
|
status.textContent = ''; // OK
|
||||||
|
resultLink.setAttribute('class', 'keygen-link');
|
||||||
|
resultLink.setAttribute('href', url);
|
||||||
|
resultLink.setAttribute('download', 'cert.p12');
|
||||||
|
resultLink.setAttribute('onclick', 'myagent.play("Save");');
|
||||||
|
resultLink.textContent = 'Save certificate';
|
||||||
|
})();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('keygen.js certificate generation error:', e);
|
||||||
|
status.textContent = 'Error!';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send(data);
|
||||||
|
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function polyfillAll() {
|
||||||
|
var keygens = document.getElementsByTagName('keygen');
|
||||||
|
for (var keygen of keygens) {
|
||||||
|
try {
|
||||||
|
polyfillKeygen(keygen);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('keygen.js polyfill error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading')
|
||||||
|
document.addEventListener('DOMContentLoaded', polyfillAll);
|
||||||
|
else
|
||||||
|
polyfillAll();
|
||||||
|
}
|
43
files/js/window.js
Normal file
43
files/js/window.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//Make the DIV element draggagle:
|
||||||
|
dragElement(document.getElementById("drag"));
|
||||||
|
|
||||||
|
function dragElement(elmnt) {
|
||||||
|
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||||||
|
if (document.getElementById(elmnt.id + "title")) {
|
||||||
|
/* if present, the header is where you move the DIV from:*/
|
||||||
|
document.getElementById(elmnt.id + "title").onmousedown = dragMouseDown;
|
||||||
|
} else {
|
||||||
|
/* otherwise, move the DIV from anywhere inside the DIV:*/
|
||||||
|
elmnt.onmousedown = dragMouseDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragMouseDown(e) {
|
||||||
|
e = e || window.event;
|
||||||
|
e.preventDefault();
|
||||||
|
// get the mouse cursor position at startup:
|
||||||
|
pos3 = e.clientX;
|
||||||
|
pos4 = e.clientY;
|
||||||
|
document.onmouseup = closeDragElement;
|
||||||
|
// call a function whenever the cursor moves:
|
||||||
|
document.onmousemove = elementDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementDrag(e) {
|
||||||
|
e = e || window.event;
|
||||||
|
e.preventDefault();
|
||||||
|
// calculate the new cursor position:
|
||||||
|
pos1 = pos3 - e.clientX;
|
||||||
|
pos2 = pos4 - e.clientY;
|
||||||
|
pos3 = e.clientX;
|
||||||
|
pos4 = e.clientY;
|
||||||
|
// set the element's new position:
|
||||||
|
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
|
||||||
|
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDragElement() {
|
||||||
|
/* stop moving when mouse button is released:*/
|
||||||
|
document.onmouseup = null;
|
||||||
|
document.onmousemove = null;
|
||||||
|
}
|
||||||
|
}
|
BIN
files/media/banner.jpg
Executable file
BIN
files/media/banner.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 276 KiB |
BIN
files/media/chaser.png
Executable file
BIN
files/media/chaser.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
BIN
files/media/cursor.cur
Executable file
BIN
files/media/cursor.cur
Executable file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
files/media/cursor.png
Executable file
BIN
files/media/cursor.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 949 B |
BIN
files/media/cursor2x.png
Executable file
BIN
files/media/cursor2x.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
files/media/font.ttf
Executable file
BIN
files/media/font.ttf
Executable file
Binary file not shown.
198
files/media/help.pdf
Executable file
198
files/media/help.pdf
Executable file
@ -0,0 +1,198 @@
|
|||||||
|
%PDF-1.3
|
||||||
|
%âãÏÓ
|
||||||
|
|
||||||
|
1 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Catalog
|
||||||
|
/Outlines 2 0 R
|
||||||
|
/Pages 3 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
2 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Outlines
|
||||||
|
/Count 0
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Pages
|
||||||
|
/Count 2
|
||||||
|
/Kids [ 4 0 R 6 0 R ]
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
4 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Page
|
||||||
|
/Parent 3 0 R
|
||||||
|
/Resources <<
|
||||||
|
/Font <<
|
||||||
|
/F1 9 0 R
|
||||||
|
>>
|
||||||
|
/ProcSet 8 0 R
|
||||||
|
>>
|
||||||
|
/MediaBox [0 0 612.0000 792.0000]
|
||||||
|
/Contents 5 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
5 0 obj
|
||||||
|
<< /Length 1074 >>
|
||||||
|
stream
|
||||||
|
2 J
|
||||||
|
BT
|
||||||
|
0 0 0 rg
|
||||||
|
/F1 0027 Tf
|
||||||
|
57.3750 722.2800 Td
|
||||||
|
( A Simple PDF File ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 688.6080 Td
|
||||||
|
( This is a small demonstration .pdf file - ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 664.7040 Td
|
||||||
|
( just for use in the Virtual Mechanics tutorials. More text. And more ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 652.7520 Td
|
||||||
|
( text. And more text. And more text. And more text. ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 628.8480 Td
|
||||||
|
( And more text. And more text. And more text. And more text. And more ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 616.8960 Td
|
||||||
|
( text. And more text. Boring, zzzzz. And more text. And more text. And ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 604.9440 Td
|
||||||
|
( more text. And more text. And more text. And more text. And more text. ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 592.9920 Td
|
||||||
|
( And more text. And more text. ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 569.0880 Td
|
||||||
|
( And more text. And more text. And more text. And more text. And more ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 557.1360 Td
|
||||||
|
( text. And more text. And more text. Even more. Continued on page 2 ...) Tj
|
||||||
|
ET
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
6 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Page
|
||||||
|
/Parent 3 0 R
|
||||||
|
/Resources <<
|
||||||
|
/Font <<
|
||||||
|
/F1 9 0 R
|
||||||
|
>>
|
||||||
|
/ProcSet 8 0 R
|
||||||
|
>>
|
||||||
|
/MediaBox [0 0 612.0000 792.0000]
|
||||||
|
/Contents 7 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
7 0 obj
|
||||||
|
<< /Length 676 >>
|
||||||
|
stream
|
||||||
|
2 J
|
||||||
|
BT
|
||||||
|
0 0 0 rg
|
||||||
|
/F1 0027 Tf
|
||||||
|
57.3750 722.2800 Td
|
||||||
|
( Simple PDF File 2 ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 688.6080 Td
|
||||||
|
( ...continued from page 1. Yet more text. And more text. And more text. ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 676.6560 Td
|
||||||
|
( And more text. And more text. And more text. And more text. And more ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 664.7040 Td
|
||||||
|
( text. Oh, how boring typing this stuff. But not as boring as watching ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 652.7520 Td
|
||||||
|
( paint dry. And more text. And more text. And more text. And more text. ) Tj
|
||||||
|
ET
|
||||||
|
BT
|
||||||
|
/F1 0010 Tf
|
||||||
|
69.2500 640.8000 Td
|
||||||
|
( Boring. More, a little more text. The end, and just as well. ) Tj
|
||||||
|
ET
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
8 0 obj
|
||||||
|
[/PDF /Text]
|
||||||
|
endobj
|
||||||
|
|
||||||
|
9 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Font
|
||||||
|
/Subtype /Type1
|
||||||
|
/Name /F1
|
||||||
|
/BaseFont /Helvetica
|
||||||
|
/Encoding /WinAnsiEncoding
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
10 0 obj
|
||||||
|
<<
|
||||||
|
/Creator (Rave \(http://www.nevrona.com/rave\))
|
||||||
|
/Producer (Nevrona Designs)
|
||||||
|
/CreationDate (D:20060301072826)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
xref
|
||||||
|
0 11
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000019 00000 n
|
||||||
|
0000000093 00000 n
|
||||||
|
0000000147 00000 n
|
||||||
|
0000000222 00000 n
|
||||||
|
0000000390 00000 n
|
||||||
|
0000001522 00000 n
|
||||||
|
0000001690 00000 n
|
||||||
|
0000002423 00000 n
|
||||||
|
0000002456 00000 n
|
||||||
|
0000002574 00000 n
|
||||||
|
|
||||||
|
trailer
|
||||||
|
<<
|
||||||
|
/Size 11
|
||||||
|
/Root 1 0 R
|
||||||
|
/Info 10 0 R
|
||||||
|
>>
|
||||||
|
|
||||||
|
startxref
|
||||||
|
2714
|
||||||
|
%%EOF
|
BIN
files/media/key.png
Executable file
BIN
files/media/key.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
files/media/segfault.mov
Executable file
BIN
files/media/segfault.mov
Executable file
Binary file not shown.
79
index.php
Normal file
79
index.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Autoload files using the Composer autoloader.
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
// First Party
|
||||||
|
use Tor\Tor;
|
||||||
|
use Tor\Api\Api;
|
||||||
|
|
||||||
|
use Tor\Data\Ticket;
|
||||||
|
use Tor\Data\Grant;
|
||||||
|
use Tor\Data\Service;
|
||||||
|
use Tor\Data\User;
|
||||||
|
|
||||||
|
// Third Party Routing Library
|
||||||
|
use Gac\Routing\Exceptions\CallbackNotFound;
|
||||||
|
use Gac\Routing\Exceptions\RouteNotFoundException;
|
||||||
|
use Gac\Routing\Request;
|
||||||
|
use Gac\Routing\Routes;
|
||||||
|
|
||||||
|
// Initialize an instance of the routing library
|
||||||
|
$routes = new Routes();
|
||||||
|
|
||||||
|
// Initialize Twig
|
||||||
|
// TODO: Only do this if needed
|
||||||
|
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/templates');
|
||||||
|
$twig = new \Twig\Environment($loader, [
|
||||||
|
'cache' => __DIR__. '/compilation_cache',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tor = new Tor();
|
||||||
|
$api = new Api();
|
||||||
|
|
||||||
|
$service = new Service();
|
||||||
|
$service->id = "minetest";
|
||||||
|
$service->token = "45678-hsjsndjs-272892-shgdzusjd-6788";
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$user->id = "bananafish";
|
||||||
|
$user->serial = '0C:87:64:78';
|
||||||
|
$user->cert = "Yee Haw";
|
||||||
|
|
||||||
|
$grant = new Grant();
|
||||||
|
$grant->id = uniqid();
|
||||||
|
$grant->service = $service;
|
||||||
|
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$ticket->id = uniqid();
|
||||||
|
$ticket->start = 1471111;
|
||||||
|
$ticket->end = 1474567;
|
||||||
|
$ticket->user = $user;
|
||||||
|
$ticket->grant = $grant;
|
||||||
|
|
||||||
|
echo(microtime(true));
|
||||||
|
echo($ticket->Serialize(true));
|
||||||
|
echo(var_dump(Ticket::Deserialize($ticket->Serialize(true), true)));
|
||||||
|
echo(Ticket::Deserialize($ticket->Serialize(true), true))->Serialize(true);
|
||||||
|
echo(microtime(true));
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialize Tor in order to register routes
|
||||||
|
$tor->init();
|
||||||
|
|
||||||
|
// Initialize Tor API in order to register routes
|
||||||
|
$api->init();
|
||||||
|
|
||||||
|
// Handle routes
|
||||||
|
$routes->handle();
|
||||||
|
} catch (RouteNotFoundException $ex) {
|
||||||
|
$routes->request->status(404, "Route not found")->send(["error" => ["message" => $ex->getMessage()]]);
|
||||||
|
} catch (CallbackNotFound $ex) {
|
||||||
|
$routes->request->status(404, "Callback method not found")->send(["error" => ["message" => $ex->getMessage()]]);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$code = $ex->getCode() ?? 500;
|
||||||
|
$routes->request->status($code)->send(["error" => ["message" => $ex->getMessage()]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
10
scripts/first-install-dev.sh
Executable file
10
scripts/first-install-dev.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
chmod +x ./scripts/*.sh
|
||||||
|
|
||||||
|
./scripts/setup-composer.sh
|
||||||
|
./composer.phar install
|
||||||
|
|
||||||
|
./scripts/setup-ca.sh
|
||||||
|
|
||||||
|
./scripts/run-tests.sh
|
8
scripts/first-install.sh
Executable file
8
scripts/first-install.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
chmod +x ./scripts/*.sh
|
||||||
|
|
||||||
|
./scripts/setup-composer.sh
|
||||||
|
./composer.phar install --no-dev
|
||||||
|
|
||||||
|
./scripts/setup-ca.sh
|
2
scripts/run-dev.sh
Executable file
2
scripts/run-dev.sh
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
php -S localhost:4000
|
1
scripts/run-tests.sh
Executable file
1
scripts/run-tests.sh
Executable file
@ -0,0 +1 @@
|
|||||||
|
./vendor/bin/phpunit tests/TestRunner.php
|
56
scripts/setup-ca.sh
Executable file
56
scripts/setup-ca.sh
Executable file
@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2020 Vladimir Panteleev
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
set -eEuo pipefail
|
||||||
|
|
||||||
|
# Script to set up a minimal certificate authority for keygen.js testing.
|
||||||
|
# Not a secure configuration! Do not use in production!
|
||||||
|
|
||||||
|
rm -rf ca/ssl
|
||||||
|
mkdir -p ca/ssl
|
||||||
|
cd ca/ssl
|
||||||
|
|
||||||
|
mkdir 161sh
|
||||||
|
mkdir 161sh/private
|
||||||
|
openssl genpkey -algorithm RSA -out 161sh/private/cakey.pem
|
||||||
|
openssl req -x509 -days 358000 -new -nodes -key 161sh/private/cakey.pem -sha256 -out 161sh/cacert.pem \
|
||||||
|
-subj "/C=SH/CN=161.sh"
|
||||||
|
mkdir 161sh/newcerts
|
||||||
|
touch 161sh/index.txt
|
||||||
|
echo '0000000000000000' > 161sh/serial
|
||||||
|
mkdir tmp
|
||||||
|
|
||||||
|
cat > openssl.cnf <<'EOF'
|
||||||
|
[ ca ]
|
||||||
|
default_ca = CA_default
|
||||||
|
|
||||||
|
[ CA_default ]
|
||||||
|
dir = ./161sh
|
||||||
|
private_key = $dir/private/cakey.pem
|
||||||
|
certificate = $dir/cacert.pem
|
||||||
|
new_certs_dir = $dir/newcerts
|
||||||
|
database = $dir/index.txt
|
||||||
|
serial = $dir/serial
|
||||||
|
policy = policy_match
|
||||||
|
|
||||||
|
[ policy_match ]
|
||||||
|
countryName = match
|
||||||
|
stateOrProvinceName = optional
|
||||||
|
organizationName = supplied
|
||||||
|
organizationalUnitName = optional
|
||||||
|
commonName = supplied
|
||||||
|
emailAddress = optional
|
||||||
|
EOF
|
17
scripts/setup-composer.sh
Executable file
17
scripts/setup-composer.sh
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')"
|
||||||
|
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
||||||
|
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
|
||||||
|
|
||||||
|
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
|
||||||
|
then
|
||||||
|
>&2 echo 'ERROR: Invalid installer checksum'
|
||||||
|
rm composer-setup.php
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
php composer-setup.php --quiet
|
||||||
|
RESULT=$?
|
||||||
|
rm composer-setup.php
|
||||||
|
exit $RESULT
|
11
src/Cave/Cave.php
Normal file
11
src/Cave/Cave.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cave;
|
||||||
|
|
||||||
|
class Cave {
|
||||||
|
public function init() {
|
||||||
|
// Push Routes
|
||||||
|
return "Hello World";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
157
src/Tor/Api/Api.php
Normal file
157
src/Tor/Api/Api.php
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Api;
|
||||||
|
|
||||||
|
use Gac\Routing\Request;
|
||||||
|
|
||||||
|
// Implements the dispatcher for Tor's API
|
||||||
|
class Api {
|
||||||
|
public function init() {
|
||||||
|
global $routes;
|
||||||
|
|
||||||
|
// Respond with supported legacy versions and current version of API
|
||||||
|
$routes->add('/api', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok', 'legacy' => [], 'version' => ['v1'] ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$routes->add('/api/v1', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'unauthorized' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: List all users
|
||||||
|
$routes->add('/api/v1/user/list', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Assign a given new public key id to username
|
||||||
|
// Creates user if it doesn't exist
|
||||||
|
$routes->add('/api/v1/user/assign', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Delete User and all Sessions + ExtraData
|
||||||
|
$routes->add('/api/v1/user/delete', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Fetch persistent extradata for user
|
||||||
|
$routes->add('/api/v1/user/extra', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Create a new permission group
|
||||||
|
$routes->add('/api/v1/group/create', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Delete a permission group
|
||||||
|
$routes->add('/api/v1/group/delete', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Assign User to Group
|
||||||
|
$routes->add('/api/v1/group/assign', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Revoke User's Group Membership
|
||||||
|
$routes->add('/api/v1/group/revoke', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Delete User and all Sessions + ExtraData
|
||||||
|
$routes->add('/api/v1/user/delete', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Ticket Granting Ticket
|
||||||
|
$routes->add('/api/v1/grant/create', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Invalidate Ticket Granting Ticket
|
||||||
|
$routes->add('/api/v1/grant/destroy', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Poll Authorization Status of Grant
|
||||||
|
$routes->add('/api/v1/grant/status', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch ExtraData for Grant
|
||||||
|
$routes->add('/api/v1/grant/extra', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: List all active Grants
|
||||||
|
$routes->add('/api/v1/grants/list', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Approve of Grant with specified subset of extradata
|
||||||
|
$routes->add('/api/v1/grants/approve', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: Reject Grant
|
||||||
|
$routes->add('/api/v1/grants/reject', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch ID of Session Ticket by using authorized Grant ID
|
||||||
|
// Useful when polling manually, not neccessary when using callback
|
||||||
|
$routes->add('/api/v1/ticket/fetch', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Destroy active Session
|
||||||
|
$routes->add('/api/v1/ticket/destroy', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Poll authorization status and start / end date for session ticket
|
||||||
|
$routes->add('/api/v1/ticket/status', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Session KeepAlive
|
||||||
|
$routes->add('/api/v1/ticket/heartbeat', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch ExtraData for Ticket
|
||||||
|
$routes->add('/api/v1/ticket/extra', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Internal: List all active Session Tickets
|
||||||
|
$routes->add('/api/v1/ticket/list', function (Request $request) {
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send([ 'result' => 'ok' ]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
10
src/Tor/Common/Utils.php
Normal file
10
src/Tor/Common/Utils.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Common;
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
public function foo() {
|
||||||
|
return "Hello World";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
25
src/Tor/Controller/BasePage.php
Normal file
25
src/Tor/Controller/BasePage.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Controller;
|
||||||
|
|
||||||
|
use Gac\Routing\Request;
|
||||||
|
|
||||||
|
abstract class BasePage {
|
||||||
|
private $tpl_values;
|
||||||
|
|
||||||
|
function __construct() {
|
||||||
|
$this->$tpl_values = array();
|
||||||
|
$this->$tpl_values["client_ip"] = $_SERVER['REMOTE_ADDR'] ?? 'N/A';
|
||||||
|
$this->$tpl_values["client_host"] = gethostbyaddr($_SERVER['REMOTE_ADDR']) ?? "N/A";
|
||||||
|
|
||||||
|
$this->$tpl_values["files_url"] = "/files/";
|
||||||
|
|
||||||
|
$this->$tpl_values["app_name"] = "Fire Systems SSO";
|
||||||
|
|
||||||
|
$this->$tpl_values["page_title"] = "Welcome";
|
||||||
|
$this->$tpl_values["window_title"] = "Welcome";
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract function render(\Gac\Routing\Request $request);
|
||||||
|
|
||||||
|
}
|
13
src/Tor/Controller/Message.php
Normal file
13
src/Tor/Controller/Message.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
namespace Tor\Controller;
|
||||||
|
|
||||||
|
class Message extends BasePage {
|
||||||
|
|
||||||
|
function render(\Gac\Routing\Request $request)
|
||||||
|
{
|
||||||
|
global $twig;
|
||||||
|
|
||||||
|
$request->status(200, 'OK');
|
||||||
|
echo($twig->render('message.html.twig', $this->$tpl_values));
|
||||||
|
}
|
||||||
|
}
|
13
src/Tor/Data/BaseEntity.php
Normal file
13
src/Tor/Data/BaseEntity.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
abstract class BaseEntity extends \SeriousJSON\JsonSerializable {
|
||||||
|
// Primary Identifier
|
||||||
|
public string $id;
|
||||||
|
|
||||||
|
public function flatIdentifier()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
}
|
17
src/Tor/Data/Grant.php
Normal file
17
src/Tor/Data/Grant.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
class Grant extends BaseEntity {
|
||||||
|
public Service $service;
|
||||||
|
public int $create;
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
if(get_class($this) == get_called_class()) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return get_class($this) . ' = ' . get_called_class();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/Tor/Data/Service.php
Normal file
16
src/Tor/Data/Service.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
class Service extends BaseEntity {
|
||||||
|
public string $token;
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
if(get_class($this) == get_called_class()) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return get_class($this) . ' = ' . get_called_class();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/Tor/Data/Ticket.php
Normal file
19
src/Tor/Data/Ticket.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
class Ticket extends BaseEntity {
|
||||||
|
public Grant $grant;
|
||||||
|
public User $user;
|
||||||
|
public int $start;
|
||||||
|
public int $end;
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
if(get_class($this) == get_called_class()) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return get_class($this) . ' = ' . get_called_class();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
src/Tor/Data/User.php
Normal file
17
src/Tor/Data/User.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
class User extends BaseEntity {
|
||||||
|
public string $serial;
|
||||||
|
public string $cert;
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
if(get_class($this) == get_called_class()) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return get_class($this) . ' = ' . get_called_class();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/Tor/Tor.php
Normal file
42
src/Tor/Tor.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor;
|
||||||
|
|
||||||
|
use Gac\Routing\Request;
|
||||||
|
use Tor\Controller\Message;
|
||||||
|
|
||||||
|
// Implements the Dispatcher for Tor
|
||||||
|
class Tor {
|
||||||
|
|
||||||
|
public function init() {
|
||||||
|
global $routes;
|
||||||
|
|
||||||
|
$routes->add('/', [ Message::class, 'render' ]);
|
||||||
|
|
||||||
|
// Generate new User Certificate
|
||||||
|
$routes->add('/generate', function (Request $request) {
|
||||||
|
global $twig;
|
||||||
|
|
||||||
|
$request->status(200, 'OK');
|
||||||
|
echo($twig->render('generate.html.twig', ['name' => 'Fabien', 'title' => 'Fire Systems Internet Access Point', 'files_url' => './files']));
|
||||||
|
});
|
||||||
|
|
||||||
|
// User Authorization Extradata Consent Form
|
||||||
|
$routes->add('/consent', function (Request $request) {
|
||||||
|
global $twig;
|
||||||
|
|
||||||
|
$request->status(200, 'OK');
|
||||||
|
echo($twig->render('consent.html.twig', ['name' => 'Fabien', 'title' => 'Fire Systems Internet Access Point', 'files_url' => './files']));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assign Certificate to User / Register Username with Certificate
|
||||||
|
$routes->add('/activate', function (Request $request) {
|
||||||
|
global $twig;
|
||||||
|
|
||||||
|
$request->status(200, 'OK');
|
||||||
|
echo($twig->render('activate.html.twig', ['name' => 'Fabien', 'title' => 'Fire Systems Internet Access Point', 'files_url' => './files']));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
18
templates/activate.html.twig
Normal file
18
templates/activate.html.twig
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<form action="/activate" method="post" style="display: inline;">
|
||||||
|
Attach the provided X.509 Certificate to an Identity<br><br><label><b>Username:</b></label><br><input name="username" value="{{ username | e }}Hankypants" disabled><br>
|
||||||
|
<textarea rows=10 cols=40 disabled>
|
||||||
|
Common Name:
|
||||||
|
Hankypants
|
||||||
|
Issuer:
|
||||||
|
tor.161.sh
|
||||||
|
Key Fingerprint:
|
||||||
|
CA:3A:85:33:03:1F:39:7B
|
||||||
|
Key ID:
|
||||||
|
A5:CE:37:EA:EB:B0:75:0E</textarea><br>
|
||||||
|
<i>Please check the Certificate before confirming.</i><br>
|
||||||
|
<b><input type="submit" name="confirm" value="Attach Certificate" onclick="myagent.play('Print'); myagent.play('GetTechy');"></b>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
49
templates/base.html.twig
Normal file
49
templates/base.html.twig
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{ title | e }} | {% block page_title %}Welcome{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ files_url }}/css/style.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ files_url }}/css/clippy.css">
|
||||||
|
<script type="text/javascript" src="{{ files_url }}/js/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ files_url }}/js/clippy.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var myagent;
|
||||||
|
clippy.load('Links', function(agent){
|
||||||
|
myagent = agent;
|
||||||
|
// Do anything with the loaded agent
|
||||||
|
agent.show();
|
||||||
|
agent.speak('If you need assistance with generating and importing certificates, press the [?] Help Button. My name is Links.');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
myagent.animate();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="padring">
|
||||||
|
<div class="container" id="drag">
|
||||||
|
<div class="title" id="dragtitle">
|
||||||
|
<img src="{{ files_url }}/media/key.png" width="18" height="18" class="title" onclick="if (event.shiftKey) window.open('<?php echo $CONF_BASE_URI; ?>/media/segfault.mov', '_blank', 'toolbar=no,scrollbars=no,location=no,fullscreen=yes,status=no,height='+screen.height + ',width='+screen.width); else myagent.play('Congratulate'); return false; "/>
|
||||||
|
<p class="title">{% block window_title %}Welcome{% endblock %}</p>
|
||||||
|
<button onclick="if (typeof helpOpen === 'undefined') myagent.animate(); else window.location.href = window.location.href; return false;">X</button>
|
||||||
|
<button onclick="document.getElementById('content').innerHTML='<div class=pdfcontainer><object type=application/pdf height=500 width=100% data={{ files_url }}/media/help.pdf></object></div>'; document.getElementsByClassName('clippy')[0].style.position = 'absolute'; window.helpOpen = true; myagent.play('CheckingSomething'); return false;">?</button>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
<img src="{{ files_url }}/media/banner.jpg" class="banner" onclick="myagent.play('GetArtsy'); return false;" />
|
||||||
|
<div class="left">
|
||||||
|
<?php echo(gethostbyaddr($_SERVER['REMOTE_ADDR'])); ?><br/>IP <?php echo($_SERVER['REMOTE_ADDR']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
Served by dual SPARC64 VIIIs<br>Mainframe access at port 443
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
<div class="powered">Protected by <img src="{{ files_url }}/media/chaser.png" style="height: 2em; vertical-align: -30%;" /> Software</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="{{ files_url }}/js/window.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
18
templates/consent.html.twig
Normal file
18
templates/consent.html.twig
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<form action="/consent" method="post" style="display: inline;">
|
||||||
|
A service has requested access to your Identity<br><br><label><b>Username:</b></label><br><input name="username" value="{{ username | e }}Hankypants" disabled><br>
|
||||||
|
<textarea rows=10 cols=40 disabled>
|
||||||
|
Common Name:
|
||||||
|
Hankypants
|
||||||
|
Issuer:
|
||||||
|
tor.161.sh
|
||||||
|
Key Fingerprint:
|
||||||
|
CA:3A:85:33:03:1F:39:7B
|
||||||
|
Key ID:
|
||||||
|
A5:CE:37:EA:EB:B0:75:0E</textarea><br>
|
||||||
|
<i>Please verify before continuing.</i><br>
|
||||||
|
<input type="submit" name="confirm" value="Consent" onclick="myagent.play('Print'); myagent.play('GetTechy');"><input type="submit" name="deny" value="Deny Access" onclick="myagent.play('Print'); myagent.play('GetTechy');">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
16
templates/generate.html.twig
Normal file
16
templates/generate.html.twig
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<script src="{{ files_url }}/js/keygen.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
import { OpenSSL } from {{ files_url }}/js/browser.openssl.js';
|
||||||
|
keygenJS(OpenSSL);
|
||||||
|
</script>
|
||||||
|
<br><br>
|
||||||
|
<form action="/generate/spkac-endpoint.php" method="post" style="display: inline;">
|
||||||
|
<b><label>Username:</label></b><br><input name="username" required pattern="([A-z0-9]){4,16}"><br>
|
||||||
|
<input type="submit" value="Generate key" onclick="myagent.play('Print'); myagent.play('GetTechy');"><a href="/" style="display: inline; text-decoration: none;"><button type="button" class="button">Authenticate</button></a><br>
|
||||||
|
<keygen name="key" keytype="rsa" -keygenjs-pkeyopts="rsa_keygen_bits:4096">
|
||||||
|
</form>
|
||||||
|
<br><br>
|
||||||
|
{% endblock %}
|
6
templates/message.html.twig
Normal file
6
templates/message.html.twig
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<b>{{ msg_title | e }}</b><br><br>
|
||||||
|
{{ msg_body | e }}
|
||||||
|
{% endblock %}
|
15
tests/TestRunner.php
Normal file
15
tests/TestRunner.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Autoload files using the Composer autoloader.
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Tor\Tor;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class TestRunner extends TestCase
|
||||||
|
{
|
||||||
|
public function testPrintHelloWorld() {
|
||||||
|
$actualClass = new Tor();
|
||||||
|
$this->assertEquals('Hello World', $actualClass->init());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user