2016-09-05 2 views
3

Je travaille sur une implémentation java pure pour WebPush avec VAPID et le chiffrement de charge utile (j'ai déjà fait des implémentations pour GCM et FCM). Cependant, la documentation est encore marginale et les exemples de code ne sont toujours pas importants. En ce moment j'essaye de l'obtenir pour fonctionner dans Chrome. Bien que je reçois des abonnements réussis en utilisant VAPID, quand j'envoie un Tickle ou un message Push Payload je reçois un 400 UnauthorizedRegistration. Je suppose que cela a quelque chose à voir avec l'en-tête d'autorisation ou l'en-tête Crypto-Key. C'est ce que j'envoie à ce jour pour une Tickle (Une notification push sans charge utile):[WebPush] La requête [VAPID] échoue avec 400 UnauthorizedRegistration

URL: https://fcm.googleapis.com/fcm/send/xxxxx:xxxxxxxxxxx... 
Action: POST/PUT (Both give same result) 
With headers: 
    Authorization: Bearer URLBase64(JWT_HEAD).URLBase64(JWT_Payload).SIGN 
    Crypto-Key: p265ecdsa=X9.62(PublicKey) 
    Content-Type: "text/plain;charset=utf8" 
    Content-Length: 0 
    TTL: 120 

JWT_HEAD="{\"typ\":\"JWT\",\"alg\":\"ES256\"}" 
JWT_Payload={ 
    aud: "https://fcm.googleapis.com", 
    exp: (System.currentTimeMillis()/1000) + (60 * 60 * 12)), 
    sub: "mailto:[email protected]" 
} 
SIGN = the "SHA256withECDSA" signature algorithm over: "URLBase64(JWT_HEAD).URLBase64(JWT_Payload)" 

J'ai dépouillé les espaces blancs des deux années JSON dans le JWT puisque la spécification est pas très clair sur l'utilisation des espaces que semblait la chose la plus sûre à faire. La signature valide après avoir décodé à nouveau le x9.62 vers ECPoint, de sorte que publicKey semble valablement codé. Cependant, je continue à obtenir la réponse:

<HTML><HEAD><TITLE>UnauthorizedRegistration</TITLE></HEAD><BODY BGCOLOR="#FFFFFF" TEXT="#000000"><H1>UnauthorizedRegistration</H1><H2>Error 400</H2></BODY></HTML> 

Selon la documentation FCM cela ne happends lorsqu'une erreur JSON se produit, mais je me sens la spécification ne couvre pas WebPush du tout. Pour l'instant j'ai tous les deux essayé la construction dans Java Crypto fournisseurs et BC tous les deux produisent les mêmes résultats.

Quelques extraits de code pour la clarification:

KeyGeneration:

KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "BC"); 
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1"); 
keyGen.initialize(spec, secureRandom); 
KeyPair vapidPair = keyGen.generateKeyPair(); 

ecPublicKey à X9.62:

public byte[] toUncompressedPoint(ECPublicKey publicKey){ 
    final ECPoint publicPoint = publicKey.getW(); 

    final int keySizeBytes = (publicKey.getParams().getOrder().bitLength() + Byte.SIZE - 1)/Byte.SIZE; 
    final byte[] x = publicPoint.getAffineX().toByteArray(); 
    final byte[] y = publicPoint.getAffineY().toByteArray(); 
    final byte[] res = new byte[1 + 2 * keySizeBytes]; 
    int offset = 0; 
    res[offset++] = 0x04; //Indicating no key compression is used 
    if(x.length <= keySizeBytes) 
     System.arraycopy(x, 0, res, offset + keySizeBytes - x.length, x.length); 
    else if(x.length == keySizeBytes + 1) System.arraycopy(x, 1, res, offset, keySizeBytes); 
    else throw new IllegalArgumentException("X value is too large!"); 

    offset += keySizeBytes; 
    if(y.length <= keySizeBytes) 
     System.arraycopy(y, 0, res, offset + keySizeBytes - y.length, y.length); 
    else if(y.length == keySizeBytes + 1 && y[0] == 0) System.arraycopy(y, 1, res, offset, keySizeBytes); 
    else throw new IllegalArgumentException("Y value is too large!"); 

    return res; 
} 

