Thursday, November 2, 2017

NaMeCryReNoWriMo, day 2: AES and GCM

AES (Advanced Encryption Standard)


https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
https://en.wikipedia.org/wiki/Rijndael_key_schedule
https://csrc.nist.gov/csrc/media/publications/fips/197/final/documents/fips-197.pdf

AES is a symmetric block cipher, one of the most widely used and most secure (if used properly).  Although quantum computers would weaken AES (approximately cutting the effective key size in half), it's still feasible to use by simply doubling the key size.  This is distinctly better than, say, RSA, which becomes unusable due to how much faster quantum computers could potentially be at breaking it.

Originally called Rijndael, which they should've stuck with because it's way cooler.

Multiple different versions for different key sizes.

Based on a substitution-permutation network, meaning it's a mix of exchanging bits for other bits (substitution) and shuffling bits into new orders (permutation).  Due to the simplicity of these operations it's extremely fast in both hardware and software.  Most processors these days have specific logical circuits for it (I think?) and you can buy ICs specifically dedicated to performing AES operations.

AES is performed in rounds.  10 for 128-bit, 12 for 192-bit, and 14 for 256-bit.  It's performed on each 128 bits of the message, and it's done with the bits arranged in a 4x4 matrix of bytes.  Upper-left is the first byte, then it counts down and then right, so it's in the order:

00, 04, 08, 12
01, 05, 09, 13
02, 06, 10, 14
03, 07, 11, 15

In total, the algorithm is something vaguely resembling the following:

-Generate the keys for the rounds, using a set formula and the key provided by the user.  In total AES needs n+1 128-bit keys, where n is the number of rounds, so these are generated from the 128/192/256-bit main key.
-xor each byte of the block being encrypted with one of the bytes of the first round key
-Now, do the following n-1 times:
--A lookup table is used to do some non-linear substitution.
--Rotate the lower three rows of the matrix a number of times.
--Perform an (invertible) linear transformation on the columns of the matrix
--XOR in the next round key
-For the last round, don't do the linear transformation:
--Substitution
--Rotate rows
--XOR round key
-Done!

There are attacks directly against AES that are faster than brute force, but none that appear to approach the level at which they could realistically be implemented.  However, modified versions of AES with a reduced number of rounds are potentially breakable, not than anyone uses AES with a reduced number of rounds and actually expects it to still be secure.

Most meaningful attacks against AES are side-channel attacks that don't attempt to break the cipher directly and instead rely on analyzing other information, such as how long it takes for an encryption to occur, what the resource usage of the machine performing the encryption is, and so on.

Modern intel processors have an AES throughput over 700MB/s

AES is a block cipher, so there are a number of different modes in which it can be used.  Based on glancing around online and talking with cryptographers, it sounds like GCM (Galois/counter mode) is the preferred mode.


??? Need to dive more into the details of the different transformations performed during the encryption process.


GCM (Galois/counter mode)


https://en.wikipedia.org/wiki/Galois/Counter_Mode
http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf

Mode of operation for symmetric key cryptographic block ciphers
efficient
authenticated encryption

Counter mode is just generating the keystream by encrypting successive values from a counter (1, 2, 3, etc).  Allows parallelization of both encryption and decryption and direct access to a specific part of the plaintext without decrypting the rest, because you can just increment the counter to whatever spot the block is at and generate the decryption key.
Although it sounds iffy to generate the keystream just from a basic counter like 1,2,3, as long as the cipher being used is secure, this is totally fine.  If there are security issues with it, that means that you're using a bad cipher, not that you should change the counter.

Galois refers to the authentication that's added on afterwards.
Based on Galois (finite) field multiplication.

Two functions, authenticated encryption and authenticated decryption

??? Need to study more math to understand how it works
??? What are the other modes of encrypting with symmetric block ciphers?


Galois fields (also called finite fields)


https://en.wikipedia.org/wiki/Finite_field
https://en.wikipedia.org/wiki/Field_(mathematics)

