Skip to content
This repository was archived by the owner on Apr 20, 2024. It is now read-only.

Commit ffbc2bf

Browse files
authored
Merge pull request #10 from nodes-vapor/aws-signer
Updates to AWS Signer V4 [WIP]
2 parents 51ef14d + fdb91b7 commit ffbc2bf

12 files changed

+353
-202
lines changed

Package.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ let package = Package(
1010
],
1111
dependencies: [
1212
.Package(url: "https://github.com/vapor/crypto.git", majorVersion: 1),
13-
.Package(url: "https://github.com/vapor/engine.git", majorVersion: 1),
14-
.Package(url: "https://github.com/JustinM1/S3SignerAWS.git", majorVersion: 1),
13+
.Package(url: "https://github.com/vapor/engine.git", majorVersion: 1)
1514
]
1615
)

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Nodes AWS
22

33
![Swift](http://img.shields.io/badge/swift-3.0-brightgreen.svg)
4+
[![Build Status](https://travis-ci.org/nodes-vapor/aws.svg?branch=master)](https://travis-ci.org/nodes-vapor/aws)
45
[![codebeat badge](https://codebeat.co/badges/52c2f960-625c-4a63-ae63-52a24d747da1)](https://codebeat.co/projects/github-com-nodes-vapor-aws)
56
[![codecov](https://codecov.io/gh/nodes-vapor/aws/branch/master/graph/badge.svg)](https://codecov.io/gh/nodes-vapor/aws)
67
[![Readme Score](http://readme-score-api.herokuapp.com/score.svg?url=https://github.com/nodes-vapor/aws)](http://clayallsopp.github.io/readme-score?url=https://github.com/nodes-vapor/aws)

Sources/Driver/AWSDriver.swift

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
import HTTP
22
import Transport
3-
4-
public enum AccessControlList: String {
5-
case privateAccess = "private"
6-
case publicRead = "public-read"
7-
case publicReadWrite = "public-read-write"
8-
case awsExecRead = "aws-exec-read"
9-
case authenticatedRead = "authenticated-read"
10-
case bucketOwnerRead = "bucket-owner-read"
11-
case bucketOwnerFullControl = "bucket-owner-full-control"
12-
}
3+
import Foundation
134

145
class AWSDriver {
156
enum Error: Swift.Error {
@@ -26,8 +17,8 @@ class AWSDriver {
2617

2718
}
2819

29-
public func call(authentication: Authentication) throws -> Response {
30-
let headers = authentication.getCanonicalHeaders()
20+
public func call(authentication: AWSSignatureV4) throws -> Response {
21+
/*let headers = authentication.getCanonicalHeaders()
3122

3223
let response: Response
3324
switch authentication.method {
@@ -41,6 +32,8 @@ class AWSDriver {
4132
throw Error.unsupported("method: \(authentication.method)")
4233
}
4334

44-
return response
35+
return response*/
36+
37+
throw Error.unsupported("call(authentiction:)")
4538
}
4639
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import Core
2+
import Hash
3+
import HMAC
4+
import HTTP
5+
import Essentials
6+
import Foundation
7+
8+
public enum AccessControlList: String {
9+
case privateAccess = "private"
10+
case publicRead = "public-read"
11+
case publicReadWrite = "public-read-write"
12+
case awsExecRead = "aws-exec-read"
13+
case authenticatedRead = "authenticated-read"
14+
case bucketOwnerRead = "bucket-owner-read"
15+
case bucketOwnerFullControl = "bucket-owner-full-control"
16+
}
17+
18+
public struct AWSSignatureV4 {
19+
public enum Method: String {
20+
case delete = "DELETE"
21+
case get = "GET"
22+
case post = "POST"
23+
case put = "PUT"
24+
}
25+
26+
let service: String
27+
let host: String
28+
let region: String
29+
let accessKey: String
30+
let secretKey: String
31+
32+
var unitTestDate: Date?
33+
34+
var amzDate: String {
35+
let dateFormatter = DateFormatter()
36+
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
37+
dateFormatter.dateFormat = "YYYYMMdd'T'HHmmss'Z'"
38+
return dateFormatter.string(from: unitTestDate ?? Date())
39+
}
40+
41+
public init(
42+
service: String,
43+
host: String,
44+
region: Region,
45+
accessKey: String,
46+
secretKey: String
47+
) {
48+
self.service = service
49+
self.host = host
50+
self.region = region.rawValue
51+
self.accessKey = accessKey
52+
self.secretKey = secretKey
53+
}
54+
55+
func getStringToSign(
56+
algorithm: String,
57+
date: String,
58+
scope: String,
59+
canonicalHash: String
60+
) -> String {
61+
return [
62+
algorithm,
63+
date,
64+
scope,
65+
canonicalHash
66+
].joined(separator: "\n")
67+
}
68+
69+
func getSignature(_ stringToSign: String) throws -> String {
70+
let dateHMAC = try HMAC.make(.sha256, dateStamp().bytes, key: "AWS4\(secretKey)".bytes)
71+
let regionHMAC = try HMAC.make(.sha256, region.bytes, key: dateHMAC)
72+
let serviceHMAC = try HMAC.make(.sha256, service.bytes, key: regionHMAC)
73+
let signingHMAC = try HMAC.make(.sha256, "aws4_request".bytes, key: serviceHMAC)
74+
75+
let signature = try HMAC.make(.sha256, stringToSign.bytes, key: signingHMAC)
76+
return signature.hexString
77+
}
78+
79+
func getCredentialScope() -> String {
80+
return [
81+
dateStamp(),
82+
region,
83+
service,
84+
"aws4_request"
85+
].joined(separator: "/")
86+
}
87+
88+
func getCanonicalRequest(
89+
payload: Payload,
90+
method: Method,
91+
path: String,
92+
query: String,
93+
headers: [String : String] = [:]
94+
) throws -> String {
95+
let path = try path.percentEncode(allowing: Byte.awsPathAllowed)
96+
let query = try query.percentEncode(allowing: Byte.awsQueryAllowed)
97+
let payloadHash = try payload.hashed()
98+
99+
var headers = headers
100+
generateHeadersToSign(headers: &headers, host: host, hash: payloadHash)
101+
102+
let sortedHeaders = alphabetize(headers)
103+
let canonicalHeaders = createCanonicalHeaders(sortedHeaders)
104+
let headersToSign = sortedHeaders.map { $0.key.lowercased() }.joined(separator: ";")
105+
106+
return [
107+
method.rawValue,
108+
path,
109+
query,
110+
canonicalHeaders,
111+
"",
112+
headersToSign,
113+
payloadHash
114+
].joined(separator: "\n")
115+
}
116+
117+
func dateStamp() -> String {
118+
let date = unitTestDate ?? Date()
119+
let dateFormatter = DateFormatter()
120+
dateFormatter.dateFormat = "YYYYMMdd"
121+
return dateFormatter.string(from: date)
122+
}
123+
}
124+
125+
extension AWSSignatureV4 {
126+
func generateHeadersToSign(
127+
headers: inout [String: String],
128+
host: String,
129+
hash: String
130+
) {
131+
headers["host"] = host
132+
headers["X-Amz-Date"] = amzDate
133+
134+
if hash != "UNSIGNED-PAYLOAD" {
135+
headers["x-amz-content-sha256"] = hash
136+
}
137+
}
138+
139+
func alphabetize(_ dict: [String : String]) -> [(key: String, value: String)] {
140+
return dict.sorted(by: { $0.0.lowercased() < $1.0.lowercased() })
141+
}
142+
143+
func createCanonicalHeaders(_ headers: [(key: String, value: String)]) -> String {
144+
return headers.map {
145+
"\($0.key.lowercased()):\($0.value)"
146+
}.joined(separator: "\n")
147+
}
148+
149+
func signPayload(
150+
_ payload: Payload,
151+
mime: String?,
152+
headers: inout [HeaderKey : String]
153+
) throws {
154+
/*let contentLength: Int
155+
156+
switch payload {
157+
case .bytes(let bytes):
158+
contentLength = bytes.count
159+
default:
160+
contentLength = 0
161+
}
162+
163+
headers["Content-Length"] = "\(contentLength)"
164+
if let mime = mime {
165+
headers["Content-Type"] = mime
166+
}
167+
168+
headers["x-amz-content-sha256"] = try payload.hashed()*/
169+
}
170+
}
171+
172+
extension AWSSignatureV4 {
173+
public func sign(
174+
payload: Payload = .none,
175+
method: Method = .get,
176+
path: String,
177+
query: String? = nil,
178+
headers: [String : String] = [:]
179+
) throws -> [HeaderKey : String] {
180+
let algorithm = "AWS4-HMAC-SHA256"
181+
let credentialScope = getCredentialScope()
182+
183+
let canonicalRequest = try getCanonicalRequest(
184+
payload: payload,
185+
method: method,
186+
path: path,
187+
query: query ?? ""
188+
)
189+
190+
let canonicalHash = try Hash.make(.sha256, canonicalRequest).hexString
191+
192+
let stringToSign = getStringToSign(
193+
algorithm: algorithm,
194+
date: amzDate,
195+
scope: credentialScope,
196+
canonicalHash: canonicalHash
197+
)
198+
199+
let signature = try getSignature(stringToSign)
200+
201+
let authorizationHeader = "\(algorithm) Credential=\(accessKey)/\(credentialScope), SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=\(signature)"
202+
203+
return [
204+
"X-Amz-Date": amzDate,
205+
"x-amz-content-sha256": try payload.hashed(),
206+
"Authorization": authorizationHeader
207+
]
208+
}
209+
}

0 commit comments

Comments
 (0)