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

[87] Implement direct encryption and symmetric JWKs #92

Merged
33 commits merged into from
Jul 30, 2018

Conversation

ghost
Copy link

@ghost ghost commented Jul 17, 2018

Resolves #87.

This implements direct encryption for JWEs as well as symmetric JWKs.

Usage

This is a made up example to show off all new functionality.

let data = "🔥🌵🕶".data(using: .utf8)!

let random = try! SecureRandom.generate(count: ...)
let jwk = SymmetricKey(key: random)
let keyData = jwk.converted(to: Data.self)

let header = JWEHeader(algorithm: .direct, encryptionAlgorithm: .A256CBCHS512)
let payload = Payload(data)
let encrypter = Encrypter(keyEncryptionAlgorithm: .direct, encryptionKey: keyData, contentEncyptionAlgorithm: .A256CBCHS512)!

let jwe = try! JWE(header: header, payload: payload, encrypter: encrypter)

let serialization = jwe.compactSerializedString // eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiZGlyIn0..HUTNQ9m2Z8Q77tQJhLs5gg.DWQCCkrCPFeZ2-65L9__z83N1exh4oVIk4rOO2_v1eE.8sOW54Soupo_-TdXg5A9qXvokaHzS8cGb__ca3MvuEo

API Changes

The main Encrypter and Decrypter initializers changed from

init?(keyEncryptionAlgorithm: AsymmetricKeyAlgorithm, keyEncryptionKey kek: KeyType, contentEncyptionAlgorithm: SymmetricKeyAlgorithm)

// to

