I'm currently developing server-side support for sending iOS push messages (the server's in Java if that's relevant). Apple developer documentation "Communicating with APNs" claims that one can either use a certificate or a JWT token that is just inserted into the header if HTTP/2 protocol is used. Again, as far as I understood certificates should be manually renewed every year (which seems error-prone to me) while JWTs have all the parts to be regenerated automatically over indefinitely long periods (or not?). If that's the case, I'd definitely want to try using JWT.
Now, having zero experience with iOS development, no registration in iOS dev program and even no iOS devices, I have a hard time understanding how exactly to concoct the correct JWT. Namely, I don't get what are
"A 10-character key identifier (kid) key, obtained from your developer account"
"The issuer (iss) registered claim key, whose value is your 10-character Team ID, obtained from your developer account"
"After you create the token, you must sign it with a private key."
In the last sentence I don't understand what this private key is.
iOS developers I'm working with are not very enthusiastic about researching this topic, having given me the p12 certificate the way they always had done in the past. So, if I could point them to the right place (preferably pictures or working "paths") saying "send me this and this", my problem will hopefully be solved. If any of these are not readily available in any developer's account and should be arrived at by some process, I'm afraid I'll need these instructions as well (a working reference to docs or your own description would be perfect).
I would be very grateful if you could confirm my assumptions about JWT in general and clarify the missing details to me.
The Key ID and Key is obtained from the Apple Developer account portal. The process is described in the Xcode help and can be found by searching the help for “Configure push notifications.”.
You create a new Push Notification Authentication key in the Developer portal:
Go to Certificates, Identifiers & Profiles, and under Certificates, select All or APNs Auth Key.
Click the Add button (+) in the upper-right corner.
Under Production, select the “Apple Push Notification Authentication Key (Sandbox & Production)” checkbox, and click Continue.
Once you click Continue, you will see the following screen:
The Key ID is the KID referred to in the documentation and when you click Download you will get the private key that is associated with this key ID.
You can use this to generate the token, which is a JSON document with the following format:
{
"alg": "ES256",
"kid": "ABC123DEFG"
}
{
"iss": "DEF123GHIJ",
"iat": 1437179036
}
where kid is the Key ID and iss the team identifier, also from the Developer portal. iat is the issued at time for this token, which is the number of seconds since Epoch, in UTC
After you create the token, you must sign it with the private key that was downloaded from the portal when the kid was generated. You must then encrypt the token using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve and the SHA-256 hash algorithm.
To ensure security, APNs requires new tokens to be generated periodically. A new token has an updated issued at claim key, whose value indicates the time the token was generated. If the timestamp for token issue is not within the last hour, APNs rejects subsequent push messages, returning an ExpiredProviderToken (403) error.
Related
My use case is that once I have a user signed into my app, I use the Oauth token, resulting from the sign-in, when I make endpoint calls from my app to my custom server-- to authenticate the caller. E.g., I use Google Sign In in this way.
This method (e.g., with Google Sign In) has several useful properties:
Updated tokens are created automatically on the client app.
My custom server can easily verify the validity of the token, using Google's endpoints.
Initial token verification can take place early in the endpoint request processing-- without access to the custom servers database (as in the style in https://github.com/IBM-Swift/Kitura-Credentials).
My question is: Given that we're being told we have to incorporate Apple Sign-In into our iOS apps (if we offer general purpose sign-in facilities), how can I do endpoint authentication with my custom server?
I see two alternatives, neither of which I like very much.
First, I can have my client app send an Apple Sign In id_token to my server and ignore the exp (expiry) field. I can regenerate the id_token periodically (apparently, no more than once a day) and send it back to my client. I don't like this idea both because of ignoring the expiry of the token, and because of the need to periodically send the token from server to client. (My app uses multiple sign in systems and this just creates extra difficulty).
Second, I could have my client send an Apple Sign In refresh token to my server. My server would need, of course, to initially generate that refresh token and send it back to the client. I like this idea even less than the first idea. My initial token verification in my custom server would need to access its database to look for a match this token. I can't generally use an Apple endpoint -- because, again, Apple is apparently going to throttle this verification.
Additionally, I don't really like the idea that my custom server can, at best, check on token validity once a day. If the user revokes the app's credentials, I would hope my custom sever would stop being able to operate on behalf of the user relatively quickly.
Thoughts?
10/5/19-- update to the first alternative above. Upon actual use of https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens for refresh token validation, I find that it is not actually generating an updated id token. It is generating an access token (but Apple doesn't define a use for that), and is validating the refresh token. And so, there is no way to send an updated id token to the client iOS app. Thus, using the first alternative, the expiry date of the id token cannot be used.
10/10/19-- update: I've written a blog article on this subject-- https://medium.com/#crspybits/apple-sign-in-custom-servers-and-an-expiry-conundrum-d1ad63223870
8/6/20-- update: Follow on blog article with possible path forward, pending details from Apple: https://medium.com/#crspybits/part-ii-apple-sign-in-custom-servers-and-an-expiry-conundrum-b3e9735dc079
In Get the most out of Sign in with Apple in WWDC 2020, at 11:30 in their presentation, they introduce server-to-server notifications to enable your server to monitor user account state changes on a real-time basis.
So far, few details on this though.
----------------- UPDATE (12/23/20) -----------------
I now have these server-to-server notifications working in a testing environment with my server. Some notes:
I decided on the endpoint to use, on my server, to allow Apple to send my server these REST endpoint requests.
I pasted that into developer.apple.com > Account > Certificates, Identifiers & Profiles > Identifiers > Select your app identifier > Click 'Edit' next to 'Sign In with Apple' > Server to Server Notification Endpoint
This endpoint is effectively unauthorized. E.g., it is made by Apple with no OAuth credential access to your server. How this is setup will depend on your server. I had a means to set up a new endpoint/route for my server that was unauthorized.
I have the client side and other parts of my server set up to allow creation of accounts using Apple Sign In. So, using one of those accounts, I now started taking actions that would cause Apple to invoke their server-to-server notification endpoint on my server. I wanted to reverse engineer the details of the endpoint request Apple is making, since details are scarce.
This provides some ideas on how to cause the notification events to occur:
How to revoke Sign in with Apple credentials for a specific app?
You can revoke credentials, but it's easier (because you can do it repeatedly) to enable and disable the email relay. Of course, to do this, you have to initially sign-in with Apple using the private/email relay.
I next learned two things:
a) After you take the action (e.g., revoke the email relay), the server-to-server notification endpoint is accessed on your server within about 30 seconds. I had added various log output into my server, so could watch my server log and see this happening.
b) The endpoint request Apple makes to your server has body data containing JSON in the following format:
{"payload" : "-- SNIP -- JWT"}
I'm using the following Swift structure to decode this.
struct ApplePayload: Decodable {
let payload: String // JWT
}
As Apple has indicated in the WWDC 2020 video (https://developer.apple.com/videos/play/wwdc2020/10173/), the main content of the body data is a JWT. Above, this is the value of the key "payload" in the JSON.
The next step is decoding this JWT. I just guessed that it would use the same mechanism for decoding as with the JWT in other parts of the Apple Sign In server-side process. And specifically, in decoding the identity token (a JWT) passed up to your server by a client using Apple sign in. See https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple
I had some code that did this JWT decoding, so I factored that out and put it in a common place:
https://github.com/SyncServerII/AppleJWTDecoder.git
Integrating that into my server-side processing of Apple's server-to-server notification requests, I found that indeed this JWT can be decoded in this manner.
Another aspect that became evident is that the structure indicated by Apple in the WWDC 2020 video isn't 100% what is present in the JWT, after decoding. Specifically, in my tests so far at least the events field is not an array, rather it has a single value. See https://github.com/SyncServerII/AppleJWTDecoder/blob/main/Sources/AppleJWTDecoder/AppleSignInClaims.swift for a Swift structure.
I am now successfully parsing the JWT. The next main step on my server is to actually utilize the different event types in my server to take actions. For me this is going to involve the two account (not email) related actions:
User decided to stop using their Apple Id with your application. And
should be treated as a sign-out by the user. E.g., when a user decides
to disconnect your application from Settings. (From
https://developer.apple.com/videos/play/wwdc2020/10173/)
Also considered a request from user to "delete their app account"
(broader context: "Server to Server Notification Endpoint Sign in with
Apple server to server notifications allow you to receive important
updates about your users and their accounts. Notifications are sent
for each app group when users change mail forwarding preferences,
delete their app account, or permanently delete their Apple ID. Each
group of apps can have one URL, which must be absolute and include the
scheme, host, and path. TLS 1.2 or higher is required to receive
notifications. Learn more.") To see these docs, go to:
developer.apple.com > Account > Certificates, Identifiers & Profiles >
Identifiers > Select your app identifier > Click 'Edit' next to 'Sign
In with Apple' > Server to Server Notification Endpoint
case consentRevoked = "consent-revoked"
User has asked Apple to delete their Apple Id. The user identifier will now no longer be valid.
case accountDelete = "account-delete"
My plan is to take both of these events as equivalent- and delete the user's account on my server. I'm then going to have to consider how to communicate this to my client (iOS app). It will need to know that the user has deleted their account.
I recently noticed a new option when creating a certificate for a given iOS client.
The option is titled
Apple Push Notification Authentication Key (Sandbox & Production)
Get an authentication key to generate server-side tokens. You can use
these tokens as an alternative to certificates for your notification
requests.
One authentication key can be used for multiple apps and does not
expire.
How does one go about setting this up?
Apple Push Notification token-based authentication is an alternative to using provider certificates to connect to APNs. The provider API supports JSON Web Token (or JWT), an open standard, to pass authentication claims to APNs along with the push message.
To generate a provider token, obtain a private key for signing the
token as described in Creating a Universal Provider Tokenin App
Distribution Guide. You should construct a token with header
containing a 10 character Key ID (kid). The token claims portion
contains Issuer (iss) which is a 10 character Team ID. Your Team ID
and Key ID values can be obtained from your developer account. The
claims shall also contain Issued At (iat) which is the number of
seconds from Epoch in UTC when the token was generated. The token must
be signed with the Elliptic Curve Digital Signature Algorithm (ECDSA)
using the P-256 curve and the SHA-256 hash algorithm (ES256),
specified as a value in the algorithm key (alg).
{
"alg": "ES256",
"kid": "ABC123DEFG"
}
{
"iss": "DEF123GHIJ",
"iat": 1437179036
}
For additional information along with list of available libraries for generating signed JSON web tokens, refer to https://jwt.io
This is a swift library to sign your JSON Web Token (or JWT) : kylef/JSONWebToken.swift
Note: Only providers tokens signed with ES256 algorithm are supported
by APNs. Unsecured JWT or JWT signed with other algorithms will be
rejected with a response indicating an Invalid Provider Token.
SOURCE : APPLE : Provider Authentication Tokens
WWDC 2016 - Session 724 : Token Based Authentication
PS:
The biggest difference is that The Key Way will not be expired than Certificate will be expired after one year.
For iOS applications that require push notifications, it must first request the user for permission to do so. After that, a device token is generated and with this, the remote server may communicate to the user through this token.
I have read a similar question here and I do not feel it is enough. The picture below is a trusted certificate, it allows me to view all traffic that happens on this device.
With Fiddler2 as well as CertMaker, I can sniff HTTPS traffic, which means the client can probably know what data they are sending, and to where.
My question is, knowing that SSL is not secure from protecting my clients from seeing what I send to the remote server, should I simply encypt with a secret key found within my application?
Such as encrypt("device_token","secretkey_a0a0a0a") (pretend this is Objective-C)?
Couldn't someone just find that key within my application? I also read this question, and it seems that it would be possible to get back the secret key.
My plan for this goes like this:
Within the iOS application, Generate a random string named activate.
Encrypt (not hash), the token by the random string and a secret key that I only know. (secretkey_a0a0a0)
Send the encrypted string along with the generated randomly generated string (active).
Within serverside, I check if I can decrypt a valid token from using the active and my secret key.
I save the token in my database if it is valid.
This prevents people from random entering tokens yes, however, secretkey_a0a0a0 is a string literal. It's very possible to get this within the application binary itself.
My question is, how do I protect this secret key? The answer can also be, how can I prevent people from sending invalid tokens to my server as well.
I have heard of encryption, but doesn't that only apply to resource files?
How should I approach this?
If you do SSL-Pinning ( AFNetworking has this implemented ) you won't be able to (in a reasonable timeframe) sniff the https traffic between the client and server if you don't have the servers private key.
If your fear is that man in the middle can steal your token and send fake push notifications to users of your application, be sure that this cant happend. Since requests to apple apn servers must be signed with pem file, the main concern should be how to keep certificate file secured, and not apn token. If you want to prevent writing invalid tokens in your database then you should implement some CRC or odd/even bit mechanism.
You might want to check the security section in the Push Notifications Guide, in particular the section titled "Token Generation and Dispersal".
The device token is generated by the device connecting through the Apple's APNS. My guess (they don't say in the docs) is that it's unique for a given app identifier.
The APNS then will probably match those identifiers with the pem certificate you use to communicate with it thus validating that the push notifications are actually originating from your app.
Encrypting the device token seems overkill in this scenario.
To prevent someone maliciously spamming your server with tokens, I would hash the token when a secret key and send both the token and the hash to the server. You can then hash the token again on the server, with your secret key, and check that the request is valid.
I've been handed an iOS app codebase, which I'd like to distribute via the existing Enterprise certificate used by the prior developer.
After importing the provided .mobileprovision file, I'm (not unsurprisingly) getting the "Valid signing identity not found" error. Specifically when building:
The identity '[name]' doesn't match any valid, non-expired certificate/private key pair in your keychains
I was given the original CertificateSigningRequest.certSigningRequest file, a .p12 file, and the .cer file. I was not given the password to the .p12 file.
Is it possible to rebuild what I need from the CertificateSigningRequest.certSigningRequest without the .p12 file's password? I can likely get the .p12 password, but not in a timely manner.
Thanks!
I recognize that you've solved your issue by getting the password for the .p12 file, but I thought I'd shine a little light on what lives in each of those files you mentioned for the benefit of anyone running across the question in the future.
To answer the main question in this question: Can I rebuild what I need from the CertificateSigningRequest.certSigningRequest file?
Regrettably the answer is a very solid 'No'. The root cause of this is the very heart of Public Key Infrastructure (PKI), a set of management technologies, people, and practices dealing with the creation, verification, use, and revocation of digital certificates. Central to PKI is the notion of a public-private key pair. The 'Public' key is the one you share widely, anyone may have a copy of it and anyone wishing to validate messages signed by a digital certificate will require access to this key. The 'Private' key is the linked key that only you (or more accurately, your machine) knows and uses when signing messages. It is this signature that is verified via use of the 'Public' key that is shared widely authenticating that the message is in fact authentic.
When we are constructing development or distribution certificates, we are inherently asking Keychain Access, openssl, or your preferred SSL toolchain to create a public-private key pair. The public key goes into the CertificateSigningRequest file along with the other 'Subject' fields like name and email address and we ship this file off to Apple. That file primarily tells Apple what Public Key they can use to validate your app signature -- it does not give them a copy of your Private Key after all, if others had your private key they'd be able to codesign as you effectively destroying the notion of accountability on the iOS platform (ex. This App's signature checks out as valid, but I still don't know if it was actually signed by a developer I trust...). At no point in time, is your Private Key transmitted to Apple or the Developer Portal; it resides quite happily in your Keychain until such a time as 1) The certificate expires, 2) you actively revoke the certificate from the Developer Portal, or 3) you accidentally (or intentionally) delete the keypair from Keychain.
So what lives in each of these files?
CertificateSigningRequest.certSigningRequest - This contains a copy of the Public Key from the Public-Private keypair you generated locally, plus some additional required subject information required by the Certificate Signing Request format. Apple disregards this additional information and uses the name and email address they have on file for your Developer Account when constructing your certificate.
.p12 - This is a PKCS#12 formatted file containing a copy of the Apple-issued Certificate (which itself contains the Public Key) and a copy of the linked Private Key. This data is encrypted to prevent unauthenticated access and thus requires a password to decrypt.
.cer - This is the Apple-issued Certificate that contains the Public key portion of the key pair. This certificate is used by Apple to validate that Apps you submit are not tampered with while in transit to the App Store review team:
You sign your app using the Private key that only you know and upload the signed binary to Apple.
Apple then validate the signature using the public key that you've already shared with them.
If the math works out, then the App hasn't been tampered with and you are good to go.
If the math doesn't work out, either the app was tampered with, or (much more likely) the certificate was revoked or regenerated and the app was signed with an old or incorrect key pair.
As you can see, the only places the Private Key resides is in the original developer's keychain as well as the encrypted .p12 file. Consistent with both your comment and flup's comment, you either have to get the password to that .p12 file or look into breaking through the encryption.
Regardless, good to hear that you were able to get the password from the original developer. Let me know if you have any followup questions.
Okay, so look at this diagram.
There are two little boxes, that signify how a given profile should be signed.
In Phase 2, step 1, it says "Apple issued certificate", but it doesn't say which apple issued certificate (they issue more than one). I have tried my developer certificate and the MDM (APNS) certificate. It wasn't one of those. Is there a third magic certificate I somehow need (and how do I get it)?
In Phase 3, step 2, it says "Identity certificate", but again it's a little sketchy on the details. The only identity certificate I know of is installed on the device, using the device's private key, how is the server supposed to use that to sign a profile?
The only way I've gotten this to work, is by creating my own self-signed certificate, and pre-installing it on the device. Obviously this is not an elegant or particularly secure way to do things.
Follow up questions
My server certificate is issued by "DigiCert High Assurance EV Root CA" and is on the list: http://support.apple.com/kb/ht5012, but iOS 6 devices consider it "untrusted" when signing profiles, but just fine for SSL which is wierd. iOS 5 devices are fine though. Any idea why?
I don't really understand the encryption bit either. From the MDM documentation: "Each device must have a unique client identity certificate. You may deliver these certificates as PKCS#12 containers, or via SCEP. Using SCEP is recommended because the protocol ensures that the private key for the identity exists only on the device."
While I agree it is ultimately more secure that only the device itself knows its private key, it's somewhat problematic as a 2048-bit public key can only be used to encrypt about 100 bytes of data, which isn't enough for even the smallest possible payload.
Let me go over phase 2 and phase 3 first
In the Phase 2, step 1, iOS device will send to a server response which is signed by device certificate/key (each device comes with preinstalled certificate/key which is different for each device). These on device certificates/keys are issued by Apple.
On the server side, you should verify it using Apple Root Cetificate.
In the Phase 2, step 1-3 your profile service will send a SCEP request. This SCEP request contains information to let device know to which SCEP server it should talk. This SCEP server is your server. So, a device will talk to this SCEP server and will request new identity certificate from it.
In Phase 3, step 2 device response will be signed with certificate/key of this identity certificate. And now you should verify it with your Certificate authority root certificate. (One more note SCEP server in Phase 2 is kind-of proxy to yours Certificate authority)
And now answering your questions "MDM profile signining, which certificate to use?"
MDM profile could be encrypted and/or signed.
If you want to encrypt it, you encrypt it using identity certificate associated with this device. So, device which has a key for this identity, so it can decrypt it.
If you want to sign it, you sign with your server key. Device should have a server certificate installed, so it can verify signature.
BTW. On this subject. One thing which isn't shown on this diagram, but usually is requited - first step (before whole this enrollment) is usually installation of server certificate (for future profile signature verification). Potentially, you can skip this step if your server certificate is issued by well known CA (as example Verisign or something like that).
Let me know, if you have any followup questions. It took me a while to understand whole this OTA/MDM enrollment.
Update 1
I don't know why iOS 6 treat your certificate as untrusted for signing. I didn't work with certificates which were signed by well known CA's.
I have only one guess. It could be that between iOS 5 and iOS 6 they changed something regarding key chain. Generally speaking, each app has it's own key chain. And all well known certificates, I believe should be stored in Mobile Safari keychain. It could be that MDM/Preferences shared this keychain with MobileSafari in iOS 6 and now they don't share it.
In such case, you will have to install this "DigiCert High Assurance EV Root CA" through a profile (to put it in correct keychain). However, it's wild guess.
Regarding encryption. First of all, you are right, if each device has it's own private key, it's way more secure. In such case, if anybody will steal a profile they won't be able to decrypt it (because only a device has a private key to do so). This is especially critically, if you are sending down profiles which are sensitive (as example, email account with both user name and password).
Very high level introduction into cryptography:
Any key (with any length) can encrypt data of any length. All encryption algorithms are designed that way that you can use the same key to encrypt any amount of data.
Asymmetric algorithms (like RSA) rarely used to encrypt data directly. In most cases, this algorithm is used to encrypt a key for symmetric algorithm (as example AES) and all following encryption/decryption is done using AES. There are two reasons for that: performance (AES is faster then RSA) and resources (AES is less resource hungry than RSA).
So, as result, if you need to encrypt profile you use PKCS7, which is internally uses RSA, AES (or other algorithms). Usually, you have a library to do this (OpenSSL or BouncyCastle). So, you don't have to figure out all these complexities.
BTW. If you have questions which aren't good fit for SO, you are welcome to contact me directly (my contact info in my profile).