@breakside/jskit 2023.20.0 → 2023.22.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.
Files changed (63) hide show
  1. package/Frameworks/DOM.jsframework/Info.json +2 -2
  2. package/Frameworks/DOM.jsframework/io.breakside.JSKit.DOM-bundle.js +2 -2
  3. package/Frameworks/FontKit.jsframework/Info.json +2 -2
  4. package/Frameworks/FontKit.jsframework/io.breakside.JSKit.FontKit-bundle.js +2 -2
  5. package/Frameworks/Foundation.jsframework/Info.json +2 -2
  6. package/Frameworks/Foundation.jsframework/JS/JSImage.js +9 -7
  7. package/Frameworks/Foundation.jsframework/JS/JSURLResponse.js +1 -1
  8. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  9. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  10. package/Frameworks/SecurityKit.jsframework/JS/SECCipher+HTML.js +31 -44
  11. package/Frameworks/SecurityKit.jsframework/JS/SECCipher+Node.js +16 -38
  12. package/Frameworks/SecurityKit.jsframework/JS/SECCipher.js +33 -3
  13. package/Frameworks/SecurityKit.jsframework/JS/SECHMAC.js +17 -0
  14. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  15. package/Info.json +2 -2
  16. package/Node/HTMLBuilder.js +76 -20
  17. package/Node/Resources.js +21 -120
  18. package/Node/io.breakside.jskit-bundle.js +2 -2
  19. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  20. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  21. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  22. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  23. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  24. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  25. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  26. package/Root/Frameworks/DOM/Info.yaml +1 -1
  27. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  28. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  29. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  30. package/Root/Frameworks/Foundation/JSImage.js +9 -7
  31. package/Root/Frameworks/Foundation/JSURLResponse.js +1 -1
  32. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  33. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  34. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  35. package/Root/Frameworks/NotificationKit/Info.yaml +2 -1
  36. package/Root/Frameworks/NotificationKit/NKHTMLUserNotificationCenter.js +59 -18
  37. package/Root/Frameworks/NotificationKit/NKUserNotification.js +12 -1
  38. package/Root/Frameworks/NotificationKit/NKUserNotificationCenter.js +5 -0
  39. package/Root/Frameworks/NotificationKit/NKWebPushService.js +145 -38
  40. package/Root/Frameworks/NotificationKit/NotificationKit+Node.js +19 -0
  41. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  42. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  43. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  44. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  45. package/Root/Frameworks/SecurityKit/SECCipher+HTML.js +31 -44
  46. package/Root/Frameworks/SecurityKit/SECCipher+Node.js +16 -38
  47. package/Root/Frameworks/SecurityKit/SECCipher.js +33 -3
  48. package/Root/Frameworks/SecurityKit/SECHMAC.js +17 -0
  49. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  50. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  51. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  52. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  53. package/Root/Frameworks/UIKit/UIHTMLApplication.js +2 -0
  54. package/Root/Frameworks/UIKit/UIHTMLContentEditableTextInputManager.js +1 -1
  55. package/Root/Frameworks/UIKit/UIHTMLWindowServer.js +8 -1
  56. package/Root/Frameworks/UIKit/UIListView.js +4 -2
  57. package/Root/Frameworks/UIKit/UIWindowServer.js +7 -13
  58. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  59. package/Root/Templates/html/${PROJECT_NAME}/www/${PROJECT_NAME}.webmanifest +9 -0
  60. package/Root/Templates/html/${PROJECT_NAME}/www/HTMLAppBootstrapper.js +7 -1
  61. package/Root/Templates/html/${PROJECT_NAME}/www/index.html +1 -0
  62. package/Root/Templates/html/${PROJECT_NAME}/www/service-worker.js +25 -1
  63. package/package.json +1 -1
