Skip to content

Commit

Permalink
Added support for content encryption using AES GCM according to JWE s…
Browse files Browse the repository at this point in the history
…tandard
  • Loading branch information
tobihagemann authored and haeser committed May 7, 2024
1 parent 1eae9ec commit bd1e5de
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 82 deletions.
2 changes: 1 addition & 1 deletion JOSESwift.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Pod::Spec.new do |s|
s.social_media_url = "https://twitter.com/airsideout"

s.swift_version = "5.0"
s.platform = :ios, "10.0"
s.platform = :ios, "13.0"
s.source = { :git => "https://github.com/airsidemobile/JOSESwift.git", :tag => "#{s.version}" }
s.source_files = "JOSESwift/**/*.{h,swift}"
end
22 changes: 11 additions & 11 deletions JOSESwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
7402BEAB26274D430012801E /* HMACVerifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7402BEAA26274D430012801E /* HMACVerifierTests.swift */; };
7402BEAF26274DA40012801E /* HMACCryptoTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7402BEAE26274DA40012801E /* HMACCryptoTestCase.swift */; };
7402BEB526288DCD0012801E /* JWSHMACTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7402BEB426288DCD0012801E /* JWSHMACTests.swift */; };
74618E4E28A64EBA0028B066 /* AESGCMEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74618E4D28A64EBA0028B066 /* AESGCMEncryption.swift */; };
74618E5028A6A3080028B066 /* AESGCMEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74618E4F28A6A3080028B066 /* AESGCMEncryptionTests.swift */; };
8A30B8F822118FD0001834E3 /* DeflateWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A30B8F722118FD0001834E3 /* DeflateWrapper.swift */; };
8A30B8FA22118FE6001834E3 /* JWECompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A30B8F922118FE6001834E3 /* JWECompressionTests.swift */; };
8A4B08B22213FF0600828536 /* Compressor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4B08B12213FF0600828536 /* Compressor.swift */; };
Expand Down Expand Up @@ -234,6 +236,8 @@
7402BEAA26274D430012801E /* HMACVerifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMACVerifierTests.swift; sourceTree = "<group>"; };
7402BEAE26274DA40012801E /* HMACCryptoTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMACCryptoTestCase.swift; sourceTree = "<group>"; };
7402BEB426288DCD0012801E /* JWSHMACTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWSHMACTests.swift; sourceTree = "<group>"; };
74618E4D28A64EBA0028B066 /* AESGCMEncryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AESGCMEncryption.swift; sourceTree = "<group>"; };
74618E4F28A6A3080028B066 /* AESGCMEncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AESGCMEncryptionTests.swift; sourceTree = "<group>"; };
8A30B8F722118FD0001834E3 /* DeflateWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeflateWrapper.swift; sourceTree = "<group>"; };
8A30B8F922118FE6001834E3 /* JWECompressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JWECompressionTests.swift; sourceTree = "<group>"; };
8A4B08B12213FF0600828536 /* Compressor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Compressor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -312,6 +316,7 @@
C86AC8CA1FCEC20F0007E611 /* AESCBCContentEncryptionTests.swift */,
C83070041FD1B7390068C5CB /* AESCBCContentDecryptionTests.swift */,
65B5B93F23F5604A009C8396 /* DirectEncryptionKeyManagementModeTests.swift */,
74618E4F28A6A3080028B066 /* AESGCMEncryptionTests.swift */,
);
name = JWE;
sourceTree = "<group>";
Expand Down Expand Up @@ -390,17 +395,10 @@
isa = PBXGroup;
children = (
6558164C23F45CC200EA5FEC /* ContentEncryption.swift */,
6558164E23F462BC00EA5FEC /* AESCBCEncryption */,
);
name = ContentEncryption;
sourceTree = "<group>";
};
6558164E23F462BC00EA5FEC /* AESCBCEncryption */ = {
isa = PBXGroup;
children = (
6558164F23F463C200EA5FEC /* AESCBCEncryption.swift */,
74618E4D28A64EBA0028B066 /* AESGCMEncryption.swift */,
);
name = AESCBCEncryption;
name = ContentEncryption;
sourceTree = "<group>";
};
657D0F7523FAE4B5004A7975 /* KeyWrapping */ = {
Expand Down Expand Up @@ -794,6 +792,7 @@
5FB76093CE81BF1F8E7C254A /* JWKECDecodingTests.swift in Sources */,
5FB765B20A256B93440E79E2 /* JWKECEncodingTests.swift in Sources */,
5FB7656EDD94AE3620A8C13C /* SecKeyECPrivateKeyTests.swift in Sources */,
74618E5028A6A3080028B066 /* AESGCMEncryptionTests.swift in Sources */,
65B5B94023F5604A009C8396 /* DirectEncryptionKeyManagementModeTests.swift in Sources */,
5FB7656FF300A4A3EF0F3597 /* DataECPrivateKeyTests.swift in Sources */,
5FB762ED79A6159B5A9EDECC /* ECPrivateKeyToSecKeyTests.swift in Sources */,
Expand All @@ -814,6 +813,7 @@
6572C2F21F96428800D4186D /* Decrypter.swift in Sources */,
653656092035D86E00A3AC3B /* JWKSet.swift in Sources */,
65D1D0671F7A878D006377CD /* Deserializer.swift in Sources */,
74618E4E28A64EBA0028B066 /* AESGCMEncryption.swift in Sources */,
65F44EB11FE2D941000C5EA0 /* JWK.swift in Sources */,
6582614F2029F2D100B594ED /* ASN1DERParsing.swift in Sources */,
C85012E31FE04E0C00EC49FA /* SecureRandom.swift in Sources */,
Expand Down Expand Up @@ -956,7 +956,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -1016,7 +1016,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
Expand Down
14 changes: 10 additions & 4 deletions JOSESwift/Sources/AESCBCEncryption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ struct AESCBCEncryption {
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)
guard let hmacAlgorithm = contentEncryptionAlgorithm.hmacAlgorithm else {
throw JWEError.contentEncryptionAlgorithmMismatch
}
let hmac = try HMAC.calculate(from: concatData, with: hmacKey, using: hmacAlgorithm)
let authenticationTag = try contentEncryptionAlgorithm.authenticationTag(for: hmac)

return ContentEncryptionContext(
ciphertext: ciphertext,
Expand Down Expand Up @@ -80,14 +83,17 @@ struct AESCBCEncryption {
concatData.append(additionalAuthenticatedData.getByteLengthAsOctetHexData())

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

guard
authenticationTag.timingSafeCompare(with: contentEncryptionAlgorithm.authenticationTag(for: hmacOutput))
authenticationTag.timingSafeCompare(with: try contentEncryptionAlgorithm.authenticationTag(for: hmacOutput))
else {
throw JWEError.hmacNotAuthenticated
}
Expand Down
82 changes: 82 additions & 0 deletions JOSESwift/Sources/AESGCMEncryption.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// AESGCMEncryption.swift
// JOSESwift
//
// Created by Tobias Hagemann on 12.08.22.
//
// ---------------------------------------------------------------------------
// Copyright 2022 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 CryptoKit
import Foundation

struct AESGCMEncryption {
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 {
return try encrypt(plaintext, initializationVector: nil, additionalAuthenticatedData: additionalAuthenticatedData)
}

func encrypt(_ plaintext: Data, initializationVector: Data?, additionalAuthenticatedData: Data) throws -> ContentEncryptionContext {
let key = CryptoKit.SymmetricKey(data: contentEncryptionKey)
let nonce: CryptoKit.AES.GCM.Nonce
if let initializationVector = initializationVector {
nonce = try CryptoKit.AES.GCM.Nonce(data: initializationVector)
} else {
nonce = CryptoKit.AES.GCM.Nonce()
}
let encrypted = try CryptoKit.AES.GCM.seal(plaintext, using: key, nonce: nonce, authenticating: additionalAuthenticatedData)
return ContentEncryptionContext(
ciphertext: encrypted.ciphertext,
authenticationTag: encrypted.tag,
initializationVector: encrypted.nonce.withUnsafeBytes({ Data(Array($0)) })
)
}

func decrypt(_ ciphertext: Data, initializationVector: Data, additionalAuthenticatedData: Data, authenticationTag: Data) throws -> Data {
let key = CryptoKit.SymmetricKey(data: contentEncryptionKey)
let nonce = try CryptoKit.AES.GCM.Nonce(data: initializationVector)
let encrypted = try CryptoKit.AES.GCM.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: authenticationTag)
let decrypted = try CryptoKit.AES.GCM.open(encrypted, using: key, authenticating: additionalAuthenticatedData)
return decrypted
}
}

extension AESGCMEncryption: 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 AESGCMEncryption: ContentDecrypter {
func decrypt(decryptionContext: ContentDecryptionContext) throws -> Data {
return try decrypt(
decryptionContext.ciphertext,
initializationVector: decryptionContext.initializationVector,
additionalAuthenticatedData: decryptionContext.additionalAuthenticatedData,
authenticationTag: decryptionContext.authenticationTag
)
}
}
108 changes: 54 additions & 54 deletions JOSESwift/Sources/AlgorithmExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,65 +24,65 @@
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
}
}
var hmacAlgorithm: HMACAlgorithm? {
switch self {
case .A256CBCHS512:
return .SHA512
case .A128CBCHS256:
return .SHA256
case .A256GCM, .A128GCM:
return nil
}
}

