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.
- checksums.yaml +4 -4
- data/README.md +32 -2
- data/Rakefile +7 -0
- data/ext/quickjsrb/extconf.rb +7 -0
- data/ext/quickjsrb/quickjsrb.c +177 -43
- data/ext/quickjsrb/quickjsrb.h +6 -4
- data/ext/quickjsrb/quickjsrb_crypto.c +71 -0
- data/ext/quickjsrb/quickjsrb_crypto.h +6 -0
- data/ext/quickjsrb/quickjsrb_crypto_subtle.c +1001 -0
- data/ext/quickjsrb/quickjsrb_crypto_subtle.h +6 -0
- data/ext/quickjsrb/vendor/polyfill-intl-en.min.js +3 -11
- data/ext/quickjsrb/vendor/polyfill-url.min.js +1 -0
- data/lib/quickjs/crypto_key.rb +15 -0
- data/lib/quickjs/subtle_crypto.rb +493 -0
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +3 -0
- data/polyfills/check-licenses.mjs +72 -0
- data/polyfills/package-lock.json +183 -154
- data/polyfills/package.json +9 -8
- data/polyfills/rolldown.config.mjs +8 -0
- data/polyfills/src/url.js +1089 -0
- data/sig/quickjs.rbs +6 -1
- metadata +11 -2
|
@@ -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
|
data/lib/quickjs/version.rb
CHANGED
data/lib/quickjs.rb
CHANGED
|
@@ -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);
|