├── .ruby-version ├── .swift-version ├── BuildTools ├── Empty.swift ├── .gitignore ├── Package.swift ├── README.md └── Package.resolved ├── .swiftformat ├── keys ├── test.ec256.durl.key ├── test.ec256.pub.xurl.key ├── test.ec256.pub.yurl.key ├── message.txt ├── test.ec256.d.key ├── test.ec384.durl.key ├── test.ec256.sig ├── test.ec384.pub.xurl.key ├── test.ec384.pub.yurl.key ├── test.ec384.sig ├── test.ec521.sig ├── test.ec384.keyparam ├── test.ec521.keyparam ├── test.ec256.keyparam ├── test.ec521.durl.key ├── test.ec256.sig.b64 ├── test.ec384.d.key ├── test.ec521.pub.xurl.key ├── test.ec521.pub.yurl.key ├── test.ec256.pub.x.key ├── test.ec256.pub.y.key ├── test.ec521.d.key ├── test.ec384.sig.b64 ├── test.ec384.pub.x.key ├── test.ec384.pub.y.key ├── test.ec256.pub.key ├── test.ec521.sig.b64 ├── test.ec521.pub.x.key ├── test.ec521.pub.y.key ├── test.ec384.pub.key ├── test.ec256.key ├── test.ec521.pub.key ├── test.ec384.key └── test.ec521.key ├── Gemfile ├── .swiftlint.yml ├── JOSESwift.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── JOSESwift.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Tests.xcscheme │ └── JOSESwift.xcscheme ├── Package.swift ├── Tests ├── alice.pub.pem ├── bob.pub.pem ├── Info.plist ├── HMACVerifierTests.swift ├── HMACSignerTests.swift ├── RSAPublicKeyToDataTests.swift ├── ECPrivateKeyToDataTests.swift ├── ECPublicKeyToDataTests.swift ├── RSAVerifierTests.swift ├── PBES2Tests.swift ├── RSASignerTests.swift ├── JWEPBES2Tests.swift ├── RSAPublicKeyToSecKeyTests.swift ├── JWSSigningInputTest.swift ├── ECSignerTests.swift ├── ECVerifierTests.swift ├── ECPublicKeyToSecKeyTests.swift ├── DirectEncryptionKeyManagementModeTests.swift ├── JWSCustomSignerVerifierTests.swift ├── JWKtoJSONTests.swift ├── ECPrivateKeyToSecKeyTests.swift ├── CryptoTestCase.swift ├── JWEAESKeyWrapTests.swift ├── JWKSetCollectionTests.swift ├── HMACCryptoTestCase.swift ├── JWSECTests.swift ├── JWEECTests.swift ├── SecKeyECPublicKeyTests.swift ├── DataRSAPublicKeyTests.swift ├── JWSHMACTests.swift ├── HMACTests.swift ├── SecKeyECPrivateKeyTests.swift └── DataECPublicKeyTests.swift ├── .github ├── renovate.json ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── sonar-project.properties ├── scripts ├── update-license-year.sh └── xccov-to-sonarqube.sh ├── JOSESwift ├── Support │ ├── Info.plist │ └── JOSESwift.h └── Sources │ ├── Common │ ├── DataConvertible.swift │ ├── Payload.swift │ ├── Serializer.swift │ ├── ASN1DEREncoding.swift │ ├── JOSESwiftError.swift │ ├── AlgorithmExtensions.swift │ ├── Deserializer.swift │ └── JOSEHeader.swift │ ├── JWK │ ├── Symmetric │ │ ├── DataSymmetricKey.swift │ │ └── SymmetricKeyCodable.swift │ ├── JWKExtensions.swift │ ├── RSA │ │ ├── DataRSAPublicKey.swift │ │ └── SecKeyRSAPublicKey.swift │ ├── EC │ │ ├── DataECPublicKey.swift │ │ ├── ECKeyPair.swift │ │ ├── DataECPrivateKey.swift │ │ ├── SecKeyECPublicKey.swift │ │ └── SecKeyECPrivateKey.swift │ ├── JWKParameters.swift │ └── JWKSet.swift │ ├── JWS │ ├── DefaultCrypto │ │ ├── RSA │ │ │ ├── RSASigner.swift │ │ │ └── RSAVerifier.swift │ │ ├── EC │ │ │ ├── ECSigner.swift │ │ │ └── ECVerifier.swift │ │ └── HMAC │ │ │ ├── HMACSigner.swift │ │ │ └── HMACVerifier.swift │ ├── Verifier.swift │ └── Signer.swift │ ├── CryptoPrimitives │ ├── Randomness │ │ └── SecureRandom.swift │ ├── SHA │ │ └── Thumbprint.swift │ ├── AESGCM │ │ └── AESGCM.swift │ ├── HMAC │ │ └── HMAC.swift │ └── PBES2 │ │ └── PBES2.swift │ └── JWE │ ├── KeyManagement │ └── DefaultCrypto │ │ ├── DirectEncryption │ │ └── DirectEncryptionMode.swift │ │ └── AESKW │ │ └── AESKeyWrappingMode.swift │ ├── Compression │ └── Compressor.swift │ └── ContentEncryption │ ├── ContentEncryption.swift │ └── DefaultCrypto │ └── AESGCM │ └── AESGCMEncryption.swift ├── JOSESwift.podspec ├── SECURITY.md ├── .gitignore └── .circleci └── config.yml /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.0 2 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.10 2 | -------------------------------------------------------------------------------- /BuildTools/Empty.swift: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --disable all 2 | -------------------------------------------------------------------------------- /BuildTools/.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | -------------------------------------------------------------------------------- /keys/test.ec256.durl.key: -------------------------------------------------------------------------------- 1 | EupBKRVOSC4BkqUoiXCtVoIZv8glXvIDDkwdz0aKfiU 2 | -------------------------------------------------------------------------------- /keys/test.ec256.pub.xurl.key: -------------------------------------------------------------------------------- 1 | TvQ0_muDyvS4RX9bJm8Rzy9XTpSG7xwo3Ffgu8Oq7OY 2 | -------------------------------------------------------------------------------- /keys/test.ec256.pub.yurl.key: -------------------------------------------------------------------------------- 1 | saNr4hM3qrojSoY4eaO1WGVna5yW_I4EqdFQ4TRl8iQ 2 | -------------------------------------------------------------------------------- /keys/message.txt: -------------------------------------------------------------------------------- 1 | The true sign of intelligence is not knowledge but imagination. -------------------------------------------------------------------------------- /keys/test.ec256.d.key: -------------------------------------------------------------------------------- 1 | 12EA4129154E482E0192A5288970AD568219BFC8255EF2030E4C1DCF468A7E25 2 | -------------------------------------------------------------------------------- /keys/test.ec384.durl.key: -------------------------------------------------------------------------------- 1 | -VHKhu9gi6tC69NobYpH2TWXE1B_5IDnpvzQNqS11n6m6Ww20feAxIO2UWFbdjSp 2 | -------------------------------------------------------------------------------- /keys/test.ec256.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airsidemobile/JOSESwift/HEAD/keys/test.ec256.sig -------------------------------------------------------------------------------- /keys/test.ec384.pub.xurl.key: -------------------------------------------------------------------------------- 1 | m7lcNx4D36LdfX-MoDCKG05I7lCv9AtTN6n_u8Rsijt-Jpz9JBMhSPrSEmZ1dvCf 2 | -------------------------------------------------------------------------------- /keys/test.ec384.pub.yurl.key: -------------------------------------------------------------------------------- 1 | h3zkf9eaHcJ7Dw7SMPfKwJ2PEgHYeZ-P-vOxlBS_OsBx7RFz-yNan1e_P0hS-LgN 2 | -------------------------------------------------------------------------------- /keys/test.ec384.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airsidemobile/JOSESwift/HEAD/keys/test.ec384.sig -------------------------------------------------------------------------------- /keys/test.ec521.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airsidemobile/JOSESwift/HEAD/keys/test.ec521.sig -------------------------------------------------------------------------------- /keys/test.ec384.keyparam: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIg== 3 | -----END EC PARAMETERS----- 4 | -------------------------------------------------------------------------------- /keys/test.ec521.keyparam: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIw== 3 | -----END EC PARAMETERS----- 4 | -------------------------------------------------------------------------------- /keys/test.ec256.keyparam: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BggqhkjOPQMBBw== 3 | -----END EC PARAMETERS----- 4 | -------------------------------------------------------------------------------- /keys/test.ec521.durl.key: -------------------------------------------------------------------------------- 1 | AbYMXgy5IeW30rgkcBsTskJXXCLZjUj5NcqmfmEZL5RmB4AM7m07qYugflzzx869he1h6N3KLbiaA20iExTxWxcu 2 | -------------------------------------------------------------------------------- /keys/test.ec256.sig.b64: -------------------------------------------------------------------------------- 1 | MEUCIQDXGf5sy-xoOiWUNw7BqTpVUc2Lw6JyN0sP6WXFM3IwcgIgSVl_U_y4MIkOp4ilKN6BjLBVHVliKVYFDUOE22MivFI 2 | -------------------------------------------------------------------------------- /keys/test.ec384.d.key: -------------------------------------------------------------------------------- 1 | F951CA86EF608BAB42EBD3686D8A47D9359713507FE480E7A6FCD036A4B5D67EA6E96C36D1F780C483B651615B7634A9 2 | -------------------------------------------------------------------------------- /keys/test.ec521.pub.xurl.key: -------------------------------------------------------------------------------- 1 | AQu_5zLkErGrlNXre2NJvyydEz0e4gpIJH2jyS4tysTlwRSW1UJ1fUkF4byCFcKAyMq8stD_mN0b88gGn7W8d6yD 2 | -------------------------------------------------------------------------------- /keys/test.ec521.pub.yurl.key: -------------------------------------------------------------------------------- 1 | ASeWzvPD_vogLYkT1GkyXEpwR3V94hKVCrWGNLFvlAJ-CpgGO2XA_MnvjwoCNzV8kElKz2G7K3thxzamfJot-lYh 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods' 4 | gem 'rb-readline' 5 | gem 'fastlane' 6 | gem 'xcpretty' 7 | -------------------------------------------------------------------------------- /keys/test.ec256.pub.x.key: -------------------------------------------------------------------------------- 1 | 4e f4 34 fe 6b 83 ca f4 b8 45 7f 5b 26 6f 11 cf 2f 57 4e 94 86 ef 1c 28 dc 57 e0 bb c3 aa ec e6 2 | -------------------------------------------------------------------------------- /keys/test.ec256.pub.y.key: -------------------------------------------------------------------------------- 1 | b1 a3 6b e2 13 37 aa ba 23 4a 86 38 79 a3 b5 58 65 67 6b 9c 96 fc 8e 04 a9 d1 50 e1 34 65 f2 24 2 | -------------------------------------------------------------------------------- /keys/test.ec521.d.key: -------------------------------------------------------------------------------- 1 | 01B60C5E0CB921E5B7D2B824701B13B242575C22D98D48F935CAA67E61192F946607800CEE6D3BA98BA07E5CF3C7CEBD85ED61E8DDCA2DB89A036D221314F15B172E 2 | -------------------------------------------------------------------------------- /keys/test.ec384.sig.b64: -------------------------------------------------------------------------------- 1 | MGUCMBST8cdeI4mCEtgx3Z1p-XM5u6OYgAOdM7xF2sw_EqWICoGZxx4CXokuoSKuR2O_igIxAJzykizFCjl_y5gjsoujibJ0OeyqUyhux2aeuvTjGf7qX_14pgqGXBq1cbXgEYCM3g 2 | -------------------------------------------------------------------------------- /keys/test.ec384.pub.x.key: -------------------------------------------------------------------------------- 1 | 9b b9 5c 37 1e 03 df a2 dd 7d 7f 8c a0 30 8a 1b 4e 48 ee 50 af f4 0b 53 37 a9 ff bb c4 6c 8a 3b 7e 26 9c fd 24 13 21 48 fa d2 12 66 75 76 f0 9f 2 | -------------------------------------------------------------------------------- /keys/test.ec384.pub.y.key: -------------------------------------------------------------------------------- 1 | 87 7c e4 7f d7 9a 1d c2 7b 0f 0e d2 30 f7 ca c0 9d 8f 12 01 d8 79 9f 8f fa f3 b1 94 14 bf 3a c0 71 ed 11 73 fb 23 5a 9f 57 bf 3f 48 52 f8 b8 0d 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - vendor 3 | - BuildTools/.build 4 | 5 | only_rules: 6 | - custom_rules 7 | 8 | custom_rules: 9 | noop: 10 | regex: "noop^" 11 | match_kinds: comment 12 | -------------------------------------------------------------------------------- /keys/test.ec256.pub.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETvQ0/muDyvS4RX9bJm8Rzy9XTpSG 3 | 7xwo3Ffgu8Oq7Oaxo2viEzequiNKhjh5o7VYZWdrnJb8jgSp0VDhNGXyJA== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /keys/test.ec521.sig.b64: -------------------------------------------------------------------------------- 1 | MIGIAkIB2sLvjVjwLYIfEIUnpNY5zecLi817rTfN8lpkYo1kBq4YLpYZRSjKXWDrgPZUo89Qw4b8cacI-PctUnw713ExjVMCQgCZFoBuQVPed2sAGF_wAUigBU9RCOV33UfVyqpYM7FuAEYSGafTreH-dcCWQr5mnkwkdCIhl1iLbXJe-9HWVgjImA 2 | -------------------------------------------------------------------------------- /keys/test.ec521.pub.x.key: -------------------------------------------------------------------------------- 1 | 01 0b bf e7 32 e4 12 b1 ab 94 d5 eb 7b 63 49 bf 2c 9d 13 3d 1e e2 0a 48 24 7d a3 c9 2e 2d ca c4 e5 c1 14 96 d5 42 75 7d 49 05 e1 bc 82 15 c2 80 c8 ca bc b2 d0 ff 98 dd 1b f3 c8 06 9f b5 bc 77 ac 83 2 | -------------------------------------------------------------------------------- /keys/test.ec521.pub.y.key: -------------------------------------------------------------------------------- 1 | 01 27 96 ce f3 c3 fe fa 20 2d 89 13 d4 69 32 5c 4a 70 47 75 7d e2 12 95 0a b5 86 34 b1 6f 94 02 7e 0a 98 06 3b 65 c0 fc c9 ef 8f 0a 02 37 35 7c 90 49 4a cf 61 bb 2b 7b 61 c7 36 a6 7c 9a 2d fa 56 21 2 | -------------------------------------------------------------------------------- /JOSESwift.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JOSESwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /keys/test.ec384.pub.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEm7lcNx4D36LdfX+MoDCKG05I7lCv9AtT 3 | N6n/u8Rsijt+Jpz9JBMhSPrSEmZ1dvCfh3zkf9eaHcJ7Dw7SMPfKwJ2PEgHYeZ+P 4 | +vOxlBS/OsBx7RFz+yNan1e/P0hS+LgN 5 | -----END PUBLIC KEY----- 6 | -------------------------------------------------------------------------------- /keys/test.ec256.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIBLqQSkVTkguAZKlKIlwrVaCGb/IJV7yAw5MHc9Gin4loAoGCCqGSM49 3 | AwEHoUQDQgAETvQ0/muDyvS4RX9bJm8Rzy9XTpSG7xwo3Ffgu8Oq7Oaxo2viEzeq 4 | uiNKhjh5o7VYZWdrnJb8jgSp0VDhNGXyJA== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /keys/test.ec521.pub.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBC7/nMuQSsauU1et7Y0m/LJ0TPR7i 3 | CkgkfaPJLi3KxOXBFJbVQnV9SQXhvIIVwoDIyryy0P+Y3RvzyAaftbx3rIMBJ5bO 4 | 88P++iAtiRPUaTJcSnBHdX3iEpUKtYY0sW+UAn4KmAY7ZcD8ye+PCgI3NXyQSUrP 5 | Ybsre2HHNqZ8mi36ViE= 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /JOSESwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /keys/test.ec384.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIGkAgEBBDD5UcqG72CLq0Lr02htikfZNZcTUH/kgOem/NA2pLXWfqbpbDbR94DE 3 | g7ZRYVt2NKmgBwYFK4EEACKhZANiAASbuVw3HgPfot19f4ygMIobTkjuUK/0C1M3 4 | qf+7xGyKO34mnP0kEyFI+tISZnV28J+HfOR/15odwnsPDtIw98rAnY8SAdh5n4/6 5 | 87GUFL86wHHtEXP7I1qfV78/SFL4uA0= 6 | -----END EC PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /keys/test.ec521.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIHcAgEBBEIBtgxeDLkh5bfSuCRwGxOyQldcItmNSPk1yqZ+YRkvlGYHgAzubTup 3 | i6B+XPPHzr2F7WHo3cotuJoDbSITFPFbFy6gBwYFK4EEACOhgYkDgYYABAELv+cy 4 | 5BKxq5TV63tjSb8snRM9HuIKSCR9o8kuLcrE5cEUltVCdX1JBeG8ghXCgMjKvLLQ 5 | /5jdG/PIBp+1vHesgwEnls7zw/76IC2JE9RpMlxKcEd1feISlQq1hjSxb5QCfgqY 6 | BjtlwPzJ748KAjc1fJBJSs9huyt7Ycc2pnyaLfpWIQ== 7 | -----END EC PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /BuildTools/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.10 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "BuildTools", 6 | platforms: [.macOS(.v14)], 7 | dependencies: [ 8 | .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.58.7"), 9 | .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", from: "0.62.2") 10 | ], 11 | targets: [.target(name: "BuildTools", path: "")] 12 | ) 13 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "JOSESwift", 6 | platforms: [.iOS(.v13), .macOS(.v10_15), .watchOS(.v8), .tvOS(.v15), .visionOS(.v1)], 7 | products: [ 8 | .library(name: "JOSESwift", targets: ["JOSESwift"]) 9 | ], 10 | dependencies: [], 11 | targets: [ 12 | .target(name: "JOSESwift", path: "JOSESwift") 13 | ], 14 | swiftLanguageVersions: [.v5]) 15 | -------------------------------------------------------------------------------- /Tests/alice.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiADzxMJ+l/NIVPbqz9eo 3 | BenUCCUiNNfZ37c6gUJwWEfJRyGchAe96m4GLr3pzj2A3Io4MSKf9dDWMak6qkR/ 4 | XYljSjZBbXAhQan2sIB5qyPW7NJ7XpJWHoaHdHwEN9Cj29zL+WtFk6lC1rPDmNPR 5 | TmRy0ct4EP4YJ49PMcoKJQKbog79ws1KdDzNGTVVkEgLB4VOlW8A164kaK8+xMUx 6 | TqySUtigLTDUMqjQ/81SFgsNnMUqnxp87bKD77olYBia88r8V2YXEx1Jgl8t22gN 7 | Nh6lkN8BDqlkb/Y2uS+c7vlYIfSH6WYkVsSPsrA+GLLRo/R07FGxvs2M5gZxnmlv 8 | ewIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /Tests/bob.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQ7PgCbJbhcuTO/ah0Zm 3 | f81I7+sGPhgScbzaHMBO88IXaNcIlfqOcUbymUUp5UaexoLOpphiuetq08l7X6aw 4 | UeY/YfgtPVpehdFg8gx/tzc0Nm8CT6OCeptiJzEJXoOuuRLuicVcjQP1pdL73Si+ 5 | l334dQyk62CRi5rxiTZZncl4DxMOZI/nkfTYoqb+Y8ncRUiDMozjaS/OYihjyouw 6 | qDGJmbFiImfCHylc3fc/31uuAszBH+x4DjDOnSjkrU2h6fMJ27fA5W2/BI1tEk2h 7 | R5xsWEb1fcy1rSJ/xoXQcqWmWLpzJTGbvWLwnWjldHK9HuLpPObZyhXQrP5fj0Gk 8 | xwIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "packageRules": [ 7 | { 8 | "matchUpdateTypes": [ 9 | "minor", 10 | "patch" 11 | ], 12 | "automerge": true 13 | }, 14 | { 15 | "matchUpdateTypes": [ 16 | "lockFileMaintenance" 17 | ], 18 | "automerge": true 19 | } 20 | ], 21 | "lockFileMaintenance": { 22 | "enabled": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BuildTools/README.md: -------------------------------------------------------------------------------- 1 | # BuildTools 2 | 3 | Use Swift Package Manager to setup build tools and enforce the same version of these tools being used for development and in CI. 4 | 5 | Resolve build tools by running: 6 | 7 | ``` 8 | swift package resolve 9 | ``` 10 | 11 | in this directory. 12 | 13 | ## Updating Build Tools 14 | 15 | To update the version of build tools to the latest version matching this pacakge's `Package.swift` file, run: 16 | 17 | ``` 18 | swift package update 19 | ``` 20 | 21 | in this directory. 22 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.host.url=https://sonarcloud.io 2 | 3 | sonar.projectKey=airsidemobile_JOSESwift 4 | sonar.projectName=JOSESwift 5 | 6 | sonar.sourceEncoding=UTF-8 7 | sonar.swift.excludedPathsFromCoverage=**/*Tests*/** 8 | sonar.swift.file.suffixes=.swift 9 | 10 | sonar.cpd.exclusions=**/*Tests*/** 11 | 12 | sonar.workspace=JOSESwift.xcworkspace 13 | sonar.scheme=JOSESwift 14 | sonar.sources=JOSESwift/Sources 15 | sonar.tests=Tests 16 | 17 | sonar.externalIssuesReportPaths="./fastlane/test_output/swiftformat/report.sonarqube","./fastlane/test_output/swiftlint/report.sonarqube" 18 | -------------------------------------------------------------------------------- /scripts/update-license-year.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Updates the year in the license header of Swift files to the current year 4 | 5 | correct_license_header="Copyright $(date +%Y) Airside Mobile Inc." # we want this header 6 | any_license_header="Copyright [0-9]* Airside Mobile Inc." # this could be any header 7 | 8 | # 1. find all Swift files 9 | # 2. filter out those not having the correct license header 10 | # 3. replace whatever license header they have with the correct license header 11 | find JOSESwift/Sources Tests -name "*.swift" \ 12 | | xargs grep -lv "$correct_license_header" \ 13 | | xargs sed -i '' "s/$any_license_header/$correct_license_header/" 14 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 3.0.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /BuildTools/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "56f1795190186ac5786bb83ae903f58b55f66c9c8d3e6470614a89e47f909786", 3 | "pins" : [ 4 | { 5 | "identity" : "swiftformat", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/nicklockwood/SwiftFormat", 8 | "state" : { 9 | "revision" : "d6309f7440889427426143b4a0b100b959d3f3e6", 10 | "version" : "0.54.3" 11 | } 12 | }, 13 | { 14 | "identity" : "swiftlintplugins", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/SimplyDanny/SwiftLintPlugins", 17 | "state" : { 18 | "revision" : "6c3d6c32a37224179dc290f21e03d1238f3d963b", 19 | "version" : "0.56.2" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /JOSESwift/Support/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.0.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /JOSESwift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "JOSESwift" 3 | s.version = "3.0.0" 4 | s.license = "Apache License, Version 2.0" 5 | 6 | s.summary = "JOSE framework for Swift" 7 | s.authors = { "Airside Mobile, Inc. an Entrust Company" => "joseswift@airsidemobile.com" } 8 | s.homepage = "https://github.com/airsidemobile/JOSESwift" 9 | s.documentation_url = "https://github.com/airsidemobile/JOSESwift/wiki" 10 | 11 | s.swift_version = "5.0" 12 | 13 | s.ios.deployment_target = '13.0' 14 | s.osx.deployment_target = '10.15' 15 | s.watchos.deployment_target = '8.0' 16 | s.tvos.deployment_target = '15.0' 17 | s.visionos.deployment_target = '1.0' 18 | 19 | s.source = { :git => "https://github.com/airsidemobile/JOSESwift.git", :tag => "#{s.version}" } 20 | s.source_files = "JOSESwift/**/*.{h,swift}" 21 | end 22 | -------------------------------------------------------------------------------- /scripts/xccov-to-sonarqube.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # Adapted from 5 | # https://docs.sonarqube.org/display/PLUG/Swift+Coverage+Results+Import 6 | # https://github.com/SonarSource/sonar-scanning-examples/blob/master/swift-coverage/swift-coverage-example/xccov-to-sonarqube-generic.sh 7 | 8 | function convert_file { 9 | local xcresult_bundle="$1" 10 | local file_name="$2" 11 | echo " " 12 | xcrun xccov view --archive --file "$file_name" "$xcresult_bundle" | \ 13 | sed -n ' 14 | s/^ *\([0-9][0-9]*\): 0.*$/ /p; 15 | s/^ *\([0-9][0-9]*\): [1-9].*$/ /p 16 | ' 17 | echo ' ' 18 | } 19 | 20 | function xccov_to_generic { 21 | echo '' 22 | for xcresult_bundle in "$@"; do 23 | xcrun xccov view --archive --file-list "$xcresult_bundle" | while read -r file_name; do 24 | convert_file "$xcresult_bundle" "$file_name" 25 | done 26 | done 27 | echo '' 28 | } 29 | 30 | xccov_to_generic "$@" 31 | -------------------------------------------------------------------------------- /JOSESwift/Sources/Common/DataConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataConvertible.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 26/09/2017. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | public protocol DataConvertible { 27 | init?(_ data: Data) 28 | func data() -> Data 29 | } 30 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Cryptography 4 | 5 | JOSESwift exclusively uses the [iOS Security framework](https://developer.apple.com/documentation/security) and [Apple’s CommonCrypto](https://opensource.apple.com//source/CommonCrypto/) for cryptographic primitives. 6 | 7 | At the moment, we do not have plans to accept implementations that make use of cryptographic implementations that are not part of Apple’s iOS SDKs. 8 | 9 | However, we might consider extending the library in a way that would allow the current cryptographic implementation (based on Apple’s SDKs) to _optionally_ be switched out by a different implementation if this enables JOSESwift to run in a pure Swift environment. Please contact us if this is something you are thinking about. 10 | 11 | ## Supported Versions 12 | 13 | Please make sure to always update to the latest version to receive security related patches. 14 | 15 | ## Reporting a Vulnerability 16 | 17 | For security disclosures or related matters, please contact . We will do our best to review and respond to security disclosures with the highest priority. 18 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/Symmetric/DataSymmetricKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataSymmetricKey.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 10.07.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | extension Data: ExpressibleAsSymmetricKeyComponents { 27 | public static func representing(symmetricKeyComponents components: SymmetricKeyComponents) throws -> Data { 28 | return components 29 | } 30 | 31 | public func symmetricKeyComponents() throws -> SymmetricKeyComponents { 32 | return self 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /JOSESwift/Support/JOSESwift.h: -------------------------------------------------------------------------------- 1 | // 2 | // JOSESwift.h 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 17/08/2017. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2018 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | #import 25 | 26 | //! Project version number for JOSESwift. 27 | FOUNDATION_EXPORT double JOSESwiftVersionNumber; 28 | 29 | //! Project version string for JOSESwift. 30 | FOUNDATION_EXPORT const unsigned char JOSESwiftVersionString[]; 31 | 32 | // In this header, you should import all the public headers of your framework using statements like #import 33 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWS/DefaultCrypto/RSA/RSASigner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSASigner.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 21/08/2017. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | /// A `Signer` to sign an input with an `RSA` algorithm. 27 | internal struct RSASigner: SignerProtocol { 28 | typealias KeyType = RSA.KeyType 29 | 30 | let algorithm: SignatureAlgorithm 31 | let privateKey: KeyType 32 | 33 | func sign(_ signingInput: Data) throws -> Data { 34 | return try RSA.sign(signingInput, with: privateKey, and: algorithm) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWS/DefaultCrypto/EC/ECSigner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ECSigner.swift 3 | // JOSESwift 4 | // 5 | // Created by Jarrod Moldrich on 02.07.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | /// A `Signer` to sign an input with an elliptic curve algorithm. 27 | internal struct ECSigner: SignerProtocol { 28 | typealias KeyType = EC.KeyType 29 | 30 | let algorithm: SignatureAlgorithm 31 | let privateKey: KeyType 32 | 33 | func sign(_ signingInput: Data) throws -> Data { 34 | return try EC.sign(signingInput, with: privateKey, and: algorithm) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWS/DefaultCrypto/RSA/RSAVerifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSAVerifier.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 28/09/2017. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | /// A `Verifier` to verify a signature created with a `RSA` algorithm. 27 | internal struct RSAVerifier: VerifierProtocol { 28 | typealias KeyType = RSA.KeyType 29 | 30 | let algorithm: SignatureAlgorithm 31 | let publicKey: KeyType 32 | 33 | func verify(_ verifyingInput: Data, against signature: Data) throws -> Bool { 34 | return try RSA.verify(verifyingInput, against: signature, with: publicKey, and: algorithm) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWS/DefaultCrypto/EC/ECVerifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ECVerifier.swift 3 | // JOSESwift 4 | // 5 | // Created by Jarrod Moldrich on 02.07.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | /// A `Verifier` to verify a signature created with an elliptic curve algorithm. 27 | internal struct ECVerifier: VerifierProtocol { 28 | typealias KeyType = EC.KeyType 29 | 30 | let algorithm: SignatureAlgorithm 31 | let publicKey: KeyType 32 | 33 | func verify(_ verifyingInput: Data, against signature: Data) throws -> Bool { 34 | return try EC.verify(verifyingInput, against: signature, with: publicKey, and: algorithm) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /JOSESwift/Sources/Common/Payload.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Payload.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 20/09/2017. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | public struct Payload: DataConvertible { 27 | let payload: Data 28 | 29 | public init(_ payload: Data) { 30 | self.payload = payload 31 | } 32 | 33 | public func data() -> Data { 34 | return payload 35 | } 36 | 37 | func compressed(using algorithm: CompressionAlgorithm?) throws -> Payload { 38 | let compressor = try CompressorFactory.makeCompressor(algorithm: algorithm) 39 | return Payload(try compressor.compress(data: payload)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWS/DefaultCrypto/HMAC/HMACSigner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HMACSigner.swift 3 | // JOSESwift 4 | // 5 | // Created by Tobias Hagemann on 14.04.21. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | /// A `Signer` to sign an input with an `HMAC` algorithm. 27 | internal struct HMACSigner: SignerProtocol { 28 | typealias KeyType = HMAC.KeyType 29 | 30 | let algorithm: SignatureAlgorithm 31 | let key: KeyType 32 | 33 | func sign(_ signingInput: Data) throws -> Data { 34 | guard let hmacAlgorithm = algorithm.hmacAlgorithm else { 35 | throw HMACError.algorithmNotSupported 36 | } 37 | return try HMAC.calculate(from: signingInput, with: key, using: hmacAlgorithm) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/HMACVerifierTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HMACVerifierTests.swift 3 | // Tests 4 | // 5 | // Created by Tobias Hagemann on 14.04.21. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | class HMACVerifierTests: HMACCryptoTestCase { 28 | private func _testVerifying(algorithm: SignatureAlgorithm, expectedSignature: Data) { 29 | let verifier = HMACVerifier(algorithm: algorithm, key: testKey) 30 | XCTAssertTrue(try! verifier.verify(testData, against: expectedSignature)) 31 | } 32 | 33 | func testVerifying() { 34 | _testVerifying(algorithm: .HS256, expectedSignature: hmac256TestOutput) 35 | _testVerifying(algorithm: .HS384, expectedSignature: hmac384TestOutput) 36 | _testVerifying(algorithm: .HS512, expectedSignature: hmac512TestOutput) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/JWKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JWKExtensions.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 21.12.17. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | // MARK: Subscript 27 | 28 | public extension JWK { 29 | subscript(parameter: String) -> String? { 30 | return parameters[parameter] 31 | } 32 | } 33 | 34 | // MARK: Encoding Convenience Functions 35 | 36 | public extension JWK { 37 | func jsonString() -> String? { 38 | guard let json = jsonData() else { 39 | return nil 40 | } 41 | 42 | return String(data: json, encoding: .utf8) 43 | } 44 | 45 | func jsonData() -> Data? { 46 | let encoder = JSONEncoder() 47 | encoder.outputFormatting = .sortedKeys 48 | return try? encoder.encode(self) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/HMACSignerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HMACSignerTests.swift 3 | // Tests 4 | // 5 | // Created by Tobias Hagemann on 14.04.21. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | class HMACSignerTests: HMACCryptoTestCase { 28 | private func _testSigning(algorithm: SignatureAlgorithm, expectedSignature: Data) { 29 | let signer = HMACSigner(algorithm: algorithm, key: testKey) 30 | let signature = try! signer.sign(testData) 31 | XCTAssertEqual(expectedSignature, signature) 32 | } 33 | 34 | func testSigning() { 35 | _testSigning(algorithm: .HS256, expectedSignature: hmac256TestOutput) 36 | _testSigning(algorithm: .HS384, expectedSignature: hmac384TestOutput) 37 | _testSigning(algorithm: .HS512, expectedSignature: hmac512TestOutput) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/RSAPublicKeyToDataTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSAPublicKeyToDataTests.swift 3 | // Tests 4 | // 5 | // Created by Daniel Egger on 12.02.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | class RSAPublicKeyToDataTests: RSACryptoTestCase { 28 | 29 | func testpublicKeyAlice2048ToData() { 30 | let jwk = RSAPublicKey(modulus: expectedModulus2048Base64, exponent: expectedExponentBase64) 31 | let data = try! jwk.converted(to: Data.self) 32 | 33 | XCTAssertEqual(data, publicKeyAlice2048Data) 34 | } 35 | 36 | func testPublicKey4096ToData() { 37 | let jwk = RSAPublicKey(modulus: expectedModulus4096Base64, exponent: expectedExponentBase64) 38 | let data = try! jwk.converted(to: Data.self) 39 | 40 | XCTAssertEqual(data, publicKey4096Data) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Tests/ECPrivateKeyToDataTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ECPrivateKeyToDataTests.swift 3 | // Tests 4 | // 5 | // Created by Jarrod Moldrich on 10.01.2019. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | class ECPrivateKeyToDataTests: ECCryptoTestCase { 28 | 29 | func testPrivateKeyToData() { 30 | allTestData.forEach { testData in 31 | let jwk = try! ECPrivateKey( 32 | crv: testData.expectedCurveType, 33 | x: testData.expectedXCoordinateBase64Url, 34 | y: testData.expectedYCoordinateBase64Url, 35 | privateKey: testData.expectedPrivateBase64Url 36 | ) 37 | let data = try! jwk.converted(to: Data.self) 38 | 39 | XCTAssertEqual(data, testData.privateKeyData) 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Tests/ECPublicKeyToDataTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // ECPublicKeyToDataTests.swift 4 | // Tests 5 | // 6 | // Created by Jarrod Moldrich on 27.10.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class ECPublicKeyToDataTests: ECCryptoTestCase { 29 | 30 | func testPublicKeyToData() { 31 | allTestData.forEach { testData in 32 | let jwk = ECPublicKey( 33 | crv: ECCurveType(rawValue: testData.expectedCurveType)!, 34 | x: testData.expectedXCoordinateBase64Url, 35 | y: testData.expectedYCoordinateBase64Url) 36 | let data = try! jwk.converted(to: Data.self) 37 | 38 | XCTAssertEqual(data, testData.publicKeyData) 39 | } 40 | } 41 | 42 | } 43 | // swiftlint:enable force_unwrapping 44 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWS/DefaultCrypto/HMAC/HMACVerifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HMACVerifier.swift 3 | // JOSESwift 4 | // 5 | // Created by Tobias Hagemann on 14.04.21. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | /// A `Verifier` to verify a signature created with an `HMAC` algorithm. 27 | internal struct HMACVerifier: VerifierProtocol { 28 | typealias KeyType = HMAC.KeyType 29 | 30 | let algorithm: SignatureAlgorithm 31 | let key: KeyType 32 | 33 | func verify(_ signingInput: Data, against signature: Data) throws -> Bool { 34 | guard let hmacAlgorithm = algorithm.hmacAlgorithm else { 35 | throw HMACError.algorithmNotSupported 36 | } 37 | let hmacOutput = try HMAC.calculate(from: signingInput, with: key, using: hmacAlgorithm) 38 | return hmacOutput.timingSafeCompare(with: signature) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/RSAVerifierTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // RSAVerifierTests.swift 4 | // Tests 5 | // 6 | // Created by Carol Capek on 03.11.17. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class RSAVerifierTests: RSACryptoTestCase { 29 | 30 | func testVerifying() { 31 | guard publicKeyAlice2048 != nil else { 32 | XCTFail() 33 | return 34 | } 35 | 36 | let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) 37 | let verifier = RSAVerifier(algorithm: .RS512, publicKey: publicKeyAlice2048!) 38 | 39 | guard let signingInput = [jws.header, jws.payload].asJOSESigningInput() else { 40 | XCTFail() 41 | return 42 | } 43 | 44 | XCTAssertTrue(try! verifier.verify(signingInput, against: jws.signature)) 45 | } 46 | 47 | } 48 | // swiftlint:enable force_unwrapping 49 | -------------------------------------------------------------------------------- /Tests/PBES2Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PBES2Tests.swift 3 | // JOSESwift 4 | // 5 | // Created by Tobias Hagemann on 11.12.23. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | class PBES2Tests: XCTestCase { 28 | /// Tests the PBES2 key derivation with the test data provided in the [RFC-7517](https://www.rfc-editor.org/rfc/rfc7517#appendix-C.4). 29 | func testDeriveWrappingKey() throws { 30 | let password = "Thus from my lips, by yours, my sin is purged." 31 | let saltInput = Data([217, 96, 147, 112, 150, 117, 70, 247, 127, 8, 155, 137, 174, 42, 80, 215]) 32 | let iterationCount = 4096 33 | let derivedKey = try PBES2.deriveWrappingKey(password: password, algorithm: .PBES2_HS256_A128KW, saltInput: saltInput, iterationCount: iterationCount) 34 | let expectedKey = Data([110, 171, 169, 92, 129, 92, 109, 117, 233, 242, 116, 233, 170, 14, 24, 75]) 35 | XCTAssertEqual(derivedKey, expectedKey) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/RSASignerTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // RSASignerTests.swift 4 | // Tests 5 | // 6 | // Created by Carol Capek on 02.11.17. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class RSASignerTests: RSACryptoTestCase { 29 | let signatureBase64URL = """ 30 | Zs9rmMw-za1uXpUS2VIOcEHaMuzQl6fBCi_40kRVIE0GUruWSvpHro1oXhGwf7HqKPLx_LM8bL\ 31 | PCORWi9OWU4swZHY8p-GR5rhLLs2XkdIvI5kdbikr7pZOsC9NaxJKMWAntKbTZY6exkoU8vM6x\ 32 | L9MQtH8QFLXTjI-ZvAXbp2Ws9CnIcvOPFqAupuUKADFRpSlODlsXy71CJ3iQeBaPfHvLk61jdW\ 33 | 6hgHYj-0WYmrFhiF1dI9MZf9J3ApdKFsW0WFuxa8Y47HlCirEOb3lz7vm8o9lNBTnt0dpWOZMT\ 34 | U6lHuTBgiXvyENzJoKJymVsUbMmy-2LNejmVpPt1Pm3zrA 35 | """ 36 | 37 | func testSigning() { 38 | XCTAssertNotNil(privateKeyAlice2048) 39 | 40 | let signer = RSASigner(algorithm: .RS512, privateKey: privateKeyAlice2048!) 41 | let signature = try! signer.sign(message.data(using: .utf8)!) 42 | 43 | XCTAssertEqual(signature.base64URLEncodedString(), signatureBase64URL) 44 | } 45 | } 46 | // swiftlint:enable force_unwrapping 47 | -------------------------------------------------------------------------------- /JOSESwift/Sources/Common/Serializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JOSESerializer.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 21/09/2017. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | public protocol CompactSerializable { 27 | func serialize(to serializer: inout CompactSerializer) 28 | } 29 | 30 | public protocol CompactSerializer { 31 | var components: [DataConvertible] { get } 32 | mutating func serialize(_ object: T) 33 | } 34 | 35 | public struct JOSESerializer { 36 | public func serialize(compact object: T) -> String { 37 | var serializer: CompactSerializer = _CompactSerializer() 38 | object.serialize(to: &serializer) 39 | let base64URLEncodings = serializer.components.map { component in component.data().base64URLEncodedString() } 40 | return base64URLEncodings.joined(separator: ".") 41 | } 42 | } 43 | 44 | private struct _CompactSerializer: CompactSerializer { 45 | var components: [DataConvertible] = [] 46 | 47 | mutating func serialize(_ object: T) { 48 | components.append(object) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to JOSESwift 2 | 3 | Thanks for taking the time to contribute to the project. 4 | 5 | :tada: First step done: All kinds of contributions are highly appreciated and very welcome. 6 | 7 | ## Code of Conduct 8 | 9 | Everyone who participates in this project sticks to the [Code of Conduct](CODE_OF_CONDUCT.md). 10 | 11 | ## I want to contribute, but how? 12 | 13 | For feature contributions or bug fixes, please submit a pull request. For feature requests, discussions or bug reports, just open an issue. 14 | 15 | ### Pull Request Process 16 | 17 | 1. If applicable, make sure your pull request includes tests that thoroughly cover newly added lines. 18 | 2. Please don’t forget to update the [README.md](../README.md) and other documentation with relevant details of changes. 19 | 3. Submit your pull request to `master`. 20 | 4. Provide a detailed description of your changes in the pull request description. Reference any resolved issues or previous discussions in other pull requests. 21 | 5. Pull requests will need to be approved before being merged. The better the pull request description, the easier and quicker a pull request will be to review. 22 | 6. Once approved, the pull request can be squash merged into master and we’re done. :tada: 23 | 24 | ### General 25 | 26 | Please make sure to stick to general coding best practices and Swift code style as good as possible. You can find more info on that in GitHub’s [Swift Style Guide](https://github.com/github/swift-style-guide), the official Swift [API Design Guidelines](https://swift.org/documentation/api-design-guidelines/). 27 | 28 | Please also make sure that relevant code is documented and tested. :page_with_curl: 29 | 30 | More information on how to create a pull request can be found in [this tutorial](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). :mortar_board: 31 | 32 | GitHub also has a nice blog post on [how to create the perfect pull request](https://github.com/blog/1943-how-to-write-the-perfect-pull-request). :sparkles: 33 | -------------------------------------------------------------------------------- /JOSESwift.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/xcode,macos,carthage,cocoapods 3 | 4 | ### Carthage ### 5 | # Carthage 6 | # 7 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 8 | # Carthage/Checkouts 9 | 10 | Carthage/Build 11 | 12 | ### CocoaPods ### 13 | ## CocoaPods GitIgnore Template 14 | 15 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 16 | # - Also handy if you have a large number of dependant pods 17 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 18 | Pods/ 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | ### Xcode ### 49 | # Xcode 50 | # 51 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 52 | 53 | ## User settings 54 | xcuserdata/ 55 | 56 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 57 | *.xcscmblueprint 58 | *.xccheckout 59 | 60 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 61 | build/ 62 | DerivedData/ 63 | *.moved-aside 64 | *.pbxuser 65 | !default.pbxuser 66 | *.mode1v3 67 | !default.mode1v3 68 | *.mode2v3 69 | !default.mode2v3 70 | *.perspectivev3 71 | !default.perspectivev3 72 | 73 | ### Xcode Patch ### 74 | *.xcodeproj/* 75 | !*.xcodeproj/project.pbxproj 76 | !*.xcodeproj/xcshareddata/ 77 | !*.xcworkspace/contents.xcworkspacedata 78 | /*.gcno 79 | 80 | 81 | # End of https://www.gitignore.io/api/xcode,macos,carthage,cocoapods 82 | 83 | # fastlane specific 84 | fastlane/report.xml 85 | fastlane/test_output 86 | 87 | # Sonarqube 88 | .scannerwork 89 | 90 | -------------------------------------------------------------------------------- /Tests/JWEPBES2Tests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // JWEPBES2Tests.swift 4 | // JOSESwift 5 | // 6 | // Created by Tobias Hagemann on 11.12.23. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class JWEPBES2Tests: XCTestCase { 29 | func test() throws { 30 | var header = JWEHeader(keyManagementAlgorithm: .PBES2_HS256_A128KW, contentEncryptionAlgorithm: .A256CBCHS512) 31 | header.p2c = 1000 32 | let payload = Payload("Live long and prosper.".data(using: .utf8)!) 33 | let password = "123456" 34 | 35 | let encrypter = Encrypter(keyManagementAlgorithm: .PBES2_HS256_A128KW, contentEncryptionAlgorithm: .A256CBCHS512, encryptionKey: password, pbes2SaltInputLength: 32) 36 | let jwe = try JWE(header: header, payload: payload, encrypter: encrypter!) 37 | let serialization = jwe.compactSerializedString 38 | 39 | let deserialization = try JWE(compactSerialization: serialization) 40 | let decrypter = Decrypter(keyManagementAlgorithm: .PBES2_HS256_A128KW, contentEncryptionAlgorithm: .A256CBCHS512, decryptionKey: password) 41 | let decrypted = try! deserialization.decrypt(using: decrypter!) 42 | 43 | XCTAssertEqual(payload.data(), decrypted.data()) 44 | XCTAssertEqual(jwe.header.p2c, 1000) 45 | XCTAssertEqual(jwe.header.p2s?.count, 32) 46 | } 47 | } 48 | // swiftlint:enable force_unwrapping 49 | -------------------------------------------------------------------------------- /Tests/RSAPublicKeyToSecKeyTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // RSAPublicKeyToSecKeyTests.swift 4 | // Tests 5 | // 6 | // Created by Daniel Egger on 12.02.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class RSAPublicKeyToSecKeyTests: RSACryptoTestCase { 29 | 30 | func testpublicKeyAlice2048ToSecKey() { 31 | let jwk = RSAPublicKey(modulus: expectedModulus2048Base64, exponent: expectedExponentBase64) 32 | let key = try! jwk.converted(to: SecKey.self) 33 | 34 | XCTAssertEqual(SecKeyCopyExternalRepresentation(key, nil)! as Data, publicKeyAlice2048Data) 35 | } 36 | 37 | func testPublicKey4096ToSecKey() { 38 | let jwk = RSAPublicKey(modulus: expectedModulus4096Base64, exponent: expectedExponentBase64) 39 | let key = try! jwk.converted(to: SecKey.self) 40 | 41 | XCTAssertEqual(SecKeyCopyExternalRepresentation(key, nil)! as Data, publicKey4096Data) 42 | } 43 | 44 | func testMalformedModulusConversionFails() { 45 | let jwk = RSAPublicKey(modulus: "+++++", exponent: expectedExponentBase64) 46 | 47 | XCTAssertThrowsError(try jwk.converted(to: SecKey.self)) 48 | } 49 | 50 | func testMalformedExponentConversionFails() { 51 | let jwk = RSAPublicKey(modulus: expectedModulus2048Base64, exponent: "+++++") 52 | 53 | XCTAssertThrowsError(try jwk.converted(to: SecKey.self)) 54 | } 55 | 56 | } 57 | // swiftlint:enable force_unwrapping 58 | -------------------------------------------------------------------------------- /Tests/JWSSigningInputTest.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // JWSSigningInputTest.swift 4 | // Tests 5 | // 6 | // Created by Daniel Egger on 07.12.17. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class JWSSigningInputTest: XCTestCase { 29 | 30 | let header: JWSHeader = JWSHeader("{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}".data(using: .utf8)!)! 31 | let payload: Payload = Payload("{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}".data(using: .utf8)!) 32 | 33 | let expectedSigningInput: [UInt8] = [ 34 | 101, 121, 74, 48, 101, 88, 65, 105, 79, 105, 74, 75, 86, 49, 81, 35 | 105, 76, 65, 48, 75, 73, 67, 74, 104, 98, 71, 99, 105, 79, 105, 74, 36 | 73, 85, 122, 73, 49, 78, 105, 74, 57, 46, 101, 121, 74, 112, 99, 51, 37 | 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, 76, 65, 48, 75, 73, 67, 38 | 74, 108, 101, 72, 65, 105, 79, 106, 69, 122, 77, 68, 65, 52, 77, 84, 39 | 107, 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 40 | 72, 65, 54, 76, 121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 41 | 109, 78, 118, 98, 83, 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 42 | 106, 112, 48, 99, 110, 86, 108, 102, 81 43 | ] 44 | 45 | func testSigningInputComputation() { 46 | let signingInput: [UInt8] = Array([header, payload].asJOSESigningInput()!) 47 | XCTAssertEqual(signingInput, expectedSigningInput) 48 | } 49 | 50 | } 51 | // swiftlint:enable force_unwrapping 52 | -------------------------------------------------------------------------------- /Tests/ECSignerTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // ECSignerTests.swift 4 | // Tests 5 | // 6 | // Created by Jarrod Moldrich on 21.10.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class ECSignerTests: ECCryptoTestCase { 29 | 30 | private func _testSigning(algorithm: SignatureAlgorithm, keyData: ECTestKeyData) { 31 | let messageData = message.data(using: .utf8)! 32 | let signer = ECSigner(algorithm: algorithm, privateKey: keyData.privateKey) 33 | let signature = try! signer.sign(messageData) 34 | let signature2 = try! signer.sign(messageData) 35 | 36 | // As any two signing invocations will have different nonces, it is impossible to use pre-generated data from a 37 | // trusted implementation (e.g. openssl) to verify the signature. Instead we will validate by verifying the 38 | // signature. This assumes that the verification implementation is correct. 39 | XCTAssertNotEqual(signature.base64URLEncodedString(), signature2.base64URLEncodedString()) 40 | let verifier = ECVerifier(algorithm: algorithm, publicKey: keyData.publicKey) 41 | let verified = (try? verifier.verify(messageData, against: signature)) ?? false 42 | XCTAssertTrue(verified) 43 | } 44 | 45 | func testSigning() { 46 | _testSigning(algorithm: .ES256, keyData: p256) 47 | _testSigning(algorithm: .ES384, keyData: p384) 48 | _testSigning(algorithm: .ES512, keyData: p521) 49 | } 50 | } 51 | // swiftlint:enable force_unwrapping 52 | -------------------------------------------------------------------------------- /JOSESwift/Sources/CryptoPrimitives/Randomness/SecureRandom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecureRandom.swift 3 | // JOSESwift 4 | // 5 | // Created by Carol Capek on 07.12.17. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | import Security 26 | 27 | public enum SecureRandomError: Error { 28 | case failed(status: OSStatus) 29 | case countMustBeGreaterThanZero 30 | } 31 | 32 | public struct SecureRandom { 33 | /// Generates secure random data with a given count. 34 | /// 35 | /// - Parameter count: The count of the random generated data. Must be greater than 0. 36 | /// - Returns: The random generated data. 37 | /// - Throws: `SecureRandomError` if any error occurs during generation of secure random bytes. 38 | public static func generate(count: Int) throws -> Data { 39 | guard count > 0 else { 40 | throw SecureRandomError.countMustBeGreaterThanZero 41 | } 42 | 43 | var generatedRandom = Data(count: count) 44 | 45 | let randomGenerationStatus = generatedRandom.withUnsafeMutableBytes { mutableRandomBytes in 46 | // Force unwrapping is ok, since the buffer is guaranteed not to be empty. 47 | // From the docs: If the baseAddress of this buffer is nil, the count is zero. 48 | // swiftlint:disable:next force_unwrapping 49 | SecRandomCopyBytes(kSecRandomDefault, count, mutableRandomBytes.baseAddress!) 50 | } 51 | 52 | guard randomGenerationStatus == errSecSuccess else { 53 | throw SecureRandomError.failed(status: randomGenerationStatus) 54 | } 55 | 56 | return generatedRandom 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/RSA/DataRSAPublicKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataRSAPublicKey.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 06.02.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | extension Data: ExpressibleAsRSAPublicKeyComponents { 27 | public static func representing(rsaPublicKeyComponents components: RSAPublicKeyComponents) throws -> Data { 28 | var modulusBytes = [UInt8](components.modulus) 29 | let exponentBytes = [UInt8](components.exponent) 30 | 31 | // Ensure the modulus is prefixed with 0x00. 32 | if let prefix = modulusBytes.first, prefix != 0x00 { 33 | modulusBytes.insert(0x00, at: 0) 34 | } 35 | 36 | let modulusEncoded = modulusBytes.encode(as: .integer) 37 | let exponentEncoded = exponentBytes.encode(as: .integer) 38 | 39 | let sequenceEncoded = (modulusEncoded + exponentEncoded).encode(as: .sequence) 40 | 41 | return Data(sequenceEncoded) 42 | } 43 | 44 | public func rsaPublicKeyComponents() throws -> RSAPublicKeyComponents { 45 | let publicKeyBytes = [UInt8](self) 46 | 47 | let sequence = try publicKeyBytes.read(.sequence) 48 | var modulus = try sequence.read(.integer) 49 | let exponent = try sequence.skip(.integer).read(.integer) 50 | 51 | // Remove potential leading zero byte. 52 | // See https://tools.ietf.org/html/rfc7518#section-6.3.1.1. 53 | if modulus.first == 0x00 { 54 | modulus = Array(modulus.dropFirst()) 55 | } 56 | 57 | return ( 58 | Data(modulus), 59 | Data(exponent) 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/EC/DataECPublicKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataECPublicKey.swift 3 | // JOSESwift 4 | // 5 | // Created by Jarrod Moldrich on 02.07.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | extension Data: ExpressibleAsECPublicKeyComponents { 27 | public static func representing(ecPublicKeyComponents components: ECPublicKeyComponents) throws -> Data { 28 | let xBytes = [UInt8](components.x) 29 | let yBytes = [UInt8](components.y) 30 | let uncompressedIndication: [UInt8] = [ECCompression.Uncompressed.rawValue] 31 | 32 | guard 33 | xBytes.count == yBytes.count, 34 | ECCurveType.fromCoordinateOctetLength(xBytes.count) != nil else { 35 | throw JOSESwiftError.invalidCurvePointOctetLength 36 | } 37 | 38 | return Data(uncompressedIndication + xBytes + yBytes) 39 | } 40 | 41 | public func ecPublicKeyComponents() throws -> ECPublicKeyComponents { 42 | var publicKeyBytes = [UInt8](self) 43 | 44 | guard publicKeyBytes.removeFirst() == ECCompression.Uncompressed.rawValue else { 45 | throw JOSESwiftError.compressedCurvePointsUnsupported 46 | } 47 | 48 | let pointSize = publicKeyBytes.count / 2 49 | guard let curve = ECCurveType.fromCoordinateOctetLength(pointSize) else { 50 | throw JOSESwiftError.invalidCurvePointOctetLength 51 | } 52 | 53 | let xBytes = publicKeyBytes[0.. ECPrivateKey { 29 | return self as ECPrivateKey 30 | } 31 | 32 | static func generateWith(_ curveType: ECCurveType) throws -> ECKeyPair { 33 | let attributes: [String: Any] = [kSecAttrKeySizeInBits as String: curveType.keyBitLength, 34 | kSecAttrKeyType as String: kSecAttrKeyTypeEC, 35 | kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false]] 36 | var error: Unmanaged? 37 | if let ecKey: SecKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) { 38 | return try ECPrivateKey(privateKey: ecKey, additionalParameters: ["kid": UUID().uuidString]) 39 | } 40 | throw ECKeyPairError.generateECKeyPairFail 41 | } 42 | } 43 | 44 | public extension ECPrivateKey { 45 | 46 | func getPublic() -> ECPublicKey { 47 | let parametersForPublic = parameters.filter { $0.key != ECParameter.privateKey.rawValue } 48 | return ECPublicKey(crv: crv, x: x, y: y, additionalParameters: parametersForPublic) 49 | } 50 | 51 | func isCorrespondWith(_ key: ECPublicKey) -> Bool { 52 | guard 53 | crv == key.crv, 54 | x == key.x, 55 | y == key.y 56 | else { 57 | return false 58 | } 59 | return true 60 | } 61 | } 62 | 63 | public enum ECKeyPairError: Error { 64 | case generateECKeyPairFail 65 | } 66 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWE/KeyManagement/DefaultCrypto/DirectEncryption/DirectEncryptionMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DirectEncryptionMode.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 12.02.20. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | /// A key management mode in which the content encryption key value used is a given secret symmetric key value shared 27 | /// between the parties. For direct encryption the JWE encrypted key is the empty octet sequence. 28 | struct DirectEncryptionMode { 29 | typealias KeyType = Data 30 | 31 | private let keyManagementAlgorithm: KeyManagementAlgorithm 32 | private let sharedSymmetricKey: KeyType 33 | 34 | init(keyManagementAlgorithm: KeyManagementAlgorithm, sharedSymmetricKey: KeyType) { 35 | self.keyManagementAlgorithm = keyManagementAlgorithm 36 | self.sharedSymmetricKey = sharedSymmetricKey 37 | } 38 | } 39 | 40 | extension DirectEncryptionMode: EncryptionKeyManagementMode { 41 | var algorithm: KeyManagementAlgorithm { 42 | keyManagementAlgorithm 43 | } 44 | 45 | func determineContentEncryptionKey(with _: JWEHeader) throws -> EncryptionKeyManagementModeContext { 46 | return .init(contentEncryptionKey: sharedSymmetricKey, encryptedKey: KeyType(), jweHeader: nil) 47 | } 48 | } 49 | 50 | extension DirectEncryptionMode: DecryptionKeyManagementMode { 51 | func determineContentEncryptionKey(from encryptedKey: Data, with _: JWEHeader) throws -> Data { 52 | guard encryptedKey == Data() else { 53 | throw JOSESwiftError.decryptingFailed(description: "Direct encryption does not expect an encrypted key.") 54 | } 55 | 56 | return sharedSymmetricKey 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /JOSESwift/Sources/Common/ASN1DEREncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASN1DEREncoding.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 08.02.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | // MARK: Array Extension for Encoding 27 | // Inspired by: https://github.com/henrinormak/Heimdall/blob/master/Heimdall/Heimdall.swift 28 | 29 | internal extension Array where Element == UInt8 { 30 | 31 | func encode(as type: ASN1Type) -> [UInt8] { 32 | var tlvTriplet: [UInt8] = [] 33 | tlvTriplet.append(type.tag) 34 | tlvTriplet.append(contentsOf: lengthField(of: self)) 35 | tlvTriplet.append(contentsOf: self) 36 | 37 | return tlvTriplet 38 | } 39 | 40 | } 41 | 42 | // MARK: Freestanding Helper Function 43 | 44 | private func lengthField(of valueField: [UInt8]) -> [UInt8] { 45 | var count = valueField.count 46 | 47 | if count < 128 { 48 | return [ UInt8(count) ] 49 | } 50 | 51 | // The number of bytes needed to encode count. 52 | let lengthBytesCount = Int((log2(Double(count)) / 8) + 1) 53 | 54 | // The first byte in the length field encoding the number of remaining bytes. 55 | let firstLengthFieldByte = UInt8(128 + lengthBytesCount) 56 | 57 | var lengthField: [UInt8] = [] 58 | for _ in 0..> 8 65 | } 66 | 67 | // Include the first byte. 68 | lengthField.insert(firstLengthFieldByte, at: 0) 69 | 70 | return lengthField 71 | } 72 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/JWKParameters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JWKParameters.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 21.12.17. 6 | // Modified by Jarrod Moldrich on 02.07.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import Foundation 26 | 27 | /// Possible common JWK parameters. 28 | /// See [RFC-7517, Section 4](https://tools.ietf.org/html/rfc7517#section-4) for details. 29 | public enum JWKParameter: String, CodingKey { 30 | case keyType = "kty" 31 | case keyUse = "use" 32 | case keyOperations = "key_ops" 33 | case algorithm = "alg" 34 | case keyIdentifier = "kid" 35 | case X509URL = "x5u" 36 | case X509CertificateChain = "x5c" 37 | case X509CertificateSHA1Thumbprint = "x5t" 38 | case X509CertificateSHA256Thumbprint = "x5t#S256" 39 | 40 | static let nonStringParameters: [JWKParameter] = [ 41 | .keyOperations, 42 | .X509CertificateChain 43 | ] 44 | } 45 | 46 | /// RSA specific JWK parameters. 47 | /// See [RFC-7518, Section 6.3](https://tools.ietf.org/html/rfc7518#section-6.3) for details. 48 | public enum RSAParameter: String, CodingKey { 49 | case modulus = "n" 50 | case exponent = "e" 51 | case privateExponent = "d" 52 | } 53 | 54 | /// Symmetric key specific JWK parameters. 55 | /// See [RFC-7518, Section 6.3](https://tools.ietf.org/html/rfc7518#section-6.4) for details. 56 | public enum SymmetricKeyParameter: String, CodingKey { 57 | case key = "k" 58 | } 59 | 60 | /// EC specific JWK parameters. 61 | /// See [RFC-7518, Section 6.2](https://tools.ietf.org/html/rfc7518#section-6.2) for details. 62 | public enum ECParameter: String, CodingKey { 63 | case curve = "crv" 64 | case x = "x" 65 | case y = "y" 66 | case privateKey = "d" 67 | } 68 | -------------------------------------------------------------------------------- /JOSESwift/Sources/CryptoPrimitives/SHA/Thumbprint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SHA256.swift 3 | // JOSESwift 4 | // 5 | // Created by Thomas Torp on 08.06.20. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | import CommonCrypto 26 | 27 | enum ThumbprintError: Error { 28 | case inputMustBeGreaterThanZero 29 | } 30 | 31 | fileprivate extension JWKThumbprintAlgorithm { 32 | var outputLength: Int { 33 | switch self { 34 | case .SHA256: 35 | return Int(CC_SHA256_DIGEST_LENGTH) 36 | } 37 | } 38 | 39 | func calculate(input: UnsafeRawBufferPointer, output: UnsafeMutablePointer) { 40 | switch self { 41 | case .SHA256: 42 | CC_SHA256(input.baseAddress, CC_LONG(input.count), output) 43 | } 44 | } 45 | } 46 | 47 | internal struct Thumbprint { 48 | /// Calculates a hash of an input with a specific hash algorithm. 49 | /// 50 | /// - Parameters: 51 | /// - input: The input to calculate a hash for. 52 | /// - algorithm: The algorithm used to calculate the hash. 53 | /// - Returns: The calculated hash in base64URLEncoding. 54 | static func calculate(from input: Data, algorithm: JWKThumbprintAlgorithm) throws -> String { 55 | guard input.count > 0 else { 56 | throw ThumbprintError.inputMustBeGreaterThanZero 57 | } 58 | 59 | let hashBytes = UnsafeMutablePointer.allocate(capacity: algorithm.outputLength) 60 | defer { hashBytes.deallocate() } 61 | 62 | input.withUnsafeBytes { buffer in 63 | algorithm.calculate(input: buffer, output: hashBytes) 64 | } 65 | 66 | return Data(bytes: hashBytes, count: algorithm.outputLength).base64URLEncodedString() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tests/ECVerifierTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ECVerifierTests.swift 3 | // Tests 4 | // 5 | // Created by Jarrod Moldrich on 08.10.17. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | class ECVerifierTests: ECCryptoTestCase { 28 | 29 | private func _testVerifying(algorithm: SignatureAlgorithm, keyData: ECTestKeyData, validSignature: Bool = true) -> Bool { 30 | let validJWS = keyData.compactSerializedJWSConst 31 | let serializedJWS = validSignature ? validJWS : invalidateCompactSerializedJWS(validJWS) 32 | 33 | let jws = try! JWS(compactSerialization: serializedJWS) 34 | let verifier = ECVerifier(algorithm: algorithm, publicKey: keyData.publicKey) 35 | 36 | guard let signingInput = [jws.header, jws.payload].asJOSESigningInput() else { 37 | XCTFail() 38 | return false 39 | } 40 | 41 | return (try? verifier.verify(signingInput, against: jws.signature)) ?? false 42 | } 43 | 44 | private func invalidateCompactSerializedJWS(_ validJWS: String) -> String { 45 | return validJWS.dropLast(7).appending("INVALID") 46 | } 47 | 48 | func testVerifying() { 49 | XCTAssertTrue(_testVerifying(algorithm: .ES256, keyData: p256)) 50 | XCTAssertTrue(_testVerifying(algorithm: .ES384, keyData: p384)) 51 | XCTAssertTrue(_testVerifying(algorithm: .ES512, keyData: p521)) 52 | } 53 | 54 | func testVerifyingInvalid() { 55 | XCTAssertFalse(_testVerifying(algorithm: .ES256, keyData: p256, validSignature: false)) 56 | XCTAssertFalse(_testVerifying(algorithm: .ES384, keyData: p384, validSignature: false)) 57 | XCTAssertFalse(_testVerifying(algorithm: .ES512, keyData: p521, validSignature: false)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWE/Compression/Compressor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Compressor.swift 3 | // JOSESwift 4 | // 5 | // Created by Florian Häser on 13.02.19. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | protocol CompressorProtocol { 27 | /// Compresses data using the `CompressionAlgorithm`. 28 | /// 29 | /// - Parameter data: The uncompressed data. 30 | /// - Returns: The compressed data. 31 | func compress(data: Data) throws -> Data 32 | /// Decompresses data using the `CompressionAlgorithm`. 33 | /// 34 | /// - Parameter data: The compressed data. 35 | /// - Returns: The decompressed data. 36 | func decompress(data: Data) throws -> Data 37 | } 38 | 39 | /// A `Compressor` that takes the data and passes it back without doing any compression or decompression. 40 | /// Used for having the JWE implementation more readable. 41 | struct NoneCompressor: CompressorProtocol { 42 | func compress(data: Data) -> Data { 43 | return data 44 | } 45 | func decompress(data: Data) -> Data { 46 | return data 47 | } 48 | } 49 | 50 | struct CompressorFactory { 51 | /// Select the appropriate `Compressor` for a given `CompressionAlgorithm. Defaults to the `NoneCompressor`. 52 | /// 53 | /// - Parameter algorithm: The `CompressionAlgorithm` for selecting the appropriate compressor. 54 | /// - Returns: The appropriate compressor 55 | static func makeCompressor(algorithm: CompressionAlgorithm?) throws -> CompressorProtocol { 56 | switch algorithm { 57 | case .DEFLATE?: 58 | return DeflateCompressor() 59 | case .NONE?: 60 | return NoneCompressor() 61 | default: 62 | throw JOSESwiftError.compressionAlgorithmNotSupported 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Tests/ECPublicKeyToSecKeyTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // ECPublicKeyToSecKeyTests.swift 4 | // Tests 5 | // 6 | // Created by Jarrod Moldrich on 27.10.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class ECPublicKeyToSecKeyTests: ECCryptoTestCase { 29 | 30 | func testPublicKeyToSecKey() { 31 | allTestData.forEach { testData in 32 | let jwk = ECPublicKey( 33 | crv: ECCurveType(rawValue: testData.expectedCurveType)!, 34 | x: testData.expectedXCoordinateBase64Url, 35 | y: testData.expectedYCoordinateBase64Url 36 | ) 37 | let key = try! jwk.converted(to: SecKey.self) 38 | 39 | XCTAssertEqual(SecKeyCopyExternalRepresentation(key, nil)! as Data, testData.publicKeyData) 40 | } 41 | } 42 | 43 | func testInvalidPublicKeyToSecKey() { 44 | allTestData.forEach { testData in 45 | let crv = ECCurveType(rawValue: testData.expectedCurveType)! 46 | let x = testData.expectedXCoordinateBase64Url 47 | let y = testData.expectedYCoordinateBase64Url 48 | let invalid = "\u{96}" 49 | checkInvalidArgumentListForException(crv: crv, x: invalid, y: y) 50 | checkInvalidArgumentListForException(crv: crv, x: x, y: invalid) 51 | } 52 | } 53 | 54 | // MARK: Helper functions 55 | 56 | func checkInvalidArgumentListForException(crv: ECCurveType, x: String, y: String) { 57 | let closure = { 58 | let jwk = ECPublicKey(crv: crv, x: x, y: y) 59 | _ = try jwk.converted(to: SecKey.self) 60 | } 61 | XCTAssertThrowsError(try closure()) 62 | } 63 | 64 | } 65 | // swiftlint:enable force_unwrapping 66 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWE/ContentEncryption/ContentEncryption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentEncryption.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 12.02.20. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | public protocol ContentEncrypter { 27 | var algorithm: ContentEncryptionAlgorithm { get } 28 | 29 | func encrypt(headerData: Data, payload: Payload, contentEncryptionKey: Data) throws -> ContentEncryptionContext 30 | } 31 | 32 | public protocol ContentDecrypter { 33 | var algorithm: ContentEncryptionAlgorithm { get } 34 | 35 | func decrypt(decryptionContext: ContentDecryptionContext) throws -> Data 36 | } 37 | 38 | public struct ContentEncryptionContext { 39 | let ciphertext: Data 40 | let authenticationTag: Data 41 | let initializationVector: Data 42 | } 43 | 44 | public struct ContentDecryptionContext { 45 | let ciphertext: Data 46 | let initializationVector: Data 47 | let additionalAuthenticatedData: Data 48 | let authenticationTag: Data 49 | let contentEncryptionKey: Data 50 | } 51 | 52 | extension ContentEncryptionAlgorithm { 53 | func makeContentEncrypter() throws -> ContentEncrypter { 54 | switch self { 55 | case .A128CBCHS256, .A192CBCHS384, .A256CBCHS512: 56 | return try AESCBCEncryption(contentEncryptionAlgorithm: self) 57 | case .A128GCM, .A192GCM, .A256GCM: 58 | return AESGCMEncryption(contentEncryptionAlgorithm: self) 59 | } 60 | } 61 | 62 | func makeContentDecrypter() throws -> ContentDecrypter { 63 | switch self { 64 | case .A128CBCHS256, .A192CBCHS384, .A256CBCHS512: 65 | return try AESCBCEncryption(contentEncryptionAlgorithm: self) 66 | case .A128GCM, .A192GCM, .A256GCM: 67 | return AESGCMEncryption(contentEncryptionAlgorithm: self) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/DirectEncryptionKeyManagementModeTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // DirectEncryptionKeyManagementModeTests.swift 4 | // Tests 5 | // 6 | // Created by Daniel Egger on 13.02.20. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class DirectEncryptionKeyManagementModeTests: XCTestCase { 29 | let sharedSymmetricKey = "secret".data(using: .utf8)! 30 | 31 | func testReturnsSharedSymmeyricKeyAsContentEncryptionKey() throws { 32 | let header = JWEHeader(keyManagementAlgorithm: .direct, contentEncryptionAlgorithm: .A128CBCHS256) 33 | let keyEncryption = DirectEncryptionMode( 34 | keyManagementAlgorithm: .direct, 35 | sharedSymmetricKey: sharedSymmetricKey 36 | ) 37 | 38 | let context1 = try keyEncryption.determineContentEncryptionKey(with: header) 39 | let context2 = try keyEncryption.determineContentEncryptionKey(with: header) 40 | 41 | XCTAssertEqual(context1.contentEncryptionKey, sharedSymmetricKey) 42 | XCTAssertEqual(context1.contentEncryptionKey, context2.contentEncryptionKey) 43 | XCTAssertEqual(context1.encryptedKey, context2.encryptedKey) 44 | XCTAssertNil(context1.jweHeader) 45 | XCTAssertNil(context2.jweHeader) 46 | 47 | } 48 | 49 | func testEncryptedKeyIsEmpty() throws { 50 | let header = JWEHeader(keyManagementAlgorithm: .direct, contentEncryptionAlgorithm: .A256CBCHS512) 51 | 52 | let keyEncryption = DirectEncryptionMode( 53 | keyManagementAlgorithm: .direct, 54 | sharedSymmetricKey: sharedSymmetricKey 55 | ) 56 | 57 | let context = try keyEncryption.determineContentEncryptionKey(with: header) 58 | 59 | XCTAssertEqual(context.encryptedKey, Data()) 60 | } 61 | } 62 | // swiftlint:enable force_unwrapping 63 | -------------------------------------------------------------------------------- /JOSESwift/Sources/Common/JOSESwiftError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JOSESwiftError.swift 3 | // JOSESwift 4 | // 5 | // Created by Carol Capek on 21.02.18. 6 | // Modified by Jarrod Moldrich on 02.07.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import Foundation 26 | 27 | public enum JOSESwiftError: Error { 28 | case signingFailed(description: String) 29 | case verifyingFailed(description: String) 30 | case signatureInvalid 31 | 32 | case encryptingFailed(description: String) 33 | case decryptingFailed(description: String) 34 | 35 | case wrongDataEncoding(data: Data) 36 | case invalidCompactSerializationComponentCount(count: Int) 37 | case componentNotValidBase64URL(component: String) 38 | case componentCouldNotBeInitializedFromData(data: Data) 39 | 40 | case couldNotConstructJWK 41 | 42 | // RSA coding errors 43 | case modulusNotBase64URLUIntEncoded 44 | case exponentNotBase64URLUIntEncoded 45 | case privateExponentNotBase64URLUIntEncoded 46 | case symmetricKeyNotBase64URLEncoded 47 | 48 | // EC coding errors 49 | case xNotBase64URLUIntEncoded 50 | case yNotBase64URLUIntEncoded 51 | case privateKeyNotBase64URLUIntEncoded 52 | case invalidCurveType 53 | case compressedCurvePointsUnsupported 54 | case invalidCurvePointOctetLength 55 | case localAuthenticationFailed(errorCode: Int) 56 | 57 | // Compression errors 58 | case compressionFailed 59 | case decompressionFailed 60 | case compressionAlgorithmNotSupported 61 | case rawDataMustBeGreaterThanZero 62 | case compressedDataMustBeGreaterThanZero 63 | 64 | // Thumbprint computation 65 | case thumbprintSerialization 66 | 67 | // Header errors 68 | case invalidHeaderParameterValue 69 | 70 | case keyManagementAlgorithmMismatch 71 | case contentEncryptionAlgorithmMismatch 72 | case signingAlgorithmMismatch 73 | } 74 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/EC/DataECPrivateKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataECPrivateKey.swift 3 | // JOSESwift 4 | // 5 | // Created by Jarrod Moldrich on 10.01.2019. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | extension Data: ExpressibleAsECPrivateKeyComponents { 27 | public static func representing(ecPrivateKeyComponents components: ECPrivateKeyComponents) throws -> Data { 28 | let xBytes = [UInt8](components.x) 29 | let yBytes = [UInt8](components.y) 30 | let dBytes = [UInt8](components.d) 31 | let uncompressedIndication: [UInt8] = [ECCompression.Uncompressed.rawValue] 32 | 33 | guard 34 | xBytes.count == yBytes.count, 35 | xBytes.count == dBytes.count, 36 | ECCurveType.fromCoordinateOctetLength(xBytes.count) != nil else { 37 | throw JOSESwiftError.invalidCurvePointOctetLength 38 | } 39 | 40 | return Data(uncompressedIndication + xBytes + yBytes + dBytes) 41 | } 42 | 43 | public func ecPrivateKeyComponents() throws -> ECPrivateKeyComponents { 44 | var privateKeyBytes = [UInt8](self) 45 | 46 | guard privateKeyBytes.removeFirst() == ECCompression.Uncompressed.rawValue else { 47 | throw JOSESwiftError.compressedCurvePointsUnsupported 48 | } 49 | 50 | let pointSize = privateKeyBytes.count / 3 51 | guard let curve = ECCurveType.fromCoordinateOctetLength(pointSize) else { 52 | throw JOSESwiftError.invalidCurvePointOctetLength 53 | } 54 | 55 | let xBytes = privateKeyBytes[0.. ContentEncryptionContext { 27 | let key = CryptoKit.SymmetricKey(data: encryptionKey) 28 | let nonce = try CryptoKit.AES.GCM.Nonce(data: initializationVector) 29 | let encrypted = try CryptoKit.AES.GCM.seal(plaintext, using: key, nonce: nonce, authenticating: additionalAuthenticatedData) 30 | return ContentEncryptionContext( 31 | ciphertext: encrypted.ciphertext, 32 | authenticationTag: encrypted.tag, 33 | initializationVector: initializationVector 34 | ) 35 | } 36 | 37 | /// Decrypts a cipher text using a given `AES.GCM` algorithm. 38 | /// 39 | /// - Parameters: 40 | /// - cipherText: The encrypted cipher text to decrypt. 41 | /// - decryptionKey: The symmetric key. 42 | /// - initializationVector: The initial block. 43 | /// - authenticationTag: The authentication tag. 44 | /// - additionalAuthenticatedData: The additional data block that is to be authenticated. 45 | /// - Returns: The plain text (decrypted cipher text). 46 | /// - Throws: The call throws an error if decryption or authentication fails 47 | static func decrypt( 48 | cipherText: Data, 49 | decryptionKey: Data, 50 | initializationVector: Data, 51 | authenticationTag: Data, 52 | additionalAuthenticatedData: Data 53 | ) throws -> Data { 54 | let key = CryptoKit.SymmetricKey(data: decryptionKey) 55 | let nonce = try CryptoKit.AES.GCM.Nonce(data: initializationVector) 56 | let encrypted = try CryptoKit.AES.GCM.SealedBox(nonce: nonce, ciphertext: cipherText, tag: authenticationTag) 57 | let decrypted = try CryptoKit.AES.GCM.open(encrypted, using: key, authenticating: additionalAuthenticatedData) 58 | return decrypted 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/JWSCustomSignerVerifierTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // JWSCustomSignerVerifierTests.swift 4 | // Tests 5 | // 6 | // Created by Daniel Egger on 22.02.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class JWSCustomSignerVerifierTests: XCTestCase { 29 | private struct NoOpSigner: SignerProtocol { 30 | var algorithm: SignatureAlgorithm = .HS256 31 | 32 | func sign(_ signingInput: Data) throws -> Data { 33 | return Data() 34 | } 35 | } 36 | 37 | private struct NoOpVerifyer: VerifierProtocol { 38 | var algorithm: SignatureAlgorithm = .HS256 39 | 40 | func verify(_ signingInput: Data, against signature: Data) throws -> Bool { 41 | return false 42 | } 43 | } 44 | 45 | func testCustomSigner() throws { 46 | let header = JWSHeader(algorithm: .HS256) 47 | let payload = Payload("Summer, Sun, Cactus".data(using: .utf8)!) 48 | let customSigner = Signer(customSigner: NoOpSigner()) 49 | 50 | let jws = try JWS(header: header, payload: payload, signer: customSigner) 51 | 52 | XCTAssertEqual(jws.signature, Data()) 53 | } 54 | 55 | func testCustomVerifier() throws { 56 | let testDummySigningKey = "not-so-secret".data(using: .utf8)! 57 | 58 | let header = JWSHeader(algorithm: .HS256) 59 | let payload = Payload("Summer, Sun, Cactus".data(using: .utf8)!) 60 | let signer = Signer(signatureAlgorithm: .HS256, key: testDummySigningKey)! 61 | let joseVerifier = Verifier(signatureAlgorithm: .HS256, key: testDummySigningKey)! 62 | let customVerifier = Verifier(customVerifier: NoOpVerifyer()) 63 | 64 | let jws = try JWS(header: header, payload: payload, signer: signer) 65 | 66 | XCTAssertTrue(jws.isValid(for: joseVerifier)) 67 | XCTAssertFalse(jws.isValid(for: customVerifier)) 68 | } 69 | } 70 | // swiftlint:enable force_unwrapping 71 | -------------------------------------------------------------------------------- /Tests/JWKtoJSONTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // JWKtoJSONTests.swift 4 | // Tests 5 | // 6 | // Created by Daniel Egger on 21.12.17. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class JWKtoJSONTests: RSACryptoTestCase { 29 | 30 | func testJSONString() { 31 | let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!, additionalParameters: [ 32 | "alg": "RS256", 33 | "kid": "2011-04-29" 34 | ]) 35 | 36 | let jsonString = jwk.jsonString() 37 | XCTAssertNotNil(jsonString) 38 | 39 | let jsonData = jsonString!.data(using: .utf8)! 40 | let dict = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] 41 | XCTAssertNotNil(dict!) 42 | 43 | XCTAssertEqual(dict!["kty"] as? String ?? "", "RSA") 44 | XCTAssertEqual(dict!["alg"] as? String ?? "", "RS256") 45 | XCTAssertEqual(dict!["kid"] as? String ?? "", "2011-04-29") 46 | 47 | XCTAssertEqual(dict!["n"] as? String ?? "", expectedModulus2048Base64) 48 | XCTAssertEqual(dict!["e"] as? String ?? "", expectedExponentBase64) 49 | } 50 | 51 | func testJSONData() { 52 | let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!, additionalParameters: [ 53 | "alg": "RS256", 54 | "kid": "2011-04-29" 55 | ]) 56 | 57 | let jsonData = jwk.jsonData() 58 | XCTAssertNotNil(jsonData!) 59 | 60 | let dict = try? JSONSerialization.jsonObject(with: jsonData!, options: []) as? [String: Any] 61 | XCTAssertNotNil(dict!) 62 | 63 | XCTAssertEqual(dict!["kty"] as? String ?? "", "RSA") 64 | XCTAssertEqual(dict!["alg"] as? String ?? "", "RS256") 65 | XCTAssertEqual(dict!["kid"] as? String ?? "", "2011-04-29") 66 | 67 | XCTAssertEqual(dict!["n"] as? String ?? "", expectedModulus2048Base64) 68 | XCTAssertEqual(dict!["e"] as? String ?? "", expectedExponentBase64) 69 | } 70 | } 71 | // swiftlint:enable force_unwrapping 72 | -------------------------------------------------------------------------------- /Tests/ECPrivateKeyToSecKeyTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // ECPrivateKeyToSecKeyTests.swift 4 | // Tests 5 | // 6 | // Created by Jarrod Moldrich on 10.01.2019. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class ECPrivateKeyToSecKeyTests: ECCryptoTestCase { 29 | 30 | func testPrivateKeyToSecKey() { 31 | allTestData.forEach { testData in 32 | let jwk = try! ECPrivateKey( 33 | crv: testData.expectedCurveType, 34 | x: testData.expectedXCoordinateBase64Url, 35 | y: testData.expectedYCoordinateBase64Url, 36 | privateKey: testData.expectedPrivateBase64Url 37 | ) 38 | let key = try! jwk.converted(to: SecKey.self) 39 | 40 | XCTAssertEqual(SecKeyCopyExternalRepresentation(key, nil)! as Data, testData.privateKeyData) 41 | } 42 | } 43 | 44 | func testInvalidPrivateKeyToSecKey() { 45 | allTestData.forEach { testData in 46 | let crv = testData.expectedCurveType 47 | let x = testData.expectedXCoordinateBase64Url 48 | let y = testData.expectedYCoordinateBase64Url 49 | let privateKey = testData.expectedPrivateBase64Url 50 | let invalid = "\u{96}" 51 | checkInvalidArgumentListForException(crv: "P-INVALID", x: x, y: y, privateKey: privateKey) 52 | checkInvalidArgumentListForException(crv: crv, x: invalid, y: y, privateKey: privateKey) 53 | checkInvalidArgumentListForException(crv: crv, x: x, y: invalid, privateKey: privateKey) 54 | checkInvalidArgumentListForException(crv: crv, x: x, y: y, privateKey: invalid) 55 | } 56 | } 57 | 58 | // MARK: Helper functions 59 | 60 | func checkInvalidArgumentListForException(crv: String, x: String, y: String, privateKey: String) { 61 | let closure = { 62 | let jwk = try ECPrivateKey(crv: crv, x: x, y: y, privateKey: privateKey) 63 | _ = try jwk.converted(to: SecKey.self) 64 | } 65 | XCTAssertThrowsError(try closure()) 66 | } 67 | 68 | } 69 | // swiftlint:enable force_unwrapping 70 | -------------------------------------------------------------------------------- /JOSESwift/Sources/Common/AlgorithmExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlgorithmExtensions.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 12.02.20. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | extension ContentEncryptionAlgorithm { 27 | 28 | var keyLength: Int { 29 | switch self { 30 | case .A256CBCHS512: 31 | return 64 32 | case .A192CBCHS384: 33 | return 48 34 | case .A128CBCHS256, .A256GCM: 35 | return 32 36 | case .A192GCM: 37 | return 24 38 | case .A128GCM: 39 | return 16 40 | } 41 | } 42 | 43 | var initializationVectorLength: Int { 44 | switch self { 45 | case .A128CBCHS256, .A192CBCHS384, .A256CBCHS512: 46 | return 16 47 | case .A256GCM, .A192GCM, .A128GCM: 48 | return 12 49 | } 50 | } 51 | 52 | func checkKeyLength(for key: Data) -> Bool { 53 | return key.count == keyLength 54 | } 55 | 56 | } 57 | 58 | extension SignatureAlgorithm { 59 | var hmacAlgorithm: HMACAlgorithm? { 60 | switch self { 61 | case .HS256: 62 | return .SHA256 63 | case .HS384: 64 | return .SHA384 65 | case .HS512: 66 | return .SHA512 67 | default: 68 | return nil 69 | } 70 | } 71 | } 72 | 73 | extension KeyManagementAlgorithm { 74 | var hmacAlgorithm: HMACAlgorithm? { 75 | switch self { 76 | case .PBES2_HS256_A128KW: 77 | return .SHA256 78 | case .PBES2_HS384_A192KW: 79 | return .SHA384 80 | case .PBES2_HS512_A256KW: 81 | return .SHA512 82 | default: 83 | return nil 84 | } 85 | } 86 | 87 | var derivedKeyLength: Int? { 88 | switch self { 89 | case .PBES2_HS256_A128KW: 90 | return 16 91 | case .PBES2_HS384_A192KW: 92 | return 24 93 | case .PBES2_HS512_A256KW: 94 | return 32 95 | default: 96 | return nil 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Tests/CryptoTestCase.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // CryptoTestCase.swift 4 | // Tests 5 | // 6 | // Created by Carol Capek on 02.11.17. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | 27 | class CryptoTestCase: XCTestCase { 28 | let message = "The true sign of intelligence is not knowledge but imagination." 29 | 30 | override func setUp() { 31 | super.setUp() 32 | setupKeys() 33 | } 34 | 35 | public func setupKeys() { 36 | XCTFail("Setup keys function is missing") 37 | } 38 | 39 | static func setupSecKeyPair(type: String, size: Int, data: Data, tag: String) -> (privateKey: SecKey, publicKey: SecKey)? { 40 | let attributes: [String: Any] = [ 41 | kSecAttrKeyType as String: type, 42 | kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, 43 | kSecAttrKeySizeInBits as String: size, 44 | kSecPrivateKeyAttrs as String: [ 45 | kSecAttrIsPermanent as String: false, 46 | kSecAttrApplicationTag as String: tag 47 | ] 48 | ] 49 | 50 | var error: Unmanaged? 51 | guard let privateKey = SecKeyCreateWithData(data as CFData, attributes as CFDictionary, &error) else { 52 | print(error!) 53 | return nil 54 | } 55 | 56 | let publicKey = SecKeyCopyPublicKey(privateKey)! 57 | 58 | return (privateKey, publicKey) 59 | } 60 | } 61 | 62 | extension String { 63 | 64 | func hexadecimalToData() -> Data? { 65 | var data = Data(capacity: count / 2) 66 | 67 | let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive) 68 | regex.enumerateMatches(in: self, range: NSRange(location: 0, length: utf16.count)) { match, _, _ in 69 | let byteString = (self as NSString).substring(with: match!.range) 70 | var num = UInt8(byteString, radix: 16)! 71 | data.append(&num, count: 1) 72 | } 73 | 74 | guard data.count > 0 else { return nil } 75 | 76 | return data 77 | } 78 | 79 | } 80 | 81 | extension Data { 82 | func toHexadecimal() -> String { 83 | return map { String(format: "%02x", $0) } 84 | .joined(separator: "") 85 | } 86 | } 87 | // swiftlint:enable force_unwrapping 88 | -------------------------------------------------------------------------------- /Tests/JWEAESKeyWrapTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JWEAESKeyWrapTests.swift 3 | // Tests 4 | // 5 | // Created by Daniel Egger on 18.02.20. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | // swiftlint:disable force_unwrapping 28 | 29 | class JWEAESKeyWrapTests: XCTestCase { 30 | func testRoundtrip() throws { 31 | let symmetricKey = Data(base64URLEncoded: "GawgguFyGrWKav7AX4VKUg")! 32 | 33 | let header = JWEHeader(keyManagementAlgorithm: .A128KW, contentEncryptionAlgorithm: .A128CBCHS256) 34 | let payload = Payload("Live long and prosper.".data(using: .ascii)!) 35 | let encrypter = Encrypter( 36 | keyManagementAlgorithm: .A128KW, 37 | contentEncryptionAlgorithm: .A128CBCHS256, 38 | encryptionKey: symmetricKey 39 | )! 40 | 41 | let jwe = try JWE(header: header, payload: payload, encrypter: encrypter) 42 | 43 | let decyrpter = Decrypter( 44 | keyManagementAlgorithm: .A128KW, 45 | contentEncryptionAlgorithm: .A128CBCHS256, 46 | decryptionKey: symmetricKey 47 | )! 48 | 49 | let decryptedPayload = try jwe.decrypt(using: decyrpter) 50 | 51 | XCTAssertEqual(decryptedPayload.data(), payload.data()) 52 | } 53 | 54 | func testRoundtripFailsWithWrongKey() throws { 55 | let symmetricKey = Data(base64URLEncoded: "GawgguFyGrWKav7AX4VKUg")! 56 | let wrongSymmetricKey = Data(base64URLEncoded: "WRONGuFyGrWKav7AX4VKUg")! 57 | 58 | let header = JWEHeader(keyManagementAlgorithm: .A128KW, contentEncryptionAlgorithm: .A128CBCHS256) 59 | let payload = Payload("Live long and prosper.".data(using: .ascii)!) 60 | let encrypter = Encrypter( 61 | keyManagementAlgorithm: .A128KW, 62 | contentEncryptionAlgorithm: .A128CBCHS256, 63 | encryptionKey: symmetricKey 64 | )! 65 | 66 | let jwe = try JWE(header: header, payload: payload, encrypter: encrypter) 67 | 68 | let decyrpter = Decrypter( 69 | keyManagementAlgorithm: .A128KW, 70 | contentEncryptionAlgorithm: .A128CBCHS256, 71 | decryptionKey: wrongSymmetricKey 72 | )! 73 | 74 | XCTAssertThrowsError(try jwe.decrypt(using: decyrpter)) 75 | } 76 | } 77 | // swiftlint:enable force_unwrapping 78 | -------------------------------------------------------------------------------- /JOSESwift/Sources/Common/Deserializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JOSEDeserializer.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 26/09/2017. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | public protocol CompactDeserializable { 27 | static var componentCount: Int { get } 28 | init(from deserializer: CompactDeserializer) throws 29 | } 30 | 31 | public protocol CompactDeserializer { 32 | func deserialize(_ type: T.Type, at index: Int) throws -> T 33 | } 34 | 35 | public struct JOSEDeserializer { 36 | public init() { } 37 | 38 | public func deserialize(_ type: T.Type, fromCompactSerialization compactSerialization: String) throws -> T { 39 | let encodedComponents = compactSerialization.components(separatedBy: ".") 40 | 41 | guard encodedComponents.count == type.componentCount else { 42 | throw JOSESwiftError.invalidCompactSerializationComponentCount(count: encodedComponents.count) 43 | } 44 | 45 | let decodedComponents = try encodedComponents.map { (component: String) throws -> Data in 46 | guard let data = Data(base64URLEncoded: component) else { 47 | throw JOSESwiftError.componentNotValidBase64URL(component: component) 48 | } 49 | return data 50 | } 51 | 52 | let deserializer = _CompactDeserializer(components: decodedComponents) 53 | 54 | return try T(from: deserializer) 55 | } 56 | } 57 | 58 | private struct _CompactDeserializer: CompactDeserializer { 59 | let components: [Data] 60 | 61 | func deserialize(_ type: T.Type, at index: Int) throws -> T { 62 | let componentData = components[index] 63 | guard let component = T(componentData) else { 64 | throw JOSESwiftError.componentCouldNotBeInitializedFromData(data: componentData) 65 | } 66 | 67 | return component 68 | } 69 | } 70 | 71 | public enum ComponentCompactSerializedIndex { 72 | static let jwsHeaderIndex = 0 73 | static let jwsPayloadIndex = 1 74 | static let jwsSignatureIndex = 2 75 | static let jweHeaderIndex = 0 76 | static let jweEncryptedKeyIndex = 1 77 | static let jweInitializationVectorIndex = 2 78 | static let jweCiphertextIndex = 3 79 | static let jweAuthenticationTagIndex = 4 80 | } 81 | -------------------------------------------------------------------------------- /Tests/JWKSetCollectionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JWKSetCollectionTests.swift 3 | // Tests 4 | // 5 | // Created by Daniel Egger on 15.02.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | class JWKSetCollectionTests: XCTestCase { 28 | 29 | let rsaKeys = [ 30 | RSAPublicKey(modulus: "modulus0", exponent: "exponent0"), 31 | RSAPublicKey(modulus: "modulus1", exponent: "exponent1"), 32 | RSAPublicKey(modulus: "modulus2", exponent: "exponent2") 33 | ] 34 | 35 | func testFowardIterating() { 36 | let set = JWKSet(keys: rsaKeys) 37 | 38 | for (index, key) in set.enumerated() { 39 | XCTAssertEqual((key as! RSAPublicKey).modulus, rsaKeys[index].modulus) 40 | XCTAssertEqual((key as! RSAPublicKey).exponent, rsaKeys[index].exponent) 41 | } 42 | } 43 | 44 | func testCreationFromArrayLiteral() { 45 | let set: JWKSet = [ 46 | RSAPublicKey(modulus: "modulus0", exponent: "exponent0"), 47 | RSAPublicKey(modulus: "modulus1", exponent: "exponent1"), 48 | RSAPublicKey(modulus: "modulus2", exponent: "exponent2") 49 | ] 50 | 51 | for (index, key) in set.enumerated() { 52 | XCTAssertEqual((key as! RSAPublicKey).modulus, rsaKeys[index].modulus) 53 | XCTAssertEqual((key as! RSAPublicKey).exponent, rsaKeys[index].exponent) 54 | } 55 | } 56 | 57 | func testSubscriptAccess() { 58 | let set = JWKSet(keys: rsaKeys) 59 | 60 | XCTAssertEqual((set[1] as! RSAPublicKey).modulus, rsaKeys[1].modulus) 61 | XCTAssertEqual((set[0] as! RSAPublicKey).modulus, rsaKeys[0].modulus) 62 | XCTAssertEqual((set[2] as! RSAPublicKey).modulus, rsaKeys[2].modulus) 63 | } 64 | 65 | func testIndices() { 66 | let set = JWKSet(keys: rsaKeys) 67 | 68 | XCTAssertEqual(set.startIndex, rsaKeys.startIndex) 69 | XCTAssertEqual(set.endIndex, rsaKeys.endIndex) 70 | XCTAssertEqual(set.index(after: 1), rsaKeys.index(after: 1)) 71 | } 72 | 73 | func testCount() { 74 | var set = JWKSet(keys: rsaKeys) 75 | 76 | XCTAssertEqual(set.count, 3) 77 | XCTAssertFalse(set.isEmpty) 78 | 79 | set = JWKSet(keys: []) 80 | 81 | XCTAssertEqual(set.count, 0) 82 | XCTAssertTrue(set.isEmpty) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Tests/HMACCryptoTestCase.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // HMACCryptoTestCase.swift 4 | // Tests 5 | // 6 | // Created by Tobias Hagemann on 14.04.21. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | 27 | /// Test data provided in the [RFC-4231](https://tools.ietf.org/html/rfc4231). 28 | /// Compact serialization generated at [jwt.io](https://jwt.io) 29 | class HMACCryptoTestCase: CryptoTestCase { 30 | let testKey = "0102030405060708090a0b0c0d0e0f10111213141516171819".hexadecimalToData()! 31 | let testData = "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd".hexadecimalToData()! 32 | let hmac256TestOutput = "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b".hexadecimalToData()! 33 | let hmac384TestOutput = "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb".hexadecimalToData()! 34 | let hmac512TestOutput = "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd".hexadecimalToData()! 35 | 36 | let compactSerializedJWSHS256Const = 37 | """ 38 | eyJhbGciOiJIUzI1NiJ9\ 39 | .\ 40 | VGhlIHRydWUgc2lnbiBvZiBpbnRlbGxpZ2VuY2UgaXMgbm90IGtub3dsZWRnZSBidXQgaW1hZ2luYXRpb24u\ 41 | .\ 42 | Fw75Z9cJ6lV-uPrZOrN2CLXquucZ00VQit7F_PTWFUs 43 | """ 44 | let compactSerializedJWSHS384Const = 45 | """ 46 | eyJhbGciOiJIUzM4NCJ9\ 47 | .\ 48 | VGhlIHRydWUgc2lnbiBvZiBpbnRlbGxpZ2VuY2UgaXMgbm90IGtub3dsZWRnZSBidXQgaW1hZ2luYXRpb24u\ 49 | .\ 50 | H08sUX3X4bkhz0TvtJ9tAVWPeyiXZsickDSTaR7GRGstKWetstGuMgyfHvBT11vw 51 | """ 52 | let compactSerializedJWSHS512Const = 53 | """ 54 | eyJhbGciOiJIUzUxMiJ9\ 55 | .\ 56 | VGhlIHRydWUgc2lnbiBvZiBpbnRlbGxpZ2VuY2UgaXMgbm90IGtub3dsZWRnZSBidXQgaW1hZ2luYXRpb24u\ 57 | .\ 58 | bCJ58-nDpVQkpz7ftEpnOr5h6mcZeO27_j2NAzFK6fDNnKv7l54_gIs62MdAWg1DnDB21RsMDQJr7G4HFYr1nw 59 | """ 60 | let signingKey = Data(base64URLEncoded: 61 | """ 62 | AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow 63 | """ 64 | )! 65 | 66 | override func setupKeys() { 67 | // do nothing 68 | } 69 | } 70 | // swiftlint:enable force_unwrapping 71 | -------------------------------------------------------------------------------- /Tests/JWSECTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // JWSECTests.swift 4 | // Tests 5 | // 6 | // Created by Jarrod Moldrich on 28.10.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class JWSECTests: ECCryptoTestCase { 29 | 30 | func testSignAndSerialize() { 31 | allTestData.forEach { testData in 32 | self.performTestECSign(testData: testData) 33 | } 34 | } 35 | 36 | func testDeserializeFromCompactSerialization() { 37 | allTestData.forEach { testData in 38 | self.performTestECDeserialization(testData: testData) 39 | } 40 | } 41 | 42 | // MARK: - EC Tests 43 | 44 | private func performTestECSign(testData: ECTestKeyData) { 45 | let algorithm = SignatureAlgorithm(rawValue: testData.signatureAlgorithm)! 46 | let header = JWSHeader(algorithm: algorithm) 47 | let payload = Payload(plainTextPayload.data(using: .utf8)!) 48 | let signer = Signer(signatureAlgorithm: algorithm, key: testData.privateKey)! 49 | let verifier = Verifier(signatureAlgorithm: algorithm, key: testData.publicKey)! 50 | let jws = try! JWS(header: header, payload: payload, signer: signer) 51 | let compact = jws.compactSerializedString 52 | let splitCompact = compact.split(separator: ".") 53 | 54 | XCTAssertEqual(String(splitCompact[0]), testData.compactSerializedJWSSimpleHeaderConst) 55 | XCTAssertEqual(String(splitCompact[1]), testData.compactSerializedJWSPayloadConst) 56 | // n.b.: we can't verify the signature with a constant as ECDSA internally uses a nonce 57 | XCTAssertNotEqual(String(splitCompact[2]), testData.compactSerializedJWSSignatureConst) 58 | 59 | let secondJWS = try! JWS(compactSerialization: compact) 60 | 61 | XCTAssertTrue(secondJWS.isValid(for: verifier)) 62 | } 63 | 64 | private func performTestECDeserialization(testData: ECTestKeyData) { 65 | let jws = try! JWS(compactSerialization: testData.compactSerializedJWSConst) 66 | let header = String(data: jws.header.data(), encoding: .utf8) 67 | let payload = String(data: jws.payload.data(), encoding: .utf8) 68 | XCTAssertEqual(header, getHeader(with: testData.signatureAlgorithm)) 69 | XCTAssertEqual(payload, plainTextPayload) 70 | } 71 | } 72 | // swiftlint:enable force_unwrapping 73 | -------------------------------------------------------------------------------- /Tests/JWEECTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JWEECTests.swift 3 | // Tests 4 | // 5 | // Created by Mikael Rucinsky on 07.12.20. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | class JWEECTests: ECCryptoTestCase { 28 | 29 | let pubJwk = """ 30 | { 31 | "crv": "P-256", 32 | "kty": "EC", 33 | "x": "CQJxA68WhgU3hztigbedfLtJitDhScq3XSnXgO0FV5o", 34 | "y": "WFg6s36izURa733WqeoJ8zXMd7ho5OSwdWnMsEPgTEI" 35 | } 36 | """.data(using: .utf8) 37 | 38 | let privJwk = """ 39 | { 40 | "crv": "P-256", 41 | "d": "920OCD0fW97YXbQNN-JaOtaDgbuNyVxXgKwjfXPPqv4", 42 | "kty": "EC", 43 | "x": "CQJxA68WhgU3hztigbedfLtJitDhScq3XSnXgO0FV5o", 44 | "y": "WFg6s36izURa733WqeoJ8zXMd7ho5OSwdWnMsEPgTEI" 45 | } 46 | """.data(using: .utf8) 47 | 48 | let plaintext = "Lorem Ipsum" 49 | 50 | func test() { 51 | 52 | guard let publicJWK = pubJwk, let publicKey = try? ECPublicKey(data: publicJWK) else { 53 | return XCTAssertThrowsError("publicKey is nil") 54 | } 55 | 56 | guard let privateJWK = privJwk, let privateKey = try? ECPrivateKey(data: privateJWK) else { 57 | return XCTAssertThrowsError("privateKey is nil") 58 | } 59 | 60 | guard let input = plaintext.data(using: .utf8), 61 | let encrypter = Encrypter(keyManagementAlgorithm: .ECDH_ES_A128KW, 62 | contentEncryptionAlgorithm: .A256CBCHS512, 63 | encryptionKey: publicKey), 64 | let decrypter = Decrypter(keyManagementAlgorithm: .ECDH_ES_A128KW, 65 | contentEncryptionAlgorithm: .A256CBCHS512, 66 | decryptionKey: privateKey) else { 67 | return XCTAssertThrowsError("wrong inputs") 68 | } 69 | 70 | let payload = Payload(input) 71 | let jwe = try! JWE(header: JWEHeader(keyManagementAlgorithm: .ECDH_ES_A128KW, contentEncryptionAlgorithm: .A256CBCHS512), 72 | payload: payload, 73 | encrypter: encrypter) 74 | let serialization = jwe.compactSerializedString 75 | 76 | let deserialization = try! JWE(compactSerialization: serialization) 77 | let decrypted = try! deserialization.decrypt(using: decrypter) 78 | XCTAssertEqual(input, decrypted.data()) 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWE/KeyManagement/DefaultCrypto/AESKW/AESKeyWrappingMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AESKeyWrappingMode.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 17.02.20. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | /// A key management mode in which the content encryption key value is encrypted to the intended recipient using a 27 | /// symmetric key wrapping algorithm. 28 | struct AESKeyWrappingMode { 29 | typealias KeyType = AES.KeyType 30 | 31 | private let keyManagementAlgorithm: KeyManagementAlgorithm 32 | private let contentEncryptionAlgorithm: ContentEncryptionAlgorithm 33 | private let sharedSymmetricKey: KeyType 34 | 35 | init( 36 | keyManagementAlgorithm: KeyManagementAlgorithm, 37 | contentEncryptionAlgorithm: ContentEncryptionAlgorithm, 38 | sharedSymmetricKey: KeyType 39 | ) { 40 | self.keyManagementAlgorithm = keyManagementAlgorithm 41 | self.contentEncryptionAlgorithm = contentEncryptionAlgorithm 42 | self.sharedSymmetricKey = sharedSymmetricKey 43 | } 44 | } 45 | 46 | extension AESKeyWrappingMode: EncryptionKeyManagementMode { 47 | var algorithm: KeyManagementAlgorithm { 48 | keyManagementAlgorithm 49 | } 50 | 51 | func determineContentEncryptionKey(with _: JWEHeader) throws -> EncryptionKeyManagementModeContext { 52 | let contentEncryptionKey = try SecureRandom.generate(count: contentEncryptionAlgorithm.keyLength) 53 | 54 | let encryptedKey = try AES.wrap( 55 | rawKey: contentEncryptionKey, 56 | keyEncryptionKey: sharedSymmetricKey, 57 | algorithm: keyManagementAlgorithm 58 | ) 59 | 60 | return .init( 61 | contentEncryptionKey: contentEncryptionKey, 62 | encryptedKey: encryptedKey, 63 | jweHeader: nil 64 | ) 65 | } 66 | } 67 | 68 | extension AESKeyWrappingMode: DecryptionKeyManagementMode { 69 | func determineContentEncryptionKey(from encryptedKey: Data, with _: JWEHeader) throws -> Data { 70 | let contentEncryptionKey = try AES.unwrap( 71 | wrappedKey: encryptedKey, 72 | keyEncryptionKey: sharedSymmetricKey, 73 | algorithm: keyManagementAlgorithm 74 | ) 75 | 76 | guard contentEncryptionKey.count == contentEncryptionAlgorithm.keyLength else { 77 | throw AESError.keyLengthNotSatisfied 78 | } 79 | 80 | return contentEncryptionKey 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/SecKeyECPublicKeyTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // SecKeyECPublicKeyTests.swift 4 | // Tests 5 | // 6 | // Created by Jarrod Moldrich on 27.10.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class SecKeyECPublicKeyTests: ECCryptoTestCase { 29 | 30 | func testPublicKeyComponents() { 31 | allTestData.forEach { testData in 32 | let components = try? testData.publicKey.ecPublicKeyComponents() 33 | XCTAssertNotNil(components) 34 | XCTAssertEqual(components?.crv, testData.expectedCurveType) 35 | XCTAssertEqual(components?.x, testData.expectedXCoordinate) 36 | XCTAssertEqual(components?.y, testData.expectedYCoordinate) 37 | } 38 | } 39 | 40 | func testPrivateKeyToPublicComponents() { 41 | XCTAssertThrowsError(try p256.privateKey.ecPublicKeyComponents()) { error in 42 | XCTAssertEqual(error as? JWKError, JWKError.notAPublicKey) 43 | } 44 | } 45 | 46 | func testJWKFromPublicKey() { 47 | allTestData.forEach { testData in 48 | let jwk = try? ECPublicKey(publicKey: testData.publicKey) 49 | 50 | XCTAssertNotNil(jwk) 51 | XCTAssertEqual(jwk?.crv.rawValue, testData.expectedCurveType) 52 | XCTAssertEqual(jwk?.x, testData.expectedXCoordinateBase64Url) 53 | XCTAssertEqual(jwk?.y, testData.expectedYCoordinateBase64Url) 54 | } 55 | } 56 | 57 | func testPublicKeyFromPublicComponents() throws { 58 | try allTestData.forEach { testData in 59 | let components = (testData.expectedCurveType, testData.expectedXCoordinate, testData.expectedYCoordinate) 60 | let secKey = try SecKey.representing(ecPublicKeyComponents: components) 61 | 62 | let data = SecKeyCopyExternalRepresentation(secKey, nil)! as Data 63 | let dataExpected = SecKeyCopyExternalRepresentation(testData.publicKey, nil)! as Data 64 | 65 | XCTAssertEqual(data, dataExpected) 66 | } 67 | } 68 | 69 | func testPublicKeyFromInvalidCurveType() { 70 | let components = ("P-Invalid", p256.expectedXCoordinate, p256.expectedYCoordinate) 71 | XCTAssertThrowsError(try SecKey.representing(ecPublicKeyComponents: components)) { error in 72 | XCTAssertEqual(error as? JWKError, JWKError.invalidECCurveType) 73 | } 74 | } 75 | } 76 | // swiftlint:enable force_unwrapping 77 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/Symmetric/SymmetricKeyCodable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SymmetricKeyCodable.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 10.07.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | extension SymmetricKey: Encodable { 27 | public func encode(to encoder: Encoder) throws { 28 | var commonParameters = encoder.container(keyedBy: JWKParameter.self) 29 | 30 | // The key type parameter is required. 31 | try commonParameters.encode(keyType, forKey: .keyType) 32 | 33 | // Other common parameters are optional. 34 | for parameter in parameters { 35 | // Only encode known parameters. 36 | if let key = JWKParameter(rawValue: parameter.key) { 37 | try commonParameters.encode(parameter.value, forKey: key) 38 | } 39 | } 40 | 41 | // Symmetric key specific parameters. 42 | var symmetricKeyParameters = encoder.container(keyedBy: SymmetricKeyParameter.self) 43 | try symmetricKeyParameters.encode(key, forKey: .key) 44 | } 45 | } 46 | 47 | extension SymmetricKey: Decodable { 48 | public init(from decoder: Decoder) throws { 49 | let commonParameters = try decoder.container(keyedBy: JWKParameter.self) 50 | 51 | // The key type parameter is required. 52 | guard try commonParameters.decode(String.self, forKey: .keyType) == JWKKeyType.OCT.rawValue else { 53 | throw DecodingError.keyNotFound( 54 | JWKParameter.keyType, 55 | DecodingError.Context.init( 56 | codingPath: [JWKParameter.keyType], 57 | debugDescription: "Wrong parameter: key type" 58 | ) 59 | ) 60 | } 61 | 62 | // Other common parameters are optional. 63 | var parameters: [String: String] = [:] 64 | for key in commonParameters.allKeys { 65 | parameters[key.rawValue] = try commonParameters.decode(String.self, forKey: key) 66 | } 67 | 68 | // RSA public key specific parameters. 69 | let symmetricKeyParameters = try decoder.container(keyedBy: SymmetricKeyParameter.self) 70 | let key = try symmetricKeyParameters.decode(String.self, forKey: .key) 71 | 72 | guard let keyData = Data(base64URLEncoded: key) else { 73 | throw JOSESwiftError.symmetricKeyNotBase64URLEncoded 74 | } 75 | 76 | self.init(key: keyData, additionalParameters: parameters) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/RSA/SecKeyRSAPublicKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecKeyRSAPublicKey.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 06.02.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | import Security 26 | 27 | extension SecKey: ExpressibleAsRSAPublicKeyComponents { 28 | public static func representing(rsaPublicKeyComponents components: RSAPublicKeyComponents) throws -> Self { 29 | return try instantiate(type: self, from: components) 30 | } 31 | 32 | // Generic helper function is needed so the compiler can infer the type of `Self`. 33 | private static func instantiate(type: T.Type, from components: RSAPublicKeyComponents) throws -> T { 34 | let keyData = try Data.representing(rsaPublicKeyComponents: components) 35 | 36 | // RSA key size is the number of bits of the modulus. 37 | let keySize = (components.modulus.count * 8) 38 | 39 | let attributes: [String: Any] = [ 40 | kSecAttrKeyType as String: kSecAttrKeyTypeRSA, 41 | kSecAttrKeyClass as String: kSecAttrKeyClassPublic, 42 | kSecAttrKeySizeInBits as String: keySize, 43 | kSecAttrIsPermanent as String: false 44 | ] 45 | 46 | var error: Unmanaged? 47 | guard let keyReference = SecKeyCreateWithData(keyData as CFData, attributes as CFDictionary, &error) else { 48 | // swiftlint:disable:next force_unwrapping 49 | throw error!.takeRetainedValue() as Error 50 | } 51 | 52 | guard let key = keyReference as? T else { 53 | throw JWKError.cannotConvertToSecKeyChildClasses 54 | } 55 | 56 | return key 57 | } 58 | 59 | public func rsaPublicKeyComponents() throws -> RSAPublicKeyComponents { 60 | guard 61 | let attributes = SecKeyCopyAttributes(self) as? [CFString: AnyObject], 62 | let keyClass = attributes[kSecAttrKeyClass], 63 | // All possible keyClasses are of type `CFString`. 64 | // swiftlint:disable:next force_cast 65 | keyClass as! CFString == kSecAttrKeyClassPublic 66 | else { 67 | throw JWKError.notAPublicKey 68 | } 69 | 70 | var error: Unmanaged? 71 | guard let keyData = SecKeyCopyExternalRepresentation(self, &error) else { 72 | // swiftlint:disable:next force_unwrapping 73 | throw error!.takeRetainedValue() as Error 74 | } 75 | 76 | return try (keyData as Data).rsaPublicKeyComponents() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/DataRSAPublicKeyTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // DataRSAPublicKeyTests.swift 4 | // Tests 5 | // 6 | // Created by Daniel Egger on 07.02.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class DataRSAPublicKeyTests: RSACryptoTestCase { 29 | 30 | func testLeadingZeroDropped() { 31 | let components = try! publicKeyAlice2048Data.rsaPublicKeyComponents() 32 | 33 | XCTAssertEqual(try! [UInt8](publicKeyAlice2048Data).read(.sequence).read(.integer).first!, 0x00) 34 | XCTAssertNotEqual([UInt8](components.modulus).first!, 0x00) 35 | } 36 | 37 | func testpublicKeyAlice2048Modulus() { 38 | let components = try? publicKeyAlice2048Data.rsaPublicKeyComponents() 39 | 40 | XCTAssertNotNil(components) 41 | 42 | let modulus = components!.modulus 43 | 44 | XCTAssertEqual(modulus, expectedModulus2048Data) 45 | } 46 | 47 | func testpublicKeyAlice2048Exponent() { 48 | let components = try? publicKeyAlice2048Data.rsaPublicKeyComponents() 49 | 50 | XCTAssertNotNil(components) 51 | 52 | let exponent = components!.exponent 53 | 54 | XCTAssertEqual(exponent, expectedExponentData) 55 | } 56 | 57 | func testPublicKey4096Modulus() { 58 | let components = try? publicKey4096Data.rsaPublicKeyComponents() 59 | 60 | XCTAssertNotNil(components) 61 | 62 | let modulus = components!.modulus 63 | 64 | XCTAssertEqual(modulus, expectedModulus4096Data) 65 | } 66 | 67 | func testPublicKey4096Exponent() { 68 | let components = try? publicKey4096Data.rsaPublicKeyComponents() 69 | 70 | XCTAssertNotNil(components) 71 | 72 | let exponent = components!.exponent 73 | 74 | XCTAssertEqual(exponent, expectedExponentData) 75 | } 76 | 77 | func testDataFromPublicKeyComponents2048() { 78 | let components = (expectedModulus2048Data, expectedExponentData) 79 | let data = try! Data.representing(rsaPublicKeyComponents: components) 80 | 81 | let expectedData = publicKeyAlice2048Data 82 | 83 | XCTAssertEqual(data, expectedData) 84 | } 85 | 86 | func testDataFromPublicKey4096() { 87 | let components = (expectedModulus4096Data, expectedExponentData) 88 | let data = try! Data.representing(rsaPublicKeyComponents: components) 89 | 90 | let expectedData = publicKey4096Data 91 | 92 | XCTAssertEqual(data, expectedData) 93 | } 94 | 95 | } 96 | // swiftlint:enable force_unwrapping 97 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at florian.scholochow@airsidemobile.com. All complaints will be reviewed and investigated and will result in a response that 59 | is deemed necessary and appropriate to the circumstances. The project team is 60 | obligated to maintain confidentiality with regard to the reporter of an incident. 61 | Further details of specific enforcement policies may be posted separately. 62 | 63 | Project maintainers who do not follow or enforce the Code of Conduct in good 64 | faith may face temporary or permanent repercussions as determined by other 65 | members of the project's leadership. 66 | 67 | ## Attribution 68 | 69 | This Code of Conduct is adapted from the [Contributor Covenant, version 1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html). 70 | 71 | 72 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/EC/SecKeyECPublicKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecKeyECPublicKey.swift 3 | // JOSESwift 4 | // 5 | // Created by Jarrod Moldrich on 02.07.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | import Security 26 | 27 | extension SecKey: ExpressibleAsECPublicKeyComponents { 28 | public static func representing(ecPublicKeyComponents components: ECPublicKeyComponents) throws -> Self { 29 | return try instantiate(type: self, from: components) 30 | } 31 | 32 | // Generic helper function is needed so the compiler can infer the type of `Self`. 33 | private static func instantiate(type: T.Type, from components: ECPublicKeyComponents) throws -> T { 34 | let keyData = try Data.representing(ecPublicKeyComponents: components) 35 | 36 | guard 37 | let keySize = ECCurveType(rawValue: components.crv)?.keyBitLength 38 | else { 39 | throw JWKError.invalidECCurveType 40 | } 41 | 42 | let attributes: [String: Any] = [ 43 | kSecAttrKeyType as String: kSecAttrKeyTypeEC, 44 | kSecAttrKeyClass as String: kSecAttrKeyClassPublic, 45 | kSecAttrKeySizeInBits as String: keySize, 46 | kSecAttrIsPermanent as String: false 47 | ] 48 | 49 | var error: Unmanaged? 50 | guard let keyReference = SecKeyCreateWithData(keyData as CFData, attributes as CFDictionary, &error) else { 51 | // swiftlint:disable:next force_unwrapping 52 | throw error!.takeRetainedValue() as Error 53 | } 54 | 55 | guard let key = keyReference as? T else { 56 | throw JWKError.cannotConvertToSecKeyChildClasses 57 | } 58 | 59 | return key 60 | } 61 | 62 | public func ecPublicKeyComponents() throws -> ECPublicKeyComponents { 63 | guard 64 | let attributes = SecKeyCopyAttributes(self) as? [CFString: AnyObject], 65 | let keyClass = attributes[kSecAttrKeyClass], 66 | // All possible keyClasses are of type `CFString`. 67 | // swiftlint:disable:next force_cast 68 | keyClass as! CFString == kSecAttrKeyClassPublic 69 | else { 70 | throw JWKError.notAPublicKey 71 | } 72 | 73 | var error: Unmanaged? 74 | guard let keyData = SecKeyCopyExternalRepresentation(self, &error) else { 75 | // swiftlint:disable:next force_unwrapping 76 | throw error!.takeRetainedValue() as Error 77 | } 78 | 79 | return try (keyData as Data).ecPublicKeyComponents() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Tests/JWSHMACTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // JWSHMACTests.swift 4 | // Tests 5 | // 6 | // Created by Tobias Hagemann on 15.04.21. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class JWSHMACTests: HMACCryptoTestCase { 29 | private func _testHMACDeserialization(algorithm: SignatureAlgorithm, compactSerializedJWS: String) { 30 | let jws = try! JWS(compactSerialization: compactSerializedJWS) 31 | XCTAssertEqual("{\"alg\":\"\(algorithm.rawValue)\"}", String(data: jws.header.data(), encoding: .utf8)) 32 | XCTAssertEqual(message, String(data: jws.payload.data(), encoding: .utf8)) 33 | 34 | let signer = Signer(signatureAlgorithm: algorithm, key: signingKey)! 35 | let signature = try! signer.sign(header: JWSHeader(algorithm: algorithm), payload: Payload(message.data(using: .utf8)!)) 36 | XCTAssertEqual(jws.signature.data(), signature) 37 | } 38 | 39 | private func _testHMACSerializationValidationAndDeserialization(algorithm: SignatureAlgorithm) { 40 | let header = JWSHeader(algorithm: algorithm) 41 | let payload = Payload(message.data(using: .utf8)!) 42 | let signer = Signer(signatureAlgorithm: algorithm, key: signingKey)! 43 | let jws = try! JWS(header: header, payload: payload, signer: signer) 44 | let compactSerializedJWS = jws.compactSerializedString 45 | 46 | let secondJWS = try! JWS(compactSerialization: compactSerializedJWS) 47 | let verifier = Verifier(signatureAlgorithm: algorithm, key: signingKey) 48 | 49 | XCTAssertTrue(secondJWS.isValid(for: verifier!)) 50 | XCTAssertEqual(message, String(data: secondJWS.payload.data(), encoding: .utf8)) 51 | XCTAssertEqual("{\"alg\":\"\(algorithm.rawValue)\"}", String(data: jws.header.data(), encoding: .utf8)) 52 | } 53 | 54 | func testHMACDeserialization() { 55 | _testHMACDeserialization(algorithm: .HS256, compactSerializedJWS: compactSerializedJWSHS256Const) 56 | _testHMACDeserialization(algorithm: .HS384, compactSerializedJWS: compactSerializedJWSHS384Const) 57 | _testHMACDeserialization(algorithm: .HS512, compactSerializedJWS: compactSerializedJWSHS512Const) 58 | } 59 | 60 | func testHMACSerializationValidationAndDeserialization() { 61 | _testHMACSerializationValidationAndDeserialization(algorithm: .HS256) 62 | _testHMACSerializationValidationAndDeserialization(algorithm: .HS384) 63 | _testHMACSerializationValidationAndDeserialization(algorithm: .HS512) 64 | } 65 | } 66 | // swiftlint:enable force_unwrapping 67 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/EC/SecKeyECPrivateKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecKeyECPrivateKey.swift 3 | // JOSESwift 4 | // 5 | // Created by Jarrod Moldrich on 10.01.2019. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | //// 23 | 24 | import Foundation 25 | import Security 26 | 27 | extension SecKey: ExpressibleAsECPrivateKeyComponents { 28 | public static func representing(ecPrivateKeyComponents components: ECPrivateKeyComponents) throws -> Self { 29 | return try instantiate(type: self, from: components) 30 | } 31 | 32 | // Generic helper function is needed so the compiler can infer the type of `Self`. 33 | private static func instantiate(type: T.Type, from components: ECPrivateKeyComponents) throws -> T { 34 | let keyData = try Data.representing(ecPrivateKeyComponents: components) 35 | 36 | guard 37 | let keySize = ECCurveType(rawValue: components.crv)?.keyBitLength 38 | else { 39 | throw JWKError.invalidECCurveType 40 | } 41 | 42 | let attributes: [String: Any] = [ 43 | kSecAttrKeyType as String: kSecAttrKeyTypeEC, 44 | kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, 45 | kSecAttrKeySizeInBits as String: keySize, 46 | kSecAttrIsPermanent as String: false 47 | ] 48 | 49 | var error: Unmanaged? 50 | guard let keyReference = SecKeyCreateWithData(keyData as CFData, attributes as CFDictionary, &error) else { 51 | // swiftlint:disable:next force_unwrapping 52 | throw error!.takeRetainedValue() as Error 53 | } 54 | 55 | guard let key = keyReference as? T else { 56 | throw JWKError.cannotConvertToSecKeyChildClasses 57 | } 58 | 59 | return key 60 | } 61 | 62 | public func ecPrivateKeyComponents() throws -> ECPrivateKeyComponents { 63 | guard 64 | let attributes = SecKeyCopyAttributes(self) as? [CFString: AnyObject], 65 | let keyClass = attributes[kSecAttrKeyClass], 66 | // All possible keyClasses are of type `CFString`. 67 | // swiftlint:disable:next force_cast 68 | keyClass as! CFString == kSecAttrKeyClassPrivate 69 | else { 70 | throw JWKError.notAPrivateKey 71 | } 72 | 73 | var error: Unmanaged? 74 | guard let keyData = SecKeyCopyExternalRepresentation(self, &error) else { 75 | // swiftlint:disable:next force_unwrapping 76 | throw error!.takeRetainedValue() as Error 77 | } 78 | 79 | return try (keyData as Data).ecPrivateKeyComponents() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | macos: circleci/macos@2.5.4 5 | 6 | executors: 7 | mac-executor: 8 | macos: 9 | xcode: "15.4.0" 10 | resource_class: macos.m1.medium.gen1 11 | 12 | jobs: 13 | prepare-build: 14 | executor: mac-executor 15 | steps: 16 | - checkout 17 | - restore_cache: 18 | keys: 19 | - gem-cache-v3-{{ checksum "Gemfile.lock" }} 20 | - gem-cache-v3 21 | - run: 22 | name: Find Ruby Version 23 | command: | 24 | export RUBY_VERSION=$(cat .ruby-version || echo system) 25 | echo "ORB_VAL_RUBY_VERSION=$RUBY_VERSION" >> $BASH_ENV 26 | - macos/switch-ruby: 27 | version: $ORB_VAL_RUBY_VERSION 28 | - run: 29 | command: | 30 | bundle config --local path 'vendor/bundle' 31 | bundle install --jobs 4 --retry 3 32 | - save_cache: 33 | key: gem-cache-v3-{{ checksum "Gemfile.lock" }} 34 | paths: 35 | - vendor/bundle 36 | - persist_to_workspace: 37 | root: . 38 | paths: 39 | - vendor/bundle 40 | 41 | test: 42 | executor: mac-executor 43 | steps: 44 | - checkout 45 | - attach_workspace: 46 | at: . 47 | - run: 48 | name: Test 49 | command: | 50 | bundle config --local path vendor/bundle 51 | bundle exec fastlane test 52 | - store_test_results: 53 | path: fastlane/test_output 54 | - persist_to_workspace: 55 | root: . 56 | paths: 57 | - fastlane/test_output/derived_data/Logs/Test/ 58 | 59 | lint: 60 | executor: mac-executor 61 | steps: 62 | - checkout 63 | - attach_workspace: 64 | at: . 65 | - run: 66 | name: Lint 67 | command: | 68 | cd BuildTools && swift package resolve && cd .. 69 | bundle config --local path vendor/bundle 70 | bundle exec fastlane lint 71 | - store_artifacts: 72 | path: fastlane/test_output 73 | - persist_to_workspace: 74 | root: . 75 | paths: 76 | - fastlane/test_output/swiftformat 77 | - fastlane/test_output/swiftlint 78 | 79 | sonarcloud: 80 | executor: mac-executor 81 | steps: 82 | - checkout 83 | - attach_workspace: 84 | at: . 85 | - run: 86 | name: Sonarcloud 87 | command: | 88 | if [ -z "$FL_SONAR_LOGIN" ]; then 89 | echo "No Sonarcloud token is set. Failing." 90 | exit 1; 91 | fi 92 | HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 brew install sonar-scanner --display-times || true 93 | bundle config --local path vendor/bundle 94 | bundle exec fastlane sonarqube 95 | 96 | workflows: 97 | pr_workflow: 98 | jobs: 99 | - prepare-build 100 | - lint: 101 | requires: 102 | - prepare-build 103 | - test: 104 | requires: 105 | - prepare-build 106 | - sonarcloud: 107 | requires: 108 | - lint 109 | - test 110 | filters: 111 | branches: 112 | ignore: /pull\/[0-9]+/ # Forked pull requests 113 | context: 114 | - sonarcloud 115 | -------------------------------------------------------------------------------- /Tests/HMACTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // HMACTests.swift 4 | // Tests 5 | // 6 | // Created by Carol Capek on 05.12.17. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class HMACTests: HMACCryptoTestCase { 29 | func testHMAC256Calculation() { 30 | let hmacOutput = try! HMAC.calculate(from: testData, with: testKey, using: .SHA256) 31 | 32 | XCTAssertEqual(hmacOutput, hmac256TestOutput) 33 | } 34 | 35 | func testHMAC256CalculationWithFalseKey() { 36 | let falseKey = "abcdefg".hexadecimalToData()! 37 | 38 | let hmacOutput = try! HMAC.calculate(from: testData, with: falseKey, using: .SHA256) 39 | 40 | XCTAssertNotEqual(hmacOutput, hmac256TestOutput) 41 | } 42 | 43 | func testHMAC256CalculationWithFalseData() { 44 | let falseData = "abcdefg".hexadecimalToData()! 45 | 46 | let hmacOutput = try! HMAC.calculate(from: falseData, with: testKey, using: .SHA256) 47 | 48 | XCTAssertNotEqual(hmacOutput, hmac256TestOutput) 49 | } 50 | 51 | func testHMAC384Calculation() { 52 | let hmacOutput = try! HMAC.calculate(from: testData, with: testKey, using: .SHA384) 53 | 54 | XCTAssertEqual(hmacOutput, hmac384TestOutput) 55 | } 56 | 57 | func testHMAC384CalculationWithFalseKey() { 58 | let falseKey = "abcdefg".hexadecimalToData()! 59 | 60 | let hmacOutput = try! HMAC.calculate(from: testData, with: falseKey, using: .SHA384) 61 | 62 | XCTAssertNotEqual(hmacOutput, hmac384TestOutput) 63 | } 64 | 65 | func testHMAC384CalculationWithFalseData() { 66 | let falseData = "abcdefg".hexadecimalToData()! 67 | 68 | let hmacOutput = try! HMAC.calculate(from: falseData, with: testKey, using: .SHA384) 69 | 70 | XCTAssertNotEqual(hmacOutput, hmac384TestOutput) 71 | } 72 | 73 | func testHMAC512Calculation() { 74 | let hmacOutput = try! HMAC.calculate(from: testData, with: testKey, using: .SHA512) 75 | 76 | XCTAssertEqual(hmacOutput, hmac512TestOutput) 77 | } 78 | 79 | func testHMAC512CalculationWithFalseKey() { 80 | let falseKey = "abcdefg".hexadecimalToData()! 81 | 82 | let hmacOutput = try! HMAC.calculate(from: testData, with: falseKey, using: .SHA512) 83 | 84 | XCTAssertNotEqual(hmacOutput, hmac512TestOutput) 85 | } 86 | 87 | func testHMAC512CalculationWithFalseData() { 88 | let falseData = "abcdefg".hexadecimalToData()! 89 | 90 | let hmacOutput = try! HMAC.calculate(from: falseData, with: testKey, using: .SHA512) 91 | 92 | XCTAssertNotEqual(hmacOutput, hmac512TestOutput) 93 | } 94 | } 95 | // swiftlint:enable force_unwrapping 96 | -------------------------------------------------------------------------------- /JOSESwift/Sources/CryptoPrimitives/HMAC/HMAC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HMAC.swift 3 | // JOSESwift 4 | // 5 | // Created by Carol Capek on 05.12.17. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | import CommonCrypto 26 | 27 | enum HMACError: Error { 28 | case algorithmNotSupported 29 | case inputMustBeGreaterThanZero 30 | } 31 | 32 | extension HMACAlgorithm { 33 | var ccAlgorithm: CCAlgorithm { 34 | switch self { 35 | case .SHA512: 36 | return CCAlgorithm(kCCHmacAlgSHA512) 37 | case .SHA384: 38 | return CCAlgorithm(kCCHmacAlgSHA384) 39 | case .SHA256: 40 | return CCAlgorithm(kCCHmacAlgSHA256) 41 | } 42 | } 43 | 44 | var ccPseudoRandomAlgorithm: CCPseudoRandomAlgorithm { 45 | switch self { 46 | case .SHA512: 47 | return CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512) 48 | case .SHA384: 49 | return CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA384) 50 | case .SHA256: 51 | return CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256) 52 | } 53 | } 54 | } 55 | 56 | internal struct HMAC { 57 | typealias KeyType = Data 58 | 59 | /// Calculates a HMAC of an input with a specific HMAC algorithm and the corresponding HMAC key. 60 | /// 61 | /// - Parameters: 62 | /// - input: The input to calculate a HMAC for. 63 | /// - key: The key used in the HMAC algorithm. Must not be empty. 64 | /// - algorithm: The algorithm used to calculate the HMAC. 65 | /// - Returns: The calculated HMAC. 66 | static func calculate(from input: Data, with key: Data, using algorithm: HMACAlgorithm) throws -> Data { 67 | guard input.count > 0 else { 68 | throw HMACError.inputMustBeGreaterThanZero 69 | } 70 | 71 | var hmacOutData = Data(count: algorithm.outputLength) 72 | 73 | // Force unwrapping is ok, since input count is checked and key and algorithm are assumed not to be empty. 74 | // From the docs: If the baseAddress of this buffer is nil, the count is zero. 75 | // swiftlint:disable force_unwrapping 76 | hmacOutData.withUnsafeMutableBytes { hmacOutBytes in 77 | key.withUnsafeBytes { keyBytes in 78 | input.withUnsafeBytes { inputBytes in 79 | CCHmac( 80 | algorithm.ccAlgorithm, 81 | keyBytes.baseAddress!, key.count, 82 | inputBytes.baseAddress!, input.count, 83 | hmacOutBytes.baseAddress! 84 | ) 85 | } 86 | } 87 | } 88 | // swiftlint:enable force_unwrapping 89 | 90 | return hmacOutData 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /JOSESwift/Sources/CryptoPrimitives/PBES2/PBES2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PBES2.swift 3 | // JOSESwift 4 | // 5 | // Created by Tobias Hagemann on 07.12.23. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | import CommonCrypto 26 | 27 | internal enum PBES2Error: Error { 28 | case unknownOrUnsupportedAlgorithm 29 | } 30 | 31 | internal struct PBES2 { 32 | static func deriveWrappingKey( 33 | password: String, 34 | algorithm: KeyManagementAlgorithm, 35 | saltInput: Data, 36 | iterationCount: Int 37 | ) throws -> Data { 38 | let supportedKeyManagementAlgorithms: [KeyManagementAlgorithm] = [ 39 | .PBES2_HS256_A128KW, 40 | .PBES2_HS384_A192KW, 41 | .PBES2_HS512_A256KW 42 | ] 43 | 44 | guard 45 | supportedKeyManagementAlgorithms.contains(algorithm), 46 | let passwordData = password.data(using: .utf8), 47 | let algorithmData = algorithm.rawValue.data(using: .utf8), 48 | let derivedKeyLength = algorithm.derivedKeyLength 49 | else { 50 | throw PBES2Error.unknownOrUnsupportedAlgorithm 51 | } 52 | 53 | guard let hmacAlgorithm = algorithm.hmacAlgorithm else { 54 | throw HMACError.algorithmNotSupported 55 | } 56 | 57 | let saltData = algorithmData + Data([0x00]) + saltInput 58 | 59 | var derivedKey = Data(count: derivedKeyLength) 60 | let result: Int = derivedKey.withUnsafeMutableBytes { derivedKeyBytes in 61 | saltData.withUnsafeBytes { saltBytes in 62 | passwordData.withUnsafeBytes { passwordBytes in 63 | guard let derivedKeyBaseAddress = derivedKeyBytes.baseAddress, 64 | let saltBaseAddress = saltBytes.baseAddress, 65 | let passwordBaseAddress = passwordBytes.baseAddress else { 66 | return Int(kCCParamError) 67 | } 68 | return Int(CCKeyDerivationPBKDF( 69 | CCPBKDFAlgorithm(kCCPBKDF2), 70 | passwordBaseAddress, 71 | passwordData.count, 72 | saltBaseAddress, 73 | saltData.count, 74 | hmacAlgorithm.ccPseudoRandomAlgorithm, 75 | UInt32(iterationCount), 76 | derivedKeyBaseAddress, 77 | derivedKeyBytes.count 78 | )) 79 | } 80 | } 81 | } 82 | 83 | if result == kCCSuccess { 84 | return derivedKey 85 | } else { 86 | throw NSError(domain: NSOSStatusErrorDomain, code: Int(result), userInfo: nil) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Tests/SecKeyECPrivateKeyTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable force_unwrapping 2 | // 3 | // SecKeyECPrivateKeyTests.swift 4 | // Tests 5 | // 6 | // Created by Jarrod Moldrich on 10.01.2019. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import XCTest 26 | @testable import JOSESwift 27 | 28 | class SecKeyECPrivateKeyTests: ECCryptoTestCase { 29 | 30 | func testPrivateKeyComponents() { 31 | allTestData.forEach { testData in 32 | let components = try? testData.privateKey.ecPrivateKeyComponents() 33 | XCTAssertNotNil(components) 34 | XCTAssertEqual(components?.crv, testData.expectedCurveType) 35 | XCTAssertEqual(components?.x, testData.expectedXCoordinate) 36 | XCTAssertEqual(components?.y, testData.expectedYCoordinate) 37 | XCTAssertEqual(components?.d, testData.expectedPrivateOctetString) 38 | } 39 | } 40 | 41 | func testPublicKeyToPrivateComponents() { 42 | XCTAssertThrowsError(try p256.publicKey.ecPrivateKeyComponents()) { error in 43 | XCTAssertEqual(error as? JWKError, JWKError.notAPrivateKey) 44 | } 45 | } 46 | 47 | func testJWKFromPrivateKey() { 48 | allTestData.forEach { testData in 49 | let jwk = try? ECPrivateKey(privateKey: testData.privateKey) 50 | 51 | XCTAssertNotNil(jwk) 52 | XCTAssertEqual(jwk?.crv.rawValue, testData.expectedCurveType) 53 | XCTAssertEqual(jwk?.x, testData.expectedXCoordinateBase64Url) 54 | XCTAssertEqual(jwk?.y, testData.expectedYCoordinateBase64Url) 55 | XCTAssertEqual(jwk?.privateKey, testData.expectedPrivateBase64Url) 56 | } 57 | } 58 | 59 | func testPrivateKeyFromPrivateComponents() throws { 60 | try allTestData.forEach { testData in 61 | let components = ( 62 | testData.expectedCurveType, 63 | testData.expectedXCoordinate, 64 | testData.expectedYCoordinate, 65 | testData.expectedPrivateOctetString 66 | ) 67 | let secKey = try SecKey.representing(ecPrivateKeyComponents: components) 68 | 69 | let data = SecKeyCopyExternalRepresentation(secKey, nil)! as Data 70 | let dataExpected = SecKeyCopyExternalRepresentation(testData.privateKey, nil)! as Data 71 | 72 | XCTAssertEqual(data, dataExpected) 73 | } 74 | } 75 | 76 | func testPrivateKeyFromInvalidCurveType() { 77 | let components = ("P-Invalid", p256.expectedXCoordinate, p256.expectedYCoordinate, p256.expectedPrivateOctetString) 78 | XCTAssertThrowsError(try SecKey.representing(ecPrivateKeyComponents: components)) { error in 79 | XCTAssertEqual(error as? JWKError, JWKError.invalidECCurveType) 80 | } 81 | } 82 | } 83 | // swiftlint:enable force_unwrapping 84 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWK/JWKSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JWKSet.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 15.02.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | /// A JWK set is a structure that represents a set of JWKs. 27 | public struct JWKSet { 28 | 29 | /// The `keys` member is an array of JWKs. 30 | public let keys: [JWK] 31 | 32 | /// Initializes a `JWKSet` containing the given keys. 33 | /// 34 | /// - Parameter keys: The keys that the JWK set should contain. 35 | public init(keys: [JWK]) { 36 | self.keys = keys 37 | } 38 | 39 | /// Initializes a `JWKSet` from given JSON data. 40 | /// 41 | /// - Parameter data: The data containing the JWK set. 42 | /// - Throws: A `DecodingError` with relevant information. 43 | public init(data: Data) throws { 44 | self = try JSONDecoder().decode(JWKSet.self, from: data) 45 | } 46 | 47 | /// Computes the JSON representation of the `JWKSet`. 48 | /// 49 | /// - Returns: The JSON representation of the JWK set as `String` or 50 | /// `nil` if the encoding failed. 51 | public func jsonString() -> String? { 52 | guard let json = jsonData() else { 53 | return nil 54 | } 55 | 56 | return String(data: json, encoding: .utf8) 57 | } 58 | 59 | /// Computes the JSON representation of the `JWKSet`. 60 | /// 61 | /// - Returns: The JSON representation of the JWK set as `Data` or 62 | /// `nil` if the encoding failed. 63 | public func jsonData() -> Data? { 64 | let encoder = JSONEncoder() 65 | encoder.outputFormatting = .sortedKeys 66 | return try? encoder.encode(self) 67 | } 68 | } 69 | 70 | extension JWKSet: Collection { 71 | public typealias ArrayType = [JWK] 72 | 73 | public typealias Element = ArrayType.Element 74 | public typealias Index = ArrayType.Index 75 | public typealias Iterator = ArrayType.Iterator 76 | 77 | public var startIndex: Index { 78 | return self.keys.startIndex 79 | } 80 | 81 | public var endIndex: Index { 82 | return self.keys.endIndex 83 | } 84 | 85 | public subscript(index: Index) -> Element { 86 | return keys[index] 87 | } 88 | 89 | public func index(after index: Index) -> Index { 90 | return self.keys.index(after: index) 91 | } 92 | 93 | public func makeIterator() -> IndexingIterator { 94 | return self.keys.makeIterator() 95 | } 96 | } 97 | 98 | extension JWKSet: ExpressibleByArrayLiteral { 99 | public typealias ArrayLiteralElement = Element 100 | 101 | public init(arrayLiteral elements: ArrayLiteralElement...) { 102 | var keys: [Element] = [] 103 | for element in elements { 104 | keys.append(element) 105 | } 106 | 107 | self.init(keys: keys) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /JOSESwift/Sources/Common/JOSEHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JOSEHeader.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 20/09/2017. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | enum HeaderParsingError: Error { 27 | case requiredHeaderParameterMissing(parameter: String) 28 | case headerIsNotValidJSONObject 29 | } 30 | 31 | /// A `JOSEHeader` is a JSON object representing various Header Parameters. 32 | /// Moreover, a `JOSEHeader` is a `JOSEObjectComponent`. Therefore it can be initialized from and converted to `Data`. 33 | protocol JOSEHeader: DataConvertible, CommonHeaderParameterSpace { 34 | var headerData: Data { get } 35 | var parameters: [String: Any] { get set } 36 | 37 | var requiredParameters: [String] { get } 38 | 39 | init(parameters: [String: Any], headerData: Data) throws 40 | 41 | init?(_ data: Data) 42 | func data() -> Data 43 | } 44 | 45 | extension JOSEHeader { 46 | public mutating func set(_ value: Any, forParameter parameterName: String) throws { 47 | guard !requiredParameters.contains(parameterName) else { 48 | throw JOSESwiftError.invalidHeaderParameterValue 49 | } 50 | 51 | guard JSONSerialization.isValidJSONObject([parameterName: value]) else { 52 | throw JOSESwiftError.invalidHeaderParameterValue 53 | } 54 | 55 | parameters[parameterName] = value 56 | } 57 | 58 | public func get(parameter parameterName: String) -> Any? { 59 | return parameters[parameterName] 60 | } 61 | 62 | @discardableResult public mutating func remove(parameter parameterName: String) -> Any? { 63 | guard !requiredParameters.contains(parameterName) else { 64 | return nil 65 | } 66 | 67 | return parameters.removeValue(forKey: parameterName) 68 | } 69 | } 70 | 71 | // `DataConvertible` implementation. 72 | extension JOSEHeader { 73 | public init?(_ data: Data) { 74 | // Verify that the header is a completely valid JSON object. 75 | guard 76 | let json = try? JSONSerialization.jsonObject(with: data, options: []), 77 | let parameters = json as? [String: Any] 78 | else { 79 | return nil 80 | } 81 | 82 | try? self.init(parameters: parameters, headerData: data) 83 | } 84 | 85 | public func data() -> Data { 86 | return headerData 87 | } 88 | } 89 | 90 | /// JWS and JWE share a common Header Parameter space that both JWS and JWE headers must support. 91 | /// Those header parameters may have a different meaning depending on whether they are part of a JWE or JWS. 92 | public protocol CommonHeaderParameterSpace { 93 | var jku: URL? { get set } 94 | var jwk: String? { get set } 95 | var jwkTyped: JWK? { get set } 96 | var kid: String? { get set } 97 | var x5u: URL? { get set } 98 | var x5c: [String]? { get set } 99 | var x5t: String? { get set } 100 | var x5tS256: String? { get set } 101 | var typ: String? { get set } 102 | var cty: String? { get set } 103 | var crit: [String]? { get set } 104 | } 105 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWE/ContentEncryption/DefaultCrypto/AESGCM/AESGCMEncryption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AESGCMEncryption.swift 3 | // JOSESwift 4 | // 5 | // Created by Tobias Hagemann on 12.08.22. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import Foundation 25 | 26 | struct AESGCMEncryption { 27 | private let contentEncryptionAlgorithm: ContentEncryptionAlgorithm 28 | 29 | init(contentEncryptionAlgorithm: ContentEncryptionAlgorithm) { 30 | self.contentEncryptionAlgorithm = contentEncryptionAlgorithm 31 | } 32 | 33 | func encrypt(_ plaintext: Data, additionalAuthenticatedData: Data, contentEncryptionKey: Data) throws -> ContentEncryptionContext { 34 | let iv = try SecureRandom.generate(count: contentEncryptionAlgorithm.initializationVectorLength) 35 | return try encrypt(plaintext, initializationVector: iv, additionalAuthenticatedData: additionalAuthenticatedData, contentEncryptionKey: contentEncryptionKey) 36 | } 37 | 38 | func encrypt(_ plaintext: Data, initializationVector: Data, additionalAuthenticatedData: Data, contentEncryptionKey: Data) throws -> ContentEncryptionContext { 39 | return try AESGCM.encrypt(plaintext: plaintext, 40 | encryptionKey: contentEncryptionKey, 41 | initializationVector: initializationVector, 42 | additionalAuthenticatedData: additionalAuthenticatedData) 43 | } 44 | 45 | func decrypt(_ ciphertext: Data, initializationVector: Data, additionalAuthenticatedData: Data, authenticationTag: Data, contentEncryptionKey: Data) throws -> Data { 46 | return try AESGCM.decrypt(cipherText: ciphertext, 47 | decryptionKey: contentEncryptionKey, 48 | initializationVector: initializationVector, 49 | authenticationTag: authenticationTag, 50 | additionalAuthenticatedData: additionalAuthenticatedData) 51 | } 52 | } 53 | 54 | extension AESGCMEncryption: ContentEncrypter, ContentDecrypter { 55 | var algorithm: ContentEncryptionAlgorithm { 56 | contentEncryptionAlgorithm 57 | } 58 | 59 | func encrypt(headerData: Data, payload: Payload, contentEncryptionKey: Data) throws -> ContentEncryptionContext { 60 | guard contentEncryptionAlgorithm.checkKeyLength(for: contentEncryptionKey) else { 61 | throw JWEError.keyLengthNotSatisfied 62 | } 63 | 64 | let plaintext = payload.data() 65 | let additionalAuthenticatedData = headerData.base64URLEncodedData() 66 | 67 | return try encrypt(plaintext, additionalAuthenticatedData: additionalAuthenticatedData, contentEncryptionKey: contentEncryptionKey) 68 | } 69 | 70 | func decrypt(decryptionContext: ContentDecryptionContext) throws -> Data { 71 | guard contentEncryptionAlgorithm.checkKeyLength(for: decryptionContext.contentEncryptionKey) else { 72 | throw JWEError.keyLengthNotSatisfied 73 | } 74 | 75 | return try decrypt( 76 | decryptionContext.ciphertext, 77 | initializationVector: decryptionContext.initializationVector, 78 | additionalAuthenticatedData: decryptionContext.additionalAuthenticatedData, 79 | authenticationTag: decryptionContext.authenticationTag, 80 | contentEncryptionKey: decryptionContext.contentEncryptionKey 81 | ) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /JOSESwift.xcodeproj/xcshareddata/xcschemes/JOSESwift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 69 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Tests/DataECPublicKeyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataECPublicKeyTests.swift 3 | // Tests 4 | // 5 | // Created by Jarrod Moldrich on 27.10.18. 6 | // 7 | // --------------------------------------------------------------------------- 8 | // Copyright 2024 Airside Mobile Inc. 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // --------------------------------------------------------------------------- 22 | // 23 | 24 | import XCTest 25 | @testable import JOSESwift 26 | 27 | class DataECPublicKeyTests: ECCryptoTestCase { 28 | 29 | func testPublicKeyCoordinates() { 30 | allTestData.forEach { testData in 31 | let components = _getComponents(testData: testData) 32 | XCTAssertEqual(testData.expectedXCoordinate, components?.x) 33 | XCTAssertEqual(testData.expectedYCoordinate, components?.y) 34 | } 35 | } 36 | 37 | func testPublicKeyCurveType() { 38 | allTestData.forEach { testData in 39 | let components = _getComponents(testData: testData) 40 | XCTAssertEqual(testData.expectedCurveType, components?.crv) 41 | } 42 | } 43 | 44 | func testDataFromPublicKeyComponents() { 45 | allTestData.forEach { testData in 46 | let components = (testData.expectedCurveType, testData.expectedXCoordinate, testData.expectedYCoordinate) 47 | let data = try! Data.representing(ecPublicKeyComponents: components) 48 | XCTAssertEqual(data, testData.publicKeyData) 49 | } 50 | } 51 | 52 | func testCompressedPointRejection() { 53 | allTestData.forEach { testData in 54 | let errorHandler = { (error: Error) in 55 | switch error { 56 | case JOSESwiftError.compressedCurvePointsUnsupported: return 57 | default: XCTFail("Unexpected error: \(error)") 58 | } 59 | } 60 | checkInvalidDataToPublicKey( 61 | compression: UInt8(0x03), 62 | x: testData.expectedXCoordinate, 63 | y: testData.expectedYCoordinate, 64 | errorHandler: errorHandler 65 | ) 66 | } 67 | } 68 | 69 | func testInvalidPointOctetLength() { 70 | allTestData.forEach { testData in 71 | let errorHandler = { (error: Error) in 72 | switch error { 73 | case JOSESwiftError.invalidCurvePointOctetLength: return 74 | default: XCTFail("Unexpected error: \(error)") 75 | } 76 | } 77 | checkInvalidDataToPublicKey( 78 | compression: UInt8(0x04), 79 | x: testData.expectedXCoordinate, 80 | y: testData.expectedYCoordinate.dropLast(), 81 | errorHandler: errorHandler 82 | ) 83 | } 84 | } 85 | 86 | // MARK: Helper functions 87 | 88 | private func _getComponents(testData: ECTestKeyData) -> ECPublicKeyComponents? { 89 | guard let components = try? testData.publicKeyData.ecPublicKeyComponents() else { 90 | XCTFail("components = try? keyData.ecPublicKeyComponents()") 91 | return nil 92 | } 93 | return components 94 | } 95 | 96 | private func checkInvalidDataToPublicKey(compression: UInt8, x: Data, y: Data, errorHandler: (Error) -> Void) { 97 | let keyData = ECTestKeyData.createKeyData(compression: compression, x: x, y: y, privateKey: nil) 98 | XCTAssertThrowsError( 99 | try keyData.ecPublicKeyComponents(), 100 | "No error thrown for invalid point data" 101 | ) { error in errorHandler(error) } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWS/Verifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Verifier.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 28/09/2017. 6 | // Modified by Jarrod Moldrich on 02.07.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol VerifierProtocol { 28 | var algorithm: SignatureAlgorithm { get } 29 | 30 | /// Verifies a signature against a given signing input with a specific algorithm and the corresponding key. 31 | /// 32 | /// - Parameters: 33 | /// - signingInput: The input to verify against. 34 | /// - signature: The signature to verify. 35 | /// - Returns: True if the signature is verified, false if it is not verified. 36 | /// - Throws: `JWSError` if any error occurs during verifying. 37 | func verify(_ signingInput: Data, against signature: Data) throws -> Bool 38 | } 39 | 40 | public struct Verifier { 41 | let verifier: VerifierProtocol 42 | 43 | /// Constructs a verifier used to verify a JWS. 44 | /// 45 | /// - Parameters: 46 | /// - signatureAlgorithm: The desired `SignatureAlgorithm`. 47 | /// - key: The key used to verify the JWS's signature or message authentication code (MAC). 48 | /// Currently supported key types are: `SecKey` and `Data`. 49 | /// - For _digital signature algorithms_ it is the sender's public key (`SecKey`) 50 | /// with which the JWS signature should be verified. 51 | /// - For _MAC algorithms_ it is the secret symmetric key (`Data`) 52 | /// shared between the sender and the recipient. 53 | /// - Returns: A fully initialized `Verifier` or `nil` if provided key is of the wrong type. 54 | public init?(signatureAlgorithm: SignatureAlgorithm, key: KeyType) { 55 | switch signatureAlgorithm { 56 | case .HS256, .HS384, .HS512: 57 | guard type(of: key) is HMACVerifier.KeyType.Type else { 58 | return nil 59 | } 60 | // swiftlint:disable:next force_cast 61 | self.verifier = HMACVerifier(algorithm: signatureAlgorithm, key: key as! HMACVerifier.KeyType) 62 | case .RS256, .RS384, .RS512, .PS256, .PS384, .PS512: 63 | guard type(of: key) is RSAVerifier.KeyType.Type else { 64 | return nil 65 | } 66 | // swiftlint:disable:next force_cast 67 | self.verifier = RSAVerifier(algorithm: signatureAlgorithm, publicKey: key as! RSAVerifier.KeyType) 68 | case .ES256, .ES384, .ES512: 69 | guard type(of: key) is ECVerifier.KeyType.Type else { 70 | return nil 71 | } 72 | self.verifier = ECVerifier(algorithm: signatureAlgorithm, publicKey: key as! ECVerifier.KeyType) 73 | } 74 | } 75 | 76 | /// Constructs a verifier used to verify a JWS signature. 77 | /// 78 | /// - Parameters: 79 | /// - customVerifier: A custom signature verification implementation. 80 | /// 81 | /// It is the implementors responsibility to ensure compliance with the necessary specifications. 82 | /// - Returns: A fully initialized `Verifier`. 83 | public init(customVerifier verifier: VerifierProtocol) { 84 | self.verifier = verifier 85 | } 86 | 87 | internal func verify(header: JWSHeader, and payload: Payload, against signature: Data) throws -> Bool { 88 | guard let alg = header.algorithm, alg == verifier.algorithm else { 89 | throw JWSError.algorithmMismatch 90 | } 91 | 92 | guard let signingInput = [header, payload].asJOSESigningInput() else { 93 | throw JWSError.cannotComputeSigningInput 94 | } 95 | 96 | return try verifier.verify(signingInput, against: signature) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /JOSESwift/Sources/JWS/Signer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Signer.swift 3 | // JOSESwift 4 | // 5 | // Created by Daniel Egger on 18/08/2017. 6 | // Modified by Jarrod Moldrich on 02.07.18. 7 | // 8 | // --------------------------------------------------------------------------- 9 | // Copyright 2024 Airside Mobile Inc. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // --------------------------------------------------------------------------- 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol SignerProtocol { 28 | var algorithm: SignatureAlgorithm { get } 29 | 30 | /// Signs input data. 31 | /// 32 | /// - Parameter signingInput: The input to sign. 33 | /// - Returns: The signature. 34 | /// - Throws: `JWSError` if any error occurs while signing. 35 | func sign(_ signingInput: Data) throws -> Data 36 | } 37 | 38 | public struct Signer { 39 | let signer: SignerProtocol 40 | 41 | /// Constructs a signer used to sign a JWS. 42 | /// 43 | /// - Parameters: 44 | /// - signatureAlgorithm: The desired `SignatureAlgorithm`. 45 | /// - key: The key used to compute the JWS's signature or message authentication code (MAC). 46 | /// Currently supported key types are: `SecKey` and `Data`. 47 | /// - For _digital signature algorithms_ it is the sender's private key (`SecKey`) 48 | /// with which the JWS should be signed. 49 | /// - For _MAC algorithms_ it is the secret symmetric key (`Data`) 50 | /// shared between the sender and the recipient. 51 | /// - Returns: A fully initialized `Signer` or `nil` if provided key is of the wrong type. 52 | public init?(signatureAlgorithm: SignatureAlgorithm, key: KeyType) { 53 | switch signatureAlgorithm { 54 | case .HS256, .HS384, .HS512: 55 | guard type(of: key) is HMACSigner.KeyType.Type else { 56 | return nil 57 | } 58 | // swiftlint:disable:next force_cast 59 | self.signer = HMACSigner(algorithm: signatureAlgorithm, key: key as! HMACSigner.KeyType) 60 | case .RS256, .RS384, .RS512, .PS256, .PS384, .PS512: 61 | guard type(of: key) is RSASigner.KeyType.Type else { 62 | return nil 63 | } 64 | // swiftlint:disable:next force_cast 65 | self.signer = RSASigner(algorithm: signatureAlgorithm, privateKey: key as! RSASigner.KeyType) 66 | case .ES256, .ES384, .ES512: 67 | guard type(of: key) is ECSigner.KeyType.Type else { 68 | return nil 69 | } 70 | // swiftlint:disable:next force_cast 71 | self.signer = ECSigner(algorithm: signatureAlgorithm, privateKey: key as! ECSigner.KeyType) 72 | } 73 | } 74 | 75 | /// Constructs a signer used to sign a JWS. 76 | /// 77 | /// - Parameters: 78 | /// - customSigner: A custom signing implementation. 79 | /// 80 | /// It is the implementors responsibility to ensure compliance with the necessary specifications. 81 | /// - Returns: A fully initialized `Signer`. 82 | public init(customSigner signer: SignerProtocol) { 83 | self.signer = signer 84 | } 85 | 86 | internal func sign(header: JWSHeader, payload: Payload) throws -> Data { 87 | guard let alg = header.algorithm, alg == signer.algorithm else { 88 | throw JWSError.algorithmMismatch 89 | } 90 | 91 | guard let signingInput = [header, payload].asJOSESigningInput() else { 92 | throw JWSError.cannotComputeSigningInput 93 | } 94 | 95 | return try signer.sign(signingInput) 96 | } 97 | } 98 | 99 | extension Array where Element == DataConvertible { 100 | func asJOSESigningInput() -> Data? { 101 | let encoded = self.map { component in 102 | return component.data().base64URLEncodedString() 103 | } 104 | 105 | return encoded.joined(separator: ".").data(using: .ascii) 106 | } 107 | } 108 | --------------------------------------------------------------------------------