func checkKeyLength(for key: Data) -> Bool {
switch self {
case .A256CBCHS512:
return key.count == 64
case .A128CBCHS256:
return key.count == 32
}
}
var keyLength: Int {
switch self {
case .A256CBCHS512:
return 64
case .A128CBCHS256, .A256GCM:
return 32
case .A128GCM:
return 16
}
}

func retrieveKeys(from inputKey: Data) throws -> (hmacKey: Data, encryptionKey: Data) {
switch self {
case .A256CBCHS512:
guard checkKeyLength(for: inputKey) else {
throw JWEError.keyLengthNotSatisfied
}
var initializationVectorLength: Int {
switch self {
case .A128CBCHS256, .A256CBCHS512:
return 16
case .A256GCM, .A128GCM:
return 12
}
}

return (inputKey.subdata(in: 0..<32), inputKey.subdata(in: 32..<64))
func checkKeyLength(for key: Data) -> Bool {
return key.count == keyLength
}

case .A128CBCHS256:
guard checkKeyLength(for: inputKey) else {
throw JWEError.keyLengthNotSatisfied
}
return (inputKey.subdata(in: 0..<16), inputKey.subdata(in: 16..<32))
}
}
func retrieveKeys(from inputKey: Data) throws -> (hmacKey: Data, encryptionKey: Data) {
guard checkKeyLength(for: inputKey) else {
throw JWEError.keyLengthNotSatisfied
}
switch self {
case .A256CBCHS512:
return (inputKey.subdata(in: 0..<32), inputKey.subdata(in: 32..<64))
case .A128CBCHS256:
return (inputKey.subdata(in: 0..<16), inputKey.subdata(in: 16..<32))
case .A256GCM, .A128GCM:
throw JWEError.contentEncryptionAlgorithmMismatch
}
}