@@ -36,6 +36,9 @@ JSClass("NKUserNotificationCenter", JSObject, {
36
36
  registerForRemoteNotifications: function(completion, target){
37
37
  },
38
38
 
39
+ unregisterForRemoteNotifications: function(completion, target){
40
+ },
41
+
39
42
  addNotification: function(notification, date){
40
43
  },
41
44
 
@@ -44,6 +47,8 @@ JSClass("NKUserNotificationCenter", JSObject, {
44
47
 
45
48
  defaultIcon: null,
46
49
 
50
+ webPushApplicationServerJWK: null,
51
+
47
52
  });
48
53
 
49
54
  NKUserNotificationCenter.AuthorizationStatus = {
@@ -15,91 +15,198 @@
15
15
 
16
16
  // #import Foundation
17
17
  // #import SecurityKit
18
- // jshint browser: true
18
+ // #import "NKUserNotification.js"
19
+ /* global NKWebPushService */
19
20
  "use strict";
20
21
 
21
22
  (function(){
22
23
 
23
24
  var logger = JSLog("notify", "webpush");
24
25
 
25
- JSObject("NKWebPushService", JSObject, {
26
+ JSClass("NKWebPushService", JSObject, {
26
27
 
27
- url: null,
28
28
  urlSession: null,
29
- signingJWK: null,
29
+ keystore: null,
30
30
  contactURL: null,
31
31
 
32
- initWithURL: function(url, urlSession){
33
- this.url = url;
32
+ initWithKeystore: function(keystore, urlSession){
33
+ this.keystore = keystore;
34
34
  this.urlSession = urlSession || JSURLSession.shared;
35
35
  },
36
36
 
37
- pushNotification: function(notification, completion, target){
37
+ pushNotification: function(notification, registration, completion, target){
38
38
  if (!completion){
39
- completion = Promise.completion(Promise.resolveNonNull);
39
+ completion = Promise.completion(Promise.resolveNull);
40
40
  }
41
- this.createPushRequest(function(request){
42
- if (request === null){
43
- completion.call(target, false);
41
+ this.createPushRequest(notification, registration, function(request, error){
42
+ if (error !== null){
43
+ completion.call(target, error);
44
44
  return;
45
45
  }
46
46
  var task = this.urlSession.dataTaskWithRequest(request, function(error){
47
47
  if (error !== null){
48
48
  logger.error("Push request failed: %{error}", error);
49
- completion.call(target, false);
49
+ completion.call(target, error);
50
50
  return;
51
51
  }
52
52
  if (task.response.statusCode !== JSURLResponse.StatusCode.created){
53
53
  logger.error("Push request failed with %d", task.response.statusCode);
54
- completion.call(target, false);
54
+ completion.call(target, JSURLResponseError(task.response));
55
55
  return;
56
56
  }
57
- completion.call(target, true);
57
+ completion.call(target, null);
58
58
  });
59
+ task.resume();
59
60
  }, this);
60
61
  return completion.promise;
61
62
  },
62
63
 
63
- createPushRequest: function(completion, target){
64
- var request = JSURLRequest.initWithURL(this.url);
64
+ createPushRequest: function(notification, registration, completion, target){
65
+ var url = JSURL.initWithString(registration.subscription.endpoint);
66
+ var request = JSURLRequest.initWithURL(url);
65
67
  request.method = JSURLRequest.Method.post;
66
- request.data = null; // TODO
67
- this.authorizePushRequest(request, function(authorized){
68
- if (authorized){
69
- completion.call(target, request);
70
- }else{
71
- completion.call(target, null);
68
+ if (notification.identifier !== null){
69
+ var hash = JSSHA1Hash(notification.identifier.utf8());
70
+ request.headerMap.add("Topic", hash.base64URLStringRepresentation());
71
+ }
72
+ request.headerMap.add("TTL", notification.timeToLive);
73
+ request.headerMap.add("Urgency", this.urgencyForNotificationPriority(notification.priority));
74
+ this.authorizePushRequest(request, notification, registration, function(error){
75
+ if (error !== null){
76
+ completion.call(target, request, error);
77
+ return;
72
78
  }
79
+ this.encodePushRequest(request, notification, registration, function(error){
80
+ completion.call(target, request, error);
81
+ }, this);
73
82
  }, this);
74
83
  },
75
84
 
76
- authorizePushRequest: function(request, completion, target){
77
- if (this.signingJWK === null){
78
- completion.call(target, true);
79
- }else{
80
- var identificationPayload = {
81
- aud: this.url.origin,
82
- exp: JSDate.initWithTimeIntervalSinceNow(JSTimeInterval.hours(24)).timeIntervalSince1970,
85
+ authorizePushRequest: function(request, notification, registration, completion, target){
86
+ // https://www.rfc-editor.org/rfc/rfc8292
87
+ var publicJWK = registration.options.applicationServerJWK;
88
+ var privateJWK = null;
89
+ if (publicJWK){
90
+ privateJWK = this.keystore.jwkForIdentifier(publicJWK.kid);
91
+ if (privateJWK === null){
92
+ completion.call(target, new Error("No key matching kid: %s".sprintf(publicJWK.kid)));
93
+ return;
94
+ }
95
+ }
96
+ if (privateJWK){
97
+ var url = JSURL.initWithString(registration.subscription.endpoint);
98
+ var claims = {
99
+ aud: url.origin,
100
+ exp: Math.floor(JSDate.initWithTimeIntervalSinceNow(JSTimeInterval.hours(24)).timeIntervalSince1970),
83
101
  };
84
102
  if (this.contactURL !== null){
85
- identificationPayload.sub = this.contactURL.encodedString;
103
+ claims.sub = this.contactURL.encodedString;
86
104
  }
87
- var identificationToken = SECJSONWebToken.initWithPayload(identificationPayload);
88
- identificationToken.sign(this.signingJWK, function(identificationTokenString){
89
- var keyString = JSData.initWithChunks([
105
+ var token = SECJSONWebToken.initWithPayload(claims);
106
+ token.sign(privateJWK, function(tokenString){
107
+ var encodedPublicKey = JSData.initWithChunks([
90
108
  JSData.initWithArray([0x04]),
91
- this.signingJWK.x.dataByDecodingBase64URL(),
92
- this.signingJWK.y.dataByDecodingBase64URL()
109
+ privateJWK.x.dataByDecodingBase64URL(),
110
+ privateJWK.y.dataByDecodingBase64URL()
93
111
  ]).base64URLStringRepresentation();
94
- request.headerMap.add("Authorization", "vapid t=%s,k=%s".sprintf(identificationTokenString, keyString));
95
- completion.call(target, true);
112
+ request.headerMap.add("Authorization", "vapid t=%s, k=%s".sprintf(tokenString, encodedPublicKey));
113
+ completion.call(target, null);
96
114
  }, function(error){
97
115
  logger.error("Failed to sign push request: %{error}", error);
98
- completion.call(target, false);
116
+ completion.call(target, error);
99
117
  });
118
+ }else{
119
+ completion.call(target, null);
120
+ }
121
+ },
122
+
123
+ encodePushRequest: function(request, notification, registration, completion, target){
124
+ // https://www.rfc-editor.org/rfc/rfc8188
125
+ // https://www.rfc-editor.org/rfc/rfc8291
126
+ var encodedClientPublicKey = registration.subscription.keys.p256dh;
127
+ var encodedClientAuthSecret = registration.subscription.keys.auth;
128
+ var plaintext = JSON.stringify({
129
+ type: "notification",
130
+ title: notification.title,
131
+ body: notification.body
132
+ }).utf8();
133
+ if (encodedClientPublicKey && encodedClientAuthSecret){
134
+ if (plaintext.length > NKWebPushService.maximumRecordSize - 17){
135
+ completion.call(target, new Error("Message too long for web push"));
136
+ return;
137
+ }
138
+ var clientPublicKeyData = encodedClientPublicKey.dataByDecodingBase64URL();
139
+ var clientAuthSecretData = encodedClientAuthSecret.dataByDecodingBase64URL();
140
+ var salt = SECCipher.getRandomData(16);
141
+ var caughtError = null;
142
+ // TODO: abstract ECDH calls so they're not node-specific
143
+ var crypto = require("crypto");
144
+ var ecdh = crypto.createECDH('prime256v1');
145
+ ecdh.generateKeys();
146
+ var serverPublicKeyData = JSData.initWithNodeBuffer(ecdh.getPublicKey());
147
+ var sharedKeyData = JSData.initWithNodeBuffer(ecdh.computeSecret(clientPublicKeyData));
148
+ SECHMAC.digest(SECHMAC.Algorithm.sha256, clientAuthSecretData, sharedKeyData).then(function(prk){
149
+ return SECHMAC.digest(SECHMAC.Algorithm.sha256, prk, JSData.initWithChunks([
150
+ "WebPush: info\x00".utf8(),
151
+ clientPublicKeyData,
152
+ serverPublicKeyData,
153
+ JSData.initWithArray([0x01])
154
+ ]));
155
+ }).then(function(ikm){
156
+ return SECHMAC.digest(SECHMAC.Algorithm.sha256, salt, ikm);
157
+ }).then(function(prk){
158
+ return Promise.all([
159
+ SECHMAC.digest(SECHMAC.Algorithm.sha256, prk, "Content-Encoding: aes128gcm\x00\x01".utf8()),
160
+ SECHMAC.digest(SECHMAC.Algorithm.sha256, prk, "Content-Encoding: nonce\x00\x01".utf8())
161
+ ]);
162
+ }).then(function(keyAndNonce){
163
+ var keyData = keyAndNonce[0].subdataInRange(JSRange(0, 16));
164
+ var nonceData = keyAndNonce[1].subdataInRange(JSRange(0, 12));
165
+ var cipher = SECCipher.initWithAlgorithm(SECCipher.Algorithm.aesGCM, 128);
166
+ return cipher.createKeyWithData(keyData).then(function(key){
167
+ if (key === null){
168
+ return;
169
+ }
170
+ return cipher.encryptWithNonce(nonceData, JSData.initWithChunks([plaintext, JSData.initWithArray([0x02])]), key);
171
+ });
172
+ }).then(function(encrypted){
173
+ var ciphertext = encrypted.subdataInRange(JSRange(12, encrypted.length - 12));
174
+ var header = JSData.initWithLength(5);
175
+ header.dataView().setUint32(0, NKWebPushService.maximumRecordSize);
176
+ header[4] = serverPublicKeyData.length;
177
+ request.headerMap.add("Content-Encoding", "aes128gcm");
178
+ request.data = JSData.initWithChunks([
179
+ salt,
180
+ header,
181
+ serverPublicKeyData,
182
+ ciphertext
183
+ ]);
184
+ }).catch(function(error){
185
+ caughtError = error;
186
+ }).finally(function(){
187
+ completion.call(target, caughtError);
188
+ });
189
+ }else{
190
+ if (plaintext.length > NKWebPushService.maximumRecordSize - 17){
191
+ completion.call(target, new Error("Message too long for web push"));
192
+ return;
193
+ }
194
+ request.data = plaintext;
195
+ completion.call(target, null);
196
+ }
197
+ },
198
+
199
+ urgencyForNotificationPriority: function(priority){
200
+ switch (priority){
201
+ case NKUserNotification.Priority.veryLow: return "very-low";
202
+ case NKUserNotification.Priority.low: return "low";
203
+ case NKUserNotification.Priority.high: return "high";
204
+ default: return "normal";
100
205
  }
101
206
  }
102
207
 
103
208
  });
104
209
 
210
+ NKWebPushService.maximumRecordSize = 4096;
211
+
105
212
  })();
@@ -0,0 +1,19 @@
1
+ // Copyright 2021 Breakside Inc.
2
+ //
3
+ // Licensed under the Breakside Public License, Version 1.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // If a copy of the License was not distributed with this file, you may
6
+ // obtain a copy at
7
+ //
8
+ // http://breakside.io/licenses/LICENSE-1.0.txt
9
+ //
10
+ // Unless required by applicable law or agreed to in writing, software
11
+ // distributed under the License is distributed on an "AS IS" BASIS,
12
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ // See the License for the specific language governing permissions and
14
+ // limitations under the License.
15
+
16
+ // #import "NotificationKit.js"
17
+ "use strict";
18
+
19
+ // #import "NKWebPushService.js"
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.PDFKit
3
- JSBundleVersion: 2023.20.0
3
+ JSBundleVersion: 2023.22.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.QRKit
3
- JSBundleVersion: 2023.20.0
3
+ JSBundleVersion: 2023.22.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.SearchKit
3
- JSBundleVersion: 2023.20.0
3
+ JSBundleVersion: 2023.22.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.SecurityKit
3
- JSBundleVersion: 2023.20.0
3
+ JSBundleVersion: 2023.22.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -195,14 +195,6 @@ SECCipherAESCounter.definePropertiesFromExtensions({
195
195
  counter: JSData.initWithLength(16),
196
196
  length: 64
197
197
  };
198
- this.decryptedMessageId =
199
- (nonce[6] << 48)
200
- | (nonce[5] << 40)
201
- | (nonce[4] << 32)
202
- | (nonce[3] << 24)
203
- | (nonce[2] << 16)
204
- | (nonce[1] << 8)
205
- | (nonce[0]);
206
198
  nonce.copyTo(algorithm.counter, 0);
207
199
  return algorithm;
208
200
  },
@@ -282,55 +274,41 @@ SECCipherAESGaloisCounterMode.definePropertiesFromExtensions({
282
274
 
283
275
  htmlAlgorithmName: HTMLCryptoAlgorithmNames.aesGCM,
284
276
 
285
- _getHTMLEncryptAlgorithm: function(){
286
- var nonce = JSData.initWithArray([
287
- 1,
288
- ((this.encryptedMessageId / 0x100000000) >> 16) & 0xFF,
289
- ((this.encryptedMessageId / 0x100000000) >> 8) & 0xFF,
290
- (this.encryptedMessageId / 0x100000000) & 0xFF,
291
- (this.encryptedMessageId >> 24) & 0xFF,
292
- (this.encryptedMessageId >> 16) & 0xFF,
293
- (this.encryptedMessageId >> 8) & 0xFF,
294
- this.encryptedMessageId & 0xF
295
- ]);
277
+ _getHTMLEncryptAlgorithm: function(nonce, _ivLength){
278
+ if (_ivLength === undefined){
279
+ _ivLength = nonce.length;
280
+ }
296
281
  var algorithm = {
297
282
  name: HTMLCryptoAlgorithmNames.aesGCM,
298
- iv: JSData.initWithLength(16),
283
+ iv: JSData.initWithLength(_ivLength),
299
284
  tagLength: 128
300
285
  };
301
286
  nonce.copyTo(algorithm.iv, 0);
302
287
  return algorithm;
303
288
  },
304
289
 
305
- _getHTMLDecryptAlgorithm: function(prefixedData){
306
- var nonce = prefixedData.subdataInRange(JSRange(0, 8));
290
+ _getHTMLDecryptAlgorithm: function(prefixedData, nonceLength, _ivLength){
291
+ if (nonceLength === undefined){
292
+ nonceLength = 8;
293
+ }
294
+ if (_ivLength === undefined){
295
+ _ivLength = nonceLength;
296
+ }
297
+ var nonce = prefixedData.subdataInRange(JSRange(0, nonceLength));
307
298
  var algorithm = {
308
299
  name: HTMLCryptoAlgorithmNames.aesGCM,
309
- iv: JSData.initWithLength(16),
300
+ iv: JSData.initWithLength(_ivLength),
310
301
  tagLength: 128
311
302
  };
312
- this.decryptedMessageId =
313
- (nonce[6] << 48)
314
- | (nonce[5] << 40)
315
- | (nonce[4] << 32)
316
- | (nonce[3] << 24)
317
- | (nonce[2] << 16)
318
- | (nonce[1] << 8)
319
- | (nonce[0]);
320
303
  nonce.copyTo(algorithm.iv, 0);
321
304
  return algorithm;
322
305
  },
323
306
 
324
- encrypt: function(data, key, completion, target){
307
+ encryptWithNonce: function(nonce, data, key, completion, target, _ivLength){
325
308
  if (!completion){
326
309
  completion = Promise.completion(Promise.resolveNonNull);
327
310
  }
328
- if (!this.ensureUniqueMessageID()){
329
- JSRunLoop.main.schedule(completion, target, null);
330
- return completion.promise;
331
- }
332
- var algorithm = this._getHTMLEncryptAlgorithm();
333
- var nonce = algorithm.iv.truncatedToLength(8);
311
+ var algorithm = this._getHTMLEncryptAlgorithm(nonce, _ivLength);
334
312
  crypto.subtle.encrypt(algorithm, key.htmlKey, data).then(function(encrypted){
335
313
  var noncePrefixed = JSData.initWithChunks([nonce, JSData.initWithBuffer(encrypted)]);
336
314
  completion.call(target, noncePrefixed);
@@ -340,12 +318,12 @@ SECCipherAESGaloisCounterMode.definePropertiesFromExtensions({
340
318
  return completion.promise;
341
319
  },
342
320
 
343
- decrypt: function(data, key, completion, target){
321
+ decryptWithNonceLength: function(nonceLength, data, key, completion, target, _ivLength){
344
322
  if (!completion){
345
323
  completion = Promise.completion(Promise.resolveNonNull);
346
324
  }
347
- var algorithm = this._getHTMLDecryptAlgorithm(data);
348
- var encrypted = data.subdataInRange(JSRange(8, data.length - 8));
325
+ var algorithm = this._getHTMLDecryptAlgorithm(data, nonceLength, _ivLength);
326
+ var encrypted = data.subdataInRange(JSRange(nonceLength, data.length - nonceLength));
349
327
  crypto.subtle.decrypt(algorithm, key.htmlKey, encrypted).then(function(decrypted){
350
328
  var decryptedData = JSData.initWithBuffer(decrypted);
351
329
  completion.call(target, decryptedData);
@@ -363,8 +341,17 @@ SECCipherAESGaloisCounterMode.definePropertiesFromExtensions({
363
341
  JSRunLoop.main.schedule(completion, target, null);
364
342
  return completion.promise;
365
343
  }
366
- var algorithm = this._getHTMLEncryptAlgorithm();
367
- var nonce = algorithm.iv.truncatedToLength(8);
344
+ var nonce = JSData.initWithArray([
345
+ 1,
346
+ ((this.encryptedMessageId / 0x100000000) >> 16) & 0xFF,
347
+ ((this.encryptedMessageId / 0x100000000) >> 8) & 0xFF,
348
+ (this.encryptedMessageId / 0x100000000) & 0xFF,
349
+ (this.encryptedMessageId >> 24) & 0xFF,
350
+ (this.encryptedMessageId >> 16) & 0xFF,
351
+ (this.encryptedMessageId >> 8) & 0xFF,
352
+ this.encryptedMessageId & 0xF
353
+ ]);
354
+ var algorithm = this._getHTMLEncryptAlgorithm(nonce, 16);
368
355
  crypto.subtle.wrapKey("raw", key.htmlKey, wrappingKey.htmlKey, algorithm).then(function(bytes){
369
356
  var noncePrefixed = JSData.initWithChunks([nonce, JSData.initWithBuffer(bytes)]);
370
357
  completion.call(target, noncePrefixed);
@@ -378,7 +365,7 @@ SECCipherAESGaloisCounterMode.definePropertiesFromExtensions({
378
365
  if (!completion){
379
366
  completion = Promise.completion(Promise.resolveNonNull);
380
367
  }
381
- var algorithm = this._getHTMLDecryptAlgorithm(wrappedKeyData);
368
+ var algorithm = this._getHTMLDecryptAlgorithm(wrappedKeyData, 8, 16);
382
369
  var unwrappedKeyHTMLAlgorithm = HTMLCryptoAlgorithmNames.fromAlgorithm(unwrappedKeyAlgorithm);
383
370
  wrappedKeyData = wrappedKeyData.subdataInRange(JSRange(8, wrappedKeyData.length - 8));
384
371
  crypto.subtle.unwrapKey("raw", wrappedKeyData, wrappingKey.htmlKey, algorithm, unwrappedKeyHTMLAlgorithm, true, ["encrypt", "decrypt"]).then(function(key){
@@ -182,14 +182,6 @@ SECCipherAESCounter.definePropertiesFromExtensions({
182
182
  return completion.promise;
183
183
  }
184
184
  var nonce = data.subdataInRange(JSRange(0, 8));
185
- this.decryptedMessageId =
186
- (nonce[6] << 48)
187
- | (nonce[5] << 40)
188
- | (nonce[4] << 32)
189
- | (nonce[3] << 24)
190
- | (nonce[2] << 16)
191
- | (nonce[1] << 8)
192
- | (nonce[0]);
193
185
  var iv = JSData.initWithLength(16);
194
186
  nonce.copyTo(iv, 0);
195
187
  var cipher = crypto.createDecipheriv(name, key.keyData, iv);
@@ -217,41 +209,31 @@ SECCipherAESGaloisCounterMode.definePropertiesFromExtensions({
217
209
  return null;
218
210
  },
219
211
 
220
- encrypt: function(data, key, completion, target){
212
+ encryptWithNonce: function(nonce, data, key, completion, target, _ivLength){
221
213
  if (!completion){
222
214
  completion = Promise.completion(Promise.resolveNonNull);
223
215
  }
224
- if (!this.ensureUniqueMessageID()){
225
- JSRunLoop.main.schedule(completion, target, null);
226
- return completion.promise;
227
- }
228
216
  var name = this._cipherNameForKey(key);
229
217
  if (name === null){
230
218
  JSRunLoop.main.schedule(completion, target, null);
231
219
  return completion.promise;
232
220
  }
233
- var nonce = JSData.initWithArray([
234
- 1,
235
- ((this.encryptedMessageId / 0x100000000) >> 16) & 0xFF,
236
- ((this.encryptedMessageId / 0x100000000) >> 8) & 0xFF,
237
- (this.encryptedMessageId / 0x100000000) & 0xFF,
238
- (this.encryptedMessageId >> 24) & 0xFF,
239
- (this.encryptedMessageId >> 16) & 0xFF,
240
- (this.encryptedMessageId >> 8) & 0xFF,
241
- this.encryptedMessageId & 0xF
242
- ]);
243
- var iv = JSData.initWithLength(16);
221
+ if (_ivLength === undefined){
222
+ _ivLength = nonce.length;
223
+ }
224
+ var iv = JSData.initWithLength(_ivLength);
244
225
  nonce.copyTo(iv, 0);
245
226
  var cipher = crypto.createCipheriv(name, key.keyData, iv);
246
227
  var chunks = [nonce, cipher.update(data), cipher.final()];
247
- var tag = JSData.initWithLength(16);
228
+ var tagLength = 16;
229
+ var tag = JSData.initWithLength(tagLength);
248
230
  cipher.getAuthTag().copyTo(tag, 0);
249
231
  chunks.push(tag);
250
232
  JSRunLoop.main.schedule(completion, target, JSData.initWithChunks(chunks));
251
233
  return completion.promise;
252
234
  },
253
235
 
254
- decrypt: function(data, key, completion, target){
236
+ decryptWithNonceLength: function(nonceLength, data, key, completion, target, _ivLength){
255
237
  if (!completion){
256
238
  completion = Promise.completion(Promise.resolveNonNull);
257
239
  }
@@ -261,21 +243,17 @@ SECCipherAESGaloisCounterMode.definePropertiesFromExtensions({
261
243
  JSRunLoop.main.schedule(completion, target, null);
262
244
  return completion.promise;
263
245
  }
264
- var nonce = data.subdataInRange(JSRange(0, 8));
265
- this.decryptedMessageId =
266
- (nonce[6] << 48)
267
- | (nonce[5] << 40)
268
- | (nonce[4] << 32)
269
- | (nonce[3] << 24)
270
- | (nonce[2] << 16)
271
- | (nonce[1] << 8)
272
- | (nonce[0]);
273
- var iv = JSData.initWithLength(16);
246
+ var nonce = data.subdataInRange(JSRange(0, nonceLength));
247
+ if (_ivLength === undefined){
248
+ _ivLength = nonceLength;
249
+ }
250
+ var iv = JSData.initWithLength(_ivLength);
274
251
  nonce.copyTo(iv, 0);
275
252
  var cipher = crypto.createDecipheriv(name, key.keyData, iv);
276
- var tag = data.subdataInRange(JSRange(data.length - 16, 16));
253
+ var tagLength = 16;
254
+ var tag = data.subdataInRange(JSRange(data.length - tagLength, tagLength));
277
255
  cipher.setAuthTag(tag);
278
- var chunks = [cipher.update(data.subdataInRange(JSRange(8, data.length - 8 - 16))), cipher.final()];
256
+ var chunks = [cipher.update(data.subdataInRange(JSRange(nonceLength, data.length - nonceLength - tagLength))), cipher.final()];
279
257
  JSRunLoop.main.schedule(completion, target, JSData.initWithChunks(chunks));
280
258
  }catch(e){
281
259
  JSRunLoop.main.schedule(completion, target, null);
@@ -190,7 +190,6 @@ JSClass("SECCipherAESCipherBlockChaining", SECCipher, {
190
190
  JSClass("SECCipherAESCounter", SECCipher, {
191
191
 
192
192
  encryptedMessageId: 0,
193
- decryptedMessageId: 0,
194
193
 
195
194
  ensureUniqueMessageID: function(){
196
195
  if (this.encryptedMessageId == 9007199254740991){
@@ -205,7 +204,6 @@ JSClass("SECCipherAESCounter", SECCipher, {
205
204
  JSClass("SECCipherAESGaloisCounterMode", SECCipher, {
206
205
 
207
206
  encryptedMessageId: 0,
208
- decryptedMessageId: 0,
209
207
 
210
208
  ensureUniqueMessageID: function(){
211
209
  if (this.encryptedMessageId == 9007199254740991){
@@ -213,7 +211,39 @@ JSClass("SECCipherAESGaloisCounterMode", SECCipher, {
213
211
  }
214
212
  ++this.encryptedMessageId;
215
213
  return true;
216
- }
214
+ },
215
+
216
+ encrypt: function(data, key, completion, target){
217
+ if (!completion){
218
+ completion = Promise.completion(Promise.resolveNonNull);
219
+ }
220
+ if (!this.ensureUniqueMessageID()){
221
+ JSRunLoop.main.schedule(completion, target, null);
222
+ return completion.promise;
223
+ }
224
+ var nonce = JSData.initWithArray([
225
+ 1,
226
+ ((this.encryptedMessageId / 0x100000000) >> 16) & 0xFF,
227
+ ((this.encryptedMessageId / 0x100000000) >> 8) & 0xFF,
228
+ (this.encryptedMessageId / 0x100000000) & 0xFF,
229
+ (this.encryptedMessageId >> 24) & 0xFF,
230
+ (this.encryptedMessageId >> 16) & 0xFF,
231
+ (this.encryptedMessageId >> 8) & 0xFF,
232
+ this.encryptedMessageId & 0xF
233
+ ]);
234
+ this.encryptWithNonce(nonce, data, key, completion, target, 16);
235
+ return completion.promise;
236
+ },
237
+
238
+ decrypt: function(data, key, completion, target){
239
+ return this.decryptWithNonceLength(8, data, key, completion, target, 16);
240
+ },
241
+
242
+ encryptWithNonce: function(nonce, data, key, completion, target){
243
+ },
244
+
245
+ decryptWithNonceLength: function(nonceLength, data, key, completion, target){
246
+ },
217
247
 
218
248
  });
219
249
 
@@ -64,4 +64,21 @@ SECHMAC.Algorithm = {
64
64
  sha256: "sha256",
65
65
  sha384: "sha384",
66
66
  sha512: "sha512"
67
+ };
68
+
69
+ SECHMAC.digest = function(algorithm, keyData, data, completion, target){
70
+ if (!completion){
71
+ completion = Promise.completion(Promise.resolveNonNull);
72
+ }
73
+ var hmac = SECHMAC.initWithAlgorithm(algorithm);
74
+ hmac.createKeyWithData(keyData, function(key){
75
+ if (key === null){
76
+ completion.call(target, null);
77
+ return;
78
+ }
79
+ hmac.key = key;
80
+ hmac.update(data);
81
+ hmac.sign(completion, target);
82
+ });
83
+ return completion.promise;
67
84
  };
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.ServerKit
3
- JSBundleVersion: 2023.20.0
3
+ JSBundleVersion: 2023.22.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.ServerKitTesting
3
- JSBundleVersion: 2023.20.0
3
+ JSBundleVersion: 2023.22.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.TestKit
3
- JSBundleVersion: 2023.20.0
3
+ JSBundleVersion: 2023.22.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.UIKit
3
- JSBundleVersion: 2023.20.0
3
+ JSBundleVersion: 2023.22.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments: