I'm trying to generate a JWK from an EC public key that I want to send to my server. The issue I'm running into is that the base64 URL Encoded string representation of the x and y coordinates from the external representation of the EC public key seems to be invalid on the server and I get an error saying that the points are not on the curve.
Here's what I'm doing to generate the base64 URL encoded string from the x and y coords:
The byte order on iOS seems to be little-endian (byteOrder above is 1 == CFByteOrderLittleEndian) and the JWK RFC 7517 Appendix A says it expects the big-endian values for x and y. So, I tried swapping the bytes to create a big-endian representation of the data. But, neither works.
I'd appreciate any insight or help with this.
Here's what I'm doing to generate the base64 URL encoded string from the x and y coords:
Code Block objc + (NSArray<NSString *> *)getBase64EncodedCoordinatesFromECPublicKey:(SecKeyRef)publicKey error:(out NSError **)error{ CFErrorRef copyPublicKeyError = NULL; NSData* keyData = (NSData*)CFBridgingRelease( SecKeyCopyExternalRepresentation(publicKey, ©PublicKeyError) ); if (!keyData) { NSError *err = CFBridgingRelease(copyPublicKeyError); NSLog(@"%@", err); return nil; } NSString *xbytes; NSString *ybytes; CFByteOrder byteOrder = CFByteOrderGetCurrent(); NSLog(@"%ld",(long)byteOrder); NSData *xData = [keyData subdataWithRange:NSMakeRange(1, 32)]; NSData *xDataRev = [self reverseData:xData]; NSString *xEncoded = [OIDTokenUtilities encodeBase64urlNoPadding:xDataRev]; NSData *yData = [keyData subdataWithRange:NSMakeRange(33, 32)]; NSData *yDataRev = [self reverseData:yData]; NSString *yEncoded = [OIDTokenUtilities encodeBase64urlNoPadding:yDataRev]; xbytes = [OIDTokenUtilities encodeBase64urlNoPadding:xData]; ybytes = [OIDTokenUtilities encodeBase64urlNoPadding:yData]; NSArray *coordinates = @[xbytes, ybytes]; return coordinates; }
The byte order on iOS seems to be little-endian (byteOrder above is 1 == CFByteOrderLittleEndian) and the JWK RFC 7517 Appendix A says it expects the big-endian values for x and y. So, I tried swapping the bytes to create a big-endian representation of the data. But, neither works.
I'd appreciate any insight or help with this.
So, what we found was the following -
The X and Y values of the EC public key are treated as signed base64 encoded values by our server. So, while extracting the x (or y) byte components from public key, if the most significant byte is greater than 0x7f, the x (or y) value will be treated as negative values. However, our server expects the the X and Y values to be positive for a valid EC key.
So the fix was to check If the x or y component of the public key has a first byte value greater than 0x7f, then add an extra 0x00 byte, to make the value positive before encoding.
The code is:
The X and Y values of the EC public key are treated as signed base64 encoded values by our server. So, while extracting the x (or y) byte components from public key, if the most significant byte is greater than 0x7f, the x (or y) value will be treated as negative values. However, our server expects the the X and Y values to be positive for a valid EC key.
So the fix was to check If the x or y component of the public key has a first byte value greater than 0x7f, then add an extra 0x00 byte, to make the value positive before encoding.
The code is:
Code Block objC + (NSArray<NSString *> *)getBase64EncodedCoordinatesFromECPublicKey:(SecKeyRef)publicKey error:(out NSError **)error{ CFErrorRef copyPublicKeyError = NULL; NSData* keyData = (NSData*)CFBridgingRelease( SecKeyCopyExternalRepresentation(publicKey, ©PublicKeyError) ); if (!keyData) { NSError *err = CFBridgingRelease(copyPublicKeyError); NSLog(@"%@", err); return nil; } NSString *xCoordinate; NSString *yCoordinate; NSData *xDataRaw = [keyData subdataWithRange:NSMakeRange(1, keyData.length/2)]; NSData *yDataRaw = [keyData subdataWithRange:NSMakeRange((keyData.length / 2)+1, keyData.length/2)]; uint8_t zeroByte = 0x00; const unsigned char* xBytes = [xDataRaw bytes]; const unsigned char* yBytes = [yDataRaw bytes]; int mostSignificantByte_x = xBytes[0]; int mostSignificantByte_y = yBytes[0]; if (mostSignificantByte_x > 127) { NSMutableData *xData = [[NSMutableData alloc] initWithBytes:&zeroByte length:1]; [xData appendData:xDataRaw]; xCoordinate = [self encodeBase64urlWithPadding:xData]; } else { xCoordinate = [self encodeBase64urlWithPadding:xDataRaw]; } if (mostSignificantByte_y > 127) { NSMutableData *yData = [[NSMutableData alloc] initWithBytes:&zeroByte length:1]; [yData appendData:yDataRaw]; yCoordinate = [self encodeBase64urlWithPadding:yData]; } else { yCoordinate = [self encodeBase64urlWithPadding:yDataRaw]; } NSArray *coordinates = @[xCoordinate, yCoordinate]; return coordinates; }