@opengovsg/mockpass 2.9.5 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -15
- package/index.js +1 -30
- package/lib/assertions.js +0 -97
- package/lib/crypto/myinfo-signature.js +1 -82
- package/lib/express/index.js +0 -1
- package/lib/express/myinfo/consent.js +3 -15
- package/lib/express/myinfo/controllers.js +4 -10
- package/lib/express/myinfo/index.js +1 -2
- package/package.json +1 -4
- package/lib/crypto/index.js +0 -61
- package/lib/express/saml.js +0 -195
- package/lib/saml-artifact.js +0 -39
- package/static/myinfo/v2.json +0 -6154
- package/static/saml/corppass.xml +0 -21
- package/static/saml/unsigned-assertion.xml +0 -24
- package/static/saml/unsigned-response.xml +0 -19
package/README.md
CHANGED
|
@@ -18,22 +18,18 @@ A mock SingPass/CorpPass/MyInfo server for dev purposes
|
|
|
18
18
|
Configure your application to point to the following endpoints:
|
|
19
19
|
|
|
20
20
|
SingPass:
|
|
21
|
-
- http://localhost:5156/singpass/logininitial - SAML login redirect with optional page
|
|
22
|
-
- http://localhost:5156/singpass/soap - receives SAML artifact and returns assertion
|
|
23
21
|
- http://localhost:5156/singpass/authorize - OIDC login redirect with optional page
|
|
24
22
|
- http://localhost:5156/singpass/token - receives OIDC authorization code and returns id_token
|
|
25
23
|
|
|
26
24
|
CorpPass:
|
|
27
|
-
- http://localhost:5156/corppass/logininitial
|
|
28
|
-
- http://localhost:5156/corppass/soap
|
|
29
25
|
- http://localhost:5156/corppass/authorize - OIDC login redirect with optional page
|
|
30
26
|
- http://localhost:5156/corppass/token - receives OIDC authorization code and returns id_token
|
|
31
27
|
|
|
32
28
|
MyInfo:
|
|
33
|
-
- http://localhost:5156/myinfo/
|
|
34
|
-
- http://localhost:5156/myinfo/
|
|
35
|
-
- http://localhost:5156/myinfo/
|
|
36
|
-
- http://localhost:5156/myinfo/
|
|
29
|
+
- http://localhost:5156/myinfo/v3/person-basic (exclusive to government systems)
|
|
30
|
+
- http://localhost:5156/myinfo/v3/authorise
|
|
31
|
+
- http://localhost:5156/myinfo/v3/token
|
|
32
|
+
- http://localhost:5156/myinfo/v3/person
|
|
37
33
|
|
|
38
34
|
sgID:
|
|
39
35
|
- http://localhost:5156/sgid/v1/oauth/authorize
|
|
@@ -49,11 +45,6 @@ Alternatively, provide the paths to your app cert as env vars
|
|
|
49
45
|
```
|
|
50
46
|
$ npm install @opengovsg/mockpass
|
|
51
47
|
|
|
52
|
-
# Some familiarity with SAML Artifact Binding is assumed
|
|
53
|
-
# Optional: Configure where MockPass should send SAML artifact to, default endpoint will be `PartnerId` in request query parameter.
|
|
54
|
-
$ export SINGPASS_ASSERT_ENDPOINT=http://localhost:5000/singpass/assert
|
|
55
|
-
$ export CORPPASS_ASSERT_ENDPOINT=http://localhost:5000/corppass/assert
|
|
56
|
-
|
|
57
48
|
# All values shown here are defaults
|
|
58
49
|
$ export MOCKPASS_PORT=5156
|
|
59
50
|
|
|
@@ -69,7 +60,7 @@ $ export ENCRYPT_ASSERTION=false
|
|
|
69
60
|
$ export SIGN_RESPONSE=false
|
|
70
61
|
$ export RESOLVE_ARTIFACT_REQUEST_SIGNED=false
|
|
71
62
|
|
|
72
|
-
# Encrypt payloads returned by /myinfo
|
|
63
|
+
# Encrypt payloads returned by /myinfo/v3/{person, person-basic},
|
|
73
64
|
# equivalent to MyInfo Auth Level L2 (testing and production)
|
|
74
65
|
$ export ENCRYPT_MYINFO=false
|
|
75
66
|
|
|
@@ -89,7 +80,7 @@ who then need to connect to the staging servers hosted by SingPass/CorpPass,
|
|
|
89
80
|
which may not always be available (eg, down for maintenance, or no Internet).
|
|
90
81
|
|
|
91
82
|
MockPass tries to solves this by providing an extremely lightweight implementation
|
|
92
|
-
of
|
|
83
|
+
of an OIDC Provider that returns mock SingPass and CorpPass assertions.
|
|
93
84
|
It optionally provides a mock login page that (badly) mimics the SingPass/CorpPass
|
|
94
85
|
login experience.
|
|
95
86
|
|
package/index.js
CHANGED
|
@@ -5,25 +5,10 @@ const morgan = require('morgan')
|
|
|
5
5
|
const path = require('path')
|
|
6
6
|
require('dotenv').config()
|
|
7
7
|
|
|
8
|
-
const {
|
|
9
|
-
configSAML,
|
|
10
|
-
configOIDC,
|
|
11
|
-
configMyInfo,
|
|
12
|
-
configSGID,
|
|
13
|
-
} = require('./lib/express')
|
|
8
|
+
const { configOIDC, configMyInfo, configSGID } = require('./lib/express')
|
|
14
9
|
|
|
15
10
|
const PORT = process.env.MOCKPASS_PORT || process.env.PORT || 5156
|
|
16
11
|
|
|
17
|
-
if (
|
|
18
|
-
!process.env.SINGPASS_ASSERT_ENDPOINT &&
|
|
19
|
-
!process.env.CORPPASS_ASSERT_ENDPOINT
|
|
20
|
-
) {
|
|
21
|
-
console.warn(
|
|
22
|
-
'SINGPASS_ASSERT_ENDPOINT or CORPPASS_ASSERT_ENDPOINT is not set. ' +
|
|
23
|
-
'Value of `PartnerId` request query parameter in redirect URL will be used.',
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
12
|
const serviceProvider = {
|
|
28
13
|
cert: fs.readFileSync(
|
|
29
14
|
path.resolve(
|
|
@@ -49,18 +34,6 @@ const cryptoConfig = {
|
|
|
49
34
|
|
|
50
35
|
const options = {
|
|
51
36
|
serviceProvider,
|
|
52
|
-
idpConfig: {
|
|
53
|
-
singPass: {
|
|
54
|
-
id:
|
|
55
|
-
process.env.SINGPASS_IDP_ID || 'http://localhost:5156/singpass/saml20',
|
|
56
|
-
assertEndpoint: process.env.SINGPASS_ASSERT_ENDPOINT,
|
|
57
|
-
},
|
|
58
|
-
corpPass: {
|
|
59
|
-
id:
|
|
60
|
-
process.env.CORPPASS_IDP_ID || 'http://localhost:5156/corppass/saml20',
|
|
61
|
-
assertEndpoint: process.env.CORPPASS_ASSERT_ENDPOINT,
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
37
|
showLoginPage: (req) =>
|
|
65
38
|
(req.header('X-Show-Login-Page') || process.env.SHOW_LOGIN_PAGE) === 'true',
|
|
66
39
|
encryptMyInfo: process.env.ENCRYPT_MYINFO === 'true',
|
|
@@ -70,12 +43,10 @@ const options = {
|
|
|
70
43
|
const app = express()
|
|
71
44
|
app.use(morgan('combined'))
|
|
72
45
|
|
|
73
|
-
configSAML(app, options)
|
|
74
46
|
configOIDC(app, options)
|
|
75
47
|
configSGID(app, options)
|
|
76
48
|
|
|
77
49
|
configMyInfo.consent(app)
|
|
78
|
-
configMyInfo.v2(app, options)
|
|
79
50
|
configMyInfo.v3(app, options)
|
|
80
51
|
|
|
81
52
|
app.enable('trust proxy')
|
package/lib/assertions.js
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
const base64 = require('base-64')
|
|
2
1
|
const crypto = require('crypto')
|
|
3
2
|
const fs = require('fs')
|
|
4
|
-
const { render } = require('mustache')
|
|
5
|
-
const moment = require('moment')
|
|
6
3
|
const jose = require('node-jose')
|
|
7
4
|
const path = require('path')
|
|
8
5
|
|
|
9
6
|
const readFrom = (p) => fs.readFileSync(path.resolve(__dirname, p), 'utf8')
|
|
10
7
|
|
|
11
|
-
const TEMPLATE = readFrom('../static/saml/unsigned-assertion.xml')
|
|
12
|
-
const corpPassTemplate = readFrom('../static/saml/corppass.xml')
|
|
13
|
-
|
|
14
|
-
const defaultAudience =
|
|
15
|
-
process.env.SERVICE_PROVIDER_ENTITY_ID ||
|
|
16
|
-
'http://sp.example.com/demo1/metadata.php'
|
|
17
|
-
|
|
18
8
|
const signingPem = fs.readFileSync(
|
|
19
9
|
path.resolve(__dirname, '../static/certs/spcp-key.pem'),
|
|
20
10
|
)
|
|
@@ -34,95 +24,9 @@ const hashToken = (token) => {
|
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
const myinfo = {
|
|
37
|
-
v2: JSON.parse(readFrom('../static/myinfo/v2.json')),
|
|
38
27
|
v3: JSON.parse(readFrom('../static/myinfo/v3.json')),
|
|
39
28
|
}
|
|
40
29
|
|
|
41
|
-
const saml = {
|
|
42
|
-
singPass: [
|
|
43
|
-
{ nric: 'S8979373D' },
|
|
44
|
-
{ nric: 'S8116474F' },
|
|
45
|
-
{ nric: 'S8723211E' },
|
|
46
|
-
{ nric: 'S5062854Z' },
|
|
47
|
-
{ nric: 'T0066846F' },
|
|
48
|
-
{ nric: 'F9477325W' },
|
|
49
|
-
{ nric: 'S3000024B' },
|
|
50
|
-
{ nric: 'S6005040F' },
|
|
51
|
-
{ nric: 'S6005041D' },
|
|
52
|
-
{ nric: 'S6005042B' },
|
|
53
|
-
{ nric: 'S6005043J' },
|
|
54
|
-
{ nric: 'S6005044I' },
|
|
55
|
-
{ nric: 'S6005045G' },
|
|
56
|
-
{ nric: 'S6005046E' },
|
|
57
|
-
{ nric: 'S6005047C' },
|
|
58
|
-
{ nric: 'S6005064C' },
|
|
59
|
-
{ nric: 'S6005065A' },
|
|
60
|
-
{ nric: 'S6005066Z' },
|
|
61
|
-
{ nric: 'S6005037F' },
|
|
62
|
-
{ nric: 'S6005038D' },
|
|
63
|
-
{ nric: 'S6005039B' },
|
|
64
|
-
{ nric: 'G1612357P' },
|
|
65
|
-
{ nric: 'G1612358M' },
|
|
66
|
-
{ nric: 'F1612359P' },
|
|
67
|
-
{ nric: 'F1612360U' },
|
|
68
|
-
{ nric: 'F1612361R' },
|
|
69
|
-
{ nric: 'F1612362P' },
|
|
70
|
-
{ nric: 'F1612363M' },
|
|
71
|
-
{ nric: 'F1612364K' },
|
|
72
|
-
{ nric: 'F1612365W' },
|
|
73
|
-
{ nric: 'F1612366T' },
|
|
74
|
-
{ nric: 'F1612367Q' },
|
|
75
|
-
{ nric: 'F1612358R' },
|
|
76
|
-
{ nric: 'F1612354N' },
|
|
77
|
-
{ nric: 'F1612357U' },
|
|
78
|
-
...Object.keys(myinfo.v2.personas).map((nric) => ({ nric })),
|
|
79
|
-
],
|
|
80
|
-
corpPass: [
|
|
81
|
-
{ nric: 'S8979373D', uen: '123456789A' },
|
|
82
|
-
{ nric: 'S8116474F', uen: '123456789A' },
|
|
83
|
-
{ nric: 'S8723211E', uen: '123456789A' },
|
|
84
|
-
{ nric: 'S5062854Z', uen: '123456789B' },
|
|
85
|
-
{ nric: 'T0066846F', uen: '123456789B' },
|
|
86
|
-
{ nric: 'F9477325W', uen: '123456789B' },
|
|
87
|
-
{ nric: 'S3000024B', uen: '123456789C' },
|
|
88
|
-
{ nric: 'S6005040F', uen: '123456789C' },
|
|
89
|
-
],
|
|
90
|
-
create: {
|
|
91
|
-
singPass: (
|
|
92
|
-
{ nric },
|
|
93
|
-
issuer,
|
|
94
|
-
recipient,
|
|
95
|
-
inResponseTo,
|
|
96
|
-
audience = defaultAudience,
|
|
97
|
-
) =>
|
|
98
|
-
render(TEMPLATE, {
|
|
99
|
-
name: 'UserName',
|
|
100
|
-
value: nric,
|
|
101
|
-
issueInstant: moment.utc().format(),
|
|
102
|
-
recipient,
|
|
103
|
-
issuer,
|
|
104
|
-
inResponseTo,
|
|
105
|
-
audience,
|
|
106
|
-
}),
|
|
107
|
-
corpPass: (
|
|
108
|
-
{ nric, uen },
|
|
109
|
-
issuer,
|
|
110
|
-
recipient,
|
|
111
|
-
inResponseTo,
|
|
112
|
-
audience = defaultAudience,
|
|
113
|
-
) =>
|
|
114
|
-
render(TEMPLATE, {
|
|
115
|
-
issueInstant: moment.utc().format(),
|
|
116
|
-
name: uen,
|
|
117
|
-
value: base64.encode(render(corpPassTemplate, { nric, uen })),
|
|
118
|
-
recipient,
|
|
119
|
-
issuer,
|
|
120
|
-
inResponseTo,
|
|
121
|
-
audience,
|
|
122
|
-
}),
|
|
123
|
-
},
|
|
124
|
-
}
|
|
125
|
-
|
|
126
30
|
const oidc = {
|
|
127
31
|
singPass: [
|
|
128
32
|
{ nric: 'S8979373D', uuid: 'a9865837-7bd7-46ac-bef4-42a76a946424' },
|
|
@@ -321,7 +225,6 @@ const oidc = {
|
|
|
321
225
|
}
|
|
322
226
|
|
|
323
227
|
module.exports = {
|
|
324
|
-
saml,
|
|
325
228
|
oidc,
|
|
326
229
|
myinfo,
|
|
327
230
|
}
|
|
@@ -1,86 +1,5 @@
|
|
|
1
1
|
const _ = require('lodash')
|
|
2
2
|
|
|
3
|
-
const apex = function apex(authHeader, req, context = {}) {
|
|
4
|
-
const authHeaderFieldPairs = _(authHeader)
|
|
5
|
-
.replace(/"/g, '')
|
|
6
|
-
.replace(/apex_l2_eg_/g, '')
|
|
7
|
-
.split(',')
|
|
8
|
-
.map((v) => v.replace('=', '~').split('~'))
|
|
9
|
-
|
|
10
|
-
const authHeaderFields = _(authHeaderFieldPairs)
|
|
11
|
-
.fromPairs()
|
|
12
|
-
.mapKeys((v, k) => _.camelCase(k))
|
|
13
|
-
.value()
|
|
14
|
-
|
|
15
|
-
const url = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`
|
|
16
|
-
|
|
17
|
-
const { clientSecret, redirectURI } = context
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
method: httpMethod,
|
|
21
|
-
query: { attributes, singpassEserviceId },
|
|
22
|
-
} = req
|
|
23
|
-
|
|
24
|
-
const { code } = req.body || {}
|
|
25
|
-
|
|
26
|
-
const {
|
|
27
|
-
signature,
|
|
28
|
-
appId,
|
|
29
|
-
appId: clientId,
|
|
30
|
-
nonce,
|
|
31
|
-
timestamp,
|
|
32
|
-
} = authHeaderFields
|
|
33
|
-
|
|
34
|
-
const baseString = req.path.endsWith('/token')
|
|
35
|
-
? httpMethod.toUpperCase() +
|
|
36
|
-
// url string replacement was dictated by MyInfo docs - no explanation
|
|
37
|
-
// was provided for why this is necessary
|
|
38
|
-
'&' +
|
|
39
|
-
url.replace('.api.gov.sg', '.e.api.gov.sg') +
|
|
40
|
-
'&apex_l2_eg_app_id=' +
|
|
41
|
-
appId +
|
|
42
|
-
'&apex_l2_eg_nonce=' +
|
|
43
|
-
nonce +
|
|
44
|
-
'&apex_l2_eg_signature_method=SHA256withRSA' +
|
|
45
|
-
'&apex_l2_eg_timestamp=' +
|
|
46
|
-
timestamp +
|
|
47
|
-
'&apex_l2_eg_version=1.0' +
|
|
48
|
-
'&client_id=' +
|
|
49
|
-
clientId +
|
|
50
|
-
'&client_secret=' +
|
|
51
|
-
clientSecret +
|
|
52
|
-
'&code=' +
|
|
53
|
-
code +
|
|
54
|
-
'&grant_type=authorization_code' +
|
|
55
|
-
'&redirect_uri=' +
|
|
56
|
-
redirectURI
|
|
57
|
-
: httpMethod.toUpperCase() +
|
|
58
|
-
// url string replacement was dictated by MyInfo docs - no explanation
|
|
59
|
-
// was provided for why this is necessary
|
|
60
|
-
'&' +
|
|
61
|
-
url.replace('.api.gov.sg', '.e.api.gov.sg') +
|
|
62
|
-
'&apex_l2_eg_app_id=' +
|
|
63
|
-
appId +
|
|
64
|
-
'&apex_l2_eg_nonce=' +
|
|
65
|
-
nonce +
|
|
66
|
-
'&apex_l2_eg_signature_method=SHA256withRSA' +
|
|
67
|
-
'&apex_l2_eg_timestamp=' +
|
|
68
|
-
timestamp +
|
|
69
|
-
'&apex_l2_eg_version=1.0' +
|
|
70
|
-
'&attributes=' +
|
|
71
|
-
attributes +
|
|
72
|
-
'&client_id=' +
|
|
73
|
-
clientId +
|
|
74
|
-
(req.path.includes('/person-basic')
|
|
75
|
-
? '&singpassEserviceId=' + singpassEserviceId
|
|
76
|
-
: '')
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
signature,
|
|
80
|
-
baseString,
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
3
|
const pki = function pki(authHeader, req, context = {}) {
|
|
85
4
|
const authHeaderFieldPairs = _(authHeader)
|
|
86
5
|
.replace(/"/g, '')
|
|
@@ -150,4 +69,4 @@ const pki = function pki(authHeader, req, context = {}) {
|
|
|
150
69
|
}
|
|
151
70
|
}
|
|
152
71
|
|
|
153
|
-
module.exports = { pki
|
|
72
|
+
module.exports = { pki }
|
package/lib/express/index.js
CHANGED
|
@@ -9,7 +9,6 @@ const { v1: uuid } = require('uuid')
|
|
|
9
9
|
|
|
10
10
|
const assertions = require('../../assertions')
|
|
11
11
|
const { lookUpByAuthCode } = require('../../auth-code')
|
|
12
|
-
const { lookUpBySamlArtifact } = require('../../saml-artifact')
|
|
13
12
|
|
|
14
13
|
const MYINFO_ASSERT_ENDPOINT = '/consent/myinfo-com'
|
|
15
14
|
const AUTHORIZE_ENDPOINT = '/consent/oauth2/authorize'
|
|
@@ -43,11 +42,6 @@ const authorize = (redirectTo) => (req, res) => {
|
|
|
43
42
|
res.redirect(redirectTo(relayState))
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
const authorizeViaSAML = authorize(
|
|
47
|
-
(relayState) =>
|
|
48
|
-
`/singpass/logininitial?esrvcID=MYINFO-CONSENTPLATFORM&PartnerId=${MYINFO_ASSERT_ENDPOINT}&Target=${relayState}`,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
45
|
const authorizeViaOIDC = authorize(
|
|
52
46
|
(state) =>
|
|
53
47
|
`/singpass/authorize?client_id=MYINFO-CONSENTPLATFORM&redirect_uri=${MYINFO_ASSERT_ENDPOINT}&state=${state}`,
|
|
@@ -59,14 +53,9 @@ function config(app) {
|
|
|
59
53
|
const artifact = rawArtifact.replace(/ /g, '+')
|
|
60
54
|
const state = req.query.RelayState || req.query.state
|
|
61
55
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
myinfoVersion = 'v3'
|
|
66
|
-
} else {
|
|
67
|
-
profile = lookUpBySamlArtifact(artifact)
|
|
68
|
-
myinfoVersion = 'v2'
|
|
69
|
-
}
|
|
56
|
+
const profile = lookUpByAuthCode(artifact).profile
|
|
57
|
+
const myinfoVersion = 'v3'
|
|
58
|
+
|
|
70
59
|
const { nric: id } = profile
|
|
71
60
|
|
|
72
61
|
const persona = assertions.myinfo[myinfoVersion].personas[id]
|
|
@@ -147,7 +136,6 @@ function config(app) {
|
|
|
147
136
|
}
|
|
148
137
|
|
|
149
138
|
module.exports = {
|
|
150
|
-
authorizeViaSAML,
|
|
151
139
|
authorizeViaOIDC,
|
|
152
140
|
authorizations,
|
|
153
141
|
config,
|
|
@@ -31,12 +31,9 @@ module.exports =
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const encryptPersona = async (persona) => {
|
|
34
|
-
const signedPersona =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
algorithm: 'RS256',
|
|
38
|
-
})
|
|
39
|
-
: persona
|
|
34
|
+
const signedPersona = jwt.sign(persona, MOCKPASS_PRIVATE_KEY, {
|
|
35
|
+
algorithm: 'RS256',
|
|
36
|
+
})
|
|
40
37
|
const serviceCertAsKey = await jose.JWK.asKey(serviceProvider.cert, 'pem')
|
|
41
38
|
const encryptedAndSignedPersona = await jose.JWE.createEncrypt(
|
|
42
39
|
{ format: 'compact' },
|
|
@@ -126,10 +123,7 @@ module.exports =
|
|
|
126
123
|
}
|
|
127
124
|
})
|
|
128
125
|
|
|
129
|
-
app.get(
|
|
130
|
-
`/myinfo/${version}/authorise`,
|
|
131
|
-
version === 'v3' ? consent.authorizeViaOIDC : consent.authorizeViaSAML,
|
|
132
|
-
)
|
|
126
|
+
app.get(`/myinfo/${version}/authorise`, consent.authorizeViaOIDC)
|
|
133
127
|
|
|
134
128
|
app.post(
|
|
135
129
|
`/myinfo/${version}/token`,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const { config: consent } = require('./consent')
|
|
2
2
|
const controllers = require('./controllers')
|
|
3
3
|
|
|
4
|
-
const { pki
|
|
4
|
+
const { pki } = require('../../crypto/myinfo-signature')
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
7
|
consent,
|
|
8
|
-
v2: controllers('v2', apex),
|
|
9
8
|
v3: controllers('v3', pki),
|
|
10
9
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengovsg/mockpass",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "A mock SingPass/CorpPass server for dev purposes",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -44,13 +44,10 @@
|
|
|
44
44
|
"express": "^4.16.3",
|
|
45
45
|
"jsonwebtoken": "^8.4.0",
|
|
46
46
|
"lodash": "^4.17.11",
|
|
47
|
-
"moment": "^2.24.0",
|
|
48
47
|
"morgan": "^1.9.1",
|
|
49
48
|
"mustache": "^4.2.0",
|
|
50
49
|
"node-jose": "^2.0.0",
|
|
51
50
|
"uuid": "^9.0.0",
|
|
52
|
-
"xml-crypto": "^3.0.0",
|
|
53
|
-
"xml-encryption": "^3.0.0",
|
|
54
51
|
"xpath": "0.0.32"
|
|
55
52
|
},
|
|
56
53
|
"devDependencies": {
|
package/lib/crypto/index.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const { SignedXml } = require('xml-crypto')
|
|
4
|
-
const { encrypt } = require('xml-encryption')
|
|
5
|
-
const xpath = require('xpath')
|
|
6
|
-
|
|
7
|
-
module.exports = (serviceProvider) => {
|
|
8
|
-
// NOTE - the typo in keyEncryptionAlgorighm is deliberate
|
|
9
|
-
const ENCRYPT_OPTIONS = {
|
|
10
|
-
rsa_pub: serviceProvider.pubKey,
|
|
11
|
-
pem: serviceProvider.cert,
|
|
12
|
-
encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
|
|
13
|
-
keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-1_5',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
verifySignature(xml) {
|
|
18
|
-
const [signature] =
|
|
19
|
-
xpath.select("//*[local-name(.)='Signature']", xml) || []
|
|
20
|
-
const [artifactResolvePayload] =
|
|
21
|
-
xpath.select("//*[local-name(.)='ArtifactResolve']", xml) || []
|
|
22
|
-
const verifier = new SignedXml()
|
|
23
|
-
verifier.keyInfoProvider = { getKey: () => ENCRYPT_OPTIONS.pem }
|
|
24
|
-
verifier.loadSignature(signature.toString())
|
|
25
|
-
return verifier.checkSignature(artifactResolvePayload.toString())
|
|
26
|
-
},
|
|
27
|
-
|
|
28
|
-
sign(payload, reference) {
|
|
29
|
-
const sig = new SignedXml()
|
|
30
|
-
const transforms = [
|
|
31
|
-
'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
|
|
32
|
-
'http://www.w3.org/2001/10/xml-exc-c14n#',
|
|
33
|
-
]
|
|
34
|
-
const digestAlgorithm = 'http://www.w3.org/2001/04/xmlenc#sha256'
|
|
35
|
-
sig.addReference(reference, transforms, digestAlgorithm)
|
|
36
|
-
|
|
37
|
-
sig.signingKey = fs.readFileSync(
|
|
38
|
-
path.resolve(__dirname, '../../static/certs/spcp-key.pem'),
|
|
39
|
-
)
|
|
40
|
-
sig.signatureAlgorithm =
|
|
41
|
-
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
|
42
|
-
const options = {
|
|
43
|
-
prefix: 'ds',
|
|
44
|
-
location: { reference, action: 'prepend' },
|
|
45
|
-
}
|
|
46
|
-
sig.computeSignature(payload, options)
|
|
47
|
-
return sig.getSignedXml()
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
promiseToEncryptAssertion: (assertion) =>
|
|
51
|
-
new Promise((resolve, reject) => {
|
|
52
|
-
encrypt(assertion, ENCRYPT_OPTIONS, (err, data) =>
|
|
53
|
-
err
|
|
54
|
-
? reject(err)
|
|
55
|
-
: resolve(
|
|
56
|
-
`<saml:EncryptedAssertion>${data}</saml:EncryptedAssertion>`,
|
|
57
|
-
),
|
|
58
|
-
)
|
|
59
|
-
}),
|
|
60
|
-
}
|
|
61
|
-
}
|