nskd-lbr 1.0.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/LICENSE +156 -0
- package/README.md +166 -0
- package/package.json +23 -0
- package/src/nskd-lbr.js +414 -0
- package/src/nskd-lbr.min.js +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
NSD LICENSE
|
|
2
|
+
Version 1.0, May 2025
|
|
3
|
+
(NON STANDARD DISTRIBUTION LICENSE)
|
|
4
|
+
|
|
5
|
+
================================================================================
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2025 Douxx.tech <douxx@douxx.tech>
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted to copy and distribute verbatim copies of this
|
|
10
|
+
license document, but modification is not permitted without explicit written
|
|
11
|
+
consent from the copyright holder.
|
|
12
|
+
|
|
13
|
+
================================================================================
|
|
14
|
+
|
|
15
|
+
PREAMBLE
|
|
16
|
+
|
|
17
|
+
The NSD License (Non Standard Distribution License) is designed to provide
|
|
18
|
+
a balance between open source principles and commercial viability. This
|
|
19
|
+
license ensures that software remains accessible while protecting the
|
|
20
|
+
rights and interests of original authors and contributors.
|
|
21
|
+
|
|
22
|
+
This license grants users the freedom to use, study, and distribute the
|
|
23
|
+
software while maintaining proper attribution and ensuring that derivative
|
|
24
|
+
works remain under compatible terms.
|
|
25
|
+
|
|
26
|
+
The goal is to foster innovation and collaboration while respecting
|
|
27
|
+
intellectual property rights and encouraging responsible development
|
|
28
|
+
practices.
|
|
29
|
+
|
|
30
|
+
================================================================================
|
|
31
|
+
|
|
32
|
+
TERMS AND CONDITIONS
|
|
33
|
+
|
|
34
|
+
1. DEFINITIONS
|
|
35
|
+
|
|
36
|
+
a) "License" refers to this NSD License version 1.0.
|
|
37
|
+
|
|
38
|
+
b) "Work" refers to the copyrightable software, documentation, or other
|
|
39
|
+
materials licensed under this License.
|
|
40
|
+
|
|
41
|
+
c) "You" refers to the individual or legal entity exercising rights
|
|
42
|
+
under this License.
|
|
43
|
+
|
|
44
|
+
d) "Derivative Work" means a work based upon the Work, such as a
|
|
45
|
+
revision, modification, translation, abridgment, condensation, or
|
|
46
|
+
any other form in which the Work may be recast, transformed, or
|
|
47
|
+
adapted.
|
|
48
|
+
|
|
49
|
+
e) "Distribution" means making copies of the Work available to third
|
|
50
|
+
parties through any medium.
|
|
51
|
+
|
|
52
|
+
2. GRANT OF RIGHTS
|
|
53
|
+
|
|
54
|
+
Subject to the terms and conditions of this License, You are hereby
|
|
55
|
+
granted a worldwide, royalty-free, non-exclusive license to:
|
|
56
|
+
|
|
57
|
+
a) Use the Work for any purpose, including commercial purposes
|
|
58
|
+
b) Study and examine the Work
|
|
59
|
+
c) Make copies of the Work
|
|
60
|
+
d) Distribute copies of the Work
|
|
61
|
+
e) Create and distribute Derivative Works
|
|
62
|
+
|
|
63
|
+
3. CONDITIONS FOR USE AND DISTRIBUTION
|
|
64
|
+
|
|
65
|
+
a) ATTRIBUTION: You must retain all copyright notices, this License,
|
|
66
|
+
and any other notices that were included with the Work. When
|
|
67
|
+
distributing the Work or Derivative Works, you must provide clear
|
|
68
|
+
attribution to the original author(s).
|
|
69
|
+
|
|
70
|
+
b) LICENSE INCLUSION: Any distribution of the Work must include a copy
|
|
71
|
+
of this License or a clear reference to where it can be obtained.
|
|
72
|
+
|
|
73
|
+
c) SOURCE CODE AVAILABILITY: If you distribute the Work in binary or
|
|
74
|
+
compiled form, you must either:
|
|
75
|
+
- Include the complete source code with the distribution, or
|
|
76
|
+
- Provide a written offer to supply the source code upon request
|
|
77
|
+
for a period of at least three years
|
|
78
|
+
|
|
79
|
+
d) DERIVATIVE WORKS: Derivative Works must be clearly marked as such
|
|
80
|
+
and must include a prominent notice describing the modifications
|
|
81
|
+
made and the date of modification.
|
|
82
|
+
|
|
83
|
+
4. COPYLEFT PROVISION
|
|
84
|
+
|
|
85
|
+
Any Derivative Work you create must be licensed under terms that are
|
|
86
|
+
compatible with this License. You may add additional permissions but
|
|
87
|
+
may not impose additional restrictions that contradict the terms of
|
|
88
|
+
this License.
|
|
89
|
+
|
|
90
|
+
5. COMMERCIAL USE
|
|
91
|
+
|
|
92
|
+
Commercial use is explicitly permitted under this License, provided
|
|
93
|
+
all other terms and conditions are met. You may charge fees for
|
|
94
|
+
distribution, support, or warranty services.
|
|
95
|
+
|
|
96
|
+
6. NO WARRANTY
|
|
97
|
+
|
|
98
|
+
THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
99
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
100
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT
|
|
101
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES,
|
|
102
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
|
|
103
|
+
ARISING FROM, OUT OF, OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER
|
|
104
|
+
DEALINGS IN THE WORK.
|
|
105
|
+
|
|
106
|
+
7. TERMINATION
|
|
107
|
+
|
|
108
|
+
This License and the rights granted hereunder will terminate automatically
|
|
109
|
+
if You fail to comply with the terms herein. However, parties who have
|
|
110
|
+
received copies or rights from You under this License will not have their
|
|
111
|
+
licenses terminated provided they remain in full compliance.
|
|
112
|
+
|
|
113
|
+
8. MISCELLANEOUS
|
|
114
|
+
|
|
115
|
+
a) If any provision of this License is held to be unenforceable, such
|
|
116
|
+
provision shall be reformed only to the extent necessary to make it
|
|
117
|
+
enforceable.
|
|
118
|
+
|
|
119
|
+
b) This License represents the complete agreement concerning the subject
|
|
120
|
+
matter hereof.
|
|
121
|
+
|
|
122
|
+
c) This License shall be governed by the laws of Switzerland's Juridiction,
|
|
123
|
+
excluding conflict of law provisions.
|
|
124
|
+
|
|
125
|
+
9. LICENSE VERSIONING
|
|
126
|
+
|
|
127
|
+
The copyright holder may publish revised versions of the NSD License.
|
|
128
|
+
Each version will be given a distinguishing version number. Unless
|
|
129
|
+
otherwise specified, you may use the Work under the terms of the version
|
|
130
|
+
of the License under which you originally received it.
|
|
131
|
+
|
|
132
|
+
10. APPLICATION OF LICENSE
|
|
133
|
+
|
|
134
|
+
To properly apply this License to any work, the copyright holder must
|
|
135
|
+
include a copyright notice and license identification statement in each
|
|
136
|
+
source file or in a prominent location within the work's documentation.
|
|
137
|
+
|
|
138
|
+
The required notice shall contain at minimum:
|
|
139
|
+
a) A copyright statement identifying the copyright holder and year
|
|
140
|
+
b) A clear statement that the work is licensed under this License
|
|
141
|
+
c) Reference to where the complete License text may be obtained
|
|
142
|
+
|
|
143
|
+
The standard format for such notice is:
|
|
144
|
+
|
|
145
|
+
Copyright (C) [YEAR] [COPYRIGHT HOLDER NAME] <[CONTACT EMAIL]>
|
|
146
|
+
|
|
147
|
+
This work is licensed under the NSD License v1.0.
|
|
148
|
+
You may obtain a copy of the License at: https://douxx.tech/NSD
|
|
149
|
+
|
|
150
|
+
Failure to include proper license notices may affect the enforceability
|
|
151
|
+
of the rights granted herein and may result in copyright infringement
|
|
152
|
+
claims.
|
|
153
|
+
|
|
154
|
+
================================================================================
|
|
155
|
+
|
|
156
|
+
END OF TERMS AND CONDITIONS
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# NoSkid Certificate Library
|
|
2
|
+
|
|
3
|
+
A JavaScript library for working with NoSkid certificates.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [API Reference](#api-reference)
|
|
10
|
+
- [Examples](#examples)
|
|
11
|
+
- [Configuration](#configuration)
|
|
12
|
+
- [Error Handling](#error-handling)
|
|
13
|
+
- [Contributing](#contributing)
|
|
14
|
+
- [License](#license)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### Browser
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<script src="https://lbr.noskid.today"></script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Node.js
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install nskd-lbr
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
const NskdLbr = require('nskd-lbr');
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Basic Usage
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// Initialize the library
|
|
40
|
+
const nskd = new NskdLbr();
|
|
41
|
+
|
|
42
|
+
// Verify a certificate from a file
|
|
43
|
+
const fileInput = document.getElementById('certificate-file');
|
|
44
|
+
fileInput.addEventListener('change', async (event) => {
|
|
45
|
+
try {
|
|
46
|
+
const result = await nskd.loadFromFile(event.target.files[0]);
|
|
47
|
+
if (result.valid) {
|
|
48
|
+
console.log('Certificate is valid!');
|
|
49
|
+
console.log(nskd.getFormattedDetails());
|
|
50
|
+
} else {
|
|
51
|
+
console.log('Certificate is invalid:', result.message);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Error:', error.message);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Verify a certificate with a key
|
|
59
|
+
const verificationKey = 'a1b2c3d4e5f6...'; // 64-character hex string
|
|
60
|
+
nskd.verifyWithKey(verificationKey)
|
|
61
|
+
.then(result => {
|
|
62
|
+
if (result.valid) {
|
|
63
|
+
console.log('Certificate is valid!');
|
|
64
|
+
} else {
|
|
65
|
+
console.log('Certificate is invalid:', result.message);
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
.catch(error => {
|
|
69
|
+
console.error('Error:', error.message);
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API Reference
|
|
74
|
+
|
|
75
|
+
### Constructor
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
new NskdLbr(options)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Options:**
|
|
82
|
+
|
|
83
|
+
| Option | Type | Default | Description |
|
|
84
|
+
| - | - | - | - |
|
|
85
|
+
| `apiUrl` | string | `'https://check.noskid.today/'` | The API endpoint for certificate verification |
|
|
86
|
+
| `debug` | boolean | `false` | Enable debug logging |
|
|
87
|
+
| `timeout` | number | `10000` | Request timeout in milliseconds |
|
|
88
|
+
| `strictCheck` | boolean | `true` | Validate local data against API response |
|
|
89
|
+
| `onnskdLbrLog` | function | `null` | Custom logging function |
|
|
90
|
+
|
|
91
|
+
### Methods
|
|
92
|
+
|
|
93
|
+
| Method | Description | Returns |
|
|
94
|
+
| - | - | - |
|
|
95
|
+
| `loadFromFile(file)` | Load and verify a certificate from a PNG file | `Promise<Object>` Verification result |
|
|
96
|
+
| `verifyWithKey(key)` | Verify a certificate using a verification key | `Promise<Object>` Verification result |
|
|
97
|
+
| `getCertificateData()` | Get the current certificate data | `Object|null` Certificate data or null |
|
|
98
|
+
| `isValidCertificate()` | Check if the certificate is valid | `boolean` |
|
|
99
|
+
| `getFormattedDetails()` | Get formatted certificate details | `string` |
|
|
100
|
+
| `reset()` | Reset the certificate data | `void` |
|
|
101
|
+
|
|
102
|
+
### Logging
|
|
103
|
+
|
|
104
|
+
The library provides a logging method that can be used to track the verification process:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
nskd.nskdLbrLog(message, level);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Levels:**
|
|
111
|
+
|
|
112
|
+
- `info` (default)
|
|
113
|
+
- `error`
|
|
114
|
+
- `warning`
|
|
115
|
+
- `success`
|
|
116
|
+
|
|
117
|
+
## Examples
|
|
118
|
+
|
|
119
|
+
### Custom Logging
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
const nskd = new NskdLbr({
|
|
123
|
+
onnskdLbrLog: (message, level) => {
|
|
124
|
+
// Custom logging implementation
|
|
125
|
+
console.log(`[Custom Log] [${level.toUpperCase()}] ${message}`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Disabling Strict Check
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
const nskd = new NskdLbr({
|
|
134
|
+
strictCheck: false
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Configuration
|
|
139
|
+
|
|
140
|
+
The library can be configured with various options to suit your needs. The most important options are:
|
|
141
|
+
|
|
142
|
+
- `apiUrl`: The endpoint for the NoSkid verification API.
|
|
143
|
+
- `debug`: Enable or disable debug logging.
|
|
144
|
+
- `timeout`: Set the request timeout in milliseconds.
|
|
145
|
+
- `strictCheck`: Enable or disable strict validation of local data against API response.
|
|
146
|
+
|
|
147
|
+
## Error Handling
|
|
148
|
+
|
|
149
|
+
The library throws errors in case of issues during the verification process. It is recommended to wrap the library calls in try-catch blocks to handle these errors gracefully.
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
try {
|
|
153
|
+
const result = await nskd.loadFromFile(file);
|
|
154
|
+
// Handle the result
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error('Verification failed:', error.message);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
Contributions are welcome! Please open an issue or submit a pull request on the GitHub repository.
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
This library is licensed under the NSDv1.0 License. See the LICENSE file for more information.
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nskd-lbr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A JavaScript library for working with NoSkid certificates.",
|
|
5
|
+
"main": "src/nskd-lbr.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/douxxtech/noskid.today.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"noskid",
|
|
15
|
+
"certificate"
|
|
16
|
+
],
|
|
17
|
+
"author": "Douxx",
|
|
18
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/douxxtech/noskid.today/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/douxxtech/noskid.today#readme"
|
|
23
|
+
}
|
package/src/nskd-lbr.js
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NoSkid Certificate Library
|
|
3
|
+
* A JavaScript library for working with NoSkid certificates
|
|
4
|
+
*
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
* @author Douxx <douxx@douxx.tech>
|
|
7
|
+
* @param {string} [options.apiUrl='https://check.noskid.today/'] - Logs debug messages to console
|
|
8
|
+
* @param {boolean} [options.debug=false] - Logs debug messages to console
|
|
9
|
+
* @param {boolean} [options.strictCheck=true] - Whether to validate local data against API response
|
|
10
|
+
* @param {integer} [options.timeout=10000] - API request timeout in milliseconds
|
|
11
|
+
* @param {function} [options.onLog=null] - Whether to validate local data against API response
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class NskdLbr {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.apiUrl = options.apiUrl || 'https://check.noskid.today/';
|
|
17
|
+
this.debug = options.debug || false;
|
|
18
|
+
this.timeout = options.timeout || 10000;
|
|
19
|
+
this.strictCheck = options.strictCheck !== undefined ? options.strictCheck : true;
|
|
20
|
+
this.onLog = options.onLog || null;
|
|
21
|
+
this.certificateData = null;
|
|
22
|
+
this.verificationKey = null;
|
|
23
|
+
this.localData = null;
|
|
24
|
+
this.isValid = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Logs messages with different levels
|
|
29
|
+
* @param {string} message - The message to Log
|
|
30
|
+
*/
|
|
31
|
+
nskdLbrLog(message, level = 'info') {
|
|
32
|
+
if (this.debug) {
|
|
33
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
34
|
+
const prefix = `[${timestamp}] NoSkid:`;
|
|
35
|
+
|
|
36
|
+
switch (level) {
|
|
37
|
+
case 'error':
|
|
38
|
+
console.error(prefix, message);
|
|
39
|
+
break;
|
|
40
|
+
case 'warning':
|
|
41
|
+
console.warn(prefix, message);
|
|
42
|
+
break;
|
|
43
|
+
case 'success':
|
|
44
|
+
console.nskdLbrLog(`%c${prefix} ${message}`, 'color: green');
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
console.nskdLbrLog(prefix, message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (this.onLog && typeof this.onLog === 'function') {
|
|
52
|
+
this.onLog(message, level);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load and verify a certificate from a PNG file
|
|
58
|
+
* @param {File} file - The PNG certificate file
|
|
59
|
+
* @returns {Promise<Object>} Verification result
|
|
60
|
+
*/
|
|
61
|
+
async loadFromFile(file) {
|
|
62
|
+
try {
|
|
63
|
+
this.nskdLbrLog('Starting certificate verification process...', 'info');
|
|
64
|
+
|
|
65
|
+
if (!file) {
|
|
66
|
+
throw new Error('No file provided');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!file.name.toLowerCase().endsWith('.png')) {
|
|
70
|
+
throw new Error('File must be a PNG image');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.nskdLbrLog(`Processing certificate file: ${file.name}`, 'info');
|
|
74
|
+
|
|
75
|
+
const arrayBuffer = await this.readFileAsArrayBuffer(file);
|
|
76
|
+
|
|
77
|
+
const extractedText = await this.extractTextFromPng(arrayBuffer);
|
|
78
|
+
if (!extractedText) {
|
|
79
|
+
throw new Error('Could not extract verification data from file');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.verificationKey = this.extractVerificationKey(extractedText);
|
|
83
|
+
if (!this.verificationKey) {
|
|
84
|
+
throw new Error('No valid verification key found in certificate');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.nskdLbrLog('Successfully extracted verification key', 'success');
|
|
88
|
+
|
|
89
|
+
this.localData = this.extractLocalData(extractedText);
|
|
90
|
+
if (this.localData) {
|
|
91
|
+
this.nskdLbrLog('Local certificate data extracted:', 'info');
|
|
92
|
+
this.nskdLbrLog(`Username: ${this.localData.username}`, 'info');
|
|
93
|
+
this.nskdLbrLog(`Creation Date: ${this.localData.creationDate}`, 'info');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const result = await this.verifyWithAPI();
|
|
97
|
+
return result;
|
|
98
|
+
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.nskdLbrLog(`Error loading certificate: ${error.message}`, 'error');
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Verify a certificate using a verification key directly
|
|
107
|
+
* @param {string} key - The verification key (64-character hex string)
|
|
108
|
+
* @returns {Promise<Object>} Verification result
|
|
109
|
+
*/
|
|
110
|
+
async verifyWithKey(key) {
|
|
111
|
+
try {
|
|
112
|
+
if (!key || typeof key !== 'string') {
|
|
113
|
+
throw new Error('Invalid verification key provided');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!/^[a-f0-9]{64}$/i.test(key)) {
|
|
117
|
+
throw new Error('Verification key must be a 64-character hexadecimal string');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.verificationKey = key.toLowerCase();
|
|
121
|
+
this.nskdLbrLog(`Verifying certificate with key: ${this.verificationKey.substring(0, 16)}...`, 'info');
|
|
122
|
+
|
|
123
|
+
const result = await this.verifyWithAPI();
|
|
124
|
+
return result;
|
|
125
|
+
|
|
126
|
+
} catch (error) {
|
|
127
|
+
this.nskdLbrLog(`Error verifying certificate: ${error.message}`, 'error');
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get the current certificate data
|
|
134
|
+
* @returns {Object|null} Certificate data or null if not loaded/verified
|
|
135
|
+
*/
|
|
136
|
+
getCertificateData() {
|
|
137
|
+
return this.certificateData;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if the certificate is valid
|
|
142
|
+
* @returns {boolean} True if certificate is valid
|
|
143
|
+
*/
|
|
144
|
+
isValidCertificate() {
|
|
145
|
+
return this.isValid;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get formatted certificate details as a string
|
|
150
|
+
* @returns {string} Formatted certificate details
|
|
151
|
+
*/
|
|
152
|
+
getFormattedDetails() {
|
|
153
|
+
if (!this.certificateData) {
|
|
154
|
+
return 'No certificate data available';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const data = this.certificateData;
|
|
158
|
+
return `
|
|
159
|
+
Certificate Details:
|
|
160
|
+
- Certificate #: ${data.certificate_number}
|
|
161
|
+
- Username: ${data.username}
|
|
162
|
+
- Percentage: ${data.percentage}%
|
|
163
|
+
- Creation Date: ${data.creationDate}
|
|
164
|
+
- Country: ${data.country} (${data.countryCode})
|
|
165
|
+
`.trim();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Reset the certificate data
|
|
170
|
+
*/
|
|
171
|
+
reset() {
|
|
172
|
+
this.certificateData = null;
|
|
173
|
+
this.verificationKey = null;
|
|
174
|
+
this.localData = null;
|
|
175
|
+
this.isValid = false;
|
|
176
|
+
this.nskdLbrLog('Certificate data reset', 'info');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Private methods
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Read file as array buffer
|
|
183
|
+
* @private
|
|
184
|
+
*/
|
|
185
|
+
readFileAsArrayBuffer(file) {
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
const reader = new FileReader();
|
|
188
|
+
reader.onload = (e) => resolve(e.target.result);
|
|
189
|
+
reader.onerror = () => reject(new Error('Error reading file'));
|
|
190
|
+
reader.readAsArrayBuffer(file);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Extract text from PNG tEXt chunk
|
|
196
|
+
* @private
|
|
197
|
+
*/
|
|
198
|
+
async extractTextFromPng(arrayBuffer) {
|
|
199
|
+
try {
|
|
200
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
201
|
+
|
|
202
|
+
// Check PNG header
|
|
203
|
+
if (!(bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E && bytes[3] === 0x47)) {
|
|
204
|
+
throw new Error("Not a valid PNG file");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let pos = 8; // Skip PNG header
|
|
208
|
+
let extractedText = null;
|
|
209
|
+
|
|
210
|
+
while (pos < bytes.length - 12) {
|
|
211
|
+
const length = (
|
|
212
|
+
(bytes[pos] << 24) |
|
|
213
|
+
(bytes[pos + 1] << 16) |
|
|
214
|
+
(bytes[pos + 2] << 8) |
|
|
215
|
+
(bytes[pos + 3])
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const type = String.fromCharCode(
|
|
219
|
+
bytes[pos + 4],
|
|
220
|
+
bytes[pos + 5],
|
|
221
|
+
bytes[pos + 6],
|
|
222
|
+
bytes[pos + 7]
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (type === 'tEXt') {
|
|
226
|
+
const chunkData = bytes.slice(pos + 8, pos + 8 + length);
|
|
227
|
+
const text = new TextDecoder('utf-8').decode(chunkData);
|
|
228
|
+
|
|
229
|
+
const separatorIndex = text.indexOf('\0');
|
|
230
|
+
if (separatorIndex !== -1) {
|
|
231
|
+
const keyword = text.substring(0, separatorIndex);
|
|
232
|
+
const value = text.substring(separatorIndex + 1);
|
|
233
|
+
|
|
234
|
+
if (keyword === 'noskid-key') {
|
|
235
|
+
extractedText = value;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
pos += 8 + length + 4;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (extractedText) {
|
|
245
|
+
this.nskdLbrLog("Certificate data extracted successfully from PNG", 'success');
|
|
246
|
+
return extractedText;
|
|
247
|
+
} else {
|
|
248
|
+
this.nskdLbrLog("No 'noskid-key' text chunk found in PNG", 'error');
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
this.nskdLbrLog(`Error extracting text from PNG: ${error.message}`, 'error');
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Extract verification key from certificate text
|
|
259
|
+
* @private
|
|
260
|
+
*/
|
|
261
|
+
extractVerificationKey(text) {
|
|
262
|
+
try {
|
|
263
|
+
const keyPattern = /-*BEGIN NOSKID KEY-*\s*([a-f0-9]{64})/i;
|
|
264
|
+
const match = text.match(keyPattern);
|
|
265
|
+
return match ? match[1].toLowerCase() : null;
|
|
266
|
+
} catch (error) {
|
|
267
|
+
this.nskdLbrLog(`Error extracting verification key: ${error.message}`, 'error');
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Extract local certificate data
|
|
274
|
+
* @private
|
|
275
|
+
*/
|
|
276
|
+
extractLocalData(text) {
|
|
277
|
+
try {
|
|
278
|
+
const keyPattern = /-----BEGIN NOSKID KEY-----\s*([a-f0-9]+)\s*([A-Za-z0-9+/=]+)\s*([A-Za-z0-9+/=]+)\s*-----END NOSKID KEY-----/;
|
|
279
|
+
const match = text.match(keyPattern);
|
|
280
|
+
|
|
281
|
+
if (!match) return null;
|
|
282
|
+
|
|
283
|
+
const certInfoEncoded = match[2];
|
|
284
|
+
const certInfoDecoded = atob(certInfoEncoded.replace(/=/g, ''));
|
|
285
|
+
const usernameMatch = certInfoDecoded.match(/CERT-\d+-(.+)/);
|
|
286
|
+
const username = usernameMatch ? usernameMatch[1] : null;
|
|
287
|
+
|
|
288
|
+
const dateInfoEncoded = match[3];
|
|
289
|
+
const dateInfoDecoded = atob(dateInfoEncoded.replace(/=/g, ''));
|
|
290
|
+
const dateMatch = dateInfoDecoded.match(/CREATED-(.+)/);
|
|
291
|
+
const creationDate = dateMatch ? dateMatch[1] : null;
|
|
292
|
+
|
|
293
|
+
return { username, creationDate };
|
|
294
|
+
} catch (error) {
|
|
295
|
+
this.nskdLbrLog(`Error extracting local data: ${error.message}`, 'error');
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Verify certificate with API
|
|
302
|
+
* @private
|
|
303
|
+
*/
|
|
304
|
+
async verifyWithAPI() {
|
|
305
|
+
try {
|
|
306
|
+
this.nskdLbrLog('Verifying certificate with server...', 'info');
|
|
307
|
+
|
|
308
|
+
const controller = new AbortController();
|
|
309
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
310
|
+
|
|
311
|
+
const response = await fetch(
|
|
312
|
+
`${this.apiUrl}?key=${encodeURIComponent(this.verificationKey)}`,
|
|
313
|
+
{
|
|
314
|
+
signal: controller.signal,
|
|
315
|
+
headers: {
|
|
316
|
+
'User-Agent': 'NskdLbr/1.0.0'
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
clearTimeout(timeoutId);
|
|
322
|
+
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const apiData = await response.json();
|
|
328
|
+
|
|
329
|
+
if (!apiData.success) {
|
|
330
|
+
this.isValid = false;
|
|
331
|
+
this.nskdLbrLog(`Certificate verification failed: ${apiData.message}`, 'error');
|
|
332
|
+
return {
|
|
333
|
+
valid: false,
|
|
334
|
+
message: apiData.message,
|
|
335
|
+
cached: apiData.cached || false
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Compare local data with API data if available and strictCheck is enabled
|
|
340
|
+
if (this.localData && this.strictCheck) {
|
|
341
|
+
const validationResult = this.compareData(this.localData, apiData.data);
|
|
342
|
+
if (!validationResult.valid) {
|
|
343
|
+
this.isValid = false;
|
|
344
|
+
this.nskdLbrLog('Certificate data mismatch!', 'error');
|
|
345
|
+
this.nskdLbrLog(`Mismatch reason: ${validationResult.reason}`, 'error');
|
|
346
|
+
this.nskdLbrLog('Note: Strict checking is enabled. Set strictCheck to false to skip local data validation.', 'warning');
|
|
347
|
+
return {
|
|
348
|
+
valid: false,
|
|
349
|
+
message: `Data mismatch: ${validationResult.reason}`,
|
|
350
|
+
cached: apiData.cached || false,
|
|
351
|
+
strictCheck: true
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
this.nskdLbrLog('Local data validation passed', 'success');
|
|
355
|
+
} else if (this.localData && !this.strictCheck) {
|
|
356
|
+
this.nskdLbrLog('Strict checking disabled - skipping local data validation', 'warning');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Certificate is valid
|
|
360
|
+
this.isValid = true;
|
|
361
|
+
this.certificateData = apiData.data;
|
|
362
|
+
this.nskdLbrLog('Certificate is VALID!', 'success');
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
valid: true,
|
|
366
|
+
message: 'Certificate verified successfully',
|
|
367
|
+
data: apiData.data,
|
|
368
|
+
cached: apiData.cached || false,
|
|
369
|
+
strictCheck: this.strictCheck
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (error.name === 'AbortError') {
|
|
374
|
+
throw new Error('Request timeout - server took too long to respond');
|
|
375
|
+
}
|
|
376
|
+
throw new Error(`API verification failed: ${error.message}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Compare local and API data
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
compareData(localData, apiData) {
|
|
385
|
+
if (!localData || !apiData) {
|
|
386
|
+
return { valid: false, reason: 'Missing data for comparison' };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (localData.username !== apiData.username) {
|
|
390
|
+
return {
|
|
391
|
+
valid: false,
|
|
392
|
+
reason: `Username mismatch: Local=${localData.username}, API=${apiData.username}`
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const localDateMinutes = localData.creationDate.substring(0, 16);
|
|
397
|
+
const apiDateMinutes = apiData.creationDate.substring(0, 16);
|
|
398
|
+
|
|
399
|
+
if (localDateMinutes !== apiDateMinutes) {
|
|
400
|
+
return {
|
|
401
|
+
valid: false,
|
|
402
|
+
reason: `Creation date mismatch: Local=${localDateMinutes}, API=${apiDateMinutes}`
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return { valid: true };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
411
|
+
module.exports = NskdLbr;
|
|
412
|
+
} else {
|
|
413
|
+
window.NskdLbr = NskdLbr;
|
|
414
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
class NskdLbr{constructor(t={}){this.apiUrl=t.apiUrl||"https://check.noskid.today/",this.debug=t.debug||!1,this.timeout=t.timeout||1e4,this.strictCheck=t.strictCheck!==void 0?t.strictCheck:!0,this.onLog=t.onLog||null,this.certificateData=null,this.verificationKey=null,this.localData=null,this.isValid=!1}nskdLbrLog(t,e="info"){if(this.debug){const i=`[${new Date().toLocaleTimeString()}] NoSkid:`;switch(e){case"error":console.error(i,t);break;case"warning":console.warn(i,t);break;case"success":console.nskdLbrLog(`%c${i} ${t}`,"color: green");break;default:console.nskdLbrLog(i,t)}}this.onLog&&typeof this.onLog=="function"&&this.onLog(t,e)}async loadFromFile(t){try{if(this.nskdLbrLog("Starting certificate verification process...","info"),!t)throw new Error("No file provided");if(!t.name.toLowerCase().endsWith(".png"))throw new Error("File must be a PNG image");this.nskdLbrLog(`Processing certificate file: ${t.name}`,"info");const e=await this.readFileAsArrayBuffer(t),r=await this.extractTextFromPng(e);if(!r)throw new Error("Could not extract verification data from file");if(this.verificationKey=this.extractVerificationKey(r),!this.verificationKey)throw new Error("No valid verification key found in certificate");return this.nskdLbrLog("Successfully extracted verification key","success"),this.localData=this.extractLocalData(r),this.localData&&(this.nskdLbrLog("Local certificate data extracted:","info"),this.nskdLbrLog(`Username: ${this.localData.username}`,"info"),this.nskdLbrLog(`Creation Date: ${this.localData.creationDate}`,"info")),await this.verifyWithAPI()}catch(e){throw this.nskdLbrLog(`Error loading certificate: ${e.message}`,"error"),e}}async verifyWithKey(t){try{if(!t||typeof t!="string")throw new Error("Invalid verification key provided");if(!/^[a-f0-9]{64}$/i.test(t))throw new Error("Verification key must be a 64-character hexadecimal string");return this.verificationKey=t.toLowerCase(),this.nskdLbrLog(`Verifying certificate with key: ${this.verificationKey.substring(0,16)}...`,"info"),await this.verifyWithAPI()}catch(e){throw this.nskdLbrLog(`Error verifying certificate: ${e.message}`,"error"),e}}getCertificateData(){return this.certificateData}isValidCertificate(){return this.isValid}getFormattedDetails(){if(!this.certificateData)return"No certificate data available";const t=this.certificateData;return`
|
|
2
|
+
Certificate Details:
|
|
3
|
+
- Certificate #: ${t.certificate_number}
|
|
4
|
+
- Username: ${t.username}
|
|
5
|
+
- Percentage: ${t.percentage}%
|
|
6
|
+
- Creation Date: ${t.creationDate}
|
|
7
|
+
- Country: ${t.country} (${t.countryCode})
|
|
8
|
+
`.trim()}reset(){this.certificateData=null,this.verificationKey=null,this.localData=null,this.isValid=!1,this.nskdLbrLog("Certificate data reset","info")}readFileAsArrayBuffer(t){return new Promise((e,r)=>{const i=new FileReader;i.onload=a=>e(a.target.result),i.onerror=()=>r(new Error("Error reading file")),i.readAsArrayBuffer(t)})}async extractTextFromPng(t){try{const e=new Uint8Array(t);if(!(e[0]===137&&e[1]===80&&e[2]===78&&e[3]===71))throw new Error("Not a valid PNG file");let r=8,i=null;for(;r<e.length-12;){const a=e[r]<<24|e[r+1]<<16|e[r+2]<<8|e[r+3];if(String.fromCharCode(e[r+4],e[r+5],e[r+6],e[r+7])==="tEXt"){const l=e.slice(r+8,r+8+a),s=new TextDecoder("utf-8").decode(l),o=s.indexOf("\0");if(o!==-1){const n=s.substring(0,o),d=s.substring(o+1);if(n==="noskid-key"){i=d;break}}}r+=8+a+4}return i?(this.nskdLbrLog("Certificate data extracted successfully from PNG","success"),i):(this.nskdLbrLog("No 'noskid-key' text chunk found in PNG","error"),null)}catch(e){return this.nskdLbrLog(`Error extracting text from PNG: ${e.message}`,"error"),null}}extractVerificationKey(t){try{const e=/-*BEGIN NOSKID KEY-*\s*([a-f0-9]{64})/i,r=t.match(e);return r?r[1].toLowerCase():null}catch(e){return this.nskdLbrLog(`Error extracting verification key: ${e.message}`,"error"),null}}extractLocalData(t){try{const e=/-----BEGIN NOSKID KEY-----\s*([a-f0-9]+)\s*([A-Za-z0-9+/=]+)\s*([A-Za-z0-9+/=]+)\s*-----END NOSKID KEY-----/,r=t.match(e);if(!r)return null;const i=r[2],c=atob(i.replace(/=/g,"")).match(/CERT-\d+-(.+)/),l=c?c[1]:null,s=r[3],n=atob(s.replace(/=/g,"")).match(/CREATED-(.+)/),d=n?n[1]:null;return{username:l,creationDate:d}}catch(e){return this.nskdLbrLog(`Error extracting local data: ${e.message}`,"error"),null}}async verifyWithAPI(){try{this.nskdLbrLog("Verifying certificate with server...","info");const t=new AbortController,e=setTimeout(()=>t.abort(),this.timeout),r=await fetch(`${this.apiUrl}?key=${encodeURIComponent(this.verificationKey)}`,{signal:t.signal,headers:{"User-Agent":"NskdLbr/1.0.0"}});if(clearTimeout(e),!r.ok)throw new Error(`HTTP ${r.status}: ${r.statusText}`);const i=await r.json();if(!i.success)return this.isValid=!1,this.nskdLbrLog(`Certificate verification failed: ${i.message}`,"error"),{valid:!1,message:i.message,cached:i.cached||!1};if(this.localData&&this.strictCheck){const a=this.compareData(this.localData,i.data);if(!a.valid)return this.isValid=!1,this.nskdLbrLog("Certificate data mismatch!","error"),this.nskdLbrLog(`Mismatch reason: ${a.reason}`,"error"),this.nskdLbrLog("Note: Strict checking is enabled. Set strictCheck to false to skip local data validation.","warning"),{valid:!1,message:`Data mismatch: ${a.reason}`,cached:i.cached||!1,strictCheck:!0};this.nskdLbrLog("Local data validation passed","success")}else this.localData&&!this.strictCheck&&this.nskdLbrLog("Strict checking disabled - skipping local data validation","warning");return this.isValid=!0,this.certificateData=i.data,this.nskdLbrLog("Certificate is VALID!","success"),{valid:!0,message:"Certificate verified successfully",data:i.data,cached:i.cached||!1,strictCheck:this.strictCheck}}catch(t){throw t.name==="AbortError"?new Error("Request timeout - server took too long to respond"):new Error(`API verification failed: ${t.message}`)}}compareData(t,e){if(!t||!e)return{valid:!1,reason:"Missing data for comparison"};if(t.username!==e.username)return{valid:!1,reason:`Username mismatch: Local=${t.username}, API=${e.username}`};const r=t.creationDate.substring(0,16),i=e.creationDate.substring(0,16);return r!==i?{valid:!1,reason:`Creation date mismatch: Local=${r}, API=${i}`}:{valid:!0}}}typeof module<"u"&&module.exports?module.exports=NskdLbr:window.NskdLbr=NskdLbr;
|