Field: A set of elements that has the operations multiplication, addition, division, and subtraction.  These operations must comply with:
-Associativity (addition and multiplication): a+(b+c)=(a+b)+c and a*(b*c)=(a*b)*c
-Commutativity (addition and multiplication): a+b=b+a and a*b=b*a
-Identity (addition and multiplication): There exist two elements 0 and 1 such that a+0=a and a*1=a
-Inverse (addition): For every element a, there exists an element denoted by -a such that a+(-a)=0
-Inverse (multiplication): For every element a apart from 0, there exists an element denoted by a^-1 or 1/a such that a*(1/a)=1
-Distributivity of multiplication over addition: a*(b+c)=(a*b)+(a*c)

Finite fields are (prepare to be shocked) fields that contain a (again, try not to fall off your chair) finite number of elements.  The number of elements is called the order of the field.  All finite fields contain p^k elements, where p is some prime number and k is a positive integer.  A common example is integers mod p, where p is a prime number.  As an example, let's look at integers mod 3: (0,1,2)
-Commutativity, associativity, identity, and distributivity are pretty clear so I'll skip them.
-Additive inverse?  Yep.  Inverses are (0, 2, 1), respectively. 0+0=0 is obvious.  2+1 = 3 = 0 mod 3 is a bit less clear if you aren't familiar with modular math.
-Multiplicative inverse?  Yep.  Inverses are (na, 2, 1), respectively.  0 has no multiplicative inverse, which is fine.  2*2 = 4 = 1 mod 3, and 1*1 = 1

??? Not a question, but wow do I need to refresh my college algebra.

Wednesday, November 1, 2017

National Meandering Crypto Refresher Notes Writing Month (NaMeCryReNoWriMo), day 1: TLS and KRACK

(Doesn't have quite the same ring to it as NaNoWriMo, but I've gotta work with what I have.)  I've been meaning to brush up on crypto for a while, so to help with that I'm going to post the notes I write up while reading stuff.  I'll try to make them vaguely followable but from day to day they'll jump around quite a bit depending on what I feel like reading about.  Also I won't necessarily write up notes for stuff I already know pretty well, so some of the basics will go completely uncovered.  Also also these will mostly be based on just reading a couple pieces about each topic, so it's likely there'll be quite the collection of fatal oversimplifications and straight-up errors.

At the end of every topic will be the list of questions it prompted, which is how I'll pick the next things to read about.

As a result, this is probably useless to everyone except for me, but if you happen to find it beneficial, cool.


TLS (Transport Layer Security) notes


Pages read:
https://en.wikipedia.org/wiki/Transport_Layer_Security
https://blog.cloudflare.com/tls-1-3-overview-and-q-and-a/


Followup to Secure Sockets Layer (SSL).  SSL is no longer used due to vulnerabilities to POODLE and attacks on RC4.


