Open JanC opened 7 years ago
Hi! Sure!
@JanC do you have samples of ec256 keys (private/public) (PEM format)? It seems that found samples are broken :/
Hi, according to OpenSSL, I think this is what we need
openssl ecparam -genkey -name prime256v1 -out ec256-private.pem
openssl ec -in ec256-private.pem -pubout -out ec256-public.pem
If you need a PKCS8 format you can convert it from PEM using
openssl pkcs8 -topk8 -nocrypt -in ec256-private.pem -out ec256-private.p8
I have some real keys from Apple's push notification service so I'd be happy to try to see if it works
@JanC I try to retrieve key from PEM file, but it always said
Error Domain=NSOSStatusErrorDomain Code=-50 "EC public key creation from data failed" (paramErr: error in user parameter list) UserInfo={NSDescription=EC public key creation from data failed}
The error message indifferent for both keys (public/private). I could make a PR with this example. It would be great to have a response from you about problem. ( Google doesn't help or my skills in googling are too weak. )
sure, I can help to investigate
On 15 Feb 2017, at 12:48, Dmitry notifications@github.com wrote:
I try to retrieve key from PEM file, but it always said
Error Domain=NSOSStatusErrorDomain Code=-50 "EC public key creation from data failed" (paramErr: error in user parameter list) UserInfo={NSDescription=EC public key creation from data failed} The error message indifferent for both keys. I could make a PR with this example. It would be great to have a response from you about problem. ( Google doesn't help or my skills in googling are too weak. )
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.
@JanC, please, have a look at latest master ( PR will be merged as soon as possible )
Hi, I'm running the same problem when reading the EC file. Looks like this is related: https://forums.developer.apple.com/thread/72445
@JanC, hah, you revealed me at developer forums. :) Well, I am right that header should be stripped from key. The main question is: how to strip it in a right way? (Some parts were already written). Maybe we need some convertion code that could cut off all header related stuff from key for Security API.
:)
Probably, I'm trying to found out the difference between a key generated using openssl and a key ec key using SecKeyGeneratePair
NSDictionary * keyPairAttr = @{(id)kSecAttrKeySizeInBits : @(256),
(id)kSecAttrKeyType : (id)kSecAttrKeyTypeEC
};
SecKeyRef publicKey;
SecKeyRef privateKey;
CFErrorRef cfError = NULL;
OSStatus status = SecKeyGeneratePair((CFDictionaryRef)keyPairAttr, &publicKey, &privateKey);
if(status != noErr) {
NSLog(@"status: %@", @(status));
}
NSData* data = (__bridge_transfer NSData*) SecKeyCopyExternalRepresentation(privateKey, &cfError);
if(cfError != NULL) {
NSLog(@"error: %@", (__bridge NSError *)cfError);
} else {
[data writeToFile:@"/tmp/private.asn1" atomically:YES];
}
cfError = NULL;
data = (__bridge_transfer NSData*) SecKeyCopyExternalRepresentation(publicKey, &cfError);
if(cfError != NULL) {
NSLog(@"error: %@", (__bridge NSError *)cfError);
} else {
[data writeToFile:@"/tmp/public.asn1" atomically:YES];
}
and the ASN.1 structure is indeed just:
./dumpasn1 private.asn1
0 28: OCTET STRING
: 59 5D 75 44 0D A8 4F C6 67 62 0A CD 96 09 83 6F
: E7 CE 8C 12 58 15 8E 17 62 31 C5 31
Warning: Further data follows ASN.1 data at position 30.
1 warning, 0 errors.
@JanC well, we need to strip asn1 header from openssl key, am I correct? But why apple keys doesn't work well?
well, we need to strip asn1 header from openssl key, am I correct?
I believe that could be the solution
But why apple keys doesn't work well?
Which ones? The one generated via SecKeyGeneratePair
or the one I've got from Apple push notification service?
The real push notification private key I've got from Apple also has the EC headers:
cat APNsAuthKey.p8 | grep -v PRIVATE | base64 -D > APNsAuthKey.asn1
./dumpasn1 APNsAuthKey.asn1
0 147: SEQUENCE {
3 1: INTEGER 0
6 19: SEQUENCE {
8 7: OBJECT IDENTIFIER '1 2 840 10045 2 1'
17 8: OBJECT IDENTIFIER '1 2 840 10045 3 1 7'
: }
27 121: OCTET STRING, encapsulates {
29 119: SEQUENCE {
31 1: INTEGER 1
34 32: OCTET STRING
: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
68 10: [0] {
70 8: OBJECT IDENTIFIER '1 2 840 10045 3 1 7'
: }
80 68: [1] {
82 66: BIT STRING
: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
: XX
: }
: }
: }
: }
@JanC oh-oh-oh, I believe Apple has functions that cut headers from keys. So, the path is:
keyByRemovingPublicHeader
)keyByRemovingPublicHeader
)and ? [ ] add key retrieve functions
whose result would then feed the existing SecKeyCreateWithData
?
@JanC If I am correct, PEM files contain several structures/headers ( right? ) for different formats. However, we need to strip specific headers from, for example, public rsa key ( already implemented ).
It finds ASN.1 first, then it checks X509 header sequence and skip it if needed.
After cleaning data from all necessary ( or not ) structures/headers we get the key data for SecKeyCreateWithData
Your suggestion could be a candy code for all this stuff :)
yes that's how I see it as well :)
Btw "PEM" is just a way to encode and contain any ASN.1 key material (certificate, keys ..). So a PEM contains a header such as -----BEGIN PRIVATE KEY-----
, -----BEGIN EC PRIVATE KEY-----
or -----BEGIN CERTIFICATE-----
... and the base64 encoded ASN.1 structure. The ASN.1 structure contains itself the entire key or certificate material that is defined by its ASN.1 attributes. Be it a X.509 certificate or a EC keys.
It appears that we have to read the EC ASN.1 structure and get rid of those EC headers (I honestly ignore what a EC key is composed of)
@JanC "I honestly ignore what a EC key is composed of" that's why we have troubles with SecKeyCreateWithData
( Apple engineers ignore it too )
:) Btw this is also useful tool since it puts names on the ASN.1 object identifiers https://lapo.it/asn1js/
@JanC could you add test data for EC keys? I would like to add support for it even if only latest OS could use it.
Hi,
I downloaded 3.0.0-beta.7, I don't see support for es256.
Will this be ready and when for 3.0.0 release?
thx
@lolgear, sorry I missed your comment. Yes I will upload some keys that I use
@flash23 still in progress. I suppose that ES256 is available only for macOS 10.13 and iOS 11 ( system API )
Hi @lolgear @JanC
I made ES256 with openssl I verified it and it works. I am in hurry so I cant wait ;)
But maybe you can use the code for your lib. I will switch to your lib when u implement it
-(NSData*) SHA256withECDSA_JWT:(NSString*)privateKeyPEM and:(NSData*)dataToSign
{
char *pemC = (char*)[privateKeyPEM cStringUsingEncoding:NSASCIIStringEncoding];
EC_KEY *privEC;
privEC = readPrivatePemToEC(pemC);
NSMutableData *digest = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(dataToSign.bytes, (unsigned int)dataToSign.length, digest.mutableBytes);
ECDSA_SIG *signature;
signature = ECDSA_do_sign(digest.bytes, (unsigned int)digest.length, privEC);
int signatureLength = i2d_ECDSA_SIG(signature, NULL);
NSMutableData *derSignature = [[NSMutableData alloc] initWithLength:signatureLength];
uint8_t *signatureBytes2 = derSignature.mutableBytes;
i2d_ECDSA_SIG(signature, &signatureBytes2);
const unsigned char* signatureBytes = [derSignature bytes];
if (derSignature.length < 8 || signatureBytes[0] != (uint8_t)0x30) {
//assert!
}
int offset;
if (signatureBytes[1] > 0) {
offset = 2;
} else if (signatureBytes[1] == (uint8_t) 0x81) {
offset = 3;
} else {
//assert!
}
int rLength = signatureBytes[offset + 1];
int i = rLength;
while ((i > 0) && (signatureBytes[(offset + 2 + rLength) - i] == 0))
i--;
int sLength = signatureBytes[offset + 2 + rLength + 1];
int j = sLength;
while ((j > 0) && (signatureBytes[(offset + 2 + rLength + 2 + sLength) - j] == 0))
j--;
int rawLen = MAX(i, j);
rawLen = MAX(rawLen, 64 / 2);
if ((signatureBytes[offset - 1] & 0xff) != derSignature.length - offset
|| (signatureBytes[offset - 1] & 0xff) != 2 + rLength + 2 + sLength
|| signatureBytes[offset] != 2
|| signatureBytes[offset + 2 + rLength] != 2) {
//assert!
}
NSMutableData *concatSignature = [[NSMutableData alloc] initWithCapacity:2 * rawLen];
[concatSignature replaceBytesInRange:NSMakeRange(rawLen - i, i) withBytes:(signatureBytes + (offset + 2 + rLength) - i)];
[concatSignature replaceBytesInRange:NSMakeRange(2 * rawLen - j, j) withBytes:(signatureBytes + (offset + 2 + rLength + 2 + sLength) - j)];
EC_KEY_free(privEC);
ECDSA_SIG_free(signature);
return concatSignature;
}
NSMutableDictionary *dicPayload = [[NSMutableDictionary alloc] init];
//dicPayload all useful info goes in here
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dicPayload options:0 error:&error];
NSString *payloadBase64 = [jsonData base64EncodedStringWithOptions:0];
payloadBase64 = [payloadBase64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
payloadBase64 = [payloadBase64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
payloadBase64 = [payloadBase64 stringByReplacingOccurrencesOfString:@"=" withString:@""];
NSMutableDictionary *dicHeader = [[NSMutableDictionary alloc] init];
[dicHeader setObject:@"ES256" forKey:@"alg"];
jsonData = [NSJSONSerialization dataWithJSONObject:dicHeader options:0 error:&error];
NSString *headerBase64 = [jsonData base64EncodedStringWithOptions:0];
headerBase64 = [headerBase64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
headerBase64 = [headerBase64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
headerBase64 = [headerBase64 stringByReplacingOccurrencesOfString:@"=" withString:@""];
NSString *toSignStr = [NSString stringWithFormat:@"%@.%@",headerBase64,payloadBase64];
NSData *toSignDat = [toSignStr dataUsingEncoding:NSUTF8StringEncoding];
NSData *signedHash = [self SHA256withECDSA_JWT:privateKeyPEM and:toSignDat];
NSString *signedHashBase64 = [signedHash base64EncodedStringWithOptions:0];
signedHashBase64 = [signedHashBase64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
signedHashBase64 = [signedHashBase64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
signedHashBase64 = [signedHashBase64 stringByReplacingOccurrencesOfString:@"=" withString:@""];
JWTtoken = [NSString stringWithFormat:@"%@.%@.%@",headerBase64,payloadBase64,signedHashBase64];
@flash23 thanks
@lolgear, here is a key directly from Apple in the format they deliver it
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgaPxM+EysxcazERan
WfD/SwnB6HeAgfMLyVFBMXsrxU2gCgYIKoZIzj0DAQehRANCAASl233aKSR+l3e/
7qF80JQ2YG6PqEt3HYRyUPbfBQbaJNPfPDVsyrxo1wjYnYv7edt82vkF70pwDoGA
yCIXB1Jj
-----END PRIVATE KEY-----
@JanC Nice! What about Public counterpart?
No, Apple gives us only the private key and they keep the public one for verification of the signature.
@JanC Hoh, I would like to check both verification and signing in tests :)
Hi @flash23 could you provide a full example of your code or at least readPrivatePemToEC
implementation? It'd be very helpful.
@JanC Hi! Could you test latest master?
Hi
I tried with the latest master (3.0.0-beta.11
) and the following snippet still gives me the error
Error Domain=NSOSStatusErrorDomain Code=-50 "EC public key creation from data failed" UserInfo={NSDescription=EC public key creation from data failed}
Is it actually the correct way to sign?
NSString *algorithmName = @"ES256";
NSString *privateKey = @"-----BEGIN PRIVATE KEY-----\n"
"MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgaPxM+EysxcazERan\n"
"WfD/SwnB6HeAgfMLyVFBMXsrxU2gCgYIKoZIzj0DAQehRANCAASl233aKSR+l3e/\n"
"7qF80JQ2YG6PqEt3HYRyUPbfBQbaJNPfPDVsyrxo1wjYnYv7edt82vkF70pwDoGA\n"
"yCIXB1Jj\n"
"-----END PRIVATE KEY-----";
id <JWTAlgorithmDataHolderProtocol> signDataHolder = [JWTAlgorithmRSFamilyDataHolder new]
.keyExtractorType([JWTCryptoKeyExtractor privateKeyWithPEMBase64].type)
.algorithmName(algorithmName)
.secret(privateKey);
// sign
NSDictionary *payloadDictionary = @{ @"hello": @"world" };
JWTCodingBuilder *signBuilder = [JWTEncodingBuilder encodePayload:payloadDictionary].addHolder(signDataHolder);
JWTCodingResultType *signResult = signBuilder.result;
NSString *token = nil;
if (signResult.successResult) {
NSLog(@"%@ success: %@", self.debugDescription, signResult.successResult.encoded);
token = signResult.successResult.encoded;
} else {
NSLog(@"Signing failed: %@", signResult.errorResult.error);
}
@JanC If you want to sign Apple's JWT on iOS/macOS, you could also try CupertinoJWT, which was pre-released recently. I've wrote it because I couldn't find a solution for creating JWT used for Apple's server APIs on iOS/macOS.
@ethanhuang13 thanks, that works fine
Hi @lolgear @JanC
I made ES256 with openssl I verified it and it works. I am in hurry so I cant wait ;)
But maybe you can use the code for your lib. I will switch to your lib when u implement it
-(NSData*) SHA256withECDSA_JWT:(NSString*)privateKeyPEM and:(NSData*)dataToSign { char *pemC = (char*)[privateKeyPEM cStringUsingEncoding:NSASCIIStringEncoding]; EC_KEY *privEC; privEC = readPrivatePemToEC(pemC); NSMutableData *digest = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; CC_SHA256(dataToSign.bytes, (unsigned int)dataToSign.length, digest.mutableBytes); ECDSA_SIG *signature; signature = ECDSA_do_sign(digest.bytes, (unsigned int)digest.length, privEC); int signatureLength = i2d_ECDSA_SIG(signature, NULL); NSMutableData *derSignature = [[NSMutableData alloc] initWithLength:signatureLength]; uint8_t *signatureBytes2 = derSignature.mutableBytes; i2d_ECDSA_SIG(signature, &signatureBytes2); const unsigned char* signatureBytes = [derSignature bytes]; if (derSignature.length < 8 || signatureBytes[0] != (uint8_t)0x30) { //assert! } int offset; if (signatureBytes[1] > 0) { offset = 2; } else if (signatureBytes[1] == (uint8_t) 0x81) { offset = 3; } else { //assert! } int rLength = signatureBytes[offset + 1]; int i = rLength; while ((i > 0) && (signatureBytes[(offset + 2 + rLength) - i] == 0)) i--; int sLength = signatureBytes[offset + 2 + rLength + 1]; int j = sLength; while ((j > 0) && (signatureBytes[(offset + 2 + rLength + 2 + sLength) - j] == 0)) j--; int rawLen = MAX(i, j); rawLen = MAX(rawLen, 64 / 2); if ((signatureBytes[offset - 1] & 0xff) != derSignature.length - offset || (signatureBytes[offset - 1] & 0xff) != 2 + rLength + 2 + sLength || signatureBytes[offset] != 2 || signatureBytes[offset + 2 + rLength] != 2) { //assert! } NSMutableData *concatSignature = [[NSMutableData alloc] initWithCapacity:2 * rawLen]; [concatSignature replaceBytesInRange:NSMakeRange(rawLen - i, i) withBytes:(signatureBytes + (offset + 2 + rLength) - i)]; [concatSignature replaceBytesInRange:NSMakeRange(2 * rawLen - j, j) withBytes:(signatureBytes + (offset + 2 + rLength + 2 + sLength) - j)]; EC_KEY_free(privEC); ECDSA_SIG_free(signature); return concatSignature; }
NSMutableDictionary *dicPayload = [[NSMutableDictionary alloc] init]; //dicPayload all useful info goes in here NSError *error; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dicPayload options:0 error:&error]; NSString *payloadBase64 = [jsonData base64EncodedStringWithOptions:0]; payloadBase64 = [payloadBase64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; payloadBase64 = [payloadBase64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; payloadBase64 = [payloadBase64 stringByReplacingOccurrencesOfString:@"=" withString:@""]; NSMutableDictionary *dicHeader = [[NSMutableDictionary alloc] init]; [dicHeader setObject:@"ES256" forKey:@"alg"]; jsonData = [NSJSONSerialization dataWithJSONObject:dicHeader options:0 error:&error]; NSString *headerBase64 = [jsonData base64EncodedStringWithOptions:0]; headerBase64 = [headerBase64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; headerBase64 = [headerBase64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; headerBase64 = [headerBase64 stringByReplacingOccurrencesOfString:@"=" withString:@""]; NSString *toSignStr = [NSString stringWithFormat:@"%@.%@",headerBase64,payloadBase64]; NSData *toSignDat = [toSignStr dataUsingEncoding:NSUTF8StringEncoding]; NSData *signedHash = [self SHA256withECDSA_JWT:privateKeyPEM and:toSignDat]; NSString *signedHashBase64 = [signedHash base64EncodedStringWithOptions:0]; signedHashBase64 = [signedHashBase64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; signedHashBase64 = [signedHashBase64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; signedHashBase64 = [signedHashBase64 stringByReplacingOccurrencesOfString:@"=" withString:@""]; JWTtoken = [NSString stringWithFormat:@"%@.%@.%@",headerBase64,payloadBase64,signedHashBase64];
i need demo, can you help me?i email, fengsh998@163.com
@fengsh998
You can use Tests
project and Certificates.xcasset
inside it.
Hi, this is not an issue but a question. Is there a plan to support the Elliptic Curve Digital Signature Algorithm
ES256
?cheers, Jan