Создайте ключи VAPID в Java и передайте их в JavaScript PushManager
Я пытаюсь использовать веб-push-уведомления с протоколом веб-push в моем приложении. Чтобы использовать Push API с VAPID, мне нужен applicationServerKey
,
PushManager subscribe
Метод принимает ключ VAPID (только открытый ключ) в качестве параметра и дает конечную точку подписки и ключи для отправки сообщений.
Для генерации VAPID-ключей я использовал node.js (Google web-push
пакет) и openssl
до сих пор. Но в моем случае VAPID-ключи должны генерироваться в Java и передаваться в JavaScript для подписки из браузера.
Я пытаюсь с кодом ниже в Java для генерации ключей VAPID. Я могу успешно создавать ключи, но когда я передаю сгенерированный открытый ключ (строку в кодировке base64), subscribe
Метод возвращает сообщение об ошибке:
Невозможно зарегистрировать сервисного работника. DOMException: не удалось выполнить "подписку" для "PushManager": предоставленный applicationServerKey недействителен.
Пожалуйста, помогите мне решить эту проблему. Ниже мой код Java:
ECNamedCurveParameterSpec parameterSpec =
ECNamedCurveTable.getParameterSpec("prime256v1");
KeyPairGenerator keyPairGenerator =
KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
KeyPair serverKey = keyPairGenerator.generateKeyPair();
PrivateKey priv = serverKey.getPrivate();
PublicKey pub = serverKey.getPublic();`
System.out.println(Base64.toBase64String(pub.getEncoded()));
4 ответа
Пожалуйста, обратитесь к ссылке ниже для ответа от MartijnDwars. https://github.com/web-push-libs/webpush-java/issues/30
Вы можете использовать Utils.savePublicKey для преобразования сгенерированного Java PublicKey в байт []. Этот байт [] затем передается методу PushManager.subscribe.
Может быть удобнее кодировать base64 byte[] в Java, а base64 декодировать строку в JavaScript. Например, после генерации пары ключей в Java:
KeyPair keyPair = generateKeyPair(); byte[] publicKey = Utils.savePublicKey((ECPublicKey) keyPair.getPublic()); String publicKeyBase64 = BaseEncoding.base64Url().encode(publicKey); System.out.println("PublicKey = " + publicKeyBase64); // PublicKey = BPf36QAqZNNvvnl9kkpTDerXUOt6Nm6P4x9GEvmFVFKgVyCVWy24KUTs6wLQtbV2Ug81utbNnx86_vZzXDyrl88=
Тогда в JavaScript:
function subscribe() { const publicKey = base64UrlToUint8Array('BPf36QAqZNNvvnl9kkpTDerXUOt6Nm6P4x9GEvmFVFKgVyCVWy24KUTs6wLQtbV2Ug81utbNnx86_vZzXDyrl88='); navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) { serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: publicKey }) .then(function (subscription) { return sendSubscriptionToServer(subscription); }) .catch(function (e) { if (Notification.permission === 'denied') { console.warn('Permission for Notifications was denied'); } else { console.error('Unable to subscribe to push.', e); } }); }); } function base64UrlToUint8Array(base64UrlData) { const padding = '='.repeat((4 - base64UrlData.length % 4) % 4); const base64 = (base64UrlData + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); const rawData = atob(base64); const buffer = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { buffer[i] = rawData.charCodeAt(i); } return buffer; }
С bountycastle вы можете генерировать бессодержательные ключи с помощью этого кода:
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
String publicKeyString = Base64.getUrlEncoder().withoutPadding().encodeToString(publicKey.getQ().getEncoded(false));
System.out.println(publicKeyString);
String privateKeyString = Base64.getUrlEncoder().withoutPadding().encodeToString(privateKey.getD().toByteArray());
System.out.println(privateKeyString);
Потратив на это часы несколько часов, я подумал, что поделюсь решением, которое я разработал, изучив несколько веб-сайтов, избегая использования Bouncy Castle, который порождает множество других проблем. Попробуйте следующее:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
keyPairGenerator.initialize(spec, new SecureRandom());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPoint ecp = publicKey.getW();
byte[] x = ecp.getAffineX().toByteArray();
byte[] y = ecp.getAffineY().toByteArray();
// Convert 04 to bytes
String s= "04";
int len = s.length();
byte[] firstBit = new byte[len / 2];
for (int i = 0; i < len; i += 2)
{
firstBit[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) +
Character.digit(s.charAt(i+1), 16));
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
outputStream.write(firstBit);
outputStream.write(x);
outputStream.write(y);
publicKeyBytes = outputStream.toByteArray( );
Base64 encoder = new Base64(-1,null,true);
byte[] encodedBytes = encoder.encode(publicKeyBytes);
String publicKeyBase64 = new String(encodedBytes, StandardCharsets.UTF_8);
Это решение с плохой Java 8, которое обрабатывает случайную длину BigIntegers.
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( "EC" );
keyPairGenerator.initialize( new ECGenParameterSpec( "secp256r1" ), new SecureRandom() );
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey)keyPair.getPublic();
ECPoint ecp = publicKey.getW();
byte[] applicationServerKey = new byte[65];
applicationServerKey[0] = 4;
// copy getAffineX() to the target
byte[] affine = ecp.getAffineX().toByteArray(); // typical 31 to 33 bytes
int pos = 1;
int off, length;
off = affine.length - 32;
if( off >= 0 ) {
// there are leading zero values which we cut
length = 32;
} else {
pos -= off;
length = 32 + off;
off = 0;
}
System.arraycopy( affine, off, applicationServerKey, pos, length );
// copy getAffineY() to the target
affine = ecp.getAffineY().toByteArray(); // typical 31 to 33 bytes
pos = 33;
off = affine.length - 32;
if( off >= 0 ) {
// there are leading zero values which we cut
length = 32;
} else {
pos -= off;
length = 32 + off;
off = 0;
}
System.arraycopy( affine, off, applicationServerKey, pos, length );
return Base64.getEncoder().encodeToString( applicationServerKey );
или проще с кодировкой сборки Java:
ECPublicKey publicKey = ...
byte[] keyBytes = publicKey.getEncoded();
// 26 -> X509 overhead, length ever 91, results in 65 bytes
keyBytes = Arrays.copyOfRange( keyBytes, 26, 91 );
return this.applicationServerKey = Base64.getEncoder().encodeToString( keyBytes );