Allows private, authenticated, integrous communication.
-Privacy: Asymmetric (public-key) cryptography is used to establish a symmetric session key, which provides encryption and makes it apparent if the messages are being altered (they'll likely become gibberish).  This encryption makes it so that anyone who sees the messages traveling over the wire can't see what they actually say.
-Authentication: Almost always, the server (the recipient of the initial connection request, not the initiator) uses an asymmetric certificate to demonstrate its identity.  This allows the caller to trust that it is who it says it is.  In certain cases the server will also require the client to demonstrate its identity in the same way.
-Integrity: Message authentication codes are used to make it clear when the message has changed in transit.


Some implementations of TLS provide forward secrecy, which means that even if the asymmetric keys used for the initial key exchange are exposed, any old communications are still secure.  This requires that the combination of the asymmetric keys and the data that was sent over the network are not sufficient to determine the session key.  If they're used to directly exchange a symmetric key, this doesn't work and old communications can be decrypted.  If they're used to encrypt something like a Diffie-Hellman key exchange, and the key generated during that exchange is deleted when the session is over, then forward secrecy is maintained.


Over time, there have been a couple versions of TLS, 1.0, 1.1, 1.2, and 1.3 (which is still incomplete).  These have fixed or improved aspects related to:
-Attacks on the CBC (cipher-block chaining) mode of symmetric encryption.
-Weaknesses in the MD5-SHA-1 hash function used in pseudorandom number generation.
-Added support for authenticated encryption like Galois/Counter Mode and CCM mode of AES.


What's (probably) changing in TLS 1.3?
-Certain weak elliptic curves are no longer supported.
-Certain weak hash functions (MD5 and SHA-224) are no longer supported.
-Adding support for HKDF.
-Adding support for PSK (pre-shared keys).
-Supporting faster handshakes (1 round-trip and moving towards 0 round-trip)
-Adding support for the ChaCha20 stream cipher with Poly1305 MAC
-Adding support for the Ed25519 and Ed448 digital signature algorithms.
-Adding support for the x25519 and x448 key exchange protocols.


What algorithms are supported in TLS1.3?
Initial key exchange:
-DHE-RSA
-ECDHE-RSA
-ECDHE-ECDSA
-and possibly others.
Notably, the three listed above all offer forward secrecy.
Cipher:
-AES GCM
-AES CCM
-Camellia GCM
-ARIA GCM
-ChaCha20-Poly1305


TLS1.2 has a handshake along the lines of the following:
Client: Yo server, I support (x,y,z) ciphers.
Server: Cool, let's use x.  Here's my part of the key exchange
Client: Awesome, here's my part.
Server: Looks legit.  Go on.
Client: (gets to start using the website)

TLS1.3 cuts down on round-trips by (basically) having the client guess what key the server will want to use:
Client: Yo server, I support (x,y,z) ciphers, and here's my part of the key exchange for x because x rocks.
Server: x is cool, so here's my part.
Client: Looks legit. (gets to start using the website)

I assume that if the server doesn't support x, it'll just fall back to the normal 2-round-trip process?  Haven't read through the explicit TLS1.3 spec yet so I'm not positive.


TLS1.3 also allows 0-round-trip session reestablishment by having the client and/or server cache one of the keys from the last time they communicated.


In general, TLS1.3 straight-up bans a lot of dumb stuff that was recommended against but still allowed in certain situations by TLS1.2


Open questions from this:


??? What's POODLE?
??? What's RC4?
??? What does the PKI and CAs look like that support TLS?
??? What exactly is a message authentication code?
??? What's Galois/Counter Mode?
??? What's CCM?
??? How exactly does AES function?
??? What's HKDF?
??? What's TLS-PSK?
??? What's ChaCha20 and Poly1305?
??? What are Ed25519 and Ed448?
??? What are x25519 and x448?
??? What are DHE-RSA, ECDHE-RSA, and ECDHE-ECDSA?
??? What are Camellia and ARIA?


KRACK (Key Reinstallation AttaCKs) notes


Pages read:


At the start of a WPA2 session, the device and the router perform a key handshake.  KRACK interferes with this handshake and performs a replay attack.  One of the messages of the handshake causes the client to set the key and nonce (IV) used to the initial value for the session.  By repeatedly sending this message, the 3rd in the 4-part handshake, the client can be made to send multiple messages encrypted using the same keystream (that is, the messages are encrypted by XORing them against the same stream of bytes) (because apparently WPA2 uses a stream cipher?).  With this, a single known message will let you decrypt all other messages encrypted with the same key.  This could be accomplished by finding a common header, or, if you can predict characteristics of the underlying data (for example, that it's likely ASCII text), this could be accomplished with a couple messages even without one being concretely known in advance.


If the TCP SYN packets are captured, the connection itself can be hijacked.


wpa_supplicant 2.4 and above (a wifi client commonly used on linux, and by extension, android) clears the negotiated key from memory after the first time it's told to install it, as apparently the spec for the protocol says that's something you can consider doing, and as a result the replay in the attack causes the installation of a key that's all 0s (or something like that).  That makes it even easier to eavesdrop.


Doesn't leak the actual WPA2 password.


Open questions from this:


??? How does the TCP hijack work?
??? What exact stream generator is used in WPA2, and what does the full handshake (or handshakes?) look like.
??? WPA-TKIP? GCMP? AES-CCMP?
??? 4-way handshake? Fast BSS Transitional (FT) handshake?

Saturday, February 6, 2016

Using KMS encryption contexts in AWS IAM policies

I use KMS encryption contexts in some of my projects, but the documentation available on how to embed them in IAM policies is incredibly minimal.  I couldn't find a single full example, just a vague description in the standard IAM docs.  So, here are some different example snippets I've come up with.

(Note: in all of the examples I'm going to talk about the permissions required to decrypt an encrypted blob, as that's the easiest to follow conceptually, but the same rules apply to the other actions included.)

--------------------

A statement to allow an IAM entity to perform all crypto operations on an object that was encrypted with a context including, but not limited to, {"Key":"Value"}:

{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value"
    }
  }
}

