Skip to content

Commit

Permalink
fix: make custom NRIC SAML artifact spec compliant
Browse files Browse the repository at this point in the history
  • Loading branch information
whipermr5 committed Aug 1, 2021
1 parent 5c718c5 commit 3428070
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 24 deletions.
6 changes: 3 additions & 3 deletions lib/express/myinfo/consent.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ function config(app) {
const rawArtifact = req.query.SAMLart || req.query.code
const artifact = rawArtifact.replace(/ /g, '+')
const artifactBuffer = Buffer.from(artifact, 'base64')
const artifactMessage = artifactBuffer.toString('utf8', 24)
let index = artifactBuffer.readInt8(artifactBuffer.length - 1)

const state = req.query.RelayState || req.query.state
let id
const isRawNRIC = rawArtifact.length === 9
if (isRawNRIC) {
id = rawArtifact
if (artifactMessage.startsWith('customNric:')) {
id = artifactMessage.slice('customNric:'.length)
} else {
const assertionType = req.query.code ? 'oidc' : 'saml'

Expand Down
2 changes: 1 addition & 1 deletion lib/express/oidc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const path = require('path')
const ExpiryMap = require('expiry-map')

const assertions = require('../assertions')
const samlArtifact = require('../saml-artifact')
const { samlArtifact } = require('../saml-artifact')

const LOGIN_TEMPLATE = fs.readFileSync(
path.resolve(__dirname, '../../static/html/login-page.html'),
Expand Down
28 changes: 16 additions & 12 deletions lib/express/saml.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const moment = require('moment')

const assertions = require('../assertions')
const crypto = require('../crypto')
const samlArtifact = require('../saml-artifact')
const { samlArtifact, hashPartnerId } = require('../saml-artifact')

const domParser = new DOMParser()
const dom = (xmlString) => domParser.parseFromString(xmlString)
Expand Down Expand Up @@ -44,27 +44,28 @@ function config(
? MYINFO_ASSERT_ENDPOINT
: idpConfig[idp].assertEndpoint || req.query.PartnerId
const relayState = req.query.Target
const partnerId = idpConfig[idp].id
if (showLoginPage) {
const saml = assertions.saml[idp]
const values = saml.map((rawId, index) => {
const samlArt = encodeURIComponent(
samlArtifact(idpConfig[idp].id, index),
)
const samlArt = encodeURIComponent(samlArtifact(partnerId, index))
let assertURL = `${assertEndpoint}?SAMLart=${samlArt}`
if (relayState !== undefined) {
assertURL += `&RelayState=${encodeURIComponent(relayState)}`
}
const id = idGenerator[idp](rawId)
return { id, assertURL }
})
const hashedPartnerId = hashPartnerId(partnerId)
const response = render(LOGIN_TEMPLATE, {
values,
assertEndpoint,
relayState,
hashedPartnerId,
})
res.send(response)
} else {
const samlArt = encodeURIComponent(samlArtifact(idpConfig[idp].id))
const samlArt = encodeURIComponent(samlArtifact(partnerId))
let assertURL = `${assertEndpoint}?SAMLart=${samlArt}`
if (relayState !== undefined) {
assertURL += `&RelayState=${encodeURIComponent(relayState)}`
Expand Down Expand Up @@ -98,13 +99,16 @@ function config(
xml,
)
console.warn(`Received SAML Artifact ${samlArtifact}`)
let nric = samlArtifact
const isRawNRIC = samlArtifact.length === 9
if (!isRawNRIC) {
// Handle encoded base64 Artifact
// Take the template and plug in the typical SingPass/CorpPass response
// Sign and encrypt the assertion
const samlArtifactBuffer = Buffer.from(samlArtifact, 'base64')
// Handle encoded base64 Artifact
// Take the template and plug in the typical SingPass/CorpPass response
// Sign and encrypt the assertion
const samlArtifactBuffer = Buffer.from(samlArtifact, 'base64')
const samlArtifactMessage = samlArtifactBuffer.toString('utf8', 24)

let nric
if (samlArtifactMessage.startsWith('customNric:')) {
nric = samlArtifactMessage.slice('customNric:'.length)
} else {
let index = samlArtifactBuffer.readInt8(
samlArtifactBuffer.length - 1,
)
Expand Down
2 changes: 1 addition & 1 deletion lib/express/sgid.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const path = require('path')
const ExpiryMap = require('expiry-map')

const assertions = require('../assertions')
const samlArtifact = require('../saml-artifact')
const { samlArtifact } = require('../saml-artifact')

const LOGIN_TEMPLATE = fs.readFileSync(
path.resolve(__dirname, '../../static/html/login-page.html'),
Expand Down
11 changes: 6 additions & 5 deletions lib/saml-artifact.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ const crypto = require('crypto')
* the hash of the partner id, followed by 20 random bytes
*/
function samlArtifact(partnerId, index) {
let hashedPartnerId = crypto
.createHash('sha1')
.update(partnerId, 'utf8')
.digest('hex')
const hashedPartnerId = hashPartnerId(partnerId)
const randomBytes = crypto.randomBytes(19).toString('hex')
const indexBuffer = Buffer.alloc(1)
indexBuffer.writeInt8(index || index === 0 ? index : -1)
Expand All @@ -28,4 +25,8 @@ function samlArtifact(partnerId, index) {
).toString('base64')
}

module.exports = samlArtifact
function hashPartnerId(partnerId) {
return crypto.createHash('sha1').update(partnerId, 'utf8').digest('hex')
}

module.exports = { samlArtifact, hashPartnerId }
25 changes: 25 additions & 0 deletions public/mockpass/resources/js/login-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,31 @@ function invalidLoginAction(errorMessage, captchaVal) {
}
}

function hexEncode(str) {
var result = '';
for (var i = 0; i < str.length; i++) {
result += str.charCodeAt(i).toString(16);
}
return result;
}

function hexToBase64(hexString) {
return btoa(hexString.match(/\w{2}/g).map(function(a) {
return String.fromCharCode(parseInt(a, 16));
}).join(''));
}

function generateSamlArtFromCustomNric() {
var customNric = document.getElementById('customNric').value;
if (customNric.length !== 9) {
return false;
}
var hashedPartnerId = document.getElementById('hashedPartnerId').value;
var artifactDataHex = '00040000' + hashedPartnerId + hexEncode('customNric:' + customNric);
document.getElementById('customNricSamlArt').value = hexToBase64(artifactDataHex);
return true;
}

/*******************************************************************************
* WOGAA RELATED METHODS STARTS
******************************************************************************/
Expand Down
6 changes: 4 additions & 2 deletions static/html/login-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,15 @@
<input type="hidden" name="CSRFToken" value="null" />
</div>
</form>
<form action="{{assertEndpoint}}" method="get">
<form action="{{assertEndpoint}}" method="get" onsubmit="return generateSamlArtFromCustomNric()">
<br>
{{#assertEndpoint}}
<h6>or with your own user</h6>
<br>
<input type="hidden" name="RelayState" value="{{ relayState }}" />
<input maxlength="9" name="SAMLart" placeholder="NRIC" value="S1234567A" style="width: 100%; border: 2px solid #ccc; border-radius: 5px; background: white; color: rgb(42, 45, 51); text-align: left;">
<input type="hidden" id="hashedPartnerId" value="{{ hashedPartnerId }}" />
<input minlength="9" maxlength="9" id="customNric" placeholder="NRIC" value="S1234567A" style="width: 100%; border: 2px solid #ccc; border-radius: 5px; background: white; color: rgb(42, 45, 51); text-align: left;">
<input type="hidden" id="customNricSamlArt" name="SAMLart" />
<button autofocus="" type="submit">Login</button>
<br>
<br>
Expand Down

0 comments on commit 3428070

Please sign in to comment.