EC Signature verification failure

Hello,


The problem I have is that an EC generated signature using iOS/OSX does verify on multiple places: jwt.io and java code using oauth jwt library. I can sign and verify data (jws tokens) on iOS devices and those signatures verify elsewere. The inverse however is not true. I am aware of differences in generated signatures (Apple's functions output DER encoded values) and verified that using an apple issued EC key (Apple Music service allows queries using signed jwt tokens) but the results are exactly the same.


I use the following piece of code:



-(BOOL)signJWT {

/*

Private key taken from jwt.io generated page (I tested with apple issued ecc private key, same results):

-----BEGIN PRIVATE KEY-----

MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2

OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r

1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G

-----END PRIVATE KEY-----


=> openssl ec -in private.pem -text


read EC key

Private-Key: (256 bit)

priv:

7a:f6:73:2f:58:1d:00:5a:fc:f2:16:f6:38:5f:f6:

37:10:29:24:2c:c6:08:40:dd:7d:2a:7a:55:03:b7:

d2:1c

pub:

04:11:5b:3f:a3:9f:ae:41:b4:e3:2f:77:21:ca:72:

f8:c1:78:14:83:64:7d:ab:d5:14:f0:8e:66:12:8b:

d4:7f:ce:90:67:b9:0e:04:88:c9:c2:a9:f3:0f:5a:

26:6a:07:84:1d:6c:07:74:13:ba:07:e7:45:69:b9:

9d:4f:d3:ce:c6

ASN1 OID: prime256v1

NIST CURVE: P-256

writing EC key

-----BEGIN EC PRIVATE KEY-----

MHcCAQEEIHr2cy9YHQBa/PIW9jhf9jcQKSQsxghA3X0qelUDt9IcoAoGCCqGSM49

AwEHoUQDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJ

wqnzD1omageEHWwHdBO6B+dFabmdT9POxg==

-----END EC PRIVATE KEY-----


We now have to convert the key to suit Apple's security functions:


// Using https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_as_data?language=objc

// For an elliptic curve public key, the format follows the ANSI X9.63 standard using a byte string of 04 || X || Y

// where || denotes concatenation

// For an elliptic curve private key, the output is formatted as the public key concatenated with the big endian

// encoding of the secret scalar, or 04 || X || Y || K

// The key must be public, 256-bit ECDSA


And so the above information gives

04115b3fa39fae41b4e32f7721ca72f8c1781483647dabd514f08e66128bd47fce9067b90e0488c9c2a9f30f5a266a07841d6c077413ba07e74569b99d4fd3cec6 ||

7af6732f581d005afcf216f6385ff6371029242cc60840dd7d2a7a5503b7d21c


Base64 encoding it produces the contents of variable aPrivateKeyB64

*/

NSString *aPrivateKeyB64 = @"BBFbP6OfrkG04y93Icpy+MF4FINkfavVFPCOZhKL1H/OkGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU/TzsZ69nMvWB0AWvzyFvY4X/Y3ECkkLMYIQN19KnpVA7fSHA==";


NSData *aPrivateKeyX509 = [Base64 decode:aPrivateKeyB64];


NSDictionary* aPrivateKeyCreateOptions =

@{

(id)kSecAttrKeyType: (id)kSecAttrKeyTypeEC,

(id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,

(id)kSecAttrKeySizeInBits: @256,

};

CFErrorRef aCFError = NULL;

SecKeyRef aPrivateKey = SecKeyCreateWithData((__bridge CFDataRef)aPrivateKeyX509,

(__bridge CFDictionaryRef)aPrivateKeyCreateOptions, &aCFError);


if (!aPrivateKey) {

NSLog(@"SecKeyCreateWithData() fail!"); // Tell ARC to take ownership of aCFError

return NO;

}

// End log

SecKeyAlgorithm aAlgorithm = kSecKeyAlgorithmECDSASignatureMessageX962SHA256;


// Check that the key is suitable for verification

Boolean aCanVerify = SecKeyIsAlgorithmSupported(aPrivateKey, kSecKeyOperationTypeSign, aAlgorithm);

if (!aCanVerify)

{

CFRelease(aPrivateKey);

NSLog(@"Invalid key for signing algorithm");

return NO;

}

/* Data to be tested: JWT token without signature */

NSString *mData = @"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0";

NSData *aByteData = [mData dataUsingEncoding:NSUTF8StringEncoding];


/* Create signature */

CFDataRef aSignedDataBlob = SecKeyCreateSignature(aPrivateKey, aAlgorithm, (__bridge CFDataRef)aByteData, &aCFError);

if(aCFError){

NSError *err = CFBridgingRelease(aCFError); // ARC takes ownership

NSLog(@"SecKeyCreateSignature() failed: %@", err.description);

}

NSString *aEncodedEncryptedData = [Base64 encode:(__bridge NSData *)(aSignedDataBlob)];

NSLog(@"Printing signature data: %@", aEncodedEncryptedData);


NSLog(@"Extracting public key from provided private one ...");

// Extract public key from the private

SecKeyRef publicKey = SecKeyCopyPublicKey(aPrivateKey);

CFErrorRef error;

NSData* keyData = (NSData*)CFBridgingRelease(SecKeyCopyExternalRepresentation(publicKey, &error));

NSString* aB64KeyData = [Base64 encode:keyData];

NSLog(@"Public Key: %@", aB64KeyData);

// Verify the above signature: This always succeeds! The signature also veryfies in both

// java and jwt.io. Note: if you wish to verify the signature on jwt.io you have to extract the

// raw 64 bytes from ASN.1 encoding, see https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-ASN.1

// or use implementation of function from https://github.com/auth0/java-jwt/blob/master/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java

BOOL aIsVerified = SecKeyVerifySignature(publicKey, aAlgorithm,

(__bridge CFDataRef)aByteData, aSignedDataBlob, &aCFError);

if (!aIsVerified) {

CFRelease(publicKey);

NSLog(@"NOT VERIFIED!!!!!!"); // Tell ARC to take ownership of aCFError

if(aCFError) {

NSError *err = CFBridgingRelease(aCFError);

NSLog(@"Error is %@", err.description);

}

return NO;

}

NSLog(@"Success verifying signature!!!!!!");


// Android generated signature! This always fails to VALIDATE, despite validating on jwt.io and android.

/* Actual raw signature is 64 bytes long: gU5DQgVhqnlpOKbq3JvpirRu7l1LioIpuNq0SR4a5sCxCl_QB7Ww04nVJlDr_wMpGDAFgZLtz2NM3eEK1aICKw

* This is converted to ASN.1 form by using the function JOSE2DER from https://github.com/auth0/java-jwt/blob/master/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java and produces a valid DER value used as aExternalSig variable.

*/

NSString* aExternalSig = @"MEYCIQCBTkNCBWGqeWk4purcm+mKtG7uXUuKgim42rRJHhrmwAIhALEKX9AHtbDTidUmUOv/AykYMAWBku3PY0zd4QrVogIr";


aIsVerified = SecKeyVerifySignature(publicKey, aAlgorithm,

(__bridge CFDataRef)aByteData, (__bridge CFDataRef)aAnotherSigBytes, &aCFError);


if (!aIsVerified) {

CFRelease(publicKey);

if(aCFError) {

NSLog(@"NOT VERIFIED!!!!!!"); // Tell ARC to take ownership of aCFError

NSError *err = CFBridgingRelease(aCFError);

NSLog(@"Error is %@", err.description);

}

return NO;

}

// Code path never reached

NSLog(@"Success verifying external signature!!!!!!");

return YES;

}



I am at a loss as to why this is happening. Any help would be greatly appreciated.


EDIT: removed a comment with old signature.

EDIT2: Actual JWS token to be tested with Base64URL signature

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.gU5DQgVhqnlpOKbq3JvpirRu7l1LioIpuNq0SR4a5sCxCl_QB7Ww04nVJlDr_wMpGDAFgZLtz2NM3eEK1aICKw

eukariota asked the same question via another channel and I’ll respond there (I can’t post a URL because it’s not public).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
EC Signature verification failure
 
 
Q