Acceptable contexts according to this policy:
{"Key":"Value"}
{"Key":"Value", "Marco":"Polo"}
{"Key":"Value", "Foo":"Bar"}
{"Key":"Value", "Foo":"Bar", "Marco":"Polo"}

Unacceptable contexts according to this policy:
{"Foo":"Bar"}
{"Marco":"Polo"}
{"Foo":"Bar", "Marco":"Polo"}

--------------------

One important note that you probably already know about decrypting with encryption contexts: when the actual decrypt call is being made, the entire context used to encrypt it must be provided, not just the part in the user's IAM policy.  I personally just store the encryption context alongside the encrypted blob; I believe this is both not a security risk and probably standard practice but someone please correct me if I'm wrong.

--------------------

If you wanted to limit the above policy to only be able to decrypt objects with the context {"Key":"Value", "Foo":"Bar"}, and not objects with just {"Key":"Value"} or just {"Foo":"Bar"}, you'd add another element to the StringEquals list like so:

{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value",
      "kms:EncryptionContext:Foo": "Bar"
    }
  }
}

Acceptable contexts according to this policy:
{"Key":"Value", "Foo":"Bar"}
{"Key":"Value", "Foo":"Bar", "Marco":"Polo"}

Unacceptable contexts according to this policy:
{"Key":"Value"}
{"Foo":"Bar"}
{"Marco":"Polo"}
{"Foo":"Bar", "Marco":"Polo"}
{"Key":"Value", "Marco":"Polo"}

--------------------

If you want a policy to allow a user to decrypt objects with contexts containing either {"Key":"Value"} OR {"Foo":"Bar"}, I'm pretty sure you'd need two policy statements:

{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value"
    }
  }
},
{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Foo": "Bar"
    }
  }
}

Acceptable contexts according to this policy:
{"Key":"Value"}
{"Foo":"Bar"}
{"Key":"Value", "Marco":"Polo"}
{"Key":"Value", "Foo":"Bar"}
{"Foo":"Bar", "Marco":"Polo"}
{"Key":"Value", "Foo":"Bar", "Marco":"Polo"}

Unacceptable contexts according to this policy:
{"Marco":"Polo"}

--------------------

Finally, if you want to allow decryption of blobs encrypted with either {"Key":"Value"} or {"Foo":"Bar"}, but not both {"Key":"Value","Foo":"Bar"}, you could use a combination of the following three statements (note that the first two have the effect "Allow" but the third has the effect "Deny"):

{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value"
    }
  }
},
{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Foo": "Bar"
    }
  }
},
{
  "Effect": "Deny",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value",
      "kms:EncryptionContext:Foo": "Bar"
    }
  }
}

Acceptable contexts according to this policy:
{"Key":"Value"}
{"Foo":"Bar"}
{"Key":"Value", "Marco":"Polo"}
{"Foo":"Bar", "Marco":"Polo"}

Unacceptable contexts according to this policy:
{"Marco":"Polo"}
{"Key":"Value", "Foo":"Bar"}
{"Key":"Value", "Foo":"Bar", "Marco":"Polo"}

