Skip to content
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

Merged
7 commits merged into from Jun 14, 2019

Conversation

mschwaig
Copy link
Contributor

@mschwaig mschwaig commented Jun 7, 2019

This PR changes the EC crypto implemenation to use SecKeyCreateSignature and SecKeyVerfiySignature 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.

Martin Schwaighofer added 4 commits May 29, 2019 16:15
* 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()))")

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

Copy link

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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)

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@Jose-ElRobot
Copy link

Jose-ElRobot commented Jun 7, 2019

1 Message
📖 Any non-trivial changes to code should be reflected in the changelog. Please consider adding a note in the Unreleased section of the CHANGELOG.md.

Generated by 🚫 Danger

@ghost ghost self-assigned this Jun 7, 2019
@ghost
Copy link

ghost commented Jun 7, 2019

Awesome, thanks @mschwaig! 🎉

I'll get back to you with a review early next week.

Copy link

@ghost ghost left a 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.

JOSESwift/Sources/CryptoImplementation/EC.swift Outdated Show resolved Hide resolved
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()))")
Copy link

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
Copy link

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.

Copy link
Contributor Author

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.

Copy link

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.

Copy link
Contributor Author

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.

Copy link

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")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw ECError.signingFailed(description: "could not unpack ASN.1 EC signature")
throw ECError.signingFailed(description: "Could not unpack ASN.1 EC signature.")

JOSESwift/Sources/CryptoImplementation/EC.swift Outdated Show resolved Hide resolved
JOSESwift/Sources/CryptoImplementation/EC.swift Outdated Show resolved Hide resolved
}

return signature as Data
// unpack BER encoded ASN.1 format signature to raw format as specified for JWS
let ecSignatureTLV = [UInt8](signature! as Data)
Copy link

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) {
Copy link

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

Copy link

@ghost ghost left a 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
Copy link

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.

@mschwaig
Copy link
Contributor Author

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.

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.
I will only be able to continue working on them after NovaRock. Adding the tests in a seperate PR would be good I think, but either way is fine for me. Feel free to proceed with this however you like.

@mschwaig
Copy link
Contributor Author

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.

@ghost
Copy link

ghost commented Jun 14, 2019

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.

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 SecKeyRawVerify did not return a boolean but only a status code. I think mirroring the behavior of the newer SecKeyVerifySignature, as you did, is the way to go. What do you think?

I will only be able to continue working on them after NovaRock. Adding the tests in a seperate PR would be good I think, but either way is fine for me. Feel free to proceed with this however you like.

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! ☀️ 🇦🇹 🎸 🍻

@mschwaig
Copy link
Contributor Author

Sounds great, thanks. 😁

@ghost
Copy link

ghost commented Jun 14, 2019

For completeness, I tracked #157. Let me know if you need anything!

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants