├── .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 |
--------------------------------------------------------------------------------