Создайте ключи 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 );
Другие вопросы по тегам