diff --git a/.swiftformatignore b/.swiftformatignore new file mode 100644 index 00000000..ea8271df --- /dev/null +++ b/.swiftformatignore @@ -0,0 +1 @@ +Plugins/AWSLambdaInitializer/Template.swift diff --git a/Examples/_MyFirstFunction/create_function.sh b/Examples/_MyFirstFunction/create_function.sh new file mode 100755 index 00000000..71fd4ad7 --- /dev/null +++ b/Examples/_MyFirstFunction/create_function.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +# check if docker is installed +which docker > /dev/null +if [[ $? != 0 ]]; then + echo "Docker is not installed. Please install Docker and try again." + exit 1 +fi + +# check if user has an access key and secret access key +echo "This script creates and deploys a Lambda function on your AWS Account. + +You must have an AWS account and know an AWS access key, secret access key, and an optional session token. These values are read from '~/.aws/credentials' or asked interactively. +" + +read -p "Are you ready to create your first Lambda function in Swift? [y/n] " continue +if [[ continue != ^[Yy]$ ]]; then + echo "OK, try again later when you feel ready" + exit 1 +fi + +echo "⚡️ Create your Swift command line project" +swift package init --type executable --name MyLambda + +echo "📦 Add the AWS Lambda Swift runtime to your project" +swift package add-dependency https://github.com/swift-server/swift-aws-lambda-runtime.git --branch main +swift package add-dependency https://github.com/swift-server/swift-aws-lambda-events.git --branch main +swift package add-target-dependency AWSLambdaRuntime MyLambda --package swift-aws-lambda-runtime +swift package add-target-dependency AWSLambdaEvents MyLambda --package swift-aws-lambda-events + +echo "📝 Write the Swift code" +swift package lambda-init --allow-writing-to-package-directory + +echo "📦 Compile and package the function for deployment" +swift package archive --allow-network-connections docker + +echo "🚀 Deploy to AWS Lambda" + + diff --git a/Package.swift b/Package.swift index 99ea9667..2b51e8a5 100644 --- a/Package.swift +++ b/Package.swift @@ -13,9 +13,24 @@ let package = Package( name: "swift-aws-lambda-runtime", products: [ .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), + + // + // The plugins + // 'lambda-init' creates a new Lambda function + // 'lambda-build' packages the Lambda function + // 'lambda-deploy' deploys the Lambda function + // + // Plugins requires Linux or at least macOS v15 + // + + // plugin to create a new Lambda function, based on a template + .plugin(name: "AWSLambdaInitializer", targets: ["AWSLambdaInitializer"]), + // plugin to package the lambda, creating an archive that can be uploaded to AWS - // requires Linux or at least macOS v15 - .plugin(name: "AWSLambdaPackager", targets: ["AWSLambdaPackager"]), + .plugin(name: "AWSLambdaBuilder", targets: ["AWSLambdaBuilder"]), + + // plugin to deploy a Lambda function + .plugin(name: "AWSLambdaDeployer", targets: ["AWSLambdaDeployer"]), ], traits: [ "FoundationJSONSupport", @@ -53,20 +68,84 @@ let package = Package( swiftSettings: defaultSwiftSettings ), .plugin( - name: "AWSLambdaPackager", + name: "AWSLambdaInitializer", + capability: .command( + intent: .custom( + verb: "lambda-init", + description: + "Create a new Lambda function in the current project directory." + ), + permissions: [ + .writeToPackageDirectory(reason: "Create a file with an HelloWorld Lambda function.") + ] + ), + dependencies: [ + .target(name: "AWSLambdaPluginHelper") + ] + ), + // keep this one (with "archive") to not break workflows + // This will be deprecated at some point in the future + // .plugin( + // name: "AWSLambdaPackager", + // capability: .command( + // intent: .custom( + // verb: "archive", + // description: + // "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." + // ), + // permissions: [ + // .allowNetworkConnections( + // scope: .docker, + // reason: "This plugin uses Docker to create the AWS Lambda ZIP package." + // ) + // ] + // ), + // path: "Plugins/AWSLambdaBuilder" // same sources as the new "lambda-build" plugin + // ), + .plugin( + name: "AWSLambdaBuilder", capability: .command( intent: .custom( - verb: "archive", + verb: "lambda-build", description: - "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." + "Compile and archive (zip) the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." ), permissions: [ .allowNetworkConnections( scope: .docker, - reason: "This plugin uses Docker to create the AWS Lambda ZIP package." + reason: "This plugin uses Docker to compile code for Amazon Linux." ) ] - ) + ), + dependencies: [ + .target(name: "AWSLambdaPluginHelper") + ] + ), + .plugin( + name: "AWSLambdaDeployer", + capability: .command( + intent: .custom( + verb: "lambda-deploy", + description: + "Deploy the Lambda function. You must have an AWS account and an access key and secret access key." + ), + permissions: [ + .allowNetworkConnections( + scope: .all(ports: [443]), + reason: "This plugin uses the AWS Lambda API to deploy the function." + ) + ] + ), + dependencies: [ + .target(name: "AWSLambdaPluginHelper") + ] + ), + .executableTarget( + name: "AWSLambdaPluginHelper", + dependencies: [ + .product(name: "NIOHTTP1", package: "swift-nio"), + .product(name: "NIOCore", package: "swift-nio"), + ] ), .testTarget( name: "AWSLambdaRuntimeTests", @@ -89,5 +168,12 @@ let package = Package( ], swiftSettings: defaultSwiftSettings ), + .testTarget( + name: "AWSLambdaPluginHelperTests", + dependencies: [ + .byName(name: "AWSLambdaPluginHelper") + ] + ), + ] ) diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 5f4021d4..bdcff8e9 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -15,9 +15,24 @@ let package = Package( name: "swift-aws-lambda-runtime", products: [ .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), + + // + // The plugins + // 'lambda-init' creates a new Lambda function + // 'lambda-build' packages the Lambda function + // 'lambda-deploy' deploys the Lambda function + // + // Plugins requires Linux or at least macOS v15 + // + + // plugin to create a new Lambda function, based on a template + .plugin(name: "AWSLambdaInitializer", targets: ["AWSLambdaInitializer"]), + // plugin to package the lambda, creating an archive that can be uploaded to AWS - // requires Linux or at least macOS v15 - .plugin(name: "AWSLambdaPackager", targets: ["AWSLambdaPackager"]), + .plugin(name: "AWSLambdaBuilder", targets: ["AWSLambdaBuilder"]), + + // plugin to deploy a Lambda function + .plugin(name: "AWSLambdaDeployer", targets: ["AWSLambdaDeployer"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.81.0"), @@ -39,20 +54,84 @@ let package = Package( swiftSettings: defaultSwiftSettings ), .plugin( - name: "AWSLambdaPackager", + name: "AWSLambdaInitializer", capability: .command( intent: .custom( - verb: "archive", + verb: "lambda-init", description: - "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." + "Create a new Lambda function in the current project directory." + ), + permissions: [ + .writeToPackageDirectory(reason: "Create a file with an HelloWorld Lambda function.") + ] + ), + dependencies: [ + .target(name: "AWSLambdaPluginHelper") + ] + ), + // keep this one (with "archive") to not break workflows + // This will be deprecated at some point in the future + // .plugin( + // name: "AWSLambdaPackager", + // capability: .command( + // intent: .custom( + // verb: "archive", + // description: + // "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." + // ), + // permissions: [ + // .allowNetworkConnections( + // scope: .docker, + // reason: "This plugin uses Docker to create the AWS Lambda ZIP package." + // ) + // ] + // ), + // path: "Plugins/AWSLambdaBuilder" // same sources as the new "lambda-build" plugin + // ), + .plugin( + name: "AWSLambdaBuilder", + capability: .command( + intent: .custom( + verb: "lambda-build", + description: + "Compile and archive (zip) the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." ), permissions: [ .allowNetworkConnections( scope: .docker, - reason: "This plugin uses Docker to create the AWS Lambda ZIP package." + reason: "This plugin uses Docker to compile code for Amazon Linux." + ) + ] + ), + dependencies: [ + .target(name: "AWSLambdaPluginHelper") + ] + ), + .plugin( + name: "AWSLambdaDeployer", + capability: .command( + intent: .custom( + verb: "lambda-deploy", + description: + "Deploy the Lambda function. You must have an AWS account and an access key and secret access key." + ), + permissions: [ + .allowNetworkConnections( + scope: .all(ports: [443]), + reason: "This plugin uses the AWS Lambda API to deploy the function." ) ] - ) + ), + dependencies: [ + .target(name: "AWSLambdaPluginHelper") + ] + ), + .executableTarget( + name: "AWSLambdaPluginHelper", + dependencies: [ + .product(name: "NIOHTTP1", package: "swift-nio"), + .product(name: "NIOCore", package: "swift-nio"), + ] ), .testTarget( name: "AWSLambdaRuntimeTests", diff --git a/Plugins/AWSLambdaBuilder/Plugin.swift b/Plugins/AWSLambdaBuilder/Plugin.swift new file mode 100644 index 00000000..fdb97efb --- /dev/null +++ b/Plugins/AWSLambdaBuilder/Plugin.swift @@ -0,0 +1,175 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +import PackagePlugin + +@main +struct AWSLambdaPackager: CommandPlugin { + + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { + + // values to pass to the AWSLambdaPluginHelper + let outputDirectory: URL + let products: [Product] + let buildConfiguration: PackageManager.BuildConfiguration + let packageID: String = context.package.id + let packageDisplayName = context.package.displayName + let packageDirectory = context.package.directoryURL + let dockerToolPath = try context.tool(named: "docker").url + let zipToolPath = try context.tool(named: "zip").url + + // extract arguments that require PluginContext to fully resolve + // resolve them here and pass them to the AWSLambdaPluginHelper as arguments + var argumentExtractor = ArgumentExtractor(arguments) + + let outputPathArgument = argumentExtractor.extractOption(named: "output-path") + let productsArgument = argumentExtractor.extractOption(named: "products") + let configurationArgument = argumentExtractor.extractOption(named: "configuration") + + if let outputPath = outputPathArgument.first { + #if os(Linux) + var isDirectory: Bool = false + #else + var isDirectory: ObjCBool = false + #endif + guard FileManager.default.fileExists(atPath: outputPath, isDirectory: &isDirectory) + else { + throw BuilderErrors.invalidArgument("invalid output directory '\(outputPath)'") + } + outputDirectory = URL(string: outputPath)! + } else { + outputDirectory = context.pluginWorkDirectoryURL.appending(path: "\(AWSLambdaPackager.self)") + } + + let explicitProducts = !productsArgument.isEmpty + if explicitProducts { + let _products = try context.package.products(named: productsArgument) + for product in _products { + guard product is ExecutableProduct else { + throw BuilderErrors.invalidArgument("product named '\(product.name)' is not an executable product") + } + } + products = _products + + } else { + products = context.package.products.filter { $0 is ExecutableProduct } + } + + if let _buildConfigurationName = configurationArgument.first { + guard let _buildConfiguration = PackageManager.BuildConfiguration(rawValue: _buildConfigurationName) else { + throw BuilderErrors.invalidArgument("invalid build configuration named '\(_buildConfigurationName)'") + } + buildConfiguration = _buildConfiguration + } else { + buildConfiguration = .release + } + + // TODO: When running on Amazon Linux 2, we have to build directly from the plugin + // launch the build, then call the helper just for the ZIP part + + let tool = try context.tool(named: "AWSLambdaPluginHelper") + let args = + [ + "build", + "--output-path", outputDirectory.path(), + "--products", products.map { $0.name }.joined(separator: ","), + "--configuration", buildConfiguration.rawValue, + "--package-id", packageID, + "--package-display-name", packageDisplayName, + "--package-directory", packageDirectory.path(), + "--docker-tool-path", dockerToolPath.path, + "--zip-tool-path", zipToolPath.path, + ] + arguments + + // Invoke the plugin helper on the target directory, passing a configuration + // file from the package directory. + let process = try Process.run(tool.url, arguments: args) + process.waitUntilExit() + + // Check whether the subprocess invocation was successful. + if !(process.terminationReason == .exit && process.terminationStatus == 0) { + let problem = "\(process.terminationReason):\(process.terminationStatus)" + Diagnostics.error("AWSLambdaPluginHelper invocation failed: \(problem)") + } + } + + // TODO: When running on Amazon Linux 2, we have to build directly from the plugin + // private func build( + // packageIdentity: Package.ID, + // products: [Product], + // buildConfiguration: PackageManager.BuildConfiguration, + // verboseLogging: Bool + // ) throws -> [LambdaProduct: URL] { + // print("-------------------------------------------------------------------------") + // print("building \"\(packageIdentity)\"") + // print("-------------------------------------------------------------------------") + // + // var results = [LambdaProduct: URL]() + // for product in products { + // print("building \"\(product.name)\"") + // var parameters = PackageManager.BuildParameters() + // parameters.configuration = buildConfiguration + // parameters.otherSwiftcFlags = ["-static-stdlib"] + // parameters.logging = verboseLogging ? .verbose : .concise + // + // let result = try packageManager.build( + // .product(product.name), + // parameters: parameters + // ) + // guard let artifact = result.executableArtifact(for: product) else { + // throw Errors.productExecutableNotFound(product.name) + // } + // results[.init(product)] = artifact.url + // } + // return results + // } + + // private func isAmazonLinux2() -> Bool { + // if let data = FileManager.default.contents(atPath: "/etc/system-release"), + // let release = String(data: data, encoding: .utf8) + // { + // return release.hasPrefix("Amazon Linux release 2") + // } else { + // return false + // } + // } +} + +private enum BuilderErrors: Error, CustomStringConvertible { + case invalidArgument(String) + + var description: String { + switch self { + case .invalidArgument(let description): + return description + } + } +} + +extension PackageManager.BuildResult { + // find the executable produced by the build + func executableArtifact(for product: Product) -> PackageManager.BuildResult.BuiltArtifact? { + let executables = self.builtArtifacts.filter { + $0.kind == .executable && $0.url.lastPathComponent == product.name + } + guard !executables.isEmpty else { + return nil + } + guard executables.count == 1, let executable = executables.first else { + return nil + } + return executable + } +} diff --git a/Plugins/AWSLambdaDeployer/Plugin.swift b/Plugins/AWSLambdaDeployer/Plugin.swift new file mode 100644 index 00000000..b8a0915e --- /dev/null +++ b/Plugins/AWSLambdaDeployer/Plugin.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +import PackagePlugin + +@main +struct AWSLambdaDeployer: CommandPlugin { + + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { + + let tool = try context.tool(named: "AWSLambdaPluginHelper") + + let args = ["deploy"] + arguments + + // Invoke the plugin helper on the target directory, passing a configuration + // file from the package directory. + let process = try Process.run(tool.url, arguments: args) + process.waitUntilExit() + + // Check whether the subprocess invocation was successful. + if !(process.terminationReason == .exit && process.terminationStatus == 0) { + let problem = "\(process.terminationReason):\(process.terminationStatus)" + Diagnostics.error("AWSLambdaPluginHelper invocation failed: \(problem)") + } + } + +} diff --git a/Plugins/AWSLambdaInitializer/Plugin.swift b/Plugins/AWSLambdaInitializer/Plugin.swift new file mode 100644 index 00000000..5b508ff3 --- /dev/null +++ b/Plugins/AWSLambdaInitializer/Plugin.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +import PackagePlugin + +@main +struct AWSLambdaPackager: CommandPlugin { + + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { + let tool = try context.tool(named: "AWSLambdaPluginHelper") + + let args = ["init", "--dest-dir", context.package.directoryURL.path()] + arguments + + // Invoke the plugin helper on the target directory, passing a configuration + // file from the package directory. + let process = try Process.run(tool.url, arguments: args) + process.waitUntilExit() + + // Check whether the subprocess invocation was successful. + if !(process.terminationReason == .exit && process.terminationStatus == 0) { + let problem = "\(process.terminationReason):\(process.terminationStatus)" + Diagnostics.error("AWSLambdaPluginHelper invocation failed: \(problem)") + } + } +} diff --git a/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift b/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift new file mode 100644 index 00000000..2c124b5f --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@main +struct AWSLambdaPluginHelper { + + private enum Command: String { + case `init` + case build + case deploy + } + + public static func main() async throws { + let args = CommandLine.arguments + let helper = AWSLambdaPluginHelper() + let command = try helper.command(from: args) + switch command { + case .`init`: + try await Initializer().initialize(arguments: args) + case .build: + try await Builder().build(arguments: args) + case .deploy: + try await Deployer().deploy(arguments: args) + } + } + + private func command(from arguments: [String]) throws -> Command { + let args = CommandLine.arguments + + guard args.count > 2 else { + throw AWSLambdaPluginHelperError.noCommand + } + let commandName = args[1] + guard let command = Command(rawValue: commandName) else { + throw AWSLambdaPluginHelperError.invalidCommand(commandName) + } + + return command + } +} + +private enum AWSLambdaPluginHelperError: Error { + case noCommand + case invalidCommand(String) +} diff --git a/Sources/AWSLambdaPluginHelper/Extensions.swift b/Sources/AWSLambdaPluginHelper/Extensions.swift new file mode 100644 index 00000000..67a8202b --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Extensions.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +// extension Array where Element == UInt8 { +// public var base64: String { +// Data(self).base64EncodedString() +// } +// } + +extension Data { + var bytes: [UInt8] { + [UInt8](self) + } +} + +extension String { + public var array: [UInt8] { + Array(self.utf8) + } +} + +extension HMAC { + public static func authenticate( + for data: [UInt8], + using key: [UInt8], + variant: HMAC.Variant = .sha2(.sha256) + ) throws -> [UInt8] { + let authenticator = HMAC(key: key, variant: variant) + return try authenticator.authenticate(data) + } + public static func authenticate( + for data: Data, + using key: [UInt8], + variant: HMAC.Variant = .sha2(.sha256) + ) throws -> [UInt8] { + let authenticator = HMAC(key: key, variant: variant) + return try authenticator.authenticate(data.bytes) + } +} diff --git a/Plugins/AWSLambdaPackager/PluginUtils.swift b/Sources/AWSLambdaPluginHelper/Process.swift similarity index 99% rename from Plugins/AWSLambdaPackager/PluginUtils.swift rename to Sources/AWSLambdaPluginHelper/Process.swift index f4e8cb02..235ae056 100644 --- a/Plugins/AWSLambdaPackager/PluginUtils.swift +++ b/Sources/AWSLambdaPluginHelper/Process.swift @@ -14,7 +14,6 @@ import Dispatch import Foundation -import PackagePlugin import Synchronization @available(macOS 15.0, *) diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift new file mode 100644 index 00000000..00010018 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift @@ -0,0 +1,155 @@ +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +extension Array { + @inlinable + init(reserveCapacity: Int) { + self = [Element]() + self.reserveCapacity(reserveCapacity) + } + + @inlinable + var slice: ArraySlice { + self[self.startIndex.. Element? { + indices.contains(index) ? self[index] : nil + } +} + +extension Array where Element == UInt8 { + public init(hex: String) { + self.init(reserveCapacity: hex.unicodeScalars.lazy.underestimatedCount) + var buffer: UInt8? + var skip = hex.hasPrefix("0x") ? 2 : 0 + for char in hex.unicodeScalars.lazy { + guard skip == 0 else { + skip -= 1 + continue + } + guard char.value >= 48 && char.value <= 102 else { + removeAll() + return + } + let v: UInt8 + let c: UInt8 = UInt8(char.value) + switch c { + case let c where c <= 57: + v = c - 48 + case let c where c >= 65 && c <= 70: + v = c - 55 + case let c where c >= 97: + v = c - 87 + default: + removeAll() + return + } + if let b = buffer { + append(b << 4 | v) + buffer = nil + } else { + buffer = v + } + } + if let b = buffer { + append(b) + } + } + + public func toHexString() -> String { + `lazy`.reduce(into: "") { + var s = String($1, radix: 16) + if s.count == 1 { + s = "0" + s + } + $0 += s + } + } +} + +extension Array where Element == UInt8 { + /// split in chunks with given chunk size + @available(*, deprecated) + public func chunks(size chunksize: Int) -> [[Element]] { + var words = [[Element]]() + words.reserveCapacity(count / chunksize) + for idx in stride(from: chunksize, through: count, by: chunksize) { + words.append(Array(self[idx - chunksize.. [Element] { + // Digest.md5(self) + // } + + // public func sha1() -> [Element] { + // Digest.sha1(self) + // } + + // public func sha224() -> [Element] { + // Digest.sha224(self) + // } + + public func sha256() -> [Element] { + Digest.sha256(self) + } + + public func sha384() -> [Element] { + Digest.sha384(self) + } + + public func sha512() -> [Element] { + Digest.sha512(self) + } + + public func sha2(_ variant: SHA2.Variant) -> [Element] { + Digest.sha2(self, variant: variant) + } + + public func sha3(_ variant: SHA3.Variant) -> [Element] { + Digest.sha3(self, variant: variant) + } + + // public func crc32(seed: UInt32? = nil, reflect: Bool = true) -> UInt32 { + // Checksum.crc32(self, seed: seed, reflect: reflect) + // } + + // public func crc32c(seed: UInt32? = nil, reflect: Bool = true) -> UInt32 { + // Checksum.crc32c(self, seed: seed, reflect: reflect) + // } + + // public func crc16(seed: UInt16? = nil) -> UInt16 { + // Checksum.crc16(self, seed: seed) + // } + + // public func encrypt(cipher: Cipher) throws -> [Element] { + // try cipher.encrypt(self.slice) + // } + + // public func decrypt(cipher: Cipher) throws -> [Element] { + // try cipher.decrypt(self.slice) + // } + + public func authenticate(with authenticator: A) throws -> [Element] { + try authenticator.authenticate(self) + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift new file mode 100644 index 00000000..d45363e6 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift @@ -0,0 +1,20 @@ +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// Message authentication code. +public protocol Authenticator { + /// Calculate Message Authentication Code (MAC) for message. + func authenticate(_ bytes: [UInt8]) throws -> [UInt8] +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift new file mode 100644 index 00000000..1dd0fd0e --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift @@ -0,0 +1,87 @@ +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +@usableFromInline +struct BatchedCollectionIndex { + let range: Range +} + +extension BatchedCollectionIndex: Comparable { + @usableFromInline + static func == ( + lhs: BatchedCollectionIndex, + rhs: BatchedCollectionIndex + ) -> Bool { + lhs.range.lowerBound == rhs.range.lowerBound + } + + @usableFromInline + static func < ( + lhs: BatchedCollectionIndex, + rhs: BatchedCollectionIndex + ) -> Bool { + lhs.range.lowerBound < rhs.range.lowerBound + } +} + +protocol BatchedCollectionType: Collection { + associatedtype Base: Collection +} + +@usableFromInline +struct BatchedCollection: Collection { + let base: Base + let size: Int + + @usableFromInline + init(base: Base, size: Int) { + self.base = base + self.size = size + } + + @usableFromInline + typealias Index = BatchedCollectionIndex + + private func nextBreak(after idx: Base.Index) -> Base.Index { + self.base.index(idx, offsetBy: self.size, limitedBy: self.base.endIndex) ?? self.base.endIndex + } + + @usableFromInline + var startIndex: Index { + Index(range: self.base.startIndex.. Index { + Index(range: idx.range.upperBound.. Base.SubSequence { + self.base[idx.range] + } +} + +extension Collection { + @inlinable + func batched(by size: Int) -> BatchedCollection { + BatchedCollection(base: self, size: size) + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift new file mode 100644 index 00000000..c2e29de6 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift @@ -0,0 +1,26 @@ +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +public enum Bit: Int { + case zero + case one +} + +extension Bit { + @inlinable + func inverted() -> Bit { + self == .zero ? .one : .zero + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift new file mode 100644 index 00000000..a205b2df --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// +extension Collection where Self.Element == UInt8, Self.Index == Int { + // Big endian order + @inlinable + func toUInt32Array() -> [UInt32] { + guard !isEmpty else { + return [] + } + + let c = strideCount(from: startIndex, to: endIndex, by: 4) + return [UInt32](unsafeUninitializedCapacity: c) { buf, count in + var counter = 0 + for idx in stride(from: startIndex, to: endIndex, by: 4) { + let val = UInt32(bytes: self, fromIndex: idx).bigEndian + buf[counter] = val + counter += 1 + } + count = counter + assert(counter == c) + } + } + + // Big endian order + @inlinable + func toUInt64Array() -> [UInt64] { + guard !isEmpty else { + return [] + } + + let c = strideCount(from: startIndex, to: endIndex, by: 8) + return [UInt64](unsafeUninitializedCapacity: c) { buf, count in + var counter = 0 + for idx in stride(from: startIndex, to: endIndex, by: 8) { + let val = UInt64(bytes: self, fromIndex: idx).bigEndian + buf[counter] = val + counter += 1 + } + count = counter + assert(counter == c) + } + } +} + +@usableFromInline +func strideCount(from: Int, to: Int, by: Int) -> Int { + let count = to - from + return count / by + (count % by > 0 ? 1 : 0) +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift new file mode 100644 index 00000000..020df591 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +@available(*, renamed: "Digest") +public typealias Hash = Digest + +/// Hash functions to calculate Digest. +public struct Digest { + /// Calculate MD5 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + // public static func md5(_ bytes: Array) -> Array { + // MD5().calculate(for: bytes) + // } + + /// Calculate SHA1 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha1(_ bytes: [UInt8]) -> [UInt8] { + SHA1().calculate(for: bytes) + } + + /// Calculate SHA2-224 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha224(_ bytes: [UInt8]) -> [UInt8] { + self.sha2(bytes, variant: .sha224) + } + + /// Calculate SHA2-256 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha256(_ bytes: [UInt8]) -> [UInt8] { + self.sha2(bytes, variant: .sha256) + } + + /// Calculate SHA2-384 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha384(_ bytes: [UInt8]) -> [UInt8] { + self.sha2(bytes, variant: .sha384) + } + + /// Calculate SHA2-512 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha512(_ bytes: [UInt8]) -> [UInt8] { + self.sha2(bytes, variant: .sha512) + } + + /// Calculate SHA2 Digest + /// - parameter bytes: input message + /// - parameter variant: SHA-2 variant + /// - returns: Digest bytes + public static func sha2(_ bytes: [UInt8], variant: SHA2.Variant) -> [UInt8] { + SHA2(variant: variant).calculate(for: bytes) + } + + /// Calculate SHA3 Digest + /// - parameter bytes: input message + /// - parameter variant: SHA-3 variant + /// - returns: Digest bytes + public static func sha3(_ bytes: [UInt8], variant: SHA3.Variant) -> [UInt8] { + SHA3(variant: variant).calculate(for: bytes) + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift new file mode 100644 index 00000000..35138786 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +internal protocol DigestType { + func calculate(for bytes: [UInt8]) -> [UInt8] +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift new file mode 100644 index 00000000..38c6e3b9 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// Array of bytes. Caution: don't use directly because generic is slow. +/// +/// - parameter value: integer value +/// - parameter length: length of output array. By default size of value type +/// +/// - returns: Array of bytes +@_specialize(where T == Int) +@_specialize(where T == UInt) +@_specialize(where T == UInt8) +@_specialize(where T == UInt16) +@_specialize(where T == UInt32) +@_specialize(where T == UInt64) +@inlinable +func arrayOfBytes(value: T, length totalBytes: Int = MemoryLayout.size) -> [UInt8] { + let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) + valuePointer.pointee = value + + let bytesPointer = UnsafeMutablePointer(OpaquePointer(valuePointer)) + var bytes = [UInt8](repeating: 0, count: totalBytes) + for j in 0...size, totalBytes) { + bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee + } + + valuePointer.deinitialize(count: 1) + valuePointer.deallocate() + + return bytes +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift new file mode 100644 index 00000000..b6fffc88 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +public final class HMAC: Authenticator { + public enum Error: Swift.Error { + case authenticateError + case invalidInput + } + + public enum Variant { + // case md5 + case sha1 + case sha2(SHA2.Variant) + case sha3(SHA3.Variant) + + @available(*, deprecated, message: "Use sha2(variant) instead.") + case sha256, sha384, sha512 + + var digestLength: Int { + switch self { + case .sha1: + return SHA1.digestLength + case .sha256: + return SHA2.Variant.sha256.digestLength + case .sha384: + return SHA2.Variant.sha384.digestLength + case .sha512: + return SHA2.Variant.sha512.digestLength + case .sha2(let variant): + return variant.digestLength + case .sha3(let variant): + return variant.digestLength + // case .md5: + // return MD5.digestLength + } + } + + func calculateHash(_ bytes: [UInt8]) -> [UInt8] { + switch self { + case .sha1: + return Digest.sha1(bytes) + case .sha256: + return Digest.sha256(bytes) + case .sha384: + return Digest.sha384(bytes) + case .sha512: + return Digest.sha512(bytes) + case .sha2(let variant): + return Digest.sha2(bytes, variant: variant) + case .sha3(let variant): + return Digest.sha3(bytes, variant: variant) + // case .md5: + // return Digest.md5(bytes) + } + } + + func blockSize() -> Int { + switch self { + // case .md5: + // return MD5.blockSize + case .sha1: + return SHA1.blockSize + case .sha256: + return SHA2.Variant.sha256.blockSize + case .sha384: + return SHA2.Variant.sha384.blockSize + case .sha512: + return SHA2.Variant.sha512.blockSize + case .sha2(let variant): + return variant.blockSize + case .sha3(let variant): + return variant.blockSize + } + } + } + + var key: [UInt8] + let variant: Variant + + // public init(key: Array, variant: HMAC.Variant = .md5) { + public init(key: [UInt8], variant: HMAC.Variant = .sha2(.sha256)) { + self.variant = variant + self.key = key + + if key.count > variant.blockSize() { + let hash = variant.calculateHash(key) + self.key = hash + } + + if key.count < variant.blockSize() { + self.key = ZeroPadding().add(to: key, blockSize: variant.blockSize()) + } + } + + // MARK: Authenticator + + public func authenticate(_ bytes: [UInt8]) throws -> [UInt8] { + var opad = [UInt8](repeating: 0x5c, count: variant.blockSize()) + for idx in self.key.indices { + opad[idx] = self.key[idx] ^ opad[idx] + } + var ipad = [UInt8](repeating: 0x36, count: variant.blockSize()) + for idx in self.key.indices { + ipad[idx] = self.key[idx] ^ ipad[idx] + } + + let ipadAndMessageHash = self.variant.calculateHash(ipad + bytes) + let result = self.variant.calculateHash(opad + ipadAndMessageHash) + + // return Array(result[0..<10]) // 80 bits + return result + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift new file mode 100644 index 00000000..33dddb51 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Created by Marcin Krzyzanowski on 12/08/14. +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(ucrt) +import ucrt +#endif + +extension FixedWidthInteger { + @inlinable + func bytes(totalBytes: Int = MemoryLayout.size) -> [UInt8] { + arrayOfBytes(value: self.littleEndian, length: totalBytes) + // TODO: adjust bytes order + // var value = self.littleEndian + // return withUnsafeBytes(of: &value, Array.init).reversed() + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift new file mode 100644 index 00000000..a6c51913 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +struct NoPadding: PaddingProtocol { + init() { + } + + func add(to data: [UInt8], blockSize _: Int) -> [UInt8] { + data + } + + func remove(from data: [UInt8], blockSize _: Int?) -> [UInt8] { + data + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift new file mode 100644 index 00000000..fb714a4f --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +public protocol PaddingProtocol { + func add(to: [UInt8], blockSize: Int) -> [UInt8] + func remove(from: [UInt8], blockSize: Int?) -> [UInt8] +} + +public enum Padding: PaddingProtocol { + case noPadding, zeroPadding + public func add(to: [UInt8], blockSize: Int) -> [UInt8] { + switch self { + case .noPadding: + return to // NoPadding().add(to: to, blockSize: blockSize) + case .zeroPadding: + return ZeroPadding().add(to: to, blockSize: blockSize) + } + } + + public func remove(from: [UInt8], blockSize: Int?) -> [UInt8] { + switch self { + case .noPadding: + return from //NoPadding().remove(from: from, blockSize: blockSize) + case .zeroPadding: + return ZeroPadding().remove(from: from, blockSize: blockSize) + } + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift new file mode 100644 index 00000000..b5fcd3fb --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift @@ -0,0 +1,178 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +public final class SHA1: DigestType { + + @usableFromInline + static let digestLength: Int = 20 // 160 / 8 + + @usableFromInline + static let blockSize: Int = 64 + + @usableFromInline + static let hashInitialValue: ContiguousArray = [ + 0x6745_2301, 0xefcd_ab89, 0x98ba_dcfe, 0x1032_5476, 0xc3d2_e1f0, + ] + + @usableFromInline + var accumulated = [UInt8]() + + @usableFromInline + var processedBytesTotalCount: Int = 0 + + @usableFromInline + var accumulatedHash: ContiguousArray = SHA1.hashInitialValue + + public init() { + } + + @inlinable + public func calculate(for bytes: [UInt8]) -> [UInt8] { + do { + return try update(withBytes: bytes.slice, isLast: true) + } catch { + return [] + } + } + + public func callAsFunction(_ bytes: [UInt8]) -> [UInt8] { + calculate(for: bytes) + } + + @usableFromInline + func process(block chunk: ArraySlice, currentHash hh: inout ContiguousArray) { + // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15, big-endian + // Extend the sixteen 32-bit words into eighty 32-bit words: + let M = UnsafeMutablePointer.allocate(capacity: 80) + M.initialize(repeating: 0, count: 80) + defer { + M.deinitialize(count: 80) + M.deallocate() + } + + for x in 0..<80 { + switch x { + case 0...15: + let start = chunk.startIndex.advanced(by: x * 4) // * MemoryLayout.size + M[x] = UInt32(bytes: chunk, fromIndex: start) + default: + M[x] = rotateLeft(M[x - 3] ^ M[x - 8] ^ M[x - 14] ^ M[x - 16], by: 1) + } + } + + var A = hh[0] + var B = hh[1] + var C = hh[2] + var D = hh[3] + var E = hh[4] + + // Main loop + for j in 0...79 { + var f: UInt32 = 0 + var k: UInt32 = 0 + + switch j { + case 0...19: + f = (B & C) | ((~B) & D) + k = 0x5a82_7999 + case 20...39: + f = B ^ C ^ D + k = 0x6ed9_eba1 + case 40...59: + f = (B & C) | (B & D) | (C & D) + k = 0x8f1b_bcdc + case 60...79: + f = B ^ C ^ D + k = 0xca62_c1d6 + default: + break + } + + let temp = rotateLeft(A, by: 5) &+ f &+ E &+ M[j] &+ k + E = D + D = C + C = rotateLeft(B, by: 30) + B = A + A = temp + } + + hh[0] = hh[0] &+ A + hh[1] = hh[1] &+ B + hh[2] = hh[2] &+ C + hh[3] = hh[3] &+ D + hh[4] = hh[4] &+ E + } +} + +extension SHA1: Updatable { + @discardableResult @inlinable + public func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> [UInt8] { + self.accumulated += bytes + + if isLast { + let lengthInBits = (processedBytesTotalCount + self.accumulated.count) * 8 + let lengthBytes = lengthInBits.bytes(totalBytes: 64 / 8) // A 64-bit representation of b + + // Step 1. Append padding + bitPadding(to: &self.accumulated, blockSize: SHA1.blockSize, allowance: 64 / 8) + + // Step 2. Append Length a 64-bit representation of lengthInBits + self.accumulated += lengthBytes + } + + var processedBytes = 0 + for chunk in self.accumulated.batched(by: SHA1.blockSize) { + if isLast || (self.accumulated.count - processedBytes) >= SHA1.blockSize { + self.process(block: chunk, currentHash: &self.accumulatedHash) + processedBytes += chunk.count + } + } + self.accumulated.removeFirst(processedBytes) + self.processedBytesTotalCount += processedBytes + + // output current hash + var result = [UInt8](repeating: 0, count: SHA1.digestLength) + var pos = 0 + for idx in 0..> 24) & 0xff) + result[pos + 1] = UInt8((h >> 16) & 0xff) + result[pos + 2] = UInt8((h >> 8) & 0xff) + result[pos + 3] = UInt8(h & 0xff) + pos += 4 + } + + // reset hash value for instance + if isLast { + self.accumulatedHash = SHA1.hashInitialValue + } + + return result + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift new file mode 100644 index 00000000..4f54e39e --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift @@ -0,0 +1,416 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +// TODO: generic for process32/64 (UInt32/UInt64) +// + +public final class SHA2: DigestType { + @usableFromInline + let variant: Variant + + @usableFromInline + let size: Int + + @usableFromInline + let blockSize: Int + + @usableFromInline + let digestLength: Int + + private let k: [UInt64] + + @usableFromInline + var accumulated = [UInt8]() + + @usableFromInline + var processedBytesTotalCount: Int = 0 + + @usableFromInline + var accumulatedHash32 = [UInt32]() + + @usableFromInline + var accumulatedHash64 = [UInt64]() + + @frozen + public enum Variant: RawRepresentable { + case sha224, sha256, sha384, sha512 + + public var digestLength: Int { + self.rawValue / 8 + } + + public var blockSize: Int { + switch self { + case .sha224, .sha256: + return 64 + case .sha384, .sha512: + return 128 + } + } + + public typealias RawValue = Int + public var rawValue: RawValue { + switch self { + case .sha224: + return 224 + case .sha256: + return 256 + case .sha384: + return 384 + case .sha512: + return 512 + } + } + + public init?(rawValue: RawValue) { + switch rawValue { + case 224: + self = .sha224 + case 256: + self = .sha256 + case 384: + self = .sha384 + case 512: + self = .sha512 + default: + return nil + } + } + + @usableFromInline + var h: [UInt64] { + switch self { + case .sha224: + return [ + 0xc105_9ed8, 0x367c_d507, 0x3070_dd17, 0xf70e_5939, 0xffc0_0b31, 0x6858_1511, 0x64f9_8fa7, + 0xbefa_4fa4, + ] + case .sha256: + return [ + 0x6a09_e667, 0xbb67_ae85, 0x3c6e_f372, 0xa54f_f53a, 0x510e_527f, 0x9b05_688c, 0x1f83_d9ab, + 0x5be0_cd19, + ] + case .sha384: + return [ + 0xcbbb_9d5d_c105_9ed8, 0x629a_292a_367c_d507, 0x9159_015a_3070_dd17, 0x152f_ecd8_f70e_5939, + 0x6733_2667_ffc0_0b31, 0x8eb4_4a87_6858_1511, 0xdb0c_2e0d_64f9_8fa7, 0x47b5_481d_befa_4fa4, + ] + case .sha512: + return [ + 0x6a09_e667_f3bc_c908, 0xbb67_ae85_84ca_a73b, 0x3c6e_f372_fe94_f82b, 0xa54f_f53a_5f1d_36f1, + 0x510e_527f_ade6_82d1, 0x9b05_688c_2b3e_6c1f, 0x1f83_d9ab_fb41_bd6b, 0x5be0_cd19_137e_2179, + ] + } + } + + @usableFromInline + var finalLength: Int { + switch self { + case .sha224: + return 7 + case .sha384: + return 6 + default: + return Int.max + } + } + } + + public init(variant: SHA2.Variant) { + self.variant = variant + switch self.variant { + case .sha224, .sha256: + self.accumulatedHash32 = variant.h.map { UInt32($0) } // FIXME: UInt64 for process64 + self.blockSize = variant.blockSize + self.size = variant.rawValue + self.digestLength = variant.digestLength + self.k = [ + 0x428a_2f98, 0x7137_4491, 0xb5c0_fbcf, 0xe9b5_dba5, 0x3956_c25b, 0x59f1_11f1, 0x923f_82a4, 0xab1c_5ed5, + 0xd807_aa98, 0x1283_5b01, 0x2431_85be, 0x550c_7dc3, 0x72be_5d74, 0x80de_b1fe, 0x9bdc_06a7, 0xc19b_f174, + 0xe49b_69c1, 0xefbe_4786, 0x0fc1_9dc6, 0x240c_a1cc, 0x2de9_2c6f, 0x4a74_84aa, 0x5cb0_a9dc, 0x76f9_88da, + 0x983e_5152, 0xa831_c66d, 0xb003_27c8, 0xbf59_7fc7, 0xc6e0_0bf3, 0xd5a7_9147, 0x06ca_6351, 0x1429_2967, + 0x27b7_0a85, 0x2e1b_2138, 0x4d2c_6dfc, 0x5338_0d13, 0x650a_7354, 0x766a_0abb, 0x81c2_c92e, 0x9272_2c85, + 0xa2bf_e8a1, 0xa81a_664b, 0xc24b_8b70, 0xc76c_51a3, 0xd192_e819, 0xd699_0624, 0xf40e_3585, 0x106a_a070, + 0x19a4_c116, 0x1e37_6c08, 0x2748_774c, 0x34b0_bcb5, 0x391c_0cb3, 0x4ed8_aa4a, 0x5b9c_ca4f, 0x682e_6ff3, + 0x748f_82ee, 0x78a5_636f, 0x84c8_7814, 0x8cc7_0208, 0x90be_fffa, 0xa450_6ceb, 0xbef9_a3f7, 0xc671_78f2, + ] + case .sha384, .sha512: + self.accumulatedHash64 = variant.h + self.blockSize = variant.blockSize + self.size = variant.rawValue + self.digestLength = variant.digestLength + self.k = [ + 0x428a_2f98_d728_ae22, 0x7137_4491_23ef_65cd, 0xb5c0_fbcf_ec4d_3b2f, 0xe9b5_dba5_8189_dbbc, + 0x3956_c25b_f348_b538, + 0x59f1_11f1_b605_d019, 0x923f_82a4_af19_4f9b, 0xab1c_5ed5_da6d_8118, 0xd807_aa98_a303_0242, + 0x1283_5b01_4570_6fbe, + 0x2431_85be_4ee4_b28c, 0x550c_7dc3_d5ff_b4e2, 0x72be_5d74_f27b_896f, 0x80de_b1fe_3b16_96b1, + 0x9bdc_06a7_25c7_1235, + 0xc19b_f174_cf69_2694, 0xe49b_69c1_9ef1_4ad2, 0xefbe_4786_384f_25e3, 0x0fc1_9dc6_8b8c_d5b5, + 0x240c_a1cc_77ac_9c65, + 0x2de9_2c6f_592b_0275, 0x4a74_84aa_6ea6_e483, 0x5cb0_a9dc_bd41_fbd4, 0x76f9_88da_8311_53b5, + 0x983e_5152_ee66_dfab, + 0xa831_c66d_2db4_3210, 0xb003_27c8_98fb_213f, 0xbf59_7fc7_beef_0ee4, 0xc6e0_0bf3_3da8_8fc2, + 0xd5a7_9147_930a_a725, + 0x06ca_6351_e003_826f, 0x1429_2967_0a0e_6e70, 0x27b7_0a85_46d2_2ffc, 0x2e1b_2138_5c26_c926, + 0x4d2c_6dfc_5ac4_2aed, + 0x5338_0d13_9d95_b3df, 0x650a_7354_8baf_63de, 0x766a_0abb_3c77_b2a8, 0x81c2_c92e_47ed_aee6, + 0x9272_2c85_1482_353b, + 0xa2bf_e8a1_4cf1_0364, 0xa81a_664b_bc42_3001, 0xc24b_8b70_d0f8_9791, 0xc76c_51a3_0654_be30, + 0xd192_e819_d6ef_5218, + 0xd699_0624_5565_a910, 0xf40e_3585_5771_202a, 0x106a_a070_32bb_d1b8, 0x19a4_c116_b8d2_d0c8, + 0x1e37_6c08_5141_ab53, + 0x2748_774c_df8e_eb99, 0x34b0_bcb5_e19b_48a8, 0x391c_0cb3_c5c9_5a63, 0x4ed8_aa4a_e341_8acb, + 0x5b9c_ca4f_7763_e373, + 0x682e_6ff3_d6b2_b8a3, 0x748f_82ee_5def_b2fc, 0x78a5_636f_4317_2f60, 0x84c8_7814_a1f0_ab72, + 0x8cc7_0208_1a64_39ec, + 0x90be_fffa_2363_1e28, 0xa450_6ceb_de82_bde9, 0xbef9_a3f7_b2c6_7915, 0xc671_78f2_e372_532b, + 0xca27_3ece_ea26_619c, + 0xd186_b8c7_21c0_c207, 0xeada_7dd6_cde0_eb1e, 0xf57d_4f7f_ee6e_d178, 0x06f0_67aa_7217_6fba, + 0x0a63_7dc5_a2c8_98a6, + 0x113f_9804_bef9_0dae, 0x1b71_0b35_131c_471b, 0x28db_77f5_2304_7d84, 0x32ca_ab7b_40c7_2493, + 0x3c9e_be0a_15c9_bebc, + 0x431d_67c4_9c10_0d4c, 0x4cc5_d4be_cb3e_42b6, 0x597f_299c_fc65_7e2a, 0x5fcb_6fab_3ad6_faec, + 0x6c44_198c_4a47_5817, + ] + } + } + + @inlinable + public func calculate(for bytes: [UInt8]) -> [UInt8] { + do { + return try update(withBytes: bytes.slice, isLast: true) + } catch { + return [] + } + } + + public func callAsFunction(_ bytes: [UInt8]) -> [UInt8] { + calculate(for: bytes) + } + + @usableFromInline + func process64(block chunk: ArraySlice, currentHash hh: inout [UInt64]) { + // break chunk into sixteen 64-bit words M[j], 0 ≤ j ≤ 15, big-endian + // Extend the sixteen 64-bit words into eighty 64-bit words: + let M = UnsafeMutablePointer.allocate(capacity: self.k.count) + M.initialize(repeating: 0, count: self.k.count) + defer { + M.deinitialize(count: self.k.count) + M.deallocate() + } + for x in 0...size + M[x] = UInt64(bytes: chunk, fromIndex: start) + default: + let s0 = rotateRight(M[x - 15], by: 1) ^ rotateRight(M[x - 15], by: 8) ^ (M[x - 15] >> 7) + let s1 = rotateRight(M[x - 2], by: 19) ^ rotateRight(M[x - 2], by: 61) ^ (M[x - 2] >> 6) + M[x] = M[x - 16] &+ s0 &+ M[x - 7] &+ s1 + } + } + + var A = hh[0] + var B = hh[1] + var C = hh[2] + var D = hh[3] + var E = hh[4] + var F = hh[5] + var G = hh[6] + var H = hh[7] + + // Main loop + for j in 0.., currentHash hh: inout [UInt32]) { + // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15, big-endian + // Extend the sixteen 32-bit words into sixty-four 32-bit words: + let M = UnsafeMutablePointer.allocate(capacity: self.k.count) + M.initialize(repeating: 0, count: self.k.count) + defer { + M.deinitialize(count: self.k.count) + M.deallocate() + } + + for x in 0...size + M[x] = UInt32(bytes: chunk, fromIndex: start) + default: + let s0 = rotateRight(M[x - 15], by: 7) ^ rotateRight(M[x - 15], by: 18) ^ (M[x - 15] >> 3) + let s1 = rotateRight(M[x - 2], by: 17) ^ rotateRight(M[x - 2], by: 19) ^ (M[x - 2] >> 10) + M[x] = M[x - 16] &+ s0 &+ M[x - 7] &+ s1 + } + } + + var A = hh[0] + var B = hh[1] + var C = hh[2] + var D = hh[3] + var E = hh[4] + var F = hh[5] + var G = hh[6] + var H = hh[7] + + // Main loop + for j in 0.., isLast: Bool = false) throws -> [UInt8] { + self.accumulated += bytes + + if isLast { + let lengthInBits = (processedBytesTotalCount + self.accumulated.count) * 8 + let lengthBytes = lengthInBits.bytes(totalBytes: self.blockSize / 8) + // A 64-bit/128-bit representation of b. blockSize fit by accident. + + // Step 1. Append padding + bitPadding(to: &self.accumulated, blockSize: self.blockSize, allowance: self.blockSize / 8) + + // Step 2. Append Length a 64-bit representation of lengthInBits + self.accumulated += lengthBytes + } + + var processedBytes = 0 + for chunk in self.accumulated.batched(by: self.blockSize) { + if isLast || (self.accumulated.count - processedBytes) >= self.blockSize { + switch self.variant { + case .sha224, .sha256: + self.process32(block: chunk, currentHash: &self.accumulatedHash32) + case .sha384, .sha512: + self.process64(block: chunk, currentHash: &self.accumulatedHash64) + } + processedBytes += chunk.count + } + } + self.accumulated.removeFirst(processedBytes) + self.processedBytesTotalCount += processedBytes + + // output current hash + var result = [UInt8](repeating: 0, count: variant.digestLength) + switch self.variant { + case .sha224, .sha256: + var pos = 0 + for idx in 0..> 24) & 0xff) + result[pos + 1] = UInt8((h >> 16) & 0xff) + result[pos + 2] = UInt8((h >> 8) & 0xff) + result[pos + 3] = UInt8(h & 0xff) + pos += 4 + } + case .sha384, .sha512: + var pos = 0 + for idx in 0..> 56) & 0xff) + result[pos + 1] = UInt8((h >> 48) & 0xff) + result[pos + 2] = UInt8((h >> 40) & 0xff) + result[pos + 3] = UInt8((h >> 32) & 0xff) + result[pos + 4] = UInt8((h >> 24) & 0xff) + result[pos + 5] = UInt8((h >> 16) & 0xff) + result[pos + 6] = UInt8((h >> 8) & 0xff) + result[pos + 7] = UInt8(h & 0xff) + pos += 8 + } + } + + // reset hash value for instance + if isLast { + switch self.variant { + case .sha224, .sha256: + // FIXME: UInt64 for process64 + self.accumulatedHash32 = self.variant.h.lazy.map { UInt32($0) } + case .sha384, .sha512: + self.accumulatedHash64 = self.variant.h + } + } + + return result + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift new file mode 100644 index 00000000..6fceac6d --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift @@ -0,0 +1,316 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +// http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf +// http://keccak.noekeon.org/specs_summary.html +// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(ucrt) +import ucrt +#endif + +public final class SHA3: DigestType { + let round_constants: [UInt64] = [ + 0x0000_0000_0000_0001, 0x0000_0000_0000_8082, 0x8000_0000_0000_808a, 0x8000_0000_8000_8000, + 0x0000_0000_0000_808b, 0x0000_0000_8000_0001, 0x8000_0000_8000_8081, 0x8000_0000_0000_8009, + 0x0000_0000_0000_008a, 0x0000_0000_0000_0088, 0x0000_0000_8000_8009, 0x0000_0000_8000_000a, + 0x0000_0000_8000_808b, 0x8000_0000_0000_008b, 0x8000_0000_0000_8089, 0x8000_0000_0000_8003, + 0x8000_0000_0000_8002, 0x8000_0000_0000_0080, 0x0000_0000_0000_800a, 0x8000_0000_8000_000a, + 0x8000_0000_8000_8081, 0x8000_0000_0000_8080, 0x0000_0000_8000_0001, 0x8000_0000_8000_8008, + ] + + public let blockSize: Int + public let digestLength: Int + public let markByte: UInt8 + + @usableFromInline + var accumulated = [UInt8]() + + @usableFromInline + var accumulatedHash: [UInt64] + + public enum Variant { + case sha224, sha256, sha384, sha512, keccak224, keccak256, keccak384, keccak512 + + var digestLength: Int { + 100 - (self.blockSize / 2) + } + + var blockSize: Int { + (1600 - self.outputLength * 2) / 8 + } + + var markByte: UInt8 { + switch self { + case .sha224, .sha256, .sha384, .sha512: + return 0x06 // 0x1F for SHAKE + case .keccak224, .keccak256, .keccak384, .keccak512: + return 0x01 + } + } + + public var outputLength: Int { + switch self { + case .sha224, .keccak224: + return 224 + case .sha256, .keccak256: + return 256 + case .sha384, .keccak384: + return 384 + case .sha512, .keccak512: + return 512 + } + } + } + + public init(variant: SHA3.Variant) { + self.blockSize = variant.blockSize + self.digestLength = variant.digestLength + self.markByte = variant.markByte + self.accumulatedHash = [UInt64](repeating: 0, count: self.digestLength) + } + + @inlinable + public func calculate(for bytes: [UInt8]) -> [UInt8] { + do { + return try update(withBytes: bytes.slice, isLast: true) + } catch { + return [] + } + } + + public func callAsFunction(_ bytes: [UInt8]) -> [UInt8] { + calculate(for: bytes) + } + + /// 1. For all pairs (x,z) such that 0≤x<5 and 0≤z.allocate(capacity: 5) + c.initialize(repeating: 0, count: 5) + defer { + c.deinitialize(count: 5) + c.deallocate() + } + let d = UnsafeMutablePointer.allocate(capacity: 5) + d.initialize(repeating: 0, count: 5) + defer { + d.deinitialize(count: 5) + d.deallocate() + } + + for i in 0..<5 { + c[i] = a[i] ^ a[i &+ 5] ^ a[i &+ 10] ^ a[i &+ 15] ^ a[i &+ 20] + } + + d[0] = rotateLeft(c[1], by: 1) ^ c[4] + d[1] = rotateLeft(c[2], by: 1) ^ c[0] + d[2] = rotateLeft(c[3], by: 1) ^ c[1] + d[3] = rotateLeft(c[4], by: 1) ^ c[2] + d[4] = rotateLeft(c[0], by: 1) ^ c[3] + + for i in 0..<5 { + a[i] ^= d[i] + a[i &+ 5] ^= d[i] + a[i &+ 10] ^= d[i] + a[i &+ 15] ^= d[i] + a[i &+ 20] ^= d[i] + } + } + + /// A′[x, y, z]=A[(x &+ 3y) mod 5, x, z] + private func π(_ a: inout [UInt64]) { + let a1 = a[1] + a[1] = a[6] + a[6] = a[9] + a[9] = a[22] + a[22] = a[14] + a[14] = a[20] + a[20] = a[2] + a[2] = a[12] + a[12] = a[13] + a[13] = a[19] + a[19] = a[23] + a[23] = a[15] + a[15] = a[4] + a[4] = a[24] + a[24] = a[21] + a[21] = a[8] + a[8] = a[16] + a[16] = a[5] + a[5] = a[3] + a[3] = a[18] + a[18] = a[17] + a[17] = a[11] + a[11] = a[7] + a[7] = a[10] + a[10] = a1 + } + + /// For all triples (x, y, z) such that 0≤x<5, 0≤y<5, and 0≤z, currentHash hh: inout [UInt64]) { + // expand + hh[0] ^= chunk[0].littleEndian + hh[1] ^= chunk[1].littleEndian + hh[2] ^= chunk[2].littleEndian + hh[3] ^= chunk[3].littleEndian + hh[4] ^= chunk[4].littleEndian + hh[5] ^= chunk[5].littleEndian + hh[6] ^= chunk[6].littleEndian + hh[7] ^= chunk[7].littleEndian + hh[8] ^= chunk[8].littleEndian + if self.blockSize > 72 { // 72 / 8, sha-512 + hh[9] ^= chunk[9].littleEndian + hh[10] ^= chunk[10].littleEndian + hh[11] ^= chunk[11].littleEndian + hh[12] ^= chunk[12].littleEndian + if self.blockSize > 104 { // 104 / 8, sha-384 + hh[13] ^= chunk[13].littleEndian + hh[14] ^= chunk[14].littleEndian + hh[15] ^= chunk[15].littleEndian + hh[16] ^= chunk[16].littleEndian + if self.blockSize > 136 { // 136 / 8, sha-256 + hh[17] ^= chunk[17].littleEndian + // FULL_SHA3_FAMILY_SUPPORT + if self.blockSize > 144 { // 144 / 8, sha-224 + hh[18] ^= chunk[18].littleEndian + hh[19] ^= chunk[19].littleEndian + hh[20] ^= chunk[20].littleEndian + hh[21] ^= chunk[21].littleEndian + hh[22] ^= chunk[22].littleEndian + hh[23] ^= chunk[23].littleEndian + hh[24] ^= chunk[24].littleEndian + } + } + } + } + + // Keccak-f + for round in 0..<24 { + self.θ(&hh) + + hh[1] = rotateLeft(hh[1], by: 1) + hh[2] = rotateLeft(hh[2], by: 62) + hh[3] = rotateLeft(hh[3], by: 28) + hh[4] = rotateLeft(hh[4], by: 27) + hh[5] = rotateLeft(hh[5], by: 36) + hh[6] = rotateLeft(hh[6], by: 44) + hh[7] = rotateLeft(hh[7], by: 6) + hh[8] = rotateLeft(hh[8], by: 55) + hh[9] = rotateLeft(hh[9], by: 20) + hh[10] = rotateLeft(hh[10], by: 3) + hh[11] = rotateLeft(hh[11], by: 10) + hh[12] = rotateLeft(hh[12], by: 43) + hh[13] = rotateLeft(hh[13], by: 25) + hh[14] = rotateLeft(hh[14], by: 39) + hh[15] = rotateLeft(hh[15], by: 41) + hh[16] = rotateLeft(hh[16], by: 45) + hh[17] = rotateLeft(hh[17], by: 15) + hh[18] = rotateLeft(hh[18], by: 21) + hh[19] = rotateLeft(hh[19], by: 8) + hh[20] = rotateLeft(hh[20], by: 18) + hh[21] = rotateLeft(hh[21], by: 2) + hh[22] = rotateLeft(hh[22], by: 61) + hh[23] = rotateLeft(hh[23], by: 56) + hh[24] = rotateLeft(hh[24], by: 14) + + self.π(&hh) + self.χ(&hh) + self.ι(&hh, round: round) + } + } +} + +extension SHA3: Updatable { + + @inlinable + public func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> [UInt8] { + self.accumulated += bytes + + if isLast { + // Add padding + let markByteIndex = self.accumulated.count + + // We need to always pad the input. Even if the input is a multiple of blockSize. + let r = self.blockSize * 8 + let q = (r / 8) - (accumulated.count % (r / 8)) + self.accumulated += [UInt8](repeating: 0, count: q) + + self.accumulated[markByteIndex] |= self.markByte + self.accumulated[self.accumulated.count - 1] |= 0x80 + } + + var processedBytes = 0 + for chunk in self.accumulated.batched(by: self.blockSize) { + if isLast || (self.accumulated.count - processedBytes) >= self.blockSize { + self.process(block: chunk.toUInt64Array().slice, currentHash: &self.accumulatedHash) + processedBytes += chunk.count + } + } + self.accumulated.removeFirst(processedBytes) + + // TODO: verify performance, reduce vs for..in + let result = self.accumulatedHash.reduce(into: [UInt8]()) { (result, value) in + result += value.bigEndian.bytes() + } + + // reset hash value for instance + if isLast { + self.accumulatedHash = [UInt64](repeating: 0, count: self.digestLength) + } + + return Array(result[0.. +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// array of bytes +extension UInt16 { + @_specialize(where T == ArraySlice) + init(bytes: T) where T.Element == UInt8, T.Index == Int { + self = UInt16(bytes: bytes, fromIndex: bytes.startIndex) + } + + @_specialize(where T == ArraySlice) + init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { + if bytes.isEmpty { + self = 0 + return + } + + let count = bytes.count + + let val0 = count > 0 ? UInt16(bytes[index.advanced(by: 0)]) << 8 : 0 + let val1 = count > 1 ? UInt16(bytes[index.advanced(by: 1)]) : 0 + + self = val0 | val1 + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift new file mode 100644 index 00000000..e0e6434f --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(ucrt) +import ucrt +#endif + +protocol _UInt32Type {} +extension UInt32: _UInt32Type {} + +/// array of bytes +extension UInt32 { + @_specialize(where T == ArraySlice) + init(bytes: T) where T.Element == UInt8, T.Index == Int { + self = UInt32(bytes: bytes, fromIndex: bytes.startIndex) + } + + @_specialize(where T == ArraySlice) + @inlinable + init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { + if bytes.isEmpty { + self = 0 + return + } + + let count = bytes.count + + let val0 = count > 0 ? UInt32(bytes[index.advanced(by: 0)]) << 24 : 0 + let val1 = count > 1 ? UInt32(bytes[index.advanced(by: 1)]) << 16 : 0 + let val2 = count > 2 ? UInt32(bytes[index.advanced(by: 2)]) << 8 : 0 + let val3 = count > 3 ? UInt32(bytes[index.advanced(by: 3)]) : 0 + + self = val0 | val1 | val2 | val3 + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift new file mode 100644 index 00000000..afcd7e47 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// array of bytes +extension UInt64 { + @_specialize(where T == ArraySlice) + init(bytes: T) where T.Element == UInt8, T.Index == Int { + self = UInt64(bytes: bytes, fromIndex: bytes.startIndex) + } + + @_specialize(where T == ArraySlice) + @inlinable + init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { + if bytes.isEmpty { + self = 0 + return + } + + let count = bytes.count + + let val0 = count > 0 ? UInt64(bytes[index.advanced(by: 0)]) << 56 : 0 + let val1 = count > 1 ? UInt64(bytes[index.advanced(by: 1)]) << 48 : 0 + let val2 = count > 2 ? UInt64(bytes[index.advanced(by: 2)]) << 40 : 0 + let val3 = count > 3 ? UInt64(bytes[index.advanced(by: 3)]) << 32 : 0 + let val4 = count > 4 ? UInt64(bytes[index.advanced(by: 4)]) << 24 : 0 + let val5 = count > 5 ? UInt64(bytes[index.advanced(by: 5)]) << 16 : 0 + let val6 = count > 6 ? UInt64(bytes[index.advanced(by: 6)]) << 8 : 0 + let val7 = count > 7 ? UInt64(bytes[index.advanced(by: 7)]) : 0 + + self = val0 | val1 | val2 | val3 | val4 | val5 | val6 | val7 + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift new file mode 100644 index 00000000..f6651013 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(ucrt) +import ucrt +#endif + +public protocol _UInt8Type {} +extension UInt8: _UInt8Type {} + +/// casting +extension UInt8 { + // cast because UInt8() because std initializer crash if value is > byte + static func with(value: UInt64) -> UInt8 { + let tmp = value & 0xff + return UInt8(tmp) + } + + static func with(value: UInt32) -> UInt8 { + let tmp = value & 0xff + return UInt8(tmp) + } + + static func with(value: UInt16) -> UInt8 { + let tmp = value & 0xff + return UInt8(tmp) + } +} + +/// Bits +extension UInt8 { + // array of bits + public func bits() -> [Bit] { + let totalBitsCount = MemoryLayout.size * 8 + + var bitsArray = [Bit](repeating: Bit.zero, count: totalBitsCount) + + for j in 0.. String { + var s = String() + let arr: [Bit] = self.bits() + for idx in arr.indices { + s += (arr[idx] == Bit.one ? "1" : "0") + if idx.advanced(by: 1) % 8 == 0 { s += " " } + } + return s + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift new file mode 100644 index 00000000..94d0efaa --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift @@ -0,0 +1,129 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// A type that supports incremental updates. For example Digest or Cipher may be updatable +/// and calculate result incerementally. +public protocol Updatable { + /// Update given bytes in chunks. + /// + /// - parameter bytes: Bytes to process. + /// - parameter isLast: Indicate if given chunk is the last one. No more updates after this call. + /// - returns: Processed partial result data or empty array. + mutating func update(withBytes bytes: ArraySlice, isLast: Bool) throws -> [UInt8] + + /// Update given bytes in chunks. + /// + /// - Parameters: + /// - bytes: Bytes to process. + /// - isLast: Indicate if given chunk is the last one. No more updates after this call. + /// - output: Resulting bytes callback. + /// - Returns: Processed partial result data or empty array. + mutating func update(withBytes bytes: ArraySlice, isLast: Bool, output: (_ bytes: [UInt8]) -> Void) throws +} + +extension Updatable { + @inlinable + public mutating func update( + withBytes bytes: ArraySlice, + isLast: Bool = false, + output: (_ bytes: [UInt8]) -> Void + ) throws { + let processed = try update(withBytes: bytes, isLast: isLast) + if !processed.isEmpty { + output(processed) + } + } + + @inlinable + public mutating func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> [UInt8] { + try self.update(withBytes: bytes, isLast: isLast) + } + + @inlinable + public mutating func update(withBytes bytes: [UInt8], isLast: Bool = false) throws -> [UInt8] { + try self.update(withBytes: bytes.slice, isLast: isLast) + } + + @inlinable + public mutating func update( + withBytes bytes: [UInt8], + isLast: Bool = false, + output: (_ bytes: [UInt8]) -> Void + ) throws { + try self.update(withBytes: bytes.slice, isLast: isLast, output: output) + } + + /// Finish updates. This may apply padding. + /// - parameter bytes: Bytes to process + /// - returns: Processed data. + @inlinable + public mutating func finish(withBytes bytes: ArraySlice) throws -> [UInt8] { + try self.update(withBytes: bytes, isLast: true) + } + + @inlinable + public mutating func finish(withBytes bytes: [UInt8]) throws -> [UInt8] { + try self.finish(withBytes: bytes.slice) + } + + /// Finish updates. May add padding. + /// + /// - Returns: Processed data + /// - Throws: Error + @inlinable + public mutating func finish() throws -> [UInt8] { + try self.update(withBytes: [], isLast: true) + } + + /// Finish updates. This may apply padding. + /// - parameter bytes: Bytes to process + /// - parameter output: Resulting data + /// - returns: Processed data. + @inlinable + public mutating func finish(withBytes bytes: ArraySlice, output: (_ bytes: [UInt8]) -> Void) throws { + let processed = try update(withBytes: bytes, isLast: true) + if !processed.isEmpty { + output(processed) + } + } + + @inlinable + public mutating func finish(withBytes bytes: [UInt8], output: (_ bytes: [UInt8]) -> Void) throws { + try self.finish(withBytes: bytes.slice, output: output) + } + + /// Finish updates. May add padding. + /// + /// - Parameter output: Processed data + /// - Throws: Error + @inlinable + public mutating func finish(output: ([UInt8]) -> Void) throws { + try self.finish(withBytes: [], output: output) + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift new file mode 100644 index 00000000..8a4ea776 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift @@ -0,0 +1,145 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +@inlinable +func rotateLeft(_ value: UInt8, by: UInt8) -> UInt8 { + ((value << by) & 0xff) | (value >> (8 - by)) +} + +@inlinable +func rotateLeft(_ value: UInt16, by: UInt16) -> UInt16 { + ((value << by) & 0xffff) | (value >> (16 - by)) +} + +@inlinable +func rotateLeft(_ value: UInt32, by: UInt32) -> UInt32 { + ((value << by) & 0xffff_ffff) | (value >> (32 - by)) +} + +@inlinable +func rotateLeft(_ value: UInt64, by: UInt64) -> UInt64 { + (value << by) | (value >> (64 - by)) +} + +@inlinable +func rotateRight(_ value: UInt16, by: UInt16) -> UInt16 { + (value >> by) | (value << (16 - by)) +} + +@inlinable +func rotateRight(_ value: UInt32, by: UInt32) -> UInt32 { + (value >> by) | (value << (32 - by)) +} + +@inlinable +func rotateRight(_ value: UInt64, by: UInt64) -> UInt64 { + ((value >> by) | (value << (64 - by))) +} + +@inlinable +func reversed(_ uint8: UInt8) -> UInt8 { + var v = uint8 + v = (v & 0xf0) >> 4 | (v & 0x0f) << 4 + v = (v & 0xcc) >> 2 | (v & 0x33) << 2 + v = (v & 0xaa) >> 1 | (v & 0x55) << 1 + return v +} + +@inlinable +func reversed(_ uint32: UInt32) -> UInt32 { + var v = uint32 + v = ((v >> 1) & 0x5555_5555) | ((v & 0x5555_5555) << 1) + v = ((v >> 2) & 0x3333_3333) | ((v & 0x3333_3333) << 2) + v = ((v >> 4) & 0x0f0f_0f0f) | ((v & 0x0f0f_0f0f) << 4) + v = ((v >> 8) & 0x00ff_00ff) | ((v & 0x00ff_00ff) << 8) + v = ((v >> 16) & 0xffff) | ((v & 0xffff) << 16) + return v +} + +@inlinable +func xor(_ left: T, _ right: V) -> ArraySlice +where + T: RandomAccessCollection, + V: RandomAccessCollection, + T.Element == UInt8, + T.Index == Int, + V.Element == UInt8, + V.Index == Int +{ + xor(left, right).slice +} + +@inlinable +func xor(_ left: T, _ right: V) -> [UInt8] +where + T: RandomAccessCollection, + V: RandomAccessCollection, + T.Element == UInt8, + T.Index == Int, + V.Element == UInt8, + V.Index == Int +{ + let length = Swift.min(left.count, right.count) + + let buf = UnsafeMutablePointer.allocate(capacity: length) + buf.initialize(repeating: 0, count: length) + defer { + buf.deinitialize(count: length) + buf.deallocate() + } + + // xor + for i in 0.. +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// All the bytes that are required to be padded are padded with zero. +/// Zero padding may not be reversible if the original file ends with one or more zero bytes. +struct ZeroPadding: PaddingProtocol { + init() { + } + + @inlinable + func add(to bytes: [UInt8], blockSize: Int) -> [UInt8] { + let paddingCount = blockSize - (bytes.count % blockSize) + if paddingCount > 0 { + return bytes + [UInt8](repeating: 0, count: paddingCount) + } + return bytes + } + + @inlinable + func remove(from bytes: [UInt8], blockSize _: Int?) -> [UInt8] { + for (idx, value) in bytes.reversed().enumerated() { + if value != 0 { + return Array(bytes[0.. [UInt8] { + Digest.sha256(data) + } + + static func hash(data: Data) -> [UInt8] { + SHA256.hash(data: data.bytes) + } + + static func hash(data: UnsafeBufferPointer) -> [UInt8] { + SHA256.hash(data: [UInt8](data)) + } +} + +// adapter for the vendored hexDigest function +extension Array where Element == UInt8 { + fileprivate func hexDigest() -> String { + // call the hexEncoded function from the Array+Extensions.swift file + self.toHexString() + } +} + +/// Amazon Web Services V4 Signer +public struct AWSSigner { + /// security credentials for accessing AWS services + public let credentials: Credential + /// service signing name. In general this is the same as the service name + public let name: String + /// AWS region you are working in + public let region: String + + static let hashedEmptyBody = SHA256.hash(data: [UInt8]()).hexDigest() + + static private let timeStampDateFormatter: DateFormatter = createTimeStampDateFormatter() + + /// Initialise the Signer class with AWS credentials + public init(credentials: Credential, name: String, region: String) { + self.credentials = credentials + self.name = name + self.region = region + } + + /// Enum for holding your body data + public enum BodyData { + case string(String) + case data(Data) + case byteBuffer(ByteBuffer) + } + + /// Generate signed headers, for a HTTP request + public func signHeaders( + url: URL, + method: HTTPMethod = .GET, + headers: HTTPHeaders = HTTPHeaders(), + body: BodyData? = nil, + date: Date = Date() + ) -> HTTPHeaders { + let bodyHash = AWSSigner.hashedPayload(body) + let dateString = AWSSigner.timestamp(date) + var headers = headers + // add date, host, sha256 and if available security token headers + headers.add(name: "X-Amz-Date", value: dateString) + headers.add(name: "host", value: url.host ?? "") + headers.add(name: "x-amz-content-sha256", value: bodyHash) + if let sessionToken = credentials.sessionToken { + headers.add(name: "x-amz-security-token", value: sessionToken) + } + + // construct signing data. + // Do this after adding the headers as it uses data from the headers + let signingData = AWSSigner.SigningData( + url: url, + method: method, + headers: headers, + body: body, + bodyHash: bodyHash, + date: dateString, + signer: self + ) + + // construct authorization string + let authorization = + "AWS4-HMAC-SHA256 " + + "Credential=\(credentials.accessKeyId)/\(signingData.date)/\(region)/\(name)/aws4_request, " + + "SignedHeaders=\(signingData.signedHeaders), " + "Signature=\(signature(signingData: signingData))" + + // add Authorization header + headers.add(name: "Authorization", value: authorization) + + return headers + } + + /// Generate a signed URL, for a HTTP request + public func signURL( + url: URL, + method: HTTPMethod = .GET, + body: BodyData? = nil, + date: Date = Date(), + expires: Int = 86400 + ) -> URL { + let headers = HTTPHeaders([("host", url.host ?? "")]) + // Create signing data + var signingData = AWSSigner.SigningData( + url: url, + method: method, + headers: headers, + body: body, + date: AWSSigner.timestamp(date), + signer: self + ) + // Construct query string. + // Start with original query strings and append all the signing info. + var query = url.query ?? "" + if query.count > 0 { + query += "&" + } + query += "X-Amz-Algorithm=AWS4-HMAC-SHA256" + query += "&X-Amz-Credential=\(credentials.accessKeyId)/\(signingData.date)/\(region)/\(name)/aws4_request" + query += "&X-Amz-Date=\(signingData.datetime)" + query += "&X-Amz-Expires=\(expires)" + query += "&X-Amz-SignedHeaders=\(signingData.signedHeaders)" + if let sessionToken = credentials.sessionToken { + query += "&X-Amz-Security-Token=\(sessionToken.uriEncode())" + } + // Split the string and sort to ensure the order of query strings is the same as AWS + query = query.split(separator: "&") + .sorted() + .joined(separator: "&") + .queryEncode() + + // update unsignedURL in the signingData + // so when the canonical request is constructed it includes all the signing query items + signingData.unsignedURL = URL(string: url.absoluteString.split(separator: "?")[0] + "?" + query)! + // FIXME: NEED TO DEAL WITH SITUATION WHERE THIS FAILS + query += "&X-Amz-Signature=\(signature(signingData: signingData))" + + // Add signature to query items and build a new Request + let signedURL = URL(string: url.absoluteString.split(separator: "?")[0] + "?" + query)! + + return signedURL + } + + /// structure used to store data used throughout the signing process + struct SigningData { + let url: URL + let method: HTTPMethod + let hashedPayload: String + let datetime: String + let headersToSign: [String: String] + let signedHeaders: String + var unsignedURL: URL + + var date: String { String(datetime.prefix(8)) } + + init( + url: URL, + method: HTTPMethod = .GET, + headers: HTTPHeaders = HTTPHeaders(), + body: BodyData? = nil, + bodyHash: String? = nil, + date: String, + signer: AWSSigner + ) { + if url.path == "" { + //URL has to have trailing slash + self.url = url.appendingPathComponent("/") + } else { + self.url = url + } + self.method = method + self.datetime = date + self.unsignedURL = self.url + + if let hash = bodyHash { + self.hashedPayload = hash + } else if signer.name == "s3" { + self.hashedPayload = "UNSIGNED-PAYLOAD" + } else { + self.hashedPayload = AWSSigner.hashedPayload(body) + } + + let headersNotToSign: Set = [ + "Authorization" + ] + var headersToSign: [String: String] = [:] + var signedHeadersArray: [String] = [] + for header in headers { + if headersNotToSign.contains(header.name) { + continue + } + headersToSign[header.name] = header.value + signedHeadersArray.append(header.name.lowercased()) + } + self.headersToSign = headersToSign + self.signedHeaders = signedHeadersArray.sorted().joined(separator: ";") + } + } + + // Stage 3 Calculating signature as in + // https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + func signature(signingData: SigningData) -> String { + + var signature = "" + do { + let kDate = try HMAC.authenticate( + for: Data(signingData.date.utf8), + using: "AWS4\(credentials.secretAccessKey)".array + ) + let kRegion = try HMAC.authenticate(for: Data(region.utf8), using: kDate) + let kService = try HMAC.authenticate(for: Data(name.utf8), using: kRegion) + let kSigning = try HMAC.authenticate(for: Data("aws4_request".utf8), using: kService) + let kSignature = try HMAC.authenticate(for: stringToSign(signingData: signingData), using: kSigning) + + signature = kSignature.toHexString() + } catch { + fatalError("HMAC computation is not supposed to throw") + } + + return signature + + // original code from Adam Fowler, using swift-crypto package: + + // let kDate = HMAC.authenticationCode(for: Data(signingData.date.utf8), using: SymmetricKey(data: Array("AWS4\(credentials.secretAccessKey)".utf8))) + // let kRegion = HMAC.authenticationCode(for: Data(region.utf8), using: SymmetricKey(data: kDate)) + // let kService = HMAC.authenticationCode(for: Data(name.utf8), using: SymmetricKey(data: kRegion)) + // let kSigning = HMAC.authenticationCode(for: Data("aws4_request".utf8), using: SymmetricKey(data: kService)) + // let kSignature = HMAC.authenticationCode(for: stringToSign(signingData: signingData), using: SymmetricKey(data: kSigning)) + // return kSignature.hexDigest() + + } + + /// Stage 2 Create the string to sign as in + // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html + func stringToSign(signingData: SigningData) -> Data { + let stringToSign = + "AWS4-HMAC-SHA256\n" + "\(signingData.datetime)\n" + "\(signingData.date)/\(region)/\(name)/aws4_request\n" + + SHA256.hash(data: canonicalRequest(signingData: signingData)).hexDigest() + return Data(stringToSign.utf8) + } + + /// Stage 1 Create the canonical request as in + // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + func canonicalRequest(signingData: SigningData) -> Data { + let canonicalHeaders = signingData.headersToSign.map { + "\($0.key.lowercased()):\($0.value.trimmingCharacters(in: CharacterSet.whitespaces))" + } + .sorted() + .joined(separator: "\n") + let canonicalRequest = + "\(signingData.method.rawValue)\n" + "\(signingData.unsignedURL.path.uriEncodeWithSlash())\n" + // should really uriEncode all the query string values + + "\(signingData.unsignedURL.query ?? "")\n" + + "\(canonicalHeaders)\n\n" + "\(signingData.signedHeaders)\n" + signingData.hashedPayload + return Data(canonicalRequest.utf8) + } + + /// Create a SHA256 hash of the Requests body + static func hashedPayload(_ payload: BodyData?) -> String { + guard let payload = payload else { return hashedEmptyBody } + let hash: String? + switch payload { + case .string(let string): + hash = SHA256.hash(data: Data(string.utf8)).hexDigest() + case .data(let data): + hash = SHA256.hash(data: data).hexDigest() + case .byteBuffer(let byteBuffer): + let byteBufferView = byteBuffer.readableBytesView + hash = byteBufferView.withContiguousStorageIfAvailable { bytes in + SHA256.hash(data: bytes).hexDigest() + } + } + if let hash = hash { + return hash + } else { + return hashedEmptyBody + } + } + + /// return a hexEncoded string buffer from an array of bytes + static func hexEncoded(_ buffer: [UInt8]) -> String { + buffer.map { String(format: "%02x", $0) }.joined(separator: "") + } + /// create timestamp dateformatter + static private func createTimeStampDateFormatter() -> DateFormatter { + let formatter = DateFormatter() + formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" + formatter.timeZone = TimeZone(abbreviation: "UTC") + formatter.locale = Locale(identifier: "en_US_POSIX") + return formatter + } + + /// return a timestamp formatted for signing requests + static func timestamp(_ date: Date) -> String { + timeStampDateFormatter.string(from: date) + } +} + +extension String { + func queryEncode() -> String { + addingPercentEncoding(withAllowedCharacters: String.queryAllowedCharacters) ?? self + } + + func uriEncode() -> String { + addingPercentEncoding(withAllowedCharacters: String.uriAllowedCharacters) ?? self + } + + func uriEncodeWithSlash() -> String { + addingPercentEncoding(withAllowedCharacters: String.uriAllowedWithSlashCharacters) ?? self + } + + static let uriAllowedWithSlashCharacters = CharacterSet( + charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/" + ) + static let uriAllowedCharacters = CharacterSet( + charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + ) + static let queryAllowedCharacters = CharacterSet(charactersIn: "/;+").inverted +} + +extension Sequence where Element == UInt8 { + /// return a hexEncoded string buffer from an array of bytes + public func hexDigest() -> String { + self.map { String(format: "%02x", $0) }.joined(separator: "") + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift b/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift new file mode 100644 index 00000000..cee229a0 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A rudimentary helper for extracting options and flags from a string list representing command line arguments. The idea is to extract all known options and flags, leaving just the positional arguments. This does not handle well the case in which positional arguments (or option argument values) happen to have the same name as an option or a flag. It only handles the long `--` form of options, but it does respect `--` as an indication that all remaining arguments are positional. +public struct ArgumentExtractor { + private var args: [String] + private let literals: [String] + + /// Initializes a ArgumentExtractor with a list of strings from which to extract flags and options. If the list contains `--`, any arguments that follow it are considered to be literals. + public init(_ arguments: [String]) { + // Split the array on the first `--`, if there is one. Everything after that is a literal. + let parts = arguments.split(separator: "--", maxSplits: 1, omittingEmptySubsequences: false) + self.args = Array(parts[0]) + self.literals = Array(parts.count == 2 ? parts[1] : []) + } + + /// Extracts options of the form `-- ` or `--=` from the remaining arguments, and returns the extracted values. + public mutating func extractOption(named name: String) -> [String] { + var values: [String] = [] + var idx = 0 + while idx < args.count { + var arg = args[idx] + if arg == "--\(name)" { + args.remove(at: idx) + if idx < args.count { + let val = args[idx] + values.append(val) + args.remove(at: idx) + } + } else if arg.starts(with: "--\(name)=") { + arg.removeFirst(2 + name.count + 1) + values.append(arg) + args.remove(at: idx) + } else { + idx += 1 + } + } + return values + } + + /// Extracts flags of the form `--` from the remaining arguments, and returns the count. + public mutating func extractFlag(named name: String) -> Int { + var count = 0 + var idx = 0 + while idx < args.count { + let arg = args[idx] + if arg == "--\(name)" { + args.remove(at: idx) + count += 1 + } else { + idx += 1 + } + } + return count + } + + /// Returns any unextracted flags or options (based strictly on whether remaining arguments have a "--" prefix). + public var unextractedOptionsOrFlags: [String] { + args.filter { $0.hasPrefix("--") } + } + + /// Returns all remaining arguments, including any literals after the first `--` if there is one. + public var remainingArguments: [String] { + args + literals + } +} diff --git a/Plugins/AWSLambdaPackager/Plugin.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift similarity index 63% rename from Plugins/AWSLambdaPackager/Plugin.swift rename to Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift index 10d9c8a3..c1f8b45a 100644 --- a/Plugins/AWSLambdaPackager/Plugin.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftAWSLambdaRuntime open source project // -// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,60 +12,44 @@ // //===----------------------------------------------------------------------===// +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation -import PackagePlugin +#endif -@main @available(macOS 15.0, *) -struct AWSLambdaPackager: CommandPlugin { - func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { - let configuration = try Configuration(context: context, arguments: arguments) +struct Builder { + func build(arguments: [String]) async throws { + let configuration = try BuilderConfiguration(arguments: arguments) if configuration.help { self.displayHelpMessage() return } - guard !configuration.products.isEmpty else { - throw Errors.unknownProduct("no appropriate products found to package") - } + let builtProducts: [String: URL] - if configuration.products.count > 1 && !configuration.explicitProducts { - let productNames = configuration.products.map(\.name) - print( - "No explicit products named, building all executable products: '\(productNames.joined(separator: "', '"))'" - ) - } - - let builtProducts: [LambdaProduct: URL] - if self.isAmazonLinux2() { - // build directly on the machine - builtProducts = try self.build( - packageIdentity: context.package.id, - products: configuration.products, - buildConfiguration: configuration.buildConfiguration, - verboseLogging: configuration.verboseLogging - ) - } else { - // build with docker - builtProducts = try self.buildInDocker( - packageIdentity: context.package.id, - packageDirectory: context.package.directoryURL, - products: configuration.products, - toolsProvider: { name in try context.tool(named: name).url }, - outputDirectory: configuration.outputDirectory, - baseImage: configuration.baseDockerImage, - disableDockerImageUpdate: configuration.disableDockerImageUpdate, - buildConfiguration: configuration.buildConfiguration, - verboseLogging: configuration.verboseLogging - ) - } + // build with docker + // TODO: check if dockerToolPath is provided + // When not provided, it means we're building on Amazon Linux 2 + builtProducts = try self.buildInDocker( + packageIdentity: configuration.packageID, + packageDirectory: configuration.packageDirectory, + products: configuration.products, + dockerToolPath: configuration.dockerToolPath, + outputDirectory: configuration.outputDirectory, + baseImage: configuration.baseDockerImage, + disableDockerImageUpdate: configuration.disableDockerImageUpdate, + buildConfiguration: configuration.buildConfiguration, + verboseLogging: configuration.verboseLogging + ) // create the archive let archives = try self.package( - packageName: context.package.displayName, + packageName: configuration.packageDisplayName, products: builtProducts, - toolsProvider: { name in try context.tool(named: name).url }, + zipToolPath: configuration.zipToolPath, outputDirectory: configuration.outputDirectory, verboseLogging: configuration.verboseLogging ) @@ -74,22 +58,21 @@ struct AWSLambdaPackager: CommandPlugin { "\(archives.count > 0 ? archives.count.description : "no") archive\(archives.count != 1 ? "s" : "") created" ) for (product, archivePath) in archives { - print(" * \(product.name) at \(archivePath.path())") + print(" * \(product) at \(archivePath.path())") } } private func buildInDocker( - packageIdentity: Package.ID, + packageIdentity: String, packageDirectory: URL, - products: [Product], - toolsProvider: (String) throws -> URL, + products: [String], + dockerToolPath: URL, outputDirectory: URL, baseImage: String, disableDockerImageUpdate: Bool, - buildConfiguration: PackageManager.BuildConfiguration, + buildConfiguration: BuildConfiguration, verboseLogging: Bool - ) throws -> [LambdaProduct: URL] { - let dockerToolPath = try toolsProvider("docker") + ) throws -> [String: URL] { print("-------------------------------------------------------------------------") print("building \"\(packageIdentity)\" in docker") @@ -101,7 +84,7 @@ struct AWSLambdaPackager: CommandPlugin { try Utils.execute( executable: dockerToolPath, arguments: ["pull", baseImage], - logLevel: verboseLogging ? .debug : .output + logLevel: verboseLogging ? .debug : .silent ) } @@ -116,18 +99,18 @@ struct AWSLambdaPackager: CommandPlugin { logLevel: verboseLogging ? .debug : .silent ) guard let buildPathOutput = dockerBuildOutputPath.split(separator: "\n").last else { - throw Errors.failedParsingDockerOutput(dockerBuildOutputPath) + throw BuilderErrors.failedParsingDockerOutput(dockerBuildOutputPath) } let buildOutputPath = URL( string: buildPathOutput.replacingOccurrences(of: "/workspace/", with: packageDirectory.description) )! // build the products - var builtProducts = [LambdaProduct: URL]() + var builtProducts = [String: URL]() for product in products { - print("building \"\(product.name)\"") + print("building \"\(product)\"") let buildCommand = - "swift build -c \(buildConfiguration.rawValue) --product \(product.name) --static-swift-stdlib" + "swift build -c \(buildConfiguration.rawValue) --product \(product) --static-swift-stdlib" if let localPath = ProcessInfo.processInfo.environment["LAMBDA_USE_LOCAL_DEPS"] { // when developing locally, we must have the full swift-aws-lambda-runtime project in the container // because Examples' Package.swift have a dependency on ../.. @@ -152,66 +135,35 @@ struct AWSLambdaPackager: CommandPlugin { logLevel: verboseLogging ? .debug : .output ) } - let productPath = buildOutputPath.appending(path: product.name) + let productPath = buildOutputPath.appending(path: product) guard FileManager.default.fileExists(atPath: productPath.path()) else { - Diagnostics.error("expected '\(product.name)' binary at \"\(productPath.path())\"") - throw Errors.productExecutableNotFound(product.name) + print("expected '\(product)' binary at \"\(productPath.path())\"") + throw BuilderErrors.productExecutableNotFound(product) } builtProducts[.init(product)] = productPath } return builtProducts } - private func build( - packageIdentity: Package.ID, - products: [Product], - buildConfiguration: PackageManager.BuildConfiguration, - verboseLogging: Bool - ) throws -> [LambdaProduct: URL] { - print("-------------------------------------------------------------------------") - print("building \"\(packageIdentity)\"") - print("-------------------------------------------------------------------------") - - var results = [LambdaProduct: URL]() - for product in products { - print("building \"\(product.name)\"") - var parameters = PackageManager.BuildParameters() - parameters.configuration = buildConfiguration - parameters.otherSwiftcFlags = ["-static-stdlib"] - parameters.logging = verboseLogging ? .verbose : .concise - - let result = try packageManager.build( - .product(product.name), - parameters: parameters - ) - guard let artifact = result.executableArtifact(for: product) else { - throw Errors.productExecutableNotFound(product.name) - } - results[.init(product)] = artifact.url - } - return results - } - // TODO: explore using ziplib or similar instead of shelling out private func package( packageName: String, - products: [LambdaProduct: URL], - toolsProvider: (String) throws -> URL, + products: [String: URL], + zipToolPath: URL, outputDirectory: URL, verboseLogging: Bool - ) throws -> [LambdaProduct: URL] { - let zipToolPath = try toolsProvider("zip") + ) throws -> [String: URL] { - var archives = [LambdaProduct: URL]() + var archives = [String: URL]() for (product, artifactPath) in products { print("-------------------------------------------------------------------------") - print("archiving \"\(product.name)\"") + print("archiving \"\(product)\"") print("-------------------------------------------------------------------------") // prep zipfile location - let workingDirectory = outputDirectory.appending(path: product.name) - let zipfilePath = workingDirectory.appending(path: "\(product.name).zip") + let workingDirectory = outputDirectory.appending(path: product) + let zipfilePath = workingDirectory.appending(path: "\(product).zip") if FileManager.default.fileExists(atPath: workingDirectory.path()) { try FileManager.default.removeItem(atPath: workingDirectory.path()) } @@ -288,16 +240,6 @@ struct AWSLambdaPackager: CommandPlugin { return archives } - private func isAmazonLinux2() -> Bool { - if let data = FileManager.default.contents(atPath: "/etc/system-release"), - let release = String(data: data, encoding: .utf8) - { - return release.hasPrefix("Amazon Linux release 2") - } else { - return false - } - } - private func displayHelpMessage() { print( """ @@ -305,7 +247,7 @@ struct AWSLambdaPackager: CommandPlugin { REQUIREMENTS: To use this plugin, you must have docker installed and started. - USAGE: swift package --allow-network-connections docker archive + USAGE: swift package --allow-network-connections docker lambda-build [--help] [--verbose] [--output-path ] [--products ] @@ -313,7 +255,7 @@ struct AWSLambdaPackager: CommandPlugin { [--swift-version ] [--base-docker-image ] [--disable-docker-image-update] - + OPTIONS: --verbose Produce verbose output for debugging. @@ -323,7 +265,7 @@ struct AWSLambdaPackager: CommandPlugin { (default is taken from Package.swift) --configuration The build configuration (debug or release) (default is release) - --swift-version The swift version to use for building. + --swift-version The swift version to use for building. (default is latest) This parameter cannot be used when --base-docker-image is specified. --base-docker-image The name of the base docker image to use for the build. @@ -336,24 +278,34 @@ struct AWSLambdaPackager: CommandPlugin { } } -@available(macOS 15.0, *) -private struct Configuration: CustomStringConvertible { +private struct BuilderConfiguration: CustomStringConvertible { + + // passed by the user public let help: Bool public let outputDirectory: URL - public let products: [Product] - public let explicitProducts: Bool - public let buildConfiguration: PackageManager.BuildConfiguration + public let products: [String] + public let buildConfiguration: BuildConfiguration public let verboseLogging: Bool public let baseDockerImage: String public let disableDockerImageUpdate: Bool - public init( - context: PluginContext, - arguments: [String] - ) throws { + // passed by the plugin + public let packageID: String + public let packageDisplayName: String + public let packageDirectory: URL + public let dockerToolPath: URL + public let zipToolPath: URL + + public init(arguments: [String]) throws { var argumentExtractor = ArgumentExtractor(arguments) + let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 let outputPathArgument = argumentExtractor.extractOption(named: "output-path") + let packageIDArgument = argumentExtractor.extractOption(named: "package-id") + let packageDisplayNameArgument = argumentExtractor.extractOption(named: "package-display-name") + let packageDirectoryArgument = argumentExtractor.extractOption(named: "package-directory") + let dockerToolPathArgument = argumentExtractor.extractOption(named: "docker-tool-path") + let zipToolPathArgument = argumentExtractor.extractOption(named: "zip-tool-path") let productsArgument = argumentExtractor.extractOption(named: "products") let configurationArgument = argumentExtractor.extractOption(named: "configuration") let swiftVersionArgument = argumentExtractor.extractOption(named: "swift-version") @@ -367,46 +319,59 @@ private struct Configuration: CustomStringConvertible { // verbose logging required ? self.verboseLogging = verboseArgument - if let outputPath = outputPathArgument.first { - #if os(Linux) - var isDirectory: Bool = false - #else - var isDirectory: ObjCBool = false - #endif - guard FileManager.default.fileExists(atPath: outputPath, isDirectory: &isDirectory) - else { - throw Errors.invalidArgument("invalid output directory '\(outputPath)'") - } - self.outputDirectory = URL(string: outputPath)! - } else { - self.outputDirectory = context.pluginWorkDirectoryURL.appending(path: "\(AWSLambdaPackager.self)") + // package id + guard !packageIDArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--package-id argument is required") } + self.packageID = packageIDArgument.first! - self.explicitProducts = !productsArgument.isEmpty - if self.explicitProducts { - let products = try context.package.products(named: productsArgument) - for product in products { - guard product is ExecutableProduct else { - throw Errors.invalidArgument("product named '\(product.name)' is not an executable product") - } - } - self.products = products + // package display name + guard !packageDisplayNameArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--package-display-name argument is required") + } + self.packageDisplayName = packageDisplayNameArgument.first! - } else { - self.products = context.package.products.filter { $0 is ExecutableProduct } + // package directory + guard !packageDirectoryArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--package-directory argument is required") } + self.packageDirectory = URL(fileURLWithPath: packageDirectoryArgument.first!) - if let buildConfigurationName = configurationArgument.first { - guard let buildConfiguration = PackageManager.BuildConfiguration(rawValue: buildConfigurationName) else { - throw Errors.invalidArgument("invalid build configuration named '\(buildConfigurationName)'") - } - self.buildConfiguration = buildConfiguration - } else { - self.buildConfiguration = .release + // docker tool path + guard !dockerToolPathArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--docker-tool-path argument is required") } + self.dockerToolPath = URL(fileURLWithPath: dockerToolPathArgument.first!) + + // zip tool path + guard !zipToolPathArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--zip-tool-path argument is required") + } + self.zipToolPath = URL(fileURLWithPath: zipToolPathArgument.first!) + + // output directory + guard !outputPathArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--output-path is required") + } + self.outputDirectory = URL(fileURLWithPath: outputPathArgument.first!) + + // products + guard !productsArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--products argument is required") + } + self.products = productsArgument.flatMap { $0.split(separator: ",").map(String.init) } + + // build configuration + guard let buildConfigurationName = configurationArgument.first else { + throw BuilderErrors.invalidArgument("--configuration argument is equired") + } + guard let _buildConfiguration = BuildConfiguration(rawValue: buildConfigurationName) else { + throw BuilderErrors.invalidArgument("invalid build configuration named '\(buildConfigurationName)'") + } + self.buildConfiguration = _buildConfiguration guard !(!swiftVersionArgument.isEmpty && !baseDockerImageArgument.isEmpty) else { - throw Errors.invalidArgument("--swift-version and --base-docker-image are mutually exclusive") + throw BuilderErrors.invalidArgument("--swift-version and --base-docker-image are mutually exclusive") } let swiftVersion = swiftVersionArgument.first ?? .none // undefined version will yield the latest docker image @@ -427,45 +392,21 @@ private struct Configuration: CustomStringConvertible { """ { outputDirectory: \(self.outputDirectory) - products: \(self.products.map(\.name)) + products: \(self.products) buildConfiguration: \(self.buildConfiguration) + dockerToolPath: \(self.dockerToolPath) baseDockerImage: \(self.baseDockerImage) disableDockerImageUpdate: \(self.disableDockerImageUpdate) + zipToolPath: \(self.zipToolPath) + packageID: \(self.packageID) + packageDisplayName: \(self.packageDisplayName) + packageDirectory: \(self.packageDirectory) } """ } } -private enum ProcessLogLevel: Comparable { - case silent - case output(outputIndent: Int) - case debug(outputIndent: Int) - - var naturalOrder: Int { - switch self { - case .silent: - return 0 - case .output: - return 1 - case .debug: - return 2 - } - } - - static var output: Self { - .output(outputIndent: 2) - } - - static var debug: Self { - .debug(outputIndent: 2) - } - - static func < (lhs: ProcessLogLevel, rhs: ProcessLogLevel) -> Bool { - lhs.naturalOrder < rhs.naturalOrder - } -} - -private enum Errors: Error, CustomStringConvertible { +private enum BuilderErrors: Error, CustomStringConvertible { case invalidArgument(String) case unsupportedPlatform(String) case unknownProduct(String) @@ -494,38 +435,7 @@ private enum Errors: Error, CustomStringConvertible { } } -private struct LambdaProduct: Hashable { - let underlying: Product - - init(_ underlying: Product) { - self.underlying = underlying - } - - var name: String { - self.underlying.name - } - - func hash(into hasher: inout Hasher) { - self.underlying.id.hash(into: &hasher) - } - - static func == (lhs: Self, rhs: Self) -> Bool { - lhs.underlying.id == rhs.underlying.id - } -} - -extension PackageManager.BuildResult { - // find the executable produced by the build - func executableArtifact(for product: Product) -> PackageManager.BuildResult.BuiltArtifact? { - let executables = self.builtArtifacts.filter { - $0.kind == .executable && $0.url.lastPathComponent == product.name - } - guard !executables.isEmpty else { - return nil - } - guard executables.count == 1, let executable = executables.first else { - return nil - } - return executable - } +private enum BuildConfiguration: String { + case debug + case release } diff --git a/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift b/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift new file mode 100644 index 00000000..480ff2d4 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +struct Deployer { + + func deploy(arguments: [String]) async throws { + let configuration = try DeployerConfiguration(arguments: arguments) + + if configuration.help { + self.displayHelpMessage() + return + } + + //FIXME: use Logger + print("TODO: deploy") + } + + private func displayHelpMessage() { + print( + """ + OVERVIEW: A SwiftPM plugin to deploy a Lambda function. + + USAGE: swift package lambda-deploy + [--with-url] + [--help] [--verbose] + + OPTIONS: + --with-url Add an URL to access the Lambda function + --verbose Produce verbose output for debugging. + --help Show help information. + """ + ) + } +} + +private struct DeployerConfiguration: CustomStringConvertible { + public let help: Bool + public let verboseLogging: Bool + + public init(arguments: [String]) throws { + var argumentExtractor = ArgumentExtractor(arguments) + let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 + let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 + + // help required ? + self.help = helpArgument + + // verbose logging required ? + self.verboseLogging = verboseArgument + } + + var description: String { + """ + { + verboseLogging: \(self.verboseLogging) + } + """ + } +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift b/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift new file mode 100644 index 00000000..8c3d0f28 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift @@ -0,0 +1,120 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +struct Initializer { + + private let destFileName = "Sources/main.swift" + + func initialize(arguments: [String]) async throws { + + let configuration = try InitializerConfiguration(arguments: arguments) + + if configuration.help { + self.displayHelpMessage() + return + } + + let destFileURL = configuration.destinationDir.appendingPathComponent(destFileName) + do { + + let template = TemplateType.template(for: configuration.templateType) + try template.write(to: destFileURL, atomically: true, encoding: .utf8) + + if configuration.verboseLogging { + print("File created at: \(destFileURL)") + } + + print("✅ Lambda function written to \(destFileName)") + print("📦 You can now package with: 'swift package lambda-build'") + } catch { + print("🛑Failed to create the Lambda function file: \(error)") + } + } + + private func displayHelpMessage() { + print( + """ + OVERVIEW: A SwiftPM plugin to scaffold a HelloWorld Lambda function. + By default, it creates a Lambda function that receives a JSON + document and responds with another JSON document. + + USAGE: swift package lambda-init + [--help] [--verbose] + [--with-url] + [--allow-writing-to-package-directory] + + OPTIONS: + --with-url Create a Lambda function exposed with an URL + --allow-writing-to-package-directory Don't ask for permissions to write files. + --verbose Produce verbose output for debugging. + --help Show help information. + """ + ) + } +} + +private enum TemplateType { + case `default` + case url + + static func template(for type: TemplateType) -> String { + switch type { + case .default: return functionWithJSONTemplate + case .url: return functionWithUrlTemplate + } + } +} + +private struct InitializerConfiguration: CustomStringConvertible { + public let help: Bool + public let verboseLogging: Bool + public let destinationDir: URL + public let templateType: TemplateType + + public init(arguments: [String]) throws { + var argumentExtractor = ArgumentExtractor(arguments) + let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 + let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 + let destDirArgument = argumentExtractor.extractOption(named: "dest-dir") + let templateURLArgument = argumentExtractor.extractFlag(named: "with-url") > 0 + + // help required ? + self.help = helpArgument + + // verbose logging required ? + self.verboseLogging = verboseArgument + + // dest dir + self.destinationDir = URL(fileURLWithPath: destDirArgument[0]) + + // template type. Default is the JSON one + self.templateType = templateURLArgument ? .url : .default + } + + var description: String { + """ + { + verboseLogging: \(self.verboseLogging) + destinationDir: \(self.destinationDir) + templateType: \(self.templateType) + } + """ + } +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift b/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift new file mode 100644 index 00000000..404d743b --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +let functionWithUrlTemplate = #""" + import AWSLambdaRuntime + import AWSLambdaEvents + + // in this example we receive a FunctionURLRequest and we return a FunctionURLResponse + // https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads + + let runtime = LambdaRuntime { + (event: FunctionURLRequest, context: LambdaContext) -> FunctionURLResponse in + + guard let name = event.queryStringParameters?["name"] else { + return FunctionURLResponse(statusCode: .badRequest) + } + + return FunctionURLResponse(statusCode: .ok, body: #"{ "message" : "Hello \#\#(name)" } "#) + } + + try await runtime.run() + """# + +let functionWithJSONTemplate = #""" + import AWSLambdaRuntime + + // the data structure to represent the input parameter + struct HelloRequest: Decodable { + let name: String + let age: Int + } + + // the data structure to represent the output response + struct HelloResponse: Encodable { + let greetings: String + } + + // in this example we receive a HelloRequest JSON and we return a HelloResponse JSON + + // the Lambda runtime + let runtime = LambdaRuntime { + (event: HelloRequest, context: LambdaContext) in + + HelloResponse( + greetings: "Hello \(event.name). You look \(event.age > 30 ? "younger" : "older") than your age." + ) + } + + // start the loop + try await runtime.run() + """# diff --git a/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift b/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift new file mode 100644 index 00000000..4cf5727d --- /dev/null +++ b/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Logging +import Testing + +@testable import AWSLambdaPluginHelper + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@Suite +struct SignerTests { + let credentials: Credential = StaticCredential(accessKeyId: "MYACCESSKEY", secretAccessKey: "MYSECRETACCESSKEY") + + @Test + func testSignGetHeaders() { + let signer = AWSSigner(credentials: credentials, name: "glacier", region: "us-east-1") + let headers = signer.signHeaders( + url: URL(string: "https://glacier.us-east-1.amazonaws.com/-/vaults")!, + method: .GET, + headers: ["x-amz-glacier-version": "2012-06-01"], + date: Date(timeIntervalSinceReferenceDate: 2_000_000) + ) + #expect( + headers["Authorization"].first + == "AWS4-HMAC-SHA256 Credential=MYACCESSKEY/20010124/us-east-1/glacier/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-glacier-version, Signature=acfa9b03fca6b098d7b88bfd9bbdb4687f5b34e944a9c6ed9f4814c1b0b06d62" + ) + } + + @Test + func testSignPutHeaders() { + let signer = AWSSigner(credentials: credentials, name: "sns", region: "eu-west-1") + let headers = signer.signHeaders( + url: URL(string: "https://sns.eu-west-1.amazonaws.com/")!, + method: .POST, + headers: ["Content-Type": "application/x-www-form-urlencoded; charset=utf-8"], + body: .string("Action=ListTopics&Version=2010-03-31"), + date: Date(timeIntervalSinceReferenceDate: 200) + ) + #expect( + headers["Authorization"].first + == "AWS4-HMAC-SHA256 Credential=MYACCESSKEY/20010101/eu-west-1/sns/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=1d29943055a8ad094239e8de06082100f2426ebbb2c6a5bbcbb04c63e6a3f274" + ) + } + + @Test + func testSignS3GetURL() { + let signer = AWSSigner(credentials: credentials, name: "s3", region: "us-east-1") + let url = signer.signURL( + url: URL(string: "https://s3.us-east-1.amazonaws.com/")!, + method: .GET, + date: Date(timeIntervalSinceReferenceDate: 100000) + ) + #expect( + url.absoluteString + == "https://s3.us-east-1.amazonaws.com/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MYACCESSKEY%2F20010102%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20010102T034640Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=27957103c8bfdff3560372b1d85976ed29c944f34295eca2d4fdac7fc02c375a" + ) + } + + @Test + func testSignS3PutURL() { + let signer = AWSSigner(credentials: credentials, name: "s3", region: "eu-west-1") + let url = signer.signURL( + url: URL(string: "https://test-bucket.s3.amazonaws.com/test-put.txt")!, + method: .PUT, + body: .string("Testing signed URLs"), + date: Date(timeIntervalSinceReferenceDate: 100000) + ) + #expect( + url.absoluteString + == "https://test-bucket.s3.amazonaws.com/test-put.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MYACCESSKEY%2F20010102%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20010102T034640Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=13d665549a6ea5eb6a1615ede83440eaed3e0ee25c964e62d188c896d916d96f" + ) + } +} diff --git a/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift b/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift new file mode 100644 index 00000000..7a65e304 --- /dev/null +++ b/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Logging +import Testing + +@testable import AWSLambdaPluginHelper + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@Suite +struct CryptoTests { + + @Test + func testSHA256() { + + // given + let input = "hello world" + let expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" + + // when + let result = Digest.sha256(input.array).toHexString() + + // then + #expect(result == expected) + } + + @Test + func testHMAC() throws { + + // given + let input = "hello world" + let secret = "secretkey" + let expected = "ae6cd2605d622316564d1f76bfc0c04f89d9fafb14f45b3e18c2a3e28bdef29d" + + // when + let authenticator = HMAC(key: secret.array, variant: .sha2(.sha256)) + + #expect(throws: Never.self) { + let result = try authenticator.authenticate(input.array).toHexString() + // then + #expect(result == expected) + } + + } + + @Test + func testHMACExtension() throws { + + // given + let input = "hello world" + let secret = "secretkey" + let expected = "ae6cd2605d622316564d1f76bfc0c04f89d9fafb14f45b3e18c2a3e28bdef29d" + + // when + let result = try HMAC.authenticate(for: input.array, using: secret.array).toHexString() + + // then + #expect(result == expected) + + } + +}