New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for EC256 keys that are stored inside the Secure Enclave #156
Conversation
* This is meant to enable keeping keys inside the Secure Enclave. * Parses ASN1 signature and extracts raw parameters R and S with fixed length. * Implementation passes tests, but does not seem to work with keys generated inside the Secure Enclave for some reason. * EC signature verification is not changed yet and still uses SecKeyRawVerify.
var cfErrorRef: Unmanaged<CFError>? | ||
let signature = SecKeyCreateSignature(privateKey, secKeyAlgorithm, signingInput as CFData, &cfErrorRef) | ||
if cfErrorRef != nil { | ||
throw ECError.signingFailed(description: "Error creating signature. (CFError: \(cfErrorRef!.takeRetainedValue()))") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Force unwrapping should be avoided.force_unwrapping EC.swift:188 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your code is perfectly fine. A little trick to get rid of the force unwrapped warnings would be to use a let binding.
For example something like this (taken from Apple's documentation):
var error: Unmanaged<CFError>?
guard let signature = SecKeyCreateSignature(privateKey, secKeyAlgorithm, signingInput as CFData, &error) as Data? else {
// swiftlint:disable:next force_unwrapping
throw error!.takeRetainedValue() as Error
}
Now you can use signature
without casting and force unwrapping below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I opt to cast and throw the retained value or add it to the description string like I am currently doing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did the description string thing for now.
} | ||
|
||
return signature as Data | ||
// unpack BER encoded ASN.1 format signature to raw format as specified for JWS | ||
let ecSignatureTLV = [UInt8](signature! as Data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Force unwrapping should be avoided.force_unwrapping EC.swift:192 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a preferable alternative to the force unwrap here?
The signature should always be non-null at this point since the contract of SecKeyCreateSignature
only allow nil
here on failure.
If not I would add a comment and disable the linter here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found another way to fix address this with an if let
inside a guard let
and a fatalError
if they mismatch.
Generated by 🚫 Danger |
Awesome, thanks @mschwaig! 🎉 I'll get back to you with a review early next week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty awesome, great job @mschwaig! 👏 Also thanks for extending the tests!
I left a couple of clarification questions just to make sure I understand everything correctly.
Your implementation looks perfectly fine to me. If possible, it would be nice to get rid of the force unwrappings wherever possible. Wherever you feel that they are justified, feel free to leave a clarifying comment and disable the linter for that line. See here on how to do that.
One more suggestion from my side: What would you think about moving the DER/ASN.1 packing and unpacking code to functions that can be tested independently from the signing/verifying code
logic? Since this is the crucial feature of this pull request, it would be awesome to be able to test them independently.
Let me know what you think! After that, we'll be able to merge and release an update right away!
Again, great job and thanks for taking care of this.
var cfErrorRef: Unmanaged<CFError>? | ||
let signature = SecKeyCreateSignature(privateKey, secKeyAlgorithm, signingInput as CFData, &cfErrorRef) | ||
if cfErrorRef != nil { | ||
throw ECError.signingFailed(description: "Error creating signature. (CFError: \(cfErrorRef!.takeRetainedValue()))") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your code is perfectly fine. A little trick to get rid of the force unwrapped warnings would be to use a let binding.
For example something like this (taken from Apple's documentation):
var error: Unmanaged<CFError>?
guard let signature = SecKeyCreateSignature(privateKey, secKeyAlgorithm, signingInput as CFData, &error) as Data? else {
// swiftlint:disable:next force_unwrapping
throw error!.takeRetainedValue() as Error
}
Now you can use signature
without casting and force unwrapping below.
let fixlenR = varlenR.withLengthFixedTo(curveType.coordinateOctetLength) | ||
let fixlenS = varlenS.withLengthFixedTo(curveType.coordinateOctetLength) | ||
|
||
return fixlenR + fixlenS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
☝️ That's pretty nice. 👏
Glad to see that the existing ASN.1 utilities could be reused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more suggestion from my side: What would you think about moving the DER/ASN.1 packing and unpacking code to functions that can be tested independently from the signing/verifying code
logic? Since this is the crucial feature of this pull request, it would be awesome to be able to test them independently.
I think testing this logic independently would be a good idea, but I would not want to extend the implementation to support parsing more than what is strictly required for this use case or have these functions exposed to other parts of the code in a way that suggests they can be reused elsewhere when actually right now they only work within the constraints that apply for this use case.
I have to look into how to scope things in Swift to achieve this and have it be testable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good point. For testing, we would have to raise the access control level from fileprivate
to internal
. I totally agree with you that that might suggest that the functions could be used from elsewhere in the code base- That's certainly not what we want.
What would you think of having them as an extension of ECCurveType
that would clearly namespace it as something EC
internal and would even give you direct access to the coordinateOctetLength
. Just an idea. Let me know what you think, I'm open for suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am halfway-done with the tests on my work machine. Currently I am using another struct inside of the EC
struct with two static functions to avoid the extension getting exposed everywhere, what you are suggesting sounds like it may be a better idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave it up to you how to best handle this. 👍
|
||
return fixlenR + fixlenS | ||
} catch { | ||
throw ECError.signingFailed(description: "could not unpack ASN.1 EC signature") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw ECError.signingFailed(description: "could not unpack ASN.1 EC signature") | |
throw ECError.signingFailed(description: "Could not unpack ASN.1 EC signature.") |
} | ||
|
||
return signature as Data | ||
// unpack BER encoded ASN.1 format signature to raw format as specified for JWS | ||
let ecSignatureTLV = [UInt8](signature! as Data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Force unwrapping should be avoided.force_unwrapping EC.swift:204 |
guard let secKeyAlgorithm = algorithm.secKeyAlgorithm else { | ||
throw ECError.algorithmNotSupported | ||
} | ||
if (signature.count != curveType.coordinateOctetLength * 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if , for , guard , switch , while , and catch statements shouldn’t unnecessarily wrap their conditionals or arguments in parentheses.control_statement EC.swift:235 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job @mschwaig, thanks for going over it again and resolving all remaining issues.
Let me know what you think regarding testing the two asn1/fixedlength functions. If you want we can also track an issue for that and add it in a separate pull request. If you want I can handle that for you.
let fixlenR = varlenR.withLengthFixedTo(curveType.coordinateOctetLength) | ||
let fixlenS = varlenS.withLengthFixedTo(curveType.coordinateOctetLength) | ||
|
||
return fixlenR + fixlenS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good point. For testing, we would have to raise the access control level from fileprivate
to internal
. I totally agree with you that that might suggest that the functions could be used from elsewhere in the code base- That's certainly not what we want.
What would you think of having them as an extension of ECCurveType
that would clearly namespace it as something EC
internal and would even give you direct access to the coordinateOctetLength
. Just an idea. Let me know what you think, I'm open for suggestions.
I am about halfway done with those tests. I prepared test data for all of the cases I think are relevant, but I did not implement any actual test cases yet. |
One thing I am still concerned about is if the EC implementation still behaves in the same way as before for negative cases like invalid signatures or blatantly incorrectly formatted signatures, since the verify method can return false but also throw errors. This negative case behaviour might be inconsistent between the old and new implementation. I cannot judge if this would be an issue, I remember thinking maybe also differs for RSA and EC right now, but I did not investigate further. |
Good point. Your implementation now behaves as its RSA counterpart. It returns true/false for valid/invalid signatures and throws in case of an error. The old
Ok, cool! I'll merge this pull request now but will hold off on releasing the change until the tests are in. That way we get smaller and easier reviewable pull requests while still maintaining the quality of the release. Have fun at Nova Rock! ☀️ 🇦🇹 🎸 🍻 |
Sounds great, thanks. 😁 |
For completeness, I tracked #157. Let me know if you need anything! |
This PR changes the EC crypto implemenation to use
SecKeyCreateSignature
andSecKeyVerfiySignature
for signature creation and verification and adds the required conversions to and from BER encoded ASN.1 format for the signatures.It also introduces negative tests for EC signature verification, to detect trivial false acceptances for the signature validation. This is important because the tests for the signing also rely on the validation implementation.
See #155 for the discussion which lead to this PR.
Please be thorough with the iOS side of the review, since I do not have that much experience as developer for iOS.