Signer la demande JWT:

ObjectNode claim = om.createObjectNode(); 
    claim.put("aud", host); 
    claim.put("exp", (System.currentTimeMillis()/1000) + (60 * 60 * 12)); 
    claim.put("sub", "mailto:[email protected]"); 
    String claimString = claim.toString(); 
    String encHeader = URLBase64.encodeString(VAPID_HEADER, false); 
    String encPayload = URLBase64.encodeString(claimString, false); 
    String vapid = null; 
    ECPublicKey pubKey = (ECPublicKey) vapidPair.getPublic(); 
    byte[] point = toUncompressedPoint(pubKey); 
    String vapidKey = URLBase64.encodeToString(point, false); 
    try{ 
     Signature dsa = Signature.getInstance("SHA256withECDSA", "BC"); 
     dsa.initSign(vapidPair.getPrivate()); 
     dsa.update((encHeader + "." + encPayload).getBytes(StandardCharsets.US_ASCII)); 
     byte[] signature = dsa.sign(); 
     vapid = encHeader + "." + encPayload + "." + URLBase64.encodeToString(signature, false); 

Certains questions qui résident dans mon esprit:

  • quel est le champ auth pour dans la réponse d'enregistrement JSON? Puisque à ma connaissance pour le cryptage seul le p256dh est utilisé pour générer les clés de cryptage avec un serveur basé sur KeyPair.

    Des recherches plus approfondies du projet ietf 03 m'a donné la réponse à la section: 2.3 Lien: https://tools.ietf.org/html/draft-ietf-webpush-encryption-03 également le lien dans la réponse de Vincent Cheung donne une bonne explication

  • La documentation parle de différents usages d'en-tête pour mièvres à l'aide Bearer/WebPush et en utilisant l'en-tête Crypto-Key ou l'en-tête Encryption-Key. Wat est la bonne chose à faire?

  • Des idées pour lesquelles le serveur FCM continue à renvoyer un: 400 enregistrement non autorisé?

Quelqu'un peut-il ajouter le tag VAPID à cette question? Cela ne semble pas encore exister.

+0

Pendant ce temps, j'ai fait quelques tests en utilisant le https://web-push-libs.github.io/vapid/js/ Une découverte intéressante est que même si le signataire, la signature et le ECPoint x et y les valeurs sont égales à ce que j'ai côté serveur pour signer le JWT. Même ainsi, la validation de la signature de la page de test insipide donne faux. Pourrait-il être que l'implémentation Java (à la fois soleil et BC) de "SHA256withECDSA" n'est pas compatible avec le Javascript: {name: "ECDSA", appelé Curve: "P-256", hash: {name: "SHA- 256 "}}; –

+0

Juste un peu plus loin, maintenant besoin de refaire certains des anciens tests avec ordre et ainsi de suite.Il semble que l'encodage de la signature Java soit en ASN.1 DER et le format pour le JWT est concaténé R + S. Ceci explique l'échec de la validation de la signature dans mon dernier commentaire. Cependant, même avec cela changé, je reçois toujours 400 réponses UnauthorizedRegistration. –

+0

@bruha merci d'ajouter l'étiquette insipide! –

Répondre

2

Le problème principal dans la demande de transmission échouée à FCM était dans le codage de signature. J'ai toujours pensé à une signature identique à un hachage, juste un flux d'octets non codé. Cependant, une signature ECDSA contient une partie R et S, en Java, elles sont représentées dans ASN.1 DER et pour JWT, elles doivent être concaténées sans autre codage. Techniquement, cela résout ma question. Je suis encore en train d'achever la bibliothèque et j'enverrai la solution complète ici (et peut-être sur GitHub) quand elle sera finie.

+0

Une question que j'ai à ce sujet: Quelle est l'utilisation d'une signature plus d'un flux d'octets non codé? Que pouvons-nous faire de plus avec les portions R + S qui valident la signature? Ou est-ce qu'une signature ECDSA est validée autrement que par une signature RSA? où, dans le processus de validation, les portions R + S sont requises. –

5

Quel est le champ d'authentification dans la réponse d'enregistrement JSON? Puisque à ma connaissance pour le cryptage seul le p256dh est utilisé pour générer les clés de cryptage avec un serveur basé sur KeyPair.

Le champ auth est utilisé pour le chiffrement si vous envoyez une notification push contenant des données.Je ne suis pas un expert en cryptographie, mais voici un article de Mozilla qui l'explique. https://blog.mozilla.org/services/2016/08/23/sending-vapid-identified-webpush-notifications-via-mozillas-push-service/

La documentation parle de différents usages d'en-tête pour mièvres à l'aide Bearer/WebPush et en utilisant l'en-tête Crypto-clé ou l'en-tête de chiffrement à clé. Wat est la bonne chose à faire?

Utilisez le support avec votre JWT.

Des idées pour lesquelles le serveur FCM continue de renvoyer un: 400 enregistrement non autorisé?

Ceci est la partie frustrante: le UnauthorizedRegistration de la FCM n'a pas vraiment vous en dire beaucoup. Pour moi, le problème était avec le triage de l'en-tête de JWT. J'écrivais le mien dans Go et je rassemblais une structure contenant les champs "typ" et "alg". Je ne pense pas que la spécification JWT parle de l'ordre des champs, mais FCM voulait clairement un en-tête spécifique. Je l'ai seulement réalisé quand j'ai vu une implémentation qui utilisait un constant header.

J'ai résolu le problème de 400 en remplaçant l'en-tête que je créais par marshalling avec l'en-tête ci-dessus.

Il y a quelques autres petites choses que vous devriez regarder dehors pour:

  1. Chrome a un bug avec l'en-tête Crypto-clé: Si l'en-tête a plus d'une entrée (ie: le chiffrement d'une charge utile aussi exiger l'utilisation de l'en-tête clé de chiffrement), vous devez utiliser un point-virgule à la place d'une virgule comme séparateur

  2. base64 de votre JWT doit être urlencoded sans rembourrage. Il y a apparemment un autre bug de Chrome avec l'encodage base64 donc vous devrez vous en occuper. Voici un exemple d'une bibliothèque qui prend ce bug en considération.

