@pagopa/io-react-native-wallet 0.23.0 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +1 -0
- package/lib/commonjs/credential/index.js +3 -1
- package/lib/commonjs/credential/index.js.map +1 -1
- package/lib/commonjs/credential/trustmark/README.md +62 -0
- package/lib/commonjs/credential/trustmark/get-credential-trustmark.js +72 -0
- package/lib/commonjs/credential/trustmark/get-credential-trustmark.js.map +1 -0
- package/lib/commonjs/credential/trustmark/index.js +13 -0
- package/lib/commonjs/credential/trustmark/index.js.map +1 -0
- package/lib/commonjs/utils/string.js +50 -0
- package/lib/commonjs/utils/string.js.map +1 -0
- package/lib/module/credential/index.js +2 -1
- package/lib/module/credential/index.js.map +1 -1
- package/lib/module/credential/trustmark/README.md +62 -0
- package/lib/module/credential/trustmark/get-credential-trustmark.js +63 -0
- package/lib/module/credential/trustmark/get-credential-trustmark.js.map +1 -0
- package/lib/module/credential/trustmark/index.js +3 -0
- package/lib/module/credential/trustmark/index.js.map +1 -0
- package/lib/module/utils/string.js +43 -0
- package/lib/module/utils/string.js.map +1 -0
- package/lib/typescript/credential/index.d.ts +2 -1
- package/lib/typescript/credential/index.d.ts.map +1 -1
- package/lib/typescript/credential/trustmark/get-credential-trustmark.d.ts +50 -0
- package/lib/typescript/credential/trustmark/get-credential-trustmark.d.ts.map +1 -0
- package/lib/typescript/credential/trustmark/index.d.ts +4 -0
- package/lib/typescript/credential/trustmark/index.d.ts.map +1 -0
- package/lib/typescript/utils/string.d.ts +17 -0
- package/lib/typescript/utils/string.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/credential/index.ts +2 -1
- package/src/credential/trustmark/README.md +62 -0
- package/src/credential/trustmark/get-credential-trustmark.ts +111 -0
- package/src/credential/trustmark/index.ts +8 -0
- package/src/utils/string.ts +45 -0
package/README.md
CHANGED
@@ -146,6 +146,7 @@ Different flows are provided to perform common operations. Each flow is a set of
|
|
146
146
|
- [Issuance](./src/credential/issuance/README.md)
|
147
147
|
- [Presentation](./src/credential/presentation/README.md) (TODO)
|
148
148
|
- [Status](./src/credential/status/README.md)
|
149
|
+
- [Trustmark](./src/credential/trustmark/README.md)
|
149
150
|
|
150
151
|
### Example
|
151
152
|
|
@@ -3,13 +3,15 @@
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
4
4
|
value: true
|
5
5
|
});
|
6
|
-
exports.Status = exports.Presentation = exports.Issuance = void 0;
|
6
|
+
exports.Trustmark = exports.Status = exports.Presentation = exports.Issuance = void 0;
|
7
7
|
var Issuance = _interopRequireWildcard(require("./issuance"));
|
8
8
|
exports.Issuance = Issuance;
|
9
9
|
var Presentation = _interopRequireWildcard(require("./presentation"));
|
10
10
|
exports.Presentation = Presentation;
|
11
11
|
var Status = _interopRequireWildcard(require("./status"));
|
12
12
|
exports.Status = Status;
|
13
|
+
var Trustmark = _interopRequireWildcard(require("./trustmark"));
|
14
|
+
exports.Trustmark = Trustmark;
|
13
15
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
14
16
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
15
17
|
//# sourceMappingURL=index.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"names":["Issuance","_interopRequireWildcard","require","exports","Presentation","Status","_getRequireWildcardCache","nodeInterop","WeakMap","cacheBabelInterop","cacheNodeInterop","obj","__esModule","default","cache","has","get","newObj","hasPropertyDescriptor","Object","defineProperty","getOwnPropertyDescriptor","key","prototype","hasOwnProperty","call","desc","set"],"sourceRoot":"../../../src","sources":["credential/index.ts"],"mappings":";;;;;;AAAA,IAAAA,QAAA,GAAAC,uBAAA,CAAAC,OAAA;AAAuCC,OAAA,CAAAH,QAAA,GAAAA,QAAA;AACvC,IAAAI,YAAA,GAAAH,uBAAA,CAAAC,OAAA;AAA+CC,OAAA,CAAAC,YAAA,GAAAA,YAAA;AAC/C,IAAAC,MAAA,GAAAJ,uBAAA,CAAAC,OAAA;AAAmCC,OAAA,CAAAE,MAAA,GAAAA,MAAA;AAAA,SAAAC,yBAAAC,WAAA,eAAAC,OAAA,kCAAAC,iBAAA,OAAAD,OAAA,QAAAE,gBAAA,OAAAF,OAAA,YAAAF,wBAAA,YAAAA,CAAAC,WAAA,WAAAA,WAAA,GAAAG,gBAAA,GAAAD,iBAAA,KAAAF,WAAA;AAAA,
|
1
|
+
{"version":3,"names":["Issuance","_interopRequireWildcard","require","exports","Presentation","Status","Trustmark","_getRequireWildcardCache","nodeInterop","WeakMap","cacheBabelInterop","cacheNodeInterop","obj","__esModule","default","cache","has","get","newObj","hasPropertyDescriptor","Object","defineProperty","getOwnPropertyDescriptor","key","prototype","hasOwnProperty","call","desc","set"],"sourceRoot":"../../../src","sources":["credential/index.ts"],"mappings":";;;;;;AAAA,IAAAA,QAAA,GAAAC,uBAAA,CAAAC,OAAA;AAAuCC,OAAA,CAAAH,QAAA,GAAAA,QAAA;AACvC,IAAAI,YAAA,GAAAH,uBAAA,CAAAC,OAAA;AAA+CC,OAAA,CAAAC,YAAA,GAAAA,YAAA;AAC/C,IAAAC,MAAA,GAAAJ,uBAAA,CAAAC,OAAA;AAAmCC,OAAA,CAAAE,MAAA,GAAAA,MAAA;AACnC,IAAAC,SAAA,GAAAL,uBAAA,CAAAC,OAAA;AAAyCC,OAAA,CAAAG,SAAA,GAAAA,SAAA;AAAA,SAAAC,yBAAAC,WAAA,eAAAC,OAAA,kCAAAC,iBAAA,OAAAD,OAAA,QAAAE,gBAAA,OAAAF,OAAA,YAAAF,wBAAA,YAAAA,CAAAC,WAAA,WAAAA,WAAA,GAAAG,gBAAA,GAAAD,iBAAA,KAAAF,WAAA;AAAA,SAAAP,wBAAAW,GAAA,EAAAJ,WAAA,SAAAA,WAAA,IAAAI,GAAA,IAAAA,GAAA,CAAAC,UAAA,WAAAD,GAAA,QAAAA,GAAA,oBAAAA,GAAA,wBAAAA,GAAA,4BAAAE,OAAA,EAAAF,GAAA,UAAAG,KAAA,GAAAR,wBAAA,CAAAC,WAAA,OAAAO,KAAA,IAAAA,KAAA,CAAAC,GAAA,CAAAJ,GAAA,YAAAG,KAAA,CAAAE,GAAA,CAAAL,GAAA,SAAAM,MAAA,WAAAC,qBAAA,GAAAC,MAAA,CAAAC,cAAA,IAAAD,MAAA,CAAAE,wBAAA,WAAAC,GAAA,IAAAX,GAAA,QAAAW,GAAA,kBAAAH,MAAA,CAAAI,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAd,GAAA,EAAAW,GAAA,SAAAI,IAAA,GAAAR,qBAAA,GAAAC,MAAA,CAAAE,wBAAA,CAAAV,GAAA,EAAAW,GAAA,cAAAI,IAAA,KAAAA,IAAA,CAAAV,GAAA,IAAAU,IAAA,CAAAC,GAAA,KAAAR,MAAA,CAAAC,cAAA,CAAAH,MAAA,EAAAK,GAAA,EAAAI,IAAA,YAAAT,MAAA,CAAAK,GAAA,IAAAX,GAAA,CAAAW,GAAA,SAAAL,MAAA,CAAAJ,OAAA,GAAAF,GAAA,MAAAG,KAAA,IAAAA,KAAA,CAAAa,GAAA,CAAAhB,GAAA,EAAAM,MAAA,YAAAA,MAAA"}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Credential Trustmark
|
2
|
+
|
3
|
+
A credential TrustMark is a signed JWT that verifies the authenticity of a credential issued by a trusted source. It serves as proof that a credential is valid and linked to a specific wallet instance.
|
4
|
+
The TrustMark is often presented as a QR code, containing cryptographic data to ensure it hasn't been tampered with. It includes fields like issuer, issuance and expiration timestamps, and credential-specific details. TrustMarks have a short validity period and are used to enhance security and prevent misuse, such as QR code swapping.
|
5
|
+
|
6
|
+
### getCredentialTrustmark
|
7
|
+
|
8
|
+
A function that generates a signed JWT Trustmark to verify the authenticity of a digital credential. The Trustmark serves as a cryptographic proof linking a credential to a specific wallet instance, ensuring the credential's validity and preventing unauthorized modifications or misuse.
|
9
|
+
|
10
|
+
#### Signature
|
11
|
+
|
12
|
+
```typescript
|
13
|
+
function getCredentialTrustmark({
|
14
|
+
walletInstanceAttestation: string,
|
15
|
+
wiaCryptoContext: CryptoContext,
|
16
|
+
credentialType: string,
|
17
|
+
docNumber?: string,
|
18
|
+
expirationTime?: number | string
|
19
|
+
}): Promise<{
|
20
|
+
jwt: string,
|
21
|
+
expirationTime: number
|
22
|
+
}>
|
23
|
+
```
|
24
|
+
|
25
|
+
#### Parameters
|
26
|
+
| Parameter | Type | Required | Description |
|
27
|
+
|-----------|------|----------|-------------|
|
28
|
+
| walletInstanceAttestation | string | Yes | A base64-encoded string containing the Wallet Instance Attestation (WIA). This attestation proves the authenticity of the wallet instance. |
|
29
|
+
| wiaCryptoContext | CryptoContext | Yes | The cryptographic context associated with the wallet instance. Must contain the same key pair used to generate the WIA. |
|
30
|
+
| credentialType | string | Yes | Identifier for the type of credential (e.g., "MDL" for Mobile Driver's License). |
|
31
|
+
| docNumber | string | No | The document number of the credential. If provided, it will be obfuscated in the Trustmark for privacy. |
|
32
|
+
| expirationTime | number \| string | No | Specifies when the Trustmark expires. Can be either:<br>- A timestamp in seconds<br>- A time span string (e.g., "2m" for 2 minutes)<br>Default: "2m" |
|
33
|
+
|
34
|
+
#### Return Value
|
35
|
+
|
36
|
+
Returns a Promise that resolves to an object containing:
|
37
|
+
| Property | Type | Description |
|
38
|
+
|----------|------|-------------|
|
39
|
+
| jwt | string | The signed trustmark JWT string |
|
40
|
+
| expirationTime | number | The expiration timestamp of the JWT in seconds |
|
41
|
+
|
42
|
+
## Example
|
43
|
+
|
44
|
+
```typescript
|
45
|
+
// Required inputs
|
46
|
+
const walletInstanceAttestation = "base64AttestationString";
|
47
|
+
const credentialType = "MDL"; // Credential type (e.g., Mobile Driver's License)
|
48
|
+
const documentNumber = "AB123456"; // Optional document number
|
49
|
+
const cryptoContext = createCryptoContextFor("wiaKeyTag"); // Sample crypto context
|
50
|
+
|
51
|
+
// Generate the TrustMark JWT
|
52
|
+
const { jwt, expirationTime } = await getCredentialTrustmark({
|
53
|
+
walletInstanceAttestation: "eyJ0eXAi...", // WIA JWT
|
54
|
+
wiaCryptoContext: cryptoContext,
|
55
|
+
credentialType: "IdentityCard",
|
56
|
+
docNumber: "AB123456",
|
57
|
+
expirationTime: "5m", // 5 minutes
|
58
|
+
});
|
59
|
+
|
60
|
+
console.log("Generated TrustMark JWT:", jwt);
|
61
|
+
console.log("Expires at:", new Date(expirationTime * 1000));
|
62
|
+
```
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
exports.getCredentialTrustmark = void 0;
|
7
|
+
var _ioReactNativeJwt = require("@pagopa/io-react-native-jwt");
|
8
|
+
var WalletInstanceAttestation = _interopRequireWildcard(require("../../wallet-instance-attestation"));
|
9
|
+
var _errors = require("../../utils/errors");
|
10
|
+
var _string = require("../../utils/string");
|
11
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
12
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
13
|
+
/**
|
14
|
+
* Generates a trustmark signed JWT, which is used to verify the authenticity of a credential.
|
15
|
+
* The public key used to sign the trustmark must the same used for the Wallet Instance Attestation.
|
16
|
+
*
|
17
|
+
* @param walletInstanceAttestation the Wallet Instance's attestation
|
18
|
+
* @param wiaCryptoContext The Wallet Instance's crypto context associated with the walletInstanceAttestation parameter
|
19
|
+
* @param credentialType The type of credential for which the trustmark is generated
|
20
|
+
* @param docNumber (Optional) Document number contained in the credential, if applicable
|
21
|
+
* @param expirationTime (Optional) Expiration time for the trustmark, default is 2 minutes.
|
22
|
+
* If a number is provided, it is interpreted as a timestamp in seconds.
|
23
|
+
* If a string is provided, it is interpreted as a time span and added to the current timestamp.
|
24
|
+
* @throws {IoWalletError} If the public key associated to the WIA is not the same for the CryptoContext
|
25
|
+
* @returns A promise containing the signed JWT and its expiration time in seconds
|
26
|
+
*/
|
27
|
+
const getCredentialTrustmark = async _ref => {
|
28
|
+
let {
|
29
|
+
walletInstanceAttestation,
|
30
|
+
wiaCryptoContext,
|
31
|
+
credentialType,
|
32
|
+
docNumber,
|
33
|
+
expirationTime = "2m"
|
34
|
+
} = _ref;
|
35
|
+
/**
|
36
|
+
* Check that the public key used to sign the trustmark is the one used for the WIA
|
37
|
+
*/
|
38
|
+
const holderBindingKey = await wiaCryptoContext.getPublicKey();
|
39
|
+
const decodedWia = WalletInstanceAttestation.decode(walletInstanceAttestation);
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Verify holder binding by comparing thumbprints of the WIA and the CryptoContext key
|
43
|
+
*/
|
44
|
+
const wiaThumbprint = await (0, _ioReactNativeJwt.thumbprint)(decodedWia.payload.cnf.jwk);
|
45
|
+
const cryptoContextThumbprint = await (0, _ioReactNativeJwt.thumbprint)(holderBindingKey);
|
46
|
+
if (wiaThumbprint !== cryptoContextThumbprint) {
|
47
|
+
throw new _errors.IoWalletError(`Failed to verify holder binding for status attestation, expected thumbprint: ${cryptoContextThumbprint}, got: ${wiaThumbprint}`);
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Generate Trustmark signed JWT
|
52
|
+
*/
|
53
|
+
const signedTrustmarkJwt = await new _ioReactNativeJwt.SignJWT(wiaCryptoContext).setProtectedHeader({
|
54
|
+
alg: "ES256"
|
55
|
+
}).setPayload({
|
56
|
+
iss: walletInstanceAttestation,
|
57
|
+
sub: credentialType,
|
58
|
+
/**
|
59
|
+
* If present, the document number is obfuscated before adding it to the payload
|
60
|
+
*/
|
61
|
+
...(docNumber ? {
|
62
|
+
subtyp: (0, _string.obfuscateString)(docNumber)
|
63
|
+
} : {})
|
64
|
+
}).setIssuedAt().setExpirationTime(expirationTime).sign();
|
65
|
+
const decodedTrustmark = (0, _ioReactNativeJwt.decode)(signedTrustmarkJwt);
|
66
|
+
return {
|
67
|
+
jwt: signedTrustmarkJwt,
|
68
|
+
expirationTime: decodedTrustmark.payload.exp ?? 0
|
69
|
+
};
|
70
|
+
};
|
71
|
+
exports.getCredentialTrustmark = getCredentialTrustmark;
|
72
|
+
//# sourceMappingURL=get-credential-trustmark.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"names":["_ioReactNativeJwt","require","WalletInstanceAttestation","_interopRequireWildcard","_errors","_string","_getRequireWildcardCache","nodeInterop","WeakMap","cacheBabelInterop","cacheNodeInterop","obj","__esModule","default","cache","has","get","newObj","hasPropertyDescriptor","Object","defineProperty","getOwnPropertyDescriptor","key","prototype","hasOwnProperty","call","desc","set","getCredentialTrustmark","_ref","walletInstanceAttestation","wiaCryptoContext","credentialType","docNumber","expirationTime","holderBindingKey","getPublicKey","decodedWia","decode","wiaThumbprint","thumbprint","payload","cnf","jwk","cryptoContextThumbprint","IoWalletError","signedTrustmarkJwt","SignJWT","setProtectedHeader","alg","setPayload","iss","sub","subtyp","obfuscateString","setIssuedAt","setExpirationTime","sign","decodedTrustmark","decodeJwt","jwt","exp","exports"],"sourceRoot":"../../../../src","sources":["credential/trustmark/get-credential-trustmark.ts"],"mappings":"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"}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
Object.defineProperty(exports, "getCredentialTrustmark", {
|
7
|
+
enumerable: true,
|
8
|
+
get: function () {
|
9
|
+
return _getCredentialTrustmark.getCredentialTrustmark;
|
10
|
+
}
|
11
|
+
});
|
12
|
+
var _getCredentialTrustmark = require("./get-credential-trustmark");
|
13
|
+
//# sourceMappingURL=index.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"names":["_getCredentialTrustmark","require"],"sourceRoot":"../../../../src","sources":["credential/trustmark/index.ts"],"mappings":";;;;;;;;;;;AAAA,IAAAA,uBAAA,GAAAC,OAAA"}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
exports.obfuscateString = void 0;
|
7
|
+
/**
|
8
|
+
* Randomly obfuscates characters in a string by replacing them with a specified character.
|
9
|
+
*
|
10
|
+
* @example
|
11
|
+
* ```ts
|
12
|
+
* const value = "1234567890";
|
13
|
+
* const obfuscated = obfuscateString(value, 60, "*");
|
14
|
+
* // Could output: "12**5*78**"
|
15
|
+
* ```
|
16
|
+
*
|
17
|
+
* @param value - The input string to obfuscate
|
18
|
+
* @param percentage - Percentage of characters to obfuscate (0-100). Defaults to 60
|
19
|
+
* @param obfuscatedChar - Character used for obfuscation. Defaults to "*"
|
20
|
+
* @returns The obfuscated string with random characters replaced
|
21
|
+
*/
|
22
|
+
const obfuscateString = function (value) {
|
23
|
+
let percentage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
|
24
|
+
let obfuscatedChar = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "*";
|
25
|
+
if (!value) {
|
26
|
+
return "";
|
27
|
+
}
|
28
|
+
|
29
|
+
// Ensure percentage is between 0 and 100
|
30
|
+
const safePercentage = Math.max(0, Math.min(100, percentage));
|
31
|
+
|
32
|
+
// Calculate number of characters to obfuscate
|
33
|
+
const charsToObfuscate = Math.floor(value.length * safePercentage / 100);
|
34
|
+
|
35
|
+
// Convert string to array for manipulation
|
36
|
+
const chars = value.split("");
|
37
|
+
|
38
|
+
// Get random positions to obfuscate
|
39
|
+
const positions = Array.from({
|
40
|
+
length: value.length
|
41
|
+
}, (_, i) => i).sort(() => Math.random() - 0.5).slice(0, charsToObfuscate);
|
42
|
+
|
43
|
+
// Replace characters at random positions
|
44
|
+
positions.forEach(pos => {
|
45
|
+
chars[pos] = obfuscatedChar;
|
46
|
+
});
|
47
|
+
return chars.join("");
|
48
|
+
};
|
49
|
+
exports.obfuscateString = obfuscateString;
|
50
|
+
//# sourceMappingURL=string.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"names":["obfuscateString","value","percentage","arguments","length","undefined","obfuscatedChar","safePercentage","Math","max","min","charsToObfuscate","floor","chars","split","positions","Array","from","_","i","sort","random","slice","forEach","pos","join","exports"],"sourceRoot":"../../../src","sources":["utils/string.ts"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMA,eAAe,GAAG,SAAAA,CAC7BC,KAAa,EAGF;EAAA,IAFXC,UAAkB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,EAAE;EAAA,IACvBG,cAAsB,GAAAH,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,GAAG;EAE5B,IAAI,CAACF,KAAK,EAAE;IACV,OAAO,EAAE;EACX;;EAEA;EACA,MAAMM,cAAc,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC,EAAED,IAAI,CAACE,GAAG,CAAC,GAAG,EAAER,UAAU,CAAC,CAAC;;EAE7D;EACA,MAAMS,gBAAgB,GAAGH,IAAI,CAACI,KAAK,CAAEX,KAAK,CAACG,MAAM,GAAGG,cAAc,GAAI,GAAG,CAAC;;EAE1E;EACA,MAAMM,KAAK,GAAGZ,KAAK,CAACa,KAAK,CAAC,EAAE,CAAC;;EAE7B;EACA,MAAMC,SAAS,GAAGC,KAAK,CAACC,IAAI,CAAC;IAAEb,MAAM,EAAEH,KAAK,CAACG;EAAO,CAAC,EAAE,CAACc,CAAC,EAAEC,CAAC,KAAKA,CAAC,CAAC,CAChEC,IAAI,CAAC,MAAMZ,IAAI,CAACa,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC,CAC/BC,KAAK,CAAC,CAAC,EAAEX,gBAAgB,CAAC;;EAE7B;EACAI,SAAS,CAACQ,OAAO,CAAEC,GAAG,IAAK;IACzBX,KAAK,CAACW,GAAG,CAAC,GAAGlB,cAAc;EAC7B,CAAC,CAAC;EAEF,OAAOO,KAAK,CAACY,IAAI,CAAC,EAAE,CAAC;AACvB,CAAC;AAACC,OAAA,CAAA1B,eAAA,GAAAA,eAAA"}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import * as Issuance from "./issuance";
|
2
2
|
import * as Presentation from "./presentation";
|
3
3
|
import * as Status from "./status";
|
4
|
-
|
4
|
+
import * as Trustmark from "./trustmark";
|
5
|
+
export { Issuance, Presentation, Status, Trustmark };
|
5
6
|
//# sourceMappingURL=index.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"names":["Issuance","Presentation","Status"],"sourceRoot":"../../../src","sources":["credential/index.ts"],"mappings":"AAAA,OAAO,KAAKA,QAAQ,MAAM,YAAY;AACtC,OAAO,KAAKC,YAAY,MAAM,gBAAgB;AAC9C,OAAO,KAAKC,MAAM,MAAM,UAAU;
|
1
|
+
{"version":3,"names":["Issuance","Presentation","Status","Trustmark"],"sourceRoot":"../../../src","sources":["credential/index.ts"],"mappings":"AAAA,OAAO,KAAKA,QAAQ,MAAM,YAAY;AACtC,OAAO,KAAKC,YAAY,MAAM,gBAAgB;AAC9C,OAAO,KAAKC,MAAM,MAAM,UAAU;AAClC,OAAO,KAAKC,SAAS,MAAM,aAAa;AAExC,SAASH,QAAQ,EAAEC,YAAY,EAAEC,MAAM,EAAEC,SAAS"}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Credential Trustmark
|
2
|
+
|
3
|
+
A credential TrustMark is a signed JWT that verifies the authenticity of a credential issued by a trusted source. It serves as proof that a credential is valid and linked to a specific wallet instance.
|
4
|
+
The TrustMark is often presented as a QR code, containing cryptographic data to ensure it hasn't been tampered with. It includes fields like issuer, issuance and expiration timestamps, and credential-specific details. TrustMarks have a short validity period and are used to enhance security and prevent misuse, such as QR code swapping.
|
5
|
+
|
6
|
+
### getCredentialTrustmark
|
7
|
+
|
8
|
+
A function that generates a signed JWT Trustmark to verify the authenticity of a digital credential. The Trustmark serves as a cryptographic proof linking a credential to a specific wallet instance, ensuring the credential's validity and preventing unauthorized modifications or misuse.
|
9
|
+
|
10
|
+
#### Signature
|
11
|
+
|
12
|
+
```typescript
|
13
|
+
function getCredentialTrustmark({
|
14
|
+
walletInstanceAttestation: string,
|
15
|
+
wiaCryptoContext: CryptoContext,
|
16
|
+
credentialType: string,
|
17
|
+
docNumber?: string,
|
18
|
+
expirationTime?: number | string
|
19
|
+
}): Promise<{
|
20
|
+
jwt: string,
|
21
|
+
expirationTime: number
|
22
|
+
}>
|
23
|
+
```
|
24
|
+
|
25
|
+
#### Parameters
|
26
|
+
| Parameter | Type | Required | Description |
|
27
|
+
|-----------|------|----------|-------------|
|
28
|
+
| walletInstanceAttestation | string | Yes | A base64-encoded string containing the Wallet Instance Attestation (WIA). This attestation proves the authenticity of the wallet instance. |
|
29
|
+
| wiaCryptoContext | CryptoContext | Yes | The cryptographic context associated with the wallet instance. Must contain the same key pair used to generate the WIA. |
|
30
|
+
| credentialType | string | Yes | Identifier for the type of credential (e.g., "MDL" for Mobile Driver's License). |
|
31
|
+
| docNumber | string | No | The document number of the credential. If provided, it will be obfuscated in the Trustmark for privacy. |
|
32
|
+
| expirationTime | number \| string | No | Specifies when the Trustmark expires. Can be either:<br>- A timestamp in seconds<br>- A time span string (e.g., "2m" for 2 minutes)<br>Default: "2m" |
|
33
|
+
|
34
|
+
#### Return Value
|
35
|
+
|
36
|
+
Returns a Promise that resolves to an object containing:
|
37
|
+
| Property | Type | Description |
|
38
|
+
|----------|------|-------------|
|
39
|
+
| jwt | string | The signed trustmark JWT string |
|
40
|
+
| expirationTime | number | The expiration timestamp of the JWT in seconds |
|
41
|
+
|
42
|
+
## Example
|
43
|
+
|
44
|
+
```typescript
|
45
|
+
// Required inputs
|
46
|
+
const walletInstanceAttestation = "base64AttestationString";
|
47
|
+
const credentialType = "MDL"; // Credential type (e.g., Mobile Driver's License)
|
48
|
+
const documentNumber = "AB123456"; // Optional document number
|
49
|
+
const cryptoContext = createCryptoContextFor("wiaKeyTag"); // Sample crypto context
|
50
|
+
|
51
|
+
// Generate the TrustMark JWT
|
52
|
+
const { jwt, expirationTime } = await getCredentialTrustmark({
|
53
|
+
walletInstanceAttestation: "eyJ0eXAi...", // WIA JWT
|
54
|
+
wiaCryptoContext: cryptoContext,
|
55
|
+
credentialType: "IdentityCard",
|
56
|
+
docNumber: "AB123456",
|
57
|
+
expirationTime: "5m", // 5 minutes
|
58
|
+
});
|
59
|
+
|
60
|
+
console.log("Generated TrustMark JWT:", jwt);
|
61
|
+
console.log("Expires at:", new Date(expirationTime * 1000));
|
62
|
+
```
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { SignJWT, thumbprint, decode as decodeJwt } from "@pagopa/io-react-native-jwt";
|
2
|
+
import * as WalletInstanceAttestation from "../../wallet-instance-attestation";
|
3
|
+
import { IoWalletError } from "../../utils/errors";
|
4
|
+
import { obfuscateString } from "../../utils/string";
|
5
|
+
/**
|
6
|
+
* Generates a trustmark signed JWT, which is used to verify the authenticity of a credential.
|
7
|
+
* The public key used to sign the trustmark must the same used for the Wallet Instance Attestation.
|
8
|
+
*
|
9
|
+
* @param walletInstanceAttestation the Wallet Instance's attestation
|
10
|
+
* @param wiaCryptoContext The Wallet Instance's crypto context associated with the walletInstanceAttestation parameter
|
11
|
+
* @param credentialType The type of credential for which the trustmark is generated
|
12
|
+
* @param docNumber (Optional) Document number contained in the credential, if applicable
|
13
|
+
* @param expirationTime (Optional) Expiration time for the trustmark, default is 2 minutes.
|
14
|
+
* If a number is provided, it is interpreted as a timestamp in seconds.
|
15
|
+
* If a string is provided, it is interpreted as a time span and added to the current timestamp.
|
16
|
+
* @throws {IoWalletError} If the public key associated to the WIA is not the same for the CryptoContext
|
17
|
+
* @returns A promise containing the signed JWT and its expiration time in seconds
|
18
|
+
*/
|
19
|
+
export const getCredentialTrustmark = async _ref => {
|
20
|
+
let {
|
21
|
+
walletInstanceAttestation,
|
22
|
+
wiaCryptoContext,
|
23
|
+
credentialType,
|
24
|
+
docNumber,
|
25
|
+
expirationTime = "2m"
|
26
|
+
} = _ref;
|
27
|
+
/**
|
28
|
+
* Check that the public key used to sign the trustmark is the one used for the WIA
|
29
|
+
*/
|
30
|
+
const holderBindingKey = await wiaCryptoContext.getPublicKey();
|
31
|
+
const decodedWia = WalletInstanceAttestation.decode(walletInstanceAttestation);
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Verify holder binding by comparing thumbprints of the WIA and the CryptoContext key
|
35
|
+
*/
|
36
|
+
const wiaThumbprint = await thumbprint(decodedWia.payload.cnf.jwk);
|
37
|
+
const cryptoContextThumbprint = await thumbprint(holderBindingKey);
|
38
|
+
if (wiaThumbprint !== cryptoContextThumbprint) {
|
39
|
+
throw new IoWalletError(`Failed to verify holder binding for status attestation, expected thumbprint: ${cryptoContextThumbprint}, got: ${wiaThumbprint}`);
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Generate Trustmark signed JWT
|
44
|
+
*/
|
45
|
+
const signedTrustmarkJwt = await new SignJWT(wiaCryptoContext).setProtectedHeader({
|
46
|
+
alg: "ES256"
|
47
|
+
}).setPayload({
|
48
|
+
iss: walletInstanceAttestation,
|
49
|
+
sub: credentialType,
|
50
|
+
/**
|
51
|
+
* If present, the document number is obfuscated before adding it to the payload
|
52
|
+
*/
|
53
|
+
...(docNumber ? {
|
54
|
+
subtyp: obfuscateString(docNumber)
|
55
|
+
} : {})
|
56
|
+
}).setIssuedAt().setExpirationTime(expirationTime).sign();
|
57
|
+
const decodedTrustmark = decodeJwt(signedTrustmarkJwt);
|
58
|
+
return {
|
59
|
+
jwt: signedTrustmarkJwt,
|
60
|
+
expirationTime: decodedTrustmark.payload.exp ?? 0
|
61
|
+
};
|
62
|
+
};
|
63
|
+
//# sourceMappingURL=get-credential-trustmark.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"names":["SignJWT","thumbprint","decode","decodeJwt","WalletInstanceAttestation","IoWalletError","obfuscateString","getCredentialTrustmark","_ref","walletInstanceAttestation","wiaCryptoContext","credentialType","docNumber","expirationTime","holderBindingKey","getPublicKey","decodedWia","wiaThumbprint","payload","cnf","jwk","cryptoContextThumbprint","signedTrustmarkJwt","setProtectedHeader","alg","setPayload","iss","sub","subtyp","setIssuedAt","setExpirationTime","sign","decodedTrustmark","jwt","exp"],"sourceRoot":"../../../../src","sources":["credential/trustmark/get-credential-trustmark.ts"],"mappings":"AAAA,SACEA,OAAO,EACPC,UAAU,EAEVC,MAAM,IAAIC,SAAS,QACd,6BAA6B;AACpC,OAAO,KAAKC,yBAAyB,MAAM,mCAAmC;AAC9E,SAASC,aAAa,QAAQ,oBAAoB;AAClD,SAASC,eAAe,QAAQ,oBAAoB;AAoCpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,sBAAiD,GAAG,MAAAC,IAAA,IAM3D;EAAA,IANkE;IACtEC,yBAAyB;IACzBC,gBAAgB;IAChBC,cAAc;IACdC,SAAS;IACTC,cAAc,GAAG;EACnB,CAAC,GAAAL,IAAA;EACC;AACF;AACA;EACE,MAAMM,gBAAgB,GAAG,MAAMJ,gBAAgB,CAACK,YAAY,CAAC,CAAC;EAC9D,MAAMC,UAAU,GAAGZ,yBAAyB,CAACF,MAAM,CACjDO,yBACF,CAAC;;EAED;AACF;AACA;EACE,MAAMQ,aAAa,GAAG,MAAMhB,UAAU,CAACe,UAAU,CAACE,OAAO,CAACC,GAAG,CAACC,GAAG,CAAC;EAClE,MAAMC,uBAAuB,GAAG,MAAMpB,UAAU,CAACa,gBAAgB,CAAC;EAElE,IAAIG,aAAa,KAAKI,uBAAuB,EAAE;IAC7C,MAAM,IAAIhB,aAAa,CACpB,gFAA+EgB,uBAAwB,UAASJ,aAAc,EACjI,CAAC;EACH;;EAEA;AACF;AACA;EACE,MAAMK,kBAAkB,GAAG,MAAM,IAAItB,OAAO,CAACU,gBAAgB,CAAC,CAC3Da,kBAAkB,CAAC;IAClBC,GAAG,EAAE;EACP,CAAC,CAAC,CACDC,UAAU,CAAC;IACVC,GAAG,EAAEjB,yBAAyB;IAC9BkB,GAAG,EAAEhB,cAAc;IACnB;AACN;AACA;IACM,IAAIC,SAAS,GAAG;MAAEgB,MAAM,EAAEtB,eAAe,CAACM,SAAS;IAAE,CAAC,GAAG,CAAC,CAAC;EAC7D,CAAC,CAAC,CACDiB,WAAW,CAAC,CAAC,CACbC,iBAAiB,CAACjB,cAAc,CAAC,CACjCkB,IAAI,CAAC,CAAC;EAET,MAAMC,gBAAgB,GAAG7B,SAAS,CAACmB,kBAAkB,CAAC;EAEtD,OAAO;IACLW,GAAG,EAAEX,kBAAkB;IACvBT,cAAc,EAAEmB,gBAAgB,CAACd,OAAO,CAACgB,GAAG,IAAI;EAClD,CAAC;AACH,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"names":["getCredentialTrustmark"],"sourceRoot":"../../../../src","sources":["credential/trustmark/index.ts"],"mappings":"AAAA,SAEEA,sBAAsB,QACjB,4BAA4B;AAEnC,SAASA,sBAAsB"}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
/**
|
2
|
+
* Randomly obfuscates characters in a string by replacing them with a specified character.
|
3
|
+
*
|
4
|
+
* @example
|
5
|
+
* ```ts
|
6
|
+
* const value = "1234567890";
|
7
|
+
* const obfuscated = obfuscateString(value, 60, "*");
|
8
|
+
* // Could output: "12**5*78**"
|
9
|
+
* ```
|
10
|
+
*
|
11
|
+
* @param value - The input string to obfuscate
|
12
|
+
* @param percentage - Percentage of characters to obfuscate (0-100). Defaults to 60
|
13
|
+
* @param obfuscatedChar - Character used for obfuscation. Defaults to "*"
|
14
|
+
* @returns The obfuscated string with random characters replaced
|
15
|
+
*/
|
16
|
+
export const obfuscateString = function (value) {
|
17
|
+
let percentage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
|
18
|
+
let obfuscatedChar = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "*";
|
19
|
+
if (!value) {
|
20
|
+
return "";
|
21
|
+
}
|
22
|
+
|
23
|
+
// Ensure percentage is between 0 and 100
|
24
|
+
const safePercentage = Math.max(0, Math.min(100, percentage));
|
25
|
+
|
26
|
+
// Calculate number of characters to obfuscate
|
27
|
+
const charsToObfuscate = Math.floor(value.length * safePercentage / 100);
|
28
|
+
|
29
|
+
// Convert string to array for manipulation
|
30
|
+
const chars = value.split("");
|
31
|
+
|
32
|
+
// Get random positions to obfuscate
|
33
|
+
const positions = Array.from({
|
34
|
+
length: value.length
|
35
|
+
}, (_, i) => i).sort(() => Math.random() - 0.5).slice(0, charsToObfuscate);
|
36
|
+
|
37
|
+
// Replace characters at random positions
|
38
|
+
positions.forEach(pos => {
|
39
|
+
chars[pos] = obfuscatedChar;
|
40
|
+
});
|
41
|
+
return chars.join("");
|
42
|
+
};
|
43
|
+
//# sourceMappingURL=string.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"names":["obfuscateString","value","percentage","arguments","length","undefined","obfuscatedChar","safePercentage","Math","max","min","charsToObfuscate","floor","chars","split","positions","Array","from","_","i","sort","random","slice","forEach","pos","join"],"sourceRoot":"../../../src","sources":["utils/string.ts"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,eAAe,GAAG,SAAAA,CAC7BC,KAAa,EAGF;EAAA,IAFXC,UAAkB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,EAAE;EAAA,IACvBG,cAAsB,GAAAH,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,GAAG;EAE5B,IAAI,CAACF,KAAK,EAAE;IACV,OAAO,EAAE;EACX;;EAEA;EACA,MAAMM,cAAc,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC,EAAED,IAAI,CAACE,GAAG,CAAC,GAAG,EAAER,UAAU,CAAC,CAAC;;EAE7D;EACA,MAAMS,gBAAgB,GAAGH,IAAI,CAACI,KAAK,CAAEX,KAAK,CAACG,MAAM,GAAGG,cAAc,GAAI,GAAG,CAAC;;EAE1E;EACA,MAAMM,KAAK,GAAGZ,KAAK,CAACa,KAAK,CAAC,EAAE,CAAC;;EAE7B;EACA,MAAMC,SAAS,GAAGC,KAAK,CAACC,IAAI,CAAC;IAAEb,MAAM,EAAEH,KAAK,CAACG;EAAO,CAAC,EAAE,CAACc,CAAC,EAAEC,CAAC,KAAKA,CAAC,CAAC,CAChEC,IAAI,CAAC,MAAMZ,IAAI,CAACa,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC,CAC/BC,KAAK,CAAC,CAAC,EAAEX,gBAAgB,CAAC;;EAE7B;EACAI,SAAS,CAACQ,OAAO,CAAEC,GAAG,IAAK;IACzBX,KAAK,CAACW,GAAG,CAAC,GAAGlB,cAAc;EAC7B,CAAC,CAAC;EAEF,OAAOO,KAAK,CAACY,IAAI,CAAC,EAAE,CAAC;AACvB,CAAC"}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import * as Issuance from "./issuance";
|
2
2
|
import * as Presentation from "./presentation";
|
3
3
|
import * as Status from "./status";
|
4
|
-
|
4
|
+
import * as Trustmark from "./trustmark";
|
5
|
+
export { Issuance, Presentation, Status, Trustmark };
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/credential/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/credential/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC"}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import { type CryptoContext } from "@pagopa/io-react-native-jwt";
|
2
|
+
export type GetCredentialTrustmarkJwt = (params: {
|
3
|
+
/**
|
4
|
+
* The Wallet Instance's attestation
|
5
|
+
*/
|
6
|
+
walletInstanceAttestation: string;
|
7
|
+
/**
|
8
|
+
* The Wallet Instance's crypto context associated with the walletInstanceAttestation parameter
|
9
|
+
*/
|
10
|
+
wiaCryptoContext: CryptoContext;
|
11
|
+
/**
|
12
|
+
* The type of credential for which the trustmark is generated
|
13
|
+
*/
|
14
|
+
credentialType: string;
|
15
|
+
/**
|
16
|
+
* (Optional) Document number contained in the credential, if applicable
|
17
|
+
*/
|
18
|
+
docNumber?: string;
|
19
|
+
/**
|
20
|
+
* (Optional) Expiration time for the trustmark, default is 2 minutes.
|
21
|
+
* If a number is provided, it is interpreted as a timestamp in seconds.
|
22
|
+
* If a string is provided, it is interpreted as a time span and added to the current timestamp.
|
23
|
+
*/
|
24
|
+
expirationTime?: number | string;
|
25
|
+
}) => Promise<{
|
26
|
+
/**
|
27
|
+
* The signed JWT
|
28
|
+
*/
|
29
|
+
jwt: string;
|
30
|
+
/**
|
31
|
+
* The expiration time of the JWT in seconds
|
32
|
+
*/
|
33
|
+
expirationTime: number;
|
34
|
+
}>;
|
35
|
+
/**
|
36
|
+
* Generates a trustmark signed JWT, which is used to verify the authenticity of a credential.
|
37
|
+
* The public key used to sign the trustmark must the same used for the Wallet Instance Attestation.
|
38
|
+
*
|
39
|
+
* @param walletInstanceAttestation the Wallet Instance's attestation
|
40
|
+
* @param wiaCryptoContext The Wallet Instance's crypto context associated with the walletInstanceAttestation parameter
|
41
|
+
* @param credentialType The type of credential for which the trustmark is generated
|
42
|
+
* @param docNumber (Optional) Document number contained in the credential, if applicable
|
43
|
+
* @param expirationTime (Optional) Expiration time for the trustmark, default is 2 minutes.
|
44
|
+
* If a number is provided, it is interpreted as a timestamp in seconds.
|
45
|
+
* If a string is provided, it is interpreted as a time span and added to the current timestamp.
|
46
|
+
* @throws {IoWalletError} If the public key associated to the WIA is not the same for the CryptoContext
|
47
|
+
* @returns A promise containing the signed JWT and its expiration time in seconds
|
48
|
+
*/
|
49
|
+
export declare const getCredentialTrustmark: GetCredentialTrustmarkJwt;
|
50
|
+
//# sourceMappingURL=get-credential-trustmark.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"get-credential-trustmark.d.ts","sourceRoot":"","sources":["../../../../src/credential/trustmark/get-credential-trustmark.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,6BAA6B,CAAC;AAKrC,MAAM,MAAM,yBAAyB,GAAG,CAAC,MAAM,EAAE;IAC/C;;OAEG;IACH,yBAAyB,EAAE,MAAM,CAAC;IAClC;;OAEG;IACH,gBAAgB,EAAE,aAAa,CAAC;IAChC;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAClC,KAAK,OAAO,CAAC;IACZ;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,EAAE,yBAoDpC,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/credential/trustmark/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,yBAAyB,EAC9B,sBAAsB,EACvB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAElC,YAAY,EAAE,yBAAyB,EAAE,CAAC"}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
/**
|
2
|
+
* Randomly obfuscates characters in a string by replacing them with a specified character.
|
3
|
+
*
|
4
|
+
* @example
|
5
|
+
* ```ts
|
6
|
+
* const value = "1234567890";
|
7
|
+
* const obfuscated = obfuscateString(value, 60, "*");
|
8
|
+
* // Could output: "12**5*78**"
|
9
|
+
* ```
|
10
|
+
*
|
11
|
+
* @param value - The input string to obfuscate
|
12
|
+
* @param percentage - Percentage of characters to obfuscate (0-100). Defaults to 60
|
13
|
+
* @param obfuscatedChar - Character used for obfuscation. Defaults to "*"
|
14
|
+
* @returns The obfuscated string with random characters replaced
|
15
|
+
*/
|
16
|
+
export declare const obfuscateString: (value: string, percentage?: number, obfuscatedChar?: string) => string;
|
17
|
+
//# sourceMappingURL=string.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../../../src/utils/string.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,UACnB,MAAM,eACD,MAAM,mBACF,MAAM,KACrB,MAyBF,CAAC"}
|
package/package.json
CHANGED
package/src/credential/index.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import * as Issuance from "./issuance";
|
2
2
|
import * as Presentation from "./presentation";
|
3
3
|
import * as Status from "./status";
|
4
|
+
import * as Trustmark from "./trustmark";
|
4
5
|
|
5
|
-
export { Issuance, Presentation, Status };
|
6
|
+
export { Issuance, Presentation, Status, Trustmark };
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Credential Trustmark
|
2
|
+
|
3
|
+
A credential TrustMark is a signed JWT that verifies the authenticity of a credential issued by a trusted source. It serves as proof that a credential is valid and linked to a specific wallet instance.
|
4
|
+
The TrustMark is often presented as a QR code, containing cryptographic data to ensure it hasn't been tampered with. It includes fields like issuer, issuance and expiration timestamps, and credential-specific details. TrustMarks have a short validity period and are used to enhance security and prevent misuse, such as QR code swapping.
|
5
|
+
|
6
|
+
### getCredentialTrustmark
|
7
|
+
|
8
|
+
A function that generates a signed JWT Trustmark to verify the authenticity of a digital credential. The Trustmark serves as a cryptographic proof linking a credential to a specific wallet instance, ensuring the credential's validity and preventing unauthorized modifications or misuse.
|
9
|
+
|
10
|
+
#### Signature
|
11
|
+
|
12
|
+
```typescript
|
13
|
+
function getCredentialTrustmark({
|
14
|
+
walletInstanceAttestation: string,
|
15
|
+
wiaCryptoContext: CryptoContext,
|
16
|
+
credentialType: string,
|
17
|
+
docNumber?: string,
|
18
|
+
expirationTime?: number | string
|
19
|
+
}): Promise<{
|
20
|
+
jwt: string,
|
21
|
+
expirationTime: number
|
22
|
+
}>
|
23
|
+
```
|
24
|
+
|
25
|
+
#### Parameters
|
26
|
+
| Parameter | Type | Required | Description |
|
27
|
+
|-----------|------|----------|-------------|
|
28
|
+
| walletInstanceAttestation | string | Yes | A base64-encoded string containing the Wallet Instance Attestation (WIA). This attestation proves the authenticity of the wallet instance. |
|
29
|
+
| wiaCryptoContext | CryptoContext | Yes | The cryptographic context associated with the wallet instance. Must contain the same key pair used to generate the WIA. |
|
30
|
+
| credentialType | string | Yes | Identifier for the type of credential (e.g., "MDL" for Mobile Driver's License). |
|
31
|
+
| docNumber | string | No | The document number of the credential. If provided, it will be obfuscated in the Trustmark for privacy. |
|
32
|
+
| expirationTime | number \| string | No | Specifies when the Trustmark expires. Can be either:<br>- A timestamp in seconds<br>- A time span string (e.g., "2m" for 2 minutes)<br>Default: "2m" |
|
33
|
+
|
34
|
+
#### Return Value
|
35
|
+
|
36
|
+
Returns a Promise that resolves to an object containing:
|
37
|
+
| Property | Type | Description |
|
38
|
+
|----------|------|-------------|
|
39
|
+
| jwt | string | The signed trustmark JWT string |
|
40
|
+
| expirationTime | number | The expiration timestamp of the JWT in seconds |
|
41
|
+
|
42
|
+
## Example
|
43
|
+
|
44
|
+
```typescript
|
45
|
+
// Required inputs
|
46
|
+
const walletInstanceAttestation = "base64AttestationString";
|
47
|
+
const credentialType = "MDL"; // Credential type (e.g., Mobile Driver's License)
|
48
|
+
const documentNumber = "AB123456"; // Optional document number
|
49
|
+
const cryptoContext = createCryptoContextFor("wiaKeyTag"); // Sample crypto context
|
50
|
+
|
51
|
+
// Generate the TrustMark JWT
|
52
|
+
const { jwt, expirationTime } = await getCredentialTrustmark({
|
53
|
+
walletInstanceAttestation: "eyJ0eXAi...", // WIA JWT
|
54
|
+
wiaCryptoContext: cryptoContext,
|
55
|
+
credentialType: "IdentityCard",
|
56
|
+
docNumber: "AB123456",
|
57
|
+
expirationTime: "5m", // 5 minutes
|
58
|
+
});
|
59
|
+
|
60
|
+
console.log("Generated TrustMark JWT:", jwt);
|
61
|
+
console.log("Expires at:", new Date(expirationTime * 1000));
|
62
|
+
```
|
@@ -0,0 +1,111 @@
|
|
1
|
+
import {
|
2
|
+
SignJWT,
|
3
|
+
thumbprint,
|
4
|
+
type CryptoContext,
|
5
|
+
decode as decodeJwt,
|
6
|
+
} from "@pagopa/io-react-native-jwt";
|
7
|
+
import * as WalletInstanceAttestation from "../../wallet-instance-attestation";
|
8
|
+
import { IoWalletError } from "../../utils/errors";
|
9
|
+
import { obfuscateString } from "../../utils/string";
|
10
|
+
|
11
|
+
export type GetCredentialTrustmarkJwt = (params: {
|
12
|
+
/**
|
13
|
+
* The Wallet Instance's attestation
|
14
|
+
*/
|
15
|
+
walletInstanceAttestation: string;
|
16
|
+
/**
|
17
|
+
* The Wallet Instance's crypto context associated with the walletInstanceAttestation parameter
|
18
|
+
*/
|
19
|
+
wiaCryptoContext: CryptoContext;
|
20
|
+
/**
|
21
|
+
* The type of credential for which the trustmark is generated
|
22
|
+
*/
|
23
|
+
credentialType: string;
|
24
|
+
/**
|
25
|
+
* (Optional) Document number contained in the credential, if applicable
|
26
|
+
*/
|
27
|
+
docNumber?: string;
|
28
|
+
/**
|
29
|
+
* (Optional) Expiration time for the trustmark, default is 2 minutes.
|
30
|
+
* If a number is provided, it is interpreted as a timestamp in seconds.
|
31
|
+
* If a string is provided, it is interpreted as a time span and added to the current timestamp.
|
32
|
+
*/
|
33
|
+
expirationTime?: number | string;
|
34
|
+
}) => Promise<{
|
35
|
+
/**
|
36
|
+
* The signed JWT
|
37
|
+
*/
|
38
|
+
jwt: string;
|
39
|
+
/**
|
40
|
+
* The expiration time of the JWT in seconds
|
41
|
+
*/
|
42
|
+
expirationTime: number;
|
43
|
+
}>;
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Generates a trustmark signed JWT, which is used to verify the authenticity of a credential.
|
47
|
+
* The public key used to sign the trustmark must the same used for the Wallet Instance Attestation.
|
48
|
+
*
|
49
|
+
* @param walletInstanceAttestation the Wallet Instance's attestation
|
50
|
+
* @param wiaCryptoContext The Wallet Instance's crypto context associated with the walletInstanceAttestation parameter
|
51
|
+
* @param credentialType The type of credential for which the trustmark is generated
|
52
|
+
* @param docNumber (Optional) Document number contained in the credential, if applicable
|
53
|
+
* @param expirationTime (Optional) Expiration time for the trustmark, default is 2 minutes.
|
54
|
+
* If a number is provided, it is interpreted as a timestamp in seconds.
|
55
|
+
* If a string is provided, it is interpreted as a time span and added to the current timestamp.
|
56
|
+
* @throws {IoWalletError} If the public key associated to the WIA is not the same for the CryptoContext
|
57
|
+
* @returns A promise containing the signed JWT and its expiration time in seconds
|
58
|
+
*/
|
59
|
+
export const getCredentialTrustmark: GetCredentialTrustmarkJwt = async ({
|
60
|
+
walletInstanceAttestation,
|
61
|
+
wiaCryptoContext,
|
62
|
+
credentialType,
|
63
|
+
docNumber,
|
64
|
+
expirationTime = "2m",
|
65
|
+
}) => {
|
66
|
+
/**
|
67
|
+
* Check that the public key used to sign the trustmark is the one used for the WIA
|
68
|
+
*/
|
69
|
+
const holderBindingKey = await wiaCryptoContext.getPublicKey();
|
70
|
+
const decodedWia = WalletInstanceAttestation.decode(
|
71
|
+
walletInstanceAttestation
|
72
|
+
);
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Verify holder binding by comparing thumbprints of the WIA and the CryptoContext key
|
76
|
+
*/
|
77
|
+
const wiaThumbprint = await thumbprint(decodedWia.payload.cnf.jwk);
|
78
|
+
const cryptoContextThumbprint = await thumbprint(holderBindingKey);
|
79
|
+
|
80
|
+
if (wiaThumbprint !== cryptoContextThumbprint) {
|
81
|
+
throw new IoWalletError(
|
82
|
+
`Failed to verify holder binding for status attestation, expected thumbprint: ${cryptoContextThumbprint}, got: ${wiaThumbprint}`
|
83
|
+
);
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Generate Trustmark signed JWT
|
88
|
+
*/
|
89
|
+
const signedTrustmarkJwt = await new SignJWT(wiaCryptoContext)
|
90
|
+
.setProtectedHeader({
|
91
|
+
alg: "ES256",
|
92
|
+
})
|
93
|
+
.setPayload({
|
94
|
+
iss: walletInstanceAttestation,
|
95
|
+
sub: credentialType,
|
96
|
+
/**
|
97
|
+
* If present, the document number is obfuscated before adding it to the payload
|
98
|
+
*/
|
99
|
+
...(docNumber ? { subtyp: obfuscateString(docNumber) } : {}),
|
100
|
+
})
|
101
|
+
.setIssuedAt()
|
102
|
+
.setExpirationTime(expirationTime)
|
103
|
+
.sign();
|
104
|
+
|
105
|
+
const decodedTrustmark = decodeJwt(signedTrustmarkJwt);
|
106
|
+
|
107
|
+
return {
|
108
|
+
jwt: signedTrustmarkJwt,
|
109
|
+
expirationTime: decodedTrustmark.payload.exp ?? 0,
|
110
|
+
};
|
111
|
+
};
|
@@ -0,0 +1,45 @@
|
|
1
|
+
/**
|
2
|
+
* Randomly obfuscates characters in a string by replacing them with a specified character.
|
3
|
+
*
|
4
|
+
* @example
|
5
|
+
* ```ts
|
6
|
+
* const value = "1234567890";
|
7
|
+
* const obfuscated = obfuscateString(value, 60, "*");
|
8
|
+
* // Could output: "12**5*78**"
|
9
|
+
* ```
|
10
|
+
*
|
11
|
+
* @param value - The input string to obfuscate
|
12
|
+
* @param percentage - Percentage of characters to obfuscate (0-100). Defaults to 60
|
13
|
+
* @param obfuscatedChar - Character used for obfuscation. Defaults to "*"
|
14
|
+
* @returns The obfuscated string with random characters replaced
|
15
|
+
*/
|
16
|
+
export const obfuscateString = (
|
17
|
+
value: string,
|
18
|
+
percentage: number = 60,
|
19
|
+
obfuscatedChar: string = "*"
|
20
|
+
): string => {
|
21
|
+
if (!value) {
|
22
|
+
return "";
|
23
|
+
}
|
24
|
+
|
25
|
+
// Ensure percentage is between 0 and 100
|
26
|
+
const safePercentage = Math.max(0, Math.min(100, percentage));
|
27
|
+
|
28
|
+
// Calculate number of characters to obfuscate
|
29
|
+
const charsToObfuscate = Math.floor((value.length * safePercentage) / 100);
|
30
|
+
|
31
|
+
// Convert string to array for manipulation
|
32
|
+
const chars = value.split("");
|
33
|
+
|
34
|
+
// Get random positions to obfuscate
|
35
|
+
const positions = Array.from({ length: value.length }, (_, i) => i)
|
36
|
+
.sort(() => Math.random() - 0.5)
|
37
|
+
.slice(0, charsToObfuscate);
|
38
|
+
|
39
|
+
// Replace characters at random positions
|
40
|
+
positions.forEach((pos) => {
|
41
|
+
chars[pos] = obfuscatedChar;
|
42
|
+
});
|
43
|
+
|
44
|
+
return chars.join("");
|
45
|
+
};
|