!7 !include once #1030462 // BouncyCastle import nl.martijndwars.webpush.Subscription; import nl.martijndwars.webpush.PushService; import nl.martijndwars.webpush.Notification; concept Entry { S publicKey; SecretValue privateKey; S comment; S globalID = aGlobalID(); } cmodule2 WebPushKeyManager > DynCRUD { start { registerBouncyCastle(); } KeyPair generateKeyPair() ctex { ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(nl.martijndwars.webpush.Utils.CURVE); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(nl.martijndwars.webpush.Utils.ALGORITHM, org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME); keyPairGenerator.initialize(parameterSpec); ret keyPairGenerator.generateKeyPair(); } enhanceFrame { internalFrameMenuItem(f, "Make key pair", rThreadEnter getKeyPair); } // API PKIKeyPair getKeyPair() { lock dbLock(); Entry entry = conceptWhere Entry(); if (entry == null) { KeyPair keyPair = generateKeyPair(); ECPublicKey publicKey = cast keyPair.getPublic(); ECPrivateKey privateKey = cast keyPair.getPrivate(); byte[] encodedPublicKey = nl.martijndwars.webpush.Utils.encode(publicKey); byte[] encodedPrivateKey = nl.martijndwars.webpush.Utils.encode(privateKey); S publicKeyString = base64encode(encodedPublicKey); S privateKeyString = base64encode(encodedPrivateKey); entry = cnew Entry(publicKey := publicKeyString, privateKey := SecretValue(privateKeyString)); } ret PKIKeyPair(entry.privateKey!, entry.publicKey); } // subData = subscription data as received from e.g. mozilla.com void sendNotification(PKIKeyPair keyPair default getKeyPair(), MapSO subData, S msg) ctex { // not using PushAsyncService because lazy PushService pushService = new(keyPair.publicKey(), keyPair.privateKey()); SS keys = cast subData.get("keys"); Subscription subscription = new((S) subData.get("endpoint"), new Subscription.Keys(keys.get("p256dh"), keys.get("auth"))); Notification notification = new(subscription, msg); pushService.send(notification); print("Sent push msg: " + msg); } }