quickjs 0.13.0 → 0.14.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.
@@ -0,0 +1 @@
1
+ (function(){let e={ftp:21,file:null,http:80,https:443,ws:80,wss:443};function t(t){return Object.prototype.hasOwnProperty.call(e,t)}function n(t){return e[t]??null}let r=/[\x00-\x1f\x7f-\uffff]/,i=/[\x00-\x1f \x22\x3c\x3e\x60\x7f-\uffff]/,a=/[\x00-\x1f \x22\x23\x3c\x3e\x7f-\uffff]/,o=/[\x00-\x1f \x22\x23\x27\x3c\x3e\x7f-\uffff]/,s=/[\x00-\x1f \x22\x23\x3c\x3e\x3f\x60\x7b-\x7d\x7f-\uffff]/,c=/[\x00-\x1f \x22\x23\x2f\x3a\x3b\x3d\x40\x5b-\x5e\x60\x7b-\x7d\x7f-\uffff]/;function l(e,t){let n=``;for(let r=0;r<e.length;r++){let i=e[r],a=e.charCodeAt(r);if(a>=55296&&a<=56319&&r+1<e.length){let t=e.charCodeAt(r+1);if(t>=56320&&t<=57343){let e=u((a-55296)*1024+(t-56320)+65536);for(let t of e)n+=`%`+t.toString(16).toUpperCase().padStart(2,`0`);r++;continue}}if(t.test(i)){let e=u(a);for(let t of e)n+=`%`+t.toString(16).toUpperCase().padStart(2,`0`)}else n+=i}return n}function u(e){return e<=127?[e]:e<=2047?[192|e>>6,128|e&63]:e<=65535?[224|e>>12,128|e>>6&63,128|e&63]:[240|e>>18,128|e>>12&63,128|e>>6&63,128|e&63]}function d(e){return e.replace(/%([0-9A-Fa-f]{2})/g,(e,t)=>String.fromCharCode(parseInt(t,16)))}function f(e){return e>=`0`&&e<=`9`}function p(e){return e>=`0`&&e<=`9`||e>=`a`&&e<=`f`||e>=`A`&&e<=`F`}function m(e){return e>=`a`&&e<=`z`||e>=`A`&&e<=`Z`}function h(e,t){if(e.length<2||!m(e[0]))return!1;let n=e[1];return t?n===`:`:n===`:`||n===`|`}function g(e){return e.length<2||!m(e[0])||e[1]!==`:`&&e[1]!==`|`?!1:e.length===2?!0:e[2]===`/`||e[2]===`\\`||e[2]===`?`||e[2]===`#`}function _(e,t){t===`file`&&e.length===1&&h(e[0],!0)||e.length>0&&e.pop()}function v(){return{scheme:``,username:``,password:``,host:null,port:null,path:[],query:null,fragment:null,cannotBeABaseURL:!1}}function y(e,t){let n=e.scheme+`:`;if(e.host===null?e.scheme===`file`&&(n+=`//`):(n+=`//`,(e.username!==``||e.password!==``)&&(n+=e.username,e.password!==``&&(n+=`:`+e.password),n+=`@`),n+=b(e.host),e.port!==null&&(n+=`:`+e.port)),e.cannotBeABaseURL)n+=e.path[0]??``;else for(let t of e.path)n+=`/`+t;return e.query!==null&&(n+=`?`+e.query),!t&&e.fragment!==null&&(n+=`#`+e.fragment),n}function b(e){return typeof e==`number`?x(e):Array.isArray(e)?`[`+S(e)+`]`:e}function x(e){let t=e,n=[];for(let e=0;e<4;e++)n.unshift(t%256),t=Math.floor(t/256);return n.join(`.`)}function S(e){let t=``,n=C(e),r=!1;for(let i=0;i<8;i++){if(r)if(e[i]!==0)r=!1;else continue;if(i===n){t+=i===0?`::`:`:`,r=!0;continue}t+=e[i].toString(16),i!==7&&(t+=`:`)}return t}function C(e){let t=-1,n=1,r=-1,i=0;for(let a=0;a<8;a++)e[a]===0?(r===-1&&(r=a,i=0),i++,i>n&&(n=i,t=r)):r=-1;return t}function w(e){return t(e.scheme)&&e.scheme!==`file`?e.scheme+`://`+b(e.host)+(e.port===null?``:`:`+e.port):`null`}function T(e){let t=e.split(`.`);if(t[t.length-1]===``&&t.pop(),t.length>4)return null;let n=[];for(let e of t){if(e===``)return null;let t;if(t=/^0[xX]/.test(e)?parseInt(e,16):e.startsWith(`0`)&&e.length>1?parseInt(e,8):parseInt(e,10),isNaN(t)||t<0)return null;n.push(t)}if(n.some((e,t)=>t<n.length-1&&e>255))return null;let r=n[n.length-1];if(r>=256**(5-n.length))return null;let i=r;for(let e=0;e<n.length-1;e++){if(n[e]>255)return null;i+=n[e]*256**(3-e)}return i}function E(e){let t=[0,0,0,0,0,0,0,0],n=0,r=null,i=0;for(e[i]===`:`&&e[i+1]===`:`&&(i+=2,n++,r=n);i<e.length;){if(n===8)return null;if(e[i]===`:`){if(r!==null)return null;i++,n++,r=n;continue}let a=0,o=0;for(;o<4&&i<e.length&&p(e[i]);)a=a*16+parseInt(e[i],16),i++,o++;if(i<e.length&&e[i]===`.`){if(o===0||(i-=o,n>6))return null;let r=0;for(;i<e.length;){let a=null;if(r>0)if(e[i]===`.`&&r<4)i++;else return null;if(!f(e[i]))return null;for(;i<e.length&&f(e[i]);){let t=parseInt(e[i],10);if(a=a===null?t:a*10+t,a>255)return null;i++}t[n]=t[n]*256+a,r++,(r===2||r===4)&&n++}if(r!==4)return null;break}else if(i<e.length&&e[i]===`:`){if(i++,i>=e.length)return null}else if(i<e.length)return null;t[n]=a,n++}if(r!==null){let e=n-r;for(n=7;n!==0&&e>0;)[t[n],t[r+e-1]]=[t[r+e-1],t[n]],n--,e--}else if(r===null&&n!==8)return null;return t}function D(e){for(let t of e)if(` #%/:?@[\\]`.includes(t)||t.charCodeAt(0)>126)return null;return l(e,r)}function O(e,t){if(e.startsWith(`[`))return e.endsWith(`]`)?E(e.slice(1,-1)):null;if(t)return D(e);let n=d(e).toLowerCase();if(!n)return null;let r=T(n);return r===null?n.includes(`..`)?null:n:r}function k(e,u,d,p){d||=v(),e=e.trim().replace(/[\t\n\r]/g,``);let y=p||`scheme start`,b=``,x=!1,S=!1,C=!1,w=0;for(;w<=e.length;){let v=e[w];switch(y){case`scheme start`:if(v!==void 0&&m(v))b+=v.toLowerCase(),y=`scheme`;else if(p)return null;else{y=`no scheme`;continue}break;case`scheme`:if(v!==void 0&&(m(v)||f(v)||`+-.`.includes(v)))b+=v.toLowerCase();else if(v===`:`){if(p&&(t(d.scheme)!==t(b)||(b===`http`||b===`https`)&&d.scheme===`file`||d.scheme===`file`&&(d.host===``||d.host===null)))return d;if(d.scheme=b,p)return d.port===n(d.scheme)&&(d.port=null),d;b=``,d.scheme===`file`?y=`file`:t(d.scheme)&&u&&u.scheme===d.scheme?y=`special relative or authority`:t(d.scheme)?y=`special authority slashes`:e[w+1]===`/`?(y=`path or authority`,w++):(d.cannotBeABaseURL=!0,d.path.push(``),y=`cannot-be-a-base-URL path`)}else if(!p)b=``,y=`no scheme`,w=-1;else return null;break;case`no scheme`:if(!u||u.cannotBeABaseURL&&v!==`#`)return null;if(u.cannotBeABaseURL&&v===`#`){d.scheme=u.scheme,d.path=u.path.slice(),d.query=u.query,d.fragment=``,d.cannotBeABaseURL=!0,y=`fragment`;break}y=u.scheme===`file`?`file`:`relative`;continue;case`special relative or authority`:if(v===`/`&&e[w+1]===`/`)y=`special authority ignore slashes`,w++;else{y=`relative`;continue}break;case`path or authority`:if(v===`/`)y=`authority`;else{y=`path`;continue}break;case`relative`:if(d.scheme=u.scheme,v===void 0)d.username=u.username,d.password=u.password,d.host=u.host,d.port=u.port,d.path=u.path.slice(),d.query=u.query;else if(v===`/`)y=`relative slash`;else if(v===`?`)d.username=u.username,d.password=u.password,d.host=u.host,d.port=u.port,d.path=u.path.slice(),d.query=``,y=`query`;else if(v===`#`)d.username=u.username,d.password=u.password,d.host=u.host,d.port=u.port,d.path=u.path.slice(),d.query=u.query,d.fragment=``,y=`fragment`;else if(t(d.scheme)&&v===`\\`)y=`relative slash`;else{d.username=u.username,d.password=u.password,d.host=u.host,d.port=u.port,d.path=u.path.slice(),d.path.length>0&&d.path.pop(),y=`path`;continue}break;case`relative slash`:if(t(d.scheme)&&(v===`/`||v===`\\`))y=`special authority ignore slashes`;else if(v===`/`)y=`authority`;else{d.username=u.username,d.password=u.password,d.host=u.host,d.port=u.port,y=`path`;continue}break;case`special authority slashes`:if(v===`/`&&e[w+1]===`/`)y=`special authority ignore slashes`,w++;else{y=`special authority ignore slashes`;continue}break;case`special authority ignore slashes`:if(v!==`/`&&v!==`\\`){y=`authority`;continue}break;case`authority`:if(v===`@`){x&&(b=`%40`+b),x=!0;for(let e=0;e<b.length;e++){let t=b[e];if(t===`:`&&!C){C=!0;continue}let n=l(t,c);C?d.password+=n:d.username+=n}b=``}else if(v===void 0||v===`/`||v===`?`||v===`#`||t(d.scheme)&&v===`\\`){if(x&&b===``)return null;w-=b.length+1,b=``,y=`host`}else b+=v;break;case`host`:case`hostname`:if(p&&d.scheme===`file`){y=`file host`;continue}else if(v===`:`&&!S){if(b===``)return null;let e=O(b,!t(d.scheme));if(e===null)return null;if(d.host=e,b=``,y=`port`,p===`hostname`)return d}else if(v===void 0||v===`/`||v===`?`||v===`#`||t(d.scheme)&&v===`\\`){if(t(d.scheme)&&b===``)return null;if(p&&b===``&&(d.username!==``||d.password!==``||d.port!==null))return d;let e=O(b,!t(d.scheme));if(e===null)return null;if(d.host=e,b=``,y=`path start`,p)return d;continue}else v===`[`&&(S=!0),v===`]`&&(S=!1),b+=v;break;case`port`:if(f(v))b+=v;else if(v===void 0||v===`/`||v===`?`||v===`#`||t(d.scheme)&&v===`\\`||p){if(b!==``){let e=parseInt(b,10);if(e>65535)return null;d.port=e===n(d.scheme)?null:e,b=``}if(p)return d;y=`path start`;continue}else return null;break;case`file`:if(d.scheme=`file`,d.host=``,v===`/`||v===`\\`)y=`file slash`;else if(u&&u.scheme===`file`){if(d.host=u.host,d.path=u.path.slice(),d.query=u.query,v===`?`)d.query=``,y=`query`;else if(v===`#`)d.fragment=``,y=`fragment`;else if(v!==void 0){d.query=null,g(e.slice(w))?d.path=[]:_(d.path,d.scheme),y=`path`;continue}}else{y=`path`;continue}break;case`file slash`:if(v===`/`||v===`\\`)y=`file host`;else{u&&u.scheme===`file`&&(d.host=u.host,!g(e.slice(w))&&u.path.length>0&&h(u.path[0],!0)&&d.path.push(u.path[0])),y=`path`;continue}break;case`file host`:if(v===void 0||v===`/`||v===`\\`||v===`?`||v===`#`){if(!p&&h(b,!1))y=`path`;else if(b===``){if(d.host=``,p)return d;y=`path start`}else{let e=O(b,!1);if(e===null)return null;if(d.host=e===`localhost`?``:e,p)return d;b=``,y=`path start`}continue}else b+=v;break;case`path start`:if(t(d.scheme)){if(y=`path`,v!==`/`&&v!==`\\`)continue}else if(!p&&v===`?`)d.query=``,y=`query`;else if(!p&&v===`#`)d.fragment=``,y=`fragment`;else if(v!==void 0){if(y=`path`,v!==`/`)continue}else p&&d.host===null&&d.path.push(``);break;case`path`:if(v===void 0||v===`/`||t(d.scheme)&&v===`\\`||!p&&(v===`?`||v===`#`)){let e=b.toLowerCase();e===`%2e`||e===`.`?b=`.`:(e===`%2e%2e`||e===`.%2e`||e===`%2e.`||e===`..`)&&(b=`..`),b===`..`?(_(d.path,d.scheme),v!==`/`&&!(t(d.scheme)&&v===`\\`)&&d.path.push(``)):b===`.`?v!==`/`&&!(t(d.scheme)&&v===`\\`)&&d.path.push(``):(d.scheme===`file`&&d.path.length===0&&h(b,!1)&&(b=b[0]+`:`),d.path.push(b)),b=``,v===`?`?(d.query=``,y=`query`):v===`#`&&(d.fragment=``,y=`fragment`)}else b+=l(v,s);break;case`cannot-be-a-base-URL path`:v===`?`?(d.query=``,y=`query`):v===`#`?(d.fragment=``,y=`fragment`):v!==void 0&&(d.path[0]+=l(v,r));break;case`query`:if(v===void 0||!p&&v===`#`){let e=t(d.scheme)?o:a;d.query+=l(b,e),b=``,v===`#`&&(d.fragment=``,y=`fragment`)}else b+=v;break;case`fragment`:v!==void 0&&(d.fragment+=l(v,i));break}w++}return d}var A=class{#e;#t;constructor(e=``){if(this.#e=[],this.#t=null,typeof e==`string`)e.startsWith(`?`)&&(e=e.slice(1)),this.#e=j(e);else if(Array.isArray(e))for(let t of e){if(!Array.isArray(t)||t.length!==2)throw TypeError(`Each pair must be an iterable [name, value] tuple`);this.#e.push([String(t[0]),String(t[1])])}else if(typeof e==`object`&&e)for(let t of Object.keys(e))this.#e.push([t,String(e[t])])}_setURL(e){this.#t=e}_update(){if(this.#t){let e=this.toString();this.#t._urlRecord.query=e===``?null:e}}append(e,t){this.#e.push([String(e),String(t)]),this._update()}delete(e,t){e=String(e),t===void 0?this.#e=this.#e.filter(([t])=>t!==e):(t=String(t),this.#e=this.#e.filter(([n,r])=>!(n===e&&r===t))),this._update()}get(e){e=String(e);let t=this.#e.find(([t])=>t===e);return t?t[1]:null}getAll(e){return e=String(e),this.#e.filter(([t])=>t===e).map(([,e])=>e)}has(e,t){return e=String(e),t===void 0?this.#e.some(([t])=>t===e):(t=String(t),this.#e.some(([n,r])=>n===e&&r===t))}set(e,t){e=String(e),t=String(t);let n=!1;if(this.#e=this.#e.filter(([t])=>t===e?n?!1:(n=!0,!0):!0),!n)this.#e.push([e,t]);else{let n=this.#e.findIndex(([t])=>t===e);this.#e[n][1]=t}this._update()}sort(){this.#e.sort((e,t)=>e[0]<t[0]?-1:+(e[0]>t[0])),this._update()}keys(){return this.#e.map(([e])=>e)[Symbol.iterator]()}values(){return this.#e.map(([,e])=>e)[Symbol.iterator]()}entries(){return this.#e.slice()[Symbol.iterator]()}forEach(e,t){for(let[n,r]of this.#e)e.call(t,r,n,this)}[Symbol.iterator](){return this.entries()}get size(){return this.#e.length}toString(){return this.#e.map(([e,t])=>M(e)+`=`+M(t)).join(`&`)}get[Symbol.toStringTag](){return`URLSearchParams`}};function j(e){return e===``?[]:e.split(`&`).map(e=>{let t=e.indexOf(`=`),n,r;return t===-1?(n=e,r=``):(n=e.slice(0,t),r=e.slice(t+1)),[N(n),N(r)]})}function M(e){return e.replace(/[^*\-._A-Za-z0-9]/g,e=>e===` `?`+`:u(e.charCodeAt(0)).map(e=>`%`+e.toString(16).toUpperCase().padStart(2,`0`)).join(``))}function N(e){return decodeURIComponent(e.replace(/\+/g,` `))}globalThis.URL=class e{#e;#t;constructor(e,t){let n=null;if(t!==void 0&&(n=k(String(t)),n===null))throw TypeError(`Failed to construct 'URL': Invalid base URL`);let r=k(String(e),n);if(r===null)throw TypeError(`Failed to construct 'URL': Invalid URL`);this.#e=r,this.#t=new A(r.query??``),this.#t._setURL(this)}get _urlRecord(){return this.#e}get href(){return y(this.#e,!1)}set href(e){let t=k(String(e));if(t===null)throw TypeError(`Invalid URL`);this.#e=t,this.#t=new A(t.query??``),this.#t._setURL(this)}get origin(){return w(this.#e)}get protocol(){return this.#e.scheme+`:`}set protocol(e){k(String(e)+`:`,null,this.#e,`scheme start`)}get username(){return this.#e.username}set username(e){this.#e.host===null||this.#e.host===``||this.#e.cannotBeABaseURL||this.#e.scheme===`file`||(this.#e.username=l(String(e),c))}get password(){return this.#e.password}set password(e){this.#e.host===null||this.#e.host===``||this.#e.cannotBeABaseURL||this.#e.scheme===`file`||(this.#e.password=l(String(e),c))}get host(){let e=this.#e;return e.host===null?``:e.port===null?b(e.host):b(e.host)+`:`+e.port}set host(e){this.#e.cannotBeABaseURL||k(String(e),null,this.#e,`host`)}get hostname(){return this.#e.host===null?``:b(this.#e.host)}set hostname(e){this.#e.cannotBeABaseURL||k(String(e),null,this.#e,`hostname`)}get port(){return this.#e.port===null?``:String(this.#e.port)}set port(e){this.#e.host===null||this.#e.host===``||this.#e.cannotBeABaseURL||this.#e.scheme===`file`||(e=String(e),e===``?this.#e.port=null:k(e,null,this.#e,`port`))}get pathname(){let e=this.#e;return e.cannotBeABaseURL?e.path[0]??``:e.path.length===0?``:`/`+e.path.join(`/`)}set pathname(e){this.#e.cannotBeABaseURL||(this.#e.path=[],k(String(e),null,this.#e,`path start`))}get search(){return this.#e.query===null||this.#e.query===``?``:`?`+this.#e.query}set search(e){if(e=String(e),e===``){this.#e.query=null,this.#t=new A(``),this.#t._setURL(this);return}e.startsWith(`?`)&&(e=e.slice(1)),this.#e.query=``,k(e,null,this.#e,`query`),this.#t=new A(this.#e.query??``),this.#t._setURL(this)}get searchParams(){return this.#t}get hash(){return this.#e.fragment===null||this.#e.fragment===``?``:`#`+this.#e.fragment}set hash(e){if(e=String(e),e===``){this.#e.fragment=null;return}e.startsWith(`#`)&&(e=e.slice(1)),this.#e.fragment=``,k(e,null,this.#e,`fragment`)}toString(){return this.href}toJSON(){return this.href}get[Symbol.toStringTag](){return`URL`}static canParse(t,n){try{return new e(t,n),!0}catch{return!1}}static parse(t,n){try{return new e(t,n)}catch{return null}}},globalThis.URLSearchParams=A})();
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickjs
4
+ class CryptoKey
5
+ attr_reader :type, :extractable, :algorithm, :usages, :key_data
6
+
7
+ def initialize(type, extractable, algorithm, usages, key_data)
8
+ @type = type
9
+ @extractable = extractable
10
+ @algorithm = algorithm
11
+ @usages = usages
12
+ @key_data = key_data
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,493 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Quickjs
6
+ module SubtleCrypto
7
+ DIGEST_ALGORITHMS = {
8
+ "SHA-1" => "SHA1",
9
+ "SHA-256" => "SHA256",
10
+ "SHA-384" => "SHA384",
11
+ "SHA-512" => "SHA512",
12
+ }.freeze
13
+
14
+ AES_ALGORITHMS = %w[AES-GCM AES-CBC AES-CTR AES-KW].freeze
15
+ AES_VALID_LENGTHS = [128, 192, 256].freeze
16
+
17
+ HMAC_HASH_OUTPUT_LENGTHS = {
18
+ "SHA-1" => 160, "SHA-256" => 256, "SHA-384" => 384, "SHA-512" => 512,
19
+ }.freeze
20
+
21
+ EC_CURVE_MAP = {
22
+ "P-256" => "prime256v1",
23
+ "P-384" => "secp384r1",
24
+ "P-521" => "secp521r1",
25
+ }.freeze
26
+
27
+ EC_COORD_SIZES = {
28
+ "P-256" => 32, "P-384" => 48, "P-521" => 66,
29
+ }.freeze
30
+
31
+ RSA_ALGORITHMS = %w[RSASSA-PKCS1-v1_5 RSA-PSS RSA-OAEP].freeze
32
+ ECDSA_ALGORITHMS = %w[ECDSA].freeze
33
+ ECDH_ALGORITHMS = %w[ECDH X25519].freeze
34
+ KDF_ALGORITHMS = %w[PBKDF2 HKDF].freeze
35
+
36
+ def self.digest(algorithm_name, data)
37
+ ossl_name = DIGEST_ALGORITHMS[algorithm_name] or
38
+ raise ArgumentError, "SubtleCrypto: unsupported digest algorithm '#{algorithm_name}'"
39
+ OpenSSL::Digest.digest(ossl_name, data)
40
+ end
41
+
42
+ def self.generate_key(algo, extractable, usages)
43
+ name = algo[:name] or raise ArgumentError, "SubtleCrypto: algorithm name is required"
44
+ case name
45
+ when *AES_ALGORITHMS
46
+ length = algo[:length] or raise ArgumentError, "SubtleCrypto: AES key requires length"
47
+ raise ArgumentError, "SubtleCrypto: invalid AES key length #{length}" unless AES_VALID_LENGTHS.include?(length)
48
+ key_data = OpenSSL::Random.random_bytes(length / 8)
49
+ Quickjs::CryptoKey.new("secret", extractable, { "name" => name, "length" => length }, usages, key_data)
50
+ when "HMAC"
51
+ hash = algo[:hash] or raise ArgumentError, "SubtleCrypto: HMAC requires hash"
52
+ length = algo[:length] || HMAC_HASH_OUTPUT_LENGTHS[hash] or
53
+ raise ArgumentError, "SubtleCrypto: unsupported HMAC hash '#{hash}'"
54
+ key_data = OpenSSL::Random.random_bytes(length / 8)
55
+ Quickjs::CryptoKey.new("secret", extractable, { "name" => "HMAC", "hash" => hash, "length" => length }, usages, key_data)
56
+ when *RSA_ALGORITHMS
57
+ modulus_length = algo[:modulus_length] or raise ArgumentError, "SubtleCrypto: RSA key requires modulusLength"
58
+ public_exponent_bytes = algo[:public_exponent] or raise ArgumentError, "SubtleCrypto: RSA key requires publicExponent"
59
+ hash = algo[:hash] or raise ArgumentError, "SubtleCrypto: RSA key requires hash"
60
+ exponent = public_exponent_bytes.bytes.reduce(0) { |acc, b| (acc << 8) | b }
61
+ pkey = OpenSSL::PKey::RSA.generate(modulus_length, exponent)
62
+ algo_hash = { "name" => name, "modulusLength" => modulus_length, "publicExponent" => public_exponent_bytes, "hash" => hash }
63
+ {
64
+ private_key: Quickjs::CryptoKey.new("private", extractable, algo_hash, usages.select { |u| %w[sign decrypt unwrapKey].include?(u) }, pkey),
65
+ public_key: Quickjs::CryptoKey.new("public", true, algo_hash, usages.select { |u| %w[verify encrypt wrapKey].include?(u) }, pkey),
66
+ }
67
+ when "ECDSA", "ECDH"
68
+ named_curve = algo[:named_curve] or raise ArgumentError, "SubtleCrypto: EC key requires namedCurve"
69
+ ossl_curve = EC_CURVE_MAP[named_curve] or raise ArgumentError, "SubtleCrypto: unsupported curve '#{named_curve}'"
70
+ pkey = OpenSSL::PKey::EC.generate(ossl_curve)
71
+ algo_hash = { "name" => name, "namedCurve" => named_curve }
72
+ private_usages = name == "ECDSA" ? %w[sign] : %w[deriveKey deriveBits]
73
+ public_usages = name == "ECDSA" ? %w[verify] : []
74
+ {
75
+ private_key: Quickjs::CryptoKey.new("private", extractable, algo_hash, usages.select { |u| private_usages.include?(u) }, pkey),
76
+ public_key: Quickjs::CryptoKey.new("public", true, algo_hash, usages.select { |u| public_usages.include?(u) }, pkey),
77
+ }
78
+ when "Ed25519"
79
+ pkey = OpenSSL::PKey.generate_key("ED25519")
80
+ algo_hash = { "name" => "Ed25519" }
81
+ {
82
+ private_key: Quickjs::CryptoKey.new("private", extractable, algo_hash, usages.select { |u| u == "sign" }, pkey),
83
+ public_key: Quickjs::CryptoKey.new("public", true, algo_hash, usages.select { |u| u == "verify" }, pkey),
84
+ }
85
+ when "X25519"
86
+ pkey = OpenSSL::PKey.generate_key("X25519")
87
+ algo_hash = { "name" => "X25519" }
88
+ {
89
+ private_key: Quickjs::CryptoKey.new("private", extractable, algo_hash, usages.select { |u| %w[deriveKey deriveBits].include?(u) }, pkey),
90
+ public_key: Quickjs::CryptoKey.new("public", true, algo_hash, [], pkey),
91
+ }
92
+ else
93
+ raise ArgumentError, "SubtleCrypto: unsupported generateKey algorithm '#{name}'"
94
+ end
95
+ end
96
+
97
+ def self.import_key(format, key_data, algo, extractable, usages)
98
+ name = algo[:name] or raise ArgumentError, "SubtleCrypto: algorithm name is required"
99
+ case format
100
+ when "raw"
101
+ case name
102
+ when *AES_ALGORITHMS
103
+ length = key_data.bytesize * 8
104
+ raise ArgumentError, "SubtleCrypto: invalid AES key length #{length}" unless AES_VALID_LENGTHS.include?(length)
105
+ Quickjs::CryptoKey.new("secret", extractable, { "name" => name, "length" => length }, usages, key_data)
106
+ when "HMAC"
107
+ hash = algo[:hash] or raise ArgumentError, "SubtleCrypto: HMAC requires hash"
108
+ length = key_data.bytesize * 8
109
+ Quickjs::CryptoKey.new("secret", extractable, { "name" => "HMAC", "hash" => hash, "length" => length }, usages, key_data)
110
+ when "PBKDF2"
111
+ Quickjs::CryptoKey.new("secret", false, { "name" => "PBKDF2" }, usages, key_data)
112
+ when "HKDF"
113
+ Quickjs::CryptoKey.new("secret", false, { "name" => "HKDF" }, usages, key_data)
114
+ when "ECDSA", "ECDH"
115
+ named_curve = algo[:named_curve] or raise ArgumentError, "SubtleCrypto: EC import requires namedCurve"
116
+ pkey = import_raw_ec_public(key_data, named_curve)
117
+ Quickjs::CryptoKey.new("public", extractable, { "name" => name, "namedCurve" => named_curve }, usages, pkey)
118
+ when "Ed25519"
119
+ pkey = import_raw_okp_public(key_data, "ED25519")
120
+ Quickjs::CryptoKey.new("public", extractable, { "name" => "Ed25519" }, usages, pkey)
121
+ when "X25519"
122
+ pkey = import_raw_okp_public(key_data, "X25519")
123
+ Quickjs::CryptoKey.new("public", extractable, { "name" => "X25519" }, usages, pkey)
124
+ else
125
+ raise ArgumentError, "SubtleCrypto: unsupported raw importKey algorithm '#{name}'"
126
+ end
127
+ when "spki"
128
+ pkey = OpenSSL::PKey.read(key_data)
129
+ case name
130
+ when *RSA_ALGORITHMS
131
+ rsa = pkey.is_a?(OpenSSL::PKey::RSA) ? pkey : raise(ArgumentError, "SubtleCrypto: key is not RSA")
132
+ hash = algo[:hash] or raise ArgumentError, "SubtleCrypto: RSA import requires hash"
133
+ pub_exp_bytes = [rsa.e.to_s(16)].pack("H*")
134
+ algo_hash = { "name" => name, "modulusLength" => rsa.n.num_bits, "publicExponent" => pub_exp_bytes, "hash" => hash }
135
+ Quickjs::CryptoKey.new("public", extractable, algo_hash, usages, pkey)
136
+ when "ECDSA", "ECDH"
137
+ named_curve = algo[:named_curve] or raise ArgumentError, "SubtleCrypto: EC import requires namedCurve"
138
+ Quickjs::CryptoKey.new("public", extractable, { "name" => name, "namedCurve" => named_curve }, usages, pkey)
139
+ when "Ed25519"
140
+ Quickjs::CryptoKey.new("public", extractable, { "name" => "Ed25519" }, usages, pkey)
141
+ when "X25519"
142
+ Quickjs::CryptoKey.new("public", extractable, { "name" => "X25519" }, usages, pkey)
143
+ else
144
+ raise ArgumentError, "SubtleCrypto: unsupported spki importKey algorithm '#{name}'"
145
+ end
146
+ when "pkcs8"
147
+ pkey = OpenSSL::PKey.read(key_data)
148
+ case name
149
+ when *RSA_ALGORITHMS
150
+ hash = algo[:hash] or raise ArgumentError, "SubtleCrypto: RSA import requires hash"
151
+ rsa = pkey.is_a?(OpenSSL::PKey::RSA) ? pkey : raise(ArgumentError, "SubtleCrypto: key is not RSA")
152
+ pub_exp_bytes = [rsa.e.to_s(16)].pack("H*")
153
+ algo_hash = { "name" => name, "modulusLength" => rsa.n.num_bits, "publicExponent" => pub_exp_bytes, "hash" => hash }
154
+ Quickjs::CryptoKey.new("private", extractable, algo_hash, usages, pkey)
155
+ when "ECDSA", "ECDH"
156
+ named_curve = algo[:named_curve] or raise ArgumentError, "SubtleCrypto: EC import requires namedCurve"
157
+ Quickjs::CryptoKey.new("private", extractable, { "name" => name, "namedCurve" => named_curve }, usages, pkey)
158
+ when "Ed25519"
159
+ Quickjs::CryptoKey.new("private", extractable, { "name" => "Ed25519" }, usages, pkey)
160
+ when "X25519"
161
+ Quickjs::CryptoKey.new("private", extractable, { "name" => "X25519" }, usages, pkey)
162
+ else
163
+ raise ArgumentError, "SubtleCrypto: unsupported pkcs8 importKey algorithm '#{name}'"
164
+ end
165
+ else
166
+ raise ArgumentError, "SubtleCrypto: unsupported importKey format '#{format}'"
167
+ end
168
+ end
169
+
170
+ def self.export_key(format, key)
171
+ name = key.algorithm["name"]
172
+ case format
173
+ when "raw"
174
+ raise ArgumentError, "SubtleCrypto: key is not extractable" unless key.extractable
175
+ case name
176
+ when *AES_ALGORITHMS, "HMAC"
177
+ key.key_data
178
+ when "ECDSA", "ECDH"
179
+ raise ArgumentError, "SubtleCrypto: raw export only for public EC keys" unless key.type == "public"
180
+ key.key_data.public_to_der.then { |spki_der| ec_spki_to_raw(spki_der) }
181
+ when "Ed25519", "X25519"
182
+ raise ArgumentError, "SubtleCrypto: raw export only for public OKP keys" unless key.type == "public"
183
+ if key.key_data.respond_to?(:raw_public_key)
184
+ key.key_data.raw_public_key
185
+ else
186
+ # raw_public_key added in openssl gem 3.2.0 (Ruby 3.3): https://github.com/ruby/openssl/blob/master/History.md
187
+ # Ed25519/X25519 SPKI DER is 44 bytes with the 32-byte key at the end
188
+ key.key_data.public_to_der[-32..]
189
+ end
190
+ else
191
+ raise ArgumentError, "SubtleCrypto: raw export not supported for '#{name}'"
192
+ end
193
+ when "spki"
194
+ raise ArgumentError, "SubtleCrypto: key is not extractable" unless key.extractable
195
+ raise ArgumentError, "SubtleCrypto: spki export requires public key" unless key.type == "public"
196
+ key.key_data.public_to_der
197
+ when "pkcs8"
198
+ raise ArgumentError, "SubtleCrypto: key is not extractable" unless key.extractable
199
+ raise ArgumentError, "SubtleCrypto: pkcs8 export requires private key" unless key.type == "private"
200
+ key.key_data.private_to_der
201
+ else
202
+ raise ArgumentError, "SubtleCrypto: unsupported exportKey format '#{format}'"
203
+ end
204
+ end
205
+
206
+ def self.encrypt(name, key, data, params = {})
207
+ case name
208
+ when "AES-GCM"
209
+ aes_gcm_encrypt(key, data, params)
210
+ when "AES-CBC"
211
+ aes_cbc_crypt(key, data, params, :encrypt)
212
+ when "AES-CTR"
213
+ aes_ctr_crypt(key, data, params, :encrypt)
214
+ when "AES-KW"
215
+ aes_kw_crypt(key, data, :encrypt)
216
+ when "RSA-OAEP"
217
+ rsa_oaep_crypt(key, data, params, :encrypt)
218
+ else
219
+ raise ArgumentError, "SubtleCrypto: unsupported encrypt algorithm '#{name}'"
220
+ end
221
+ end
222
+
223
+ def self.decrypt(name, key, data, params = {})
224
+ case name
225
+ when "AES-GCM"
226
+ aes_gcm_decrypt(key, data, params)
227
+ when "AES-CBC"
228
+ aes_cbc_crypt(key, data, params, :decrypt)
229
+ when "AES-CTR"
230
+ aes_ctr_crypt(key, data, params, :decrypt)
231
+ when "AES-KW"
232
+ aes_kw_crypt(key, data, :decrypt)
233
+ when "RSA-OAEP"
234
+ rsa_oaep_crypt(key, data, params, :decrypt)
235
+ else
236
+ raise ArgumentError, "SubtleCrypto: unsupported decrypt algorithm '#{name}'"
237
+ end
238
+ end
239
+
240
+ def self.sign(name, key, data, params = {})
241
+ case name
242
+ when "HMAC"
243
+ hash_name = ossl_digest_name(key.algorithm["hash"])
244
+ OpenSSL::HMAC.digest(hash_name, key.key_data, data)
245
+ when "RSASSA-PKCS1-v1_5"
246
+ hash_name = ossl_digest_name(key.algorithm["hash"])
247
+ key.key_data.sign(hash_name, data)
248
+ when "RSA-PSS"
249
+ hash_name = ossl_digest_name(key.algorithm["hash"])
250
+ salt_length = params[:salt_length] || :digest
251
+ key.key_data.sign_pss(hash_name, data, salt_length: salt_length, mgf1_hash: hash_name)
252
+ when "ECDSA"
253
+ hash_name = ossl_digest_name(params[:hash] || key.algorithm["hash"])
254
+ named_curve = key.algorithm["namedCurve"]
255
+ coord_size = EC_COORD_SIZES[named_curve] or raise ArgumentError, "SubtleCrypto: unsupported curve '#{named_curve}'"
256
+ der_sig = key.key_data.sign(hash_name, data)
257
+ der_to_p1363(der_sig, coord_size)
258
+ when "Ed25519"
259
+ key.key_data.sign(nil, data)
260
+ else
261
+ raise ArgumentError, "SubtleCrypto: unsupported sign algorithm '#{name}'"
262
+ end
263
+ end
264
+
265
+ def self.verify(name, key, signature, data, params = {})
266
+ case name
267
+ when "HMAC"
268
+ hash_name = ossl_digest_name(key.algorithm["hash"])
269
+ expected = OpenSSL::HMAC.digest(hash_name, key.key_data, data)
270
+ OpenSSL::HMAC.hexdigest(hash_name, key.key_data, data) == OpenSSL::HMAC.hexdigest(hash_name, key.key_data, data) &&
271
+ secure_compare(expected, signature)
272
+ when "RSASSA-PKCS1-v1_5"
273
+ hash_name = ossl_digest_name(key.algorithm["hash"])
274
+ key.key_data.verify(hash_name, signature, data)
275
+ when "RSA-PSS"
276
+ hash_name = ossl_digest_name(key.algorithm["hash"])
277
+ salt_length = params[:salt_length] || :digest
278
+ key.key_data.verify_pss(hash_name, signature, data, salt_length: salt_length, mgf1_hash: hash_name)
279
+ when "ECDSA"
280
+ hash_name = ossl_digest_name(params[:hash] || key.algorithm["hash"])
281
+ named_curve = key.algorithm["namedCurve"]
282
+ coord_size = EC_COORD_SIZES[named_curve] or raise ArgumentError, "SubtleCrypto: unsupported curve '#{named_curve}'"
283
+ begin
284
+ der_sig = p1363_to_der(signature, coord_size)
285
+ key.key_data.verify(hash_name, der_sig, data)
286
+ rescue OpenSSL::PKey::PKeyError
287
+ false
288
+ end
289
+ when "Ed25519"
290
+ begin
291
+ key.key_data.verify(nil, signature, data)
292
+ rescue OpenSSL::PKey::PKeyError
293
+ false
294
+ end
295
+ else
296
+ raise ArgumentError, "SubtleCrypto: unsupported verify algorithm '#{name}'"
297
+ end
298
+ end
299
+
300
+ def self.derive_bits(name, key, length_bits, params = {})
301
+ case name
302
+ when "PBKDF2"
303
+ hash_name = ossl_digest_name(params[:hash]) or raise ArgumentError, "SubtleCrypto: PBKDF2 requires hash"
304
+ salt = params[:salt] or raise ArgumentError, "SubtleCrypto: PBKDF2 requires salt"
305
+ iterations = params[:iterations] or raise ArgumentError, "SubtleCrypto: PBKDF2 requires iterations"
306
+ OpenSSL::KDF.pbkdf2_hmac(key.key_data, salt: salt, iterations: iterations, length: length_bits / 8, hash: hash_name)
307
+ when "HKDF"
308
+ hash_name = ossl_digest_name(params[:hash]) or raise ArgumentError, "SubtleCrypto: HKDF requires hash"
309
+ salt = params[:salt] || ""
310
+ info = params[:info] || ""
311
+ OpenSSL::KDF.hkdf(key.key_data, salt: salt, info: info, length: length_bits / 8, hash: hash_name)
312
+ when "ECDH"
313
+ peer_key = params[:public] or raise ArgumentError, "SubtleCrypto: ECDH requires public key"
314
+ shared = key.key_data.dh_compute_key(peer_key.key_data.public_key)
315
+ shared.byteslice(0, length_bits / 8)
316
+ when "X25519"
317
+ peer_key = params[:public] or raise ArgumentError, "SubtleCrypto: X25519 requires public key"
318
+ shared = key.key_data.derive(peer_key.key_data)
319
+ shared.byteslice(0, length_bits / 8)
320
+ else
321
+ raise ArgumentError, "SubtleCrypto: unsupported deriveBits algorithm '#{name}'"
322
+ end
323
+ end
324
+
325
+ def self.derive_key(name, key, derived_algo, extractable, usages, params = {})
326
+ derived_name = derived_algo[:name] or raise ArgumentError, "SubtleCrypto: derived key algorithm name is required"
327
+ derived_length = case derived_name
328
+ when *AES_ALGORITHMS then derived_algo[:length] or raise ArgumentError, "SubtleCrypto: derived AES key requires length"
329
+ when "HMAC" then derived_algo[:length] || HMAC_HASH_OUTPUT_LENGTHS[derived_algo[:hash]]
330
+ else raise ArgumentError, "SubtleCrypto: unsupported derived key algorithm '#{derived_name}'"
331
+ end
332
+ key_bytes = derive_bits(name, key, derived_length, params)
333
+ import_key("raw", key_bytes, derived_algo, extractable, usages)
334
+ end
335
+
336
+ def self.wrap_key(format, key, wrapping_key, wrap_algo_hash, wrap_name)
337
+ key_data = export_key(format, key)
338
+ encrypt(wrap_name, wrapping_key, key_data, wrap_algo_hash)
339
+ end
340
+
341
+ def self.unwrap_key(format, wrapped, unwrapping_key, unwrap_algo_hash, unwrapped_algo_hash, extractable, usages, unwrap_name)
342
+ key_data = decrypt(unwrap_name, unwrapping_key, wrapped, unwrap_algo_hash)
343
+ import_key(format, key_data, unwrapped_algo_hash, extractable, usages)
344
+ end
345
+
346
+ def self.aes_gcm_encrypt(key, data, params)
347
+ iv = params.fetch(:iv)
348
+ tag_length = params.fetch(:tag_length, 128)
349
+ additional_data = params[:additional_data]
350
+
351
+ cipher = OpenSSL::Cipher.new("aes-#{key.algorithm["length"]}-gcm")
352
+ cipher.encrypt
353
+ cipher.key = key.key_data
354
+ cipher.iv = iv
355
+ cipher.auth_data = additional_data || ""
356
+ ciphertext = cipher.update(data) + cipher.final
357
+ tag = cipher.auth_tag(tag_length / 8)
358
+ ciphertext + tag
359
+ end
360
+ private_class_method :aes_gcm_encrypt
361
+
362
+ def self.aes_gcm_decrypt(key, data, params)
363
+ iv = params.fetch(:iv)
364
+ tag_length = params.fetch(:tag_length, 128)
365
+ additional_data = params[:additional_data]
366
+
367
+ tag_bytes = tag_length / 8
368
+ ciphertext = data[0, data.bytesize - tag_bytes]
369
+ tag = data[-tag_bytes, tag_bytes]
370
+
371
+ decipher = OpenSSL::Cipher.new("aes-#{key.algorithm["length"]}-gcm")
372
+ decipher.decrypt
373
+ decipher.key = key.key_data
374
+ decipher.iv = iv
375
+ decipher.auth_tag = tag
376
+ decipher.auth_data = additional_data || ""
377
+ decipher.update(ciphertext) + decipher.final
378
+ rescue OpenSSL::Cipher::CipherError => e
379
+ raise RuntimeError, "SubtleCrypto: AES-GCM decryption failed: #{e.message}"
380
+ end
381
+ private_class_method :aes_gcm_decrypt
382
+
383
+ def self.aes_cbc_crypt(key, data, params, direction)
384
+ iv = params.fetch(:iv)
385
+
386
+ cipher = OpenSSL::Cipher.new("aes-#{key.algorithm["length"]}-cbc")
387
+ direction == :encrypt ? cipher.encrypt : cipher.decrypt
388
+ cipher.key = key.key_data
389
+ cipher.iv = iv
390
+ cipher.update(data) + cipher.final
391
+ rescue OpenSSL::Cipher::CipherError => e
392
+ raise RuntimeError, "SubtleCrypto: AES-CBC failed: #{e.message}"
393
+ end
394
+ private_class_method :aes_cbc_crypt
395
+
396
+ def self.aes_kw_crypt(key, data, direction)
397
+ bits = key.algorithm["length"]
398
+ cipher = OpenSSL::Cipher.new("aes-#{bits}-wrap")
399
+ direction == :encrypt ? cipher.encrypt : cipher.decrypt
400
+ cipher.key = key.key_data
401
+ cipher.update(data) + cipher.final
402
+ rescue OpenSSL::Cipher::CipherError => e
403
+ raise RuntimeError, "SubtleCrypto: AES-KW failed: #{e.message}"
404
+ end
405
+ private_class_method :aes_kw_crypt
406
+
407
+ def self.aes_ctr_crypt(key, data, params, direction)
408
+ counter = params.fetch(:counter)
409
+ length = params[:length] || 64
410
+
411
+ cipher = OpenSSL::Cipher.new("aes-#{key.algorithm["length"]}-ctr")
412
+ direction == :encrypt ? cipher.encrypt : cipher.decrypt
413
+ cipher.key = key.key_data
414
+ cipher.iv = counter
415
+ cipher.update(data) + cipher.final
416
+ end
417
+ private_class_method :aes_ctr_crypt
418
+
419
+ def self.rsa_oaep_crypt(key, data, params, direction)
420
+ hash_name = ossl_digest_name(key.algorithm["hash"])
421
+ opts = { rsa_padding_mode: "oaep", rsa_oaep_md: hash_name, rsa_mgf1_md: hash_name }
422
+ if direction == :encrypt
423
+ key.key_data.encrypt(data, opts)
424
+ else
425
+ key.key_data.decrypt(data, opts)
426
+ end
427
+ rescue OpenSSL::PKey::PKeyError => e
428
+ raise RuntimeError, "SubtleCrypto: RSA-OAEP failed: #{e.message}"
429
+ end
430
+ private_class_method :rsa_oaep_crypt
431
+
432
+ def self.ossl_digest_name(hash_name)
433
+ DIGEST_ALGORITHMS[hash_name] or raise ArgumentError, "SubtleCrypto: unsupported hash '#{hash_name}'"
434
+ end
435
+ private_class_method :ossl_digest_name
436
+
437
+ def self.secure_compare(a, b)
438
+ return false unless a.bytesize == b.bytesize
439
+ OpenSSL.fixed_length_secure_compare(a, b)
440
+ end
441
+ private_class_method :secure_compare
442
+
443
+ def self.der_to_p1363(der_sig, coord_size)
444
+ asn = OpenSSL::ASN1.decode(der_sig)
445
+ r_bn = asn.value[0].value
446
+ s_bn = asn.value[1].value
447
+ r_bytes = r_bn.to_s(2)
448
+ s_bytes = s_bn.to_s(2)
449
+ pad = "\x00" * coord_size
450
+ r_padded = (pad + r_bytes).byteslice(-coord_size, coord_size)
451
+ s_padded = (pad + s_bytes).byteslice(-coord_size, coord_size)
452
+ r_padded + s_padded
453
+ end
454
+ private_class_method :der_to_p1363
455
+
456
+ def self.p1363_to_der(p1363_sig, coord_size)
457
+ r_bytes = p1363_sig.byteslice(0, coord_size)
458
+ s_bytes = p1363_sig.byteslice(coord_size, coord_size)
459
+ r_bn = OpenSSL::BN.new(r_bytes, 2)
460
+ s_bn = OpenSSL::BN.new(s_bytes, 2)
461
+ OpenSSL::ASN1::Sequence([
462
+ OpenSSL::ASN1::Integer(r_bn),
463
+ OpenSSL::ASN1::Integer(s_bn),
464
+ ]).to_der
465
+ end
466
+ private_class_method :p1363_to_der
467
+
468
+ def self.import_raw_ec_public(raw_bytes, named_curve)
469
+ ossl_curve = EC_CURVE_MAP[named_curve] or
470
+ raise ArgumentError, "SubtleCrypto: unsupported EC curve '#{named_curve}'"
471
+ ec_oid = OpenSSL::ASN1::ObjectId("id-ecPublicKey")
472
+ curve_oid = OpenSSL::ASN1::ObjectId(ossl_curve)
473
+ algo_seq = OpenSSL::ASN1::Sequence([ec_oid, curve_oid])
474
+ spki = OpenSSL::ASN1::Sequence([algo_seq, OpenSSL::ASN1::BitString(raw_bytes)])
475
+ OpenSSL::PKey.read(spki.to_der)
476
+ end
477
+ private_class_method :import_raw_ec_public
478
+
479
+ def self.import_raw_okp_public(raw_bytes, ossl_algo)
480
+ oid = OpenSSL::ASN1::ObjectId(ossl_algo)
481
+ algo_seq = OpenSSL::ASN1::Sequence([oid])
482
+ spki = OpenSSL::ASN1::Sequence([algo_seq, OpenSSL::ASN1::BitString(raw_bytes)])
483
+ OpenSSL::PKey.read(spki.to_der)
484
+ end
485
+ private_class_method :import_raw_okp_public
486
+
487
+ def self.ec_spki_to_raw(spki_der)
488
+ asn = OpenSSL::ASN1.decode(spki_der)
489
+ asn.value[1].value
490
+ end
491
+ private_class_method :ec_spki_to_raw
492
+ end
493
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quickjs
4
- VERSION = "0.13.0"
4
+ VERSION = "0.14.0"
5
5
  end
data/lib/quickjs.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "securerandom"
3
4
  require "timeout"
4
5
  require_relative "quickjs/version"
6
+ require_relative "quickjs/subtle_crypto"
7
+ require_relative "quickjs/crypto_key"
5
8
  require_relative "quickjs/quickjsrb"
6
9
 
7
10
  module Quickjs
@@ -0,0 +1,72 @@
1
+ import { readFileSync } from "fs";
2
+
3
+ const lock = JSON.parse(readFileSync(new URL("./package-lock.json", import.meta.url)));
4
+
5
+ const ROOT_PACKAGES = [
6
+ "@formatjs/intl-getcanonicallocales",
7
+ "@formatjs/intl-locale",
8
+ "@formatjs/intl-pluralrules",
9
+ "@formatjs/intl-numberformat",
10
+ "@formatjs/intl-datetimeformat",
11
+ ];
12
+
13
+ const BUILD_ONLY = new Set([
14
+ "rolldown", "@rolldown/pluginutils", "@oxc-project/types",
15
+ "@emnapi/core", "@emnapi/runtime", "@emnapi/wasi-threads",
16
+ "@napi-rs/wasm-runtime", "@tybys/wasm-util",
17
+ ]);
18
+
19
+ function collectDeps(pkgName, visited = new Set()) {
20
+ if (visited.has(pkgName) || BUILD_ONLY.has(pkgName)) return visited;
21
+ visited.add(pkgName);
22
+ const info = lock.packages[`node_modules/${pkgName}`];
23
+ if (!info) return visited;
24
+ for (const dep of Object.keys({ ...info.dependencies, ...info.peerDependencies ?? {} })) {
25
+ collectDeps(dep, visited);
26
+ }
27
+ return visited;
28
+ }
29
+
30
+ const allDeps = new Set();
31
+ for (const root of ROOT_PACKAGES) collectDeps(root, allDeps);
32
+
33
+ let hasError = false;
34
+ const byYear = {};
35
+
36
+ for (const pkg of [...allDeps].sort()) {
37
+ let licenseText;
38
+ try {
39
+ licenseText = readFileSync(`node_modules/${pkg}/LICENSE.md`, "utf8");
40
+ } catch {
41
+ try {
42
+ licenseText = readFileSync(`node_modules/${pkg}/LICENSE`, "utf8");
43
+ } catch {
44
+ console.error(`ERROR: No license file found for ${pkg}`);
45
+ hasError = true;
46
+ continue;
47
+ }
48
+ }
49
+
50
+ const isMIT = licenseText.includes("MIT License");
51
+ const yearMatch = licenseText.match(/Copyright\s+\(c\)\s+(\d{4})/i);
52
+ const authorMatch = licenseText.match(/Copyright\s+\(c\)\s+\d{4}\s+(.+)/i);
53
+
54
+ if (!isMIT) {
55
+ console.error(`ERROR: ${pkg} is NOT MIT licensed`);
56
+ hasError = true;
57
+ }
58
+
59
+ const year = yearMatch?.[1] ?? "unknown";
60
+ const author = authorMatch?.[1]?.trim() ?? "unknown";
61
+ byYear[year] ??= { author, packages: [] };
62
+ byYear[year].packages.push(pkg);
63
+ }
64
+
65
+ console.log("Bundled dependencies by copyright year:\n");
66
+ for (const year of Object.keys(byYear).sort()) {
67
+ const { author, packages } = byYear[year];
68
+ console.log(` MIT License Copyright (c) ${year} ${author}`);
69
+ for (const pkg of packages) console.log(` - ${pkg}`);
70
+ }
71
+
72
+ if (hasError) process.exit(1);