Editer: J'ai apparemment besoin de 10 réputations pour publier plus de 2 liens. Trouvez "push-encryption-go" sur Github et dans le fichier webpush/encrypt.go, les lignes 118-130 s'occupent du bug base64 de chrome.

+0

Merci pour votre grand effort! Cela pourrait donner quelques indices, malheureusement si mon en-tête JWT est codé égal à la chaîne statique dans le lien que vous avez fourni. Cependant, puisque FCM est si difficile sur l'en-tête de JWT je ne serai pas surpris si elle est aussi sur la charge utile JWT, je vais essayer quelques ordres différents là pour être sûr. Dans l'objet d'enregistrement de Chrome, il y a en effet un remplissage sur les chaînes encodées en base64. Cependant, ceux-ci sont utilisés côté serveur seulement et il ne donne aucun problème lors du décodage, donc ce n'est pas non plus le cœur de mon problème, je suppose. –

+0

Juste testé toutes les commandes possibles pour la charge utile JWT, sans chance ... Maintenant, luttant mon chemin à travers le code source Go (je n'ai aucune expérience dans Go, donc c'est un peu confus pour le moment). Une chose que vous aimez à propos de vos résultats est que sur une très petite "erreur" dans l'en-tête JWT il a donné la même erreur, donc j'espère que c'est la bonne direction à regarder. –

2

J'ai eu le même problème. Résolu en supprimant le "gcm_sender_id" du manifeste JSON.