@ohos-ports/trezor-device-authenticity 1.1.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/lib/config/deviceAuthenticityBlacklistConfig.d.ts +3 -0
- package/lib/config/deviceAuthenticityBlacklistConfig.js +14 -0
- package/lib/config/deviceAuthenticityBlacklistConfigTypes.d.ts +11 -0
- package/lib/config/deviceAuthenticityBlacklistConfigTypes.js +15 -0
- package/lib/config/deviceAuthenticityConfig.d.ts +3 -0
- package/lib/config/deviceAuthenticityConfig.js +36 -0
- package/lib/config/deviceAuthenticityConfigTypes.d.ts +47 -0
- package/lib/config/deviceAuthenticityConfigTypes.js +21 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +68 -0
- package/lib/tsconfig.lib.tsbuildinfo +1 -0
- package/lib/types.d.ts +27 -0
- package/lib/types.js +6 -0
- package/lib/utils.d.ts +17 -0
- package/lib/utils.js +38 -0
- package/lib/verifyAuthenticityProof.d.ts +4 -0
- package/lib/verifyAuthenticityProof.js +171 -0
- package/lib/x509certificate.d.ts +93 -0
- package/lib/x509certificate.js +277 -0
- package/libESM/config/deviceAuthenticityBlacklistConfig.d.ts +3 -0
- package/libESM/config/deviceAuthenticityBlacklistConfig.js +8 -0
- package/libESM/config/deviceAuthenticityBlacklistConfigTypes.d.ts +11 -0
- package/libESM/config/deviceAuthenticityBlacklistConfigTypes.js +9 -0
- package/libESM/config/deviceAuthenticityConfig.d.ts +3 -0
- package/libESM/config/deviceAuthenticityConfig.js +30 -0
- package/libESM/config/deviceAuthenticityConfigTypes.d.ts +47 -0
- package/libESM/config/deviceAuthenticityConfigTypes.js +15 -0
- package/libESM/index.d.ts +9 -0
- package/libESM/index.js +8 -0
- package/libESM/tsconfig.libESM.tsbuildinfo +1 -0
- package/libESM/types.d.ts +27 -0
- package/libESM/types.js +1 -0
- package/libESM/utils.d.ts +17 -0
- package/libESM/utils.js +28 -0
- package/libESM/verifyAuthenticityProof.d.ts +4 -0
- package/libESM/verifyAuthenticityProof.js +162 -0
- package/libESM/x509certificate.d.ts +93 -0
- package/libESM/x509certificate.js +269 -0
- package/package.json +62 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
2
|
+
import * as crypto from "crypto";
|
|
3
|
+
import { getSubtleCrypto } from "@trezor/crypto-utils";
|
|
4
|
+
import { bufferUtils } from "@trezor/utils";
|
|
5
|
+
import { getRootPubKeyBlacklist, getRootPubKeys } from "./utils.js";
|
|
6
|
+
import { parseCertificate } from "./x509certificate.js";
|
|
7
|
+
export const verifySignatureP256 = async (rawKey, data, signature) => {
|
|
8
|
+
const signer = crypto.createVerify('sha256');
|
|
9
|
+
signer.update(Buffer.from(data));
|
|
10
|
+
const SubtleCrypto = getSubtleCrypto();
|
|
11
|
+
try {
|
|
12
|
+
const ecPubKey = await SubtleCrypto.importKey('raw', rawKey, {
|
|
13
|
+
name: 'ECDSA',
|
|
14
|
+
namedCurve: 'P-256'
|
|
15
|
+
}, true, ['verify']);
|
|
16
|
+
const spkiPubKey = await SubtleCrypto.exportKey('spki', ecPubKey);
|
|
17
|
+
const key = `-----BEGIN PUBLIC KEY-----\n${Buffer.from(spkiPubKey).toString('base64')}\n-----END PUBLIC KEY-----`;
|
|
18
|
+
return signer.verify({
|
|
19
|
+
key
|
|
20
|
+
}, Buffer.from(signature));
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const verifySignatureEd25519 = (rawKey, data, signature) => {
|
|
26
|
+
try {
|
|
27
|
+
return ed25519.verify(signature, data, rawKey);
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const getVerifyFn = algorithmName => {
|
|
33
|
+
if (algorithmName === 'P-256') return verifySignatureP256;
|
|
34
|
+
if (algorithmName === 'Ed25519') return verifySignatureEd25519;
|
|
35
|
+
throw new Error(`Unsupported signature algorithm.`);
|
|
36
|
+
};
|
|
37
|
+
const validateCaCertExtensions = (cert, pathLen) => {
|
|
38
|
+
let hasKeyUsage,
|
|
39
|
+
hasBasicConstraints = false;
|
|
40
|
+
cert.tbsCertificate.extensions.forEach(ext => {
|
|
41
|
+
if (ext.key === 'keyUsage') {
|
|
42
|
+
if (ext.keyCertSign !== '1') {
|
|
43
|
+
throw new Error(`CA keyCertSign not set`);
|
|
44
|
+
}
|
|
45
|
+
hasKeyUsage = true;
|
|
46
|
+
} else if (ext.key === 'basicConstraints') {
|
|
47
|
+
if (!ext.cA) {
|
|
48
|
+
throw new Error(`CA basicConstraints.cA not set`);
|
|
49
|
+
}
|
|
50
|
+
if (typeof ext.pathLenConstraint != 'number') {
|
|
51
|
+
throw new Error('CA basicConstraints.pathLenConstraint not set');
|
|
52
|
+
}
|
|
53
|
+
if (ext.pathLenConstraint < pathLen) {
|
|
54
|
+
throw new Error('Issuer was not permitted to issue certificate');
|
|
55
|
+
}
|
|
56
|
+
hasBasicConstraints = true;
|
|
57
|
+
} else if (ext.critical) {
|
|
58
|
+
throw new Error(`Unknown critical extension ${ext.key} in CA certificate`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
if (!hasKeyUsage || !hasBasicConstraints) {
|
|
62
|
+
throw new Error(`CA missing extensions keyUsage: ${hasKeyUsage}, basicConstraints: ${hasBasicConstraints}`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
export const verifyAuthenticityProof = async ({
|
|
66
|
+
challenge,
|
|
67
|
+
certificates,
|
|
68
|
+
signature,
|
|
69
|
+
deviceModel,
|
|
70
|
+
allowDebugKeys,
|
|
71
|
+
config,
|
|
72
|
+
blacklistConfig,
|
|
73
|
+
challengePrefix = 'AuthenticateDevice:',
|
|
74
|
+
bufferChunks = []
|
|
75
|
+
}) => {
|
|
76
|
+
const allRootPubKeys = getRootPubKeys({
|
|
77
|
+
config,
|
|
78
|
+
deviceModel,
|
|
79
|
+
allowDebugKeys
|
|
80
|
+
});
|
|
81
|
+
const rootPubKeyBlacklist = getRootPubKeyBlacklist({
|
|
82
|
+
blacklistConfig,
|
|
83
|
+
allowDebugKeys
|
|
84
|
+
});
|
|
85
|
+
const [deviceCert, caCert] = certificates.map((c, i) => {
|
|
86
|
+
const cert = parseCertificate(new Uint8Array(Buffer.from(c, 'hex')));
|
|
87
|
+
if (i === 0) {
|
|
88
|
+
return cert;
|
|
89
|
+
}
|
|
90
|
+
validateCaCertExtensions(cert, i - 1);
|
|
91
|
+
return cert;
|
|
92
|
+
});
|
|
93
|
+
const deviceCertAlgName = deviceCert.signatureAlgorithm.algorithmName;
|
|
94
|
+
const caCertAlgName = caCert.signatureAlgorithm.algorithmName;
|
|
95
|
+
if (deviceCertAlgName !== caCertAlgName) {
|
|
96
|
+
throw new Error('Mismatched signature algorithms in device and CA certificates');
|
|
97
|
+
}
|
|
98
|
+
const verifySignatureFn = getVerifyFn(deviceCertAlgName);
|
|
99
|
+
const caPubKey = Buffer.from(caCert.tbsCertificate.subjectPublicKeyInfo.bits.bytes).toString('hex');
|
|
100
|
+
const isCertSignedByRootPubkey = await Promise.all(allRootPubKeys.map(rootPubKey => verifySignatureFn(Buffer.from(rootPubKey, 'hex'), caCert.tbsCertificate.asn1.raw, caCert.signatureValue.bits.bytes)));
|
|
101
|
+
const rootPubKeyIndex = isCertSignedByRootPubkey.findIndex(valid => !!valid);
|
|
102
|
+
const rootPubKeyMatch = allRootPubKeys[rootPubKeyIndex];
|
|
103
|
+
const caCertValidityFrom = caCert.tbsCertificate.validity.from.getTime();
|
|
104
|
+
if (caCertValidityFrom > new Date().getTime()) {
|
|
105
|
+
throw new Error(`CA validity from ${caCertValidityFrom} can't be in the future!`);
|
|
106
|
+
}
|
|
107
|
+
if (rootPubKeyMatch === undefined) {
|
|
108
|
+
return {
|
|
109
|
+
valid: false,
|
|
110
|
+
caPubKey,
|
|
111
|
+
error: 'ROOT_PUBKEY_NOT_FOUND'
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const [subject] = deviceCert.tbsCertificate.subject;
|
|
115
|
+
if (!subject.parameters || subject.algorithmOid !== '2.5.4.3') {
|
|
116
|
+
throw new Error('Missing certificate subject');
|
|
117
|
+
}
|
|
118
|
+
const subjectValue = Buffer.from(subject.parameters.asn1.contents.subarray(0, 4)).toString();
|
|
119
|
+
if (subjectValue !== deviceModel) {
|
|
120
|
+
return {
|
|
121
|
+
valid: false,
|
|
122
|
+
caPubKey,
|
|
123
|
+
rootPubKey: rootPubKeyMatch,
|
|
124
|
+
error: 'INVALID_DEVICE_MODEL'
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const isDeviceCertValid = await verifySignatureFn(Buffer.from(caCert.tbsCertificate.subjectPublicKeyInfo.bits.bytes), deviceCert.tbsCertificate.asn1.raw, deviceCert.signatureValue.bits.bytes);
|
|
128
|
+
const challengePrefixBuffer = Buffer.from(challengePrefix);
|
|
129
|
+
const chunks = bufferChunks && bufferChunks.length > 0 ? [challengePrefixBuffer, ...bufferChunks] : [challengePrefixBuffer, challenge];
|
|
130
|
+
const prefixedBuffer = Buffer.concat(chunks.flatMap(chunk => [bufferUtils.getChunkSize(chunk.byteLength), chunk]));
|
|
131
|
+
const isSignatureValid = await verifySignatureFn(Buffer.from(deviceCert.tbsCertificate.subjectPublicKeyInfo.bits.bytes), prefixedBuffer, Buffer.from(signature, 'hex'));
|
|
132
|
+
if (isDeviceCertValid && isSignatureValid) {
|
|
133
|
+
if (rootPubKeyBlacklist.includes(caPubKey)) {
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
caPubKey,
|
|
137
|
+
rootPubKey: rootPubKeyMatch,
|
|
138
|
+
error: 'CA_PUBKEY_BLACKLISTED'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
valid: true,
|
|
143
|
+
caPubKey,
|
|
144
|
+
rootPubKey: rootPubKeyMatch
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (!isDeviceCertValid) {
|
|
148
|
+
return {
|
|
149
|
+
valid: false,
|
|
150
|
+
caPubKey,
|
|
151
|
+
rootPubKey: rootPubKeyMatch,
|
|
152
|
+
error: 'INVALID_DEVICE_CERTIFICATE'
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
valid: false,
|
|
157
|
+
caPubKey,
|
|
158
|
+
rootPubKey: rootPubKeyMatch,
|
|
159
|
+
error: 'INVALID_DEVICE_SIGNATURE'
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=verifyAuthenticityProof.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
interface Asn1 {
|
|
2
|
+
cls: number;
|
|
3
|
+
tag: number;
|
|
4
|
+
structured: boolean;
|
|
5
|
+
byteLength: number;
|
|
6
|
+
contents: Uint8Array;
|
|
7
|
+
raw: Uint8Array;
|
|
8
|
+
}
|
|
9
|
+
type Oid = `${number}.${number}.${number}.${number}` | `${number}.${number}.${number}.${number}.${number}.${number}`;
|
|
10
|
+
export type AlgorithmName = 'P-256' | 'Ed25519' | 'unknown';
|
|
11
|
+
type Extension = {
|
|
12
|
+
key: 'keyUsage';
|
|
13
|
+
critical?: boolean;
|
|
14
|
+
keyCertSign: '0' | '1';
|
|
15
|
+
} | {
|
|
16
|
+
key: 'basicConstraints';
|
|
17
|
+
critical?: boolean;
|
|
18
|
+
cA: boolean;
|
|
19
|
+
pathLenConstraint?: number;
|
|
20
|
+
} | (Asn1 & {
|
|
21
|
+
key: Oid;
|
|
22
|
+
critical?: boolean;
|
|
23
|
+
});
|
|
24
|
+
export declare const parseName: (asn1: Asn1) => {
|
|
25
|
+
asn1: Asn1;
|
|
26
|
+
algorithmOid: Oid;
|
|
27
|
+
algorithmName: AlgorithmName;
|
|
28
|
+
parameters: {
|
|
29
|
+
asn1: Asn1;
|
|
30
|
+
} | null;
|
|
31
|
+
}[];
|
|
32
|
+
export declare const parseCertificate: (byteArray: Uint8Array) => {
|
|
33
|
+
asn1: Asn1;
|
|
34
|
+
tbsCertificate: {
|
|
35
|
+
asn1: Asn1;
|
|
36
|
+
version: Asn1;
|
|
37
|
+
serialNumber: Asn1;
|
|
38
|
+
signature: {
|
|
39
|
+
asn1: Asn1;
|
|
40
|
+
algorithmOid: Oid;
|
|
41
|
+
algorithmName: AlgorithmName;
|
|
42
|
+
parameters: {
|
|
43
|
+
asn1: Asn1;
|
|
44
|
+
} | null;
|
|
45
|
+
};
|
|
46
|
+
issuer: Asn1;
|
|
47
|
+
validity: {
|
|
48
|
+
from: Date;
|
|
49
|
+
to: Date;
|
|
50
|
+
};
|
|
51
|
+
subject: {
|
|
52
|
+
asn1: Asn1;
|
|
53
|
+
algorithmOid: Oid;
|
|
54
|
+
algorithmName: AlgorithmName;
|
|
55
|
+
parameters: {
|
|
56
|
+
asn1: Asn1;
|
|
57
|
+
} | null;
|
|
58
|
+
}[];
|
|
59
|
+
subjectPublicKeyInfo: {
|
|
60
|
+
asn1: Asn1;
|
|
61
|
+
algorithm: {
|
|
62
|
+
asn1: Asn1;
|
|
63
|
+
algorithmOid: Oid;
|
|
64
|
+
algorithmName: AlgorithmName;
|
|
65
|
+
parameters: {
|
|
66
|
+
asn1: Asn1;
|
|
67
|
+
} | null;
|
|
68
|
+
};
|
|
69
|
+
bits: {
|
|
70
|
+
unusedBits: number;
|
|
71
|
+
bytes: Uint8Array<ArrayBufferLike>;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
extensions: Extension[];
|
|
75
|
+
};
|
|
76
|
+
signatureAlgorithm: {
|
|
77
|
+
asn1: Asn1;
|
|
78
|
+
algorithmOid: Oid;
|
|
79
|
+
algorithmName: AlgorithmName;
|
|
80
|
+
parameters: {
|
|
81
|
+
asn1: Asn1;
|
|
82
|
+
} | null;
|
|
83
|
+
};
|
|
84
|
+
signatureValue: {
|
|
85
|
+
asn1: Asn1;
|
|
86
|
+
bits: {
|
|
87
|
+
unusedBits: number;
|
|
88
|
+
bytes: Uint8Array<ArrayBufferLike>;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
export {};
|
|
93
|
+
//# sourceMappingURL=x509certificate.d.ts.map
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
const parseOidToAlgorithmName = oid => {
|
|
2
|
+
if (oid === '1.2.840.10045.4.3.2') return 'P-256';
|
|
3
|
+
if (oid === '1.3.101.112') return 'Ed25519';
|
|
4
|
+
return 'unknown';
|
|
5
|
+
};
|
|
6
|
+
const derToAsn1 = byteArray => {
|
|
7
|
+
let position = 0;
|
|
8
|
+
function getTag() {
|
|
9
|
+
let tag = byteArray[0] & 0x1f;
|
|
10
|
+
position += 1;
|
|
11
|
+
if (tag === 0x1f) {
|
|
12
|
+
tag = 0;
|
|
13
|
+
while (byteArray[position] >= 0x80) {
|
|
14
|
+
tag = tag * 128 + byteArray[position] - 0x80;
|
|
15
|
+
position += 1;
|
|
16
|
+
}
|
|
17
|
+
tag = tag * 128 + byteArray[position] - 0x80;
|
|
18
|
+
position += 1;
|
|
19
|
+
}
|
|
20
|
+
return tag;
|
|
21
|
+
}
|
|
22
|
+
function getLength() {
|
|
23
|
+
let length = 0;
|
|
24
|
+
if (byteArray[position] < 0x80) {
|
|
25
|
+
length = byteArray[position];
|
|
26
|
+
position += 1;
|
|
27
|
+
} else {
|
|
28
|
+
const numberOfDigits = byteArray[position] & 0x7f;
|
|
29
|
+
position += 1;
|
|
30
|
+
length = 0;
|
|
31
|
+
for (let i = 0; i < numberOfDigits; i++) {
|
|
32
|
+
length = length * 256 + byteArray[position];
|
|
33
|
+
position += 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return length;
|
|
37
|
+
}
|
|
38
|
+
const cls = (byteArray[0] & 0xc0) / 64;
|
|
39
|
+
const structured = (byteArray[0] & 0x20) === 0x20;
|
|
40
|
+
const tag = getTag();
|
|
41
|
+
if (byteArray[position] === 0x80) {
|
|
42
|
+
throw new Error('Unsupported length encoding');
|
|
43
|
+
}
|
|
44
|
+
const length = getLength();
|
|
45
|
+
const byteLength = position + length;
|
|
46
|
+
const contents = byteArray.subarray(position, byteLength);
|
|
47
|
+
const raw = byteArray.subarray(0, byteLength);
|
|
48
|
+
return {
|
|
49
|
+
cls,
|
|
50
|
+
tag,
|
|
51
|
+
structured,
|
|
52
|
+
byteLength,
|
|
53
|
+
contents,
|
|
54
|
+
raw
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const derToAsn1List = byteArray => {
|
|
58
|
+
const result = [];
|
|
59
|
+
let nextPosition = 0;
|
|
60
|
+
while (nextPosition < byteArray.length) {
|
|
61
|
+
const nextPiece = derToAsn1(byteArray.subarray(nextPosition));
|
|
62
|
+
result.push(nextPiece);
|
|
63
|
+
nextPosition += nextPiece.byteLength;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
};
|
|
67
|
+
const derBitStringValue = byteArray => ({
|
|
68
|
+
unusedBits: byteArray[0],
|
|
69
|
+
bytes: byteArray.subarray(1)
|
|
70
|
+
});
|
|
71
|
+
const parseSignatureValue = asn1 => {
|
|
72
|
+
if (asn1.cls !== 0 || asn1.tag !== 3 || asn1.structured) {
|
|
73
|
+
throw new Error('Bad signature value. Not a BIT STRING.');
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
asn1,
|
|
77
|
+
bits: derBitStringValue(asn1.contents)
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
const derObjectIdentifierValue = byteArray => {
|
|
81
|
+
let oid = `${Math.floor(byteArray[0] / 40)}.${byteArray[0] % 40}`;
|
|
82
|
+
let position = 1;
|
|
83
|
+
while (position < byteArray.length) {
|
|
84
|
+
let nextInteger = 0;
|
|
85
|
+
while (byteArray[position] >= 0x80) {
|
|
86
|
+
nextInteger = nextInteger * 0x80 + (byteArray[position] & 0x7f);
|
|
87
|
+
position += 1;
|
|
88
|
+
}
|
|
89
|
+
nextInteger = nextInteger * 0x80 + byteArray[position];
|
|
90
|
+
position += 1;
|
|
91
|
+
oid += `.${nextInteger}`;
|
|
92
|
+
}
|
|
93
|
+
return oid;
|
|
94
|
+
};
|
|
95
|
+
const parseAlgorithmIdentifier = asn1 => {
|
|
96
|
+
if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
|
|
97
|
+
throw new Error('Bad algorithm identifier. Not a SEQUENCE.');
|
|
98
|
+
}
|
|
99
|
+
const pieces = derToAsn1List(asn1.contents);
|
|
100
|
+
if (pieces.length > 2) {
|
|
101
|
+
throw new Error('Bad algorithm identifier. Contains too many child objects.');
|
|
102
|
+
}
|
|
103
|
+
const encodedAlgorithm = pieces[0];
|
|
104
|
+
if (encodedAlgorithm.cls !== 0 || encodedAlgorithm.tag !== 6 || encodedAlgorithm.structured) {
|
|
105
|
+
throw new Error('Bad algorithm identifier. Does not begin with an OBJECT IDENTIFIER.');
|
|
106
|
+
}
|
|
107
|
+
const algorithmOid = derObjectIdentifierValue(encodedAlgorithm.contents);
|
|
108
|
+
const algorithmName = parseOidToAlgorithmName(algorithmOid);
|
|
109
|
+
return {
|
|
110
|
+
asn1,
|
|
111
|
+
algorithmOid,
|
|
112
|
+
algorithmName,
|
|
113
|
+
parameters: pieces.length === 2 ? {
|
|
114
|
+
asn1: pieces[1]
|
|
115
|
+
} : null
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
export const parseName = asn1 => derToAsn1List(asn1.contents).map(item => {
|
|
119
|
+
const attrSet = derToAsn1(item.contents);
|
|
120
|
+
return parseAlgorithmIdentifier(attrSet);
|
|
121
|
+
});
|
|
122
|
+
const parseSubjectPublicKeyInfo = asn1 => {
|
|
123
|
+
if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
|
|
124
|
+
throw new Error('Bad SPKI. Not a SEQUENCE.');
|
|
125
|
+
}
|
|
126
|
+
const pieces = derToAsn1List(asn1.contents);
|
|
127
|
+
if (pieces.length !== 2) {
|
|
128
|
+
throw new Error('Bad SubjectPublicKeyInfo. Wrong number of child objects.');
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
asn1,
|
|
132
|
+
algorithm: parseAlgorithmIdentifier(pieces[0]),
|
|
133
|
+
bits: derBitStringValue(pieces[1].contents)
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
const parseUtcTime = time => {
|
|
137
|
+
let offset = 4;
|
|
138
|
+
let yearOffset = 0;
|
|
139
|
+
if (time.tag === 23) {
|
|
140
|
+
offset = 2;
|
|
141
|
+
yearOffset = 2000;
|
|
142
|
+
}
|
|
143
|
+
const utc = Buffer.from(time.contents).toString();
|
|
144
|
+
const year = yearOffset + Number(utc.substring(0, offset));
|
|
145
|
+
const month = Number(utc.substring(offset, offset + 2)) - 1;
|
|
146
|
+
const day = Number(utc.substring(offset + 2, offset + 4));
|
|
147
|
+
const hour = Number(utc.substring(offset + 4, offset + 6));
|
|
148
|
+
const minute = Number(utc.substring(offset + 6, offset + 8));
|
|
149
|
+
const date = new Date();
|
|
150
|
+
date.setUTCFullYear(year, month, day);
|
|
151
|
+
date.setUTCHours(hour, minute, 0);
|
|
152
|
+
return date;
|
|
153
|
+
};
|
|
154
|
+
const parseValidity = asn1 => {
|
|
155
|
+
const [from, to] = derToAsn1List(asn1.contents);
|
|
156
|
+
return {
|
|
157
|
+
from: parseUtcTime(from),
|
|
158
|
+
to: parseUtcTime(to)
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
const parseExtensions = data => {
|
|
162
|
+
const asn1 = derToAsn1(data.contents);
|
|
163
|
+
if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
|
|
164
|
+
throw new Error("This can't be a Extension. Wrong data type.");
|
|
165
|
+
}
|
|
166
|
+
const readBoolean = value => {
|
|
167
|
+
if (!value) return false;
|
|
168
|
+
if (value.cls !== 0 || value.tag !== 1 || value.contents.length !== 1 || value.structured) {
|
|
169
|
+
throw new Error("This can't be a boolean. Wrong data type.");
|
|
170
|
+
}
|
|
171
|
+
if (![0x00, 0xff].includes(value.contents[0])) {
|
|
172
|
+
throw new Error('Invalid boolean value.');
|
|
173
|
+
}
|
|
174
|
+
return value.contents[0] === 0xff;
|
|
175
|
+
};
|
|
176
|
+
const readBitString = uint8Array => {
|
|
177
|
+
const buffer = Buffer.from(uint8Array);
|
|
178
|
+
const tag = buffer.readUInt8(0);
|
|
179
|
+
if (tag !== 3) {
|
|
180
|
+
throw new Error("This can't be a bit string. Wrong data type.");
|
|
181
|
+
}
|
|
182
|
+
const length = buffer.readUInt8(1);
|
|
183
|
+
const unusedBits = buffer.readUInt8(2);
|
|
184
|
+
const bitStringBytes = buffer.subarray(3, 3 + length - 1);
|
|
185
|
+
const bitString = bitStringBytes.reduce((str, byte) => str + byte.toString(2).padStart(8, '0'), '');
|
|
186
|
+
return bitString.slice(0, bitString.length - unusedBits);
|
|
187
|
+
};
|
|
188
|
+
const readInteger = value => {
|
|
189
|
+
if (!value) return undefined;
|
|
190
|
+
if (value.cls !== 0 || value.tag !== 2 || value.contents.length !== 1 || value.structured) {
|
|
191
|
+
throw new Error("This can't be a integer. Wrong data type.");
|
|
192
|
+
}
|
|
193
|
+
return Buffer.from(value.contents).readInt8();
|
|
194
|
+
};
|
|
195
|
+
const extensions = [];
|
|
196
|
+
derToAsn1List(asn1.contents).forEach(item => {
|
|
197
|
+
const [id, ...pieces] = derToAsn1List(item.contents);
|
|
198
|
+
if (id.cls !== 0 || id.tag !== 6 || id.structured) {
|
|
199
|
+
throw new Error('Bad extension. Does not begin with an OBJECT IDENTIFIER.');
|
|
200
|
+
}
|
|
201
|
+
const algorithm = derObjectIdentifierValue(id.contents);
|
|
202
|
+
const critical = pieces.length > 1 ? readBoolean(pieces[0]) : false;
|
|
203
|
+
const extnValue = pieces.length > 1 ? pieces[1] : pieces[0];
|
|
204
|
+
if (extnValue.cls !== 0 || extnValue.tag !== 4 || extnValue.structured) {
|
|
205
|
+
throw new Error("This can't be a octet string. Wrong data type.");
|
|
206
|
+
}
|
|
207
|
+
if (algorithm === '2.5.29.15') {
|
|
208
|
+
extensions.push({
|
|
209
|
+
key: 'keyUsage',
|
|
210
|
+
critical,
|
|
211
|
+
keyCertSign: readBitString(extnValue.contents)[5]
|
|
212
|
+
});
|
|
213
|
+
} else if (algorithm === '2.5.29.19') {
|
|
214
|
+
const fields = derToAsn1List(derToAsn1(extnValue.contents).contents);
|
|
215
|
+
const ca = fields.length > 0 && fields[0].tag === 1 ? fields[0] : undefined;
|
|
216
|
+
const len = fields.length > 0 && fields[0].tag === 2 ? fields[0] : fields[1];
|
|
217
|
+
extensions.push({
|
|
218
|
+
key: 'basicConstraints',
|
|
219
|
+
critical,
|
|
220
|
+
cA: readBoolean(ca),
|
|
221
|
+
pathLenConstraint: readInteger(len)
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
extensions.push({
|
|
225
|
+
key: algorithm,
|
|
226
|
+
critical,
|
|
227
|
+
...item
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
return extensions;
|
|
232
|
+
};
|
|
233
|
+
const parseTBSCertificate = asn1 => {
|
|
234
|
+
if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
|
|
235
|
+
throw new Error("This can't be a TBSCertificate. Wrong data type.");
|
|
236
|
+
}
|
|
237
|
+
const pieces = derToAsn1List(asn1.contents);
|
|
238
|
+
if (pieces.length < 7) {
|
|
239
|
+
throw new Error('Bad TBS Certificate. There are fewer than the seven required children.');
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
asn1,
|
|
243
|
+
version: pieces[0],
|
|
244
|
+
serialNumber: pieces[1],
|
|
245
|
+
signature: parseAlgorithmIdentifier(pieces[2]),
|
|
246
|
+
issuer: pieces[3],
|
|
247
|
+
validity: parseValidity(pieces[4]),
|
|
248
|
+
subject: parseName(pieces[5]),
|
|
249
|
+
subjectPublicKeyInfo: parseSubjectPublicKeyInfo(pieces[6]),
|
|
250
|
+
extensions: parseExtensions(pieces[7])
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
export const parseCertificate = byteArray => {
|
|
254
|
+
const asn1 = derToAsn1(byteArray);
|
|
255
|
+
if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) {
|
|
256
|
+
throw new Error("This can't be an X.509 certificate. Wrong data type.");
|
|
257
|
+
}
|
|
258
|
+
const pieces = derToAsn1List(asn1.contents);
|
|
259
|
+
if (pieces.length !== 3) {
|
|
260
|
+
throw new Error('Certificate contains more than the three specified children.');
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
asn1,
|
|
264
|
+
tbsCertificate: parseTBSCertificate(pieces[0]),
|
|
265
|
+
signatureAlgorithm: parseAlgorithmIdentifier(pieces[1]),
|
|
266
|
+
signatureValue: parseSignatureValue(pieces[2])
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
//# sourceMappingURL=x509certificate.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ohos-ports/trezor-device-authenticity",
|
|
3
|
+
"version": "1.1.2-beta.1",
|
|
4
|
+
"author": "Trezor <info@trezor.io>",
|
|
5
|
+
"homepage": "https://github.com/trezor/trezor-suite/tree/develop/packages/device-authenticity",
|
|
6
|
+
"description": "Utils for authenticity verification of trezor device secure element response.",
|
|
7
|
+
"npmPublishAccess": "public",
|
|
8
|
+
"license": "See LICENSE.md in repo root",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/ohos-ports/ohos-ports.git",
|
|
12
|
+
"directory": "ports/trezor-device-authenticity/1.1.2"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/ohos-ports/ohos-ports/issues"
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"main": "lib/index.js",
|
|
20
|
+
"types": "lib/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./libESM/index.js",
|
|
24
|
+
"require": "./lib/index.js"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"main": "lib/index.js",
|
|
29
|
+
"files": [
|
|
30
|
+
"lib/",
|
|
31
|
+
"libESM/",
|
|
32
|
+
"!**/*.map"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build:lib": "yarn build:lib:cjs && yarn build:lib:esm",
|
|
36
|
+
"build:lib:cjs": "yarn g:rimraf ./lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/publish/replace-imports.sh ./lib cjs",
|
|
37
|
+
"build:lib:esm": "yarn g:rimraf ./libESM && yarn g:tsc --build tsconfig.libESM.json && ../../scripts/publish/replace-imports.sh ./libESM esm",
|
|
38
|
+
"depcheck": "yarn g:depcheck",
|
|
39
|
+
"type-check": "yarn g:tsc --build",
|
|
40
|
+
"test:unit": "yarn g:jest",
|
|
41
|
+
"lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'",
|
|
42
|
+
"prepublishOnly": "yarn tsx ../../scripts/publish/prepublishNPM.js",
|
|
43
|
+
"prepublish": "yarn tsx ../../scripts/publish/prepublish.js"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@noble/curves": "^2.0.1",
|
|
47
|
+
"@trezor/crypto-utils": "1.2.0",
|
|
48
|
+
"@trezor/protobuf": "1.5.2",
|
|
49
|
+
"@trezor/schema-utils": "1.4.0",
|
|
50
|
+
"@trezor/utils": "9.5.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@ohos-ports/tsx": "^4.22.4-beta.4"
|
|
54
|
+
},
|
|
55
|
+
"exports": {
|
|
56
|
+
".": {
|
|
57
|
+
"import": "./libESM/index.js",
|
|
58
|
+
"require": "./lib/index.js"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"types": "lib/index.d.ts"
|
|
62
|
+
}
|