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

Restructure JWE encryption and decryption #210

Merged
9 commits merged into from
Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ disabled_rules:
- type_name
- identifier_name
- todo
- no_space_in_method_call

opt_in_rules:
- force_unwrapping
Expand Down
120 changes: 88 additions & 32 deletions JOSESwift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

124 changes: 124 additions & 0 deletions JOSESwift/Sources/AESCBCEncryption.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// AESCBCEncryption.swift
// JOSESwift
//
// Created by Daniel Egger on 12.02.20.
//
// ---------------------------------------------------------------------------
// Copyright 2020 Airside Mobile Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ---------------------------------------------------------------------------
//

import Foundation

struct AESCBCEncryption {
private let contentEncryptionAlgorithm: ContentEncryptionAlgorithm
private let contentEncryptionKey: Data

init(contentEncryptionAlgorithm: ContentEncryptionAlgorithm, contentEncryptionKey: Data) {
self.contentEncryptionAlgorithm = contentEncryptionAlgorithm
self.contentEncryptionKey = contentEncryptionKey
}

func encrypt(_ plaintext: Data, additionalAuthenticatedData: Data) throws -> ContentEncryptionContext {
let iv = try SecureRandom.generate(count: contentEncryptionAlgorithm.initializationVectorLength)

let keys = try contentEncryptionAlgorithm.retrieveKeys(from: contentEncryptionKey)
let hmacKey = keys.hmacKey
let encryptionKey = keys.encryptionKey

let ciphertext = try AES.encrypt(plaintext, with: encryptionKey, using: contentEncryptionAlgorithm, and: iv)

// Put together the input data for the HMAC. It consists of A || IV || E || AL.
var concatData = additionalAuthenticatedData
concatData.append(iv)
concatData.append(ciphertext)
concatData.append(additionalAuthenticatedData.getByteLengthAsOctetHexData())

let hmac = try HMAC.calculate(from: concatData, with: hmacKey, using: contentEncryptionAlgorithm.hmacAlgorithm)
let authenticationTag = contentEncryptionAlgorithm.authenticationTag(for: hmac)

return ContentEncryptionContext(
ciphertext: ciphertext,
authenticationTag: authenticationTag,
initializationVector: iv
)
}

func decrypt(
_ ciphertext: Data,
initializationVector: Data,
additionalAuthenticatedData: Data,
authenticationTag: Data
) throws -> Data {
// Check if the key length contains both HMAC key and the actual symmetric key.
guard contentEncryptionAlgorithm.checkKeyLength(for: contentEncryptionKey) else {
throw JWEError.keyLengthNotSatisfied
}

// Get the two keys for the HMAC and the symmetric encryption.
let keys = try contentEncryptionAlgorithm.retrieveKeys(from: contentEncryptionKey)
let hmacKey = keys.hmacKey
let decryptionKey = keys.encryptionKey

// Put together the input data for the HMAC. It consists of A || IV || E || AL.
var concatData = additionalAuthenticatedData
concatData.append(initializationVector)
concatData.append(ciphertext)
concatData.append(additionalAuthenticatedData.getByteLengthAsOctetHexData())

// Calculate the HMAC for the concatenated input data and compare it with the reference authentication tag.
let hmacOutput = try HMAC.calculate(
from: concatData,
with: hmacKey,
using: contentEncryptionAlgorithm.hmacAlgorithm
)

guard authenticationTag == contentEncryptionAlgorithm.authenticationTag(for: hmacOutput) else {
throw JWEError.hmacNotAuthenticated
}

// Decrypt the cipher text with a symmetric decryption key, a symmetric algorithm and the initialization vector,
// return the plaintext if no error occured.
let plaintext = try AES.decrypt(
cipherText: ciphertext,
with: decryptionKey,
using: contentEncryptionAlgorithm,
and: initializationVector
)

return plaintext
}
}

extension AESCBCEncryption: ContentEncrypter {
func encrypt(header: JWEHeader, payload: Payload) throws -> ContentEncryptionContext {
let plaintext = payload.data()
let additionalAuthenticatedData = header.data().base64URLEncodedData()

return try encrypt(plaintext, additionalAuthenticatedData: additionalAuthenticatedData)
}
}

extension AESCBCEncryption: ContentDecrypter {
func decrypt(decryptionContext: ContentDecryptionContext) throws -> Data {
return try decrypt(
decryptionContext.ciphertext,
initializationVector: decryptionContext.initializationVector,
additionalAuthenticatedData: decryptionContext.additionalAuthenticatedData,
authenticationTag: decryptionContext.authenticationTag
)
}
}
67 changes: 0 additions & 67 deletions JOSESwift/Sources/AESDecrypter.swift

This file was deleted.

66 changes: 0 additions & 66 deletions JOSESwift/Sources/AESEncrypter.swift

This file was deleted.

86 changes: 86 additions & 0 deletions JOSESwift/Sources/AlgorithmExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// AlgorithmExtensions.swift
// JOSESwift
//
// Created by Daniel Egger on 12.02.20.
//
// ---------------------------------------------------------------------------
// Copyright 2020 Airside Mobile Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ---------------------------------------------------------------------------
//

import Foundation

extension ContentEncryptionAlgorithm {
var hmacAlgorithm: HMACAlgorithm {
switch self {
case .A256CBCHS512:
return .SHA512
case .A128CBCHS256:
return .SHA256
}
}

var keyLength: Int {
switch self {
case .A256CBCHS512:
return 64
case .A128CBCHS256:
return 32
}
}

var initializationVectorLength: Int {
switch self {
case .A128CBCHS256, .A256CBCHS512:
return 16
}
}

func checkKeyLength(for key: Data) -> Bool {
switch self {
case .A256CBCHS512:
return key.count == 64
case .A128CBCHS256:
return key.count == 32
}
}

func retrieveKeys(from inputKey: Data) throws -> (hmacKey: Data, encryptionKey: Data) {
switch self {
case .A256CBCHS512:
guard checkKeyLength(for: inputKey) else {
throw JWEError.keyLengthNotSatisfied
}

return (inputKey.subdata(in: 0..<32), inputKey.subdata(in: 32..<64))

case .A128CBCHS256:
guard checkKeyLength(for: inputKey) else {
throw JWEError.keyLengthNotSatisfied
}
return (inputKey.subdata(in: 0..<16), inputKey.subdata(in: 16..<32))
}
}

func authenticationTag(for hmac: Data) -> Data {
switch self {
case .A256CBCHS512:
return hmac.subdata(in: 0..<32)
case .A128CBCHS256:
return hmac.subdata(in: 0..<16)
}
}
}
Loading