--------------------

I think with the above examples you should be able to piece together any encryption-context-related IAM policy that you'd need.  If you come up with something that I didn't cover, though, definitely let me know and I'll try to put something together.

Friday, January 29, 2016

toco: DynamoDB-based user management for Django

I've been playing around a bit with Django websites lately, but I just can't stand relational databases.  I recognize that they're sometimes the right solution to a problem, but even the hosted solutions like AWS RDS add infrastructure overhead that managed NoSQL DBs like DynamoDB don't.  There are more creds to manage, annoying schemas to limit you, scaling pain that needs handled, all that jazz.  As such, I'm attempting to make a Django user management system based on DynamoDB.  I'm trying to make the underpinnings generic enough that it'll also be usable for other models, not just user auth and session management, but we'll see.

Repo is stevenorum/toco on GitHub.  Still very early on, just a couple basic things have been implemented so far, but hopefully it'll eventually be useful.

Tuesday, January 26, 2016

Chocolate raspberry stout (partial mash)

Grains:
Chocolate malt (40 oz)
Chocolate rye (48 oz)
Midnight wheat (12 oz)

Extract:
Wheat DME (6 lb)

Boil:
60 minutes:
Cocoa powder (8 oz)
Cascade hops (2 oz, ~5.5% alpha)

Yeast:

Water:
1 gallon spring water
4 gallons distilled water

OG: 1.07

Secondary fermentation will split the batch.  1 gallon is going to have raspberry flavor extract added to it; the other ~4 gallons are going to have ~10 lbs of raspberry puree added.  ETA for this is 31 January or 5 February.

Started late 24th/early 25th January 2016.  Updates to come as it progresses.

Saturday, April 11, 2015

National parks and article titles

A few days ago, articles with titles like "Senate Republicans Vote To Sell Off Our National Parks To Private Industry" started appearing regarding the US Senate passing Amendment 838 to Concurrent Resolution 11.  Although the amendment in question is certainly not one I support for a number of reasons, the title of the article I linked above (and many other articles about the amendment have similar titles) is misleading.  First, the amendment actually specifically excludes national parks, as well as national preserves and national monuments.  It includes all other federal lands, though, meaning national forests, wilderness areas, and others.  Second, it isn't a bill selling any lands, or even making legal the selling of any lands.  What it discusses is "sell[ing] or transfer[ring] [the aforementioned lands] to, or exchang[ing] with, a State or local government", and it doesn't even authorize that.  It basically says that in the future this should be considered in another committee or amendment.  (It also doesn't directly include anything about private industry, but the discussion surrounding the amendment and other attempted laws over the past few years make it clear that the purpose of this law would be to then have the state and local governments sell or lease the lands to energy companies.)
Not all of the articles are straight-up factually incorrect like the above one.  Gizmodo's article at least properly references national forests instead of parks in the title, and (albeit buried deep in the article) includes the disclaimer about it being a nonbinding budget amendment.  However, I expect that 90%+ of the people who see even that article are going to come away with an incorrect impression of what occurred, and that's not a stunning example of good journalistic ethics, to put it mildly.

That said, I completely agree with the authors of the above articles about this being a very bad sign, even if it isn't yet legally binding.  Although the protected national parks are more well-known, some of the most beautiful and impressive places in the country lies in the forests and wilderness areas, including pretty much every trail or peak I've ever hiked.  If a law like what's mentioned above does get passed and forests start being sold, I will be in the first wave of monkeywrenchers out there to stop it, but that hasn't happened yet and it's misleading to make people think that it has.

Wednesday, February 11, 2015

Random notes about AWS CodeDeploy

I've been playing around a bit with AWS CodeDeploy recently and found the documentation to be a bit lacking in places.  Here's what I've found so far that took a bit of digging to find, or that isn't what I expected the behavior to be.  It's only four things right now, but if I find more as I keep using it, I'll post them.

1. Scripts don't run during DownloadBundle, Install, or nonexistent steps, and no errors are thrown if you try.

The deployment lifecycle events are as follows:

-DownloadBundle
-BeforeInstall
-Install
-AfterInstall
-ApplicationStart
-ValidateService
-ApplicationStop

However, scripts that you list in your appspec.yml won't run during DownloadBundle (makes sense, as they aren't downloaded yet) or Install (which initially made no sense to me).  The Install step seems to just be copying over the files specified in the files block of the appspec file.  I imagine this is to avoid confusion about which happens first, the file copying or the script execution.  If you could run scripts in the same step as the file placement, you'd need some way to specify which came first, and if you let the user copy files, then run a script, then copy more files, specifying the exact order becomes prohibitively difficult.  I understand the need for this separate file-copy step, but the name is really misleading.  It should be something like CopyFiles instead of Install.