func authenticationTag(for hmac: Data) -> Data {
switch self {
case .A256CBCHS512:
return hmac.subdata(in: 0..<32)
case .A128CBCHS256:
return hmac.subdata(in: 0..<16)
}
}
func authenticationTag(for hmac: Data) throws -> Data {
switch self {
case .A256CBCHS512:
return hmac.subdata(in: 0..<32)
case .A128CBCHS256:
return hmac.subdata(in: 0..<16)
case .A256GCM, .A128GCM:
throw JWEError.contentEncryptionAlgorithmMismatch
}
}
}

extension SignatureAlgorithm {
Expand Down
4 changes: 4 additions & 0 deletions JOSESwift/Sources/Algorithms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public enum ContentEncryptionAlgorithm: String {
case A256CBCHS512 = "A256CBC-HS512"
/// Content encryption using AES_128_CBC_HMAC_SHA_256
case A128CBCHS256 = "A128CBC-HS256"
/// Content encryption using AES GCM with 256-bit key
case A256GCM = "A256GCM"
/// Content encryption using AES GCM with 128-bit key
case A128GCM = "A128GCM"
}

/// An algorithm for HMAC calculation.
Expand Down
4 changes: 4 additions & 0 deletions JOSESwift/Sources/ContentEncryption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,17 @@ extension ContentEncryptionAlgorithm {
switch self {
case .A128CBCHS256, .A256CBCHS512:
return AESCBCEncryption(contentEncryptionAlgorithm: self, contentEncryptionKey: contentEncryptionKey)
case .A128GCM, .A256GCM:
return AESGCMEncryption(contentEncryptionAlgorithm: self, contentEncryptionKey: contentEncryptionKey)
}
}

func makeContentDecrypter(contentEncryptionKey: Data) -> ContentDecrypter {
switch self {
case .A128CBCHS256, .A256CBCHS512:
return AESCBCEncryption(contentEncryptionAlgorithm: self, contentEncryptionKey: contentEncryptionKey)
case .A128GCM, .A256GCM:
return AESGCMEncryption(contentEncryptionAlgorithm: self, contentEncryptionKey: contentEncryptionKey)
}
}
}
Loading

0 comments on commit bd1e5de

Please sign in to comment.