diff --git a/Package.resolved b/Package.resolved index 26763fb..5d16f6d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -2,57 +2,147 @@ "object": { "pins": [ { - "package": "Bits", - "repositoryURL": "https://github.com/vapor/bits.git", + "package": "Console", + "repositoryURL": "https://github.com/vapor/console.git", "state": { "branch": null, - "revision": "c32f5e6ae2007dccd21a92b7e33eba842dd80d2f", - "version": "1.1.0" + "revision": "038e30ec9004fb1915d14d964a3facc1ec5c80f4", + "version": "3.0.0" } }, { - "package": "CTLS", - "repositoryURL": "https://github.com/vapor/ctls.git", + "package": "Core", + "repositoryURL": "https://github.com/vapor/core.git", "state": { "branch": null, - "revision": "fddec6a4643d6e85b6bb6dc54b1b5cdbabd395d2", - "version": "1.1.2" + "revision": "ce64e70a48adf54835d040ba4c4dab431e2cd020", + "version": "3.1.1" } }, { - "package": "Core", - "repositoryURL": "https://github.com/vapor/core.git", + "package": "Crypto", + "repositoryURL": "https://github.com/vapor/crypto.git", "state": { "branch": null, - "revision": "b8330808f4f6b69941961afe8ad6b015562f7b7c", - "version": "2.1.2" + "revision": "a5598aba7a118c29b224122c16598a1919b9e67d", + "version": "3.0.1" } }, { - "package": "Crypto", - "repositoryURL": "https://github.com/vapor/crypto.git", + "package": "DatabaseKit", + "repositoryURL": "https://github.com/vapor/database-kit.git", "state": { "branch": null, - "revision": "bf4470b9da79024aab79c85de80374f6c29e3864", - "version": "2.1.1" + "revision": "e594e658cc001e04b8d4ad13881d8714c510f94f", + "version": "1.0.0-rc.2.2.2" } }, { - "package": "Debugging", - "repositoryURL": "https://github.com/vapor/debugging.git", + "package": "Engine", + "repositoryURL": "https://github.com/vapor/engine.git", "state": { "branch": null, - "revision": "49c5e8f0a7cb5456a8f7c72c6cd9f1553e5885a8", - "version": "1.1.0" + "revision": "2419e37d689b78c9197b2f38cd8f2901cd7dcf9e", + "version": "3.0.0-rc.2.3.1" } }, { - "package": "Random", - "repositoryURL": "https://github.com/vapor/random.git", + "package": "Multipart", + "repositoryURL": "https://github.com/vapor/multipart.git", "state": { "branch": null, - "revision": "d7c4397d125caba795d14d956efacfe2a27a63d0", - "version": "1.2.0" + "revision": "d7b641af117910e66781022c1c82638216cad62c", + "version": "3.0.0" + } + }, + { + "package": "Routing", + "repositoryURL": "https://github.com/vapor/routing.git", + "state": { + "branch": null, + "revision": "2fc1d4de22a54848b35ad17b3e7f7816f19ebf90", + "version": "3.0.0-rc.2" + } + }, + { + "package": "Service", + "repositoryURL": "https://github.com/vapor/service.git", + "state": { + "branch": null, + "revision": "281a70b69783891900be31a9e70051b6fe19e146", + "version": "1.0.0" + } + }, + { + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", + "state": { + "branch": null, + "revision": "3275ff7c9c791a628631c2c51b39fd94346b2492", + "version": "1.4.2" + } + }, + { + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", + "state": { + "branch": null, + "revision": "ea006b6368dbd9dbfd297deb6ddb3f070b72d043", + "version": "1.0.1" + } + }, + { + "package": "swift-nio-ssl-support", + "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", + "state": { + "branch": null, + "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", + "version": "1.0.0" + } + }, + { + "package": "swift-nio-zlib-support", + "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", + "state": { + "branch": null, + "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version": "1.0.0" + } + }, + { + "package": "TemplateKit", + "repositoryURL": "https://github.com/vapor/template-kit.git", + "state": { + "branch": null, + "revision": "497b987a79291c3743fe4ba6f17eadcdf20c1728", + "version": "1.0.0" + } + }, + { + "package": "Validation", + "repositoryURL": "https://github.com/vapor/validation.git", + "state": { + "branch": null, + "revision": "ab6c5a352d97c8687b91ed4963aef8e7cfe0795b", + "version": "2.0.0" + } + }, + { + "package": "Vapor", + "repositoryURL": "https://github.com/vapor/vapor.git", + "state": { + "branch": null, + "revision": "26b5c4032f236cc78e6fd3a51ac6d8aceb5a3a4f", + "version": "3.0.0-rc.2.4.1" + } + }, + { + "package": "VaporTestTools", + "repositoryURL": "https://github.com/LiveUI/VaporTestTools.git", + "state": { + "branch": "master", + "revision": "0791548a30e6a805bf9291b0ad6707c4085cc3ee", + "version": null } } ] diff --git a/Package.swift b/Package.swift index 9c432ff..4b557df 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,36 @@ +// swift-tools-version:4.0 import PackageDescription let package = Package( - name: "S3SignerAWS", - targets: [], + name: "S3", + products: [ + .library(name: "S3", targets: ["S3"]), + .library(name: "S3Signer", targets: ["S3Signer"]), + .library(name: "S3TestTools", targets: ["S3TestTools"]) + ], dependencies: [ - .Package(url: "https://github.com/vapor/crypto.git", majorVersion: 2) + .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0-rc.2"), + .package(url: "https://github.com/LiveUI/VaporTestTools.git", .branch("master")) + ], + targets: [ + .target(name: "S3", dependencies: [ + "Vapor", + "S3Signer" + ] + ), + .target(name: "S3Signer", dependencies: [ + "Vapor" + ] + ), + .target(name: "S3TestTools", dependencies: [ + "Vapor", + "VaporTestTools", + "S3" + ] + ), + .testTarget(name: "S3Tests", dependencies: [ + "S3" + ] + ) ] ) diff --git a/Package@swift-4.swift b/Package@swift-4.swift deleted file mode 100644 index 00fe67d..0000000 --- a/Package@swift-4.swift +++ /dev/null @@ -1,16 +0,0 @@ -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "S3SignerAWS", - products: [ - .library(name: "S3SignerAWS", targets: ["S3SignerAWS"]) - ], - dependencies: [ - .package(url: "https://github.com/vapor/crypto.git", .upToNextMajor(from: "2.0.0")), - ], - targets: [ - .target(name: "S3SignerAWS", dependencies: ["Crypto"], path: "Sources"), - .testTarget(name: "S3SignerAWSTests", dependencies: ["S3SignerAWS"]), - ] -) diff --git a/Sources/Payload.swift b/Sources/Payload.swift deleted file mode 100644 index 9b9e434..0000000 --- a/Sources/Payload.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Core -import Crypto - -/// The Payload associated with a request. -/// -/// - bytes: The bytes of the request. -/// - none: No payload is in the request. i.e. GET request. -/// - unsigned: The size of payload will not go into signature calcuation. Useful if size is unknown at time of signature creation. Less secure as the payload can be changed and the signature won't be effected. -public enum Payload { - case bytes(Bytes) - case none - case unsigned - - internal var bytes: Bytes { - switch self { - case .bytes(let bytes): - return bytes - default: - return "".bytes - } - } - - /// Hash the payload being sent to AWS. - /// - Bytes: are hashed using SHA256 - /// - None: Guaranteed no payload being sent, requires an empty string SHA256. - /// - Unsigned: Any size payload will be accepted, wasn't considered in part of the signature. - /// - /// - Returns: The hashed hexString. - /// - Throws: Hash Error. - internal func hashed() throws -> String { - switch self { - case .bytes(let bytes): - return try Hash.make(.sha256, bytes).hexString - case .none: - return try Hash.make(.sha256, "".bytes).hexString - case .unsigned: - return "UNSIGNED-PAYLOAD" - - } - } - - internal var isBytes: Bool { - switch self { - case .bytes( _), .none: - return true - default: - return false - } - } - - internal func size() -> String { - switch self { - case .bytes, .none: - return self.bytes.count.description - case .unsigned: - return "UNSIGNED-PAYLOAD" - } - } - - internal var isUnsigned: Bool { - switch self { - case .unsigned: - return true - default: - return false - } - } -} diff --git a/Sources/PercentEncoder.swift b/Sources/PercentEncoder.swift deleted file mode 100644 index 47c7391..0000000 --- a/Sources/PercentEncoder.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Core - -// MARK: - Allowed characters when calculating AWS Signatures. -extension Byte { - internal static let awsQueryAllowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~=&".makeBytes() - - internal static let awsPathAllowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~/".makeBytes() -} - -extension String { - internal func percentEncode(allowing allowed: Bytes) throws -> String { - let bytes = self.makeBytes() - let encodedBytes = try percentEncodedUppercase(bytes, shouldEncode: { - return !allowed.contains($0) - }) - return encodedBytes.makeString() - } - - private func percentEncodedUppercase( - _ input: [Byte], - shouldEncode: (Byte) throws -> Bool = { _ in true } - ) throws -> [Byte] { - var group: [Byte] = [] - try input.forEach { byte in - if try shouldEncode(byte) { - let hex = String(byte, radix: 16).uppercased().utf8 - group.append(.percent) - if hex.count == 1 { - group.append(.zero) - } - group.append(contentsOf: hex) - } else { - group.append(byte) - } - } - return group - } -} diff --git a/Sources/S3/Dates.swift b/Sources/S3/Dates.swift new file mode 100644 index 0000000..6550f03 --- /dev/null +++ b/Sources/S3/Dates.swift @@ -0,0 +1,6 @@ +import Foundation + + +class Bbbbb { + +} diff --git a/Sources/Dates.swift b/Sources/S3Signer/Dates.swift similarity index 100% rename from Sources/Dates.swift rename to Sources/S3Signer/Dates.swift diff --git a/Sources/S3Signer/Expiration.swift b/Sources/S3Signer/Expiration.swift new file mode 100644 index 0000000..17453bf --- /dev/null +++ b/Sources/S3Signer/Expiration.swift @@ -0,0 +1,34 @@ +import Foundation + +public typealias Seconds = Int + +/// Pre-Sign URL Expiration time +public enum Expiration { + /// 30 minutes + case thirtyMinutes + + /// 60 minutes + case oneHour + + /// 180 minutes + case threeHours + + /// Custom expiration time, in seconds. + case custom(Seconds) +} + +extension Expiration { + /// Expiration Value + internal var value: Seconds { + switch self { + case .thirtyMinutes: + return 60 * 30 + case .oneHour: + return 60 * 60 + case .threeHours: + return 60 * 60 * 3 + case .custom(let exp): + return exp + } + } +} diff --git a/Sources/HTTPMethod.swift b/Sources/S3Signer/HTTPMethod.swift similarity index 100% rename from Sources/HTTPMethod.swift rename to Sources/S3Signer/HTTPMethod.swift diff --git a/Sources/S3Signer/Payload.swift b/Sources/S3Signer/Payload.swift new file mode 100644 index 0000000..b265695 --- /dev/null +++ b/Sources/S3Signer/Payload.swift @@ -0,0 +1,46 @@ +import Vapor +import Crypto + +public enum Payload { + case bytes(Data) + case none + case unsigned +} + +extension Payload { + internal var bytes: Data { + switch self { + case .bytes(let bytes): return bytes + default: return "".convertToData() + } + } + + internal func hashed() throws -> String { + switch self { + case .bytes(let bytes): return try SHA256.hash(bytes).hexEncodedString() + case .none: return try SHA256.hash("".convertToData()).hexEncodedString() + case .unsigned: return "UNSIGNED-PAYLOAD" + } + } + + internal var isBytes: Bool { + switch self { + case .bytes( _), .none: return true + default: return false + } + } + + internal func size() -> String { + switch self { + case .bytes, .none: return self.bytes.count.description + case .unsigned: return "UNSIGNED-PAYLOAD" + } + } + + internal var isUnsigned: Bool { + switch self { + case .unsigned: return true + default: return false + } + } +} diff --git a/Sources/S3Signer/PercentEncoder.swift b/Sources/S3Signer/PercentEncoder.swift new file mode 100644 index 0000000..e6a027a --- /dev/null +++ b/Sources/S3Signer/PercentEncoder.swift @@ -0,0 +1,16 @@ +import Core + +struct AWSEncoding { + internal static let QueryAllowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~=&" + internal static let PathAllowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~/" +} + +extension String { + + func awsStringEncoding(_ type: String) -> String? { + let allowed = NSMutableCharacterSet.alphanumeric() + allowed.addCharacters(in: type) + return self.addingPercentEncoding(withAllowedCharacters: allowed as CharacterSet) + } + +} diff --git a/Sources/Region.swift b/Sources/S3Signer/Region.swift similarity index 100% rename from Sources/Region.swift rename to Sources/S3Signer/Region.swift diff --git a/Sources/S3SignerAWS.swift b/Sources/S3Signer/S3SignerAWS.swift similarity index 71% rename from Sources/S3SignerAWS.swift rename to Sources/S3Signer/S3SignerAWS.swift index 4377b02..f0f113f 100644 --- a/Sources/S3SignerAWS.swift +++ b/Sources/S3Signer/S3SignerAWS.swift @@ -1,8 +1,20 @@ import Foundation import Crypto import Core +import Bits + + +public enum S3Error: Error { + case badURL + case invalidEncoding +} + + public class S3SignerAWS { + + // the salt for this hash + public let config: BCryptConfig /// AWS Access Key private let accessKey: String @@ -73,7 +85,8 @@ public class S3SignerAWS { bodyDigest: bodyDigest) if httpMethod == .put && payload.isBytes { - updatedHeaders["content-md5"] = try Hash.make(.md5, payload.bytes).base64Encoded.makeString() + // WARNING: S3 might be failing with this + updatedHeaders["Content-MD5"] = try MD5.hash(payload.bytes).hexEncodedString() } updatedHeaders["Authorization"] = try generateAuthHeader( @@ -128,7 +141,7 @@ public class S3SignerAWS { let stringToSign = try createStringToSign(canonicalRequest: canonRequest, dates: dates) - let signature = try createSignature(stringToSign: stringToSign, timeStampShort: dates.short) + let signature = try createSignature(stringToSign, timeStampShort: dates.short) let presignedURL = fullURL.absoluteString.appending("&X-Amz-Signature=\(signature)") @@ -164,7 +177,7 @@ public class S3SignerAWS { #endif } - internal func createCanonicalRequest( + internal func createCanonicalRequest(_ httpMethod: HTTPMethod, url: URL, headers: [String: String], @@ -174,7 +187,7 @@ public class S3SignerAWS { return try [ httpMethod.rawValue, path(url: url), - query(url: url), + query(url), canonicalHeaders(headers: headers), signedHeaders(headers: headers), bodyDigest @@ -188,18 +201,14 @@ public class S3SignerAWS { /// - timeStampShort: Short timestamp. /// - Returns: Signature. /// - Throws: HMAC error. - internal func createSignature( - stringToSign: String, - timeStampShort: String) - throws -> String - { - let dateKey = try HMAC.make(.sha256, timeStampShort.bytes, key: "AWS4\(secretKey)".bytes) - let dateRegionKey = try HMAC.make(.sha256, region.rawValue.bytes, key: dateKey) - let dateRegionServiceKey = try HMAC.make(.sha256, service.bytes, key: dateRegionKey) - let signingKey = try HMAC.make(.sha256, "aws4_request".bytes, key: dateRegionServiceKey) - let signature = try HMAC.make(.sha256, stringToSign.bytes, key: signingKey).hexString - return signature - } + private func createSignature(_ stringToSign: String, timeStampShort: String) throws -> String { + let dateKey = try HMAC.SHA256.authenticate(timeStampShort.convertToData(), key: "AWS4\(self.config.secretKey)".convertToData()) + let dateRegionKey = try HMAC.SHA256.authenticate(self.config.region.rawValue.convertToData(), key: dateKey) + let dateRegionServiceKey = try HMAC.SHA256.authenticate(self.config.service.convertToData(), key: dateRegionKey) + let signingKey = try HMAC.SHA256.authenticate("aws4_request".convertToData(), key: dateRegionServiceKey) + let signature = try HMAC.SHA256.authenticate(stringToSign.convertToData(), key: signingKey) + return signature.hexEncodedString() + } /// Create the String To Sign portion of signature. /// @@ -213,7 +222,7 @@ public class S3SignerAWS { dates: Dates) throws -> String { - let canonRequestHash = try Hash.make(.sha256, canonicalRequest.bytes).hexString + let canonRequestHash = try SHA256.hash(canonicalRequest.convertToData()).hexEncodedString() return ["AWS4-HMAC-SHA256", dates.long, credentialScope(timeStampShort: dates.short), @@ -225,16 +234,9 @@ public class S3SignerAWS { /// /// - Parameter timeStampShort: Short timestamp. /// - Returns: Credential Scope. - private func credentialScope( - timeStampShort: String) - -> String - { - return [ - timeStampShort, - region.rawValue, - service, "aws4_request" - ].joined(separator: "/") - } + private func credentialScope(_ timeStampShort: String) -> String { + return [timeStampShort, self.config.region.rawValue, self.config.service, "aws4_request"].joined(separator: "/") + } /// Generate Auth Header for V4 Authorization Header request. /// @@ -246,21 +248,13 @@ public class S3SignerAWS { /// - dates: The short and long timestamps of time of request. /// - Returns: Authorization header value. /// - Throws: S3SignerError - internal func generateAuthHeader( - httpMethod: HTTPMethod, - url: URL, - headers: [String:String], - bodyDigest: String, - dates: Dates) - throws -> String - { - let canonicalRequestHex = try createCanonicalRequest(httpMethod: httpMethod, url: url, headers: headers, bodyDigest: bodyDigest) - let stringToSign = try createStringToSign(canonicalRequest: canonicalRequestHex, dates: dates) - let signature = try createSignature(stringToSign: stringToSign, timeStampShort: dates.short) - let authHeader = "AWS4-HMAC-SHA256 Credential=\(accessKey)/\(credentialScope(timeStampShort: dates.short)), SignedHeaders=\(signedHeaders(headers: headers)), Signature=\(signature)" - - return authHeader - } + private func generateAuthHeader(_ httpMethod: HTTPMethod, url: URL, headers: [String: String], bodyDigest: String, dates: Dates) throws -> String { + let canonicalRequestHex = try self.createCanonicalRequest(httpMethod, url: url, headers: headers, bodyDigest: bodyDigest) + let stringToSign = try self.createStringToSign(canonicalRequestHex, dates: dates) + let signature = try self.createSignature(stringToSign, timeStampShort: dates.short) + let authHeader = "AWS4-HMAC-SHA256 Credential=\(self.config.accessKey)/\(self.credentialScope(dates.short)), SignedHeaders=\(self.signedHeaders(headers)), Signature=\(signature)" + return authHeader + } /// Instantiate Dates object containing the required date formats needed for signature calculation. /// @@ -289,47 +283,32 @@ public class S3SignerAWS { /// - headers: Headers used to sign and add to presigned URL. /// - Returns: Canonical request for pre-signed URL. /// - Throws: S3SignerError - internal func presignedURLCanonRequest( - httpMethod: HTTPMethod, - dates: Dates, - expiration: TimeFromNow, - url: URL, - headers: [String: String]) - throws -> (String, URL) - { - let credScope = try credentialScope(timeStampShort: dates.short).percentEncode(allowing: Byte.awsQueryAllowed) - let signHeaders = try signedHeaders(headers: headers).percentEncode(allowing: Byte.awsQueryAllowed) - let fullURL = "\(url.absoluteString)?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=\(accessKey)%2F\(credScope)&X-Amz-Date=\(dates.long)&X-Amz-Expires=\(expiration.expiration)&X-Amz-SignedHeaders=\(signHeaders)" - - // This should never throw. - guard let url = URL(string: fullURL) else { - throw S3SignerError.badURL - } - - return try ( - [ - httpMethod.rawValue, - path(url: url), - query(url: url), - canonicalHeaders(headers: headers), - signedHeaders(headers: headers), - "UNSIGNED-PAYLOAD" - ].joined(separator: "\n"), - url) - } + private func presignedURLCanonRequest(_ httpMethod: HTTPMethod, dates: Dates, expiration: Expiration, url: URL, headers: [String: String]) throws -> (String, URL) { + guard let credScope = self.credentialScope(dates.short).awsStringEncoding(AWSEncoding.QueryAllowed), + let signHeaders = self.signedHeaders(headers).awsStringEncoding(AWSEncoding.QueryAllowed) else { throw S3Error.invalidEncoding } + let fullURL = "\(url.absoluteString)?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=\(self.config.accessKey)%2F\(credScope)&X-Amz-Date=\(dates.long)&X-Amz-Expires=\(expiration.value)&X-Amz-SignedHeaders=\(signHeaders)" + + // This should never throw. + guard let url = URL(string: fullURL) else { + throw S3Error.badURL + } + + return try ([httpMethod.description, self.path(url), self.query(url), self.canonicalHeaders(headers), self.signedHeaders(headers), "UNSIGNED-PAYLOAD"].joined(separator: "\n"), url) + } /// Encode and sort queryItems. /// /// - Parameter url: The URL for request containing the possible queryItems. /// - Returns: Encoded and sorted(By Key) queryItem String. /// - Throws: Encoding Error - internal func query(url: URL)throws -> String { - if let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems { - let encodedItems = try queryItems.map { try "\($0.name.percentEncode(allowing: Byte.awsQueryAllowed))=\($0.value?.percentEncode(allowing: Byte.awsQueryAllowed) ?? "")"} - return encodedItems.sorted().joined(separator: "&") - } - return "" - } + private func query(_ url: URL) throws -> String { + if let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems { + let items = queryItems.map({ ($0.name.awsStringEncoding(AWSEncoding.QueryAllowed) ?? "", $0.value?.awsStringEncoding(AWSEncoding.QueryAllowed) ?? "") }) + let encodedItems = items.map({ "\($0.0)=\($0.1)" }) + return encodedItems.sorted().joined(separator: "&") + } + return "" + } /// Signed headers /// diff --git a/Sources/S3SignerError.swift b/Sources/S3Signer/S3SignerError.swift similarity index 100% rename from Sources/S3SignerError.swift rename to Sources/S3Signer/S3SignerError.swift diff --git a/Sources/TimeFromNow.swift b/Sources/S3Signer/TimeFromNow.swift similarity index 100% rename from Sources/TimeFromNow.swift rename to Sources/S3Signer/TimeFromNow.swift diff --git a/Sources/S3TestTools/Dates.swift b/Sources/S3TestTools/Dates.swift new file mode 100644 index 0000000..d86622c --- /dev/null +++ b/Sources/S3TestTools/Dates.swift @@ -0,0 +1,5 @@ +import Foundation + +class Aaaaa { + +} diff --git a/Tests/S3SignerAWSTests/AWSTestSuite.swift b/Tests/S3Tests/AWSTestSuite.swift similarity index 100% rename from Tests/S3SignerAWSTests/AWSTestSuite.swift rename to Tests/S3Tests/AWSTestSuite.swift diff --git a/Tests/S3SignerAWSTests/Info.plist b/Tests/S3Tests/Info.plist similarity index 100% rename from Tests/S3SignerAWSTests/Info.plist rename to Tests/S3Tests/Info.plist diff --git a/Tests/S3SignerAWSTests/S3SignerAWSTests.swift b/Tests/S3Tests/S3SignerAWSTests.swift similarity index 100% rename from Tests/S3SignerAWSTests/S3SignerAWSTests.swift rename to Tests/S3Tests/S3SignerAWSTests.swift diff --git a/Tests/S3SignerAWSTests/S3SignerTester.swift b/Tests/S3Tests/S3SignerTester.swift similarity index 100% rename from Tests/S3SignerAWSTests/S3SignerTester.swift rename to Tests/S3Tests/S3SignerTester.swift diff --git a/Tests/S3SignerAWSTests/S3Tests.swift b/Tests/S3Tests/S3Tests.swift similarity index 100% rename from Tests/S3SignerAWSTests/S3Tests.swift rename to Tests/S3Tests/S3Tests.swift