In addition, CodeDeploy won't complain at all if you have scripts specified in the DownloadBundle or Install blocks, it just won't ever do anything with them.  It won't give any indication that anything is wrong even if you add a block for a step that's completely made up, as long as everything is still valid YAML.  I really hope that this gets fixed.  I understand that the API call itself doesn't actually have the appspec file, as it's off in an archive somewhere, so they can't easily have the createDeployment call fail, but they could at least fail the deployment later or display a warning message in the console when you view the deployment.

(In fairness, this is included in the documentation (specifically, here), so the RTFM argument can always be made.  That said, I still wish they'd give some indication that you attempted something that won't work, as failing silently like this is a pretty bad user experience.)

2. The 'revision' argument structure for boto is poorly documented.

In boto, CodeDeploy deployments take in a "revision" argument that's just a dict.  It's never said specifically what the arguments are.  The names are the same as the ones contained in the API spec, but case matters, both for the keys ("revisionType" works but "RevisionType" fails) and the non-freeform values ("S3" is a valid revisionType, "s3" is not).  I've included an example S3 revision below.

(I imagine that for a GitHub revision it's the same, with the keys being the camelCased version of the argument name, but I haven't specifically tried.)

Here's an example of the proper format for an S3 revision:

    revision = {
        "revisionType": "S3",
        "s3Location": {
            "bucket": "MyBucket",
            "bundleType": "tar",
            "key": "PackagedStuff.tar"
        }
    }

I know it's traditional for Python and boto to use a much more free-form and less-documented data structure than type-safe languages like Java, but making the caller pass in a raw dict without clearly specifying how to set it up is a bit much.

3. DownloadBundle will fail due to any archive unpacking error.

The DownloadBundle step will fail if any of the archive doesn't unpack correctly.  This should probably be expected, but I've had archives where everything I cared about worked fine yet some random symlink was busted and caused the installation to fail.  If you normally unpack archives on the command line with verbose set to true, as I do, it can be easy to miss a couple files out of thousands failing, and so it's confusing when an archive that seemingly worked for you fails a deployment.  If you're test-unpacking a potential CodeDeploy archive with the utility "tar", make sure to call "echo $?" right afterwards to check the output code.

4. Install will fail if you try to copy a file over an existing file.

During the Install step, if there's already a file in a location where you're trying to put a file, it'll fail.  Even if they have the same contents, it'll fail.  I've gotten around this by copying the files to a staging directory in /tmp/, and then having an AfterInstall script that's basically:

#!/bin/bash
rsync /tmp/foo/bar/ /foo/bar/
rm -rf /tmp/foo/bar

There are probably countless ways to do this file swapover, depending on exactly your needs, but you'll need to pick and implement one as it isn't something that CodeDeploy handles nicely for you.  I can understand not wanting to pick how to handle this, as every possible choice they could make would break stuff for someone, but even still the current failure cases seem sub-optimal.

-----

Obligatory disclaimer: I'm currently a software engineer for Amazon Web Services; however, all of the random notes and views I post about AWS are 1. purely mine and not in any way a reflection of my employer's views and 2. based only on public information.  I don't work on CodeDeploy.