-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: ida auth sdk #49
Open
naftis
wants to merge
1
commit into
main
Choose a base branch
from
ida-auth
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# certs | ||
certs | ||
|
||
# dependencies | ||
node_modules | ||
package-lock.json | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# MOSIP API Crypto | ||
|
||
Helpers for encryption and decryption of the communication with MOSIP. It's separated here for clarity, as it can be complex at parts. | ||
|
||
These functions take in the following files, which the other packages must supply to these functions. | ||
|
||
- `keystore.p12` | ||
- `ida-partner.crt` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "@opencrvs/mosip-crypto", | ||
"license": "MPL-2.0", | ||
"version": "1.7.0-alpha.16", | ||
"main": "index.js", | ||
"dependencies": { | ||
"@fastify/formbody": "^8.0.1", | ||
"@fastify/static": "^8.0.3", | ||
"@types/node": "^22.4.1", | ||
"@types/node-forge": "^1.3.11", | ||
"casual": "^1.6.2", | ||
"envalid": "^8.0.0", | ||
"fastify": "^5.0.0", | ||
"jose": "^5.9.6", | ||
"jsonwebtoken": "^9.0.2", | ||
"node-forge": "^1.3.1", | ||
"tsx": "^4.19.2", | ||
"typescript": "^5.6.3" | ||
Comment on lines
+7
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. likely most are unneeded |
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import * as crypto from "node:crypto"; | ||
import * as jose from "jose"; | ||
import { base64Encode, padBase64 } from "./utils"; | ||
import forge from "node-forge"; | ||
|
||
export const getPemCertificateThumbprint = (pemCertificate: string) => { | ||
const fingerprint = new crypto.X509Certificate(pemCertificate).fingerprint256; // In "node:crypto", this gives the SHA-256 fingerprint as a hexadecimal string | ||
return Buffer.from(fingerprint.replace(/:/g, ""), "hex"); | ||
}; | ||
|
||
export const urlSafeCertificateThumbprint = (pemCertificate: string) => | ||
padBase64(getPemCertificateThumbprint(pemCertificate).toString("base64url")); | ||
|
||
const SYMMETRIC_NONCE_SIZE = 128 / 8; | ||
const SYMMETRIC_KEY_LENGTH = 256; | ||
|
||
/** Symmetrically (allows to be decrypted by the same key) encrypts the data */ | ||
const symmetricEncrypt = (data: Buffer, key: Buffer) => { | ||
const nonce = crypto.randomBytes(SYMMETRIC_NONCE_SIZE); | ||
const cipher = crypto.createCipheriv("aes-256-gcm", key, nonce, { | ||
authTagLength: 16, | ||
}); | ||
|
||
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]); | ||
const tag = cipher.getAuthTag(); | ||
|
||
return Buffer.concat([encrypted, tag, nonce]); | ||
}; | ||
|
||
/** | ||
* Asymmetrically (allowed to be decrypted by the partner certificate) encrypts the data | ||
*/ | ||
const asymmetricEncrypt = ( | ||
aesKey: Buffer, | ||
encryptPemCertificate: string, | ||
): Buffer => { | ||
const cert = forge.pki.certificateFromPem(encryptPemCertificate); | ||
const publicKey = cert.publicKey as forge.pki.rsa.PublicKey; // Explicitly cast to RSA public key | ||
|
||
const encryptedKey = publicKey.encrypt( | ||
aesKey.toString("binary"), | ||
"RSA-OAEP", | ||
{ | ||
md: forge.md.sha256.create(), | ||
mgf1: forge.mgf.mgf1.create(forge.md.sha256.create()), | ||
label: Buffer.alloc(0), // Explicitly set an empty label | ||
}, | ||
); | ||
|
||
return Buffer.from(encryptedKey, "binary"); | ||
}; | ||
|
||
export const encryptAuthData = ( | ||
data: string, | ||
encryptPemCertificate: string, | ||
) => { | ||
// Generate a random AES Key and encrypt Auth Request Data using the generated random key. | ||
const aesKey = crypto.randomBytes(SYMMETRIC_KEY_LENGTH / 8); | ||
|
||
const encryptedData = symmetricEncrypt(Buffer.from(data, "utf-8"), aesKey); | ||
const encryptedAuthB64Data = padBase64(encryptedData.toString("base64url")); | ||
|
||
// Encrypt the randomly generated key using the IDA partner certificate | ||
const encryptedAesKey = asymmetricEncrypt(aesKey, encryptPemCertificate); | ||
const encryptedAesKeyB64 = encryptedAesKey.toString("base64url"); | ||
|
||
// Generate SHA256 hash for the Auth Request Data | ||
const sha256Hash = crypto | ||
.createHash("sha256") | ||
.update(data) | ||
.digest("hex") | ||
.toUpperCase(); | ||
const authDataHashBuffer = Buffer.from(sha256Hash, "utf-8"); | ||
const encryptedAuthDataHash = symmetricEncrypt(authDataHashBuffer, aesKey); | ||
const encryptedAuthDataHashBase64 = padBase64( | ||
encryptedAuthDataHash.toString("base64url"), | ||
); | ||
|
||
return { | ||
encryptedAuthB64Data, | ||
encryptedAesKeyB64, | ||
encryptedAuthDataHashBase64, | ||
}; | ||
}; | ||
|
||
export async function signAuthRequestData( | ||
authRequestData: string, | ||
encryptPemCertificate: string, | ||
signPemPrivateKey: string, | ||
signPemCertificate: string, | ||
algorithm = "RS256", | ||
) { | ||
const protectedHeader = { | ||
alg: algorithm, | ||
x5c: [base64Encode(signPemCertificate)], | ||
}; | ||
|
||
const unprotectedHeader = { | ||
kid: crypto | ||
.createHash("sha256") | ||
.update(encryptPemCertificate) | ||
.digest("base64url"), | ||
}; | ||
|
||
const privateKey = await jose.importPKCS8(signPemPrivateKey, algorithm); | ||
|
||
const flattenedSign = await new jose.FlattenedSign( | ||
Buffer.from(authRequestData, "utf-8"), | ||
) | ||
.setProtectedHeader(protectedHeader) | ||
.setUnprotectedHeader(unprotectedHeader) | ||
.sign(privateKey); | ||
|
||
const parts = [ | ||
flattenedSign.protected, | ||
"", // No payload in this case | ||
flattenedSign.signature, | ||
]; | ||
return `${parts[0]}..${parts[2]}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { asn1, pkcs12, pki } from "node-forge"; | ||
|
||
/** | ||
* Reads and extracts private key and certificate from a PKCS#12 file. | ||
* @param filePath - The path to the PKCS#12 (.p12) file. | ||
* @param password - The password for decrypting the PKCS#12 file. | ||
* @returns An object containing the private key and certificate in PEM format. | ||
*/ | ||
export const extractKeysFromPkcs12 = ( | ||
fileContents: string, | ||
password: string, | ||
) => { | ||
const p12Asn1 = asn1.fromDer(fileContents); | ||
const p12Object = pkcs12.pkcs12FromAsn1(p12Asn1, password); | ||
|
||
let privateKeyPkcs8: pki.PEM | null = null; | ||
let certificate: pki.PEM | null = null; | ||
|
||
// Extract private key and certificate | ||
p12Object.safeContents.forEach((safeContent) => { | ||
safeContent.safeBags.forEach((safeBag) => { | ||
if (safeBag.type === pki.oids.pkcs8ShroudedKeyBag && safeBag.key) { | ||
// To return PKCS#1: | ||
// privateKeyPkcs1 = pki.privateKeyToPem(safeBag.key); | ||
const privateKeyAsn1 = pki.privateKeyToAsn1(safeBag.key); | ||
const privateKeyPkcs8Asn1 = pki.wrapRsaPrivateKey(privateKeyAsn1); | ||
privateKeyPkcs8 = pki.privateKeyInfoToPem(privateKeyPkcs8Asn1); | ||
} else if (safeBag.type === pki.oids.certBag && safeBag.cert) { | ||
certificate = pki.certificateToPem(safeBag.cert); | ||
} | ||
}); | ||
}); | ||
|
||
if (privateKeyPkcs8 === null) | ||
throw new Error("PEM private key not available in keystore"); | ||
|
||
if (certificate === null) | ||
throw new Error("PEM certificate not available in keystore"); | ||
|
||
return { privateKeyPkcs8, certificate } as { | ||
privateKeyPkcs8: pki.PEM; | ||
certificate: pki.PEM; | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./encrypt"; | ||
export * from "./extract-pkcs12"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export const padBase64 = (str: string) => | ||
str + "=".repeat((4 - (str.length % 4)) % 4); | ||
|
||
export const base64Encode = (input: string) => | ||
Buffer.from(input, "utf8").toString("base64url"); | ||
Comment on lines
+1
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment what and why |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "@opencrvs/mosip-ida-auth-sdk", | ||
"license": "MPL-2.0", | ||
"version": "1.7.0-alpha.16", | ||
"main": "index.js", | ||
"dependencies": { | ||
"@fastify/formbody": "^8.0.1", | ||
"@fastify/static": "^8.0.3", | ||
"@types/node": "^22.4.1", | ||
"@types/node-forge": "^1.3.11", | ||
"casual": "^1.6.2", | ||
"envalid": "^8.0.0", | ||
"fastify": "^5.0.0", | ||
"jose": "^5.9.6", | ||
"jsonwebtoken": "^9.0.2", | ||
"node-forge": "^1.3.1", | ||
"tsx": "^4.19.2", | ||
"typescript": "^5.6.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import MOSIPAuthenticator from "./mosip-authenticator"; | ||
|
||
const main = async () => { | ||
const authenticator = new MOSIPAuthenticator({ | ||
// MOSIP Auth | ||
partnerApiKey: "", | ||
partnerMispLk: "", | ||
partnerId: "", | ||
|
||
// MOSIP Auth Server | ||
idaAuthDomainUri: "https://api-internal.collab.mosip.net", | ||
idaAuthUrl: "https://api.collab.mosip.net/idauthentication/v1/auth", | ||
|
||
// Crypto encrypt | ||
encryptCertPath: "../../certs/ida-partner.crt", | ||
decryptP12FilePath: "../../certs/keystore.p12", | ||
decryptP12FilePassword: "", | ||
|
||
// Crypto signature | ||
signP12FilePath: "../../certs/keystore.p12", | ||
signP12FilePassword: "", | ||
}); | ||
|
||
const response = await authenticator.auth({ | ||
individualId: "6580954839", | ||
individualIdType: "UIN", | ||
demographicData: { | ||
dob: "1992/04/29", | ||
}, | ||
consent: true, | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error(`Error in MOSIP Authenticator: ${await response.text()}`); | ||
} | ||
|
||
console.log(await response.json()); | ||
}; | ||
|
||
main(); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GPT created helper for...