Sanitized public release v1.0.3

Source-Tag: v1.0.3
Manifest-SHA256: b7b09bf48f9e092ed8ad82f99c0319d6c3837d0a1549bd0cc4a374276c0f3896
This commit is contained in:
NIA Sanitized Release Publisher
2026-07-20 22:35:36 +00:00
commit 7141326ea0
41 changed files with 6397 additions and 0 deletions
+326
View File
@@ -0,0 +1,326 @@
(function(root, factory) {
'use strict';
var api = factory();
if (typeof module === 'object' && module.exports) module.exports = api;
else root.NeoDropCrypto = api;
}(typeof self !== 'undefined' ? self : this, function() {
'use strict';
var CHUNK_SIZE = 4 * 1024 * 1024;
var SMALL_MAX = 50 * 1024 * 1024;
var LARGE_MAX = 1024 * 1024 * 1024;
var encoder = new TextEncoder();
var SHA256_K = new Uint32Array([
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
]);
function rotate(value, bits) {
return (value >>> bits) | (value << (32 - bits));
}
function Sha256() {
this.state = new Uint32Array([
0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19
]);
this.buffer = new Uint8Array(64);
this.bufferLength = 0;
this.bytes = 0;
}
Sha256.prototype.transform = function(bytes, offset) {
var words = new Uint32Array(64);
var view = new DataView(bytes.buffer, bytes.byteOffset + offset, 64);
var i;
for (i = 0; i < 16; i++) words[i] = view.getUint32(i * 4, false);
for (i = 16; i < 64; i++) {
var x = words[i - 15];
var y = words[i - 2];
var s0 = rotate(x, 7) ^ rotate(x, 18) ^ (x >>> 3);
var s1 = rotate(y, 17) ^ rotate(y, 19) ^ (y >>> 10);
words[i] = (words[i - 16] + s0 + words[i - 7] + s1) >>> 0;
}
var a = this.state[0], b = this.state[1], c = this.state[2], d = this.state[3];
var e = this.state[4], f = this.state[5], g = this.state[6], h = this.state[7];
for (i = 0; i < 64; i++) {
var sum1 = rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25);
var choice = (e & f) ^ (~e & g);
var temporary1 = (h + sum1 + choice + SHA256_K[i] + words[i]) >>> 0;
var sum0 = rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22);
var majority = (a & b) ^ (a & c) ^ (b & c);
var temporary2 = (sum0 + majority) >>> 0;
h = g; g = f; f = e; e = (d + temporary1) >>> 0;
d = c; c = b; b = a; a = (temporary1 + temporary2) >>> 0;
}
this.state[0] = (this.state[0] + a) >>> 0;
this.state[1] = (this.state[1] + b) >>> 0;
this.state[2] = (this.state[2] + c) >>> 0;
this.state[3] = (this.state[3] + d) >>> 0;
this.state[4] = (this.state[4] + e) >>> 0;
this.state[5] = (this.state[5] + f) >>> 0;
this.state[6] = (this.state[6] + g) >>> 0;
this.state[7] = (this.state[7] + h) >>> 0;
};
Sha256.prototype.update = function(bytes) {
this.bytes += bytes.length;
var offset = 0;
if (this.bufferLength) {
var copied = Math.min(64 - this.bufferLength, bytes.length);
this.buffer.set(bytes.subarray(0, copied), this.bufferLength);
this.bufferLength += copied;
offset = copied;
if (this.bufferLength === 64) {
this.transform(this.buffer, 0);
this.bufferLength = 0;
}
}
while (offset + 64 <= bytes.length) {
this.transform(bytes, offset);
offset += 64;
}
if (offset < bytes.length) {
this.buffer.set(bytes.subarray(offset), 0);
this.bufferLength = bytes.length - offset;
}
};
Sha256.prototype.hex = function() {
var high = Math.floor(this.bytes / 0x20000000);
var low = (this.bytes * 8) >>> 0;
this.buffer[this.bufferLength++] = 0x80;
if (this.bufferLength > 56) {
this.buffer.fill(0, this.bufferLength);
this.transform(this.buffer, 0);
this.bufferLength = 0;
}
this.buffer.fill(0, this.bufferLength, 56);
var view = new DataView(this.buffer.buffer);
view.setUint32(56, high, false);
view.setUint32(60, low, false);
this.transform(this.buffer, 0);
var result = '';
for (var i = 0; i < this.state.length; i++) {
result += this.state[i].toString(16).padStart(8, '0');
}
return result;
};
function b64url(bytes) {
var binary = '';
for (var i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
function derive(secret, info, length) {
return crypto.subtle.importKey('raw', secret, 'HKDF', false, ['deriveBits']).then(function(key) {
return crypto.subtle.deriveBits({
name: 'HKDF', hash: 'SHA-256',
salt: encoder.encode('{{NEODROP_CRYPTO_CONTEXT}}'),
info: encoder.encode(info)
}, key, length * 8);
}).then(function(bits) { return new Uint8Array(bits); });
}
function importAes(raw) {
return crypto.subtle.importKey('raw', raw, {name: 'AES-GCM'}, false, ['encrypt']);
}
function setU32(bytes, offset, value) {
new DataView(bytes.buffer).setUint32(offset, value, false);
}
function setU64(bytes, offset, value) {
new DataView(bytes.buffer).setBigUint64(offset, BigInt(value), false);
}
function makeAad(type, profile, objectId, size, count, index, length) {
var bytes = new Uint8Array(60);
bytes.set(encoder.encode('NEODRP01'));
bytes[8] = type;
bytes[9] = profile;
bytes.set(objectId, 12);
setU64(bytes, 36, size);
setU32(bytes, 44, CHUNK_SIZE);
setU32(bytes, 48, count);
setU32(bytes, 52, index);
setU32(bytes, 56, length);
return bytes;
}
function makeIv(type, index) {
var bytes = new Uint8Array(12);
setU32(bytes, 0, type);
setU64(bytes, 4, index);
return bytes;
}
function encrypt(key, plaintext, nonce, additionalData) {
return crypto.subtle.encrypt({
name: 'AES-GCM', iv: nonce, additionalData: additionalData, tagLength: 128
}, key, plaintext);
}
function typeFor(type, name) {
var value = String(type || '').toLowerCase();
if (value && value !== 'application/octet-stream') return value;
var match = String(name || '').toLowerCase().match(/\.([a-z0-9]+)$/);
var types = {
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',
webp: 'image/webp', avif: 'image/avif', flac: 'audio/flac', m4a: 'audio/mp4',
mp3: 'audio/mpeg', oga: 'audio/ogg', ogg: 'audio/ogg', opus: 'audio/opus',
wav: 'audio/wav', wave: 'audio/wav', m4v: 'video/mp4', mkv: 'video/x-matroska',
mov: 'video/quicktime', mp4: 'video/mp4', ogv: 'video/ogg', webm: 'video/webm'
};
return match && types[match[1]] ? types[match[1]] : 'application/octet-stream';
}
function kindFor(type) {
if (/^image\/(png|jpeg|gif|webp|avif)$/i.test(type)) return 'image';
if (/^audio\//i.test(type)) return 'audio';
if (/^video\//i.test(type)) return 'video';
if (/^text\//i.test(type) || /^(application\/(json|javascript|x-.*script))$/i.test(type)) return 'text';
return 'binary';
}
function safeName(name) {
var value = String(name || 'drop.bin').split(/[\\/]/).pop()
.replace(/[\u0000-\u001f\u007f]/g, '').replace(/^\.+|\.+$/g, '').slice(0, 240);
return value || 'drop.bin';
}
function request(base, path, options, jsonResponse) {
return fetch(base + path, options).then(function(response) {
if (!response.ok) throw new Error('NeoDrop request failed');
return jsonResponse ? response.json() : response;
}).catch(function(error) {
if (error && error.name === 'AbortError') throw error;
throw new Error('NeoDrop request failed');
});
}
async function upload(file, options) {
options = options || {};
if (!file || typeof file.size !== 'number') throw new Error('Select a file.');
var profile = Number(options.profile);
if (profile !== 0 && profile !== 1) throw new Error('Invalid retention profile.');
var maximum = profile === 0 ? SMALL_MAX : LARGE_MAX;
if (file.size > maximum) throw new Error('File exceeds the selected profile limit.');
var base = String(options.base || '');
var progress = typeof options.onProgress === 'function' ? options.onProgress : function() {};
var signal = options.signal;
var secret = crypto.getRandomValues(new Uint8Array(32));
var objectId;
var objectIdText;
var uploadSession;
var chunks = file.size ? Math.ceil(file.size / CHUNK_SIZE) : 0;
var totalSteps = chunks + 3;
var completed = 0;
try {
var keys = await Promise.all([
derive(secret, 'object-id', 24),
derive(secret, 'content-key', 32).then(importAes),
derive(secret, 'metadata-key', 32).then(importAes)
]);
objectId = keys[0];
objectIdText = b64url(objectId);
uploadSession = await request(base, '/api/v1/uploads', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
v: 1, id: objectIdText, profile: profile, size: file.size,
chunkSize: CHUNK_SIZE, chunks: chunks
}),
signal: signal
}, true);
if (!uploadSession || !/^[A-Za-z0-9_-]{22}$/.test(uploadSession.uploadId || '') ||
!/^[A-Za-z0-9_-]{43}$/.test(uploadSession.uploadToken || '')) {
throw new Error('NeoDrop request failed');
}
progress(++completed / totalSteps);
var authorization = 'Bearer ' + uploadSession.uploadToken;
var hasher = new Sha256();
for (var index = 0; index < chunks; index++) {
var plaintext = new Uint8Array(await file.slice(
index * CHUNK_SIZE, Math.min(file.size, (index + 1) * CHUNK_SIZE)
).arrayBuffer());
hasher.update(plaintext);
var cipher = await encrypt(
keys[1], plaintext, makeIv(1, index),
makeAad(1, profile, objectId, file.size, chunks, index, plaintext.byteLength)
);
await request(base, '/api/v1/uploads/' + uploadSession.uploadId + '/chunks/' + index, {
method: 'PUT',
headers: {'Content-Type': 'application/octet-stream', 'Authorization': authorization},
body: cipher,
signal: signal
}, false);
progress(++completed / totalSteps);
}
var metadataName = safeName(file.name);
var metadataValue = {
v: 1,
name: metadataName,
type: typeFor(file.type, metadataName).slice(0, 255),
size: file.size,
lastModified: Number.isSafeInteger(file.lastModified) ? file.lastModified : null,
sha256: hasher.hex()
};
var metadata = encoder.encode(JSON.stringify(metadataValue));
if (metadata.length > 4096) throw new Error('File metadata is too large.');
var encryptedMetadata = await encrypt(
keys[2], metadata, makeIv(2, 0),
makeAad(0, profile, objectId, file.size, chunks, 0xffffffff, metadata.length)
);
await request(base, '/api/v1/uploads/' + uploadSession.uploadId + '/metadata', {
method: 'PUT',
headers: {'Content-Type': 'application/octet-stream', 'Authorization': authorization},
body: encryptedMetadata,
signal: signal
}, false);
progress(++completed / totalSteps);
var committed = await request(base, '/api/v1/uploads/' + uploadSession.uploadId + '/commit', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'Authorization': authorization},
body: '{}',
signal: signal
}, true);
if (!committed || !Number.isSafeInteger(committed.expiresAt)) {
throw new Error('NeoDrop request failed');
}
progress(1);
return {
id: objectIdText,
readUrl: base + '/d/' + objectIdText + '?k=' + kindFor(metadataValue.type) + '#d1.' + b64url(secret),
deleteCapability: objectIdText + '.' + uploadSession.uploadToken,
name: metadataValue.name,
type: metadataValue.type,
size: file.size,
sha256: metadataValue.sha256,
profile: profile,
expiresAt: committed.expiresAt
};
} finally {
secret.fill(0);
}
}
return {
CHUNK_SIZE: CHUNK_SIZE,
SMALL_MAX: SMALL_MAX,
LARGE_MAX: LARGE_MAX,
upload: upload
};
}));