@pagopa/io-react-native-wallet 0.23.0 → 0.24.0
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 +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
|
+
};
|