├── README.md ├── aws.py ├── demo.py ├── hsm.ino └── hsmd.py /README.md: -------------------------------------------------------------------------------- 1 | (See the original posting [on my blog at stefan.arentz.ca](http://stefan.arentz.ca)) 2 | 3 | TL;DR 4 | ===== 5 | 6 | Are you nervous when you include that [Amazon Web Services](https://aws.amazon.com) secret key in a config file? Fear no 7 | more. With this weekend hack you can turn your [Arduino Due](http://arduino.cc/en/Main/ArduinoBoardDue) into a 8 | device that securely stores your AWS credentials and let it sign AWS 9 | API requests. 10 | 11 | Introduction 12 | ============ 13 | 14 | In the industry a device like this is commonly known as a [Hardware Security Module](http://en.wikipedia.org/wiki/Hardware_security_module); an appliance (or PCI card) that manages your keys 15 | and/or certificates and then allows you to execute operations like 16 | encryption and signature generation. Once stored in the device, the 17 | key material is not directly accessible anymore and management of the 18 | device and keys usually requires direct physical access and the use of 19 | hardware tokens. 20 | 21 | These devices are usually very expensive. In the order of tens of 22 | thousands of dollars. They come with guarantees and certifications 23 | like [FIPS-140-2](http://en.wikipedia.org/wiki/FIPS_140-2). 24 | 25 | This hack not so much. For this 'Mini HSM' I have used my Christmas 26 | present, a $50 Arduino Due. There is no warranty or certification, 27 | but it has enough power to sign over 2250 API requests per second. 28 | 29 | It is also pretty secure; extracting the AWS secret key from the 30 | Arduino's CPU can theoretically be done by reading the contents of the 31 | chip with an electron microscope. This of course after getting 32 | physical access to the Arduino Due board and then carefully opening up 33 | the chip in a specialized laboratory. 34 | 35 | You can see the whole project on Github. It is surprisingly easy to 36 | get it going. And a small demo that talks to EC2 is included. 37 | 38 | I am currently using this with great success in some small personal 39 | projects that talk to Amazon's EC2 and SQS services. (Most AWS API 40 | requests are signed using the same method) 41 | 42 | Running the demo 43 | ================ 44 | 45 | Prerequisites 46 | ------------- 47 | 48 | * An [Arduino Due](http://arduino.cc/en/Main/ArduinoBoardDue) (It won't easily work on older Arduinos) 49 | * A copy of the [arduino-aws-hsm project](https://github.com/st3fan/arduino-aws-hsm) 50 | * The [Arduino 1.5.1 IDE](http://arduino.cc/en/Main/SoftwareDue) for the Due 51 | 52 | Patch the Arduino IDE 53 | --------------------- 54 | 55 | > You can skip this step if you just want to see this hack working. If you do not make this small change, the AWS credentials will be recoverable from the Arduino's flash memory easily! 56 | 57 | Make a copy of the Arduino 1.5.1 IDE and find the `platform.txt` file. The example below is for OS X: 58 | 59 | ``` 60 | $ ls -l Arduino\ 1.5.1.app/Contents/Resources/Java/hardware/arduino/sam/platform.txt 61 | -rw-r--r-- 1 stefan staff 3399 5 Nov 18:49 platform.txt 62 | ``` 63 | 64 | Edit this file and modify the following (probably on the last line): 65 | 66 | ``` 67 | tools.bossac.upload.pattern="{path}/{cmd}" {upload.verbose} --port... 68 | ``` 69 | 70 | to look like: 71 | 72 | ``` 73 | tools.bossac.upload.pattern="{path}/{cmd}" {upload.verbose} -s --port... 74 | ``` 75 | 76 | The extra `-s` option will set the Secure Flag so that it is not 77 | possible anymore to look at the chip's memory after programming it. 78 | 79 | Setup the Arduino 80 | ----------------- 81 | 82 | First you need to configure the Arduino code with your AWS 83 | credentials. They are hard-coded in the source code. You will need to 84 | change the following section in the Arduino Sketch `hsm.ino` to 85 | include your AWS Access Key and Secret: 86 | 87 | ``` 88 | AWSCredentials gAWSCredentials[] = { 89 | {"YOURACCESSKEYID", "YOURACCESSKEYSECRET"}, 90 | {NULL, NULL} 91 | }; 92 | ``` 93 | You can add multiple credentials. The code will look at the message to sign and the figure out which one to use. 94 | 95 | Now Compile and Upload this sketch to your Arduino Due. 96 | 97 | > Make sure to delete the credentials from the code after uploading. You don't want to keep those around in plain text. 98 | 99 | Run the daemon 100 | -------------- 101 | 102 | First setup a Python virtual environment with the dependencies: 103 | 104 | ``` 105 | $ cd arduino-aws-hsm 106 | $ virtualenv --no-site-packages env 107 | $ source env/bin/activate 108 | (env) $ pip install pyserial bottle requests 109 | ``` 110 | 111 | To run the `hsmd.py` you will need to know the serial port that the 112 | Arduino is connected to. You can find this in the Arduino IDE or by 113 | taking a look at `/dev/tty.usbmodem*` on OS X. (On Linux the device 114 | probably has a different name). 115 | 116 | ``` 117 | $ cd arduino-aws-hsm 118 | $ source env/bin/activate 119 | (env) $ ./hsmd.py /dev/tty.usbmodem1411 120 | Bottle v0.11.4 server starting up (using WSGIRefServer())... 121 | Listening on http://127.0.0.1:8086/ 122 | Hit Ctrl-C to quit. 123 | ``` 124 | 125 | You are now in business. 126 | 127 | Run the demo script 128 | ------------------- 129 | 130 | The demo script makes a `DescribeInstances` call to the default EC2 131 | region and then lists all your instances: 132 | 133 | ``` 134 | $ cd arduino-aws-hsm 135 | $ source env/bin/activate 136 | (env) $ ./demo.py 137 | r-2deb584e i-84cdc8e0 c1.medium stopped 138 | r-f583f596 i-870bdde0 c1.xlarge stopped 139 | ... 140 | ``` 141 | 142 | How does it work? 143 | ================= 144 | 145 | There are three parts to make this work: 146 | 147 | * The 'firmware` that runs on the Arduino, which does the actual signing 148 | * A small python web service that accepts sign requests and then talks to the Due over the serial port 149 | * A Python library to construct AWS API requests that uses the web service instead of performing the HMAC-SHA1 itself 150 | 151 | The Arduino Due 152 | --------------- 153 | 154 | The Due listens for commands on the serial port and then prints out 155 | the HMAC-SHA1 digest. It looks very much like this: 156 | 157 | ``` 158 | HOST->DUE: SIGN-AWS-V2 MESSAGETOSIGN\x00 159 | DUE->HOST: SUCCESS D9E5B887B6E34445B94F8B4F4737AE906DF287C29A12BE67\n 160 | ``` 161 | 162 | The message to sign is described in the EC2[Query API 163 | Authentication](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-query-api.html#query-authentication) 164 | documentation. 165 | 166 | The code running on the Due will look at the message and find the 167 | AWSAccessKeyId parameters in it and use that key id look up the secret 168 | from the table that you configured prior to uploading the code to the 169 | Due. 170 | 171 | The HSM Daemon 172 | -------------- 173 | 174 | The daemon is a tiny Python [Bottle](http://bottlepy.org) application 175 | that just listens to `POST` requests on `/sign/aws`. It sends the body 176 | of the request to the Due and then returns a JSON structure with the 177 | resulting HMAC-SHA1 signature: 178 | 179 | ``` 180 | $ curl -XPOST -d @message http://127.0.0.1:8086/sign/aws 181 | {"success": true, "signature": "D9E5B887B6E34445B94F8B4F4737AE906DF287C29A12BE67"} 182 | ``` 183 | 184 | You could also talk to the Due directly from your code but I like to 185 | abstract things behind simple web services. Also, this is single 186 | threaded web server which has the nice side-effect that it solves the 187 | problem of coordinating multiple processes using a serial port. 188 | 189 | The Client Code 190 | --------------- 191 | 192 | The client code can be found in the `aws.py` module. This is work in 193 | progress for a more complete AWS API but the important bit can be 194 | found in the `_generate_signature` and `_hsm_sign_aws` methods. 195 | 196 | After constructing the message to sign, you would normally do something like: 197 | 198 | ``` 199 | def _sign_request(self, msg, secret): 200 | return hmac.new(secret, msg, hashlib.sha1).digest 201 | ``` 202 | 203 | With our Arduino HSM and API in place we do this instead: 204 | 205 | ``` 206 | def _hsm_sign_aws(self, msg): 207 | r = requests.post(self._hsm, data=msg) 208 | response = r.json() 209 | if not response.get('success'): 210 | raise Exception("Signing failed") 211 | return response['signature'].decode('hex') 212 | ``` 213 | 214 | Questions & Answers 215 | =================== 216 | 217 | *You bricked my Arduino. I cannot upload any sketches anymore!* 218 | 219 | This is the result of the `-s` (Secure) option that was added. Simply 220 | remove it from the `platform.txt` file and power up your Arduino while 221 | holding down the Erase button. This will wipe the chip and reset the 222 | secure flag. 223 | 224 | *This is just a hack? Would you run it in production?* 225 | 226 | It is a proof of concept but with some small improvements it can be 227 | turned into something that I would use in production. I would replace 228 | the serial connection with a native USB protocol (with libusb on the 229 | host side) to improve the speed and realiability and also make the 230 | protocol and daemon more robust. 231 | 232 | The Arduino code was also mostly written between turkey dinner and 233 | cocktails so it certainly needs a proper review and likely fuzzing to 234 | make sure it does not contain any exploitable vulnerabilities. 235 | 236 | *How are the keys secure?* 237 | 238 | When the Arduino is programmed with the firmware, and thus your 239 | embedded AWS credentials, the Security Bit is set in the device. This 240 | means that the contents of the Arduino's flash memory cannot be read 241 | back and that it's debugging capabilities (JTAG/ICE) are disabled. 242 | 243 | There is no simple way, other than physical access and advanced 244 | forensics science, to extract them. It is questionable if recovery is 245 | possible at all. 246 | 247 | *What is the effective speed?* 248 | 249 | The Arduino Due with it's 84 Mhz SAM3X8E ARM Cortex-M3 CPU is capable 250 | of signing about 2250 messages that are 1KB long. This translates to 251 | roughly 2.5 mbit throughput without any overhead. 252 | 253 | The current proof of concept is limited to a much lower speed because 254 | it uses a 115200 baud USB Serial connection. This is obviously not 255 | ideal and a good solution for this would be to use the SAM3X8E's 256 | native USB capabilities. 257 | 258 | Also, the SHA1 and HMAC-SHA1 code can likely be optimized heavily for 259 | the Cortex-M3. I would not be surprised if it can be at least run 260 | twice as fast. 261 | -------------------------------------------------------------------------------- /aws.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/ 4 | 5 | 6 | # This is all work in progress and just works barely to demo the Arduino 7 | # HSM proof of concept. Don't expect magic here. It might evolve to a nice 8 | # Python EC2 library at some later stage. 9 | 10 | 11 | import base64 12 | import time 13 | import urllib 14 | from xml.etree import ElementTree 15 | 16 | import requests 17 | 18 | 19 | class EC2RequestBuilder: 20 | 21 | EC2_API_ENDPOINT = "ec2.amazonaws.com" 22 | EC2_API_VERSION = "2012-12-01" 23 | 24 | def __init__(self, action, key, hsm, endpoint=EC2_API_ENDPOINT): 25 | self._path = "/" 26 | self._version = self.EC2_API_VERSION 27 | self._endpoint = endpoint 28 | self._action = action 29 | self._key = key 30 | self._hsm = hsm 31 | self._expires = 30 32 | self._parameters = {} 33 | 34 | def path(self, path): 35 | self._path = path 36 | 37 | def version(self, version): 38 | self._version = version 39 | 40 | def param(self, name, value): 41 | self._parameters[name] = value 42 | 43 | def expires(self, expires): 44 | self._expires = expires 45 | 46 | def _generate_timestamp(self, t): 47 | return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(t)) 48 | 49 | def _hsm_sign_aws(self, msg): 50 | r = requests.post(self._hsm, data=msg) 51 | response = r.json() 52 | if not response.get('success'): 53 | raise Exception("Signing failed") 54 | return response['signature'].decode('hex') 55 | 56 | def _generate_signature(self, params): 57 | query_string = "" 58 | for name in sorted(params.keys()): 59 | if len(query_string) != 0: 60 | query_string += "&" 61 | query_string += "%s=%s" % (name, urllib.quote(params[name])) 62 | msg = "%s\n%s\n%s\n%s" % ("GET", self._endpoint, self._path, query_string) 63 | return base64.b64encode(self._hsm_sign_aws(msg)) 64 | 65 | def build(self): 66 | request_params = {"Action": self._action, "Version": self._version, "AWSAccessKeyId": self._key, 67 | "SignatureMethod": "HmacSHA1", "SignatureVersion": "2", 68 | "Expires": self._generate_timestamp(time.time() + self._expires)} 69 | # Collect all the request parameters 70 | for name in self._parameters.keys(): 71 | value = self._parameters[name] 72 | if isinstance(value, (list, tuple, set)): 73 | for idx, val in enumerate(value, 1): 74 | request_params["%s.%d" % (name, idx)] = str(val) 75 | else: 76 | request_params[name] = str(value) 77 | # Generate the signature 78 | request_params["Signature"] = self._generate_signature(request_params) 79 | # Build the url 80 | return "https://%s%s?%s" % (self._endpoint, self._path, urllib.urlencode(request_params)) 81 | 82 | class Group: 83 | def __init__(self, tree): 84 | self.groupId = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}groupId") 85 | 86 | class InstanceState: 87 | def __init__(self,tree): 88 | self.code = int(tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}code")) 89 | self.name = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}name") 90 | 91 | class Instance: 92 | def __init__(self,tree): 93 | self.instanceId = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}instanceId") 94 | self.imageId = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}imageId") 95 | self.dnsName = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}dnsName") 96 | self.privateDnsName = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}privateDnsName") 97 | self.instanceType = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}instanceType") 98 | self.keyName = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}keyName") 99 | self.instanceState = InstanceState(tree.find("{http://ec2.amazonaws.com/doc/2012-12-01/}instanceState")) 100 | 101 | class Reservation: 102 | def __init__(self, tree): 103 | self.reservationId = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}reservationId") 104 | self.ownerId = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}ownerId") 105 | self.groups = [] 106 | for e in tree.findall("{http://ec2.amazonaws.com/doc/2012-12-01/}groupSet/{{http://ec2.amazonaws.com/doc/2012-12-01/}}item"): 107 | self.groups.append(Group(e)) 108 | self.instances = [] 109 | for e in tree.findall("{http://ec2.amazonaws.com/doc/2012-12-01/}instancesSet/{http://ec2.amazonaws.com/doc/2012-12-01/}item"): 110 | self.instances.append(Instance(e)) 111 | 112 | class DescribeInstancesResponse: 113 | def __init__(self, tree): 114 | self.reservations = [] 115 | for e in tree.findall("{http://ec2.amazonaws.com/doc/2012-12-01/}reservationSet/{http://ec2.amazonaws.com/doc/2012-12-01/}item"): 116 | self.reservations.append(Reservation(e)) 117 | 118 | class RunInstancesResponse: 119 | def __init__(self, tree): 120 | self.reservationId = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}reservationId") 121 | self.ownerId = tree.findtext("{http://ec2.amazonaws.com/doc/2012-12-01/}ownerId") 122 | self.groups = [] 123 | for e in tree.findall("{http://ec2.amazonaws.com/doc/2012-12-01/}groupSet/{{http://ec2.amazonaws.com/doc/2012-12-01/}}item"): 124 | self.groups.append(Group(e)) 125 | self.instances = [] 126 | for e in tree.findall("{http://ec2.amazonaws.com/doc/2012-12-01/}instancesSet/{http://ec2.amazonaws.com/doc/2012-12-01/}item"): 127 | self.instances.append(Instance(e)) 128 | 129 | EC2_RESPONSE_OBJECTS = { 130 | "{http://ec2.amazonaws.com/doc/2012-12-01/}DescribeInstancesResponse": DescribeInstancesResponse 131 | } 132 | 133 | class EC2Service: 134 | 135 | def __init__(self, key, hsm, endpoint=None): 136 | self._key = key 137 | self._hsm = hsm 138 | self._endpoint = endpoint 139 | 140 | def run_instances(self, image_id, min_count=1, max_count=None, key_name=None, 141 | security_groups=[], user_data=None, instance_type=None, client_token=None): 142 | builder = EC2RequestBuilder("RunInstances", self._key, self._hsm) 143 | builder.param("ImageId", image_id) 144 | builder.param("MinCount", min_count) 145 | builder.param("MaxCount", max_count or min_count) 146 | if key_name: 147 | builder.param("KeyName", key_name) 148 | if security_groups: 149 | builder.param("SecurityGroups", security_groups) 150 | if user_data: 151 | builder.param("UserData", base64.b64encode(user_data)) 152 | if instance_type: 153 | builder.param("InstanceType", instance_type) 154 | if client_token: 155 | builder.param("ClientToken", client_token) 156 | r = requests.get(builder.build()) 157 | tree = ElementTree.fromstring(r.text) 158 | return r.status_code,EC2_RESPONSE_OBJECTS[tree.tag](tree) 159 | 160 | def describe_instances(self, instances=[], filters=[]): 161 | builder = EC2RequestBuilder("DescribeInstances", self._key, self._hsm) 162 | url = builder.build() 163 | r = requests.get(builder.build()) 164 | tree = ElementTree.fromstring(r.text) 165 | return r.status_code,EC2_RESPONSE_OBJECTS[tree.tag](tree) 166 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | from aws import EC2Service 9 | 10 | 11 | # CHANGE THIS TO YOUR KEY ID 12 | AWS_ACCESS_KEY_ID="YOURAPIKEYID" 13 | 14 | # CHANGE THIS TO POINT TO THE HSM 15 | HSM_API_URL="http://127.0.0.1:8086/sign/aws" 16 | 17 | 18 | if __name__ == "__main__": 19 | service = EC2Service(AWS_ACCESS_KEY_ID, HSM_API_URL) 20 | status,response = service.describe_instances() 21 | if status == 200: 22 | for reservation in response.reservations: 23 | for instance in reservation.instances: 24 | print reservation.reservationId, instance.instanceId, instance.instanceType,instance.instanceState.name 25 | else: 26 | print "Request failed with error",status 27 | print response 28 | 29 | -------------------------------------------------------------------------------- /hsm.ino: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | 7 | 8 | // Configure your AWS Keys and Secrets here 9 | 10 | struct AWSCredentials { 11 | const char *key; 12 | const char *secret; 13 | }; 14 | 15 | AWSCredentials gAWSCredentials[] = { 16 | {"YOURKEYID", "YOURSECRETKEY"}, 17 | {NULL, NULL} 18 | }; 19 | 20 | 21 | 22 | 23 | // SHA1 / HMAC-SHA1 API (taken from http://gcc.gnu.org/onlinedocs/libiberty/) 24 | 25 | typedef unsigned int sha1_uint32; 26 | 27 | struct sha1_ctx 28 | { 29 | sha1_uint32 A; 30 | sha1_uint32 B; 31 | sha1_uint32 C; 32 | sha1_uint32 D; 33 | sha1_uint32 E; 34 | 35 | sha1_uint32 total[2]; 36 | sha1_uint32 buflen; 37 | sha1_uint32 buffer[32]; 38 | }; 39 | 40 | 41 | void sha1_init_ctx (struct sha1_ctx *ctx); 42 | void sha1_process_block (const void *buffer, size_t len, struct sha1_ctx *ctx); 43 | void sha1_process_bytes (const void *buffer, size_t len, struct sha1_ctx *ctx); 44 | void *sha1_finish_ctx (struct sha1_ctx *ctx, void *resbuf); 45 | void *sha1_read_ctx (const struct sha1_ctx *ctx, void *resbuf); 46 | void *sha1_buffer (const char *buffer, size_t len, void *resblock); 47 | 48 | 49 | 50 | 51 | 52 | 53 | // HSM Code Starts Here 54 | 55 | const char *FindSecretForKey(const char *key) 56 | { 57 | AWSCredentials* credentials = gAWSCredentials; 58 | while (credentials->key != NULL && credentials->secret != NULL) { 59 | if (strcmp(credentials->key, key) == 0) { 60 | return credentials->secret; 61 | } 62 | credentials++; 63 | } 64 | return NULL; 65 | } 66 | 67 | void setup() 68 | { 69 | Serial.begin(115200); 70 | } 71 | 72 | void loop() 73 | { 74 | // Read a request from the serial port 75 | 76 | char buffer[8192]; 77 | size_t length = Serial.readBytesUntil(0x00, buffer, sizeof(buffer) - 1); 78 | if (length > 0) 79 | { 80 | buffer[length] = 0x00; 81 | 82 | // We only respond to the SIGN-AWS-V2 command 83 | 84 | if (strncmp(buffer, "SIGN-AWS-V2 ", strlen("SIGN-AWS-V2 ")) != 0) { 85 | Serial.println("ERROR invalid-command"); 86 | return; 87 | } 88 | 89 | char *message = buffer + strlen("SIGN-AWS-V2 "); 90 | 91 | // Parse the key out of the request 92 | 93 | 94 | char* p = strstr(message, "AWSAccessKeyId="); 95 | if (p == NULL) { 96 | Serial.println("ERROR awsaccesskeyid-not-found"); 97 | return; 98 | } 99 | 100 | p += strlen("AWSAccessKeyId="); 101 | 102 | char key[64 + 1]; 103 | unsigned int key_length = 0; 104 | char *keyp = key; 105 | 106 | while (*p != 0x00 && *p != '&') { 107 | *keyp++ = *p++; 108 | if (++key_length > 64) { 109 | Serial.println("ERROR invalid-awsaccesskeyid"); 110 | return; 111 | } 112 | } 113 | *keyp = 0x00; 114 | 115 | // See if we have a secret for this key 116 | 117 | const char *secret = FindSecretForKey(key); 118 | if (secret == NULL) { 119 | Serial.println("ERROR unknown-awsaccesskey"); 120 | return; 121 | } 122 | 123 | // Sign the incoming request 124 | 125 | unsigned char digest[20]; 126 | hmac_sha1(secret, strlen(secret), message, strlen(message), digest); 127 | 128 | // Print the result back as a hex digest 129 | 130 | Serial.print("SUCCESS "); 131 | for (int i = 0; i < sizeof(digest); i++) { 132 | if (digest[i] < 0x10) { 133 | Serial.print("0"); 134 | } 135 | Serial.print(digest[i], HEX); 136 | } 137 | Serial.println(""); 138 | } 139 | } 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | // SHA1 / HMAC-SHA1 Code Starts Here 149 | 150 | # define SWAP(n) (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) 151 | 152 | static const unsigned char fillbuf[64] = { 153 | 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 154 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 155 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 156 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 157 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 158 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 159 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 160 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 161 | }; 162 | 163 | 164 | void sha1_init_ctx (struct sha1_ctx *ctx) 165 | { 166 | ctx->A = 0x67452301; 167 | ctx->B = 0xefcdab89; 168 | ctx->C = 0x98badcfe; 169 | ctx->D = 0x10325476; 170 | ctx->E = 0xc3d2e1f0; 171 | 172 | ctx->total[0] = ctx->total[1] = 0; 173 | ctx->buflen = 0; 174 | } 175 | 176 | void * 177 | sha1_read_ctx (const struct sha1_ctx *ctx, void *resbuf) 178 | { 179 | ((sha1_uint32 *) resbuf)[0] = SWAP (ctx->A); 180 | ((sha1_uint32 *) resbuf)[1] = SWAP (ctx->B); 181 | ((sha1_uint32 *) resbuf)[2] = SWAP (ctx->C); 182 | ((sha1_uint32 *) resbuf)[3] = SWAP (ctx->D); 183 | ((sha1_uint32 *) resbuf)[4] = SWAP (ctx->E); 184 | 185 | return resbuf; 186 | } 187 | 188 | void * 189 | sha1_finish_ctx (struct sha1_ctx *ctx, void *resbuf) 190 | { 191 | /* Take yet unprocessed bytes into account. */ 192 | sha1_uint32 bytes = ctx->buflen; 193 | size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; 194 | 195 | /* Now count remaining bytes. */ 196 | ctx->total[0] += bytes; 197 | if (ctx->total[0] < bytes) 198 | ++ctx->total[1]; 199 | 200 | /* Put the 64-bit file length in *bits* at the end of the buffer. */ 201 | ctx->buffer[size - 2] = SWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29)); 202 | ctx->buffer[size - 1] = SWAP (ctx->total[0] << 3); 203 | 204 | memcpy (&((char *) ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); 205 | 206 | /* Process last bytes. */ 207 | sha1_process_block (ctx->buffer, size * 4, ctx); 208 | 209 | return sha1_read_ctx (ctx, resbuf); 210 | } 211 | 212 | /* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The 213 | result is always in little endian byte order, so that a byte-wise 214 | output yields to the wanted ASCII representation of the message 215 | digest. */ 216 | void * 217 | sha1_buffer (const char *buffer, size_t len, void *resblock) 218 | { 219 | struct sha1_ctx ctx; 220 | 221 | /* Initialize the computation context. */ 222 | sha1_init_ctx (&ctx); 223 | 224 | /* Process whole buffer but last len % 64 bytes. */ 225 | sha1_process_bytes (buffer, len, &ctx); 226 | 227 | /* Put result in desired memory area. */ 228 | return sha1_finish_ctx (&ctx, resblock); 229 | } 230 | 231 | void 232 | sha1_process_bytes (const void *buffer, size_t len, struct sha1_ctx *ctx) 233 | { 234 | /* When we already have some bits in our internal buffer concatenate 235 | both inputs first. */ 236 | if (ctx->buflen != 0) 237 | { 238 | size_t left_over = ctx->buflen; 239 | size_t add = 128 - left_over > len ? len : 128 - left_over; 240 | 241 | memcpy (&((char *) ctx->buffer)[left_over], buffer, add); 242 | ctx->buflen += add; 243 | 244 | if (ctx->buflen > 64) 245 | { 246 | sha1_process_block (ctx->buffer, ctx->buflen & ~63, ctx); 247 | 248 | ctx->buflen &= 63; 249 | /* The regions in the following copy operation cannot overlap. */ 250 | memcpy (ctx->buffer, 251 | &((char *) ctx->buffer)[(left_over + add) & ~63], 252 | ctx->buflen); 253 | } 254 | 255 | buffer = (const char *) buffer + add; 256 | len -= add; 257 | } 258 | 259 | /* Process available complete blocks. */ 260 | if (len >= 64) 261 | { 262 | #if !_STRING_ARCH_unaligned 263 | # define alignof(type) offsetof (struct { char c; type x; }, x) 264 | # define UNALIGNED_P(p) (((size_t) p) % alignof (sha1_uint32) != 0) 265 | if (UNALIGNED_P (buffer)) 266 | while (len > 64) 267 | { 268 | sha1_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx); 269 | buffer = (const char *) buffer + 64; 270 | len -= 64; 271 | } 272 | else 273 | #endif 274 | { 275 | sha1_process_block (buffer, len & ~63, ctx); 276 | buffer = (const char *) buffer + (len & ~63); 277 | len &= 63; 278 | } 279 | } 280 | 281 | /* Move remaining bytes in internal buffer. */ 282 | if (len > 0) 283 | { 284 | size_t left_over = ctx->buflen; 285 | 286 | memcpy (&((char *) ctx->buffer)[left_over], buffer, len); 287 | left_over += len; 288 | if (left_over >= 64) 289 | { 290 | sha1_process_block (ctx->buffer, 64, ctx); 291 | left_over -= 64; 292 | memcpy (ctx->buffer, &ctx->buffer[16], left_over); 293 | } 294 | ctx->buflen = left_over; 295 | } 296 | } 297 | 298 | /* --- Code below is the primary difference between md5.c and sha1.c --- */ 299 | 300 | /* SHA1 round constants */ 301 | #define K1 0x5a827999 302 | #define K2 0x6ed9eba1 303 | #define K3 0x8f1bbcdc 304 | #define K4 0xca62c1d6 305 | 306 | /* Round functions. Note that F2 is the same as F4. */ 307 | #define F1(B,C,D) ( D ^ ( B & ( C ^ D ) ) ) 308 | #define F2(B,C,D) (B ^ C ^ D) 309 | #define F3(B,C,D) ( ( B & C ) | ( D & ( B | C ) ) ) 310 | #define F4(B,C,D) (B ^ C ^ D) 311 | 312 | /* Process LEN bytes of BUFFER, accumulating context into CTX. 313 | It is assumed that LEN % 64 == 0. 314 | Most of this code comes from GnuPG's cipher/sha1.c. */ 315 | 316 | void 317 | sha1_process_block (const void *buffer, size_t len, struct sha1_ctx *ctx) 318 | { 319 | const sha1_uint32 *words = (const sha1_uint32*) buffer; 320 | size_t nwords = len / sizeof (sha1_uint32); 321 | const sha1_uint32 *endp = words + nwords; 322 | sha1_uint32 x[16]; 323 | sha1_uint32 a = ctx->A; 324 | sha1_uint32 b = ctx->B; 325 | sha1_uint32 c = ctx->C; 326 | sha1_uint32 d = ctx->D; 327 | sha1_uint32 e = ctx->E; 328 | 329 | /* First increment the byte count. RFC 1321 specifies the possible 330 | length of the file up to 2^64 bits. Here we only compute the 331 | number of bytes. Do a double word increment. */ 332 | ctx->total[0] += len; 333 | if (ctx->total[0] < len) 334 | ++ctx->total[1]; 335 | 336 | #define rol(x, n) (((x) << (n)) | ((sha1_uint32) (x) >> (32 - (n)))) 337 | 338 | #define M(I) ( tm = x[I&0x0f] ^ x[(I-14)&0x0f] \ 339 | ^ x[(I-8)&0x0f] ^ x[(I-3)&0x0f] \ 340 | , (x[I&0x0f] = rol(tm, 1)) ) 341 | 342 | #define R(A,B,C,D,E,F,K,M) do { E += rol( A, 5 ) \ 343 | + F( B, C, D ) \ 344 | + K \ 345 | + M; \ 346 | B = rol( B, 30 ); \ 347 | } while(0) 348 | 349 | while (words < endp) 350 | { 351 | sha1_uint32 tm; 352 | int t; 353 | for (t = 0; t < 16; t++) 354 | { 355 | x[t] = SWAP (*words); 356 | words++; 357 | } 358 | 359 | R( a, b, c, d, e, F1, K1, x[ 0] ); 360 | R( e, a, b, c, d, F1, K1, x[ 1] ); 361 | R( d, e, a, b, c, F1, K1, x[ 2] ); 362 | R( c, d, e, a, b, F1, K1, x[ 3] ); 363 | R( b, c, d, e, a, F1, K1, x[ 4] ); 364 | R( a, b, c, d, e, F1, K1, x[ 5] ); 365 | R( e, a, b, c, d, F1, K1, x[ 6] ); 366 | R( d, e, a, b, c, F1, K1, x[ 7] ); 367 | R( c, d, e, a, b, F1, K1, x[ 8] ); 368 | R( b, c, d, e, a, F1, K1, x[ 9] ); 369 | R( a, b, c, d, e, F1, K1, x[10] ); 370 | R( e, a, b, c, d, F1, K1, x[11] ); 371 | R( d, e, a, b, c, F1, K1, x[12] ); 372 | R( c, d, e, a, b, F1, K1, x[13] ); 373 | R( b, c, d, e, a, F1, K1, x[14] ); 374 | R( a, b, c, d, e, F1, K1, x[15] ); 375 | R( e, a, b, c, d, F1, K1, M(16) ); 376 | R( d, e, a, b, c, F1, K1, M(17) ); 377 | R( c, d, e, a, b, F1, K1, M(18) ); 378 | R( b, c, d, e, a, F1, K1, M(19) ); 379 | R( a, b, c, d, e, F2, K2, M(20) ); 380 | R( e, a, b, c, d, F2, K2, M(21) ); 381 | R( d, e, a, b, c, F2, K2, M(22) ); 382 | R( c, d, e, a, b, F2, K2, M(23) ); 383 | R( b, c, d, e, a, F2, K2, M(24) ); 384 | R( a, b, c, d, e, F2, K2, M(25) ); 385 | R( e, a, b, c, d, F2, K2, M(26) ); 386 | R( d, e, a, b, c, F2, K2, M(27) ); 387 | R( c, d, e, a, b, F2, K2, M(28) ); 388 | R( b, c, d, e, a, F2, K2, M(29) ); 389 | R( a, b, c, d, e, F2, K2, M(30) ); 390 | R( e, a, b, c, d, F2, K2, M(31) ); 391 | R( d, e, a, b, c, F2, K2, M(32) ); 392 | R( c, d, e, a, b, F2, K2, M(33) ); 393 | R( b, c, d, e, a, F2, K2, M(34) ); 394 | R( a, b, c, d, e, F2, K2, M(35) ); 395 | R( e, a, b, c, d, F2, K2, M(36) ); 396 | R( d, e, a, b, c, F2, K2, M(37) ); 397 | R( c, d, e, a, b, F2, K2, M(38) ); 398 | R( b, c, d, e, a, F2, K2, M(39) ); 399 | R( a, b, c, d, e, F3, K3, M(40) ); 400 | R( e, a, b, c, d, F3, K3, M(41) ); 401 | R( d, e, a, b, c, F3, K3, M(42) ); 402 | R( c, d, e, a, b, F3, K3, M(43) ); 403 | R( b, c, d, e, a, F3, K3, M(44) ); 404 | R( a, b, c, d, e, F3, K3, M(45) ); 405 | R( e, a, b, c, d, F3, K3, M(46) ); 406 | R( d, e, a, b, c, F3, K3, M(47) ); 407 | R( c, d, e, a, b, F3, K3, M(48) ); 408 | R( b, c, d, e, a, F3, K3, M(49) ); 409 | R( a, b, c, d, e, F3, K3, M(50) ); 410 | R( e, a, b, c, d, F3, K3, M(51) ); 411 | R( d, e, a, b, c, F3, K3, M(52) ); 412 | R( c, d, e, a, b, F3, K3, M(53) ); 413 | R( b, c, d, e, a, F3, K3, M(54) ); 414 | R( a, b, c, d, e, F3, K3, M(55) ); 415 | R( e, a, b, c, d, F3, K3, M(56) ); 416 | R( d, e, a, b, c, F3, K3, M(57) ); 417 | R( c, d, e, a, b, F3, K3, M(58) ); 418 | R( b, c, d, e, a, F3, K3, M(59) ); 419 | R( a, b, c, d, e, F4, K4, M(60) ); 420 | R( e, a, b, c, d, F4, K4, M(61) ); 421 | R( d, e, a, b, c, F4, K4, M(62) ); 422 | R( c, d, e, a, b, F4, K4, M(63) ); 423 | R( b, c, d, e, a, F4, K4, M(64) ); 424 | R( a, b, c, d, e, F4, K4, M(65) ); 425 | R( e, a, b, c, d, F4, K4, M(66) ); 426 | R( d, e, a, b, c, F4, K4, M(67) ); 427 | R( c, d, e, a, b, F4, K4, M(68) ); 428 | R( b, c, d, e, a, F4, K4, M(69) ); 429 | R( a, b, c, d, e, F4, K4, M(70) ); 430 | R( e, a, b, c, d, F4, K4, M(71) ); 431 | R( d, e, a, b, c, F4, K4, M(72) ); 432 | R( c, d, e, a, b, F4, K4, M(73) ); 433 | R( b, c, d, e, a, F4, K4, M(74) ); 434 | R( a, b, c, d, e, F4, K4, M(75) ); 435 | R( e, a, b, c, d, F4, K4, M(76) ); 436 | R( d, e, a, b, c, F4, K4, M(77) ); 437 | R( c, d, e, a, b, F4, K4, M(78) ); 438 | R( b, c, d, e, a, F4, K4, M(79) ); 439 | 440 | a = ctx->A += a; 441 | b = ctx->B += b; 442 | c = ctx->C += c; 443 | d = ctx->D += d; 444 | e = ctx->E += e; 445 | } 446 | } 447 | 448 | 449 | static const unsigned int HMAC_SHA1_BLOCKSIZE = 64; 450 | 451 | void * 452 | hmac_sha1(const char *key_buffer, size_t key_length, const char *msg_buffer, size_t msg_length, void *result) 453 | { 454 | // Initialize the key with zeros 455 | 456 | unsigned char key[HMAC_SHA1_BLOCKSIZE]; 457 | for (unsigned int i = 0; i < HMAC_SHA1_BLOCKSIZE; i++) { 458 | key[i] = 0; 459 | } 460 | 461 | // If the key is too long, replace it with a SHA1 hash. Otherwise just copy it. It will be padded with zeros. 462 | 463 | if (key_length > HMAC_SHA1_BLOCKSIZE) { 464 | // TODO 465 | } else { 466 | for (unsigned int i = 0; i < key_length; i++) { 467 | key[i] = key_buffer[i]; 468 | } 469 | } 470 | 471 | // Calculate o and i. Both are B long 472 | 473 | unsigned char o_key_pad[HMAC_SHA1_BLOCKSIZE]; 474 | unsigned char i_key_pad[HMAC_SHA1_BLOCKSIZE]; 475 | 476 | for (unsigned int i = 0; i < HMAC_SHA1_BLOCKSIZE; i++) { 477 | i_key_pad[i] = 0x36 ^ key[i]; 478 | o_key_pad[i] = 0x5c ^ key[i]; 479 | } 480 | 481 | // hash of i_key_pad + msg 482 | 483 | unsigned char tmp[20]; 484 | 485 | struct sha1_ctx ctx; 486 | 487 | sha1_init_ctx (&ctx); 488 | sha1_process_bytes(i_key_pad, HMAC_SHA1_BLOCKSIZE, &ctx); 489 | sha1_process_bytes(msg_buffer, msg_length, &ctx); 490 | sha1_finish_ctx(&ctx, tmp); 491 | 492 | // hash of o_key_pad + tmp 493 | 494 | sha1_init_ctx (&ctx); 495 | sha1_process_bytes(o_key_pad, HMAC_SHA1_BLOCKSIZE, &ctx); 496 | sha1_process_bytes(tmp, 20, &ctx); 497 | sha1_finish_ctx(&ctx, result); 498 | 499 | return result; 500 | } 501 | 502 | 503 | -------------------------------------------------------------------------------- /hsmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | from bottle import request, route, run, template 9 | import serial 10 | import sys 11 | 12 | def sign(port, data): 13 | port.write("SIGN-AWS-V2 " + data + "\x00") 14 | response = port.readline().strip() 15 | (status, data) = response.split() 16 | return (status, data) 17 | 18 | @route('/sign/aws', method='POST') 19 | def index(): 20 | (status,data) = sign(ser, request.body.getvalue()) 21 | if status == 'SUCCESS': 22 | return {"success":True, "signature":data} 23 | else: 24 | return {"success":False, "error": data} 25 | 26 | if __name__ == "__main__": 27 | ser = serial.Serial(sys.argv[1], 115200) 28 | run(host='127.0.0.1', server='wsgiref', port=8086) 29 | --------------------------------------------------------------------------------