diff --git a/geni-auth.js b/geni-auth.js index 40e12c5..785de9f 100755 --- a/geni-auth.js +++ b/geni-auth.js @@ -3,8 +3,29 @@ var genilib = {}; genilib.trustedHost = 'https://www.emulab.net'; genilib.trustedPath = '/protogeni/speaks-for/index.html'; -genilib.authorize = function(id, cert, callback, defaultMA, userBundle) +genilib.authorize = function(idOrObject, cert, callback, defaultMA, userBundle) { + var id; + var authenticate; + + // If the user invokes with just a single object, it is a dictionary of options + if (typeof(idOrObject) === 'object') + { + id = idOrObject.id; + cert = idOrObject.toolCertificate; + callback = idOrObject.complete; + defaultMA = idOrObject.defaultMA; + userBundle = idOrObject.userBundle; + authenticate = idOrObject.authenticate; + console.log('set args based on dictionary'); + } + else + { + id = idOrObject; + } + + shouldAuthenticate = (authenticate !== undefined) + var wrapper = {}; wrapper.other = window.open(genilib.trustedHost + genilib.trustedPath + @@ -21,7 +42,8 @@ genilib.authorize = function(id, cert, callback, defaultMA, userBundle) { data = { certificate: cert, - tool: true + tool: true, + auth: shouldAuthenticate }; if (userBundle) { @@ -40,19 +62,49 @@ genilib.authorize = function(id, cert, callback, defaultMA, userBundle) event.origin === genilib.trustedHost && event.data.id && event.data.id === id && event.data.credential) { + wrapper.credential = event.data.credential; + wrapper.credentialId = event.data.id; + wrapper.authenticationToken = event.data.userToken; + wrapper.encryptedCredential = event.data.encryptedCredential; + if (shouldAuthenticate && event.data.userCertificate) + { + console.log('authenticating'); + authenticate(event.data.userCertificate, wrapper.authenticationToken, wrapper.toolAuthSuccess, wrapper.toolAuthFailure); + } + else + { + console.log('sendAck'); + wrapper.sendAck(); + } + } + }; + + wrapper.sendAck = function () { window.removeEventListener('message', wrapper.listener, false); // wrapper.other.removeEventListener('close', wrapper.close, false); data = { - id: event.data.id, + id: wrapper.credentialId, ack: true }; console.log('Sending ack to ' + genilib.trustedHost); wrapper.other.postMessage(data, genilib.trustedHost); - callback(event.data.credential); - } - }; + callback(wrapper.credential, wrapper.encryptedCredential); + }; + + wrapper.toolAuthSuccess = function (toolToken) { + data = { + id: wrapper.credentialId, + toolToken: toolToken + }; + console.log('Sending tool token to ' + genilib.trustedHost); + wrapper.other.postMessage(data, genilib.trustedHost); + }; + + wrapper.toolAuthFailure = function () { + wrapper.sendAck(); + }; wrapper.close = function (event) { window.removeEventListener('message', wrapper.message, false); diff --git a/xml-signer.js b/xml-signer.js index d2a47fa..63d9b1b 100755 --- a/xml-signer.js +++ b/xml-signer.js @@ -87,6 +87,7 @@ function ($, _, error, forge, sigExport, xmlText, noKeyText, authorizeText) { if (window.opener) { window.addEventListener('message', messageToolCert, false); window.opener.postMessage(data, '*'); + console.log('posting ready message'); } else { var fake_event = { data: { certificate: myCert, tool: true } } messageToolCert(fake_event); @@ -185,6 +186,19 @@ function ($, _, error, forge, sigExport, xmlText, noKeyText, authorizeText) { return result; } + function wrapCertificates(list) + { + var result = ""; + var i = 0; + for (; i < list.length; i += 1) + { + result += "-----BEGIN CERTIFICATE-----\n"; + result += list[i]; + result += "-----END CERTIFICATE-----\n"; + } + return result; + } + function extractKey(pem) { var inKey = false; @@ -339,6 +353,10 @@ function ($, _, error, forge, sigExport, xmlText, noKeyText, authorizeText) { }; } + var speaksForCredential; + var decryptedPrivateKey; + var userSecret; + function clickSign(event) { event.preventDefault(); @@ -358,6 +376,7 @@ function ($, _, error, forge, sigExport, xmlText, noKeyText, authorizeText) { try { var decrypted = forge.pki.decryptRsaPrivateKey(encryptedKey, password); + decryptedPrivateKey = decrypted; var sig = new SignedXml(); sig.addReference("//*[local-name(.)='credential']"); sig.signingKey = decrypted; @@ -367,12 +386,17 @@ function ($, _, error, forge, sigExport, xmlText, noKeyText, authorizeText) { sig.keyInfoProvider = new GENIKeyInfo(certList); } sig.computeSignature(xml); + speaksForCredential = sig.getSignedXml(); + var userToken = generateUserToken(speakerCert); var data = { id: toolId, - credential: sig.getSignedXml() + credential: speaksForCredential, + userToken: userToken, + userCertificate: wrapCertificates(certList) }; if (window.opener) { window.opener.postMessage(data, '*'); + console.log('posting credential'); } else { // This can't be the only way to redirect with POST, sigh... var backForm = document.createElement('form'); @@ -433,15 +457,62 @@ function ($, _, error, forge, sigExport, xmlText, noKeyText, authorizeText) { function messageAck(event) { - if (event.source === window.opener && event.data && event.data.id && - event.data.id === toolId && event.data.ack) - { - window.close(); - } + console.log('messageAck'); + if (event.source === window.opener && event.data && event.data.id && + event.data.id === toolId) + { + if (event.data.ack) + { + window.close(); + } + else if (event.data.toolToken) + { + sendAuthorizationToken(event.data.toolToken); + } + } + } + + function sendAuthorizationToken(toolToken) + { + var encryptedCredential = encryptWithSecrets(userSecret, getToolSecret(toolToken)); + var data = { + id: toolId, + encryptedCredential: encryptedCredential, + credential: speaksForCredential + }; + window.opener.postMessage(data, '*'); + console.log('posting auth token'); + } + + function generateUserToken(speakerCert) + { + var secretBytes = forge.random.getBytesSync(32); + userSecret = forge.util.bytesToHex(secretBytes); + var p7 = forge.pkcs7.createEnvelopedData(); + var cert = forge.pki.certificateFromPem(wrapCertificates([speakerCert])); + p7.addRecipient(cert); + p7.content = forge.util.createBuffer(userSecret); + p7.encrypt(); + return forge.pkcs7.messageToPem(p7); + } + + function getToolSecret(toolToken) + { + var p7 = forge.pkcs7.messageFromPem(toolToken); + p7.decrypt(p7.recipients[0], decryptedPrivateKey); + return p7.content.toString(); + } + + function encryptWithSecrets(userSecret, toolSecret) + { + var md = forge.md.sha256.create(); + md.update(speaksForCredential + userSecret + toolSecret); + return md.digest().toHex(); } function messageCert(event) { + console.log('messageCert'); if (event.source === certWindow && event.data && event.data.authority) { if (event.data.certificate) @@ -483,6 +554,7 @@ function ($, _, error, forge, sigExport, xmlText, noKeyText, authorizeText) { credential: cred }; window.opener.postMessage(data, '*'); + console.log('posting reflect cred'); window.removeEventListener('message', messageCert); window.addEventListener('message', messageAck); }