diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..428d75a --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +support@cryptomator.org. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..2502d88 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing to CryptoLib Swift + +## Did you find a bug? + +- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptolib-swift/issues). +- If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptolib-swift/issues/new). + +## Did you write a patch that fixes a bug? + +- Open a new pull request with the patch. +- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. + +## Do you intend to add a new feature or change an existing one? + +- Suggest your change by [submitting a new issue](https://github.com/cryptomator/cryptolib-swift/issues/new) and start writing code. + +## Code of Conduct + +Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md). + +## Above all, thank you for your contributions + +Thank you for taking the time to contribute to the project! :+1: + +Cryptomator Team diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1e5419a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,53 @@ +name: Build + +on: + [push] + +jobs: + build: + name: Build and test + runs-on: macos-latest + env: + DERIVED_DATA_PATH: 'DerivedData' + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: .build + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm- + - name: Run process.sh script + run: | + ./Scripts/process.sh + exit $? + - name: Build and test + run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -scheme 'CryptomatorCryptoLib' -destination 'platform=macOS' -derivedDataPath $DERIVED_DATA_PATH -enableCodeCoverage YES clean test | xcpretty + - name: Upload code coverage report + run: | + gem install slather + slather coverage -x --build-directory $DERIVED_DATA_PATH --ignore "$DERIVED_DATA_PATH/SourcePackages/*" --scheme CryptomatorCryptoLib CryptomatorCryptoLib.xcodeproj + bash <(curl -Ls https://coverage.codacy.com/get.sh) + env: + CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} + continue-on-error: true + + release: + name: Deploy and draft a release + runs-on: macos-latest + needs: build + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/checkout@v2 + - name: Draft release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + body: | + :construction: Work in Progress + draft: true + prerelease: false diff --git a/.gitignore b/.gitignore index 92d6acd..7467633 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,11 @@ -#Mac +# macOS .DS_Store + # Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ -## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) -*.xcscmblueprint -*.xccheckout - -## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) -build/ -DerivedData/ -*.moved-aside -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 - ## Obj-C/Swift specific *.hmap @@ -46,47 +28,6 @@ playground.xcworkspace # # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project -# .swiftpm +.swiftpm .build/ - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -Pods/ -# -# Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace - -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build/ - -# Accio dependency management -Dependencies/ -.accio/ - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. -# Instead, use fastlane to re-generate the screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots/**/*.png -fastlane/test_output - -# Code Injection -# -# After new code Injection tools there's a generated folder /iOSInjectionProject -# https://github.com/johnno1962/injectionforxcode - -iOSInjectionProject/ diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..d0c5fed --- /dev/null +++ b/.swiftformat @@ -0,0 +1,18 @@ +--minversion 0.49.0 + +# format options + +--closurevoid preserve +--commas inline +--ifdef no-indent +--importgrouping testable-bottom +--indent tab +--self init-only +--stripunusedargs closure-only +--swiftversion 5.1 +--tabwidth 4 + +# rules + +--enable isEmpty +--disable redundantReturn,trailingClosures diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..88e3f5d --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,20 @@ +disabled_rules: + - file_length + - line_length + - todo + - type_body_length + +included: + - Sources/CryptomatorCryptoLib + - Tests/CryptomatorCryptoLibTests +excluded: + - Tests/CryptomatorCryptoLibTests/XCTestManifests.swift + +identifier_name: + min_length: 1 + max_length: 50 +inclusive_language: + override_allowed_terms: ["masterkey"] +type_name: + max_length: 50 + allowed_symbols: "_" diff --git a/CryptoLib.podspec b/CryptoLib.podspec deleted file mode 100644 index c5b14cc..0000000 --- a/CryptoLib.podspec +++ /dev/null @@ -1,19 +0,0 @@ -Pod::Spec.new do |s| - s.name = 'CryptoLib' - s.version = '0.1.0' - s.summary = 'CryptoLib is an iOS crypto library to access Cryptomator vaults.' - - - s.homepage = 'https://github.com/cryptomator/cryptolib-swift' - s.license = { :type => 'AGPLv3', :file => 'LICENSE.txt' } - s.author = { 'Philipp Schmid' => 'philipp.schmid@skymatic.de' } - s.source = { :git => 'https://github.com/cryptomator/cryptolib-swift.git', :tag => s.version.to_s } - s.social_media_url = 'https://twitter.com/Cryptomator' - - s.public_header_files = 'CryptoLib/CryptoLib.h' - s.ios.deployment_target = '8.0' - s.swift_version = '5.0' - - s.source_files = 'CryptoLib/**/*{swift,h,m}' - -end diff --git a/CryptoLib.xcodeproj/project.pbxproj b/CryptoLib.xcodeproj/project.pbxproj deleted file mode 100644 index eb7432e..0000000 --- a/CryptoLib.xcodeproj/project.pbxproj +++ /dev/null @@ -1,508 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - -/* Begin PBXBuildFile section */ - 09D994438BAA505986957D1A /* libPods-CryptoLib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D8F927A885B3AC17754363D1 /* libPods-CryptoLib.a */; }; - 4A7C213C2451F2AC00DE81E6 /* CryptoLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A7C21322451F2AC00DE81E6 /* CryptoLib.framework */; }; - 4A7C21412451F2AC00DE81E6 /* CryptoLibTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7C21402451F2AC00DE81E6 /* CryptoLibTests.swift */; }; - 4A7C21432451F2AD00DE81E6 /* CryptoLib.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A7C21352451F2AC00DE81E6 /* CryptoLib.h */; settings = {ATTRIBUTES = (Public, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 4A7C213D2451F2AC00DE81E6 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4A7C21292451F2AC00DE81E6 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4A7C21312451F2AC00DE81E6; - remoteInfo = CryptoLib; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 04D286DFEF49CAB9C0242FB8 /* Pods-CryptoLib.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CryptoLib.debug.xcconfig"; path = "Target Support Files/Pods-CryptoLib/Pods-CryptoLib.debug.xcconfig"; sourceTree = ""; }; - 46CE315EF43B1FF833C7A4FB /* Pods-CryptoLib.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CryptoLib.release.xcconfig"; path = "Target Support Files/Pods-CryptoLib/Pods-CryptoLib.release.xcconfig"; sourceTree = ""; }; - 4A7C21322451F2AC00DE81E6 /* CryptoLib.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CryptoLib.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 4A7C21352451F2AC00DE81E6 /* CryptoLib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CryptoLib.h; sourceTree = ""; }; - 4A7C21362451F2AC00DE81E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 4A7C213B2451F2AC00DE81E6 /* CryptoLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CryptoLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 4A7C21402451F2AC00DE81E6 /* CryptoLibTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoLibTests.swift; sourceTree = ""; }; - 4A7C21422451F2AD00DE81E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D8F927A885B3AC17754363D1 /* libPods-CryptoLib.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CryptoLib.a"; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 4A7C212F2451F2AC00DE81E6 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 09D994438BAA505986957D1A /* libPods-CryptoLib.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4A7C21382451F2AC00DE81E6 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4A7C213C2451F2AC00DE81E6 /* CryptoLib.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 33F96BB57BB41A059EFBA16D /* Frameworks */ = { - isa = PBXGroup; - children = ( - D8F927A885B3AC17754363D1 /* libPods-CryptoLib.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - 4A7C21282451F2AC00DE81E6 = { - isa = PBXGroup; - children = ( - 4A7C21342451F2AC00DE81E6 /* CryptoLib */, - 4A7C213F2451F2AC00DE81E6 /* CryptoLibTests */, - 4A7C21332451F2AC00DE81E6 /* Products */, - D7D76AD3F7F6155DB2723364 /* Pods */, - 33F96BB57BB41A059EFBA16D /* Frameworks */, - ); - sourceTree = ""; - }; - 4A7C21332451F2AC00DE81E6 /* Products */ = { - isa = PBXGroup; - children = ( - 4A7C21322451F2AC00DE81E6 /* CryptoLib.framework */, - 4A7C213B2451F2AC00DE81E6 /* CryptoLibTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 4A7C21342451F2AC00DE81E6 /* CryptoLib */ = { - isa = PBXGroup; - children = ( - 4A7C21352451F2AC00DE81E6 /* CryptoLib.h */, - 4A7C21362451F2AC00DE81E6 /* Info.plist */, - ); - path = CryptoLib; - sourceTree = ""; - }; - 4A7C213F2451F2AC00DE81E6 /* CryptoLibTests */ = { - isa = PBXGroup; - children = ( - 4A7C21402451F2AC00DE81E6 /* CryptoLibTests.swift */, - 4A7C21422451F2AD00DE81E6 /* Info.plist */, - ); - path = CryptoLibTests; - sourceTree = ""; - }; - D7D76AD3F7F6155DB2723364 /* Pods */ = { - isa = PBXGroup; - children = ( - 04D286DFEF49CAB9C0242FB8 /* Pods-CryptoLib.debug.xcconfig */, - 46CE315EF43B1FF833C7A4FB /* Pods-CryptoLib.release.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 4A7C212D2451F2AC00DE81E6 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 4A7C21432451F2AD00DE81E6 /* CryptoLib.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 4A7C21312451F2AC00DE81E6 /* CryptoLib */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4A7C21462451F2AD00DE81E6 /* Build configuration list for PBXNativeTarget "CryptoLib" */; - buildPhases = ( - 6DFD694B51EDFB57A524EA41 /* [CP] Check Pods Manifest.lock */, - 4A7C212D2451F2AC00DE81E6 /* Headers */, - 4A7C212E2451F2AC00DE81E6 /* Sources */, - 4A7C212F2451F2AC00DE81E6 /* Frameworks */, - 4A7C21302451F2AC00DE81E6 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = CryptoLib; - productName = CryptoLib; - productReference = 4A7C21322451F2AC00DE81E6 /* CryptoLib.framework */; - productType = "com.apple.product-type.framework"; - }; - 4A7C213A2451F2AC00DE81E6 /* CryptoLibTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4A7C21492451F2AD00DE81E6 /* Build configuration list for PBXNativeTarget "CryptoLibTests" */; - buildPhases = ( - 4A7C21372451F2AC00DE81E6 /* Sources */, - 4A7C21382451F2AC00DE81E6 /* Frameworks */, - 4A7C21392451F2AC00DE81E6 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 4A7C213E2451F2AC00DE81E6 /* PBXTargetDependency */, - ); - name = CryptoLibTests; - productName = CryptoLibTests; - productReference = 4A7C213B2451F2AC00DE81E6 /* CryptoLibTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 4A7C21292451F2AC00DE81E6 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1140; - LastUpgradeCheck = 1140; - ORGANIZATIONNAME = "Skymatic GmbH"; - TargetAttributes = { - 4A7C21312451F2AC00DE81E6 = { - CreatedOnToolsVersion = 11.4; - }; - 4A7C213A2451F2AC00DE81E6 = { - CreatedOnToolsVersion = 11.4; - }; - }; - }; - buildConfigurationList = 4A7C212C2451F2AC00DE81E6 /* Build configuration list for PBXProject "CryptoLib" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 4A7C21282451F2AC00DE81E6; - productRefGroup = 4A7C21332451F2AC00DE81E6 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 4A7C21312451F2AC00DE81E6 /* CryptoLib */, - 4A7C213A2451F2AC00DE81E6 /* CryptoLibTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 4A7C21302451F2AC00DE81E6 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4A7C21392451F2AC00DE81E6 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 6DFD694B51EDFB57A524EA41 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-CryptoLib-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 4A7C212E2451F2AC00DE81E6 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4A7C21372451F2AC00DE81E6 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4A7C21412451F2AC00DE81E6 /* CryptoLibTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 4A7C213E2451F2AC00DE81E6 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4A7C21312451F2AC00DE81E6 /* CryptoLib */; - targetProxy = 4A7C213D2451F2AC00DE81E6 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 4A7C21442451F2AD00DE81E6 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 4A7C21452451F2AD00DE81E6 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 4A7C21472451F2AD00DE81E6 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 04D286DFEF49CAB9C0242FB8 /* Pods-CryptoLib.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = CryptoLib/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.skymatic.CryptoLib; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 4A7C21482451F2AD00DE81E6 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 46CE315EF43B1FF833C7A4FB /* Pods-CryptoLib.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = CryptoLib/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.skymatic.CryptoLib; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 4A7C214A2451F2AD00DE81E6 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = CryptoLibTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.skymatic.CryptoLibTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 4A7C214B2451F2AD00DE81E6 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = CryptoLibTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.skymatic.CryptoLibTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 4A7C212C2451F2AC00DE81E6 /* Build configuration list for PBXProject "CryptoLib" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4A7C21442451F2AD00DE81E6 /* Debug */, - 4A7C21452451F2AD00DE81E6 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4A7C21462451F2AD00DE81E6 /* Build configuration list for PBXNativeTarget "CryptoLib" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4A7C21472451F2AD00DE81E6 /* Debug */, - 4A7C21482451F2AD00DE81E6 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4A7C21492451F2AD00DE81E6 /* Build configuration list for PBXNativeTarget "CryptoLibTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4A7C214A2451F2AD00DE81E6 /* Debug */, - 4A7C214B2451F2AD00DE81E6 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 4A7C21292451F2AC00DE81E6 /* Project object */; -} diff --git a/CryptoLib.xcworkspace/contents.xcworkspacedata b/CryptoLib.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index c9346be..0000000 --- a/CryptoLib.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/CryptoLib/CryptoLib.h b/CryptoLib/CryptoLib.h deleted file mode 100644 index c3d72e8..0000000 --- a/CryptoLib/CryptoLib.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// CryptoLib.h -// CryptoLib -// -// Created by Philipp Schmid on 23.04.20. -// Copyright © 2020 Skymatic GmbH. All rights reserved. -// - -#import - -//! Project version number for CryptoLib. -FOUNDATION_EXPORT double CryptoLibVersionNumber; - -//! Project version string for CryptoLib. -FOUNDATION_EXPORT const unsigned char CryptoLibVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/CryptoLibTests/CryptoLibTests.swift b/CryptoLibTests/CryptoLibTests.swift deleted file mode 100644 index 15d7fb7..0000000 --- a/CryptoLibTests/CryptoLibTests.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// CryptoLibTests.swift -// CryptoLibTests -// -// Created by Philipp Schmid on 23.04.20. -// Copyright © 2020 Skymatic GmbH. All rights reserved. -// - -import XCTest -@testable import CryptoLib - -class CryptoLibTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/CryptomatorCryptoLib.xcodeproj/project.pbxproj b/CryptomatorCryptoLib.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a545370 --- /dev/null +++ b/CryptomatorCryptoLib.xcodeproj/project.pbxproj @@ -0,0 +1,796 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 4A7C213C2451F2AC00DE81E6 /* CryptomatorCryptoLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A7C21322451F2AC00DE81E6 /* CryptomatorCryptoLib.framework */; }; + 4A7C21432451F2AD00DE81E6 /* CryptomatorCryptoLib.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A7C21352451F2AC00DE81E6 /* CryptomatorCryptoLib.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 742023E72555A4A200822899 /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 742023E62555A4A200822899 /* Base32 */; }; + 749260D225B17A9A004B3426 /* scrypt.h in Headers */ = {isa = PBXBuildFile; fileRef = 749260D025B17A9A004B3426 /* scrypt.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 749260DC25B17AB2004B3426 /* insecure_memzero.c in Sources */ = {isa = PBXBuildFile; fileRef = 74D57DE225B095FA006D81B8 /* insecure_memzero.c */; }; + 749260DD25B17AB2004B3426 /* crypto_scrypt.c in Sources */ = {isa = PBXBuildFile; fileRef = 74D57DE825B095FA006D81B8 /* crypto_scrypt.c */; }; + 749260DE25B17AB2004B3426 /* sha256.c in Sources */ = {isa = PBXBuildFile; fileRef = 74D57DE725B095FA006D81B8 /* sha256.c */; }; + 749260E525B17AC3004B3426 /* crypto_scrypt.h in Headers */ = {isa = PBXBuildFile; fileRef = 74D57DE325B095FA006D81B8 /* crypto_scrypt.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 749260E625B17AC3004B3426 /* sha256.h in Headers */ = {isa = PBXBuildFile; fileRef = 74D57DE425B095FA006D81B8 /* sha256.h */; }; + 749260E725B17AC3004B3426 /* insecure_memzero.h in Headers */ = {isa = PBXBuildFile; fileRef = 74D57DE625B095FA006D81B8 /* insecure_memzero.h */; }; + 749260E825B17AC3004B3426 /* sysendian.h in Headers */ = {isa = PBXBuildFile; fileRef = 74D57DE525B095FA006D81B8 /* sysendian.h */; }; + 749260F325B17B0F004B3426 /* scrypt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 749260CE25B17A9A004B3426 /* scrypt.framework */; }; + 749260F425B17B0F004B3426 /* scrypt.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 749260CE25B17A9A004B3426 /* scrypt.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 74A5B57625A869DD002D10F7 /* MasterkeyFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A5B57525A869DD002D10F7 /* MasterkeyFileTests.swift */; }; + 74A5B57E25A86A69002D10F7 /* CryptoSupportMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A5B57D25A86A69002D10F7 /* CryptoSupportMock.swift */; }; + 74B4D38D2588CD60006C0567 /* MasterkeyFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B4D38C2588CD60006C0567 /* MasterkeyFile.swift */; }; + 74F0F754248FC89B00B4C26D /* CryptoError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F0F753248FC89B00B4C26D /* CryptoError.swift */; }; + 74F0F75A2490C1EB00B4C26D /* CryptoSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F0F7592490C1EB00B4C26D /* CryptoSupport.swift */; }; + 9E35C4EB24576A3D0006E50C /* CryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E35C4EA24576A3D0006E50C /* CryptorTests.swift */; }; + 9E44EEA624599C6900A37B01 /* AesSiv.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E44EEA524599C6900A37B01 /* AesSiv.swift */; }; + 9E44EEA92459AB1500A37B01 /* AesSivTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E44EEA724599C7800A37B01 /* AesSivTests.swift */; }; + 9E97DC8D25F77BA40046C83E /* ContentCryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E97DC8C25F77BA40046C83E /* ContentCryptor.swift */; }; + 9E9BB812245412E900F9FF51 /* Cryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9BB811245412E900F9FF51 /* Cryptor.swift */; }; + 9E9BB8142454708600F9FF51 /* Masterkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9BB8132454708600F9FF51 /* Masterkey.swift */; }; + 9E9BB81624558DFF00F9FF51 /* MasterkeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */; }; + 9EB822C1248AF82200879838 /* AesCtr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB822C0248AF82200879838 /* AesCtr.swift */; }; + 9EB822C3248AF9C500879838 /* AesCtrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB822C2248AF9C500879838 /* AesCtrTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 4A7C213D2451F2AC00DE81E6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4A7C21292451F2AC00DE81E6 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4A7C21312451F2AC00DE81E6; + remoteInfo = CryptoLib; + }; + 749260F525B17B0F004B3426 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4A7C21292451F2AC00DE81E6 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 749260CD25B17A9A004B3426; + remoteInfo = scrypt; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 749260F725B17B10004B3426 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 749260F425B17B0F004B3426 /* scrypt.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 4A7C21322451F2AC00DE81E6 /* CryptomatorCryptoLib.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CryptomatorCryptoLib.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A7C21352451F2AC00DE81E6 /* CryptomatorCryptoLib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CryptomatorCryptoLib.h; sourceTree = ""; }; + 4A7C21362451F2AC00DE81E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4A7C213B2451F2AC00DE81E6 /* CryptomatorCryptoLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CryptomatorCryptoLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A7C21422451F2AD00DE81E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 749260CE25B17A9A004B3426 /* scrypt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = scrypt.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 749260D025B17A9A004B3426 /* scrypt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = scrypt.h; sourceTree = ""; }; + 749260D125B17A9A004B3426 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 74A5B57525A869DD002D10F7 /* MasterkeyFileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterkeyFileTests.swift; sourceTree = ""; }; + 74A5B57D25A86A69002D10F7 /* CryptoSupportMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSupportMock.swift; sourceTree = ""; }; + 74B4D38C2588CD60006C0567 /* MasterkeyFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterkeyFile.swift; sourceTree = ""; }; + 74D57DE225B095FA006D81B8 /* insecure_memzero.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = insecure_memzero.c; sourceTree = ""; }; + 74D57DE325B095FA006D81B8 /* crypto_scrypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypto_scrypt.h; sourceTree = ""; }; + 74D57DE425B095FA006D81B8 /* sha256.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sha256.h; sourceTree = ""; }; + 74D57DE525B095FA006D81B8 /* sysendian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sysendian.h; sourceTree = ""; }; + 74D57DE625B095FA006D81B8 /* insecure_memzero.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = insecure_memzero.h; sourceTree = ""; }; + 74D57DE725B095FA006D81B8 /* sha256.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sha256.c; sourceTree = ""; }; + 74D57DE825B095FA006D81B8 /* crypto_scrypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = crypto_scrypt.c; sourceTree = ""; }; + 74F0F753248FC89B00B4C26D /* CryptoError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoError.swift; sourceTree = ""; }; + 74F0F7592490C1EB00B4C26D /* CryptoSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSupport.swift; sourceTree = ""; }; + 9E35C4EA24576A3D0006E50C /* CryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptorTests.swift; sourceTree = ""; }; + 9E44EEA524599C6900A37B01 /* AesSiv.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesSiv.swift; sourceTree = ""; }; + 9E44EEA724599C7800A37B01 /* AesSivTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesSivTests.swift; sourceTree = ""; }; + 9E97DC8C25F77BA40046C83E /* ContentCryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentCryptor.swift; sourceTree = ""; }; + 9E9BB811245412E900F9FF51 /* Cryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cryptor.swift; sourceTree = ""; }; + 9E9BB8132454708600F9FF51 /* Masterkey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Masterkey.swift; sourceTree = ""; }; + 9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterkeyTests.swift; sourceTree = ""; }; + 9EB822C0248AF82200879838 /* AesCtr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtr.swift; sourceTree = ""; }; + 9EB822C2248AF9C500879838 /* AesCtrTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtrTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4A7C212F2451F2AC00DE81E6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 742023E72555A4A200822899 /* Base32 in Frameworks */, + 749260F325B17B0F004B3426 /* scrypt.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4A7C21382451F2AC00DE81E6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A7C213C2451F2AC00DE81E6 /* CryptomatorCryptoLib.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 749260CB25B17A9A004B3426 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4A7C21282451F2AC00DE81E6 = { + isa = PBXGroup; + children = ( + 74FFC966251F3EBB004C4927 /* Sources */, + 74FFC967251F3ED0004C4927 /* Tests */, + 4A7C21332451F2AC00DE81E6 /* Products */, + 749260F225B17B0F004B3426 /* Frameworks */, + ); + sourceTree = ""; + }; + 4A7C21332451F2AC00DE81E6 /* Products */ = { + isa = PBXGroup; + children = ( + 4A7C21322451F2AC00DE81E6 /* CryptomatorCryptoLib.framework */, + 4A7C213B2451F2AC00DE81E6 /* CryptomatorCryptoLibTests.xctest */, + 749260CE25B17A9A004B3426 /* scrypt.framework */, + ); + name = Products; + sourceTree = ""; + }; + 4A7C21342451F2AC00DE81E6 /* CryptomatorCryptoLib */ = { + isa = PBXGroup; + children = ( + 4A7C21352451F2AC00DE81E6 /* CryptomatorCryptoLib.h */, + 4A7C21362451F2AC00DE81E6 /* Info.plist */, + 9EB822C0248AF82200879838 /* AesCtr.swift */, + 9E44EEA524599C6900A37B01 /* AesSiv.swift */, + 9E97DC8C25F77BA40046C83E /* ContentCryptor.swift */, + 74F0F753248FC89B00B4C26D /* CryptoError.swift */, + 9E9BB811245412E900F9FF51 /* Cryptor.swift */, + 74F0F7592490C1EB00B4C26D /* CryptoSupport.swift */, + 9E9BB8132454708600F9FF51 /* Masterkey.swift */, + 74B4D38C2588CD60006C0567 /* MasterkeyFile.swift */, + ); + path = CryptomatorCryptoLib; + sourceTree = ""; + }; + 4A7C213F2451F2AC00DE81E6 /* CryptomatorCryptoLibTests */ = { + isa = PBXGroup; + children = ( + 4A7C21422451F2AD00DE81E6 /* Info.plist */, + 9EB822C2248AF9C500879838 /* AesCtrTests.swift */, + 9E44EEA724599C7800A37B01 /* AesSivTests.swift */, + 9E35C4EA24576A3D0006E50C /* CryptorTests.swift */, + 74A5B57D25A86A69002D10F7 /* CryptoSupportMock.swift */, + 74A5B57525A869DD002D10F7 /* MasterkeyFileTests.swift */, + 9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */, + ); + path = CryptomatorCryptoLibTests; + sourceTree = ""; + }; + 749260F225B17B0F004B3426 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 74D57DE125B095FA006D81B8 /* scrypt */ = { + isa = PBXGroup; + children = ( + 74D57DE825B095FA006D81B8 /* crypto_scrypt.c */, + 74D57DE225B095FA006D81B8 /* insecure_memzero.c */, + 74D57DE725B095FA006D81B8 /* sha256.c */, + 749260D025B17A9A004B3426 /* scrypt.h */, + 749260D125B17A9A004B3426 /* Info.plist */, + 74D57DF225B09603006D81B8 /* include */, + ); + path = scrypt; + sourceTree = ""; + }; + 74D57DF225B09603006D81B8 /* include */ = { + isa = PBXGroup; + children = ( + 74D57DE325B095FA006D81B8 /* crypto_scrypt.h */, + 74D57DE625B095FA006D81B8 /* insecure_memzero.h */, + 74D57DE425B095FA006D81B8 /* sha256.h */, + 74D57DE525B095FA006D81B8 /* sysendian.h */, + ); + path = include; + sourceTree = ""; + }; + 74FFC966251F3EBB004C4927 /* Sources */ = { + isa = PBXGroup; + children = ( + 4A7C21342451F2AC00DE81E6 /* CryptomatorCryptoLib */, + 74D57DE125B095FA006D81B8 /* scrypt */, + ); + path = Sources; + sourceTree = ""; + }; + 74FFC967251F3ED0004C4927 /* Tests */ = { + isa = PBXGroup; + children = ( + 4A7C213F2451F2AC00DE81E6 /* CryptomatorCryptoLibTests */, + ); + path = Tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 4A7C212D2451F2AC00DE81E6 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A7C21432451F2AD00DE81E6 /* CryptomatorCryptoLib.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 749260C925B17A9A004B3426 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 749260E725B17AC3004B3426 /* insecure_memzero.h in Headers */, + 749260E525B17AC3004B3426 /* crypto_scrypt.h in Headers */, + 749260D225B17A9A004B3426 /* scrypt.h in Headers */, + 749260E625B17AC3004B3426 /* sha256.h in Headers */, + 749260E825B17AC3004B3426 /* sysendian.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 4A7C21312451F2AC00DE81E6 /* CryptomatorCryptoLib */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4A7C21462451F2AD00DE81E6 /* Build configuration list for PBXNativeTarget "CryptomatorCryptoLib" */; + buildPhases = ( + 4A7C212D2451F2AC00DE81E6 /* Headers */, + 4A7C212E2451F2AC00DE81E6 /* Sources */, + 4A7C212F2451F2AC00DE81E6 /* Frameworks */, + 4A7C21302451F2AC00DE81E6 /* Resources */, + 74862A712469A6B2003D81CB /* Lint With SwiftFormat */, + 749441272616051400435B0B /* Lint With SwiftLint */, + 749260F725B17B10004B3426 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 749260F625B17B0F004B3426 /* PBXTargetDependency */, + ); + name = CryptomatorCryptoLib; + packageProductDependencies = ( + 742023E62555A4A200822899 /* Base32 */, + ); + productName = CryptoLib; + productReference = 4A7C21322451F2AC00DE81E6 /* CryptomatorCryptoLib.framework */; + productType = "com.apple.product-type.framework"; + }; + 4A7C213A2451F2AC00DE81E6 /* CryptomatorCryptoLibTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4A7C21492451F2AD00DE81E6 /* Build configuration list for PBXNativeTarget "CryptomatorCryptoLibTests" */; + buildPhases = ( + 4A7C21372451F2AC00DE81E6 /* Sources */, + 4A7C21382451F2AC00DE81E6 /* Frameworks */, + 4A7C21392451F2AC00DE81E6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4A7C213E2451F2AC00DE81E6 /* PBXTargetDependency */, + ); + name = CryptomatorCryptoLibTests; + productName = CryptoLibTests; + productReference = 4A7C213B2451F2AC00DE81E6 /* CryptomatorCryptoLibTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 749260CD25B17A9A004B3426 /* scrypt */ = { + isa = PBXNativeTarget; + buildConfigurationList = 749260D325B17A9A004B3426 /* Build configuration list for PBXNativeTarget "scrypt" */; + buildPhases = ( + 749260C925B17A9A004B3426 /* Headers */, + 749260CA25B17A9A004B3426 /* Sources */, + 749260CB25B17A9A004B3426 /* Frameworks */, + 749260CC25B17A9A004B3426 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = scrypt; + productName = scrypt; + productReference = 749260CE25B17A9A004B3426 /* scrypt.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4A7C21292451F2AC00DE81E6 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1140; + LastUpgradeCheck = 1250; + ORGANIZATIONNAME = "Skymatic GmbH"; + TargetAttributes = { + 4A7C21312451F2AC00DE81E6 = { + CreatedOnToolsVersion = 11.4; + LastSwiftMigration = 1100; + }; + 4A7C213A2451F2AC00DE81E6 = { + CreatedOnToolsVersion = 11.4; + }; + 749260CD25B17A9A004B3426 = { + CreatedOnToolsVersion = 12.3; + }; + }; + }; + buildConfigurationList = 4A7C212C2451F2AC00DE81E6 /* Build configuration list for PBXProject "CryptomatorCryptoLib" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4A7C21282451F2AC00DE81E6; + packageReferences = ( + 742023E52555A4A200822899 /* XCRemoteSwiftPackageReference "Base32" */, + ); + productRefGroup = 4A7C21332451F2AC00DE81E6 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4A7C21312451F2AC00DE81E6 /* CryptomatorCryptoLib */, + 749260CD25B17A9A004B3426 /* scrypt */, + 4A7C213A2451F2AC00DE81E6 /* CryptomatorCryptoLibTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4A7C21302451F2AC00DE81E6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4A7C21392451F2AC00DE81E6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 749260CC25B17A9A004B3426 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 74862A712469A6B2003D81CB /* Lint With SwiftFormat */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Lint With SwiftFormat"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "PATH=$PATH:/opt/homebrew/bin\nif which swiftformat >/dev/null; then\n swiftformat --lint --lenient .\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\"\nfi\n"; + }; + 749441272616051400435B0B /* Lint With SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Lint With SwiftLint"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "PATH=$PATH:/opt/homebrew/bin\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4A7C212E2451F2AC00DE81E6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9E9BB812245412E900F9FF51 /* Cryptor.swift in Sources */, + 9EB822C1248AF82200879838 /* AesCtr.swift in Sources */, + 74F0F75A2490C1EB00B4C26D /* CryptoSupport.swift in Sources */, + 9E9BB8142454708600F9FF51 /* Masterkey.swift in Sources */, + 9E44EEA624599C6900A37B01 /* AesSiv.swift in Sources */, + 9E97DC8D25F77BA40046C83E /* ContentCryptor.swift in Sources */, + 74F0F754248FC89B00B4C26D /* CryptoError.swift in Sources */, + 74B4D38D2588CD60006C0567 /* MasterkeyFile.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4A7C21372451F2AC00DE81E6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74A5B57E25A86A69002D10F7 /* CryptoSupportMock.swift in Sources */, + 9E44EEA92459AB1500A37B01 /* AesSivTests.swift in Sources */, + 9EB822C3248AF9C500879838 /* AesCtrTests.swift in Sources */, + 74A5B57625A869DD002D10F7 /* MasterkeyFileTests.swift in Sources */, + 9E9BB81624558DFF00F9FF51 /* MasterkeyTests.swift in Sources */, + 9E35C4EB24576A3D0006E50C /* CryptorTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 749260CA25B17A9A004B3426 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 749260DC25B17AB2004B3426 /* insecure_memzero.c in Sources */, + 749260DE25B17AB2004B3426 /* sha256.c in Sources */, + 749260DD25B17AB2004B3426 /* crypto_scrypt.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4A7C213E2451F2AC00DE81E6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4A7C21312451F2AC00DE81E6 /* CryptomatorCryptoLib */; + targetProxy = 4A7C213D2451F2AC00DE81E6 /* PBXContainerItemProxy */; + }; + 749260F625B17B0F004B3426 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 749260CD25B17A9A004B3426 /* scrypt */; + targetProxy = 749260F525B17B0F004B3426 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 4A7C21442451F2AD00DE81E6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200 -Xfrontend -warn-long-function-bodies=200"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 4A7C21452451F2AD00DE81E6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200 -Xfrontend -warn-long-function-bodies=200"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 4A7C21472451F2AD00DE81E6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Sources/CryptomatorCryptoLib/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.cryptolib; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4A7C21482451F2AD00DE81E6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Sources/CryptomatorCryptoLib/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.cryptolib; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 4A7C214A2451F2AD00DE81E6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = Tests/CryptomatorCryptoLibTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.cryptolib.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4A7C214B2451F2AD00DE81E6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = Tests/CryptomatorCryptoLibTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.cryptolib.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 749260D425B17A9A004B3426 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Sources/scrypt/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.3.1; + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.scrypt; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 749260D525B17A9A004B3426 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Sources/scrypt/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.3.1; + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.scrypt; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4A7C212C2451F2AC00DE81E6 /* Build configuration list for PBXProject "CryptomatorCryptoLib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4A7C21442451F2AD00DE81E6 /* Debug */, + 4A7C21452451F2AD00DE81E6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4A7C21462451F2AD00DE81E6 /* Build configuration list for PBXNativeTarget "CryptomatorCryptoLib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4A7C21472451F2AD00DE81E6 /* Debug */, + 4A7C21482451F2AD00DE81E6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4A7C21492451F2AD00DE81E6 /* Build configuration list for PBXNativeTarget "CryptomatorCryptoLibTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4A7C214A2451F2AD00DE81E6 /* Debug */, + 4A7C214B2451F2AD00DE81E6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 749260D325B17A9A004B3426 /* Build configuration list for PBXNativeTarget "scrypt" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 749260D425B17A9A004B3426 /* Debug */, + 749260D525B17A9A004B3426 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 742023E52555A4A200822899 /* XCRemoteSwiftPackageReference "Base32" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/norio-nomura/Base32.git"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 0.9.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 742023E62555A4A200822899 /* Base32 */ = { + isa = XCSwiftPackageProductDependency; + package = 742023E52555A4A200822899 /* XCRemoteSwiftPackageReference "Base32" */; + productName = Base32; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 4A7C21292451F2AC00DE81E6 /* Project object */; +} diff --git a/CryptoLib.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/CryptomatorCryptoLib.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 70% rename from CryptoLib.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to CryptomatorCryptoLib.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 72fd35e..919434a 100644 --- a/CryptoLib.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/CryptomatorCryptoLib.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/CryptoLib.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/CryptomatorCryptoLib.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from CryptoLib.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to CryptomatorCryptoLib.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/CryptomatorCryptoLib.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CryptomatorCryptoLib.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..5de3924 --- /dev/null +++ b/CryptomatorCryptoLib.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "Base32", + "repositoryURL": "https://github.com/norio-nomura/Base32.git", + "state": { + "branch": null, + "revision": "c4bc0a49689999ae2c7c778f3830a6a6e694efb8", + "version": "0.9.0" + } + } + ] + }, + "version": 1 +} diff --git a/CryptomatorCryptoLib.xcodeproj/xcshareddata/xcschemes/CryptomatorCryptoLib.xcscheme b/CryptomatorCryptoLib.xcodeproj/xcshareddata/xcschemes/CryptomatorCryptoLib.xcscheme new file mode 100644 index 0000000..6962aa6 --- /dev/null +++ b/CryptomatorCryptoLib.xcodeproj/xcshareddata/xcschemes/CryptomatorCryptoLib.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CryptomatorCryptoLib.xcodeproj/xcshareddata/xcschemes/scrypt.xcscheme b/CryptomatorCryptoLib.xcodeproj/xcshareddata/xcschemes/scrypt.xcscheme new file mode 100644 index 0000000..cc67076 --- /dev/null +++ b/CryptomatorCryptoLib.xcodeproj/xcshareddata/xcschemes/scrypt.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..5de3924 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "Base32", + "repositoryURL": "https://github.com/norio-nomura/Base32.git", + "state": { + "branch": null, + "revision": "c4bc0a49689999ae2c7c778f3830a6a6e694efb8", + "version": "0.9.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..db6c710 --- /dev/null +++ b/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version:5.1 + +// +// Package.swift +// CryptomatorCryptoLib +// +// Created by Philipp Schmid on 24.09.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import PackageDescription + +let package = Package( + name: "CryptomatorCryptoLib", + platforms: [ + .iOS(.v9), + .macOS(.v10_12) + ], + products: [ + .library(name: "CryptomatorCryptoLib", targets: ["CryptomatorCryptoLib"]) + ], + dependencies: [ + .package(url: "https://github.com/norio-nomura/Base32.git", .upToNextMinor(from: "0.9.0")) + ], + targets: [ + .target(name: "CryptomatorCryptoLib", dependencies: ["Base32", "scrypt"]), + .target(name: "scrypt"), + .testTarget(name: "CryptomatorCryptoLibTests", dependencies: ["CryptomatorCryptoLib"]) + ], + swiftLanguageVersions: [.v5] +) diff --git a/Podfile b/Podfile deleted file mode 100644 index 100c397..0000000 --- a/Podfile +++ /dev/null @@ -1,6 +0,0 @@ -platform :ios, '8.0' -inhibit_all_warnings! - -target 'CryptoLib' do - -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index 3d1598a..0000000 --- a/Podfile.lock +++ /dev/null @@ -1,3 +0,0 @@ -PODFILE CHECKSUM: 5646dc38bd9a917cff701ec23e5ad0031bb81635 - -COCOAPODS: 1.9.1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..b15c518 --- /dev/null +++ b/README.md @@ -0,0 +1,205 @@ +[![Swift Compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fcryptomator%2Fcryptolib-swift%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/cryptomator/cryptolib-swift) +[![Platform Compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fcryptomator%2Fcryptolib-swift%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/cryptomator/cryptolib-swift) +[![Codacy Code Quality](https://app.codacy.com/project/badge/Grade/dba85991a19942bab0d3d587522397ef)](https://www.codacy.com/gh/cryptomator/cryptolib-swift/dashboard) +[![Codacy Coverage](https://app.codacy.com/project/badge/Coverage/dba85991a19942bab0d3d587522397ef)](https://www.codacy.com/gh/cryptomator/cryptolib-swift/dashboard) + +# CryptoLib Swift + +This library contains all cryptographic functions that are used by Cryptomator for iOS. The purpose of this project is to provide a separate light-weight library with its own release cycle that can be used in other projects, too. + +For more information on the Cryptomator encryption scheme, visit the security architecture page on [docs.cryptomator.org](https://docs.cryptomator.org/en/1.5/security/architecture/). + +## Requirements + +- iOS 9.0 or higher +- macOS 10.12 or higher + +## Installation + +### Swift Package Manager + +You can use [Swift Package Manager](https://swift.org/package-manager/ "Swift Package Manager"). + +```swift +.package(url: "https://github.com/cryptomator/cryptolib-swift.git", .upToNextMinor(from: "1.0.0")) +``` + +## Usage + +### Masterkey + +`Masterkey` is a class that only contains the key material for AES encryption/decryption and MAC authentication. + +#### Factory + +This will create a new masterkey with secure random bytes. + +```swift +let masterkey = try Masterkey.createNew() +``` + +Another way is to create a masterkey from raw bytes. + +```swift +let aesMasterKey = ... +let macMasterKey = ... +let masterkey = Masterkey.createFromRaw(aesMasterKey: aesMasterKey, macMasterKey: macMasterKey) +``` + +### MasterkeyFile + +`MasterkeyFile` is a representation of the masterkey file. With that, you can unlock a masterkey file (and get a `Masterkey`), lock a masterkey file (and serialize it as JSON), or change the passphrase. + +#### Factory + +Create a masterkey file with content provided either from URL: + +```swift +let url = ... +let masterkeyFile = try MasterkeyFile.withContentFromURL(url: url) +``` + +Or from JSON data: + +```swift +let data = ... +let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) +``` + +#### Unlock + +When you have a masterkey file, you can attempt an unlock. When successful, it unwraps the stored encryption and MAC keys into the masterkey, which can be used for the cryptor. + +```swift +let masterkeyFile = ... +let passphrase = ... +let pepper = ... // optional +let masterkey = try masterkeyFile.unlock(passphrase: passphrase, pepper: pepper) +``` + +The unlock process can also be performed in two steps: + +```swift +let masterkeyFile = ... +let passphrase = ... +let pepper = ... // optional +let kek = try masterkeyFile.deriveKey(passphrase: passphrase, pepper: pepper) +let masterkey = try masterkeyFile.unlock(kek: kek) +``` + +This is useful if you'd like to derive the key in an extra step since the function is memory-intensive (using scrypt). The result can then be used elsewhere, e.g. in a memory-restricted process. + +#### Lock + +For persisting the masterkey, use this method to export its encrypted/wrapped masterkey and other metadata as JSON data. + +```swift +let masterkey = ... +let vaultVersion = ... +let passphrase = ... +let pepper = ... // optional +let scryptCostParam = ... // optional +let data = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: vaultVersion, passphrase: passphrase, pepper: pepper, scryptCostParam: scryptCostParam) +``` + +#### Change Passphrase + +The masterkey can be re-encrypted with a new passphrase. + +```swift +let masterkeyFileData = ... +let oldPassphrase = ... +let newPassphrase = ... +let pepper = ... // optional +let scryptCostParam = ... // optional +try MasterkeyFile.changePassphrase(masterkeyFileData: masterkeyFileData, oldPassphrase: oldPassphrase, newPassphrase: newPassphrase, pepper: pepper, scryptCostParam: scryptCostParam) +``` + +### Cryptor + +`Cryptor` is the core class for cryptographic operations on Cryptomator vaults. + +#### Constructor + +Create a cryptor by providing a masterkey. + +```swift +let masterkey = ... +let cryptor = Cryptor(masterkey: masterkey) +``` + +#### Path Encryption and Decryption + +Encrypt the directory ID in order to determine the encrypted directory URL. + +```swift +let cryptor = ... +let dirId = ... +let encryptedDirId = try cryptor.encryptDirId(dirId) +``` + +Encrypt and decrypt filenames by providing a directory ID. + +```swift +let cryptor = ... +let filename = ... +let dirId = ... +let ciphertextName = try cryptor.encryptFileName(filename, dirId: dirId) +let cleartextName = try cryptor.decryptFileName(ciphertextName, dirId: dirId) +``` + +#### File Content Encryption and Decryption + +Encrypt and decrypt file content via URLs. These methods support [implicit progress composition](https://developer.apple.com/documentation/foundation/progress#1661068). + +```swift +let cryptor = ... +let fileURL = ... +let ciphertextURL = ... +let cleartextURL = ... +try cryptor.encryptContent(from: fileURL, to: ciphertextURL) +try cryptor.decryptContent(from: ciphertextURL, to: cleartextURL) +``` + +#### File Size Calculation + +Determine the cleartext and ciphertext sizes in O(1). + +```swift +let cryptor = ... +let size = ... +let ciphertextSize = cryptor.calculateCiphertextSize(size) +let cleartextSize = try cryptor.calculateCleartextSize(ciphertextSize) +``` + +## Contributing + +Please read our [contribution guide](.github/CONTRIBUTING.md), if you would like to report a bug, ask a question or help us with coding. + +In general, the following preference is used to choose the implementation of cryptographic primitives: + +1. Apple Swift Crypto (HMAC) +2. Apple CommonCrypto (AES-CTR, RFC 3394 Key Derivation) + +This project uses [SwiftFormat](https://github.com/nicklockwood/SwiftFormat) and [SwiftLint](https://github.com/realm/SwiftLint) to enforce code style and conventions. Install these tools if you haven't already. + +Please make sure that your code is correctly formatted and passes linter validations. The easiest way to do that is to set up a pre-commit hook. Create a file at `.git/hooks/pre-commit` with this content: + +```sh +./Scripts/process.sh --staged +exit $? +``` + +And make your pre-commit hook executable: + +```sh +chmod +x .git/hooks/pre-commit +``` + +## Code of Conduct + +Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](.github/CODE_OF_CONDUCT.md). + +## License + +Distributed under the AGPLv3. See the LICENSE file for more info. diff --git a/Scripts/git-format-staged.py b/Scripts/git-format-staged.py new file mode 100644 index 0000000..ea06b76 --- /dev/null +++ b/Scripts/git-format-staged.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python +# +# Git command to transform staged files according to a command that accepts file +# content on stdin and produces output on stdout. This command is useful in +# combination with `git add -p` which allows you to stage specific changes in +# a file. This command runs a formatter on the file with staged changes while +# ignoring unstaged changes. +# +# Usage: git-format-staged [OPTION]... [FILE]... +# Example: git-format-staged --formatter 'prettier --stdin' '*.js' +# +# Tested with Python 3.6 and Python 2.7. +# +# Original author: Jesse Hallett + +from __future__ import print_function +import argparse +from fnmatch import fnmatch +from gettext import gettext as _ +import os +import re +import subprocess +import sys + +# The string $VERSION is replaced during the publish process. +VERSION = '$VERSION' +PROG = sys.argv[0] + +def info(msg): + print(msg, file=sys.stderr) + +def warn(msg): + print('{}: warning: {}'.format(PROG, msg), file=sys.stderr) + +def fatal(msg): + print('{}: error: {}'.format(PROG, msg), file=sys.stderr) + exit(1) + +def format_staged_files(file_patterns, formatter, git_root, update_working_tree=True, write=True): + try: + output = subprocess.check_output([ + 'git', 'diff-index', + '--cached', + '--diff-filter=AM', # select only file additions and modifications + '--no-renames', + 'HEAD' + ]) + for line in output.splitlines(): + entry = parse_diff(line.decode('utf-8')) + entry_path = normalize_path(entry['src_path'], relative_to=git_root) + if not (matches_some_path(file_patterns, entry_path)): + continue + if format_file_in_index(formatter, entry, update_working_tree=update_working_tree, write=write): + info('Reformatted {} with {}'.format(entry['src_path'], formatter)) + except Exception as err: + fatal(str(err)) + +# Run formatter on file in the git index. Creates a new git object with the +# result, and replaces the content of the file in the index with that object. +# Returns hash of the new object if formatting produced any changes. +def format_file_in_index(formatter, diff_entry, update_working_tree=True, write=True): + orig_hash = diff_entry['dst_hash'] + new_hash = format_object(formatter, orig_hash, diff_entry['src_path']) + + # If the new hash is the same then the formatter did not make any changes. + if not write or new_hash == orig_hash: + return None + + # If the content of the new object is empty then the formatter did not + # produce any output. We want to abort instead of replacing the file with an + # empty one. + if object_is_empty(new_hash): + return None + + replace_file_in_index(diff_entry, new_hash) + + if update_working_tree: + try: + patch_working_file(diff_entry['src_path'], orig_hash, new_hash) + except Exception as err: + # Errors patching working tree files are not fatal + warn(str(err)) + + return new_hash + +file_path_placeholder = re.compile('\{\}') + +# Run formatter on a git blob identified by its hash. Writes output to a new git +# blob, and returns the hash of the new blob. +def format_object(formatter, object_hash, file_path): + get_content = subprocess.Popen( + ['git', 'cat-file', '-p', object_hash], + stdout=subprocess.PIPE + ) + format_content = subprocess.Popen( + re.sub(file_path_placeholder, file_path, formatter), + shell=True, + stdin=get_content.stdout, + stdout=subprocess.PIPE + ) + write_object = subprocess.Popen( + ['git', 'hash-object', '-w', '--stdin'], + stdin=format_content.stdout, + stdout=subprocess.PIPE + ) + + get_content.stdout.close() + format_content.stdout.close() + + if get_content.wait() != 0: + raise ValueError('unable to read file content from object database: ' + object_hash) + + if format_content.wait() != 0: + raise Exception('formatter exited with non-zero status') # TODO: capture stderr from format command + + new_hash, err = write_object.communicate() + + if write_object.returncode != 0: + raise Exception('unable to write formatted content to object database') + + return new_hash.decode('utf-8').rstrip() + +def object_is_empty(object_hash): + get_content = subprocess.Popen( + ['git', 'cat-file', '-p', object_hash], + stdout=subprocess.PIPE + ) + content, err = get_content.communicate() + + if get_content.returncode != 0: + raise Exception('unable to verify content of formatted object') + + return not content + +def replace_file_in_index(diff_entry, new_object_hash): + subprocess.check_call(['git', 'update-index', + '--cacheinfo', '{},{},{}'.format( + diff_entry['dst_mode'], + new_object_hash, + diff_entry['src_path'] + )]) + +def patch_working_file(path, orig_object_hash, new_object_hash): + patch = subprocess.check_output( + ['git', 'diff', orig_object_hash, new_object_hash] + ) + + # Substitute object hashes in patch header with path to working tree file + patch_b = patch.replace(orig_object_hash.encode(), path.encode()).replace(new_object_hash.encode(), path.encode()) + + apply_patch = subprocess.Popen( + ['git', 'apply', '-'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + output, err = apply_patch.communicate(input=patch_b) + + if apply_patch.returncode != 0: + raise Exception('could not apply formatting changes to working tree file {}'.format(path)) + +# Format: src_mode dst_mode src_hash dst_hash status/score? src_path dst_path? +diff_pat = re.compile('^:(\d+) (\d+) ([a-f0-9]+) ([a-f0-9]+) ([A-Z])(\d+)?\t([^\t]+)(?:\t([^\t]+))?$') + +# Parse output from `git diff-index` +def parse_diff(diff): + m = diff_pat.match(diff) + if not m: + raise ValueError('Failed to parse diff-index line: ' + diff) + return { + 'src_mode': unless_zeroed(m.group(1)), + 'dst_mode': unless_zeroed(m.group(2)), + 'src_hash': unless_zeroed(m.group(3)), + 'dst_hash': unless_zeroed(m.group(4)), + 'status': m.group(5), + 'score': int(m.group(6)) if m.group(6) else None, + 'src_path': m.group(7), + 'dst_path': m.group(8) + } + +zeroed_pat = re.compile('^0+$') + +# Returns the argument unless the argument is a string of zeroes, in which case +# returns `None` +def unless_zeroed(s): + return s if not zeroed_pat.match(s) else None + +def get_git_root(): + return subprocess.check_output( + ['git', 'rev-parse', '--show-toplevel'] + ).decode('utf-8').rstrip() + +def normalize_path(p, relative_to=None): + return os.path.abspath( + os.path.join(relative_to, p) if relative_to else p + ) + +def matches_some_path(patterns, target): + is_match = False + for signed_pattern in patterns: + (is_pattern_positive, pattern) = from_signed_pattern(signed_pattern) + if fnmatch(target, normalize_path(pattern)): + is_match = is_pattern_positive + return is_match + +# Checks for a '!' as the first character of a pattern, returns the rest of the +# pattern in a tuple. The tuple takes the form (is_pattern_positive, pattern). +# For example: +# from_signed_pattern('!pat') == (False, 'pat') +# from_signed_pattern('pat') == (True, 'pat') +def from_signed_pattern(pattern): + if pattern[0] == '!': + return (False, pattern[1:]) + else: + return (True, pattern) + +class CustomArgumentParser(argparse.ArgumentParser): + def parse_args(self, args=None, namespace=None): + args, argv = self.parse_known_args(args, namespace) + if argv: + msg = argparse._( + 'unrecognized arguments: %s. Do you need to quote your formatter command?' + ) + self.error(msg % ' '.join(argv)) + return args + +if __name__ == '__main__': + parser = CustomArgumentParser( + description='Transform staged files using a formatting command that accepts content via stdin and produces a result via stdout.', + epilog='Example: %(prog)s --formatter "prettier --stdin" "src/*.js" "test/*.js"' + ) + parser.add_argument( + '--formatter', '-f', + required=True, + help='Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will be replaced with a path to the file being formatted. (Example: "prettier --stdin --stdin-filepath \'{}\'")' + ) + parser.add_argument( + '--no-update-working-tree', + action='store_true', + help='By default formatting changes made to staged file content will also be applied to working tree files via a patch. This option disables that behavior, leaving working tree files untouched.' + ) + parser.add_argument( + '--no-write', + action='store_true', + help='Prevents %(prog)s from modifying staged or working tree files. You can use this option to check staged changes with a linter instead of formatting. With this option stdout from the formatter command is ignored. Example: %(prog)s --no-write -f "eslint --stdin --stdin-filename \'{}\' >&2" "*.js"' + ) + parser.add_argument( + '--version', + action='version', + version='%(prog)s version {}'.format(VERSION), + help='Display version of %(prog)s' + ) + parser.add_argument( + 'files', + nargs='+', + help='Patterns that specify files to format. The formatter will only transform staged files that are given here. Patterns may be literal file paths, or globs which will be tested against staged file paths using Python\'s fnmatch function. For example "src/*.js" will match all files with a .js extension in src/ and its subdirectories. Patterns may be negated to exclude files using a "!" character. Patterns are evaluated left-to-right. (Example: "main.js" "src/*.js" "test/*.js" "!test/todo/*")' + ) + args = parser.parse_args() + files = vars(args)['files'] + format_staged_files( + file_patterns=files, + formatter=vars(args)['formatter'], + git_root=get_git_root(), + update_working_tree=not vars(args)['no_update_working_tree'], + write=not vars(args)['no_write'] + ) diff --git a/Scripts/process.sh b/Scripts/process.sh new file mode 100755 index 0000000..9a0da33 --- /dev/null +++ b/Scripts/process.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# +# Based on https://merowing.info/2021/01/improve-build-times-by-extracting-3rd-party-tooling-to-processing-script./ + +cd "$(dirname "$0")/.." + +if [[ "$1" == "--staged" ]]; then + staged_mode=true + echo "Running in --staged mode" +else + echo "Running in full mode" +fi + +final_status=0 + +function process_output() { + printf '\n# Running %s\n' "$1" + local start=$(date +%s) + local output=$(eval "$2" 2>&1) + if [[ ! -z "$output" ]]; then + printf -- '---\n%s\n---\n' "$output" + final_status=1 + fi + local end=$(date +%s) + printf 'Execution time was %s seconds.\n' "$((end - start))" +} + +if [ "$staged_mode" = true ]; then + process_output "SwiftFormat" "python ./Scripts/git-format-staged.py -f 'swiftformat stdin --stdinpath \"{}\" --quiet' '*.swift'" + process_output "SwiftLint" "python ./Scripts/git-format-staged.py --no-write -f 'swiftlint --use-stdin --quiet >&2' '*.swift'" + if [[ "$final_status" -gt 0 ]]; then + printf '\nChanges werde made or are required. Please review the output above for further details.\n' + fi +else + process_output "SwiftFormat" "swiftformat --lint --quiet ." + process_output "SwiftLint" "swiftlint --quiet ." + if [[ "$final_status" -gt 0 ]]; then + printf '\nChanges are required. Please review the output above for further details.\n' + fi +fi + +exit $final_status diff --git a/Sources/CryptomatorCryptoLib/AesCtr.swift b/Sources/CryptomatorCryptoLib/AesCtr.swift new file mode 100644 index 0000000..5a8f4b0 --- /dev/null +++ b/Sources/CryptomatorCryptoLib/AesCtr.swift @@ -0,0 +1,50 @@ +// +// AesCtr.swift +// CryptomatorCryptoLib +// +// Created by Sebastian Stenzel on 06.06.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import CommonCrypto +import Foundation + +class AesCtr { + /** + High-level AES-CTR wrapper around CommonCrypto primitives. Can be used for encryption and decryption (it is the same in CTR mode). + + - Parameter key: 128 or 256 bit encryption key + - Parameter iv: 128 bit initialization vector (must not be reused!) + - Parameter data: data to be encrypted/decrypted + - Returns: encrypted/decrypted data + */ + static func compute(key: [UInt8], iv: [UInt8], data: [UInt8]) throws -> [UInt8] { + assert(key.count == kCCKeySizeAES256 || key.count == kCCKeySizeAES128, "key expected to be 128 or 256 bit") + assert(iv.count == kCCBlockSizeAES128, "iv expected to be 128 bit") + + var cryptor: CCCryptorRef? + var status = CCCryptorCreateWithMode(CCOperation(kCCEncrypt), CCMode(kCCModeCTR), CCAlgorithm(kCCAlgorithmAES), CCPadding(ccNoPadding), iv, key, key.count, nil, 0, 0, CCModeOptions(kCCModeOptionCTR_BE), &cryptor) + guard status == kCCSuccess, cryptor != nil else { + throw CryptoError.invalidParameter("failed to initialize cryptor") + } + defer { + CCCryptorRelease(cryptor) + } + + let outlen = CCCryptorGetOutputLength(cryptor, data.count, true) + var ciphertext = [UInt8](repeating: 0x00, count: outlen) + + var numEncryptedBytes = 0 + status = CCCryptorUpdate(cryptor, data, data.count, &ciphertext, ciphertext.count, &numEncryptedBytes) + guard status == kCCSuccess else { + throw CryptoError.ccCryptorError(status) + } + + status = CCCryptorFinal(cryptor, &ciphertext, ciphertext.count, &numEncryptedBytes) + guard status == kCCSuccess else { + throw CryptoError.ccCryptorError(status) + } + + return ciphertext + } +} diff --git a/Sources/CryptomatorCryptoLib/AesSiv.swift b/Sources/CryptomatorCryptoLib/AesSiv.swift new file mode 100644 index 0000000..417c1c2 --- /dev/null +++ b/Sources/CryptomatorCryptoLib/AesSiv.swift @@ -0,0 +1,191 @@ +// +// AesSiv.swift +// CryptomatorCryptoLib +// +// Created by Sebastian Stenzel on 29.04.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import CommonCrypto +import Foundation + +class AesSiv { + static let cryptoSupport = CryptoSupport() + static let zero = [UInt8](repeating: 0x00, count: 16) + static let dblConst: UInt8 = 0x87 + + /** + Encrypts plaintext using SIV mode. + + - Parameter aesKey: SIV mode requires two separate keys. You can use one long key, which is splitted in half. See [RFC 5297 Section 2.2](https://tools.ietf.org/html/rfc5297#section-2.2). + - Parameter macKey: SIV mode requires two separate keys. You can use one long key, which is splitted in half. See [RFC 5297 Section 2.2](https://tools.ietf.org/html/rfc5297#section-2.2). + - Parameter plaintext: Your plaintext, which shall be encrypted. + - Parameter ad: Associated data, which gets authenticated but not encrypted. + - Returns: IV + Ciphertext as a concatenated byte array. + */ + static func encrypt(aesKey: [UInt8], macKey: [UInt8], plaintext: [UInt8], ad: [UInt8]...) throws -> [UInt8] { + assert(plaintext.count <= UInt32.max - 16, "plaintext must not be longer than 2^32 - 16 bytes") + let iv = try s2v(macKey: macKey, plaintext: plaintext, ad: ad) + let ciphertext = try ctr(aesKey: aesKey, iv: iv, plaintext: plaintext) + return iv + ciphertext + } + + /** + Decrypts ciphertext using SIV mode. + + - Parameter aesKey: SIV mode requires two separate keys. You can use one long key, which is splitted in half. See [RFC 5297 Section 2.2](https://tools.ietf.org/html/rfc5297#section-2.2). + - Parameter macKey: SIV mode requires two separate keys. You can use one long key, which is splitted in half. See [RFC 5297 Section 2.2](https://tools.ietf.org/html/rfc5297#section-2.2). + - Parameter ciphertext: Your ciphertext, which shall be decrypted. + - Parameter ad: Associated data, which needs to be authenticated during decryption. + - Returns: Plaintext byte array. + */ + static func decrypt(aesKey: [UInt8], macKey: [UInt8], ciphertext: [UInt8], ad: [UInt8]...) throws -> [UInt8] { + assert(ciphertext.count >= 16, "ciphertext must be at least 16 bytes") + let iv = Array(ciphertext[..<16]) + let actualCiphertext = Array(ciphertext[16...]) + let plaintext = try ctr(aesKey: aesKey, iv: iv, plaintext: actualCiphertext) + let control = try s2v(macKey: macKey, plaintext: plaintext, ad: ad) + guard cryptoSupport.compareBytes(expected: control, actual: iv) else { + throw CryptoError.unauthenticCiphertext + } + return plaintext + } + + // MARK: - Internal + + static func ctr(aesKey key: [UInt8], iv: [UInt8], plaintext: [UInt8]) throws -> [UInt8] { + // clear out the 31st and 63rd bit (see https://tools.ietf.org/html/rfc5297#section-2.5) + var ctr = iv + ctr[8] &= 0x7F + ctr[12] &= 0x7F + return try AesCtr.compute(key: key, iv: ctr, data: plaintext) + } + + static func s2v(macKey: [UInt8], plaintext: [UInt8], ad: [[UInt8]]) throws -> [UInt8] { + // Maximum permitted AD length is the block size in bits - 2 + assert(ad.count <= 126, "too many ad") + + // RFC 5297 defines a n == 0 case here. Where n is the length of the input vector: + // S1 = associatedData1, S2 = associatedData2, ... Sn = plaintext + // Since this method is invoked only by encrypt/decrypt, we always have a plaintext. + // Thus n > 0 + + var d = try cmac(macKey: macKey, data: zero) + for s in ad { + d = xor(dbl(d), try cmac(macKey: macKey, data: s)) + } + + let t: [UInt8] + if plaintext.count >= 16 { + t = xorend(plaintext, d) + } else { + t = xor(dbl(d), pad(plaintext)) + } + + return try cmac(macKey: macKey, data: t) + } + + static func cmac(macKey key: [UInt8], data: [UInt8]) throws -> [UInt8] { + // subkey generation: + let l = try aes(key: key, plaintext: zero) + let k1 = l[0] & 0x80 == 0x00 ? shiftLeft(l) : dbl(l) + let k2 = k1[0] & 0x80 == 0x00 ? shiftLeft(k1) : dbl(k1) + + // determine number of blocks: + let n = (data.count + 15) / 16 + let lastBlockIdx: Int + let lastBlockComplete: Bool + if n == 0 { + lastBlockIdx = 0 + lastBlockComplete = false + } else { + lastBlockIdx = n - 1 + lastBlockComplete = data.count % 16 == 0 + } + + // blocks 0.. [UInt8] { + assert(key.count == kCCKeySizeAES128 || key.count == kCCKeySizeAES192 || key.count == kCCKeySizeAES256) + assert(plaintext.count == kCCBlockSizeAES128, "Attempt to run AES-ECB for plaintext != one single block") + + var ciphertext = [UInt8](repeating: 0x00, count: kCCBlockSizeAES128) + var ciphertextLen = 0 + let status = CCCrypt(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionECBMode), key, key.count, nil, plaintext, plaintext.count, &ciphertext, kCCBlockSizeAES128, &ciphertextLen) + + guard status == kCCSuccess else { + throw CryptoError.ccCryptorError(status) + } + + return ciphertext + } + + private static func shiftLeft(_ input: [UInt8]) -> [UInt8] { + var output = [UInt8](repeating: 0x00, count: input.count) + var bit: UInt8 = 0 + for i in (0 ..< input.count).reversed() { + let b = input[i] & 0xFF + output[i] = (b << 1) | bit + bit = (b >> 7) & 1 + } + return output + } + + private static func dbl(_ block: [UInt8]) -> [UInt8] { + var result = shiftLeft(block) + if block[0] & 0x80 != 0x00 { + result[block.count - 1] ^= dblConst + } + return result + } + + private static func xor(_ data1: [UInt8], _ data2: [UInt8]) -> [UInt8] { + assert(data1.count <= data2.count, "Length of first input must be <= length of second input.") + var result = [UInt8](repeating: 0x00, count: data1.count) + for i in 0 ..< data1.count { + result[i] = data1[i] ^ data2[i] + } + return result + } + + private static func xorend(_ data1: [UInt8], _ data2: [UInt8]) -> [UInt8] { + assert(data1.count >= data2.count, "Length of first input must be >= length of second input.") + var result = data1 + let diff = data1.count - data2.count + for i in 0 ..< data2.count { + result[i + diff] = data1[i + diff] ^ data2[i] + } + return result + } + + // ISO/IEC 7816-4:2005 Padding: First bit 1, following bits 0 + private static func pad(_ data: [UInt8]) -> [UInt8] { + var result = data + if result.count < 16 { + result.append(0x80) + } + while result.count < 16 { + result.append(0x00) + } + return result + } +} diff --git a/Sources/CryptomatorCryptoLib/ContentCryptor.swift b/Sources/CryptomatorCryptoLib/ContentCryptor.swift new file mode 100644 index 0000000..ffc1014 --- /dev/null +++ b/Sources/CryptomatorCryptoLib/ContentCryptor.swift @@ -0,0 +1,81 @@ +// +// ContentCryptor.swift +// CryptomatorCryptoLib +// +// Created by Sebastian Stenzel on 09.03.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CommonCrypto +import Foundation + +protocol ContentCryptor { + var nonceLen: Int { get } + var tagLen: Int { get } + + /** + Encrypts one single chunk of cleartext data. + + - Parameter chunk: The cleartext to be encrypted. + - Parameter key: The encryption key. + - Parameter nonce: The nonce/IV to use. + - Parameter ad: Associated data, which needs to be authenticated during decryption. + - Returns: Nonce/IV + ciphertext + MAC/tag, as a concatenated byte array. + */ + func encrypt(_ chunk: [UInt8], key: [UInt8], nonce: [UInt8], ad: [UInt8]...) throws -> [UInt8] + + /** + Decrypts one single chunk of encrypted data. + + - Parameter chunk: The nonce/IV + ciphertext + MAC/tag, as a concatenated byte array. + - Parameter key: The encryption key. + - Parameter ad: Associated data, which needs to be authenticated during decryption. + - Returns: The original cleartext. + */ + func decrypt(_ chunk: [UInt8], key: [UInt8], ad: [UInt8]...) throws -> [UInt8] +} + +class CtrThenHmacContentCryptor: ContentCryptor { + let nonceLen = kCCBlockSizeAES128 + let tagLen = Int(CC_SHA256_DIGEST_LENGTH) + + private let macKey: [UInt8] + private let cryptoSupport: CryptoSupport + + init(macKey: [UInt8], cryptoSupport: CryptoSupport) { + self.macKey = macKey + self.cryptoSupport = cryptoSupport + } + + func encrypt(_ chunk: [UInt8], key: [UInt8], nonce: [UInt8], ad: [UInt8]...) throws -> [UInt8] { + let ciphertext = try AesCtr.compute(key: key, iv: nonce, data: chunk) + let mac = computeHmac(ciphertext, nonce: nonce, ad: ad) + return nonce + ciphertext + mac + } + + func decrypt(_ chunk: [UInt8], key: [UInt8], ad: [UInt8]...) throws -> [UInt8] { + assert(chunk.count >= nonceLen + tagLen, "ciphertext chunk must at least contain nonce + tag") + + // decompose chunk: + let beginOfMAC = chunk.count - tagLen + let chunkNonce = [UInt8](chunk[0 ..< nonceLen]) + let ciphertext = [UInt8](chunk[nonceLen ..< beginOfMAC]) + let expectedMAC = [UInt8](chunk[beginOfMAC...]) + + // check MAC: + let mac = computeHmac(ciphertext, nonce: chunkNonce, ad: ad) + guard cryptoSupport.compareBytes(expected: expectedMAC, actual: mac) else { + throw CryptoError.unauthenticCiphertext + } + + // decrypt: + return try AesCtr.compute(key: key, iv: chunkNonce, data: ciphertext) + } + + private func computeHmac(_ ciphertext: [UInt8], nonce: [UInt8], ad: [[UInt8]]) -> [UInt8] { + let data = ad.reduce([UInt8](), +) + nonce + ciphertext + var mac = [UInt8](repeating: 0x00, count: tagLen) + CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), macKey, macKey.count, data, data.count, &mac) + return mac + } +} diff --git a/Sources/CryptomatorCryptoLib/CryptoError.swift b/Sources/CryptomatorCryptoLib/CryptoError.swift new file mode 100644 index 0000000..d626c55 --- /dev/null +++ b/Sources/CryptomatorCryptoLib/CryptoError.swift @@ -0,0 +1,18 @@ +// +// CryptoError.swift +// CryptomatorCryptoLib +// +// Created by Tobias Hagemann on 09.06.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import CommonCrypto +import Foundation + +public enum CryptoError: Error, Equatable { + case invalidParameter(_ reason: String) + case ccCryptorError(_ status: CCCryptorStatus) + case unauthenticCiphertext + case csprngError + case ioError +} diff --git a/Sources/CryptomatorCryptoLib/CryptoSupport.swift b/Sources/CryptomatorCryptoLib/CryptoSupport.swift new file mode 100644 index 0000000..8cdfe0f --- /dev/null +++ b/Sources/CryptomatorCryptoLib/CryptoSupport.swift @@ -0,0 +1,49 @@ +// +// CryptoSupport.swift +// CryptomatorCryptoLib +// +// Created by Tobias Hagemann on 10.06.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import Foundation + +class CryptoSupport { + /** + Creates an array of cryptographically secure random bytes. + + - Parameter size: The number of random bytes to return in the array. + - Returns: An array with cryptographically secure random bytes. + */ + func createRandomBytes(size: Int) throws -> [UInt8] { + var randomBytes = [UInt8](repeating: 0x00, count: size) + guard SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes) == errSecSuccess else { + throw CryptoError.csprngError + } + return randomBytes + } + + /** + Compares byte arrays in constant-time. + + The running time of this method is independent of the byte arrays compared, making it safe to use for comparing secret values such as cryptographic MACs. + + The byte arrays are expected to be of same length. + + - Parameter expected: Expected bytes for comparison. + - Parameter actual: Actual bytes for comparison. + - Returns: `true` if `expected` and `actual` are equal, otherwise `false`. + */ + func compareBytes(expected: [UInt8], actual: [UInt8]) -> Bool { + assert(expected.count == actual.count, "parameters should be of same length") + if #available(iOS 10.1, macCatalyst 13.0, macOS 10.12.1, *) { + return timingsafe_bcmp(expected, actual, expected.count) == 0 + } else { + var diff: UInt8 = 0 + for i in 0 ..< expected.count { + diff |= expected[i] ^ actual[i] + } + return diff == 0 + } + } +} diff --git a/Sources/CryptomatorCryptoLib/CryptomatorCryptoLib.h b/Sources/CryptomatorCryptoLib/CryptomatorCryptoLib.h new file mode 100644 index 0000000..d94524a --- /dev/null +++ b/Sources/CryptomatorCryptoLib/CryptomatorCryptoLib.h @@ -0,0 +1,19 @@ +// +// CryptomatorCryptoLib.h +// CryptomatorCryptoLib +// +// Created by Philipp Schmid on 23.04.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +#import + +//! Project version number for CryptomatorCryptoLib. +FOUNDATION_EXPORT double CryptomatorCryptoLibVersionNumber; + +//! Project version string for CryptomatorCryptoLib. +FOUNDATION_EXPORT const unsigned char CryptomatorCryptoLibVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Sources/CryptomatorCryptoLib/Cryptor.swift b/Sources/CryptomatorCryptoLib/Cryptor.swift new file mode 100644 index 0000000..3dae122 --- /dev/null +++ b/Sources/CryptomatorCryptoLib/Cryptor.swift @@ -0,0 +1,350 @@ +// +// Cryptor.swift +// CryptomatorCryptoLib +// +// Created by Sebastian Stenzel on 25.04.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import Base32 +import CommonCrypto +import Foundation + +public extension Data { + init?(base64UrlEncoded base64String: String, options: Data.Base64DecodingOptions = []) { + self.init(base64Encoded: base64String.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/"), options: options) + } + + func base64UrlEncodedString(options: Data.Base64EncodingOptions = []) -> String { + return base64EncodedString(options: options).replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_") + } +} + +public extension FixedWidthInteger { + func byteArray() -> [UInt8] { + return withUnsafeBytes(of: self, { [UInt8]($0) }) + } +} + +public enum InputStreamError: Error { + case readOperationFailed +} + +public extension InputStream { + func read(maxLength: Int) throws -> [UInt8]? { + var buffer = [UInt8](repeating: 0x00, count: maxLength) + let length = read(&buffer, maxLength: maxLength) + switch length { + case _ where length > 0: + assert(length <= buffer.count) + buffer.removeSubrange(length...) + return buffer + case 0: + return nil + case _ where length < 0: + throw streamError ?? InputStreamError.readOperationFailed + default: + fatalError() + } + } +} + +public enum FileNameEncoding: String { + case base64url + case base32 +} + +struct FileHeader { + let nonce: [UInt8] + let contentKey: [UInt8] +} + +public class Cryptor { + private let fileHeaderLegacyPayloadSize = 8 + public var fileHeaderSize: Int { + let fileHeaderPayloadSize = fileHeaderLegacyPayloadSize + kCCKeySizeAES256 + return contentCryptor.nonceLen + fileHeaderPayloadSize + contentCryptor.tagLen + } + + private let cleartextChunkSize = 32 * 1024 + private var ciphertextChunkSize: Int { + return contentCryptor.nonceLen + cleartextChunkSize + contentCryptor.tagLen + } + + private let masterkey: Masterkey + private let cryptoSupport: CryptoSupport + private let contentCryptor: ContentCryptor + + init(masterkey: Masterkey, cryptoSupport: CryptoSupport, contentCryptor: ContentCryptor) { + self.masterkey = masterkey + self.cryptoSupport = cryptoSupport + self.contentCryptor = contentCryptor + } + + public convenience init(masterkey: Masterkey) { + let cryptoSupport = CryptoSupport() + let contentCryptor = CtrThenHmacContentCryptor(macKey: masterkey.macMasterKey, cryptoSupport: cryptoSupport) + self.init(masterkey: masterkey, cryptoSupport: cryptoSupport, contentCryptor: contentCryptor) + } + + // MARK: - Path Encryption and Decryption + + /** + Encrypts directory ID. + + - Parameter dirId: An arbitrary directory ID to be passed to one-way hash function. + - Returns: Constant length string that is unlikely to collide with any other name. + */ + public func encryptDirId(_ dirId: Data) throws -> String { + let encrypted = try AesSiv.encrypt(aesKey: masterkey.aesMasterKey, macKey: masterkey.macMasterKey, plaintext: [UInt8](dirId)) + var digest = [UInt8](repeating: 0x00, count: Int(CC_SHA1_DIGEST_LENGTH)) + CC_SHA1(encrypted, UInt32(encrypted.count) as CC_LONG, &digest) + return Data(digest).base32EncodedString + } + + /** + Encrypts filename. + + - Parameter cleartextName: Original filename including cleartext file extension. + - Parameter dirId: Directory ID that will be used as associated data. It will not get encrypted but needs to be provided during decryption. + - Parameter encoding: Encoding to use to encode the returned ciphertext. Defaults to base64url. + - Returns: Encrypted filename without any file extension. + */ + public func encryptFileName(_ cleartextName: String, dirId: Data, encoding: FileNameEncoding = .base64url) throws -> String { + // encrypt: + let cleartext = [UInt8](cleartextName.precomposedStringWithCanonicalMapping.utf8) + let ciphertext = try AesSiv.encrypt(aesKey: masterkey.aesMasterKey, macKey: masterkey.macMasterKey, plaintext: cleartext, ad: [UInt8](dirId)) + + // encode: + switch encoding { + case .base64url: return Data(ciphertext).base64UrlEncodedString() + case .base32: return Data(ciphertext).base32EncodedString + } + } + + /** + Decrypts filename. + + - Parameter ciphertextName: Ciphertext only. Any additional strings like file extensions need to be stripped first. + - Parameter dirId: The same directed ID used during encryption as associated data. + - Parameter encoding: Encoding to use to decode `ciphertextName`. Defaults to base64url. + - Returns: Decrypted filename, probably including its cleartext file extension. + */ + public func decryptFileName(_ ciphertextName: String, dirId: Data, encoding: FileNameEncoding = .base64url) throws -> String { + // decode: + let maybeCiphertextData: Data? = { + switch encoding { + case .base64url: return Data(base64UrlEncoded: ciphertextName) + case .base32: return ciphertextName.base32DecodedData + } + }() + guard let ciphertextData = maybeCiphertextData else { + throw CryptoError.invalidParameter("Can't \(encoding.rawValue)-decode ciphertext name: \(ciphertextName)") + } + + // decrypt: + let cleartext = try AesSiv.decrypt(aesKey: masterkey.aesMasterKey, macKey: masterkey.macMasterKey, ciphertext: [UInt8](ciphertextData), ad: [UInt8](dirId)) + if let str = String(data: Data(cleartext), encoding: .utf8) { + return str + } else { + throw CryptoError.invalidParameter("Unable to decode cleartext using UTF-8.") + } + } + + // MARK: - File Header Encryption and Decryption + + func createHeader() throws -> FileHeader { + let nonce = try cryptoSupport.createRandomBytes(size: kCCBlockSizeAES128) + let contentKey = try cryptoSupport.createRandomBytes(size: kCCKeySizeAES256) + return FileHeader(nonce: nonce, contentKey: contentKey) + } + + func encryptHeader(_ header: FileHeader) throws -> [UInt8] { + let cleartext = [UInt8](repeating: 0xFF, count: fileHeaderLegacyPayloadSize) + header.contentKey + return try contentCryptor.encrypt(cleartext, key: masterkey.aesMasterKey, nonce: header.nonce) + } + + func decryptHeader(_ header: [UInt8]) throws -> FileHeader { + let nonce = [UInt8](header[0 ..< contentCryptor.nonceLen]) + let cleartext = try contentCryptor.decrypt(header, key: masterkey.aesMasterKey) + let contentKey = [UInt8](cleartext[fileHeaderLegacyPayloadSize...]) + return FileHeader(nonce: nonce, contentKey: contentKey) + } + + // MARK: - File Content Encryption and Decryption + + /** + Encrypts file content. + + This method supports implicit progress composition. + + - Parameter cleartextURL: The input URL of a cleartext file. + - Parameter ciphertextURL: The output URL of the ciphertext file. + */ + public func encryptContent(from cleartextURL: URL, to ciphertextURL: URL) throws { + // open cleartext input stream: + guard let cleartextStream = InputStream(url: cleartextURL) else { + throw CryptoError.ioError + } + cleartextStream.schedule(in: .current, forMode: .default) + cleartextStream.open() + defer { cleartextStream.close() } + + // open ciphertext output stream: + guard let ciphertextStream = OutputStream(url: ciphertextURL, append: false) else { + throw CryptoError.ioError + } + ciphertextStream.schedule(in: .current, forMode: .default) + ciphertextStream.open() + defer { ciphertextStream.close() } + + // determine cleartext size: + let attributes = try FileManager.default.attributesOfItem(atPath: cleartextURL.path) + let cleartextSize = attributes[FileAttributeKey.size] as? Int + + // encrypt: + try encryptContent(from: cleartextStream, to: ciphertextStream, cleartextSize: cleartextSize) + } + + func encryptContent(from cleartextStream: InputStream, to ciphertextStream: OutputStream, cleartextSize: Int?) throws { + // create progress: + let progress: Progress + if let cleartextSize = cleartextSize { + let ciphertextSize = calculateCiphertextSize(cleartextSize) + progress = Progress(totalUnitCount: Int64(ciphertextSize)) + } else { + progress = Progress(totalUnitCount: -1) + } + + // encrypt and write header: + let header = try createHeader() + let ciphertextHeader = try encryptHeader(header) + ciphertextStream.write(ciphertextHeader, maxLength: ciphertextHeader.count) + + // encrypt and write ciphertext content: + var chunkNumber: UInt64 = 0 + while cleartextStream.hasBytesAvailable { + try autoreleasepool { + guard let cleartextChunk = try cleartextStream.read(maxLength: cleartextChunkSize) else { + return + } + let ciphertextChunk = try encryptSingleChunk(cleartextChunk, chunkNumber: chunkNumber, headerNonce: header.nonce, fileKey: header.contentKey) + ciphertextStream.write(ciphertextChunk, maxLength: ciphertextChunk.count) + progress.completedUnitCount += Int64(ciphertextChunk.count) + chunkNumber += 1 + } + } + } + + /** + Decrypts file content. + + This method supports implicit progress composition. + + - Parameter ciphertextURL: The input URL of a ciphertext file. + - Parameter cleartextURL: The output URL of the cleartext file. + */ + public func decryptContent(from ciphertextURL: URL, to cleartextURL: URL) throws { + // open ciphertext input stream: + guard let ciphertextStream = InputStream(url: ciphertextURL) else { + throw CryptoError.ioError + } + ciphertextStream.schedule(in: .current, forMode: .default) + ciphertextStream.open() + defer { ciphertextStream.close() } + + // open cleartext output stream: + guard let cleartextStream = OutputStream(url: cleartextURL, append: false) else { + throw CryptoError.ioError + } + cleartextStream.schedule(in: .current, forMode: .default) + cleartextStream.open() + defer { cleartextStream.close() } + + // determine ciphertext size: + let attributes = try FileManager.default.attributesOfItem(atPath: ciphertextURL.path) + let ciphertextSize = attributes[FileAttributeKey.size] as? Int + + // decrypt: + try decryptContent(from: ciphertextStream, to: cleartextStream, ciphertextSize: ciphertextSize) + } + + func decryptContent(from ciphertextStream: InputStream, to cleartextStream: OutputStream, ciphertextSize: Int?) throws { + // create progress: + let progress: Progress + if let ciphertextSize = ciphertextSize, let cleartextSize = try? calculateCleartextSize(ciphertextSize - fileHeaderSize) { + progress = Progress(totalUnitCount: Int64(cleartextSize)) + } else { + progress = Progress(totalUnitCount: -1) + } + + // read and decrypt header: + guard let ciphertextHeader = try ciphertextStream.read(maxLength: fileHeaderSize) else { + throw CryptoError.ioError + } + let header = try decryptHeader(ciphertextHeader) + + // decrypt and write cleartext content: + let ciphertextChunkSize = contentCryptor.nonceLen + cleartextChunkSize + contentCryptor.tagLen + var chunkNumber: UInt64 = 0 + while ciphertextStream.hasBytesAvailable { + try autoreleasepool { + guard let ciphertextChunk = try ciphertextStream.read(maxLength: ciphertextChunkSize) else { + return + } + let cleartextChunk = try decryptSingleChunk(ciphertextChunk, chunkNumber: chunkNumber, headerNonce: header.nonce, fileKey: header.contentKey) + cleartextStream.write(cleartextChunk, maxLength: cleartextChunk.count) + progress.completedUnitCount += Int64(cleartextChunk.count) + chunkNumber += 1 + } + } + } + + func encryptSingleChunk(_ chunk: [UInt8], chunkNumber: UInt64, headerNonce: [UInt8], fileKey: [UInt8]) throws -> [UInt8] { + let chunkNonce = try cryptoSupport.createRandomBytes(size: kCCBlockSizeAES128) + return try contentCryptor.encrypt(chunk, key: fileKey, nonce: chunkNonce, ad: headerNonce, chunkNumber.bigEndian.byteArray()) + } + + func decryptSingleChunk(_ chunk: [UInt8], chunkNumber: UInt64, headerNonce: [UInt8], fileKey: [UInt8]) throws -> [UInt8] { + return try contentCryptor.decrypt(chunk, key: fileKey, ad: headerNonce, chunkNumber.bigEndian.byteArray()) + } + + // MARK: - File Size Calculation + + /** + Calculates ciphertext size from cleartext size. + + - Parameter cleartextSize: Size of the unencrypted payload. + - Precondition: `cleartextSize` must be positive. + - Returns: Ciphertext size of a `cleartextSize`-sized cleartext encrypted with this `Cryptor`. Not including the file header. + */ + public func calculateCiphertextSize(_ cleartextSize: Int) -> Int { + precondition(cleartextSize >= 0, "expected cleartextSize to be positive, but was \(cleartextSize)") + let overheadPerChunk = ciphertextChunkSize - cleartextChunkSize + let numFullChunks = cleartextSize / cleartextChunkSize // floor by int-truncation + let additionalCleartextBytes = cleartextSize % cleartextChunkSize + let additionalCiphertextBytes = (additionalCleartextBytes == 0) ? 0 : additionalCleartextBytes + overheadPerChunk + assert(additionalCiphertextBytes >= 0) + return ciphertextChunkSize * numFullChunks + additionalCiphertextBytes + } + + /** + Calculates cleartext size from ciphertext size. + + - Parameter ciphertextSize: Size of the encrypted payload. Not including the file header. + - Precondition: `ciphertextSize` must be positive. + - Returns: Cleartext size of a `ciphertextSize`-sized ciphertext decrypted with this `Cryptor`. + */ + public func calculateCleartextSize(_ ciphertextSize: Int) throws -> Int { + precondition(ciphertextSize >= 0, "expected ciphertextSize to be positive, but was \(ciphertextSize)") + let overheadPerChunk = ciphertextChunkSize - cleartextChunkSize + let numFullChunks = ciphertextSize / ciphertextChunkSize // floor by int-truncation + let additionalCiphertextBytes = ciphertextSize % ciphertextChunkSize + guard additionalCiphertextBytes == 0 || additionalCiphertextBytes > overheadPerChunk else { + throw CryptoError.invalidParameter("Method not defined for input value \(ciphertextSize)") + } + let additionalCleartextBytes = (additionalCiphertextBytes == 0) ? 0 : additionalCiphertextBytes - overheadPerChunk + assert(additionalCleartextBytes >= 0) + return cleartextChunkSize * numFullChunks + additionalCleartextBytes + } +} diff --git a/Sources/CryptomatorCryptoLib/Info.plist b/Sources/CryptomatorCryptoLib/Info.plist new file mode 100644 index 0000000..c0701c6 --- /dev/null +++ b/Sources/CryptomatorCryptoLib/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/Sources/CryptomatorCryptoLib/Masterkey.swift b/Sources/CryptomatorCryptoLib/Masterkey.swift new file mode 100644 index 0000000..230a405 --- /dev/null +++ b/Sources/CryptomatorCryptoLib/Masterkey.swift @@ -0,0 +1,59 @@ +// +// Masterkey.swift +// CryptomatorCryptoLib +// +// Created by Sebastian Stenzel on 25.04.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import CommonCrypto +import Foundation + +public class Masterkey { + private(set) var aesMasterKey: [UInt8] + private(set) var macMasterKey: [UInt8] + public var rawKey: [UInt8] { + return aesMasterKey + macMasterKey + } + + private init(aesMasterKey: [UInt8], macMasterKey: [UInt8]) { + self.aesMasterKey = aesMasterKey + self.macMasterKey = macMasterKey + } + + deinit { + for i in 0 ..< aesMasterKey.count { + aesMasterKey[i] = 0 + } + for i in 0 ..< macMasterKey.count { + macMasterKey[i] = 0 + } + } + + // MARK: - Factory + + /** + Creates new masterkey. + + - Returns: New masterkey instance with secure random bytes. + */ + public static func createNew() throws -> Masterkey { + let cryptoSupport = CryptoSupport() + let aesMasterKey = try cryptoSupport.createRandomBytes(size: kCCKeySizeAES256) + let macMasterKey = try cryptoSupport.createRandomBytes(size: kCCKeySizeAES256) + return createFromRaw(aesMasterKey: aesMasterKey, macMasterKey: macMasterKey) + } + + /** + Creates masterkey from raw bytes. + + - Parameter aesMasterKey: Key used for encryption of file specific keys. + - Parameter macMasterKey: Key used for file authentication. + - Returns: New masterkey instance using the keys from the supplied raw bytes. + */ + public static func createFromRaw(aesMasterKey: [UInt8], macMasterKey: [UInt8]) -> Masterkey { + assert(aesMasterKey.count == kCCKeySizeAES256) + assert(macMasterKey.count == kCCKeySizeAES256) + return Masterkey(aesMasterKey: aesMasterKey, macMasterKey: macMasterKey) + } +} diff --git a/Sources/CryptomatorCryptoLib/MasterkeyFile.swift b/Sources/CryptomatorCryptoLib/MasterkeyFile.swift new file mode 100644 index 0000000..19aaa31 --- /dev/null +++ b/Sources/CryptomatorCryptoLib/MasterkeyFile.swift @@ -0,0 +1,229 @@ +// +// MasterkeyFile.swift +// CryptomatorCryptoLib +// +// Created by Tobias Hagemann on 15.12.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import CommonCrypto +import Foundation +import scrypt + +struct Content: Codable, Equatable { + let version: Int + let scryptSalt: String + let scryptCostParam: Int + let scryptBlockSize: Int + let primaryMasterKey: String + let hmacMasterKey: String + let versionMac: String +} + +public enum MasterkeyFileError: Error, Equatable { + case malformedMasterkeyFile(_ reason: String) + case invalidPassphrase + case keyDerivationFailed + case keyWrapFailed(_ status: CCCryptorStatus) +} + +public class MasterkeyFile { + public static let defaultScryptCostParam = 1 << 15 // 2^15 + static let defaultScryptSaltSize = 8 + static let defaultScryptBlockSize = 8 + + let content: Content + public var version: Int { + return content.version + } + + init(content: Content) { + self.content = content + } + + // MARK: - Factory + + /** + Creates masterkey file with content provided from URL. + + - Parameter url: The URL to the masterkey file that is formatted in JSON. + - Returns: New masterkey instance using the keys from the supplied `url`. + */ + public static func withContentFromURL(url: URL) throws -> MasterkeyFile { + let data = try Data(contentsOf: url) + return try withContentFromData(data: data) + } + + /** + Creates masterkey file with content provided from JSON data. + + - Parameter data: The JSON representation of the masterkey file. + - Returns: New masterkey instance using the keys from the supplied `data`. + */ + public static func withContentFromData(data: Data) throws -> MasterkeyFile { + let decoded = try JSONDecoder().decode(Content.self, from: data) + return MasterkeyFile(content: decoded) + } + + // MARK: - Actions + + /** + Derives a KEK from the given passphrase and the params from this masterkey file using scrypt and unwraps the stored encryption and MAC keys. + + - Parameter passphrase: The passphrase used during key derivation. + - Parameter pepper: An optional application-specific pepper added to the scrypt's salt. Defaults to empty byte array. + - Returns: A masterkey with the unwrapped keys. + */ + public func unlock(passphrase: String, pepper: [UInt8] = [UInt8]()) throws -> Masterkey { + let kek = try deriveKey(passphrase: passphrase, pepper: pepper) + return try unlock(kek: kek) + } + + /** + Derives a KEK from the given passphrase and the params from this masterkey file using scrypt. + + - Parameter passphrase: The passphrase used during key derivation. + - Parameter pepper: An optional application-specific pepper added to the scrypt's salt. Defaults to empty byte array. + - Returns: A 256-bit key derived from passphrase using scrypt. + */ + public func deriveKey(passphrase: String, pepper: [UInt8] = [UInt8]()) throws -> [UInt8] { + let pw = [UInt8](passphrase.precomposedStringWithCanonicalMapping.utf8) + let salt = [UInt8](Data(base64Encoded: content.scryptSalt)!) + var kek = [UInt8](repeating: 0x00, count: kCCKeySizeAES256) + let scryptResult = crypto_scrypt(pw, pw.count, salt + pepper, salt.count + pepper.count, UInt64(content.scryptCostParam), UInt32(content.scryptBlockSize), 1, &kek, kCCKeySizeAES256) + guard scryptResult == 0 else { + throw MasterkeyFileError.keyDerivationFailed + } + return kek + } + + /** + Unwraps the stored encryption and MAC keys with the given KEK. + + - Parameter kek: The KEK for unwrapping the keys from this masterkey file. + - Returns: A masterkey with the unwrapped keys. + */ + public func unlock(kek: [UInt8]) throws -> Masterkey { + guard let wrappedMasterKey = Data(base64Encoded: content.primaryMasterKey) else { + throw MasterkeyFileError.malformedMasterkeyFile("invalid base64 data in primaryMasterKey") + } + let aesKey = try MasterkeyFile.unwrapKey([UInt8](wrappedMasterKey), kek: kek) + guard let wrappedHmacKey = Data(base64Encoded: content.hmacMasterKey) else { + throw MasterkeyFileError.malformedMasterkeyFile("invalid base64 data in hmacMasterKey") + } + let macKey = try MasterkeyFile.unwrapKey([UInt8](wrappedHmacKey), kek: kek) + + // check MAC: + try checkVaultVersion(macKey: macKey) + + // construct key: + return Masterkey.createFromRaw(aesMasterKey: aesKey, macMasterKey: macKey) + } + + private func checkVaultVersion(macKey: [UInt8]) throws { + guard let storedVersionMac = Data(base64Encoded: content.versionMac), storedVersionMac.count == CC_SHA256_DIGEST_LENGTH else { + throw MasterkeyFileError.malformedMasterkeyFile("invalid base64 data in versionMac") + } + var calculatedVersionMac = [UInt8](repeating: 0x00, count: Int(CC_SHA256_DIGEST_LENGTH)) + let versionBytes = withUnsafeBytes(of: UInt32(version).bigEndian, Array.init) + CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), macKey, macKey.count, versionBytes, versionBytes.count, &calculatedVersionMac) + var diff: UInt8 = 0x00 + for i in 0 ..< calculatedVersionMac.count { + diff |= calculatedVersionMac[i] ^ storedVersionMac[i] + } + if diff != 0x00 { + throw MasterkeyFileError.malformedMasterkeyFile("incorrect version or versionMac") + } + } + + /** + Derives a KEK from the given passphrase and wraps the key material from `masterkey`. + Then serializes the encrypted keys as well as used key derivation parameters into a JSON representation that can be stored into a masterkey file. + + - Parameter masterkey: The key to protect. + - Parameter vaultVersion: The vault version that should be stored in this masterkey file (for downwards compatibility). + - Parameter passphrase: The passphrase used during key derivation. + - Parameter pepper: An optional application-specific pepper added to the scrypt's salt. Defaults to empty byte array. + - Parameter scryptCostParam: The work factor for the key derivation function (scrypt). Defaults to 32768. + - Returns: A JSON representation of the encrypted masterkey with its key derivation parameters. + */ + public static func lock(masterkey: Masterkey, vaultVersion: Int, passphrase: String, pepper: [UInt8] = [UInt8](), scryptCostParam: Int = defaultScryptCostParam) throws -> Data { + let content: Content = try lock(masterkey: masterkey, vaultVersion: vaultVersion, passphrase: passphrase, pepper: pepper, scryptCostParam: scryptCostParam) + return try JSONEncoder().encode(content) + } + + static func lock(masterkey: Masterkey, vaultVersion: Int, passphrase: String, pepper: [UInt8], scryptCostParam: Int, cryptoSupport: CryptoSupport = CryptoSupport()) throws -> Content { + let pw = [UInt8](passphrase.precomposedStringWithCanonicalMapping.utf8) + let salt = try cryptoSupport.createRandomBytes(size: defaultScryptSaltSize) + var kek = [UInt8](repeating: 0x00, count: kCCKeySizeAES256) + let scryptResult = crypto_scrypt(pw, pw.count, salt + pepper, salt.count + pepper.count, UInt64(scryptCostParam), UInt32(defaultScryptBlockSize), 1, &kek, kCCKeySizeAES256) + guard scryptResult == 0 else { + throw MasterkeyFileError.keyDerivationFailed + } + + let wrappedMasterKey = try wrapKey(masterkey.aesMasterKey, kek: kek) + let wrappedHmacKey = try wrapKey(masterkey.macMasterKey, kek: kek) + + var versionMac = [UInt8](repeating: 0x00, count: Int(CC_SHA256_DIGEST_LENGTH)) + let versionBytes = withUnsafeBytes(of: UInt32(vaultVersion).bigEndian, Array.init) + CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), masterkey.macMasterKey, masterkey.macMasterKey.count, versionBytes, versionBytes.count, &versionMac) + + return Content( + version: vaultVersion, + scryptSalt: Data(salt).base64EncodedString(), + scryptCostParam: scryptCostParam, + scryptBlockSize: defaultScryptBlockSize, + primaryMasterKey: Data(wrappedMasterKey).base64EncodedString(), + hmacMasterKey: Data(wrappedHmacKey).base64EncodedString(), + versionMac: Data(versionMac).base64EncodedString() + ) + } + + /** + Re-encrypts a masterkey with a new passphrase. + + - Parameter masterkeyFileData: The original JSON representation of the masterkey. + - Parameter oldPassphrase: The old passphrase. + - Parameter newPassphrase: The new passphrase + - Parameter pepper: An optional application-specific pepper added to the scrypt's salt. Defaults to empty byte array. + - Parameter scryptCostParam: The work factor for the key derivation function (scrypt). Defaults to 32768. + - Returns: A JSON representation of the masterkey, now encrypted with `newPassphrase`. + */ + public static func changePassphrase(masterkeyFileData: Data, oldPassphrase: String, newPassphrase: String, pepper: [UInt8] = [UInt8](), scryptCostParam: Int = defaultScryptCostParam) throws -> Data { + let content: Content = try changePassphrase(masterkeyFileData: masterkeyFileData, oldPassphrase: oldPassphrase, newPassphrase: newPassphrase, pepper: pepper, scryptCostParam: scryptCostParam) + return try JSONEncoder().encode(content) + } + + static func changePassphrase(masterkeyFileData: Data, oldPassphrase: String, newPassphrase: String, pepper: [UInt8], scryptCostParam: Int, cryptoSupport: CryptoSupport = CryptoSupport()) throws -> Content { + let masterkeyFile = try MasterkeyFile.withContentFromData(data: masterkeyFileData) + let masterkey = try masterkeyFile.unlock(passphrase: oldPassphrase, pepper: pepper) + return try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: masterkeyFile.version, passphrase: newPassphrase, pepper: pepper, scryptCostParam: scryptCostParam, cryptoSupport: cryptoSupport) + } + + // MARK: - RFC 3394 Key Wrapping + + static func wrapKey(_ rawKey: [UInt8], kek: [UInt8]) throws -> [UInt8] { + var wrappedKeyLen = CCSymmetricWrappedSize(CCWrappingAlgorithm(kCCWRAPAES), rawKey.count) + var wrappedKey = [UInt8](repeating: 0x00, count: wrappedKeyLen) + let status = CCSymmetricKeyWrap(CCWrappingAlgorithm(kCCWRAPAES), CCrfc3394_iv, CCrfc3394_ivLen, kek, kek.count, rawKey, rawKey.count, &wrappedKey, &wrappedKeyLen) + if status == kCCSuccess { + return wrappedKey + } else { + throw MasterkeyFileError.keyWrapFailed(status) + } + } + + static func unwrapKey(_ wrappedKey: [UInt8], kek: [UInt8]) throws -> [UInt8] { + var unwrappedKeyLen = CCSymmetricUnwrappedSize(CCWrappingAlgorithm(kCCWRAPAES), wrappedKey.count) + var unwrappedKey = [UInt8](repeating: 0x00, count: unwrappedKeyLen) + let status = CCSymmetricKeyUnwrap(CCWrappingAlgorithm(kCCWRAPAES), CCrfc3394_iv, CCrfc3394_ivLen, kek, kek.count, wrappedKey, wrappedKey.count, &unwrappedKey, &unwrappedKeyLen) + if status == kCCSuccess { + assert(unwrappedKeyLen == kCCKeySizeAES256) + return unwrappedKey + } else if status == kCCDecodeError { + throw MasterkeyFileError.invalidPassphrase + } else { + throw MasterkeyFileError.keyWrapFailed(status) + } + } +} diff --git a/CryptoLib/Info.plist b/Sources/scrypt/Info.plist similarity index 100% rename from CryptoLib/Info.plist rename to Sources/scrypt/Info.plist diff --git a/Sources/scrypt/crypto_scrypt.c b/Sources/scrypt/crypto_scrypt.c new file mode 100644 index 0000000..28e0ebc --- /dev/null +++ b/Sources/scrypt/crypto_scrypt.c @@ -0,0 +1,283 @@ +/*- + * Copyright 2009 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file was originally written by Colin Percival as part of the Tarsnap + * online backup system. + */ +#include +#include +#include +#include + +#include "include/sha256.h" +#include "include/sysendian.h" + +#include "include/crypto_scrypt.h" + +static void blkcpy(uint8_t *, uint8_t *, size_t); +static void blkxor(uint8_t *, uint8_t *, size_t); +static void salsa20_8(uint8_t[64]); +static void blockmix_salsa8(uint8_t *, uint8_t *, size_t); +static uint64_t integerify(uint8_t *, size_t); +static void smix(uint8_t *, size_t, uint64_t, uint8_t *, uint8_t *); + +static void +blkcpy(uint8_t * dest, uint8_t * src, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + dest[i] = src[i]; +} + +static void +blkxor(uint8_t * dest, uint8_t * src, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + dest[i] ^= src[i]; +} + +/** + * salsa20_8(B): + * Apply the salsa20/8 core to the provided block. + */ +static void +salsa20_8(uint8_t B[64]) +{ + uint32_t B32[16]; + uint32_t x[16]; + size_t i; + + /* Convert little-endian values in. */ + for (i = 0; i < 16; i++) + B32[i] = le32dec(&B[i * 4]); + + /* Compute x = doubleround^4(B32). */ + for (i = 0; i < 16; i++) + x[i] = B32[i]; + for (i = 0; i < 8; i += 2) { +#define R(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) + /* Operate on columns. */ + x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); + x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); + + x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); + x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); + + x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); + x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); + + x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); + x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); + + /* Operate on rows. */ + x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); + x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); + + x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); + x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); + + x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); + x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); + + x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); + x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); +#undef R + } + + /* Compute B32 = B32 + x. */ + for (i = 0; i < 16; i++) + B32[i] += x[i]; + + /* Convert little-endian values out. */ + for (i = 0; i < 16; i++) + le32enc(&B[4 * i], B32[i]); +} + +/** + * blockmix_salsa8(B, Y, r): + * Compute B = BlockMix_{salsa20/8, r}(B). The input B must be 128r bytes in + * length; the temporary space Y must also be the same size. + */ +static void +blockmix_salsa8(uint8_t * B, uint8_t * Y, size_t r) +{ + uint8_t X[64]; + size_t i; + + /* 1: X <-- B_{2r - 1} */ + blkcpy(X, &B[(2 * r - 1) * 64], 64); + + /* 2: for i = 0 to 2r - 1 do */ + for (i = 0; i < 2 * r; i++) { + /* 3: X <-- H(X \xor B_i) */ + blkxor(X, &B[i * 64], 64); + salsa20_8(X); + + /* 4: Y_i <-- X */ + blkcpy(&Y[i * 64], X, 64); + } + + /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ + for (i = 0; i < r; i++) + blkcpy(&B[i * 64], &Y[(i * 2) * 64], 64); + for (i = 0; i < r; i++) + blkcpy(&B[(i + r) * 64], &Y[(i * 2 + 1) * 64], 64); +} + +/** + * integerify(B, r): + * Return the result of parsing B_{2r-1} as a little-endian integer. + */ +static uint64_t +integerify(uint8_t * B, size_t r) +{ + uint8_t * X = &B[(2 * r - 1) * 64]; + + return (le64dec(X)); +} + +/** + * smix(B, r, N, V, XY): + * Compute B = SMix_r(B, N). The input B must be 128r bytes in length; the + * temporary storage V must be 128rN bytes in length; the temporary storage + * XY must be 256r bytes in length. The value N must be a power of 2. + */ +static void +smix(uint8_t * B, size_t r, uint64_t N, uint8_t * V, uint8_t * XY) +{ + uint8_t * X = XY; + uint8_t * Y = &XY[128 * r]; + uint64_t i; + uint64_t j; + + /* 1: X <-- B */ + blkcpy(X, B, 128 * r); + + /* 2: for i = 0 to N - 1 do */ + for (i = 0; i < N; i++) { + /* 3: V_i <-- X */ + blkcpy(&V[i * (128 * r)], X, 128 * r); + + /* 4: X <-- H(X) */ + blockmix_salsa8(X, Y, r); + } + + /* 6: for i = 0 to N - 1 do */ + for (i = 0; i < N; i++) { + /* 7: j <-- Integerify(X) mod N */ + j = integerify(X, r) & (N - 1); + + /* 8: X <-- H(X \xor V_j) */ + blkxor(X, &V[j * (128 * r)], 128 * r); + blockmix_salsa8(X, Y, r); + } + + /* 10: B' <-- X */ + blkcpy(B, X, 128 * r); +} + +/** + * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen): + * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, + * p, buflen) and write the result into buf. The parameters r, p, and buflen + * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32. The parameter N + * must be a power of 2. + * + * Return 0 on success; or -1 on error. + */ +int +crypto_scrypt(const uint8_t * passwd, size_t passwdlen, + const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t _r, uint32_t _p, + uint8_t * buf, size_t buflen) +{ + uint8_t * B; + uint8_t * V; + uint8_t * XY; + size_t r = _r, p = _p; + uint32_t i; + + /* Sanity-check parameters. */ +#if SIZE_MAX > UINT32_MAX + if (buflen > (((uint64_t)(1) << 32) - 1) * 32) { + errno = EFBIG; + goto err0; + } +#endif + if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) { + errno = EFBIG; + goto err0; + } + if (((N & (N - 1)) != 0) || (N == 0)) { + errno = EINVAL; + goto err0; + } + if ((r > SIZE_MAX / 128 / p) || +#if SIZE_MAX / 256 <= UINT32_MAX + (r > SIZE_MAX / 256) || +#endif + (N > SIZE_MAX / 128 / r)) { + errno = ENOMEM; + goto err0; + } + + /* Allocate memory. */ + if ((B = malloc(128 * r * p)) == NULL) + goto err0; + if ((XY = malloc(256 * r)) == NULL) + goto err1; + if ((V = malloc(128 * r * N)) == NULL) + goto err2; + + /* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */ + PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, p * 128 * r); + + /* 2: for i = 0 to p - 1 do */ + for (i = 0; i < p; i++) { + /* 3: B_i <-- MF(B_i, N) */ + smix(&B[i * 128 * r], r, N, V, XY); + } + + /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ + PBKDF2_SHA256(passwd, passwdlen, B, p * 128 * r, 1, buf, buflen); + + /* Free memory. */ + free(V); + free(XY); + free(B); + + /* Success! */ + return (0); + +err2: + free(XY); +err1: + free(B); +err0: + /* Failure! */ + return (-1); +} diff --git a/Sources/scrypt/include/crypto_scrypt.h b/Sources/scrypt/include/crypto_scrypt.h new file mode 100644 index 0000000..7c7ddb6 --- /dev/null +++ b/Sources/scrypt/include/crypto_scrypt.h @@ -0,0 +1,47 @@ +/*- + * Copyright 2009 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file was originally written by Colin Percival as part of the Tarsnap + * online backup system. + */ +#ifndef _CRYPTO_SCRYPT_H_ +#define _CRYPTO_SCRYPT_H_ + +#include +#include + +/** + * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen): + * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, + * p, buflen) and write the result into buf. The parameters r, p, and buflen + * must satisfy 0 < r * p < 2^30 and buflen <= (2^32 - 1) * 32. The parameter + * N must be a power of 2 greater than 1. + * + * Return 0 on success; or -1 on error. + */ +int crypto_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, uint64_t, + uint32_t, uint32_t, uint8_t *, size_t); + +#endif /* !_CRYPTO_SCRYPT_H_ */ diff --git a/Sources/scrypt/include/insecure_memzero.h b/Sources/scrypt/include/insecure_memzero.h new file mode 100644 index 0000000..9aca8fc --- /dev/null +++ b/Sources/scrypt/include/insecure_memzero.h @@ -0,0 +1,63 @@ +/*- + * Copyright 2005-2020 Colin Percival. All rights reserved. + * Copyright 2011-2020 Tarsnap Backup Inc. All rights reserved. + * Copyright 2014 Sean Kelly. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef _INSECURE_MEMZERO_H_ +#define _INSECURE_MEMZERO_H_ + +#include + +/* Pointer to memory-zeroing function. */ +extern void (* volatile insecure_memzero_ptr)(volatile void *, size_t); + +/** + * insecure_memzero(buf, len): + * Attempt to zero ${len} bytes at ${buf} in spite of optimizing compilers' + * best (standards-compliant) attempts to remove the buffer-zeroing. In + * particular, to avoid performing the zeroing, a compiler would need to + * use optimistic devirtualization; recognize that non-volatile objects do not + * need to be treated as volatile, even if they are accessed via volatile + * qualified pointers; and perform link-time optimization; in addition to the + * dead-code elimination which often causes buffer-zeroing to be elided. + * + * Note however that zeroing a buffer does not guarantee that the data held + * in the buffer is not stored elsewhere; in particular, there may be copies + * held in CPU registers or in anonymous allocations on the stack, even if + * every named variable is successfully sanitized. Solving the "wipe data + * from the system" problem will require a C language extension which does not + * yet exist. + * + * For more information, see: + * http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html + * http://www.daemonology.net/blog/2014-09-06-zeroing-buffers-is-insufficient.html + */ +static inline void +insecure_memzero(volatile void * buf, size_t len) +{ + + (insecure_memzero_ptr)(buf, len); +} + +#endif /* !_INSECURE_MEMZERO_H_ */ diff --git a/Sources/scrypt/include/sha256.h b/Sources/scrypt/include/sha256.h new file mode 100644 index 0000000..b58d33e --- /dev/null +++ b/Sources/scrypt/include/sha256.h @@ -0,0 +1,121 @@ +/*- + * Copyright 2005-2020 Colin Percival. All rights reserved. + * Copyright 2011-2020 Tarsnap Backup Inc. All rights reserved. + * Copyright 2014 Sean Kelly. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef _SHA256_H_ +#define _SHA256_H_ + +#include +#include + +/* + * Use #defines in order to avoid namespace collisions with anyone else's + * SHA256 code (e.g., the code in OpenSSL). + */ +#define SHA256_Init libcperciva_SHA256_Init +#define SHA256_Update libcperciva_SHA256_Update +#define SHA256_Final libcperciva_SHA256_Final +#define SHA256_Buf libcperciva_SHA256_Buf +#define SHA256_CTX libcperciva_SHA256_CTX +#define HMAC_SHA256_Init libcperciva_HMAC_SHA256_Init +#define HMAC_SHA256_Update libcperciva_HMAC_SHA256_Update +#define HMAC_SHA256_Final libcperciva_HMAC_SHA256_Final +#define HMAC_SHA256_Buf libcperciva_HMAC_SHA256_Buf +#define HMAC_SHA256_CTX libcperciva_HMAC_SHA256_CTX + +/* Context structure for SHA256 operations. */ +typedef struct { + uint32_t state[8]; + uint64_t count; + uint8_t buf[64]; +} SHA256_CTX; + +/** + * SHA256_Init(ctx): + * Initialize the SHA256 context ${ctx}. + */ +void SHA256_Init(SHA256_CTX *); + +/** + * SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the SHA256 context ${ctx}. + */ +void SHA256_Update(SHA256_CTX *, const void *, size_t); + +/** + * SHA256_Final(digest, ctx): + * Output the SHA256 hash of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +void SHA256_Final(uint8_t[32], SHA256_CTX *); + +/** + * SHA256_Buf(in, len, digest): + * Compute the SHA256 hash of ${len} bytes from ${in} and write it to ${digest}. + */ +void SHA256_Buf(const void *, size_t, uint8_t[32]); + +/* Context structure for HMAC-SHA256 operations. */ +typedef struct { + SHA256_CTX ictx; + SHA256_CTX octx; +} HMAC_SHA256_CTX; + +/** + * HMAC_SHA256_Init(ctx, K, Klen): + * Initialize the HMAC-SHA256 context ${ctx} with ${Klen} bytes of key from + * ${K}. + */ +void HMAC_SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t); + +/** + * HMAC_SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the HMAC-SHA256 context ${ctx}. + */ +void HMAC_SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t); + +/** + * HMAC_SHA256_Final(digest, ctx): + * Output the HMAC-SHA256 of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +void HMAC_SHA256_Final(uint8_t[32], HMAC_SHA256_CTX *); + +/** + * HMAC_SHA256_Buf(K, Klen, in, len, digest): + * Compute the HMAC-SHA256 of ${len} bytes from ${in} using the key ${K} of + * length ${Klen}, and write the result to ${digest}. + */ +void HMAC_SHA256_Buf(const void *, size_t, const void *, size_t, uint8_t[32]); + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t, + uint64_t, uint8_t *, size_t); + +#endif /* !_SHA256_H_ */ diff --git a/Sources/scrypt/include/sysendian.h b/Sources/scrypt/include/sysendian.h new file mode 100644 index 0000000..9b1436b --- /dev/null +++ b/Sources/scrypt/include/sysendian.h @@ -0,0 +1,172 @@ +/*- + * Copyright 2005-2020 Colin Percival. All rights reserved. + * Copyright 2011-2020 Tarsnap Backup Inc. All rights reserved. + * Copyright 2014 Sean Kelly. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef _SYSENDIAN_H_ +#define _SYSENDIAN_H_ + +#include + +/* Avoid namespace collisions with BSD . */ +#define be16dec libcperciva_be16dec +#define be16enc libcperciva_be16enc +#define be32dec libcperciva_be32dec +#define be32enc libcperciva_be32enc +#define be64dec libcperciva_be64dec +#define be64enc libcperciva_be64enc +#define le16dec libcperciva_le16dec +#define le16enc libcperciva_le16enc +#define le32dec libcperciva_le32dec +#define le32enc libcperciva_le32enc +#define le64dec libcperciva_le64dec +#define le64enc libcperciva_le64enc + +static inline uint16_t +be16dec(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return (uint16_t)((uint16_t)(p[1]) + ((uint16_t)(p[0]) << 8)); +} + +static inline void +be16enc(void * pp, uint16_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[1] = x & 0xff; + p[0] = (x >> 8) & 0xff; +} + +static inline uint32_t +be32dec(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) + + ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24)); +} + +static inline void +be32enc(void * pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[3] = x & 0xff; + p[2] = (x >> 8) & 0xff; + p[1] = (x >> 16) & 0xff; + p[0] = (x >> 24) & 0xff; +} + +static inline uint64_t +be64dec(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return ((uint64_t)(p[7]) + ((uint64_t)(p[6]) << 8) + + ((uint64_t)(p[5]) << 16) + ((uint64_t)(p[4]) << 24) + + ((uint64_t)(p[3]) << 32) + ((uint64_t)(p[2]) << 40) + + ((uint64_t)(p[1]) << 48) + ((uint64_t)(p[0]) << 56)); +} + +static inline void +be64enc(void * pp, uint64_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[7] = x & 0xff; + p[6] = (x >> 8) & 0xff; + p[5] = (x >> 16) & 0xff; + p[4] = (x >> 24) & 0xff; + p[3] = (x >> 32) & 0xff; + p[2] = (x >> 40) & 0xff; + p[1] = (x >> 48) & 0xff; + p[0] = (x >> 56) & 0xff; +} + +static inline uint16_t +le16dec(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return (uint16_t)((uint16_t)(p[0]) + ((uint16_t)(p[1]) << 8)); +} + +static inline void +le16enc(void * pp, uint16_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; +} + +static inline uint32_t +le32dec(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) + + ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24)); +} + +static inline void +le32enc(void * pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; + p[2] = (x >> 16) & 0xff; + p[3] = (x >> 24) & 0xff; +} + +static inline uint64_t +le64dec(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return ((uint64_t)(p[0]) + ((uint64_t)(p[1]) << 8) + + ((uint64_t)(p[2]) << 16) + ((uint64_t)(p[3]) << 24) + + ((uint64_t)(p[4]) << 32) + ((uint64_t)(p[5]) << 40) + + ((uint64_t)(p[6]) << 48) + ((uint64_t)(p[7]) << 56)); +} + +static inline void +le64enc(void * pp, uint64_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; + p[2] = (x >> 16) & 0xff; + p[3] = (x >> 24) & 0xff; + p[4] = (x >> 32) & 0xff; + p[5] = (x >> 40) & 0xff; + p[6] = (x >> 48) & 0xff; + p[7] = (x >> 56) & 0xff; +} + +#endif /* !_SYSENDIAN_H_ */ diff --git a/Sources/scrypt/insecure_memzero.c b/Sources/scrypt/insecure_memzero.c new file mode 100644 index 0000000..b8a19d9 --- /dev/null +++ b/Sources/scrypt/insecure_memzero.c @@ -0,0 +1,45 @@ +/*- + * Copyright 2005-2020 Colin Percival. All rights reserved. + * Copyright 2011-2020 Tarsnap Backup Inc. All rights reserved. + * Copyright 2014 Sean Kelly. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include +#include + +#include "include/insecure_memzero.h" + +/* Function which does the zeroing. */ +static void +insecure_memzero_func(volatile void * buf, size_t len) +{ + volatile uint8_t * _buf = buf; + size_t i; + + for (i = 0; i < len; i++) + _buf[i] = 0; +} + +/* Pointer to memory-zeroing function. */ +void (* volatile insecure_memzero_ptr)(volatile void *, size_t) = + insecure_memzero_func; diff --git a/Sources/scrypt/scrypt.h b/Sources/scrypt/scrypt.h new file mode 100644 index 0000000..ecf2124 --- /dev/null +++ b/Sources/scrypt/scrypt.h @@ -0,0 +1,18 @@ +// +// scrypt.h +// scrypt +// +// Created by Tobias Hagemann on 15.01.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +#import + +//! Project version number for scrypt. +FOUNDATION_EXPORT double scryptVersionNumber; + +//! Project version string for scrypt. +FOUNDATION_EXPORT const unsigned char scryptVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import +#import diff --git a/Sources/scrypt/sha256.c b/Sources/scrypt/sha256.c new file mode 100644 index 0000000..012310a --- /dev/null +++ b/Sources/scrypt/sha256.c @@ -0,0 +1,556 @@ +/*- + * Copyright 2005-2020 Colin Percival. All rights reserved. + * Copyright 2011-2020 Tarsnap Backup Inc. All rights reserved. + * Copyright 2014 Sean Kelly. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include +#include +#include + +#include "include/insecure_memzero.h" +#include "include/sysendian.h" + +#include "include/sha256.h" + +/* + * Encode a length len/4 vector of (uint32_t) into a length len vector of + * (uint8_t) in big-endian form. Assumes len is a multiple of 4. + */ +static void +be32enc_vect(uint8_t * dst, const uint32_t * src, size_t len) +{ + size_t i; + + /* Sanity-check. */ + assert(len % 4 == 0); + + /* Encode vector, one word at a time. */ + for (i = 0; i < len / 4; i++) + be32enc(dst + i * 4, src[i]); +} + +/* + * Decode a big-endian length len vector of (uint8_t) into a length + * len/4 vector of (uint32_t). Assumes len is a multiple of 4. + */ +static void +be32dec_vect(uint32_t * dst, const uint8_t * src, size_t len) +{ + size_t i; + + /* Sanity-check. */ + assert(len % 4 == 0); + + /* Decode vector, one word at a time. */ + for (i = 0; i < len / 4; i++) + dst[i] = be32dec(src + i * 4); +} + +/* SHA256 round constants. */ +static const uint32_t Krnd[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/* Magic initialization constants. */ +static const uint32_t initial_state[8] = { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 +}; + +/* Elementary functions used by SHA256 */ +#define Ch(x, y, z) ((x & (y ^ z)) ^ z) +#define Maj(x, y, z) ((x & (y | z)) | (y & z)) +#define SHR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << (32 - n))) +#define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +/* SHA256 round function */ +#define RND(a, b, c, d, e, f, g, h, k) \ + h += S1(e) + Ch(e, f, g) + k; \ + d += h; \ + h += S0(a) + Maj(a, b, c) + +/* Adjusted round function for rotating state */ +#define RNDr(S, W, i, ii) \ + RND(S[(64 - i) % 8], S[(65 - i) % 8], \ + S[(66 - i) % 8], S[(67 - i) % 8], \ + S[(68 - i) % 8], S[(69 - i) % 8], \ + S[(70 - i) % 8], S[(71 - i) % 8], \ + W[i + ii] + Krnd[i + ii]) + +/* Message schedule computation */ +#define MSCH(W, ii, i) \ + W[i + ii + 16] = s1(W[i + ii + 14]) + W[i + ii + 9] + s0(W[i + ii + 1]) + W[i + ii] + +/* + * SHA256 block compression function. The 256-bit state is transformed via + * the 512-bit input block to produce a new state. + */ +static void +SHA256_Transform(uint32_t state[static restrict 8], + const uint8_t block[static restrict 64], + uint32_t W[static restrict 64], uint32_t S[static restrict 8]) +{ + int i; + + /* 1. Prepare the first part of the message schedule W. */ + be32dec_vect(W, block, 64); + + /* 2. Initialize working variables. */ + memcpy(S, state, 32); + + /* 3. Mix. */ + for (i = 0; i < 64; i += 16) { + RNDr(S, W, 0, i); + RNDr(S, W, 1, i); + RNDr(S, W, 2, i); + RNDr(S, W, 3, i); + RNDr(S, W, 4, i); + RNDr(S, W, 5, i); + RNDr(S, W, 6, i); + RNDr(S, W, 7, i); + RNDr(S, W, 8, i); + RNDr(S, W, 9, i); + RNDr(S, W, 10, i); + RNDr(S, W, 11, i); + RNDr(S, W, 12, i); + RNDr(S, W, 13, i); + RNDr(S, W, 14, i); + RNDr(S, W, 15, i); + + if (i == 48) + break; + MSCH(W, 0, i); + MSCH(W, 1, i); + MSCH(W, 2, i); + MSCH(W, 3, i); + MSCH(W, 4, i); + MSCH(W, 5, i); + MSCH(W, 6, i); + MSCH(W, 7, i); + MSCH(W, 8, i); + MSCH(W, 9, i); + MSCH(W, 10, i); + MSCH(W, 11, i); + MSCH(W, 12, i); + MSCH(W, 13, i); + MSCH(W, 14, i); + MSCH(W, 15, i); + } + + /* 4. Mix local working variables into global state. */ + for (i = 0; i < 8; i++) + state[i] += S[i]; +} + +static const uint8_t PAD[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* Add padding and terminating bit-count. */ +static void +SHA256_Pad(SHA256_CTX * ctx, uint32_t tmp32[static restrict 72]) +{ + size_t r; + + /* Figure out how many bytes we have buffered. */ + r = (ctx->count >> 3) & 0x3f; + + /* Pad to 56 mod 64, transforming if we finish a block en route. */ + if (r < 56) { + /* Pad to 56 mod 64. */ + memcpy(&ctx->buf[r], PAD, 56 - r); + } else { + /* Finish the current block and mix. */ + memcpy(&ctx->buf[r], PAD, 64 - r); + SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]); + + /* The start of the final block is all zeroes. */ + memset(&ctx->buf[0], 0, 56); + } + + /* Add the terminating bit-count. */ + be64enc(&ctx->buf[56], ctx->count); + + /* Mix in the final block. */ + SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]); +} + +/** + * SHA256_Init(ctx): + * Initialize the SHA256 context ${ctx}. + */ +void +SHA256_Init(SHA256_CTX * ctx) +{ + + /* Zero bits processed so far. */ + ctx->count = 0; + + /* Initialize state. */ + memcpy(ctx->state, initial_state, sizeof(initial_state)); +} + +/** + * SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the SHA256 context ${ctx}. + */ +static void +_SHA256_Update(SHA256_CTX * ctx, const void * in, size_t len, + uint32_t tmp32[static restrict 72]) +{ + uint32_t r; + const uint8_t * src = in; + + /* Return immediately if we have nothing to do. */ + if (len == 0) + return; + + /* Number of bytes left in the buffer from previous updates. */ + r = (ctx->count >> 3) & 0x3f; + + /* Update number of bits. */ + ctx->count += (uint64_t)(len) << 3; + + /* Handle the case where we don't need to perform any transforms. */ + if (len < 64 - r) { + memcpy(&ctx->buf[r], src, len); + return; + } + + /* Finish the current block. */ + memcpy(&ctx->buf[r], src, 64 - r); + SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]); + src += 64 - r; + len -= 64 - r; + + /* Perform complete blocks. */ + while (len >= 64) { + SHA256_Transform(ctx->state, src, &tmp32[0], &tmp32[64]); + src += 64; + len -= 64; + } + + /* Copy left over data into buffer. */ + memcpy(ctx->buf, src, len); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +SHA256_Update(SHA256_CTX * ctx, const void * in, size_t len) +{ + uint32_t tmp32[72]; + + /* Call the real function. */ + _SHA256_Update(ctx, in, len, tmp32); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); +} + +/** + * SHA256_Final(digest, ctx): + * Output the SHA256 hash of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +static void +_SHA256_Final(uint8_t digest[32], SHA256_CTX * ctx, + uint32_t tmp32[static restrict 72]) +{ + + /* Add padding. */ + SHA256_Pad(ctx, tmp32); + + /* Write the hash. */ + be32enc_vect(digest, ctx->state, 32); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +SHA256_Final(uint8_t digest[32], SHA256_CTX * ctx) +{ + uint32_t tmp32[72]; + + /* Call the real function. */ + _SHA256_Final(digest, ctx, tmp32); + + /* Clear the context state. */ + insecure_memzero(ctx, sizeof(SHA256_CTX)); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); +} + +/** + * SHA256_Buf(in, len, digest): + * Compute the SHA256 hash of ${len} bytes from ${in} and write it to ${digest}. + */ +void +SHA256_Buf(const void * in, size_t len, uint8_t digest[32]) +{ + SHA256_CTX ctx; + uint32_t tmp32[72]; + + SHA256_Init(&ctx); + _SHA256_Update(&ctx, in, len, tmp32); + _SHA256_Final(digest, &ctx, tmp32); + + /* Clean the stack. */ + insecure_memzero(&ctx, sizeof(SHA256_CTX)); + insecure_memzero(tmp32, 288); +} + +/** + * HMAC_SHA256_Init(ctx, K, Klen): + * Initialize the HMAC-SHA256 context ${ctx} with ${Klen} bytes of key from + * ${K}. + */ +static void +_HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen, + uint32_t tmp32[static restrict 72], uint8_t pad[static restrict 64], + uint8_t khash[static restrict 32]) +{ + const uint8_t * K = _K; + size_t i; + + /* If Klen > 64, the key is really SHA256(K). */ + if (Klen > 64) { + SHA256_Init(&ctx->ictx); + _SHA256_Update(&ctx->ictx, K, Klen, tmp32); + _SHA256_Final(khash, &ctx->ictx, tmp32); + K = khash; + Klen = 32; + } + + /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */ + SHA256_Init(&ctx->ictx); + memset(pad, 0x36, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + _SHA256_Update(&ctx->ictx, pad, 64, tmp32); + + /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */ + SHA256_Init(&ctx->octx); + memset(pad, 0x5c, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + _SHA256_Update(&ctx->octx, pad, 64, tmp32); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen) +{ + uint32_t tmp32[72]; + uint8_t pad[64]; + uint8_t khash[32]; + + /* Call the real function. */ + _HMAC_SHA256_Init(ctx, _K, Klen, tmp32, pad, khash); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); + insecure_memzero(khash, 32); + insecure_memzero(pad, 64); +} + +/** + * HMAC_SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the HMAC-SHA256 context ${ctx}. + */ +static void +_HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void * in, size_t len, + uint32_t tmp32[static restrict 72]) +{ + + /* Feed data to the inner SHA256 operation. */ + _SHA256_Update(&ctx->ictx, in, len, tmp32); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void * in, size_t len) +{ + uint32_t tmp32[72]; + + /* Call the real function. */ + _HMAC_SHA256_Update(ctx, in, len, tmp32); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); +} + +/** + * HMAC_SHA256_Final(digest, ctx): + * Output the HMAC-SHA256 of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +static void +_HMAC_SHA256_Final(uint8_t digest[32], HMAC_SHA256_CTX * ctx, + uint32_t tmp32[static restrict 72], uint8_t ihash[static restrict 32]) +{ + + /* Finish the inner SHA256 operation. */ + _SHA256_Final(ihash, &ctx->ictx, tmp32); + + /* Feed the inner hash to the outer SHA256 operation. */ + _SHA256_Update(&ctx->octx, ihash, 32, tmp32); + + /* Finish the outer SHA256 operation. */ + _SHA256_Final(digest, &ctx->octx, tmp32); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +HMAC_SHA256_Final(uint8_t digest[32], HMAC_SHA256_CTX * ctx) +{ + uint32_t tmp32[72]; + uint8_t ihash[32]; + + /* Call the real function. */ + _HMAC_SHA256_Final(digest, ctx, tmp32, ihash); + + /* Clear the context state. */ + insecure_memzero(ctx, sizeof(HMAC_SHA256_CTX)); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); + insecure_memzero(ihash, 32); +} + +/** + * HMAC_SHA256_Buf(K, Klen, in, len, digest): + * Compute the HMAC-SHA256 of ${len} bytes from ${in} using the key ${K} of + * length ${Klen}, and write the result to ${digest}. + */ +void +HMAC_SHA256_Buf(const void * K, size_t Klen, const void * in, size_t len, + uint8_t digest[32]) +{ + HMAC_SHA256_CTX ctx; + uint32_t tmp32[72]; + uint8_t tmp8[96]; + + _HMAC_SHA256_Init(&ctx, K, Klen, tmp32, &tmp8[0], &tmp8[64]); + _HMAC_SHA256_Update(&ctx, in, len, tmp32); + _HMAC_SHA256_Final(digest, &ctx, tmp32, &tmp8[0]); + + /* Clean the stack. */ + insecure_memzero(&ctx, sizeof(HMAC_SHA256_CTX)); + insecure_memzero(tmp32, 288); + insecure_memzero(tmp8, 96); +} + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void +PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, + size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen) +{ + HMAC_SHA256_CTX Phctx, PShctx, hctx; + uint32_t tmp32[72]; + uint8_t tmp8[96]; + size_t i; + uint8_t ivec[4]; + uint8_t U[32]; + uint8_t T[32]; + uint64_t j; + int k; + size_t clen; + + /* Sanity-check. */ + assert(dkLen <= 32 * (size_t)(UINT32_MAX)); + + /* Compute HMAC state after processing P. */ + _HMAC_SHA256_Init(&Phctx, passwd, passwdlen, + tmp32, &tmp8[0], &tmp8[64]); + + /* Compute HMAC state after processing P and S. */ + memcpy(&PShctx, &Phctx, sizeof(HMAC_SHA256_CTX)); + _HMAC_SHA256_Update(&PShctx, salt, saltlen, tmp32); + + /* Iterate through the blocks. */ + for (i = 0; i * 32 < dkLen; i++) { + /* Generate INT(i + 1). */ + be32enc(ivec, (uint32_t)(i + 1)); + + /* Compute U_1 = PRF(P, S || INT(i)). */ + memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX)); + _HMAC_SHA256_Update(&hctx, ivec, 4, tmp32); + _HMAC_SHA256_Final(U, &hctx, tmp32, tmp8); + + /* T_i = U_1 ... */ + memcpy(T, U, 32); + + for (j = 2; j <= c; j++) { + /* Compute U_j. */ + memcpy(&hctx, &Phctx, sizeof(HMAC_SHA256_CTX)); + _HMAC_SHA256_Update(&hctx, U, 32, tmp32); + _HMAC_SHA256_Final(U, &hctx, tmp32, tmp8); + + /* ... xor U_j ... */ + for (k = 0; k < 32; k++) + T[k] ^= U[k]; + } + + /* Copy as many bytes as necessary into buf. */ + clen = dkLen - i * 32; + if (clen > 32) + clen = 32; + memcpy(&buf[i * 32], T, clen); + } + + /* Clean the stack. */ + insecure_memzero(&Phctx, sizeof(HMAC_SHA256_CTX)); + insecure_memzero(&PShctx, sizeof(HMAC_SHA256_CTX)); + insecure_memzero(&hctx, sizeof(HMAC_SHA256_CTX)); + insecure_memzero(tmp32, 288); + insecure_memzero(tmp8, 96); + insecure_memzero(U, 32); + insecure_memzero(T, 32); +} diff --git a/Tests/CryptomatorCryptoLibTests/AesCtrTests.swift b/Tests/CryptomatorCryptoLibTests/AesCtrTests.swift new file mode 100644 index 0000000..710b69f --- /dev/null +++ b/Tests/CryptomatorCryptoLibTests/AesCtrTests.swift @@ -0,0 +1,57 @@ +// +// AesCtrTests.swift +// CryptomatorCryptoLibTests +// +// Created by Sebastian Stenzel on 06.06.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import XCTest +@testable import CryptomatorCryptoLib + +// test vectors F5.5 and F5.6 from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf +class AesCtrTests: XCTestCase { + let key: [UInt8] = [ + 0x60, 0x3D, 0xEB, 0x10, 0x15, 0xCA, 0x71, 0xBE, + 0x2B, 0x73, 0xAE, 0xF0, 0x85, 0x7D, 0x77, 0x81, + 0x1F, 0x35, 0x2C, 0x07, 0x3B, 0x61, 0x08, 0xD7, + 0x2D, 0x98, 0x10, 0xA3, 0x09, 0x14, 0xDF, 0xF4 + ] + + let iv: [UInt8] = [ + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + ] + + let plaintext: [UInt8] = [ + 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, + 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A, + 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, + 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51, + 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, + 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF, + 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, + 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 + ] + + let ciphertext: [UInt8] = [ + 0x60, 0x1E, 0xC3, 0x13, 0x77, 0x57, 0x89, 0xA5, + 0xB7, 0xA7, 0xF5, 0x04, 0xBB, 0xF3, 0xD2, 0x28, + 0xF4, 0x43, 0xE3, 0xCA, 0x4D, 0x62, 0xB5, 0x9A, + 0xCA, 0x84, 0xE9, 0x90, 0xCA, 0xCA, 0xF5, 0xC5, + 0x2B, 0x09, 0x30, 0xDA, 0xA2, 0x3D, 0xE9, 0x4C, + 0xE8, 0x70, 0x17, 0xBA, 0x2D, 0x84, 0x98, 0x8D, + 0xDF, 0xC9, 0xC5, 0x8D, 0xB6, 0x7A, 0xAD, 0xA6, + 0x13, 0xC2, 0xDD, 0x08, 0x45, 0x79, 0x41, 0xA6 + ] + + func testEncrypt() { + let result = try? AesCtr.compute(key: key, iv: iv, data: plaintext) + XCTAssertEqual(ciphertext, result) + } + + func testDecrypt() { + let result = try? AesCtr.compute(key: key, iv: iv, data: ciphertext) + XCTAssertEqual(plaintext, result) + } +} diff --git a/Tests/CryptomatorCryptoLibTests/AesSivTests.swift b/Tests/CryptomatorCryptoLibTests/AesSivTests.swift new file mode 100644 index 0000000..14c8824 --- /dev/null +++ b/Tests/CryptomatorCryptoLibTests/AesSivTests.swift @@ -0,0 +1,185 @@ +// +// AesSivTests.swift +// CryptomatorCryptoLibTests +// +// Created by Sebastian Stenzel on 29.04.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import XCTest +@testable import CryptomatorCryptoLib + +class AesSivTests: XCTestCase { + let aesKey: [UInt8] = [ + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + ] + + let macKey: [UInt8] = [ + 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, + 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0 + ] + + let ad: [UInt8] = [ + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 + ] + + func testEncrypt() { + let plaintext: [UInt8] = [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE + ] + + let expected: [UInt8] = [ + 0x85, 0x63, 0x2D, 0x07, 0xC6, 0xE8, 0xF3, 0x7F, + 0x95, 0x0A, 0xCD, 0x32, 0x0A, 0x2E, 0xCC, 0x93, + 0x40, 0xC0, 0x2B, 0x96, 0x90, 0xC4, 0xDC, 0x04, + 0xDA, 0xEF, 0x7F, 0x6A, 0xFE, 0x5C + ] + + let result = try? AesSiv.encrypt(aesKey: aesKey, macKey: macKey, plaintext: plaintext, ad: ad) + + XCTAssertEqual(expected, result) + } + + func testDecrypt() { + let ciphertext: [UInt8] = [ + 0x85, 0x63, 0x2D, 0x07, 0xC6, 0xE8, 0xF3, 0x7F, + 0x95, 0x0A, 0xCD, 0x32, 0x0A, 0x2E, 0xCC, 0x93, + 0x40, 0xC0, 0x2B, 0x96, 0x90, 0xC4, 0xDC, 0x04, + 0xDA, 0xEF, 0x7F, 0x6A, 0xFE, 0x5C + ] + + let expected: [UInt8] = [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE + ] + + let result = try? AesSiv.decrypt(aesKey: aesKey, macKey: macKey, ciphertext: ciphertext, ad: ad) + + XCTAssertEqual(expected, result) + } + + func testCtr() { + let aesKey: [UInt8] = [ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F + ] + + let iv: [UInt8] = [ + 0x7B, 0xDB, 0x6E, 0x3B, 0x43, 0x26, 0x67, 0xEB, + 0x06, 0xF4, 0xD1, 0x4B, 0x7F, 0x2F, 0xBD, 0x0F + ] + + let plaintext = [UInt8](repeating: 0x00, count: 16 * 3) + + let expected: [UInt8] = [ + 0xBF, 0xF8, 0x66, 0x5C, 0xFD, 0xD7, 0x33, 0x63, + 0x55, 0x0F, 0x74, 0x00, 0xE8, 0xF9, 0xD3, 0x76, + 0xB2, 0xC9, 0x08, 0x8E, 0x71, 0x3B, 0x86, 0x17, + 0xD8, 0x83, 0x92, 0x26, 0xD9, 0xF8, 0x81, 0x59, + 0x9E, 0x44, 0xD8, 0x27, 0x23, 0x49, 0x49, 0xBC, + 0x1B, 0x12, 0x34, 0x8E, 0xBC, 0x19, 0x5E, 0xC7 + ] + + let result = try? AesSiv.ctr(aesKey: aesKey, iv: iv, plaintext: plaintext) + + XCTAssertEqual(expected, result) + } + + func testS2v() { + let plaintext: [UInt8] = [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE + ] + + let expected: [UInt8] = [ + 0x85, 0x63, 0x2D, 0x07, 0xC6, 0xE8, 0xF3, 0x7F, + 0x95, 0x0A, 0xCD, 0x32, 0x0A, 0x2E, 0xCC, 0x93 + ] + + let result = try? AesSiv.s2v(macKey: macKey, plaintext: plaintext, ad: [ad]) + + XCTAssertEqual(expected, result) + } + + func testCmac1() { + let macKey: [UInt8] = [ + 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, + 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C + ] + let message: [UInt8] = [] + let expected: [UInt8] = [ + 0xBB, 0x1D, 0x69, 0x29, 0xE9, 0x59, 0x37, 0x28, + 0x7F, 0xA3, 0x7D, 0x12, 0x9B, 0x75, 0x67, 0x46 + ] + let result = try? AesSiv.cmac(macKey: macKey, data: message) + + XCTAssertEqual(expected, result) + } + + func testCmac2() { + let macKey: [UInt8] = [ + 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, + 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C + ] + let message: [UInt8] = [ + 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, + 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A + ] + let expected: [UInt8] = [ + 0x07, 0x0A, 0x16, 0xB4, 0x6B, 0x4D, 0x41, 0x44, + 0xF7, 0x9B, 0xDD, 0x9D, 0xD0, 0x4A, 0x28, 0x7C + ] + let result = try? AesSiv.cmac(macKey: macKey, data: message) + + XCTAssertEqual(expected, result) + } + + func testCmac3() { + let macKey: [UInt8] = [ + 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, + 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C + ] + let message: [UInt8] = [ + 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, + 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A, + 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, + 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51, + 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11 + ] + let expected: [UInt8] = [ + 0xDF, 0xA6, 0x67, 0x47, 0xDE, 0x9A, 0xE6, 0x30, + 0x30, 0xCA, 0x32, 0x61, 0x14, 0x97, 0xC8, 0x27 + ] + let result = try? AesSiv.cmac(macKey: macKey, data: message) + + XCTAssertEqual(expected, result) + } + + func testCmac4() { + let macKey: [UInt8] = [ + 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, + 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C + ] + let message: [UInt8] = [ + 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, + 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A, + 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, + 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51, + 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, + 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF, + 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, + 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 + ] + let expected: [UInt8] = [ + 0x51, 0xF0, 0xBE, 0xBF, 0x7E, 0x3B, 0x9D, 0x92, + 0xFC, 0x49, 0x74, 0x17, 0x79, 0x36, 0x3C, 0xFE + ] + let result = try? AesSiv.cmac(macKey: macKey, data: message) + + XCTAssertEqual(expected, result) + } +} diff --git a/Tests/CryptomatorCryptoLibTests/CryptoSupportMock.swift b/Tests/CryptomatorCryptoLibTests/CryptoSupportMock.swift new file mode 100644 index 0000000..a3d165b --- /dev/null +++ b/Tests/CryptomatorCryptoLibTests/CryptoSupportMock.swift @@ -0,0 +1,16 @@ +// +// CryptoSupportMock.swift +// CryptomatorCryptoLibTests +// +// Created by Tobias Hagemann on 08.01.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import Foundation +@testable import CryptomatorCryptoLib + +class CryptoSupportMock: CryptoSupport { + override func createRandomBytes(size: Int) throws -> [UInt8] { + return [UInt8](repeating: 0xF0, count: size) + } +} diff --git a/Tests/CryptomatorCryptoLibTests/CryptorTests.swift b/Tests/CryptomatorCryptoLibTests/CryptorTests.swift new file mode 100644 index 0000000..b795f83 --- /dev/null +++ b/Tests/CryptomatorCryptoLibTests/CryptorTests.swift @@ -0,0 +1,175 @@ +// +// CryptorTests.swift +// CryptomatorCryptoLibTests +// +// Created by Sebastian Stenzel on 27.04.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import XCTest +@testable import CryptomatorCryptoLib + +class CryptorTests: XCTestCase { + var cryptor: Cryptor! + var tmpDirURL: URL! + + override func setUpWithError() throws { + let aesKey = [UInt8](repeating: 0x55, count: 32) + let macKey = [UInt8](repeating: 0x77, count: 32) + let masterkey = Masterkey.createFromRaw(aesMasterKey: aesKey, macMasterKey: macKey) + let cryptoSupport = CryptoSupportMock() + let contentCryptor = CtrThenHmacContentCryptor(macKey: macKey, cryptoSupport: cryptoSupport) + cryptor = Cryptor(masterkey: masterkey, cryptoSupport: cryptoSupport, contentCryptor: contentCryptor) + + tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(UUID().uuidString, isDirectory: true) + try FileManager.default.createDirectory(at: tmpDirURL, withIntermediateDirectories: true) + } + + override func tearDownWithError() throws { + try FileManager.default.removeItem(at: tmpDirURL) + } + + func testEncryptDirId() throws { + let rootDir = try cryptor.encryptDirId("".data(using: .utf8)!) + XCTAssertEqual("VLWEHT553J5DR7OZLRJAYDIWFCXZABOD", rootDir) + + let testDir = try cryptor.encryptDirId("918acfbd-a467-3f77-93f1-f4a44f9cfe9c".data(using: .utf8)!) + XCTAssertEqual("7C3USOO3VU7IVQRKFMRFV3QE4VEZJECV", testDir) + } + + func testEncryptAndDecryptName() throws { + let dirId = "foo".data(using: .utf8)! + let originalName = "hello.txt" + + let ciphertextName = try cryptor.encryptFileName(originalName, dirId: dirId) + XCTAssertNotNil(ciphertextName) + + let cleartextName = try cryptor.decryptFileName(ciphertextName, dirId: dirId) + XCTAssertNotNil(cleartextName) + XCTAssertEqual(originalName, cleartextName) + } + + func testCreateHeader() throws { + let header = try cryptor.createHeader() + XCTAssertEqual([UInt8](repeating: 0xF0, count: 16), header.nonce) + XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), header.contentKey) + } + + func testEncryptHeader() throws { + let header = try cryptor.createHeader() + let encrypted = try cryptor.encryptHeader(header) + let expected: [UInt8] = [ + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x0D, 0x91, 0xF2, 0x9C, 0xC6, 0x35, 0xD7, 0x5E, + 0x1E, 0x42, 0x23, 0x1E, 0xC7, 0x90, 0x57, 0xE3, + 0x8D, 0x98, 0xF3, 0x58, 0x07, 0x2C, 0x9F, 0x03, + 0xBC, 0xEA, 0x5A, 0x98, 0x3B, 0x68, 0x62, 0x89, + 0x3E, 0xBC, 0x5E, 0x5E, 0x27, 0x39, 0xCB, 0x8E, + 0xD4, 0x27, 0x61, 0x06, 0x8E, 0x7F, 0x3A, 0x4E, + 0xC7, 0x9F, 0x4D, 0x3E, 0x20, 0x57, 0xDC, 0xE4, + 0x65, 0xA5, 0xFF, 0x93, 0xC2, 0x7B, 0xD2, 0xB8, + 0x3F, 0xE3, 0xD0, 0x8C, 0xB3, 0x92, 0xED, 0x96 + ] + XCTAssertEqual(expected, encrypted) + } + + func testDecryptHeader() throws { + let ciphertext: [UInt8] = [ + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x0D, 0x91, 0xF2, 0x9C, 0xC6, 0x35, 0xD7, 0x5E, + 0x1E, 0x42, 0x23, 0x1E, 0xC7, 0x90, 0x57, 0xE3, + 0x8D, 0x98, 0xF3, 0x58, 0x07, 0x2C, 0x9F, 0x03, + 0xBC, 0xEA, 0x5A, 0x98, 0x3B, 0x68, 0x62, 0x89, + 0x3E, 0xBC, 0x5E, 0x5E, 0x27, 0x39, 0xCB, 0x8E, + 0xD4, 0x27, 0x61, 0x06, 0x8E, 0x7F, 0x3A, 0x4E, + 0xC7, 0x9F, 0x4D, 0x3E, 0x20, 0x57, 0xDC, 0xE4, + 0x65, 0xA5, 0xFF, 0x93, 0xC2, 0x7B, 0xD2, 0xB8, + 0x3F, 0xE3, 0xD0, 0x8C, 0xB3, 0x92, 0xED, 0x96 + ] + let decrypted = try cryptor.decryptHeader(ciphertext) + XCTAssertEqual([UInt8](repeating: 0xF0, count: 16), decrypted.nonce) + XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), decrypted.contentKey) + } + + func testEncryptAndDecryptContent() throws { + let originalData = Data(repeating: 0x0F, count: 65 * 1024) + let originalURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) + try originalData.write(to: originalURL) + + let ciphertextURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) + let cleartextURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) + let overallProgress = Progress(totalUnitCount: 2) + let progressObserver = overallProgress.observe(\.fractionCompleted) { progress, _ in + print("\(progress.localizedDescription ?? "") (\(progress.localizedAdditionalDescription ?? ""))") + } + overallProgress.becomeCurrent(withPendingUnitCount: 1) + try cryptor.encryptContent(from: originalURL, to: ciphertextURL) + overallProgress.resignCurrent() + overallProgress.becomeCurrent(withPendingUnitCount: 1) + try cryptor.decryptContent(from: ciphertextURL, to: cleartextURL) + overallProgress.resignCurrent() + progressObserver.invalidate() + XCTAssertTrue(overallProgress.completedUnitCount >= overallProgress.totalUnitCount) + + let cleartextData = try Data(contentsOf: cleartextURL) + XCTAssertEqual(originalData, cleartextData) + } + + func testEncryptAndDecryptSingleChunk() throws { + let nonce = [UInt8](repeating: 0x00, count: 16) + let filekey = [UInt8](repeating: 0x00, count: 32) + let cleartext = [UInt8]("hello world".data(using: .ascii)!) + + let encrypted = try cryptor.encryptSingleChunk(cleartext, chunkNumber: 0, headerNonce: nonce, fileKey: filekey) + let decrypted = try cryptor.decryptSingleChunk(encrypted, chunkNumber: 0, headerNonce: nonce, fileKey: filekey) + + XCTAssertEqual(cleartext, decrypted) + } + + func testCalculateCiphertextSize() { + XCTAssertEqual(0, cryptor.calculateCiphertextSize(0)) + + XCTAssertEqual(1 + 48, cryptor.calculateCiphertextSize(1)) + XCTAssertEqual(32 * 1024 - 1 + 48, cryptor.calculateCiphertextSize(32 * 1024 - 1)) + XCTAssertEqual(32 * 1024 + 48, cryptor.calculateCiphertextSize(32 * 1024)) + + XCTAssertEqual(32 * 1024 + 1 + 48 * 2, cryptor.calculateCiphertextSize(32 * 1024 + 1)) + XCTAssertEqual(32 * 1024 + 2 + 48 * 2, cryptor.calculateCiphertextSize(32 * 1024 + 2)) + XCTAssertEqual(64 * 1024 - 1 + 48 * 2, cryptor.calculateCiphertextSize(64 * 1024 - 1)) + XCTAssertEqual(64 * 1024 + 48 * 2, cryptor.calculateCiphertextSize(64 * 1024)) + + XCTAssertEqual(64 * 1024 + 1 + 48 * 3, cryptor.calculateCiphertextSize(64 * 1024 + 1)) + } + + func testCalculateCleartextSize() throws { + XCTAssertEqual(0, try cryptor.calculateCleartextSize(0)) + + XCTAssertEqual(1, try cryptor.calculateCleartextSize(1 + 48)) + XCTAssertEqual(32 * 1024 - 1, try cryptor.calculateCleartextSize(32 * 1024 - 1 + 48)) + XCTAssertEqual(32 * 1024, try cryptor.calculateCleartextSize(32 * 1024 + 48)) + + XCTAssertEqual(32 * 1024 + 1, try cryptor.calculateCleartextSize(32 * 1024 + 1 + 48 * 2)) + XCTAssertEqual(32 * 1024 + 2, try cryptor.calculateCleartextSize(32 * 1024 + 2 + 48 * 2)) + XCTAssertEqual(64 * 1024 - 1, try cryptor.calculateCleartextSize(64 * 1024 - 1 + 48 * 2)) + XCTAssertEqual(64 * 1024, try cryptor.calculateCleartextSize(64 * 1024 + 48 * 2)) + + XCTAssertEqual(64 * 1024 + 1, try cryptor.calculateCleartextSize(64 * 1024 + 1 + 48 * 3)) + } + + func testCalculateCleartextSizeWithInvalidCiphertextSize() throws { + XCTAssertThrowsError(try cryptor.calculateCleartextSize(1), "invalid ciphertext size") { error in + XCTAssertEqual(.invalidParameter("Method not defined for input value 1"), error as? CryptoError) + } + XCTAssertThrowsError(try cryptor.calculateCleartextSize(48), "invalid ciphertext size") { error in + XCTAssertEqual(.invalidParameter("Method not defined for input value 48"), error as? CryptoError) + } + XCTAssertThrowsError(try cryptor.calculateCleartextSize(32 * 1024 + 1 + 48), "invalid ciphertext size") { error in + XCTAssertEqual(.invalidParameter("Method not defined for input value 32817"), error as? CryptoError) + } + XCTAssertThrowsError(try cryptor.calculateCleartextSize(32 * 1024 + 48 * 2), "invalid ciphertext size") { error in + XCTAssertEqual(.invalidParameter("Method not defined for input value 32864"), error as? CryptoError) + } + } +} diff --git a/CryptoLibTests/Info.plist b/Tests/CryptomatorCryptoLibTests/Info.plist similarity index 100% rename from CryptoLibTests/Info.plist rename to Tests/CryptomatorCryptoLibTests/Info.plist diff --git a/Tests/CryptomatorCryptoLibTests/MasterkeyFileTests.swift b/Tests/CryptomatorCryptoLibTests/MasterkeyFileTests.swift new file mode 100644 index 0000000..0057323 --- /dev/null +++ b/Tests/CryptomatorCryptoLibTests/MasterkeyFileTests.swift @@ -0,0 +1,250 @@ +// +// MasterkeyFileTests.swift +// CryptomatorCryptoLibTests +// +// Created by Tobias Hagemann on 08.01.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CommonCrypto +import XCTest +@testable import CryptomatorCryptoLib + +class MasterkeyFileTests: XCTestCase { + func testCreateWithContentFromData() throws { + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + XCTAssertEqual(7, masterkeyFile.content.version) + XCTAssertEqual("AAAAAAAAAAA=", masterkeyFile.content.scryptSalt) + XCTAssertEqual(2, masterkeyFile.content.scryptCostParam) + XCTAssertEqual(8, masterkeyFile.content.scryptBlockSize) + XCTAssertEqual("mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", masterkeyFile.content.primaryMasterKey) + XCTAssertEqual("mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", masterkeyFile.content.hmacMasterKey) + XCTAssertEqual("cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=", masterkeyFile.content.versionMac) + } + + func testUnlockWithPassphrase() throws { + let expectedKey = [UInt8](repeating: 0x00, count: 32) + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + let masterkey = try masterkeyFile.unlock(passphrase: "asd", pepper: [UInt8]()) + XCTAssertEqual(expectedKey, masterkey.aesMasterKey) + XCTAssertEqual(expectedKey, masterkey.macMasterKey) + } + + func testUnlockWithWrongPassphrase() throws { + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + XCTAssertThrowsError(try masterkeyFile.unlock(passphrase: "qwe", pepper: [UInt8]()), "wrong passphrase") { error in + XCTAssertEqual(.invalidPassphrase, error as? MasterkeyFileError) + } + } + + func testUnlockWithInvalidVersionMac() throws { + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+G=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + XCTAssertThrowsError(try masterkeyFile.unlock(passphrase: "asd", pepper: [UInt8]()), "invalid version mac") { error in + XCTAssertEqual(.malformedMasterkeyFile("incorrect version or versionMac"), error as? MasterkeyFileError) + } + } + + func testUnlockWithMalformedJson1() throws { + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q!!", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + XCTAssertThrowsError(try masterkeyFile.unlock(passphrase: "asd", pepper: [UInt8]()), "malformed json") { error in + XCTAssertEqual(.malformedMasterkeyFile("invalid base64 data in primaryMasterKey"), error as? MasterkeyFileError) + } + } + + func testUnlockWithMalformedJson2() throws { + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q!!", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + XCTAssertThrowsError(try masterkeyFile.unlock(passphrase: "asd", pepper: [UInt8]()), "malformed json") { error in + XCTAssertEqual(.malformedMasterkeyFile("invalid base64 data in hmacMasterKey"), error as? MasterkeyFileError) + } + } + + func testUnlockWithMalformedJson3() throws { + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + XCTAssertThrowsError(try masterkeyFile.unlock(passphrase: "asd", pepper: [UInt8]()), "malformed json") { error in + XCTAssertEqual(.malformedMasterkeyFile("invalid base64 data in versionMac"), error as? MasterkeyFileError) + } + } + + func testDeriveKey() throws { + let expectedKey: [UInt8] = [ + 0x8C, 0xF4, 0xA0, 0x4E, 0xC8, 0x45, 0xF4, 0x28, + 0xB2, 0xF9, 0xF9, 0xE1, 0xD9, 0xDF, 0x08, 0xD2, + 0x62, 0x11, 0xD9, 0xAF, 0xE2, 0xF5, 0x5F, 0xDE, + 0xDF, 0xCB, 0xB5, 0xE7, 0x5A, 0xEF, 0x34, 0xF9 + ] + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + let kek = try masterkeyFile.deriveKey(passphrase: "asd", pepper: [UInt8]()) + XCTAssertEqual(expectedKey, kek) + } + + func testUnlockWithKEK() throws { + let expectedKey = [UInt8](repeating: 0x00, count: 32) + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + let kek: [UInt8] = [ + 0x8C, 0xF4, 0xA0, 0x4E, 0xC8, 0x45, 0xF4, 0x28, + 0xB2, 0xF9, 0xF9, 0xE1, 0xD9, 0xDF, 0x08, 0xD2, + 0x62, 0x11, 0xD9, 0xAF, 0xE2, 0xF5, 0x5F, 0xDE, + 0xDF, 0xCB, 0xB5, 0xE7, 0x5A, 0xEF, 0x34, 0xF9 + ] + let masterkey = try masterkeyFile.unlock(kek: kek) + XCTAssertEqual(expectedKey, masterkey.aesMasterKey) + XCTAssertEqual(expectedKey, masterkey.macMasterKey) + } + + func testLock() throws { + let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) + let content = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 7, passphrase: "asd", pepper: [UInt8](), scryptCostParam: 2, cryptoSupport: CryptoSupportMock()) + XCTAssertEqual(7, content.version) + XCTAssertEqual("8PDw8PDw8PA=", content.scryptSalt) + XCTAssertEqual(2, content.scryptCostParam) + XCTAssertEqual(8, content.scryptBlockSize) + XCTAssertEqual("jvdghkTc01VISrFly37pgaT/UKtXrDCvZcU3tT9Y98zyzn/pJ91bxw==", content.primaryMasterKey) + XCTAssertEqual("99I+J4bT3rVpZE8yZwKRV9gHVRmQ8XQEujAL9IuwLTc2D3mg5JEjKA==", content.hmacMasterKey) + XCTAssertEqual("sAWFgFNhmtMPeNWr4zh+9Ps7GOtT0pknX11PRQ7eC9Q=", content.versionMac) + } + + func testLockWithDifferentPeppers() throws { + let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) + let content1 = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 7, passphrase: "asd", pepper: [UInt8](arrayLiteral: 0x01), scryptCostParam: 2, cryptoSupport: CryptoSupportMock()) + let content2 = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 7, passphrase: "asd", pepper: [UInt8](arrayLiteral: 0x02), scryptCostParam: 2, cryptoSupport: CryptoSupportMock()) + XCTAssertNotEqual(content1, content2) + } + + func testChangePassphrase() throws { + let expectedKey = [UInt8](repeating: 0x00, count: 32) + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let content = try MasterkeyFile.changePassphrase(masterkeyFileData: data, oldPassphrase: "asd", newPassphrase: "qwe", pepper: [UInt8](), scryptCostParam: 2, cryptoSupport: CryptoSupportMock()) + let masterkeyFile = MasterkeyFile(content: content) + let masterkey = try masterkeyFile.unlock(passphrase: "qwe", pepper: [UInt8]()) + XCTAssertEqual(expectedKey, masterkey.aesMasterKey) + XCTAssertEqual(expectedKey, masterkey.macMasterKey) + XCTAssertThrowsError(try masterkeyFile.unlock(passphrase: "asd", pepper: [UInt8]()), "wrong passphrase") { error in + XCTAssertEqual(.invalidPassphrase, error as? MasterkeyFileError) + } + } + + func testWrapAndUnwrapKey() throws { + let key = [UInt8](repeating: 0x77, count: 32) + let kek = [UInt8](repeating: 0x55, count: 32) + let wrapped = try MasterkeyFile.wrapKey(key, kek: kek) + let unwrapped = try MasterkeyFile.unwrapKey(wrapped, kek: kek) + XCTAssertEqual(key, unwrapped) + } + + func testWrapKeyWithInvalidKey() throws { + let key = [UInt8](repeating: 0x77, count: 17) + let kek = [UInt8](repeating: 0x55, count: 32) + XCTAssertThrowsError(try MasterkeyFile.wrapKey(key, kek: kek), "invalid key") { error in + XCTAssertEqual(.keyWrapFailed(CCCryptorStatus(kCCParamError)), error as? MasterkeyFileError) + } + } +} diff --git a/Tests/CryptomatorCryptoLibTests/MasterkeyTests.swift b/Tests/CryptomatorCryptoLibTests/MasterkeyTests.swift new file mode 100644 index 0000000..485f7db --- /dev/null +++ b/Tests/CryptomatorCryptoLibTests/MasterkeyTests.swift @@ -0,0 +1,20 @@ +// +// MasterkeyTests.swift +// CryptomatorCryptoLibTests +// +// Created by Sebastian Stenzel on 26.04.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import XCTest +@testable import CryptomatorCryptoLib + +class MasterkeyTests: XCTestCase { + func testCreateFromRaw() throws { + let aesMasterKey = [UInt8](repeating: 0x77, count: 32) + let macMasterKey = [UInt8](repeating: 0x55, count: 32) + let masterkey = Masterkey.createFromRaw(aesMasterKey: aesMasterKey, macMasterKey: macMasterKey) + XCTAssertEqual(aesMasterKey, masterkey.aesMasterKey) + XCTAssertEqual(macMasterKey, masterkey.macMasterKey) + } +}