init?(keyEncryptionAlgorithm: AsymmetricKeyAlgorithm, encryptionKey key: KeyType, contentEncyptionAlgorithm: SymmetricKeyAlgorithm) {

and from

init?<KeyType>(keyDecryptionAlgorithm: AsymmetricKeyAlgorithm, keyDecryptionKey kdk: KeyType, contentDecryptionAlgorithm: SymmetricKeyAlgorithm)

// to

init?<KeyType>(keyDecryptionAlgorithm: AsymmetricKeyAlgorithm, decryptionKey key: KeyType, contentDecryptionAlgorithm: SymmetricKeyAlgorithm)

respectively.

The old initializers are still available (altough marked as deprecated), so we can release this as a minor version bump with no breaking API changes.


Regarding the weird Xcode file: Apparently it should be commited.

Xcode 9.3 adds a new IDEWorkspaceChecks.plist file to a workspace's shared data, to store the state of necessary workspace checks. Committing this file to source control will prevent unnecessary rerunning of those checks for each user opening the workspace. (37293167

@ghost ghost self-assigned this Jul 17, 2018
@ghost ghost added JWE labels Jul 17, 2018
@ghost ghost added the feature label Jul 17, 2018
Copy link
Contributor

@carol-mohemian carol-mohemian left a comment

Choose a reason for hiding this comment

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

Nice work @daniel-mohemian 👏 Looks really good! Found some typos and have 1-2 questions.

@@ -0,0 +1,34 @@
//
// DataRSAPublicKey.swift
Copy link
Contributor

Choose a reason for hiding this comment

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

DataSymmetricKey

@@ -24,17 +24,17 @@
import Foundation
import Security

internal enum SecureRandomError: Error {
public enum SecureRandomError: Error {
Copy link
Contributor

Choose a reason for hiding this comment

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

👌


func decrypt(_ ciphertext: Data) throws -> Data {
guard let privateKey = privateKey else {
return Data()
Copy link
Contributor

Choose a reason for hiding this comment

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

This is awesome 👌 Couldn't remember why we returned plain Data(). Maybe write a comment that we need a empty octet for direct encryption and therefore Data() is returned. What do you think?

@@ -0,0 +1,78 @@
//
// RSAKeyCodable.swift
Copy link
Contributor

Choose a reason for hiding this comment

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

SymmetricKeyCodable

@@ -0,0 +1,120 @@
//
// RSAKeys.swift
Copy link
Contributor

Choose a reason for hiding this comment

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

SymmetricKeys


class SymmetricKeyTests: XCTestCase {

func testCreatingSymetricKeyFromData() {
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: testCreatingSymmetricKeyFromData

let json = try! SymmetricKey(
key: key,
additionalParameters: [ "alg": SymmetricKeyAlgorithm.A256CBCHS512.rawValue ]
).jsonData()!
Copy link
Contributor

Choose a reason for hiding this comment

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

Formatting.

}

func testDecodingFromJSONWithMissingKeyType() {
let json = "{\"alg\":\"A256CBC-HS512\",\"k\":\"+++==notbase64url==---\"}".data(using: .utf8)!
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe use a correct key otherwise you don't know if it throws because of the missing keytype or the not base64URL key.

Copy link
Author

Choose a reason for hiding this comment

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

Good catch!

}

func testDecodingFromJSONWithWrongKeyType() {
let json = "{\"kty\":\"RSA\",\"alg\":\"A256CBC-HS512\",\"k\":\"+++==notbase64url==---\"}".data(using: .utf8)!
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above.

}

func testParsingSymmetricKeyFromOtherKeyRepresentation() {
let key: ExpressibleAsSymmetricKeyComponents = Data(bytes: [
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this different to the above test? 🤔

Copy link
Author

Choose a reason for hiding this comment

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

The first tests initializing the key directly from Data. The second from something that is ExpressibleAsSymmetricKeyComponents. Currently, only Data conforms to that protocol so it's basically the same, but uses two different initializers.

If you compare it to the RSA keys, there SecKey and Data are ExpressibleAsRSAPublicKeyComponents but the key can only be initialized directly from modulus/exponent as Strings.

@ghost
Copy link
Author

ghost commented Jul 18, 2018

@carol-mohemian Thanks for the feedback! I addressed all your comments (I hope 😄).

@carol-mohemian
Copy link
Contributor

Nice 👍 Would love to see a review by @gigi-mohemian as well.

Copy link
Contributor

@mohemian-92817281 mohemian-92817281 left a comment

Choose a reason for hiding this comment

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

Awesome job @daniel-mohemian 👏

I have some very minor comments, none of them critical in any way.

/// - key: The key used to perform the decryption. If the `keyDecryptionAlgorithm` is `.direct`, the
/// `decryptionKey` is the shared symmetric content encryption key. Otherwise the `decryptionKey` is the
/// private key of the receiver. See [RFC-7516](https://tools.ietf.org/html/rfc7516#section-5.2) for
/// details.
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

switch (keyDecryptionAlgorithm, contentDecryptionAlgorithm) {
case (.RSA1_5, .A256CBCHS512):
guard type(of: kdk) is RSADecrypter.KeyType.Type else {
guard type(of: key) is RSADecrypter.KeyType.Type else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this also be a guard let ... as? so that we don't need the force-cast below?

Copy link
Author

@ghost ghost Jul 22, 2018

Choose a reason for hiding this comment

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

That's a very good point. I did some testing and yes, normally that would work:

func printType<T>(of key: T) {
    if let _ = key as? Data {
        print("Data")
    }
}

printType(of: Data()) // prints "Data"

However, doing the same with SecKey (if let _ = key as? SecKey) gives the following error:

error: conditional downcast to CoreFoundation type 'SecKey' will always succeed

After more testing, this seems to be the situation:

  • For normal Swift types, we could do optional let bindings as you suggested
  • The current check type(of: key) is SecKey will succeed for all CoreFoundation types (SecKey, CFString, ...) but not for Swift types.

So if one passes Data() as RSA key it will detect the wrong type, but if one passes e.g. a CFString, it won't detect the error. The correct way of checking CoreFoundation types would be to compare CFGetTypeID(key as CFTypeRef to stuff like SecKeyGetTypeID(). However, I currently don't know how we should solve that without knowing what *GetTypeID() function to call (since the KeyType is generic and CFGetTypeID() only seems to work on AnyObjects and not on types itself).

Since that's a bug not directly introduced by this pull request and it does not affect direct encryption, are you guys ok with fixing it separately? If yes, I'll track an issue. cc @carol-mohemian, @gigi-mohemian

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks a lot for the thorough explanation!

Fixing this separately as you suggested is the way to go I'd say 👍

self.symmetric = AESDecrypter(algorithm: contentDecryptionAlgorithm)
case (.direct, .A256CBCHS512):
guard type(of: key) is AESDecrypter.KeyType.Type else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here: could this be a guard let ... as??

Also two more times in Encrypter.swift

Copy link
Author

Choose a reason for hiding this comment

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

See above ☝️

@@ -41,6 +41,7 @@ internal enum JWKError: Error {
/// - RSA
public enum JWKKeyType: String, Codable {
case RSA = "RSA"
case SYM = "oct"
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

}

keys.append(rsaKey)
throw DecodingError.dataCorruptedError(in: keyContainer, debugDescription: """
No RSAPrivateKey, RSAPublicKey, or SymmetricKey found to decode.
Copy link
Contributor

Choose a reason for hiding this comment

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

👌

JWKParameter.keyType,
DecodingError.Context.init(
codingPath: [JWKParameter.keyType],
debugDescription: "Key Type parameter wrong."
Copy link
Contributor

Choose a reason for hiding this comment

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

I would opt for "Wrong parameter: key type" - but the above is okay as well 👍

The title case of Key Type is what caught my eye.

/// - key: The octet sequence containing the key data.
/// - parameters: Additional JWK parameters.
public init(key: Data, additionalParameters parameters: [String: String] = [:]) {
self.keyType = .SYM
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we sure about the naming here? While I don't actually disagree with the naming I was confused for a short while because SYM = "oct" 🤔

What do you think about going full on Symmetric so it's not confused with an abbreviation or using OCT directly? Just thinking out aloud here 😅

Copy link
Author

Choose a reason for hiding this comment

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

Good point, I agree. 👍

I changed it to .OCT. (Nimbus also uses OCT btw.)

4, 122, 29, 230, 151, 12, 244, 127,
121, 25, 4, 85, 220, 144, 215, 110,
130, 17, 68, 228, 129, 138, 7, 130
])
Copy link
Contributor

Choose a reason for hiding this comment

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

Awesome formatting 👆

👌

@ghost
Copy link
Author

ghost commented Jul 22, 2018

Thx @gigi-mohemian for the review.

@ghost ghost merged commit f390e05 into master Jul 30, 2018
@ghost ghost deleted the feature/87-direct-encryption branch July 30, 2018 09:11
mohemian-92817281 added a commit that referenced this pull request Jul 30, 2018
* master:
  [87] Implement direct encryption and symmetric JWKs (#92)

# Conflicts:
#	.gitignore
#	Tests/JWERSATests.swift
@ghost ghost mentioned this pull request Aug 30, 2018
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.

Add direct encryption in JWE
3 participants