3

we are trying to add Sign In with Apple in our ios app. When the client works fine, our backend being written in Java we are not able to decode the public key of Apple. When you hit the URL https://appleid.apple.com/auth/keys it gives you the public key. But when I try to make a PublicKey object is Java it does not recognise the n and e value from there. Are those Base64 encoded?

When I try to decode the n and e value in Base64 it gives me illegal character 2d. The reason I am suspecting it to be base64 is in a NodeJS package (https://www.npmjs.com/package/node-rsa) they are decoding the n and e value by base64. But the thing is the exponent value(e) is AQAB which can never be base64. How can I create a PublicKey object from this?

The code I am using is:

HttpResponse<String> responsePublicKeyApple =  Unirest.get("https://appleid.apple.com/auth/keys").asString();

ApplePublicKeyResponse applePublicKeyResponse = new Gson().fromJson(responsePublicKeyApple.getBody(),ApplePublicKeyResponse.class);

System.out.println("N: "+applePublicKeyResponse.getKeys().get(0).getN());
System.out.println("E: "+applePublicKeyResponse.getKeys().get(0).getE());

byte[] decodedBytesE = Base64.getDecoder().decode(applePublicKeyResponse.getKeys().get(0).getE());
String decodedE = new String(decodedBytesE);

System.out.println("decode E: "+decodedE);

byte[] decodedBytesN = Base64.getDecoder().decode(applePublicKeyResponse.getKeys().get(0).getN());
String decodedN = new String(decodedBytesN);

System.out.println("decode N: "+decodedN);

BigInteger bigIntegerN = new BigInteger(decodedN,16);
BigInteger bigIntegerE = new BigInteger(decodedE,16);

RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(bigIntegerN,bigIntegerE);
KeyFactory keyFactory = KeyFactory.getInstance(SignatureAlgorithm.RS256.getValue());
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

It is failing in decoding part of the n and e value. Another thing is Apple response is saying they used RS256 algorithm to sign the token but when I try to do

KeyFactory keyFactory = KeyFactory.getInstance(SignatureAlgorithm.RS256.getValue());

it says RS256 keyfactory is not available.

How can I fix this two problems ? Please help.

Michał Krzywański
  • 15,659
  • 4
  • 36
  • 63
Anuran Barman
  • 1,556
  • 2
  • 16
  • 31

1 Answers1

6

Actually those N and E values are encoded using base64url as explained in RFC7518. This code will show you how to do what you request. I used Jackson to read the JSON that you provided :

String json = Files.lines(Paths.get("src/main/resources/test.json")).collect(Collectors.joining());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

// here is the parsing of PublicKey
ApplePublicKeyResponse applePublicKeyResponse = objectMapper.readValue(json, ApplePublicKeyResponse.class);
        
Key key = applePublicKeyResponse.getKeys().get(0);

byte[] nBytes = Base64.getUrlDecoder().decode(key.getN());
byte[] eBytes = Base64.getUrlDecoder().decode(key.getE());

BigInteger n = new BigInteger(1, nBytes);
BigInteger e = new BigInteger(1, eBytes);

RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n,e);
KeyFactory keyFactory = KeyFactory.getInstance(key.getKty()); //kty will be "RSA"
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

Also as noted the key.getKty() will return "RSA". So you should pass this value to KeyFactory.getInstance as you want to parse RSA key and RS256 is name of signature algorithm which uses RSA and SHA-256 hash.

And I used the constructor of BigInteger that takes signum and raw bytes. Signum is set to 1 to get positive value.

Community
  • 1
  • 1
Michał Krzywański
  • 15,659
  • 4
  • 36
  • 63