Open hakonk opened 6 years ago
@hakonk Hi! Nice to hear that this library helps you :) Well, your method could work only in iOS 10.0 and later :)
Yes, somewhere in future these API methods could replace existing API methods.
For now we could add one more node in inheritance tree ( Look at implementation of RS algorithms ).
Yes, SecKeyCreateSignature
is a great replacement for inconvenience old API.
It will be in account.
If you have ready-to-use solution - feel free to PR :)
Thanks for replying so quickly! If I find a solution, I'll see if I can make a PR :)
@hakonk maybe you have test data for EC algorithms? I suppose that your investigation into Security framework could have this task (EC algorithms usage) under the hood.
@hakonk happy to announce, you could check latest master for that ;)
@lolgear Great! I'll have a look :)
@lolgear
After looking into the code base, I'm having some difficulties understanding how this can be used with the existing implementation. While signing the secret is provided as a string with RS256
:
NSString *algorithmName = @"RS256";
id <JWTAlgorithmDataHolderProtocol> signDataHolder = [JWTAlgorithmRSFamilyDataHolder new].keyExtractorType([JWTCryptoKeyExtractor privateKeyWithPEMBase64].type).algorithmName(algorithmName).secret(privateKey);
JWTCodingBuilder *signBuilder = [JWTEncodingBuilder encodePayload:claims].addHolder(signDataHolder);
JWTCodingResultType *signResult = signBuilder.result;
I can't seem to figure out how I should use the existing API to sign using a SecKeyRef
instead. Could you please enlighten me?
Thanks in advance, I appreciate you taking the time to pursue the issue.
@hakonk Well... Yes, it isn't supported yet. But no worry, you could add method for this easily.
First of all, you could set keys as JWTCryptoKeyProtocol
items.
Look at convenient setters in JWTAlgorithmRSFamilyDataHolder
holder
.signKey(item_which_sign)
.verifyKey(item_which_verify)
Now you want to provide a key for that.
This library assume that your keys are stored in .pem
format or in .p12
format.
But it always convert put them into SecKeyRef instance which is wrapped into JWTCryptoKey
.
So, you only need to add method that set SecKeyRef for JWTCryptoKey items.
But here is another trouble which could be avoided.
This library add keys and put them into Security Enclave ( into keychain ).
After usage it removes them.
It identifies keys by tags. ( That is why it is a condition to protocol JWTCryptoKeyProtocol
- item should have property tag
).
So, at the end you have something like:
JWTCryptoKeyPrivate *key = [[JWTCryptoKeyPrivate alloc] initWithRawSecKeyRef:keyRef];
holder.signKey(key);
UPD: modern Apple API support added instead, sorry :)
@hakonk Try latest master again :)
Hi, sorry for the late reply!
I will have a look at the implementation :)
@hakonk how did you get on with this?
@bencallis Since I ended up approaching the problem in a different way, I no longer was dependent on this library, and thus I didn't look into what was needed to make it work with the signatures created in the Secure Enclave. Are you looking into the issue yourself?
Hi may I know any updates on this?
@AyeChanPyaeSone Can you check latest master?
JWTCryptoKey.h
has protocol for it.
@protocol JWTCryptoKey__Raw__Generator__Protocol
- (instancetype)initWithSecKeyRef:(SecKeyRef)key;
@end
JWTCryptoKeyPrivate *key = [[JWTCryptoKeyPrivate alloc] initWithSecKeyRef:privateKey];
//Add fake data as secretData its a bug from library
id <JWTAlgorithmDataHolderProtocol> signDataHolder = [JWTAlgorithmRSFamilyDataHolder new]
.signKey(key)
.algorithmName(algorithmName)
.secretData(fakeData);
is this correct approach? I am using the algorith "ES256" please let me know is there anything wrong. However when i pass to server the signature seems invalid.
thanks you very much for your help 👍
I have to add secretData because it forced me to add secretData.
@AyeChanPyaeSone I have added you to Gitter room. Could we continue there?
Thanks!
Hi @lolgear, thanks for developing and maintaining this library for such a long time.
I'm currently experimenting with secure enclave keys and ran into this issue:
let payload = ["deviceId": "123123123"]
let privateJWTKey = JWTCryptoKeyPrivate(secKeyRef: privateKeyRef)
let publicJWTKey = JWTCryptoKeyPublic(secKeyRef: publicKeyRef)
var signDataHolder = JWTAlgorithmRSFamilyDataHolder.createWithAlgorithm256()
signDataHolder = signDataHolder?.signKey(privateJWTKey)
signDataHolder = signDataHolder?.verifyKey(publicJWTKey)
signDataHolder = signDataHolder?.secretData(Data()) // as required due to a bug
let signBuilder = JWTEncodingBuilder.encodePayload(payload)?.addHolder(signDataHolder)
let signResult = signBuilder?.encode
let signResultSuccess = signResult?.successResult
let encoded = signResultSuccess?.encoded
let signResultError = signResult?.errorResult
let signError = signResultError?.error
This results in that error:
algid:sign:RSA:message-PKCS1v15:SHA256: algorithm not supported by the key
Digging into encodeWithAlgorithm:withHeaders:withPayload:withSecretData:withError
I see that theAlgorithmName
is equal to @"RS256"
althought it should be @"ES256"
(keys in the secure enclave are ES256 to my understanding).
In chooseAlgorithm:
of JWTAlgorithmAsymmetricBase
also this case is chosen JWTAlgorithmAsymmetricBase__AlgorithmType__RS: return kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256;
although it should be this case JWTAlgorithmAsymmetricBase__AlgorithmType__ES: return kSecKeyAlgorithmECDSASignatureMessageX962SHA256;
(or I'm using the JWTAlgorithmRSFamilyDataHolder wrong).
If I manually override the values the encode succeeds without error. Decoding however (both with JWT and jwt.io) fails.
Do you have any hints? Btw. I'm on 3.0.0-beta.12
.
Thanks!
@b00tsy Yeah, I see. Raw SecKey values aren't determine correct types of encryption. Well, we have the following problem dualistic problem.
Algorithm in JWT is a name of preferred algorithm for encryption. Algorithm in Security framework is a way to encrypt/decrypt data.
On one side we have public property of desired encryption ( JWT part ), on other side - private property of a key entity.
Would you like to map private property of key to a public property of JWT?
Public JWT property -> Security Algorithm ( nice good ) Private property of key -> Security Algorithm -> Public JWT property (???)
You do everything correct, but what you really want is to specify algorithm type ( by public JWT name ) to allow and sign
further actions.
Next, what you can do in this version ( I don't test it ) is to set algorithmName directly
var signDataHolder = JWTAlgorithmRSFamilyDataHolder.createWithAlgorithm256().algorithmName(@"ES256");
You can dump key to its string representation, as I remember, by calling
@interface JWTCryptoSecurity (ExternalRepresentation)
+ (NSData *)externalRepresentationForKey:(SecKeyRef)key error:(NSError *__autoreleasing*)error;
@end
Thanks for the quick response.
This works without error (both signing and verifying):
let paylod = ["deviceId": "123123123"]
let privateJWTKey = JWTCryptoKeyPrivate(secKeyRef: privateKeyRef)
let publicJWTKey = JWTCryptoKeyPublic(secKeyRef: publicKeyRef)
var signDataHolder = JWTAlgorithmRSFamilyDataHolder.createWithAlgorithm256()
signDataHolder = signDataHolder?.algorithmName("ES256")
signDataHolder = signDataHolder?.signKey(privateJWTKey)
signDataHolder = signDataHolder?.verifyKey(publicJWTKey)
signDataHolder = signDataHolder?.secretData(Data()) // setting .secretData(Data()) is currently mandatory because of a bug
let signBuilder = JWTEncodingBuilder.encodePayload(paylod)?.addHolder(signDataHolder)
let signResult = signBuilder?.encode
let signResultSuccess = signResult?.successResult
let encoded = signResultSuccess?.encoded
let signResultError = signResult?.errorResult
let signError = signResultError?.error
let verifyBuilder = JWTDecodingBuilder.decodeMessage(encoded)?.addHolder(signDataHolder)
let verifyResult = verifyBuilder?.decode
let verifyResultSuccess = verifyResult?.successResult
let headers = verifyResultSuccess?.headers
let payload = verifyResultSuccess?.payload
let claimsSet = verifyResultSuccess?.claimsSet
let verifyResultError = verifyResult?.errorResult
let verifyError = verifyResultError?.error
However, verifying externally fails (jwt.io, or nodejs jsonwebtoken or jose).
jsonwebtoken is very specific about the error: TypeError: "ES256" signatures must be "64" bytes, saw "70"
.
Any hints?
PS sorry about trashing your issues with help requests. We could make a howto afterwards for how to use the secure enclave with your library (all other JWT libraries have no support for secure enclave at all)...
@b00tsy Could you attach example project with keys that you are using? Maybe some kind of aligning or unnecessary symbols at the end of signature could cause this issue.
@b00tsy BTW, I think that
.secretData(Data()) // setting .secretData(Data()) is currently mandatory because of a bug
is unnecessary in new release.
@lolgear
The private key itself is not exportable (within secure enclave chip on the processor).
This is a (hopefully) working example which requires the pod EllipticCurveKeyPair
.
Call it from objective-c via [TestKeyManager testECKeyJWT]
. To get a key in the secure encalve you need to run it on a real device (not the simulator).
Create a separate swift file with this:
import EllipticCurveKeyPair
struct KeyPairDemo {
static let manager: EllipticCurveKeyPair.Manager = {
let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: [.privateKeyUsage])
let publicLabel = "key.public.test"
let privateLabel = "key.private.test"
let config = EllipticCurveKeyPair.Config(
publicLabel: publicLabel,
privateLabel: privateLabel,
operationPrompt: "PROMPT!?!",
publicKeyAccessControl: publicAccessControl,
privateKeyAccessControl: privateAccessControl,
publicKeyAccessGroup: nil,
privateKeyAccessGroup: nil,
fallbackToKeychainIfSecureEnclaveIsNotAvailable: true)
return EllipticCurveKeyPair.Manager(config: config)
}()
}
@objcMembers class TestKeyManager: NSObject {
class func publicKeyRef() -> SecKey? {
do {
let publicKey = try KeyPairDemo.manager.publicKey()
let publicKeyRef = publicKey.underlying
return publicKeyRef
} catch {
print("\(error)")
}
return nil
}
class func privateKeyRef() -> SecKey? {
do {
let privateKey = try KeyPairDemo.manager.privateKey()
let privateKeyRef = privateKey.underlying
return privateKeyRef
} catch {
print("\(error)")
}
return nil
}
class func testECKeyJWT() {
do {
let privateKeyRef = TestKeyManager.privateKeyRef()
let publicKeyRef = TestKeyManager.publicKeyRef()
let paylod = ["deviceId": "123123123"]
let privateJWTKey = JWTCryptoKeyPrivate(secKeyRef: privateKeyRef)
let publicJWTKey = JWTCryptoKeyPublic(secKeyRef: publicKeyRef)
var signDataHolder = JWTAlgorithmRSFamilyDataHolder.createWithAlgorithm256()
signDataHolder = signDataHolder?.algorithmName("ES256")
signDataHolder = signDataHolder?.signKey(privateJWTKey)
signDataHolder = signDataHolder?.verifyKey(publicJWTKey)
signDataHolder = signDataHolder?.secretData(Data()) // setting .secretData(Data()) is currently mandatory because of a bug
let signBuilder = JWTEncodingBuilder.encodePayload(paylod)?.addHolder(signDataHolder)
let signResult = signBuilder?.encode
let signResultSuccess = signResult?.successResult
let encoded = signResultSuccess?.encoded
let signResultError = signResult?.errorResult
let signError = signResultError?.error
print("\(encoded)")
let verifyBuilder = JWTDecodingBuilder.decodeMessage(encoded)?.addHolder(signDataHolder)
let verifyResult = verifyBuilder?.decode
let verifyResultSuccess = verifyResult?.successResult
let headers = verifyResultSuccess?.headers
let payload = verifyResultSuccess?.payload
let claimsSet = verifyResultSuccess?.claimsSet
let verifyResultError = verifyResult?.errorResult
let verifyError = verifyResultError?.error
print("\(payload)")
} catch {
print("\(error)")
}
}
}
A very simple approach to circumvent all the issues (incompatiblity with other third party tools regarding jwt, encryption, signing and validation) created with the key and the key format of keys from the secure encalve could be to save a normal RSA key in the keychain and encrypt it on top of that with a key that's in the secure enclave... That would have the best ratio of compatibility and security for me...
Hi! Thanks for making this awesome lib. I have a question regarding Secure Enclave and signing on the iOS platform (i.e., with devices affording this capability).
From reading the documentation, it seems the private key used for signing has to be passed via a
String
orData
instance. However, if the private key is generated and stored in the secure enclave it is my understanding it cannot be retrieved and thus not be used with the current implementation ofyourcarma/JWT
. From what I understand, signing will have to be done viaSecKeyCreateSignature
where the private key will be passed in as aSecKey
(see code example below). Do you have any plans for supporting this in the future?Regards, Håkon