diff --git a/Dockerfile b/Dockerfile
index a8bbe255..b258b62a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,4 +6,4 @@ ADD ca-certificates.crt /etc/ssl/certs/
 VOLUME ["/data"]
 
 ENTRYPOINT ["/dispatch"]
-CMD ["-p=8080", "--dir=/data"]
\ No newline at end of file
+CMD ["--dir=/data"]
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 0fa4a942..94bc2d86 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -103,6 +103,10 @@
 			"ImportPath": "github.com/spf13/viper",
 			"Rev": "d62d4bb4c68a773c3b5f4e72844913a2d5de0de0"
 		},
+		{
+			"ImportPath": "github.com/square/go-jose",
+			"Rev": "37934a899dd03635373fd1e143936d32cfe48d31"
+		},
 		{
 			"ImportPath": "github.com/steveyen/gtreap",
 			"Rev": "72cd76f34c91f8d64a031af97b499e4a0b1a6e0c"
@@ -125,6 +129,11 @@
 			"Comment": "v1.0.0-17-g4b22041",
 			"Rev": "4b220417a489359f934045d0509d941a7a2a1038"
 		},
+		{
+			"ImportPath": "github.com/xenolf/lego/acme",
+			"Comment": "v0.1.1-34-g12b5de7",
+			"Rev": "12b5de7e8cb451949aabad64cce93e4b846e2aa7"
+		},
 		{
 			"ImportPath": "github.com/xordataexchange/crypt/backend",
 			"Comment": "v0.0.2-17-g749e360",
@@ -144,10 +153,18 @@
 			"ImportPath": "golang.org/x/crypto/cast5",
 			"Rev": "644910e6da851dcd66a424c71d068d971cfacba5"
 		},
+		{
+			"ImportPath": "golang.org/x/crypto/ocsp",
+			"Rev": "644910e6da851dcd66a424c71d068d971cfacba5"
+		},
 		{
 			"ImportPath": "golang.org/x/crypto/openpgp",
 			"Rev": "644910e6da851dcd66a424c71d068d971cfacba5"
 		},
+		{
+			"ImportPath": "golang.org/x/crypto/sha3",
+			"Rev": "644910e6da851dcd66a424c71d068d971cfacba5"
+		},
 		{
 			"ImportPath": "golang.org/x/text/transform",
 			"Rev": "c92eb3cd6e70951a111680995e651ea4b2c35539"
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/.gitignore b/Godeps/_workspace/src/github.com/square/go-jose/.gitignore
new file mode 100644
index 00000000..5b4d73b6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/.gitignore
@@ -0,0 +1,7 @@
+*~
+.*.swp
+*.out
+*.test
+*.pem
+*.cov
+jose-util/jose-util
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/.travis.yml b/Godeps/_workspace/src/github.com/square/go-jose/.travis.yml
new file mode 100644
index 00000000..f90eec19
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/.travis.yml
@@ -0,0 +1,36 @@
+language: go
+
+sudo: false
+
+matrix:
+  fast_finish: true
+  allow_failures:
+    - go: tip
+
+go:
+- 1.2
+- 1.3
+- 1.4
+- 1.5
+- tip
+
+before_script:
+- export PATH=$HOME/.local/bin:$PATH
+
+before_install:
+- go get github.com/axw/gocov/gocov
+- go get github.com/mattn/goveralls
+- go get golang.org/x/tools/cmd/cover || true
+- go get code.google.com/p/go.tools/cmd/cover || true
+- pip install cram --user `whoami`
+
+script:
+- go test . -v -covermode=count -coverprofile=profile.cov
+- go test ./cipher -v -covermode=count -coverprofile=cipher/profile.cov
+- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t
+- cd ..
+
+after_success:
+- tail -n+2 cipher/profile.cov >> profile.cov
+- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci
+
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/BUG-BOUNTY.md b/Godeps/_workspace/src/github.com/square/go-jose/BUG-BOUNTY.md
new file mode 100644
index 00000000..97e61dbb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/BUG-BOUNTY.md
@@ -0,0 +1,10 @@
+Serious about security
+======================
+
+Square recognizes the important contributions the security research community
+can make. We therefore encourage reporting security issues with the code
+contained in this repository.
+
+If you believe you have discovered a security vulnerability, please follow the
+guidelines at <https://hackerone.com/square-open-source>.
+
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/CONTRIBUTING.md b/Godeps/_workspace/src/github.com/square/go-jose/CONTRIBUTING.md
new file mode 100644
index 00000000..61b18365
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/CONTRIBUTING.md
@@ -0,0 +1,14 @@
+# Contributing
+
+If you would like to contribute code to go-jose you can do so through GitHub by
+forking the repository and sending a pull request.
+
+When submitting code, please make every effort to follow existing conventions
+and style in order to keep the code as readable as possible. Please also make
+sure all tests pass by running `go test`, and format your code with `go fmt`.
+We also recommend using `golint` and `errcheck`.
+
+Before your code can be accepted into the project you must also sign the
+[Individual Contributor License Agreement][1].
+
+ [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/LICENSE b/Godeps/_workspace/src/github.com/square/go-jose/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/README.md b/Godeps/_workspace/src/github.com/square/go-jose/README.md
new file mode 100644
index 00000000..8602617d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/README.md
@@ -0,0 +1,187 @@
+# Go JOSE 
+
+[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/square/go-jose) [![license](http://img.shields.io/badge/license-apache_2.0-red.svg?style=flat)](https://raw.githubusercontent.com/square/go-jose/master/LICENSE) [![build](https://img.shields.io/travis/square/go-jose.svg?style=flat)](https://travis-ci.org/square/go-jose) [![coverage](https://img.shields.io/coveralls/square/go-jose.svg?style=flat)](https://coveralls.io/r/square/go-jose)
+
+Package jose aims to provide an implementation of the Javascript Object Signing
+and Encryption set of standards. For the moment, it mainly focuses on encryption
+and signing based on the JSON Web Encryption and JSON Web Signature standards.
+
+**Disclaimer**: This library contains encryption software that is subject to
+the U.S. Export Administration Regulations. You may not export, re-export,
+transfer or download this code or any part of it in violation of any United
+States law, directive or regulation. In particular this software may not be
+exported or re-exported in any form or on any media to Iran, North Sudan,
+Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any
+US maintained blocked list.
+
+## Overview
+
+The implementation follows the
+[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516)
+standard (RFC 7516) and
+[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515)
+standard (RFC 7515). Tables of supported algorithms are shown below.
+The library supports both the compact and full serialization formats, and has
+optional support for multiple recipients. It also comes with a small
+command-line utility (`jose-util`) for encrypting/decrypting JWE messages in a
+shell.
+
+### Supported algorithms
+
+See below for a table of supported algorithms. Algorithm identifiers match
+the names in the
+[JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
+standard where possible. The
+[Godoc reference](https://godoc.org/github.com/square/go-jose#pkg-constants)
+has a list of constants.
+
+ Key encryption             | Algorithm identifier(s)
+ :------------------------- | :------------------------------
+ RSA-PKCS#1v1.5             | RSA1_5
+ RSA-OAEP                   | RSA-OAEP, RSA-OAEP-256
+ AES key wrap               | A128KW, A192KW, A256KW
+ AES-GCM key wrap           | A128GCMKW, A192GCMKW, A256GCMKW
+ ECDH-ES + AES key wrap     | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
+ ECDH-ES (direct)           | ECDH-ES<sup>1</sup>
+ Direct encryption          | dir<sup>1</sup>
+
+<sup>1. Not supported in multi-recipient mode</sup>
+
+ Signing / MAC              | Algorithm identifier(s)
+ :------------------------- | :------------------------------
+ RSASSA-PKCS#1v1.5          | RS256, RS384, RS512
+ RSASSA-PSS                 | PS256, PS384, PS512
+ HMAC                       | HS256, HS384, HS512
+ ECDSA                      | ES256, ES384, ES512
+
+ Content encryption         | Algorithm identifier(s)
+ :------------------------- | :------------------------------
+ AES-CBC+HMAC               | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
+ AES-GCM                    | A128GCM, A192GCM, A256GCM 
+
+ Compression                | Algorithm identifiers(s)
+ :------------------------- | -------------------------------
+ DEFLATE (RFC 1951)         | DEF
+
+### Supported key types
+
+See below for a table of supported key types. These are understood by the
+library, and can be passed to corresponding functions such as `NewEncrypter` or
+`NewSigner`. Note that if you are creating a new encrypter or signer with a
+JsonWebKey, the key id of the JsonWebKey (if present) will be added to any
+resulting messages.
+
+ Algorithm(s)               | Corresponding types
+ :------------------------- | -------------------------------
+ RSA                        | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey), *[jose.JsonWebKey](https://godoc.org/github.com/square/go-jose#JsonWebKey)
+ ECDH, ECDSA                | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey), *[jose.JsonWebKey](https://godoc.org/github.com/square/go-jose#JsonWebKey)
+ AES, HMAC                  | []byte, *[jose.JsonWebKey](https://godoc.org/github.com/square/go-jose#JsonWebKey)
+
+## Examples
+
+Encryption/decryption example using RSA:
+
+```Go
+// Generate a public/private key pair to use for this example. The library
+// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
+// that can be used to load keys from PEM/DER-encoded data.
+privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+if err != nil {
+	panic(err)
+}
+
+// Instantiate an encrypter using RSA-OAEP with AES128-GCM. An error would
+// indicate that the selected algorithm(s) are not currently supported.
+publicKey := &privateKey.PublicKey
+encrypter, err := NewEncrypter(RSA_OAEP, A128GCM, publicKey)
+if err != nil {
+	panic(err)
+}
+
+// Encrypt a sample plaintext. Calling the encrypter returns an encrypted
+// JWE object, which can then be serialized for output afterwards. An error
+// would indicate a problem in an underlying cryptographic primitive.
+var plaintext = []byte("Lorem ipsum dolor sit amet")
+object, err := encrypter.Encrypt(plaintext)
+if err != nil {
+	panic(err)
+}
+
+// Serialize the encrypted object using the full serialization format.
+// Alternatively you can also use the compact format here by calling
+// object.CompactSerialize() instead.
+serialized := object.FullSerialize()
+
+// Parse the serialized, encrypted JWE object. An error would indicate that
+// the given input did not represent a valid message.
+object, err = ParseEncrypted(serialized)
+if err != nil {
+	panic(err)
+}
+
+// Now we can decrypt and get back our original plaintext. An error here
+// would indicate the the message failed to decrypt, e.g. because the auth
+// tag was broken or the message was tampered with.
+decrypted, err := object.Decrypt(privateKey)
+if err != nil {
+	panic(err)
+}
+
+fmt.Printf(string(decrypted))
+// output: Lorem ipsum dolor sit amet
+```
+
+Signing/verification example using RSA:
+
+```Go
+// Generate a public/private key pair to use for this example. The library
+// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
+// that can be used to load keys from PEM/DER-encoded data.
+privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+if err != nil {
+	panic(err)
+}
+
+// Instantiate a signer using RSASSA-PSS (SHA512) with the given private key.
+signer, err := NewSigner(PS512, privateKey)
+if err != nil {
+	panic(err)
+}
+
+// Sign a sample payload. Calling the signer returns a protected JWS object,
+// which can then be serialized for output afterwards. An error would
+// indicate a problem in an underlying cryptographic primitive.
+var payload = []byte("Lorem ipsum dolor sit amet")
+object, err := signer.Sign(payload)
+if err != nil {
+	panic(err)
+}
+
+// Serialize the encrypted object using the full serialization format.
+// Alternatively you can also use the compact format here by calling
+// object.CompactSerialize() instead.
+serialized := object.FullSerialize()
+
+// Parse the serialized, protected JWS object. An error would indicate that
+// the given input did not represent a valid message.
+object, err = ParseSigned(serialized)
+if err != nil {
+	panic(err)
+}
+
+// Now we can verify the signature on the payload. An error here would
+// indicate the the message failed to verify, e.g. because the signature was
+// broken or the message was tampered with.
+output, err := object.Verify(&privateKey.PublicKey)
+if err != nil {
+	panic(err)
+}
+
+fmt.Printf(string(output))
+// output: Lorem ipsum dolor sit amet
+```
+
+More examples can be found in the [Godoc
+reference](https://godoc.org/github.com/square/go-jose) for this package. The
+`jose-util` subdirectory also contains a small command-line utility for
+encrypting/decrypting JWE messages which might be useful as an example.
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/asymmetric.go b/Godeps/_workspace/src/github.com/square/go-jose/asymmetric.go
new file mode 100644
index 00000000..274a43cb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/asymmetric.go
@@ -0,0 +1,498 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"crypto"
+	"crypto/aes"
+	"crypto/ecdsa"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha1"
+	"crypto/sha256"
+	"errors"
+	"fmt"
+	"math/big"
+
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/square/go-jose/cipher"
+)
+
+// A generic RSA-based encrypter/verifier
+type rsaEncrypterVerifier struct {
+	publicKey *rsa.PublicKey
+}
+
+// A generic RSA-based decrypter/signer
+type rsaDecrypterSigner struct {
+	privateKey *rsa.PrivateKey
+}
+
+// A generic EC-based encrypter/verifier
+type ecEncrypterVerifier struct {
+	publicKey *ecdsa.PublicKey
+}
+
+// A key generator for ECDH-ES
+type ecKeyGenerator struct {
+	size      int
+	algID     string
+	publicKey *ecdsa.PublicKey
+}
+
+// A generic EC-based decrypter/signer
+type ecDecrypterSigner struct {
+	privateKey *ecdsa.PrivateKey
+}
+
+// newRSARecipient creates recipientKeyInfo based on the given key.
+func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) {
+	// Verify that key management algorithm is supported by this encrypter
+	switch keyAlg {
+	case RSA1_5, RSA_OAEP, RSA_OAEP_256:
+	default:
+		return recipientKeyInfo{}, ErrUnsupportedAlgorithm
+	}
+
+	return recipientKeyInfo{
+		keyAlg: keyAlg,
+		keyEncrypter: &rsaEncrypterVerifier{
+			publicKey: publicKey,
+		},
+	}, nil
+}
+
+// newRSASigner creates a recipientSigInfo based on the given key.
+func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipientSigInfo, error) {
+	// Verify that key management algorithm is supported by this encrypter
+	switch sigAlg {
+	case RS256, RS384, RS512, PS256, PS384, PS512:
+	default:
+		return recipientSigInfo{}, ErrUnsupportedAlgorithm
+	}
+
+	return recipientSigInfo{
+		sigAlg: sigAlg,
+		publicKey: &JsonWebKey{
+			Key: &privateKey.PublicKey,
+		},
+		signer: &rsaDecrypterSigner{
+			privateKey: privateKey,
+		},
+	}, nil
+}
+
+// newECDHRecipient creates recipientKeyInfo based on the given key.
+func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) {
+	// Verify that key management algorithm is supported by this encrypter
+	switch keyAlg {
+	case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
+	default:
+		return recipientKeyInfo{}, ErrUnsupportedAlgorithm
+	}
+
+	return recipientKeyInfo{
+		keyAlg: keyAlg,
+		keyEncrypter: &ecEncrypterVerifier{
+			publicKey: publicKey,
+		},
+	}, nil
+}
+
+// newECDSASigner creates a recipientSigInfo based on the given key.
+func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (recipientSigInfo, error) {
+	// Verify that key management algorithm is supported by this encrypter
+	switch sigAlg {
+	case ES256, ES384, ES512:
+	default:
+		return recipientSigInfo{}, ErrUnsupportedAlgorithm
+	}
+
+	return recipientSigInfo{
+		sigAlg: sigAlg,
+		publicKey: &JsonWebKey{
+			Key: &privateKey.PublicKey,
+		},
+		signer: &ecDecrypterSigner{
+			privateKey: privateKey,
+		},
+	}, nil
+}
+
+// Encrypt the given payload and update the object.
+func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
+	encryptedKey, err := ctx.encrypt(cek, alg)
+	if err != nil {
+		return recipientInfo{}, err
+	}
+
+	return recipientInfo{
+		encryptedKey: encryptedKey,
+		header:       &rawHeader{},
+	}, nil
+}
+
+// Encrypt the given payload. Based on the key encryption algorithm,
+// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
+func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) {
+	switch alg {
+	case RSA1_5:
+		return rsa.EncryptPKCS1v15(randReader, ctx.publicKey, cek)
+	case RSA_OAEP:
+		return rsa.EncryptOAEP(sha1.New(), randReader, ctx.publicKey, cek, []byte{})
+	case RSA_OAEP_256:
+		return rsa.EncryptOAEP(sha256.New(), randReader, ctx.publicKey, cek, []byte{})
+	}
+
+	return nil, ErrUnsupportedAlgorithm
+}
+
+// Decrypt the given payload and return the content encryption key.
+func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
+	return ctx.decrypt(recipient.encryptedKey, KeyAlgorithm(headers.Alg), generator)
+}
+
+// Decrypt the given payload. Based on the key encryption algorithm,
+// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
+func (ctx rsaDecrypterSigner) decrypt(jek []byte, alg KeyAlgorithm, generator keyGenerator) ([]byte, error) {
+	// Note: The random reader on decrypt operations is only used for blinding,
+	// so stubbing is meanlingless (hence the direct use of rand.Reader).
+	switch alg {
+	case RSA1_5:
+		defer func() {
+			// DecryptPKCS1v15SessionKey sometimes panics on an invalid payload
+			// because of an index out of bounds error, which we want to ignore.
+			// This has been fixed in Go 1.3.1 (released 2014/08/13), the recover()
+			// only exists for preventing crashes with unpatched versions.
+			// See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k
+			// See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33
+			_ = recover()
+		}()
+
+		// Perform some input validation.
+		keyBytes := ctx.privateKey.PublicKey.N.BitLen() / 8
+		if keyBytes != len(jek) {
+			// Input size is incorrect, the encrypted payload should always match
+			// the size of the public modulus (e.g. using a 2048 bit key will
+			// produce 256 bytes of output). Reject this since it's invalid input.
+			return nil, ErrCryptoFailure
+		}
+
+		cek, _, err := generator.genKey()
+		if err != nil {
+			return nil, ErrCryptoFailure
+		}
+
+		// When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to
+		// prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing
+		// the Million Message Attack on Cryptographic Message Syntax". We are
+		// therefore deliberatly ignoring errors here.
+		_ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, ctx.privateKey, jek, cek)
+
+		return cek, nil
+	case RSA_OAEP:
+		// Use rand.Reader for RSA blinding
+		return rsa.DecryptOAEP(sha1.New(), rand.Reader, ctx.privateKey, jek, []byte{})
+	case RSA_OAEP_256:
+		// Use rand.Reader for RSA blinding
+		return rsa.DecryptOAEP(sha256.New(), rand.Reader, ctx.privateKey, jek, []byte{})
+	}
+
+	return nil, ErrUnsupportedAlgorithm
+}
+
+// Sign the given payload
+func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
+	var hash crypto.Hash
+
+	switch alg {
+	case RS256, PS256:
+		hash = crypto.SHA256
+	case RS384, PS384:
+		hash = crypto.SHA384
+	case RS512, PS512:
+		hash = crypto.SHA512
+	default:
+		return Signature{}, ErrUnsupportedAlgorithm
+	}
+
+	hasher := hash.New()
+
+	// According to documentation, Write() on hash never fails
+	_, _ = hasher.Write(payload)
+	hashed := hasher.Sum(nil)
+
+	var out []byte
+	var err error
+
+	switch alg {
+	case RS256, RS384, RS512:
+		out, err = rsa.SignPKCS1v15(randReader, ctx.privateKey, hash, hashed)
+	case PS256, PS384, PS512:
+		out, err = rsa.SignPSS(randReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
+			SaltLength: rsa.PSSSaltLengthAuto,
+		})
+	}
+
+	if err != nil {
+		return Signature{}, err
+	}
+
+	return Signature{
+		Signature: out,
+		protected: &rawHeader{},
+	}, nil
+}
+
+// Verify the given payload
+func (ctx rsaEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
+	var hash crypto.Hash
+
+	switch alg {
+	case RS256, PS256:
+		hash = crypto.SHA256
+	case RS384, PS384:
+		hash = crypto.SHA384
+	case RS512, PS512:
+		hash = crypto.SHA512
+	default:
+		return ErrUnsupportedAlgorithm
+	}
+
+	hasher := hash.New()
+
+	// According to documentation, Write() on hash never fails
+	_, _ = hasher.Write(payload)
+	hashed := hasher.Sum(nil)
+
+	switch alg {
+	case RS256, RS384, RS512:
+		return rsa.VerifyPKCS1v15(ctx.publicKey, hash, hashed, signature)
+	case PS256, PS384, PS512:
+		return rsa.VerifyPSS(ctx.publicKey, hash, hashed, signature, nil)
+	}
+
+	return ErrUnsupportedAlgorithm
+}
+
+// Encrypt the given payload and update the object.
+func (ctx ecEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
+	switch alg {
+	case ECDH_ES:
+		// ECDH-ES mode doesn't wrap a key, the shared secret is used directly as the key.
+		return recipientInfo{
+			header: &rawHeader{},
+		}, nil
+	case ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
+	default:
+		return recipientInfo{}, ErrUnsupportedAlgorithm
+	}
+
+	generator := ecKeyGenerator{
+		algID:     string(alg),
+		publicKey: ctx.publicKey,
+	}
+
+	switch alg {
+	case ECDH_ES_A128KW:
+		generator.size = 16
+	case ECDH_ES_A192KW:
+		generator.size = 24
+	case ECDH_ES_A256KW:
+		generator.size = 32
+	}
+
+	kek, header, err := generator.genKey()
+	if err != nil {
+		return recipientInfo{}, err
+	}
+
+	block, err := aes.NewCipher(kek)
+	if err != nil {
+		return recipientInfo{}, err
+	}
+
+	jek, err := josecipher.KeyWrap(block, cek)
+	if err != nil {
+		return recipientInfo{}, err
+	}
+
+	return recipientInfo{
+		encryptedKey: jek,
+		header:       &header,
+	}, nil
+}
+
+// Get key size for EC key generator
+func (ctx ecKeyGenerator) keySize() int {
+	return ctx.size
+}
+
+// Get a content encryption key for ECDH-ES
+func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
+	priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, randReader)
+	if err != nil {
+		return nil, rawHeader{}, err
+	}
+
+	out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size)
+
+	headers := rawHeader{
+		Epk: &JsonWebKey{
+			Key: &priv.PublicKey,
+		},
+	}
+
+	return out, headers, nil
+}
+
+// Decrypt the given payload and return the content encryption key.
+func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
+	if headers.Epk == nil {
+		return nil, errors.New("square/go-jose: missing epk header")
+	}
+
+	publicKey, ok := headers.Epk.Key.(*ecdsa.PublicKey)
+	if publicKey == nil || !ok {
+		return nil, errors.New("square/go-jose: invalid epk header")
+	}
+
+	apuData := headers.Apu.bytes()
+	apvData := headers.Apv.bytes()
+
+	deriveKey := func(algID string, size int) []byte {
+		return josecipher.DeriveECDHES(algID, apuData, apvData, ctx.privateKey, publicKey, size)
+	}
+
+	var keySize int
+
+	switch KeyAlgorithm(headers.Alg) {
+	case ECDH_ES:
+		// ECDH-ES uses direct key agreement, no key unwrapping necessary.
+		return deriveKey(string(headers.Enc), generator.keySize()), nil
+	case ECDH_ES_A128KW:
+		keySize = 16
+	case ECDH_ES_A192KW:
+		keySize = 24
+	case ECDH_ES_A256KW:
+		keySize = 32
+	default:
+		return nil, ErrUnsupportedAlgorithm
+	}
+
+	key := deriveKey(headers.Alg, keySize)
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	return josecipher.KeyUnwrap(block, recipient.encryptedKey)
+}
+
+// Sign the given payload
+func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
+	var expectedBitSize int
+	var hash crypto.Hash
+
+	switch alg {
+	case ES256:
+		expectedBitSize = 256
+		hash = crypto.SHA256
+	case ES384:
+		expectedBitSize = 384
+		hash = crypto.SHA384
+	case ES512:
+		expectedBitSize = 521
+		hash = crypto.SHA512
+	}
+
+	curveBits := ctx.privateKey.Curve.Params().BitSize
+	if expectedBitSize != curveBits {
+		return Signature{}, fmt.Errorf("square/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits)
+	}
+
+	hasher := hash.New()
+
+	// According to documentation, Write() on hash never fails
+	_, _ = hasher.Write(payload)
+	hashed := hasher.Sum(nil)
+
+	r, s, err := ecdsa.Sign(randReader, ctx.privateKey, hashed)
+	if err != nil {
+		return Signature{}, err
+	}
+
+	keyBytes := curveBits / 8
+	if curveBits%8 > 0 {
+		keyBytes += 1
+	}
+
+	// We serialize the outpus (r and s) into big-endian byte arrays and pad
+	// them with zeros on the left to make sure the sizes work out. Both arrays
+	// must be keyBytes long, and the output must be 2*keyBytes long.
+	rBytes := r.Bytes()
+	rBytesPadded := make([]byte, keyBytes)
+	copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
+
+	sBytes := s.Bytes()
+	sBytesPadded := make([]byte, keyBytes)
+	copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
+
+	out := append(rBytesPadded, sBytesPadded...)
+
+	return Signature{
+		Signature: out,
+		protected: &rawHeader{},
+	}, nil
+}
+
+// Verify the given payload
+func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
+	var keySize int
+	var hash crypto.Hash
+
+	switch alg {
+	case ES256:
+		keySize = 32
+		hash = crypto.SHA256
+	case ES384:
+		keySize = 48
+		hash = crypto.SHA384
+	case ES512:
+		keySize = 66
+		hash = crypto.SHA512
+	}
+
+	if len(signature) != 2*keySize {
+		return fmt.Errorf("square/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize)
+	}
+
+	hasher := hash.New()
+
+	// According to documentation, Write() on hash never fails
+	_, _ = hasher.Write(payload)
+	hashed := hasher.Sum(nil)
+
+	r := big.NewInt(0).SetBytes(signature[:keySize])
+	s := big.NewInt(0).SetBytes(signature[keySize:])
+
+	match := ecdsa.Verify(ctx.publicKey, hashed, r, s)
+	if !match {
+		return errors.New("square/go-jose: ecdsa signature failed to verify")
+	}
+
+	return nil
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/asymmetric_test.go b/Godeps/_workspace/src/github.com/square/go-jose/asymmetric_test.go
new file mode 100644
index 00000000..1c8c8b34
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/asymmetric_test.go
@@ -0,0 +1,431 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"bytes"
+	"crypto/rand"
+	"crypto/rsa"
+	"errors"
+	"io"
+	"math/big"
+	"testing"
+)
+
+func TestVectorsRSA(t *testing.T) {
+	// Sources:
+	//   http://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-rsa-cryptography-standard.htm
+	//   ftp://ftp.rsa.com/pub/rsalabs/tmp/pkcs1v15crypt-vectors.txt
+	priv := &rsa.PrivateKey{
+		PublicKey: rsa.PublicKey{
+			N: fromHexInt(`
+				a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8
+				ae4811a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0c
+				bc64a742c6c630f533c8cc72f62ae833c40bf25842e984bb78bd
+				bf97c0107d55bdb662f5c4e0fab9845cb5148ef7392dd3aaff93
+				ae1e6b667bb3d4247616d4f5ba10d4cfd226de88d39f16fb`),
+			E: 65537,
+		},
+		D: fromHexInt(`
+				53339cfdb79fc8466a655c7316aca85c55fd8f6dd898fdaf1195
+				17ef4f52e8fd8e258df93fee180fa0e4ab29693cd83b152a553d
+				4ac4d1812b8b9fa5af0e7f55fe7304df41570926f3311f15c4d6
+				5a732c483116ee3d3d2d0af3549ad9bf7cbfb78ad884f84d5beb
+				04724dc7369b31def37d0cf539e9cfcdd3de653729ead5d1`),
+		Primes: []*big.Int{
+			fromHexInt(`
+				d32737e7267ffe1341b2d5c0d150a81b586fb3132bed2f8d5262
+				864a9cb9f30af38be448598d413a172efb802c21acf1c11c520c
+				2f26a471dcad212eac7ca39d`),
+			fromHexInt(`
+				cc8853d1d54da630fac004f471f281c7b8982d8224a490edbeb3
+				3d3e3d5cc93c4765703d1dd791642f1f116a0dd852be2419b2af
+				72bfe9a030e860b0288b5d77`),
+		},
+	}
+
+	input := fromHexBytes(
+		"6628194e12073db03ba94cda9ef9532397d50dba79b987004afefe34")
+
+	expectedPKCS := fromHexBytes(`
+		50b4c14136bd198c2f3c3ed243fce036e168d56517984a263cd66492b808
+		04f169d210f2b9bdfb48b12f9ea05009c77da257cc600ccefe3a6283789d
+		8ea0e607ac58e2690ec4ebc10146e8cbaa5ed4d5cce6fe7b0ff9efc1eabb
+		564dbf498285f449ee61dd7b42ee5b5892cb90601f30cda07bf26489310b
+		cd23b528ceab3c31`)
+
+	expectedOAEP := fromHexBytes(`
+		354fe67b4a126d5d35fe36c777791a3f7ba13def484e2d3908aff722fad4
+		68fb21696de95d0be911c2d3174f8afcc201035f7b6d8e69402de5451618
+		c21a535fa9d7bfc5b8dd9fc243f8cf927db31322d6e881eaa91a996170e6
+		57a05a266426d98c88003f8477c1227094a0d9fa1e8c4024309ce1ecccb5
+		210035d47ac72e8a`)
+
+	// Mock random reader
+	randReader = bytes.NewReader(fromHexBytes(`
+		017341ae3875d5f87101f8cc4fa9b9bc156bb04628fccdb2f4f11e905bd3
+		a155d376f593bd7304210874eba08a5e22bcccb4c9d3882a93a54db022f5
+		03d16338b6b7ce16dc7f4bbf9a96b59772d6606e9747c7649bf9e083db98
+		1884a954ab3c6f18b776ea21069d69776a33e96bad48e1dda0a5ef`))
+	defer resetRandReader()
+
+	// RSA-PKCS1v1.5 encrypt
+	enc := new(rsaEncrypterVerifier)
+	enc.publicKey = &priv.PublicKey
+	encryptedPKCS, err := enc.encrypt(input, RSA1_5)
+	if err != nil {
+		t.Error("Encryption failed:", err)
+		return
+	}
+
+	if bytes.Compare(encryptedPKCS, expectedPKCS) != 0 {
+		t.Error("Output does not match expected value (PKCS1v1.5)")
+	}
+
+	// RSA-OAEP encrypt
+	encryptedOAEP, err := enc.encrypt(input, RSA_OAEP)
+	if err != nil {
+		t.Error("Encryption failed:", err)
+		return
+	}
+
+	if bytes.Compare(encryptedOAEP, expectedOAEP) != 0 {
+		t.Error("Output does not match expected value (OAEP)")
+	}
+
+	// Need fake cipher for PKCS1v1.5 decrypt
+	resetRandReader()
+	aes := newAESGCM(len(input))
+
+	keygen := randomKeyGenerator{
+		size: aes.keySize(),
+	}
+
+	// RSA-PKCS1v1.5 decrypt
+	dec := new(rsaDecrypterSigner)
+	dec.privateKey = priv
+	decryptedPKCS, err := dec.decrypt(encryptedPKCS, RSA1_5, keygen)
+	if err != nil {
+		t.Error("Decryption failed:", err)
+		return
+	}
+
+	if bytes.Compare(input, decryptedPKCS) != 0 {
+		t.Error("Output does not match expected value (PKCS1v1.5)")
+	}
+
+	// RSA-OAEP decrypt
+	decryptedOAEP, err := dec.decrypt(encryptedOAEP, RSA_OAEP, keygen)
+	if err != nil {
+		t.Error("decryption failed:", err)
+		return
+	}
+
+	if bytes.Compare(input, decryptedOAEP) != 0 {
+		t.Error("output does not match expected value (OAEP)")
+	}
+}
+
+func TestInvalidAlgorithmsRSA(t *testing.T) {
+	_, err := newRSARecipient("XYZ", nil)
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should return error on invalid algorithm")
+	}
+
+	_, err = newRSASigner("XYZ", nil)
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should return error on invalid algorithm")
+	}
+
+	enc := new(rsaEncrypterVerifier)
+	enc.publicKey = &rsaTestKey.PublicKey
+	_, err = enc.encryptKey([]byte{}, "XYZ")
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should return error on invalid algorithm")
+	}
+
+	err = enc.verifyPayload([]byte{}, []byte{}, "XYZ")
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should return error on invalid algorithm")
+	}
+
+	dec := new(rsaDecrypterSigner)
+	dec.privateKey = rsaTestKey
+	_, err = dec.decrypt(make([]byte, 256), "XYZ", randomKeyGenerator{size: 16})
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should return error on invalid algorithm")
+	}
+
+	_, err = dec.signPayload([]byte{}, "XYZ")
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should return error on invalid algorithm")
+	}
+}
+
+type failingKeyGenerator struct{}
+
+func (ctx failingKeyGenerator) keySize() int {
+	return 0
+}
+
+func (ctx failingKeyGenerator) genKey() ([]byte, rawHeader, error) {
+	return nil, rawHeader{}, errors.New("failed to generate key")
+}
+
+func TestPKCSKeyGeneratorFailure(t *testing.T) {
+	dec := new(rsaDecrypterSigner)
+	dec.privateKey = rsaTestKey
+	generator := failingKeyGenerator{}
+	_, err := dec.decrypt(make([]byte, 256), RSA1_5, generator)
+	if err != ErrCryptoFailure {
+		t.Error("should return error on invalid algorithm")
+	}
+}
+
+func TestInvalidAlgorithmsEC(t *testing.T) {
+	_, err := newECDHRecipient("XYZ", nil)
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should return error on invalid algorithm")
+	}
+
+	_, err = newECDSASigner("XYZ", nil)
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should return error on invalid algorithm")
+	}
+
+	enc := new(ecEncrypterVerifier)
+	enc.publicKey = &ecTestKey256.PublicKey
+	_, err = enc.encryptKey([]byte{}, "XYZ")
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should return error on invalid algorithm")
+	}
+}
+
+func TestInvalidECKeyGen(t *testing.T) {
+	gen := ecKeyGenerator{
+		size:      16,
+		algID:     "A128GCM",
+		publicKey: &ecTestKey256.PublicKey,
+	}
+
+	if gen.keySize() != 16 {
+		t.Error("ec key generator reported incorrect key size")
+	}
+
+	_, _, err := gen.genKey()
+	if err != nil {
+		t.Error("ec key generator failed to generate key", err)
+	}
+}
+
+func TestInvalidECDecrypt(t *testing.T) {
+	dec := ecDecrypterSigner{
+		privateKey: ecTestKey256,
+	}
+
+	generator := randomKeyGenerator{size: 16}
+
+	// Missing epk header
+	headers := rawHeader{
+		Alg: string(ECDH_ES),
+	}
+
+	_, err := dec.decryptKey(headers, nil, generator)
+	if err == nil {
+		t.Error("ec decrypter accepted object with missing epk header")
+	}
+
+	// Invalid epk header
+	headers.Epk = &JsonWebKey{}
+
+	_, err = dec.decryptKey(headers, nil, generator)
+	if err == nil {
+		t.Error("ec decrypter accepted object with invalid epk header")
+	}
+}
+
+func TestDecryptWithIncorrectSize(t *testing.T) {
+	priv, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	dec := new(rsaDecrypterSigner)
+	dec.privateKey = priv
+	aes := newAESGCM(16)
+
+	keygen := randomKeyGenerator{
+		size: aes.keySize(),
+	}
+
+	payload := make([]byte, 254)
+	_, err = dec.decrypt(payload, RSA1_5, keygen)
+	if err == nil {
+		t.Error("Invalid payload size should return error")
+	}
+
+	payload = make([]byte, 257)
+	_, err = dec.decrypt(payload, RSA1_5, keygen)
+	if err == nil {
+		t.Error("Invalid payload size should return error")
+	}
+}
+
+func TestPKCSDecryptNeverFails(t *testing.T) {
+	// We don't want RSA-PKCS1 v1.5 decryption to ever fail, in order to prevent
+	// side-channel timing attacks (Bleichenbacher attack in particular).
+	priv, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	dec := new(rsaDecrypterSigner)
+	dec.privateKey = priv
+	aes := newAESGCM(16)
+
+	keygen := randomKeyGenerator{
+		size: aes.keySize(),
+	}
+
+	for i := 1; i < 50; i++ {
+		payload := make([]byte, 256)
+		_, err := io.ReadFull(rand.Reader, payload)
+		if err != nil {
+			t.Error("Unable to get random data:", err)
+			return
+		}
+		_, err = dec.decrypt(payload, RSA1_5, keygen)
+		if err != nil {
+			t.Error("PKCS1v1.5 decrypt should never fail:", err)
+			return
+		}
+	}
+}
+
+func BenchmarkPKCSDecryptWithValidPayloads(b *testing.B) {
+	priv, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		panic(err)
+	}
+
+	enc := new(rsaEncrypterVerifier)
+	enc.publicKey = &priv.PublicKey
+	dec := new(rsaDecrypterSigner)
+	dec.privateKey = priv
+	aes := newAESGCM(32)
+
+	b.StopTimer()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		plaintext := make([]byte, 32)
+		_, err = io.ReadFull(rand.Reader, plaintext)
+		if err != nil {
+			panic(err)
+		}
+
+		ciphertext, err := enc.encrypt(plaintext, RSA1_5)
+		if err != nil {
+			panic(err)
+		}
+
+		keygen := randomKeyGenerator{
+			size: aes.keySize(),
+		}
+
+		b.StartTimer()
+		_, err = dec.decrypt(ciphertext, RSA1_5, keygen)
+		b.StopTimer()
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
+func BenchmarkPKCSDecryptWithInvalidPayloads(b *testing.B) {
+	priv, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		panic(err)
+	}
+
+	enc := new(rsaEncrypterVerifier)
+	enc.publicKey = &priv.PublicKey
+	dec := new(rsaDecrypterSigner)
+	dec.privateKey = priv
+	aes := newAESGCM(16)
+
+	keygen := randomKeyGenerator{
+		size: aes.keySize(),
+	}
+
+	b.StopTimer()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		plaintext := make([]byte, 16)
+		_, err = io.ReadFull(rand.Reader, plaintext)
+		if err != nil {
+			panic(err)
+		}
+
+		ciphertext, err := enc.encrypt(plaintext, RSA1_5)
+		if err != nil {
+			panic(err)
+		}
+
+		// Do some simple scrambling
+		ciphertext[128] ^= 0xFF
+
+		b.StartTimer()
+		_, err = dec.decrypt(ciphertext, RSA1_5, keygen)
+		b.StopTimer()
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
+func TestInvalidEllipticCurve(t *testing.T) {
+	signer256 := ecDecrypterSigner{privateKey: ecTestKey256}
+	signer384 := ecDecrypterSigner{privateKey: ecTestKey384}
+	signer521 := ecDecrypterSigner{privateKey: ecTestKey521}
+
+	_, err := signer256.signPayload([]byte{}, ES384)
+	if err == nil {
+		t.Error("should not generate ES384 signature with P-256 key")
+	}
+	_, err = signer256.signPayload([]byte{}, ES512)
+	if err == nil {
+		t.Error("should not generate ES512 signature with P-256 key")
+	}
+	_, err = signer384.signPayload([]byte{}, ES256)
+	if err == nil {
+		t.Error("should not generate ES256 signature with P-384 key")
+	}
+	_, err = signer384.signPayload([]byte{}, ES512)
+	if err == nil {
+		t.Error("should not generate ES512 signature with P-384 key")
+	}
+	_, err = signer521.signPayload([]byte{}, ES256)
+	if err == nil {
+		t.Error("should not generate ES256 signature with P-521 key")
+	}
+	_, err = signer521.signPayload([]byte{}, ES384)
+	if err == nil {
+		t.Error("should not generate ES384 signature with P-521 key")
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/cipher/cbc_hmac.go b/Godeps/_workspace/src/github.com/square/go-jose/cipher/cbc_hmac.go
new file mode 100644
index 00000000..a5c35834
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/cipher/cbc_hmac.go
@@ -0,0 +1,196 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+	"bytes"
+	"crypto/cipher"
+	"crypto/hmac"
+	"crypto/sha256"
+	"crypto/sha512"
+	"crypto/subtle"
+	"encoding/binary"
+	"errors"
+	"hash"
+)
+
+const (
+	nonceBytes = 16
+)
+
+// NewCBCHMAC instantiates a new AEAD based on CBC+HMAC.
+func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (cipher.AEAD, error) {
+	keySize := len(key) / 2
+	integrityKey := key[:keySize]
+	encryptionKey := key[keySize:]
+
+	blockCipher, err := newBlockCipher(encryptionKey)
+	if err != nil {
+		return nil, err
+	}
+
+	var hash func() hash.Hash
+	switch keySize {
+	case 16:
+		hash = sha256.New
+	case 24:
+		hash = sha512.New384
+	case 32:
+		hash = sha512.New
+	}
+
+	return &cbcAEAD{
+		hash:         hash,
+		blockCipher:  blockCipher,
+		authtagBytes: keySize,
+		integrityKey: integrityKey,
+	}, nil
+}
+
+// An AEAD based on CBC+HMAC
+type cbcAEAD struct {
+	hash         func() hash.Hash
+	authtagBytes int
+	integrityKey []byte
+	blockCipher  cipher.Block
+}
+
+func (ctx *cbcAEAD) NonceSize() int {
+	return nonceBytes
+}
+
+func (ctx *cbcAEAD) Overhead() int {
+	// Maximum overhead is block size (for padding) plus auth tag length, where
+	// the length of the auth tag is equivalent to the key size.
+	return ctx.blockCipher.BlockSize() + ctx.authtagBytes
+}
+
+// Seal encrypts and authenticates the plaintext.
+func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
+	// Output buffer -- must take care not to mangle plaintext input.
+	ciphertext := make([]byte, len(plaintext)+ctx.Overhead())[:len(plaintext)]
+	copy(ciphertext, plaintext)
+	ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize())
+
+	cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce)
+
+	cbc.CryptBlocks(ciphertext, ciphertext)
+	authtag := ctx.computeAuthTag(data, nonce, ciphertext)
+
+	ret, out := resize(dst, len(dst)+len(ciphertext)+len(authtag))
+	copy(out, ciphertext)
+	copy(out[len(ciphertext):], authtag)
+
+	return ret
+}
+
+// Open decrypts and authenticates the ciphertext.
+func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
+	if len(ciphertext) < ctx.authtagBytes {
+		return nil, errors.New("square/go-jose: invalid ciphertext (too short)")
+	}
+
+	offset := len(ciphertext) - ctx.authtagBytes
+	expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset])
+	match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:])
+	if match != 1 {
+		return nil, errors.New("square/go-jose: invalid ciphertext (auth tag mismatch)")
+	}
+
+	cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
+
+	// Make copy of ciphertext buffer, don't want to modify in place
+	buffer := append([]byte{}, []byte(ciphertext[:offset])...)
+
+	if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
+		return nil, errors.New("square/go-jose: invalid ciphertext (invalid length)")
+	}
+
+	cbc.CryptBlocks(buffer, buffer)
+
+	// Remove padding
+	plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize())
+	if err != nil {
+		return nil, err
+	}
+
+	ret, out := resize(dst, len(dst)+len(plaintext))
+	copy(out, plaintext)
+
+	return ret, nil
+}
+
+// Compute an authentication tag
+func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
+	buffer := make([]byte, len(aad)+len(nonce)+len(ciphertext)+8)
+	n := 0
+	n += copy(buffer, aad)
+	n += copy(buffer[n:], nonce)
+	n += copy(buffer[n:], ciphertext)
+	binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad)*8))
+
+	// According to documentation, Write() on hash.Hash never fails.
+	hmac := hmac.New(ctx.hash, ctx.integrityKey)
+	_, _ = hmac.Write(buffer)
+
+	return hmac.Sum(nil)[:ctx.authtagBytes]
+}
+
+// resize ensures the the given slice has a capacity of at least n bytes.
+// If the capacity of the slice is less than n, a new slice is allocated
+// and the existing data will be copied.
+func resize(in []byte, n int) (head, tail []byte) {
+	if cap(in) >= n {
+		head = in[:n]
+	} else {
+		head = make([]byte, n)
+		copy(head, in)
+	}
+
+	tail = head[len(in):]
+	return
+}
+
+// Apply padding
+func padBuffer(buffer []byte, blockSize int) []byte {
+	missing := blockSize - (len(buffer) % blockSize)
+	ret, out := resize(buffer, len(buffer)+missing)
+	padding := bytes.Repeat([]byte{byte(missing)}, missing)
+	copy(out, padding)
+	return ret
+}
+
+// Remove padding
+func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
+	if len(buffer)%blockSize != 0 {
+		return nil, errors.New("square/go-jose: invalid padding")
+	}
+
+	last := buffer[len(buffer)-1]
+	count := int(last)
+
+	if count == 0 || count > blockSize || count > len(buffer) {
+		return nil, errors.New("square/go-jose: invalid padding")
+	}
+
+	padding := bytes.Repeat([]byte{last}, count)
+	if !bytes.HasSuffix(buffer, padding) {
+		return nil, errors.New("square/go-jose: invalid padding")
+	}
+
+	return buffer[:len(buffer)-count], nil
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/cipher/cbc_hmac_test.go b/Godeps/_workspace/src/github.com/square/go-jose/cipher/cbc_hmac_test.go
new file mode 100644
index 00000000..c230271b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/cipher/cbc_hmac_test.go
@@ -0,0 +1,498 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"io"
+	"strings"
+	"testing"
+)
+
+func TestInvalidInputs(t *testing.T) {
+	key := []byte{
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+	}
+
+	nonce := []byte{
+		92, 80, 104, 49, 133, 25, 161, 215, 173, 101, 219, 211, 136, 91, 210, 145}
+
+	aead, _ := NewCBCHMAC(key, aes.NewCipher)
+	ciphertext := aead.Seal(nil, nonce, []byte("plaintext"), []byte("aad"))
+
+	// Changed AAD, must fail
+	_, err := aead.Open(nil, nonce, ciphertext, []byte("INVALID"))
+	if err == nil {
+		t.Error("must detect invalid aad")
+	}
+
+	// Empty ciphertext, must fail
+	_, err = aead.Open(nil, nonce, []byte{}, []byte("aad"))
+	if err == nil {
+		t.Error("must detect invalid/empty ciphertext")
+	}
+
+	// Corrupt ciphertext, must fail
+	corrupt := make([]byte, len(ciphertext))
+	copy(corrupt, ciphertext)
+	corrupt[0] ^= 0xFF
+
+	_, err = aead.Open(nil, nonce, corrupt, []byte("aad"))
+	if err == nil {
+		t.Error("must detect corrupt ciphertext")
+	}
+
+	// Corrupt authtag, must fail
+	copy(corrupt, ciphertext)
+	corrupt[len(ciphertext)-1] ^= 0xFF
+
+	_, err = aead.Open(nil, nonce, corrupt, []byte("aad"))
+	if err == nil {
+		t.Error("must detect corrupt authtag")
+	}
+
+	// Truncated data, must fail
+	_, err = aead.Open(nil, nonce, ciphertext[:10], []byte("aad"))
+	if err == nil {
+		t.Error("must detect corrupt authtag")
+	}
+}
+
+func TestVectorsAESCBC128(t *testing.T) {
+	// Source: http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-29#appendix-A.2
+	plaintext := []byte{
+		76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32,
+		112, 114, 111, 115, 112, 101, 114, 46}
+
+	aad := []byte{
+		101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69,
+		120, 88, 122, 85, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105,
+		74, 66, 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85,
+		50, 73, 110, 48}
+
+	expectedCiphertext := []byte{
+		40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6,
+		75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143,
+		112, 56, 102}
+
+	expectedAuthtag := []byte{
+		246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100,
+		191}
+
+	key := []byte{
+		4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206,
+		107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207}
+
+	nonce := []byte{
+		3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101}
+
+	enc, err := NewCBCHMAC(key, aes.NewCipher)
+	out := enc.Seal(nil, nonce, plaintext, aad)
+	if err != nil {
+		t.Error("Unable to encrypt:", err)
+		return
+	}
+
+	if bytes.Compare(out[:len(out)-16], expectedCiphertext) != 0 {
+		t.Error("Ciphertext did not match")
+	}
+	if bytes.Compare(out[len(out)-16:], expectedAuthtag) != 0 {
+		t.Error("Auth tag did not match")
+	}
+}
+
+func TestVectorsAESCBC256(t *testing.T) {
+	// Source: https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5.4
+	plaintext := []byte{
+		0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20,
+		0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75,
+		0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65,
+		0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62,
+		0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69,
+		0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66,
+		0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f,
+		0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65}
+
+	aad := []byte{
+		0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63,
+		0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20,
+		0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73}
+
+	expectedCiphertext := []byte{
+		0x4a, 0xff, 0xaa, 0xad, 0xb7, 0x8c, 0x31, 0xc5, 0xda, 0x4b, 0x1b, 0x59, 0x0d, 0x10, 0xff, 0xbd,
+		0x3d, 0xd8, 0xd5, 0xd3, 0x02, 0x42, 0x35, 0x26, 0x91, 0x2d, 0xa0, 0x37, 0xec, 0xbc, 0xc7, 0xbd,
+		0x82, 0x2c, 0x30, 0x1d, 0xd6, 0x7c, 0x37, 0x3b, 0xcc, 0xb5, 0x84, 0xad, 0x3e, 0x92, 0x79, 0xc2,
+		0xe6, 0xd1, 0x2a, 0x13, 0x74, 0xb7, 0x7f, 0x07, 0x75, 0x53, 0xdf, 0x82, 0x94, 0x10, 0x44, 0x6b,
+		0x36, 0xeb, 0xd9, 0x70, 0x66, 0x29, 0x6a, 0xe6, 0x42, 0x7e, 0xa7, 0x5c, 0x2e, 0x08, 0x46, 0xa1,
+		0x1a, 0x09, 0xcc, 0xf5, 0x37, 0x0d, 0xc8, 0x0b, 0xfe, 0xcb, 0xad, 0x28, 0xc7, 0x3f, 0x09, 0xb3,
+		0xa3, 0xb7, 0x5e, 0x66, 0x2a, 0x25, 0x94, 0x41, 0x0a, 0xe4, 0x96, 0xb2, 0xe2, 0xe6, 0x60, 0x9e,
+		0x31, 0xe6, 0xe0, 0x2c, 0xc8, 0x37, 0xf0, 0x53, 0xd2, 0x1f, 0x37, 0xff, 0x4f, 0x51, 0x95, 0x0b,
+		0xbe, 0x26, 0x38, 0xd0, 0x9d, 0xd7, 0xa4, 0x93, 0x09, 0x30, 0x80, 0x6d, 0x07, 0x03, 0xb1, 0xf6}
+
+	expectedAuthtag := []byte{
+		0x4d, 0xd3, 0xb4, 0xc0, 0x88, 0xa7, 0xf4, 0x5c, 0x21, 0x68, 0x39, 0x64, 0x5b, 0x20, 0x12, 0xbf,
+		0x2e, 0x62, 0x69, 0xa8, 0xc5, 0x6a, 0x81, 0x6d, 0xbc, 0x1b, 0x26, 0x77, 0x61, 0x95, 0x5b, 0xc5}
+
+	key := []byte{
+		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+		0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+		0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+		0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f}
+
+	nonce := []byte{
+		0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04}
+
+	enc, err := NewCBCHMAC(key, aes.NewCipher)
+	out := enc.Seal(nil, nonce, plaintext, aad)
+	if err != nil {
+		t.Error("Unable to encrypt:", err)
+		return
+	}
+
+	if bytes.Compare(out[:len(out)-32], expectedCiphertext) != 0 {
+		t.Error("Ciphertext did not match, got", out[:len(out)-32], "wanted", expectedCiphertext)
+	}
+	if bytes.Compare(out[len(out)-32:], expectedAuthtag) != 0 {
+		t.Error("Auth tag did not match, got", out[len(out)-32:], "wanted", expectedAuthtag)
+	}
+}
+
+func TestAESCBCRoundtrip(t *testing.T) {
+	key128 := []byte{
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+
+	key192 := []byte{
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+		0, 1, 2, 3, 4, 5, 6, 7,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+		0, 1, 2, 3, 4, 5, 6, 7}
+
+	key256 := []byte{
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+
+	nonce := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+
+	RunRoundtrip(t, key128, nonce)
+	RunRoundtrip(t, key192, nonce)
+	RunRoundtrip(t, key256, nonce)
+}
+
+func RunRoundtrip(t *testing.T, key, nonce []byte) {
+	aead, err := NewCBCHMAC(key, aes.NewCipher)
+	if err != nil {
+		panic(err)
+	}
+
+	if aead.NonceSize() != len(nonce) {
+		panic("invalid nonce")
+	}
+
+	// Test pre-existing data in dst buffer
+	dst := []byte{15, 15, 15, 15}
+	plaintext := []byte{0, 0, 0, 0}
+	aad := []byte{4, 3, 2, 1}
+
+	result := aead.Seal(dst, nonce, plaintext, aad)
+	if bytes.Compare(dst, result[:4]) != 0 {
+		t.Error("Existing data in dst not preserved")
+	}
+
+	// Test pre-existing (empty) dst buffer with sufficient capacity
+	dst = make([]byte, 256)[:0]
+	result, err = aead.Open(dst, nonce, result[4:], aad)
+	if err != nil {
+		panic(err)
+	}
+
+	if bytes.Compare(result, plaintext) != 0 {
+		t.Error("Plaintext does not match output")
+	}
+}
+
+func TestAESCBCOverhead(t *testing.T) {
+	aead, err := NewCBCHMAC(make([]byte, 32), aes.NewCipher)
+	if err != nil {
+		panic(err)
+	}
+
+	if aead.Overhead() != 32 {
+		t.Error("CBC-HMAC reports incorrect overhead value")
+	}
+}
+
+func TestPadding(t *testing.T) {
+	for i := 0; i < 256; i++ {
+		slice := make([]byte, i)
+		padded := padBuffer(slice, 16)
+		if len(padded)%16 != 0 {
+			t.Error("failed to pad slice properly", i)
+			return
+		}
+		unpadded, err := unpadBuffer(padded, 16)
+		if err != nil || len(unpadded) != i {
+			t.Error("failed to unpad slice properly", i)
+			return
+		}
+	}
+}
+
+func TestInvalidKey(t *testing.T) {
+	key := make([]byte, 30)
+	_, err := NewCBCHMAC(key, aes.NewCipher)
+	if err == nil {
+		t.Error("should not be able to instantiate CBC-HMAC with invalid key")
+	}
+}
+
+func TestTruncatedCiphertext(t *testing.T) {
+	key := make([]byte, 32)
+	nonce := make([]byte, 16)
+	data := make([]byte, 32)
+
+	io.ReadFull(rand.Reader, key)
+	io.ReadFull(rand.Reader, nonce)
+
+	aead, err := NewCBCHMAC(key, aes.NewCipher)
+	if err != nil {
+		panic(err)
+	}
+
+	ctx := aead.(*cbcAEAD)
+	ct := aead.Seal(nil, nonce, data, nil)
+
+	// Truncated ciphertext, but with correct auth tag
+	truncated, tail := resize(ct[:len(ct)-ctx.authtagBytes-2], len(ct)-2)
+	copy(tail, ctx.computeAuthTag(nil, nonce, truncated[:len(truncated)-ctx.authtagBytes]))
+
+	// Open should fail
+	_, err = aead.Open(nil, nonce, truncated, nil)
+	if err == nil {
+		t.Error("open on truncated ciphertext should fail")
+	}
+}
+
+func TestInvalidPaddingOpen(t *testing.T) {
+	key := make([]byte, 32)
+	nonce := make([]byte, 16)
+
+	// Plaintext with invalid padding
+	plaintext := padBuffer(make([]byte, 28), aes.BlockSize)
+	plaintext[len(plaintext)-1] = 0xFF
+
+	io.ReadFull(rand.Reader, key)
+	io.ReadFull(rand.Reader, nonce)
+
+	block, _ := aes.NewCipher(key)
+	cbc := cipher.NewCBCEncrypter(block, nonce)
+	buffer := append([]byte{}, plaintext...)
+	cbc.CryptBlocks(buffer, buffer)
+
+	aead, _ := NewCBCHMAC(key, aes.NewCipher)
+	ctx := aead.(*cbcAEAD)
+
+	// Mutated ciphertext, but with correct auth tag
+	size := len(buffer)
+	ciphertext, tail := resize(buffer, size+(len(key)/2))
+	copy(tail, ctx.computeAuthTag(nil, nonce, ciphertext[:size]))
+
+	// Open should fail (b/c of invalid padding, even though tag matches)
+	_, err := aead.Open(nil, nonce, ciphertext, nil)
+	if err == nil || !strings.Contains(err.Error(), "invalid padding") {
+		t.Error("no or unexpected error on open with invalid padding:", err)
+	}
+}
+
+func TestInvalidPadding(t *testing.T) {
+	for i := 0; i < 256; i++ {
+		slice := make([]byte, i)
+		padded := padBuffer(slice, 16)
+		if len(padded)%16 != 0 {
+			t.Error("failed to pad slice properly", i)
+			return
+		}
+
+		paddingBytes := 16 - (i % 16)
+
+		// Mutate padding for testing
+		for j := 1; j <= paddingBytes; j++ {
+			mutated := make([]byte, len(padded))
+			copy(mutated, padded)
+			mutated[len(mutated)-j] ^= 0xFF
+
+			_, err := unpadBuffer(mutated, 16)
+			if err == nil {
+				t.Error("unpad on invalid padding should fail", i)
+				return
+			}
+		}
+
+		// Test truncated padding
+		_, err := unpadBuffer(padded[:len(padded)-1], 16)
+		if err == nil {
+			t.Error("unpad on truncated padding should fail", i)
+			return
+		}
+	}
+}
+
+func TestZeroLengthPadding(t *testing.T) {
+	data := make([]byte, 16)
+	data, err := unpadBuffer(data, 16)
+	if err == nil {
+		t.Error("padding with 0x00 should never be valid")
+	}
+}
+
+func benchEncryptCBCHMAC(b *testing.B, keySize, chunkSize int) {
+	key := make([]byte, keySize*2)
+	nonce := make([]byte, 16)
+
+	io.ReadFull(rand.Reader, key)
+	io.ReadFull(rand.Reader, nonce)
+
+	chunk := make([]byte, chunkSize)
+
+	aead, err := NewCBCHMAC(key, aes.NewCipher)
+	if err != nil {
+		panic(err)
+	}
+
+	b.SetBytes(int64(chunkSize))
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		aead.Seal(nil, nonce, chunk, nil)
+	}
+}
+
+func benchDecryptCBCHMAC(b *testing.B, keySize, chunkSize int) {
+	key := make([]byte, keySize*2)
+	nonce := make([]byte, 16)
+
+	io.ReadFull(rand.Reader, key)
+	io.ReadFull(rand.Reader, nonce)
+
+	chunk := make([]byte, chunkSize)
+
+	aead, err := NewCBCHMAC(key, aes.NewCipher)
+	if err != nil {
+		panic(err)
+	}
+
+	out := aead.Seal(nil, nonce, chunk, nil)
+
+	b.SetBytes(int64(chunkSize))
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		aead.Open(nil, nonce, out, nil)
+	}
+}
+
+func BenchmarkEncryptAES128_CBCHMAC_1k(b *testing.B) {
+	benchEncryptCBCHMAC(b, 16, 1024)
+}
+
+func BenchmarkEncryptAES128_CBCHMAC_64k(b *testing.B) {
+	benchEncryptCBCHMAC(b, 16, 65536)
+}
+
+func BenchmarkEncryptAES128_CBCHMAC_1MB(b *testing.B) {
+	benchEncryptCBCHMAC(b, 16, 1048576)
+}
+
+func BenchmarkEncryptAES128_CBCHMAC_64MB(b *testing.B) {
+	benchEncryptCBCHMAC(b, 16, 67108864)
+}
+
+func BenchmarkDecryptAES128_CBCHMAC_1k(b *testing.B) {
+	benchDecryptCBCHMAC(b, 16, 1024)
+}
+
+func BenchmarkDecryptAES128_CBCHMAC_64k(b *testing.B) {
+	benchDecryptCBCHMAC(b, 16, 65536)
+}
+
+func BenchmarkDecryptAES128_CBCHMAC_1MB(b *testing.B) {
+	benchDecryptCBCHMAC(b, 16, 1048576)
+}
+
+func BenchmarkDecryptAES128_CBCHMAC_64MB(b *testing.B) {
+	benchDecryptCBCHMAC(b, 16, 67108864)
+}
+
+func BenchmarkEncryptAES192_CBCHMAC_64k(b *testing.B) {
+	benchEncryptCBCHMAC(b, 24, 65536)
+}
+
+func BenchmarkEncryptAES192_CBCHMAC_1MB(b *testing.B) {
+	benchEncryptCBCHMAC(b, 24, 1048576)
+}
+
+func BenchmarkEncryptAES192_CBCHMAC_64MB(b *testing.B) {
+	benchEncryptCBCHMAC(b, 24, 67108864)
+}
+
+func BenchmarkDecryptAES192_CBCHMAC_1k(b *testing.B) {
+	benchDecryptCBCHMAC(b, 24, 1024)
+}
+
+func BenchmarkDecryptAES192_CBCHMAC_64k(b *testing.B) {
+	benchDecryptCBCHMAC(b, 24, 65536)
+}
+
+func BenchmarkDecryptAES192_CBCHMAC_1MB(b *testing.B) {
+	benchDecryptCBCHMAC(b, 24, 1048576)
+}
+
+func BenchmarkDecryptAES192_CBCHMAC_64MB(b *testing.B) {
+	benchDecryptCBCHMAC(b, 24, 67108864)
+}
+
+func BenchmarkEncryptAES256_CBCHMAC_64k(b *testing.B) {
+	benchEncryptCBCHMAC(b, 32, 65536)
+}
+
+func BenchmarkEncryptAES256_CBCHMAC_1MB(b *testing.B) {
+	benchEncryptCBCHMAC(b, 32, 1048576)
+}
+
+func BenchmarkEncryptAES256_CBCHMAC_64MB(b *testing.B) {
+	benchEncryptCBCHMAC(b, 32, 67108864)
+}
+
+func BenchmarkDecryptAES256_CBCHMAC_1k(b *testing.B) {
+	benchDecryptCBCHMAC(b, 32, 1032)
+}
+
+func BenchmarkDecryptAES256_CBCHMAC_64k(b *testing.B) {
+	benchDecryptCBCHMAC(b, 32, 65536)
+}
+
+func BenchmarkDecryptAES256_CBCHMAC_1MB(b *testing.B) {
+	benchDecryptCBCHMAC(b, 32, 1048576)
+}
+
+func BenchmarkDecryptAES256_CBCHMAC_64MB(b *testing.B) {
+	benchDecryptCBCHMAC(b, 32, 67108864)
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/cipher/concat_kdf.go b/Godeps/_workspace/src/github.com/square/go-jose/cipher/concat_kdf.go
new file mode 100644
index 00000000..cbb5f7b8
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/cipher/concat_kdf.go
@@ -0,0 +1,75 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+	"crypto"
+	"encoding/binary"
+	"hash"
+	"io"
+)
+
+type concatKDF struct {
+	z, info []byte
+	i       uint32
+	cache   []byte
+	hasher  hash.Hash
+}
+
+// NewConcatKDF builds a KDF reader based on the given inputs.
+func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader {
+	buffer := make([]byte, len(algID)+len(ptyUInfo)+len(ptyVInfo)+len(supPubInfo)+len(supPrivInfo))
+	n := 0
+	n += copy(buffer, algID)
+	n += copy(buffer[n:], ptyUInfo)
+	n += copy(buffer[n:], ptyVInfo)
+	n += copy(buffer[n:], supPubInfo)
+	copy(buffer[n:], supPrivInfo)
+
+	hasher := hash.New()
+
+	return &concatKDF{
+		z:      z,
+		info:   buffer,
+		hasher: hasher,
+		cache:  []byte{},
+		i:      1,
+	}
+}
+
+func (ctx *concatKDF) Read(out []byte) (int, error) {
+	copied := copy(out, ctx.cache)
+	ctx.cache = ctx.cache[copied:]
+
+	for copied < len(out) {
+		ctx.hasher.Reset()
+
+		// Write on a hash.Hash never fails
+		_ = binary.Write(ctx.hasher, binary.BigEndian, ctx.i)
+		_, _ = ctx.hasher.Write(ctx.z)
+		_, _ = ctx.hasher.Write(ctx.info)
+
+		hash := ctx.hasher.Sum(nil)
+		chunkCopied := copy(out[copied:], hash)
+		copied += chunkCopied
+		ctx.cache = hash[chunkCopied:]
+
+		ctx.i++
+	}
+
+	return copied, nil
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/cipher/concat_kdf_test.go b/Godeps/_workspace/src/github.com/square/go-jose/cipher/concat_kdf_test.go
new file mode 100644
index 00000000..b4439a79
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/cipher/concat_kdf_test.go
@@ -0,0 +1,148 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+	"bytes"
+	"crypto"
+	"testing"
+)
+
+// Taken from: https://tools.ietf.org/id/draft-ietf-jose-json-web-algorithms-38.txt
+func TestVectorConcatKDF(t *testing.T) {
+	z := []byte{
+		158, 86, 217, 29, 129, 113, 53, 211, 114, 131, 66, 131, 191, 132,
+		38, 156, 251, 49, 110, 163, 218, 128, 106, 72, 246, 218, 167, 121,
+		140, 254, 144, 196}
+
+	algID := []byte{0, 0, 0, 7, 65, 49, 50, 56, 71, 67, 77}
+
+	ptyUInfo := []byte{0, 0, 0, 5, 65, 108, 105, 99, 101}
+	ptyVInfo := []byte{0, 0, 0, 3, 66, 111, 98}
+
+	supPubInfo := []byte{0, 0, 0, 128}
+	supPrivInfo := []byte{}
+
+	expected := []byte{
+		86, 170, 141, 234, 248, 35, 109, 32, 92, 34, 40, 205, 113, 167, 16, 26}
+
+	ckdf := NewConcatKDF(crypto.SHA256, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo)
+
+	out0 := make([]byte, 9)
+	out1 := make([]byte, 7)
+
+	read0, err := ckdf.Read(out0)
+	if err != nil {
+		t.Error("error when reading from concat kdf reader", err)
+		return
+	}
+
+	read1, err := ckdf.Read(out1)
+	if err != nil {
+		t.Error("error when reading from concat kdf reader", err)
+		return
+	}
+
+	if read0+read1 != len(out0)+len(out1) {
+		t.Error("did not receive enough bytes from concat kdf reader")
+		return
+	}
+
+	out := []byte{}
+	out = append(out, out0...)
+	out = append(out, out1...)
+
+	if bytes.Compare(out, expected) != 0 {
+		t.Error("did not receive expected output from concat kdf reader")
+		return
+	}
+}
+
+func TestCache(t *testing.T) {
+	z := []byte{
+		158, 86, 217, 29, 129, 113, 53, 211, 114, 131, 66, 131, 191, 132,
+		38, 156, 251, 49, 110, 163, 218, 128, 106, 72, 246, 218, 167, 121,
+		140, 254, 144, 196}
+
+	algID := []byte{1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}
+
+	ptyUInfo := []byte{1, 2, 3, 4}
+	ptyVInfo := []byte{4, 3, 2, 1}
+
+	supPubInfo := []byte{}
+	supPrivInfo := []byte{}
+
+	outputs := [][]byte{}
+
+	// Read the same amount of data in different chunk sizes
+	for i := 10; i <= 100; i++ {
+		out := make([]byte, 1024)
+		reader := NewConcatKDF(crypto.SHA256, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo)
+
+		for j := 0; j < 1024/i; j++ {
+			_, _ = reader.Read(out[j*i:])
+		}
+
+		outputs = append(outputs, out)
+	}
+
+	for i := range outputs {
+		if bytes.Compare(outputs[i], outputs[i%len(outputs)]) != 0 {
+			t.Error("not all outputs from KDF matched")
+		}
+	}
+}
+
+func benchmarkKDF(b *testing.B, total int) {
+	z := []byte{
+		158, 86, 217, 29, 129, 113, 53, 211, 114, 131, 66, 131, 191, 132,
+		38, 156, 251, 49, 110, 163, 218, 128, 106, 72, 246, 218, 167, 121,
+		140, 254, 144, 196}
+
+	algID := []byte{1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}
+
+	ptyUInfo := []byte{1, 2, 3, 4}
+	ptyVInfo := []byte{4, 3, 2, 1}
+
+	supPubInfo := []byte{}
+	supPrivInfo := []byte{}
+
+	out := make([]byte, total)
+	reader := NewConcatKDF(crypto.SHA256, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo)
+
+	b.ResetTimer()
+	b.SetBytes(int64(total))
+	for i := 0; i < b.N; i++ {
+		_, _ = reader.Read(out)
+	}
+}
+
+func BenchmarkConcatKDF_1k(b *testing.B) {
+	benchmarkKDF(b, 1024)
+}
+
+func BenchmarkConcatKDF_64k(b *testing.B) {
+	benchmarkKDF(b, 65536)
+}
+
+func BenchmarkConcatKDF_1MB(b *testing.B) {
+	benchmarkKDF(b, 1048576)
+}
+
+func BenchmarkConcatKDF_64MB(b *testing.B) {
+	benchmarkKDF(b, 67108864)
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/cipher/ecdh_es.go b/Godeps/_workspace/src/github.com/square/go-jose/cipher/ecdh_es.go
new file mode 100644
index 00000000..c6a5a821
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/cipher/ecdh_es.go
@@ -0,0 +1,51 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"encoding/binary"
+)
+
+// DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA.
+func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
+	// algId, partyUInfo, partyVInfo inputs must be prefixed with the length
+	algID := lengthPrefixed([]byte(alg))
+	ptyUInfo := lengthPrefixed(apuData)
+	ptyVInfo := lengthPrefixed(apvData)
+
+	// suppPubInfo is the encoded length of the output size in bits
+	supPubInfo := make([]byte, 4)
+	binary.BigEndian.PutUint32(supPubInfo, uint32(size)*8)
+
+	z, _ := priv.PublicKey.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes())
+	reader := NewConcatKDF(crypto.SHA256, z.Bytes(), algID, ptyUInfo, ptyVInfo, supPubInfo, []byte{})
+
+	key := make([]byte, size)
+
+	// Read on the KDF will never fail
+	_, _ = reader.Read(key)
+	return key
+}
+
+func lengthPrefixed(data []byte) []byte {
+	out := make([]byte, len(data)+4)
+	binary.BigEndian.PutUint32(out, uint32(len(data)))
+	copy(out[4:], data)
+	return out
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/cipher/ecdh_es_test.go b/Godeps/_workspace/src/github.com/square/go-jose/cipher/ecdh_es_test.go
new file mode 100644
index 00000000..f92abb17
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/cipher/ecdh_es_test.go
@@ -0,0 +1,98 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"encoding/base64"
+	"math/big"
+	"testing"
+)
+
+// Example keys from JWA, Appendix C
+var aliceKey = &ecdsa.PrivateKey{
+	PublicKey: ecdsa.PublicKey{
+		Curve: elliptic.P256(),
+		X:     fromBase64Int("gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0="),
+		Y:     fromBase64Int("SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps="),
+	},
+	D: fromBase64Int("0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo="),
+}
+
+var bobKey = &ecdsa.PrivateKey{
+	PublicKey: ecdsa.PublicKey{
+		Curve: elliptic.P256(),
+		X:     fromBase64Int("weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ="),
+		Y:     fromBase64Int("e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck="),
+	},
+	D: fromBase64Int("VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw="),
+}
+
+// Build big int from base64-encoded string. Strips whitespace (for testing).
+func fromBase64Int(data string) *big.Int {
+	val, err := base64.URLEncoding.DecodeString(data)
+	if err != nil {
+		panic("Invalid test data")
+	}
+	return new(big.Int).SetBytes(val)
+}
+
+func TestVectorECDHES(t *testing.T) {
+	apuData := []byte("Alice")
+	apvData := []byte("Bob")
+
+	expected := []byte{
+		86, 170, 141, 234, 248, 35, 109, 32, 92, 34, 40, 205, 113, 167, 16, 26}
+
+	output := DeriveECDHES("A128GCM", apuData, apvData, bobKey, &aliceKey.PublicKey, 16)
+
+	if bytes.Compare(output, expected) != 0 {
+		t.Error("output did not match what we expect, got", output, "wanted", expected)
+	}
+}
+
+func BenchmarkECDHES_128(b *testing.B) {
+	apuData := []byte("APU")
+	apvData := []byte("APV")
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		DeriveECDHES("ID", apuData, apvData, bobKey, &aliceKey.PublicKey, 16)
+	}
+}
+
+func BenchmarkECDHES_192(b *testing.B) {
+	apuData := []byte("APU")
+	apvData := []byte("APV")
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		DeriveECDHES("ID", apuData, apvData, bobKey, &aliceKey.PublicKey, 24)
+	}
+}
+
+func BenchmarkECDHES_256(b *testing.B) {
+	apuData := []byte("APU")
+	apvData := []byte("APV")
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		DeriveECDHES("ID", apuData, apvData, bobKey, &aliceKey.PublicKey, 32)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/cipher/key_wrap.go b/Godeps/_workspace/src/github.com/square/go-jose/cipher/key_wrap.go
new file mode 100644
index 00000000..1d36d501
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/cipher/key_wrap.go
@@ -0,0 +1,109 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+	"crypto/cipher"
+	"crypto/subtle"
+	"encoding/binary"
+	"errors"
+)
+
+var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
+
+// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher.
+func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
+	if len(cek)%8 != 0 {
+		return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
+	}
+
+	n := len(cek) / 8
+	r := make([][]byte, n)
+
+	for i := range r {
+		r[i] = make([]byte, 8)
+		copy(r[i], cek[i*8:])
+	}
+
+	buffer := make([]byte, 16)
+	tBytes := make([]byte, 8)
+	copy(buffer, defaultIV)
+
+	for t := 0; t < 6*n; t++ {
+		copy(buffer[8:], r[t%n])
+
+		block.Encrypt(buffer, buffer)
+
+		binary.BigEndian.PutUint64(tBytes, uint64(t+1))
+
+		for i := 0; i < 8; i++ {
+			buffer[i] = buffer[i] ^ tBytes[i]
+		}
+		copy(r[t%n], buffer[8:])
+	}
+
+	out := make([]byte, (n+1)*8)
+	copy(out, buffer[:8])
+	for i := range r {
+		copy(out[(i+1)*8:], r[i])
+	}
+
+	return out, nil
+}
+
+// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher.
+func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
+	if len(ciphertext)%8 != 0 {
+		return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
+	}
+
+	n := (len(ciphertext) / 8) - 1
+	r := make([][]byte, n)
+
+	for i := range r {
+		r[i] = make([]byte, 8)
+		copy(r[i], ciphertext[(i+1)*8:])
+	}
+
+	buffer := make([]byte, 16)
+	tBytes := make([]byte, 8)
+	copy(buffer[:8], ciphertext[:8])
+
+	for t := 6*n - 1; t >= 0; t-- {
+		binary.BigEndian.PutUint64(tBytes, uint64(t+1))
+
+		for i := 0; i < 8; i++ {
+			buffer[i] = buffer[i] ^ tBytes[i]
+		}
+		copy(buffer[8:], r[t%n])
+
+		block.Decrypt(buffer, buffer)
+
+		copy(r[t%n], buffer[8:])
+	}
+
+	if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 {
+		return nil, errors.New("square/go-jose: failed to unwrap key")
+	}
+
+	out := make([]byte, n*8)
+	for i := range r {
+		copy(out[i*8:], r[i])
+	}
+
+	return out, nil
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/cipher/key_wrap_test.go b/Godeps/_workspace/src/github.com/square/go-jose/cipher/key_wrap_test.go
new file mode 100644
index 00000000..ceecf812
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/cipher/key_wrap_test.go
@@ -0,0 +1,133 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+	"bytes"
+	"crypto/aes"
+	"encoding/hex"
+	"testing"
+)
+
+func TestAesKeyWrap(t *testing.T) {
+	// Test vectors from: http://csrc.nist.gov/groups/ST/toolkit/documents/kms/key-wrap.pdf
+	kek0, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F")
+	cek0, _ := hex.DecodeString("00112233445566778899AABBCCDDEEFF")
+
+	expected0, _ := hex.DecodeString("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5")
+
+	kek1, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F1011121314151617")
+	cek1, _ := hex.DecodeString("00112233445566778899AABBCCDDEEFF")
+
+	expected1, _ := hex.DecodeString("96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D")
+
+	kek2, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")
+	cek2, _ := hex.DecodeString("00112233445566778899AABBCCDDEEFF0001020304050607")
+
+	expected2, _ := hex.DecodeString("A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1")
+
+	block0, _ := aes.NewCipher(kek0)
+	block1, _ := aes.NewCipher(kek1)
+	block2, _ := aes.NewCipher(kek2)
+
+	out0, _ := KeyWrap(block0, cek0)
+	out1, _ := KeyWrap(block1, cek1)
+	out2, _ := KeyWrap(block2, cek2)
+
+	if bytes.Compare(out0, expected0) != 0 {
+		t.Error("output 0 not as expected, got", out0, "wanted", expected0)
+	}
+
+	if bytes.Compare(out1, expected1) != 0 {
+		t.Error("output 1 not as expected, got", out1, "wanted", expected1)
+	}
+
+	if bytes.Compare(out2, expected2) != 0 {
+		t.Error("output 2 not as expected, got", out2, "wanted", expected2)
+	}
+
+	unwrap0, _ := KeyUnwrap(block0, out0)
+	unwrap1, _ := KeyUnwrap(block1, out1)
+	unwrap2, _ := KeyUnwrap(block2, out2)
+
+	if bytes.Compare(unwrap0, cek0) != 0 {
+		t.Error("key unwrap did not return original input, got", unwrap0, "wanted", cek0)
+	}
+
+	if bytes.Compare(unwrap1, cek1) != 0 {
+		t.Error("key unwrap did not return original input, got", unwrap1, "wanted", cek1)
+	}
+
+	if bytes.Compare(unwrap2, cek2) != 0 {
+		t.Error("key unwrap did not return original input, got", unwrap2, "wanted", cek2)
+	}
+}
+
+func TestAesKeyWrapInvalid(t *testing.T) {
+	kek, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F")
+
+	// Invalid unwrap input (bit flipped)
+	input0, _ := hex.DecodeString("1EA68C1A8112B447AEF34BD8FB5A7B828D3E862371D2CFE5")
+
+	block, _ := aes.NewCipher(kek)
+
+	_, err := KeyUnwrap(block, input0)
+	if err == nil {
+		t.Error("key unwrap failed to detect invalid input")
+	}
+
+	// Invalid unwrap input (truncated)
+	input1, _ := hex.DecodeString("1EA68C1A8112B447AEF34BD8FB5A7B828D3E862371D2CF")
+
+	_, err = KeyUnwrap(block, input1)
+	if err == nil {
+		t.Error("key unwrap failed to detect truncated input")
+	}
+
+	// Invalid wrap input (not multiple of 8)
+	input2, _ := hex.DecodeString("0123456789ABCD")
+
+	_, err = KeyWrap(block, input2)
+	if err == nil {
+		t.Error("key wrap accepted invalid input")
+	}
+
+}
+
+func BenchmarkAesKeyWrap(b *testing.B) {
+	kek, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F")
+	key, _ := hex.DecodeString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
+
+	block, _ := aes.NewCipher(kek)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		KeyWrap(block, key)
+	}
+}
+
+func BenchmarkAesKeyUnwrap(b *testing.B) {
+	kek, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F")
+	input, _ := hex.DecodeString("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5")
+
+	block, _ := aes.NewCipher(kek)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		KeyUnwrap(block, input)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/crypter.go b/Godeps/_workspace/src/github.com/square/go-jose/crypter.go
new file mode 100644
index 00000000..f61af2c0
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/crypter.go
@@ -0,0 +1,349 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"fmt"
+	"reflect"
+)
+
+// Encrypter represents an encrypter which produces an encrypted JWE object.
+type Encrypter interface {
+	Encrypt(plaintext []byte) (*JsonWebEncryption, error)
+	EncryptWithAuthData(plaintext []byte, aad []byte) (*JsonWebEncryption, error)
+	SetCompression(alg CompressionAlgorithm)
+}
+
+// MultiEncrypter represents an encrypter which supports multiple recipients.
+type MultiEncrypter interface {
+	Encrypt(plaintext []byte) (*JsonWebEncryption, error)
+	EncryptWithAuthData(plaintext []byte, aad []byte) (*JsonWebEncryption, error)
+	SetCompression(alg CompressionAlgorithm)
+	AddRecipient(alg KeyAlgorithm, encryptionKey interface{}) error
+}
+
+// A generic content cipher
+type contentCipher interface {
+	keySize() int
+	encrypt(cek []byte, aad, plaintext []byte) (*aeadParts, error)
+	decrypt(cek []byte, aad []byte, parts *aeadParts) ([]byte, error)
+}
+
+// A key generator (for generating/getting a CEK)
+type keyGenerator interface {
+	keySize() int
+	genKey() ([]byte, rawHeader, error)
+}
+
+// A generic key encrypter
+type keyEncrypter interface {
+	encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) // Encrypt a key
+}
+
+// A generic key decrypter
+type keyDecrypter interface {
+	decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) // Decrypt a key
+}
+
+// A generic encrypter based on the given key encrypter and content cipher.
+type genericEncrypter struct {
+	contentAlg     ContentEncryption
+	compressionAlg CompressionAlgorithm
+	cipher         contentCipher
+	recipients     []recipientKeyInfo
+	keyGenerator   keyGenerator
+}
+
+type recipientKeyInfo struct {
+	keyID        string
+	keyAlg       KeyAlgorithm
+	keyEncrypter keyEncrypter
+}
+
+// SetCompression sets a compression algorithm to be applied before encryption.
+func (ctx *genericEncrypter) SetCompression(compressionAlg CompressionAlgorithm) {
+	ctx.compressionAlg = compressionAlg
+}
+
+// NewEncrypter creates an appropriate encrypter based on the key type
+func NewEncrypter(alg KeyAlgorithm, enc ContentEncryption, encryptionKey interface{}) (Encrypter, error) {
+	encrypter := &genericEncrypter{
+		contentAlg:     enc,
+		compressionAlg: NONE,
+		recipients:     []recipientKeyInfo{},
+		cipher:         getContentCipher(enc),
+	}
+
+	if encrypter.cipher == nil {
+		return nil, ErrUnsupportedAlgorithm
+	}
+
+	var keyID string
+	var rawKey interface{}
+	switch encryptionKey := encryptionKey.(type) {
+	case *JsonWebKey:
+		keyID = encryptionKey.KeyID
+		rawKey = encryptionKey.Key
+	default:
+		rawKey = encryptionKey
+	}
+
+	switch alg {
+	case DIRECT:
+		// Direct encryption mode must be treated differently
+		if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) {
+			return nil, ErrUnsupportedKeyType
+		}
+		encrypter.keyGenerator = staticKeyGenerator{
+			key: rawKey.([]byte),
+		}
+		recipient, _ := newSymmetricRecipient(alg, rawKey.([]byte))
+		if keyID != "" {
+			recipient.keyID = keyID
+		}
+		encrypter.recipients = []recipientKeyInfo{recipient}
+		return encrypter, nil
+	case ECDH_ES:
+		// ECDH-ES (w/o key wrapping) is similar to DIRECT mode
+		typeOf := reflect.TypeOf(rawKey)
+		if typeOf != reflect.TypeOf(&ecdsa.PublicKey{}) {
+			return nil, ErrUnsupportedKeyType
+		}
+		encrypter.keyGenerator = ecKeyGenerator{
+			size:      encrypter.cipher.keySize(),
+			algID:     string(enc),
+			publicKey: rawKey.(*ecdsa.PublicKey),
+		}
+		recipient, _ := newECDHRecipient(alg, rawKey.(*ecdsa.PublicKey))
+		if keyID != "" {
+			recipient.keyID = keyID
+		}
+		encrypter.recipients = []recipientKeyInfo{recipient}
+		return encrypter, nil
+	default:
+		// Can just add a standard recipient
+		encrypter.keyGenerator = randomKeyGenerator{
+			size: encrypter.cipher.keySize(),
+		}
+		err := encrypter.AddRecipient(alg, encryptionKey)
+		return encrypter, err
+	}
+}
+
+// NewMultiEncrypter creates a multi-encrypter based on the given parameters
+func NewMultiEncrypter(enc ContentEncryption) (MultiEncrypter, error) {
+	cipher := getContentCipher(enc)
+
+	if cipher == nil {
+		return nil, ErrUnsupportedAlgorithm
+	}
+
+	encrypter := &genericEncrypter{
+		contentAlg:     enc,
+		compressionAlg: NONE,
+		recipients:     []recipientKeyInfo{},
+		cipher:         cipher,
+		keyGenerator: randomKeyGenerator{
+			size: cipher.keySize(),
+		},
+	}
+
+	return encrypter, nil
+}
+
+func (ctx *genericEncrypter) AddRecipient(alg KeyAlgorithm, encryptionKey interface{}) (err error) {
+	var recipient recipientKeyInfo
+
+	switch alg {
+	case DIRECT, ECDH_ES:
+		return fmt.Errorf("square/go-jose: key algorithm '%s' not supported in multi-recipient mode", alg)
+	}
+
+	recipient, err = makeJWERecipient(alg, encryptionKey)
+
+	if err == nil {
+		ctx.recipients = append(ctx.recipients, recipient)
+	}
+	return err
+}
+
+func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) {
+	switch encryptionKey := encryptionKey.(type) {
+	case *rsa.PublicKey:
+		return newRSARecipient(alg, encryptionKey)
+	case *ecdsa.PublicKey:
+		return newECDHRecipient(alg, encryptionKey)
+	case []byte:
+		return newSymmetricRecipient(alg, encryptionKey)
+	case *JsonWebKey:
+		recipient, err := makeJWERecipient(alg, encryptionKey.Key)
+		if err == nil && encryptionKey.KeyID != "" {
+			recipient.keyID = encryptionKey.KeyID
+		}
+		return recipient, err
+	default:
+		return recipientKeyInfo{}, ErrUnsupportedKeyType
+	}
+}
+
+// newDecrypter creates an appropriate decrypter based on the key type
+func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
+	switch decryptionKey := decryptionKey.(type) {
+	case *rsa.PrivateKey:
+		return &rsaDecrypterSigner{
+			privateKey: decryptionKey,
+		}, nil
+	case *ecdsa.PrivateKey:
+		return &ecDecrypterSigner{
+			privateKey: decryptionKey,
+		}, nil
+	case []byte:
+		return &symmetricKeyCipher{
+			key: decryptionKey,
+		}, nil
+	case *JsonWebKey:
+		return newDecrypter(decryptionKey.Key)
+	default:
+		return nil, ErrUnsupportedKeyType
+	}
+}
+
+// Implementation of encrypt method producing a JWE object.
+func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JsonWebEncryption, error) {
+	return ctx.EncryptWithAuthData(plaintext, nil)
+}
+
+// Implementation of encrypt method producing a JWE object.
+func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JsonWebEncryption, error) {
+	obj := &JsonWebEncryption{}
+	obj.aad = aad
+
+	obj.protected = &rawHeader{
+		Enc: ctx.contentAlg,
+	}
+	obj.recipients = make([]recipientInfo, len(ctx.recipients))
+
+	if len(ctx.recipients) == 0 {
+		return nil, fmt.Errorf("square/go-jose: no recipients to encrypt to")
+	}
+
+	cek, headers, err := ctx.keyGenerator.genKey()
+	if err != nil {
+		return nil, err
+	}
+
+	obj.protected.merge(&headers)
+
+	for i, info := range ctx.recipients {
+		recipient, err := info.keyEncrypter.encryptKey(cek, info.keyAlg)
+		if err != nil {
+			return nil, err
+		}
+
+		recipient.header.Alg = string(info.keyAlg)
+		if info.keyID != "" {
+			recipient.header.Kid = info.keyID
+		}
+		obj.recipients[i] = recipient
+	}
+
+	if len(ctx.recipients) == 1 {
+		// Move per-recipient headers into main protected header if there's
+		// only a single recipient.
+		obj.protected.merge(obj.recipients[0].header)
+		obj.recipients[0].header = nil
+	}
+
+	if ctx.compressionAlg != NONE {
+		plaintext, err = compress(ctx.compressionAlg, plaintext)
+		if err != nil {
+			return nil, err
+		}
+
+		obj.protected.Zip = ctx.compressionAlg
+	}
+
+	authData := obj.computeAuthData()
+	parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
+	if err != nil {
+		return nil, err
+	}
+
+	obj.iv = parts.iv
+	obj.ciphertext = parts.ciphertext
+	obj.tag = parts.tag
+
+	return obj, nil
+}
+
+// Decrypt and validate the object and return the plaintext.
+func (obj JsonWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
+	headers := obj.mergedHeaders(nil)
+
+	if len(headers.Crit) > 0 {
+		return nil, fmt.Errorf("square/go-jose: unsupported crit header")
+	}
+
+	decrypter, err := newDecrypter(decryptionKey)
+	if err != nil {
+		return nil, err
+	}
+
+	cipher := getContentCipher(headers.Enc)
+	if cipher == nil {
+		return nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(headers.Enc))
+	}
+
+	generator := randomKeyGenerator{
+		size: cipher.keySize(),
+	}
+
+	parts := &aeadParts{
+		iv:         obj.iv,
+		ciphertext: obj.ciphertext,
+		tag:        obj.tag,
+	}
+
+	authData := obj.computeAuthData()
+
+	var plaintext []byte
+	for _, recipient := range obj.recipients {
+		recipientHeaders := obj.mergedHeaders(&recipient)
+
+		cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
+		if err == nil {
+			// Found a valid CEK -- let's try to decrypt.
+			plaintext, err = cipher.decrypt(cek, authData, parts)
+			if err == nil {
+				break
+			}
+		}
+	}
+
+	if plaintext == nil {
+		return nil, ErrCryptoFailure
+	}
+
+	// The "zip" header paramter may only be present in the protected header.
+	if obj.protected.Zip != "" {
+		plaintext, err = decompress(obj.protected.Zip, plaintext)
+	}
+
+	return plaintext, err
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/crypter_test.go b/Godeps/_workspace/src/github.com/square/go-jose/crypter_test.go
new file mode 100644
index 00000000..86b8fc0a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/crypter_test.go
@@ -0,0 +1,784 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/rsa"
+	"fmt"
+	"io"
+	"testing"
+)
+
+// We generate only a single RSA and EC key for testing, speeds up tests.
+var rsaTestKey, _ = rsa.GenerateKey(rand.Reader, 2048)
+
+var ecTestKey256, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+var ecTestKey384, _ = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
+var ecTestKey521, _ = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
+
+func RoundtripJWE(keyAlg KeyAlgorithm, encAlg ContentEncryption, compressionAlg CompressionAlgorithm, serializer func(*JsonWebEncryption) (string, error), corrupter func(*JsonWebEncryption) bool, aad []byte, encryptionKey interface{}, decryptionKey interface{}) error {
+	enc, err := NewEncrypter(keyAlg, encAlg, encryptionKey)
+	if err != nil {
+		return fmt.Errorf("error on new encrypter: %s", err)
+	}
+
+	enc.SetCompression(compressionAlg)
+
+	input := []byte("Lorem ipsum dolor sit amet")
+	obj, err := enc.EncryptWithAuthData(input, aad)
+	if err != nil {
+		return fmt.Errorf("error in encrypt: %s", err)
+	}
+
+	msg, err := serializer(obj)
+	if err != nil {
+		return fmt.Errorf("error in serializer: %s", err)
+	}
+
+	parsed, err := ParseEncrypted(msg)
+	if err != nil {
+		return fmt.Errorf("error in parse: %s, on msg '%s'", err, msg)
+	}
+
+	// (Maybe) mangle object
+	skip := corrupter(parsed)
+	if skip {
+		return fmt.Errorf("corrupter indicated message should be skipped")
+	}
+
+	if bytes.Compare(parsed.GetAuthData(), aad) != 0 {
+		return fmt.Errorf("auth data in parsed object does not match")
+	}
+
+	output, err := parsed.Decrypt(decryptionKey)
+	if err != nil {
+		return fmt.Errorf("error on decrypt: %s", err)
+	}
+
+	if bytes.Compare(input, output) != 0 {
+		return fmt.Errorf("Decrypted output does not match input, got '%s' but wanted '%s'", output, input)
+	}
+
+	return nil
+}
+
+func TestRoundtripsJWE(t *testing.T) {
+	// Test matrix
+	keyAlgs := []KeyAlgorithm{
+		DIRECT, ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW, A128KW, A192KW, A256KW,
+		RSA1_5, RSA_OAEP, RSA_OAEP_256, A128GCMKW, A192GCMKW, A256GCMKW}
+	encAlgs := []ContentEncryption{A128GCM, A192GCM, A256GCM, A128CBC_HS256, A192CBC_HS384, A256CBC_HS512}
+	zipAlgs := []CompressionAlgorithm{NONE, DEFLATE}
+
+	serializers := []func(*JsonWebEncryption) (string, error){
+		func(obj *JsonWebEncryption) (string, error) { return obj.CompactSerialize() },
+		func(obj *JsonWebEncryption) (string, error) { return obj.FullSerialize(), nil },
+	}
+
+	corrupter := func(obj *JsonWebEncryption) bool { return false }
+
+	// Note: can't use AAD with compact serialization
+	aads := [][]byte{
+		nil,
+		[]byte("Ut enim ad minim veniam"),
+	}
+
+	// Test all different configurations
+	for _, alg := range keyAlgs {
+		for _, enc := range encAlgs {
+			for _, key := range generateTestKeys(alg, enc) {
+				for _, zip := range zipAlgs {
+					for i, serializer := range serializers {
+						err := RoundtripJWE(alg, enc, zip, serializer, corrupter, aads[i], key.enc, key.dec)
+						if err != nil {
+							t.Error(err, alg, enc, zip, i)
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+func TestRoundtripsJWECorrupted(t *testing.T) {
+	// Test matrix
+	keyAlgs := []KeyAlgorithm{DIRECT, ECDH_ES, ECDH_ES_A128KW, A128KW, RSA1_5, RSA_OAEP, RSA_OAEP_256, A128GCMKW}
+	encAlgs := []ContentEncryption{A128GCM, A192GCM, A256GCM, A128CBC_HS256, A192CBC_HS384, A256CBC_HS512}
+	zipAlgs := []CompressionAlgorithm{NONE, DEFLATE}
+
+	serializers := []func(*JsonWebEncryption) (string, error){
+		func(obj *JsonWebEncryption) (string, error) { return obj.CompactSerialize() },
+		func(obj *JsonWebEncryption) (string, error) { return obj.FullSerialize(), nil },
+	}
+
+	bitflip := func(slice []byte) bool {
+		if len(slice) > 0 {
+			slice[0] ^= 0xFF
+			return false
+		}
+		return true
+	}
+
+	corrupters := []func(*JsonWebEncryption) bool{
+		func(obj *JsonWebEncryption) bool {
+			// Set invalid ciphertext
+			return bitflip(obj.ciphertext)
+		},
+		func(obj *JsonWebEncryption) bool {
+			// Set invalid auth tag
+			return bitflip(obj.tag)
+		},
+		func(obj *JsonWebEncryption) bool {
+			// Set invalid AAD
+			return bitflip(obj.aad)
+		},
+		func(obj *JsonWebEncryption) bool {
+			// Mess with encrypted key
+			return bitflip(obj.recipients[0].encryptedKey)
+		},
+		func(obj *JsonWebEncryption) bool {
+			// Mess with GCM-KW auth tag
+			return bitflip(obj.protected.Tag.bytes())
+		},
+	}
+
+	// Note: can't use AAD with compact serialization
+	aads := [][]byte{
+		nil,
+		[]byte("Ut enim ad minim veniam"),
+	}
+
+	// Test all different configurations
+	for _, alg := range keyAlgs {
+		for _, enc := range encAlgs {
+			for _, key := range generateTestKeys(alg, enc) {
+				for _, zip := range zipAlgs {
+					for i, serializer := range serializers {
+						for j, corrupter := range corrupters {
+							err := RoundtripJWE(alg, enc, zip, serializer, corrupter, aads[i], key.enc, key.dec)
+							if err == nil {
+								t.Error("failed to detect corrupt data", err, alg, enc, zip, i, j)
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+func TestEncrypterWithJWKAndKeyID(t *testing.T) {
+	enc, err := NewEncrypter(A128KW, A128GCM, &JsonWebKey{
+		KeyID: "test-id",
+		Key:   []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+	})
+	if err != nil {
+		t.Error(err)
+	}
+
+	ciphertext, _ := enc.Encrypt([]byte("Lorem ipsum dolor sit amet"))
+
+	serialized1, _ := ciphertext.CompactSerialize()
+	serialized2 := ciphertext.FullSerialize()
+
+	parsed1, _ := ParseEncrypted(serialized1)
+	parsed2, _ := ParseEncrypted(serialized2)
+
+	if parsed1.Header.KeyID != "test-id" {
+		t.Errorf("expected message to have key id from JWK, but found '%s' instead", parsed1.Header.KeyID)
+	}
+	if parsed2.Header.KeyID != "test-id" {
+		t.Errorf("expected message to have key id from JWK, but found '%s' instead", parsed2.Header.KeyID)
+	}
+}
+
+func TestEncrypterWithBrokenRand(t *testing.T) {
+	keyAlgs := []KeyAlgorithm{ECDH_ES_A128KW, A128KW, RSA1_5, RSA_OAEP, RSA_OAEP_256, A128GCMKW}
+	encAlgs := []ContentEncryption{A128GCM, A192GCM, A256GCM, A128CBC_HS256, A192CBC_HS384, A256CBC_HS512}
+
+	serializer := func(obj *JsonWebEncryption) (string, error) { return obj.CompactSerialize() }
+	corrupter := func(obj *JsonWebEncryption) bool { return false }
+
+	// Break rand reader
+	readers := []func() io.Reader{
+		// Totally broken
+		func() io.Reader { return bytes.NewReader([]byte{}) },
+		// Not enough bytes
+		func() io.Reader { return io.LimitReader(rand.Reader, 20) },
+	}
+
+	defer resetRandReader()
+
+	for _, alg := range keyAlgs {
+		for _, enc := range encAlgs {
+			for _, key := range generateTestKeys(alg, enc) {
+				for i, getReader := range readers {
+					randReader = getReader()
+					err := RoundtripJWE(alg, enc, NONE, serializer, corrupter, nil, key.enc, key.dec)
+					if err == nil {
+						t.Error("encrypter should fail if rand is broken", i)
+					}
+				}
+			}
+		}
+	}
+}
+
+func TestNewEncrypterErrors(t *testing.T) {
+	_, err := NewEncrypter("XYZ", "XYZ", nil)
+	if err == nil {
+		t.Error("was able to instantiate encrypter with invalid cipher")
+	}
+
+	_, err = NewMultiEncrypter("XYZ")
+	if err == nil {
+		t.Error("was able to instantiate multi-encrypter with invalid cipher")
+	}
+
+	_, err = NewEncrypter(DIRECT, A128GCM, nil)
+	if err == nil {
+		t.Error("was able to instantiate encrypter with invalid direct key")
+	}
+
+	_, err = NewEncrypter(ECDH_ES, A128GCM, nil)
+	if err == nil {
+		t.Error("was able to instantiate encrypter with invalid EC key")
+	}
+}
+
+func TestMultiRecipientJWE(t *testing.T) {
+	enc, err := NewMultiEncrypter(A128GCM)
+	if err != nil {
+		panic(err)
+	}
+
+	err = enc.AddRecipient(RSA_OAEP, &rsaTestKey.PublicKey)
+	if err != nil {
+		t.Error("error when adding RSA recipient", err)
+	}
+
+	sharedKey := []byte{
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+	}
+
+	err = enc.AddRecipient(A256GCMKW, sharedKey)
+	if err != nil {
+		t.Error("error when adding AES recipient: ", err)
+		return
+	}
+
+	input := []byte("Lorem ipsum dolor sit amet")
+	obj, err := enc.Encrypt(input)
+	if err != nil {
+		t.Error("error in encrypt: ", err)
+		return
+	}
+
+	msg := obj.FullSerialize()
+
+	parsed, err := ParseEncrypted(msg)
+	if err != nil {
+		t.Error("error in parse: ", err)
+		return
+	}
+
+	output, err := parsed.Decrypt(rsaTestKey)
+	if err != nil {
+		t.Error("error on decrypt with RSA: ", err)
+		return
+	}
+
+	if bytes.Compare(input, output) != 0 {
+		t.Error("Decrypted output does not match input: ", output, input)
+		return
+	}
+
+	output, err = parsed.Decrypt(sharedKey)
+	if err != nil {
+		t.Error("error on decrypt with AES: ", err)
+		return
+	}
+
+	if bytes.Compare(input, output) != 0 {
+		t.Error("Decrypted output does not match input", output, input)
+		return
+	}
+}
+
+func TestMultiRecipientErrors(t *testing.T) {
+	enc, err := NewMultiEncrypter(A128GCM)
+	if err != nil {
+		panic(err)
+	}
+
+	input := []byte("Lorem ipsum dolor sit amet")
+	_, err = enc.Encrypt(input)
+	if err == nil {
+		t.Error("should fail when encrypting to zero recipients")
+	}
+
+	err = enc.AddRecipient(DIRECT, nil)
+	if err == nil {
+		t.Error("should reject DIRECT mode when encrypting to multiple recipients")
+	}
+
+	err = enc.AddRecipient(ECDH_ES, nil)
+	if err == nil {
+		t.Error("should reject ECDH_ES mode when encrypting to multiple recipients")
+	}
+
+	err = enc.AddRecipient(RSA1_5, nil)
+	if err == nil {
+		t.Error("should reject invalid recipient key")
+	}
+}
+
+type testKey struct {
+	enc, dec interface{}
+}
+
+func symmetricTestKey(size int) []testKey {
+	key, _, _ := randomKeyGenerator{size: size}.genKey()
+
+	return []testKey{
+		testKey{
+			enc: key,
+			dec: key,
+		},
+		testKey{
+			enc: &JsonWebKey{KeyID: "test", Key: key},
+			dec: &JsonWebKey{KeyID: "test", Key: key},
+		},
+	}
+}
+
+func generateTestKeys(keyAlg KeyAlgorithm, encAlg ContentEncryption) []testKey {
+	switch keyAlg {
+	case DIRECT:
+		return symmetricTestKey(getContentCipher(encAlg).keySize())
+	case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
+		return []testKey{
+			testKey{
+				dec: ecTestKey256,
+				enc: &ecTestKey256.PublicKey,
+			},
+			testKey{
+				dec: ecTestKey384,
+				enc: &ecTestKey384.PublicKey,
+			},
+			testKey{
+				dec: ecTestKey521,
+				enc: &ecTestKey521.PublicKey,
+			},
+			testKey{
+				dec: &JsonWebKey{KeyID: "test", Key: ecTestKey256},
+				enc: &JsonWebKey{KeyID: "test", Key: &ecTestKey256.PublicKey},
+			},
+		}
+	case A128GCMKW, A128KW:
+		return symmetricTestKey(16)
+	case A192GCMKW, A192KW:
+		return symmetricTestKey(24)
+	case A256GCMKW, A256KW:
+		return symmetricTestKey(32)
+	case RSA1_5, RSA_OAEP, RSA_OAEP_256:
+		return []testKey{testKey{
+			dec: rsaTestKey,
+			enc: &rsaTestKey.PublicKey,
+		}}
+	}
+
+	panic("Must update test case")
+}
+
+func RunRoundtripsJWE(b *testing.B, alg KeyAlgorithm, enc ContentEncryption, zip CompressionAlgorithm, priv, pub interface{}) {
+	serializer := func(obj *JsonWebEncryption) (string, error) {
+		return obj.CompactSerialize()
+	}
+
+	corrupter := func(obj *JsonWebEncryption) bool { return false }
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		err := RoundtripJWE(alg, enc, zip, serializer, corrupter, nil, pub, priv)
+		if err != nil {
+			b.Error(err)
+		}
+	}
+}
+
+var (
+	chunks = map[string][]byte{
+		"1B":   make([]byte, 1),
+		"64B":  make([]byte, 64),
+		"1KB":  make([]byte, 1024),
+		"64KB": make([]byte, 65536),
+		"1MB":  make([]byte, 1048576),
+		"64MB": make([]byte, 67108864),
+	}
+
+	symKey, _, _ = randomKeyGenerator{size: 32}.genKey()
+
+	encrypters = map[string]Encrypter{
+		"OAEPAndGCM":          mustEncrypter(RSA_OAEP, A128GCM, &rsaTestKey.PublicKey),
+		"PKCSAndGCM":          mustEncrypter(RSA1_5, A128GCM, &rsaTestKey.PublicKey),
+		"OAEPAndCBC":          mustEncrypter(RSA_OAEP, A128CBC_HS256, &rsaTestKey.PublicKey),
+		"PKCSAndCBC":          mustEncrypter(RSA1_5, A128CBC_HS256, &rsaTestKey.PublicKey),
+		"DirectGCM128":        mustEncrypter(DIRECT, A128GCM, symKey),
+		"DirectCBC128":        mustEncrypter(DIRECT, A128CBC_HS256, symKey),
+		"DirectGCM256":        mustEncrypter(DIRECT, A256GCM, symKey),
+		"DirectCBC256":        mustEncrypter(DIRECT, A256CBC_HS512, symKey),
+		"AESKWAndGCM128":      mustEncrypter(A128KW, A128GCM, symKey),
+		"AESKWAndCBC256":      mustEncrypter(A256KW, A256GCM, symKey),
+		"ECDHOnP256AndGCM128": mustEncrypter(ECDH_ES, A128GCM, &ecTestKey256.PublicKey),
+		"ECDHOnP384AndGCM128": mustEncrypter(ECDH_ES, A128GCM, &ecTestKey384.PublicKey),
+		"ECDHOnP521AndGCM128": mustEncrypter(ECDH_ES, A128GCM, &ecTestKey521.PublicKey),
+	}
+)
+
+func BenchmarkEncrypt1BWithOAEPAndGCM(b *testing.B)   { benchEncrypt("1B", "OAEPAndGCM", b) }
+func BenchmarkEncrypt64BWithOAEPAndGCM(b *testing.B)  { benchEncrypt("64B", "OAEPAndGCM", b) }
+func BenchmarkEncrypt1KBWithOAEPAndGCM(b *testing.B)  { benchEncrypt("1KB", "OAEPAndGCM", b) }
+func BenchmarkEncrypt64KBWithOAEPAndGCM(b *testing.B) { benchEncrypt("64KB", "OAEPAndGCM", b) }
+func BenchmarkEncrypt1MBWithOAEPAndGCM(b *testing.B)  { benchEncrypt("1MB", "OAEPAndGCM", b) }
+func BenchmarkEncrypt64MBWithOAEPAndGCM(b *testing.B) { benchEncrypt("64MB", "OAEPAndGCM", b) }
+
+func BenchmarkEncrypt1BWithPKCSAndGCM(b *testing.B)   { benchEncrypt("1B", "PKCSAndGCM", b) }
+func BenchmarkEncrypt64BWithPKCSAndGCM(b *testing.B)  { benchEncrypt("64B", "PKCSAndGCM", b) }
+func BenchmarkEncrypt1KBWithPKCSAndGCM(b *testing.B)  { benchEncrypt("1KB", "PKCSAndGCM", b) }
+func BenchmarkEncrypt64KBWithPKCSAndGCM(b *testing.B) { benchEncrypt("64KB", "PKCSAndGCM", b) }
+func BenchmarkEncrypt1MBWithPKCSAndGCM(b *testing.B)  { benchEncrypt("1MB", "PKCSAndGCM", b) }
+func BenchmarkEncrypt64MBWithPKCSAndGCM(b *testing.B) { benchEncrypt("64MB", "PKCSAndGCM", b) }
+
+func BenchmarkEncrypt1BWithOAEPAndCBC(b *testing.B)   { benchEncrypt("1B", "OAEPAndCBC", b) }
+func BenchmarkEncrypt64BWithOAEPAndCBC(b *testing.B)  { benchEncrypt("64B", "OAEPAndCBC", b) }
+func BenchmarkEncrypt1KBWithOAEPAndCBC(b *testing.B)  { benchEncrypt("1KB", "OAEPAndCBC", b) }
+func BenchmarkEncrypt64KBWithOAEPAndCBC(b *testing.B) { benchEncrypt("64KB", "OAEPAndCBC", b) }
+func BenchmarkEncrypt1MBWithOAEPAndCBC(b *testing.B)  { benchEncrypt("1MB", "OAEPAndCBC", b) }
+func BenchmarkEncrypt64MBWithOAEPAndCBC(b *testing.B) { benchEncrypt("64MB", "OAEPAndCBC", b) }
+
+func BenchmarkEncrypt1BWithPKCSAndCBC(b *testing.B)   { benchEncrypt("1B", "PKCSAndCBC", b) }
+func BenchmarkEncrypt64BWithPKCSAndCBC(b *testing.B)  { benchEncrypt("64B", "PKCSAndCBC", b) }
+func BenchmarkEncrypt1KBWithPKCSAndCBC(b *testing.B)  { benchEncrypt("1KB", "PKCSAndCBC", b) }
+func BenchmarkEncrypt64KBWithPKCSAndCBC(b *testing.B) { benchEncrypt("64KB", "PKCSAndCBC", b) }
+func BenchmarkEncrypt1MBWithPKCSAndCBC(b *testing.B)  { benchEncrypt("1MB", "PKCSAndCBC", b) }
+func BenchmarkEncrypt64MBWithPKCSAndCBC(b *testing.B) { benchEncrypt("64MB", "PKCSAndCBC", b) }
+
+func BenchmarkEncrypt1BWithDirectGCM128(b *testing.B)   { benchEncrypt("1B", "DirectGCM128", b) }
+func BenchmarkEncrypt64BWithDirectGCM128(b *testing.B)  { benchEncrypt("64B", "DirectGCM128", b) }
+func BenchmarkEncrypt1KBWithDirectGCM128(b *testing.B)  { benchEncrypt("1KB", "DirectGCM128", b) }
+func BenchmarkEncrypt64KBWithDirectGCM128(b *testing.B) { benchEncrypt("64KB", "DirectGCM128", b) }
+func BenchmarkEncrypt1MBWithDirectGCM128(b *testing.B)  { benchEncrypt("1MB", "DirectGCM128", b) }
+func BenchmarkEncrypt64MBWithDirectGCM128(b *testing.B) { benchEncrypt("64MB", "DirectGCM128", b) }
+
+func BenchmarkEncrypt1BWithDirectCBC128(b *testing.B)   { benchEncrypt("1B", "DirectCBC128", b) }
+func BenchmarkEncrypt64BWithDirectCBC128(b *testing.B)  { benchEncrypt("64B", "DirectCBC128", b) }
+func BenchmarkEncrypt1KBWithDirectCBC128(b *testing.B)  { benchEncrypt("1KB", "DirectCBC128", b) }
+func BenchmarkEncrypt64KBWithDirectCBC128(b *testing.B) { benchEncrypt("64KB", "DirectCBC128", b) }
+func BenchmarkEncrypt1MBWithDirectCBC128(b *testing.B)  { benchEncrypt("1MB", "DirectCBC128", b) }
+func BenchmarkEncrypt64MBWithDirectCBC128(b *testing.B) { benchEncrypt("64MB", "DirectCBC128", b) }
+
+func BenchmarkEncrypt1BWithDirectGCM256(b *testing.B)   { benchEncrypt("1B", "DirectGCM256", b) }
+func BenchmarkEncrypt64BWithDirectGCM256(b *testing.B)  { benchEncrypt("64B", "DirectGCM256", b) }
+func BenchmarkEncrypt1KBWithDirectGCM256(b *testing.B)  { benchEncrypt("1KB", "DirectGCM256", b) }
+func BenchmarkEncrypt64KBWithDirectGCM256(b *testing.B) { benchEncrypt("64KB", "DirectGCM256", b) }
+func BenchmarkEncrypt1MBWithDirectGCM256(b *testing.B)  { benchEncrypt("1MB", "DirectGCM256", b) }
+func BenchmarkEncrypt64MBWithDirectGCM256(b *testing.B) { benchEncrypt("64MB", "DirectGCM256", b) }
+
+func BenchmarkEncrypt1BWithDirectCBC256(b *testing.B)   { benchEncrypt("1B", "DirectCBC256", b) }
+func BenchmarkEncrypt64BWithDirectCBC256(b *testing.B)  { benchEncrypt("64B", "DirectCBC256", b) }
+func BenchmarkEncrypt1KBWithDirectCBC256(b *testing.B)  { benchEncrypt("1KB", "DirectCBC256", b) }
+func BenchmarkEncrypt64KBWithDirectCBC256(b *testing.B) { benchEncrypt("64KB", "DirectCBC256", b) }
+func BenchmarkEncrypt1MBWithDirectCBC256(b *testing.B)  { benchEncrypt("1MB", "DirectCBC256", b) }
+func BenchmarkEncrypt64MBWithDirectCBC256(b *testing.B) { benchEncrypt("64MB", "DirectCBC256", b) }
+
+func BenchmarkEncrypt1BWithAESKWAndGCM128(b *testing.B)   { benchEncrypt("1B", "AESKWAndGCM128", b) }
+func BenchmarkEncrypt64BWithAESKWAndGCM128(b *testing.B)  { benchEncrypt("64B", "AESKWAndGCM128", b) }
+func BenchmarkEncrypt1KBWithAESKWAndGCM128(b *testing.B)  { benchEncrypt("1KB", "AESKWAndGCM128", b) }
+func BenchmarkEncrypt64KBWithAESKWAndGCM128(b *testing.B) { benchEncrypt("64KB", "AESKWAndGCM128", b) }
+func BenchmarkEncrypt1MBWithAESKWAndGCM128(b *testing.B)  { benchEncrypt("1MB", "AESKWAndGCM128", b) }
+func BenchmarkEncrypt64MBWithAESKWAndGCM128(b *testing.B) { benchEncrypt("64MB", "AESKWAndGCM128", b) }
+
+func BenchmarkEncrypt1BWithAESKWAndCBC256(b *testing.B)   { benchEncrypt("1B", "AESKWAndCBC256", b) }
+func BenchmarkEncrypt64BWithAESKWAndCBC256(b *testing.B)  { benchEncrypt("64B", "AESKWAndCBC256", b) }
+func BenchmarkEncrypt1KBWithAESKWAndCBC256(b *testing.B)  { benchEncrypt("1KB", "AESKWAndCBC256", b) }
+func BenchmarkEncrypt64KBWithAESKWAndCBC256(b *testing.B) { benchEncrypt("64KB", "AESKWAndCBC256", b) }
+func BenchmarkEncrypt1MBWithAESKWAndCBC256(b *testing.B)  { benchEncrypt("1MB", "AESKWAndCBC256", b) }
+func BenchmarkEncrypt64MBWithAESKWAndCBC256(b *testing.B) { benchEncrypt("64MB", "AESKWAndCBC256", b) }
+
+func BenchmarkEncrypt1BWithECDHOnP256AndGCM128(b *testing.B) {
+	benchEncrypt("1B", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkEncrypt64BWithECDHOnP256AndGCM128(b *testing.B) {
+	benchEncrypt("64B", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkEncrypt1KBWithECDHOnP256AndGCM128(b *testing.B) {
+	benchEncrypt("1KB", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkEncrypt64KBWithECDHOnP256AndGCM128(b *testing.B) {
+	benchEncrypt("64KB", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkEncrypt1MBWithECDHOnP256AndGCM128(b *testing.B) {
+	benchEncrypt("1MB", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkEncrypt64MBWithECDHOnP256AndGCM128(b *testing.B) {
+	benchEncrypt("64MB", "ECDHOnP256AndGCM128", b)
+}
+
+func BenchmarkEncrypt1BWithECDHOnP384AndGCM128(b *testing.B) {
+	benchEncrypt("1B", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkEncrypt64BWithECDHOnP384AndGCM128(b *testing.B) {
+	benchEncrypt("64B", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkEncrypt1KBWithECDHOnP384AndGCM128(b *testing.B) {
+	benchEncrypt("1KB", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkEncrypt64KBWithECDHOnP384AndGCM128(b *testing.B) {
+	benchEncrypt("64KB", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkEncrypt1MBWithECDHOnP384AndGCM128(b *testing.B) {
+	benchEncrypt("1MB", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkEncrypt64MBWithECDHOnP384AndGCM128(b *testing.B) {
+	benchEncrypt("64MB", "ECDHOnP384AndGCM128", b)
+}
+
+func BenchmarkEncrypt1BWithECDHOnP521AndGCM128(b *testing.B) {
+	benchEncrypt("1B", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkEncrypt64BWithECDHOnP521AndGCM128(b *testing.B) {
+	benchEncrypt("64B", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkEncrypt1KBWithECDHOnP521AndGCM128(b *testing.B) {
+	benchEncrypt("1KB", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkEncrypt64KBWithECDHOnP521AndGCM128(b *testing.B) {
+	benchEncrypt("64KB", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkEncrypt1MBWithECDHOnP521AndGCM128(b *testing.B) {
+	benchEncrypt("1MB", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkEncrypt64MBWithECDHOnP521AndGCM128(b *testing.B) {
+	benchEncrypt("64MB", "ECDHOnP521AndGCM128", b)
+}
+
+func benchEncrypt(chunkKey, primKey string, b *testing.B) {
+	data, ok := chunks[chunkKey]
+	if !ok {
+		b.Fatalf("unknown chunk size %s", chunkKey)
+	}
+
+	enc, ok := encrypters[primKey]
+	if !ok {
+		b.Fatalf("unknown encrypter %s", primKey)
+	}
+
+	b.SetBytes(int64(len(data)))
+	for i := 0; i < b.N; i++ {
+		enc.Encrypt(data)
+	}
+}
+
+var (
+	decryptionKeys = map[string]interface{}{
+		"OAEPAndGCM": rsaTestKey,
+		"PKCSAndGCM": rsaTestKey,
+		"OAEPAndCBC": rsaTestKey,
+		"PKCSAndCBC": rsaTestKey,
+
+		"DirectGCM128": symKey,
+		"DirectCBC128": symKey,
+		"DirectGCM256": symKey,
+		"DirectCBC256": symKey,
+
+		"AESKWAndGCM128": symKey,
+		"AESKWAndCBC256": symKey,
+
+		"ECDHOnP256AndGCM128": ecTestKey256,
+		"ECDHOnP384AndGCM128": ecTestKey384,
+		"ECDHOnP521AndGCM128": ecTestKey521,
+	}
+)
+
+func BenchmarkDecrypt1BWithOAEPAndGCM(b *testing.B)   { benchDecrypt("1B", "OAEPAndGCM", b) }
+func BenchmarkDecrypt64BWithOAEPAndGCM(b *testing.B)  { benchDecrypt("64B", "OAEPAndGCM", b) }
+func BenchmarkDecrypt1KBWithOAEPAndGCM(b *testing.B)  { benchDecrypt("1KB", "OAEPAndGCM", b) }
+func BenchmarkDecrypt64KBWithOAEPAndGCM(b *testing.B) { benchDecrypt("64KB", "OAEPAndGCM", b) }
+func BenchmarkDecrypt1MBWithOAEPAndGCM(b *testing.B)  { benchDecrypt("1MB", "OAEPAndGCM", b) }
+func BenchmarkDecrypt64MBWithOAEPAndGCM(b *testing.B) { benchDecrypt("64MB", "OAEPAndGCM", b) }
+
+func BenchmarkDecrypt1BWithPKCSAndGCM(b *testing.B)   { benchDecrypt("1B", "PKCSAndGCM", b) }
+func BenchmarkDecrypt64BWithPKCSAndGCM(b *testing.B)  { benchDecrypt("64B", "PKCSAndGCM", b) }
+func BenchmarkDecrypt1KBWithPKCSAndGCM(b *testing.B)  { benchDecrypt("1KB", "PKCSAndGCM", b) }
+func BenchmarkDecrypt64KBWithPKCSAndGCM(b *testing.B) { benchDecrypt("64KB", "PKCSAndGCM", b) }
+func BenchmarkDecrypt1MBWithPKCSAndGCM(b *testing.B)  { benchDecrypt("1MB", "PKCSAndGCM", b) }
+func BenchmarkDecrypt64MBWithPKCSAndGCM(b *testing.B) { benchDecrypt("64MB", "PKCSAndGCM", b) }
+
+func BenchmarkDecrypt1BWithOAEPAndCBC(b *testing.B)   { benchDecrypt("1B", "OAEPAndCBC", b) }
+func BenchmarkDecrypt64BWithOAEPAndCBC(b *testing.B)  { benchDecrypt("64B", "OAEPAndCBC", b) }
+func BenchmarkDecrypt1KBWithOAEPAndCBC(b *testing.B)  { benchDecrypt("1KB", "OAEPAndCBC", b) }
+func BenchmarkDecrypt64KBWithOAEPAndCBC(b *testing.B) { benchDecrypt("64KB", "OAEPAndCBC", b) }
+func BenchmarkDecrypt1MBWithOAEPAndCBC(b *testing.B)  { benchDecrypt("1MB", "OAEPAndCBC", b) }
+func BenchmarkDecrypt64MBWithOAEPAndCBC(b *testing.B) { benchDecrypt("64MB", "OAEPAndCBC", b) }
+
+func BenchmarkDecrypt1BWithPKCSAndCBC(b *testing.B)   { benchDecrypt("1B", "PKCSAndCBC", b) }
+func BenchmarkDecrypt64BWithPKCSAndCBC(b *testing.B)  { benchDecrypt("64B", "PKCSAndCBC", b) }
+func BenchmarkDecrypt1KBWithPKCSAndCBC(b *testing.B)  { benchDecrypt("1KB", "PKCSAndCBC", b) }
+func BenchmarkDecrypt64KBWithPKCSAndCBC(b *testing.B) { benchDecrypt("64KB", "PKCSAndCBC", b) }
+func BenchmarkDecrypt1MBWithPKCSAndCBC(b *testing.B)  { benchDecrypt("1MB", "PKCSAndCBC", b) }
+func BenchmarkDecrypt64MBWithPKCSAndCBC(b *testing.B) { benchDecrypt("64MB", "PKCSAndCBC", b) }
+
+func BenchmarkDecrypt1BWithDirectGCM128(b *testing.B)   { benchDecrypt("1B", "DirectGCM128", b) }
+func BenchmarkDecrypt64BWithDirectGCM128(b *testing.B)  { benchDecrypt("64B", "DirectGCM128", b) }
+func BenchmarkDecrypt1KBWithDirectGCM128(b *testing.B)  { benchDecrypt("1KB", "DirectGCM128", b) }
+func BenchmarkDecrypt64KBWithDirectGCM128(b *testing.B) { benchDecrypt("64KB", "DirectGCM128", b) }
+func BenchmarkDecrypt1MBWithDirectGCM128(b *testing.B)  { benchDecrypt("1MB", "DirectGCM128", b) }
+func BenchmarkDecrypt64MBWithDirectGCM128(b *testing.B) { benchDecrypt("64MB", "DirectGCM128", b) }
+
+func BenchmarkDecrypt1BWithDirectCBC128(b *testing.B)   { benchDecrypt("1B", "DirectCBC128", b) }
+func BenchmarkDecrypt64BWithDirectCBC128(b *testing.B)  { benchDecrypt("64B", "DirectCBC128", b) }
+func BenchmarkDecrypt1KBWithDirectCBC128(b *testing.B)  { benchDecrypt("1KB", "DirectCBC128", b) }
+func BenchmarkDecrypt64KBWithDirectCBC128(b *testing.B) { benchDecrypt("64KB", "DirectCBC128", b) }
+func BenchmarkDecrypt1MBWithDirectCBC128(b *testing.B)  { benchDecrypt("1MB", "DirectCBC128", b) }
+func BenchmarkDecrypt64MBWithDirectCBC128(b *testing.B) { benchDecrypt("64MB", "DirectCBC128", b) }
+
+func BenchmarkDecrypt1BWithDirectGCM256(b *testing.B)   { benchDecrypt("1B", "DirectGCM256", b) }
+func BenchmarkDecrypt64BWithDirectGCM256(b *testing.B)  { benchDecrypt("64B", "DirectGCM256", b) }
+func BenchmarkDecrypt1KBWithDirectGCM256(b *testing.B)  { benchDecrypt("1KB", "DirectGCM256", b) }
+func BenchmarkDecrypt64KBWithDirectGCM256(b *testing.B) { benchDecrypt("64KB", "DirectGCM256", b) }
+func BenchmarkDecrypt1MBWithDirectGCM256(b *testing.B)  { benchDecrypt("1MB", "DirectGCM256", b) }
+func BenchmarkDecrypt64MBWithDirectGCM256(b *testing.B) { benchDecrypt("64MB", "DirectGCM256", b) }
+
+func BenchmarkDecrypt1BWithDirectCBC256(b *testing.B)   { benchDecrypt("1B", "DirectCBC256", b) }
+func BenchmarkDecrypt64BWithDirectCBC256(b *testing.B)  { benchDecrypt("64B", "DirectCBC256", b) }
+func BenchmarkDecrypt1KBWithDirectCBC256(b *testing.B)  { benchDecrypt("1KB", "DirectCBC256", b) }
+func BenchmarkDecrypt64KBWithDirectCBC256(b *testing.B) { benchDecrypt("64KB", "DirectCBC256", b) }
+func BenchmarkDecrypt1MBWithDirectCBC256(b *testing.B)  { benchDecrypt("1MB", "DirectCBC256", b) }
+func BenchmarkDecrypt64MBWithDirectCBC256(b *testing.B) { benchDecrypt("64MB", "DirectCBC256", b) }
+
+func BenchmarkDecrypt1BWithAESKWAndGCM128(b *testing.B)   { benchDecrypt("1B", "AESKWAndGCM128", b) }
+func BenchmarkDecrypt64BWithAESKWAndGCM128(b *testing.B)  { benchDecrypt("64B", "AESKWAndGCM128", b) }
+func BenchmarkDecrypt1KBWithAESKWAndGCM128(b *testing.B)  { benchDecrypt("1KB", "AESKWAndGCM128", b) }
+func BenchmarkDecrypt64KBWithAESKWAndGCM128(b *testing.B) { benchDecrypt("64KB", "AESKWAndGCM128", b) }
+func BenchmarkDecrypt1MBWithAESKWAndGCM128(b *testing.B)  { benchDecrypt("1MB", "AESKWAndGCM128", b) }
+func BenchmarkDecrypt64MBWithAESKWAndGCM128(b *testing.B) { benchDecrypt("64MB", "AESKWAndGCM128", b) }
+
+func BenchmarkDecrypt1BWithAESKWAndCBC256(b *testing.B)   { benchDecrypt("1B", "AESKWAndCBC256", b) }
+func BenchmarkDecrypt64BWithAESKWAndCBC256(b *testing.B)  { benchDecrypt("64B", "AESKWAndCBC256", b) }
+func BenchmarkDecrypt1KBWithAESKWAndCBC256(b *testing.B)  { benchDecrypt("1KB", "AESKWAndCBC256", b) }
+func BenchmarkDecrypt64KBWithAESKWAndCBC256(b *testing.B) { benchDecrypt("64KB", "AESKWAndCBC256", b) }
+func BenchmarkDecrypt1MBWithAESKWAndCBC256(b *testing.B)  { benchDecrypt("1MB", "AESKWAndCBC256", b) }
+func BenchmarkDecrypt64MBWithAESKWAndCBC256(b *testing.B) { benchDecrypt("64MB", "AESKWAndCBC256", b) }
+
+func BenchmarkDecrypt1BWithECDHOnP256AndGCM128(b *testing.B) {
+	benchDecrypt("1B", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkDecrypt64BWithECDHOnP256AndGCM128(b *testing.B) {
+	benchDecrypt("64B", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkDecrypt1KBWithECDHOnP256AndGCM128(b *testing.B) {
+	benchDecrypt("1KB", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkDecrypt64KBWithECDHOnP256AndGCM128(b *testing.B) {
+	benchDecrypt("64KB", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkDecrypt1MBWithECDHOnP256AndGCM128(b *testing.B) {
+	benchDecrypt("1MB", "ECDHOnP256AndGCM128", b)
+}
+func BenchmarkDecrypt64MBWithECDHOnP256AndGCM128(b *testing.B) {
+	benchDecrypt("64MB", "ECDHOnP256AndGCM128", b)
+}
+
+func BenchmarkDecrypt1BWithECDHOnP384AndGCM128(b *testing.B) {
+	benchDecrypt("1B", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkDecrypt64BWithECDHOnP384AndGCM128(b *testing.B) {
+	benchDecrypt("64B", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkDecrypt1KBWithECDHOnP384AndGCM128(b *testing.B) {
+	benchDecrypt("1KB", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkDecrypt64KBWithECDHOnP384AndGCM128(b *testing.B) {
+	benchDecrypt("64KB", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkDecrypt1MBWithECDHOnP384AndGCM128(b *testing.B) {
+	benchDecrypt("1MB", "ECDHOnP384AndGCM128", b)
+}
+func BenchmarkDecrypt64MBWithECDHOnP384AndGCM128(b *testing.B) {
+	benchDecrypt("64MB", "ECDHOnP384AndGCM128", b)
+}
+
+func BenchmarkDecrypt1BWithECDHOnP521AndGCM128(b *testing.B) {
+	benchDecrypt("1B", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkDecrypt64BWithECDHOnP521AndGCM128(b *testing.B) {
+	benchDecrypt("64B", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkDecrypt1KBWithECDHOnP521AndGCM128(b *testing.B) {
+	benchDecrypt("1KB", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkDecrypt64KBWithECDHOnP521AndGCM128(b *testing.B) {
+	benchDecrypt("64KB", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkDecrypt1MBWithECDHOnP521AndGCM128(b *testing.B) {
+	benchDecrypt("1MB", "ECDHOnP521AndGCM128", b)
+}
+func BenchmarkDecrypt64MBWithECDHOnP521AndGCM128(b *testing.B) {
+	benchDecrypt("64MB", "ECDHOnP521AndGCM128", b)
+}
+
+func benchDecrypt(chunkKey, primKey string, b *testing.B) {
+	chunk, ok := chunks[chunkKey]
+	if !ok {
+		b.Fatalf("unknown chunk size %s", chunkKey)
+	}
+
+	enc, ok := encrypters[primKey]
+	if !ok {
+		b.Fatalf("unknown encrypter %s", primKey)
+	}
+
+	dec, ok := decryptionKeys[primKey]
+	if !ok {
+		b.Fatalf("unknown decryption key %s", primKey)
+	}
+
+	data, err := enc.Encrypt(chunk)
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	b.SetBytes(int64(len(chunk)))
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		data.Decrypt(dec)
+	}
+}
+
+func mustEncrypter(keyAlg KeyAlgorithm, encAlg ContentEncryption, encryptionKey interface{}) Encrypter {
+	enc, err := NewEncrypter(keyAlg, encAlg, encryptionKey)
+	if err != nil {
+		panic(err)
+	}
+	return enc
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/doc.go b/Godeps/_workspace/src/github.com/square/go-jose/doc.go
new file mode 100644
index 00000000..f96acaa2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/doc.go
@@ -0,0 +1,26 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+
+Package jose aims to provide an implementation of the Javascript Object Signing
+and Encryption set of standards. For the moment, it mainly focuses on
+encryption and signing based on the JSON Web Encryption and JSON Web Signature
+standards.  The library supports both the compact and full serialization
+formats, and has optional support for multiple recipients.
+
+*/
+package jose
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/doc_test.go b/Godeps/_workspace/src/github.com/square/go-jose/doc_test.go
new file mode 100644
index 00000000..50468295
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/doc_test.go
@@ -0,0 +1,226 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"crypto/ecdsa"
+	"crypto/rand"
+	"crypto/rsa"
+	"fmt"
+)
+
+// Dummy encrypter for use in examples
+var encrypter, _ = NewEncrypter(DIRECT, A128GCM, []byte{})
+
+func Example_jWE() {
+	// Generate a public/private key pair to use for this example. The library
+	// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
+	// that can be used to load keys from PEM/DER-encoded data.
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		panic(err)
+	}
+
+	// Instantiate an encrypter using RSA-OAEP with AES128-GCM. An error would
+	// indicate that the selected algorithm(s) are not currently supported.
+	publicKey := &privateKey.PublicKey
+	encrypter, err := NewEncrypter(RSA_OAEP, A128GCM, publicKey)
+	if err != nil {
+		panic(err)
+	}
+
+	// Encrypt a sample plaintext. Calling the encrypter returns an encrypted
+	// JWE object, which can then be serialized for output afterwards. An error
+	// would indicate a problem in an underlying cryptographic primitive.
+	var plaintext = []byte("Lorem ipsum dolor sit amet")
+	object, err := encrypter.Encrypt(plaintext)
+	if err != nil {
+		panic(err)
+	}
+
+	// Serialize the encrypted object using the full serialization format.
+	// Alternatively you can also use the compact format here by calling
+	// object.CompactSerialize() instead.
+	serialized := object.FullSerialize()
+
+	// Parse the serialized, encrypted JWE object. An error would indicate that
+	// the given input did not represent a valid message.
+	object, err = ParseEncrypted(serialized)
+	if err != nil {
+		panic(err)
+	}
+
+	// Now we can decrypt and get back our original plaintext. An error here
+	// would indicate the the message failed to decrypt, e.g. because the auth
+	// tag was broken or the message was tampered with.
+	decrypted, err := object.Decrypt(privateKey)
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf(string(decrypted))
+	// output: Lorem ipsum dolor sit amet
+}
+
+func Example_jWS() {
+	// Generate a public/private key pair to use for this example. The library
+	// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
+	// that can be used to load keys from PEM/DER-encoded data.
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		panic(err)
+	}
+
+	// Instantiate a signer using RSASSA-PSS (SHA512) with the given private key.
+	signer, err := NewSigner(PS512, privateKey)
+	if err != nil {
+		panic(err)
+	}
+
+	// Sign a sample payload. Calling the signer returns a protected JWS object,
+	// which can then be serialized for output afterwards. An error would
+	// indicate a problem in an underlying cryptographic primitive.
+	var payload = []byte("Lorem ipsum dolor sit amet")
+	object, err := signer.Sign(payload)
+	if err != nil {
+		panic(err)
+	}
+
+	// Serialize the encrypted object using the full serialization format.
+	// Alternatively you can also use the compact format here by calling
+	// object.CompactSerialize() instead.
+	serialized := object.FullSerialize()
+
+	// Parse the serialized, protected JWS object. An error would indicate that
+	// the given input did not represent a valid message.
+	object, err = ParseSigned(serialized)
+	if err != nil {
+		panic(err)
+	}
+
+	// Now we can verify the signature on the payload. An error here would
+	// indicate the the message failed to verify, e.g. because the signature was
+	// broken or the message was tampered with.
+	output, err := object.Verify(&privateKey.PublicKey)
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf(string(output))
+	// output: Lorem ipsum dolor sit amet
+}
+
+func ExampleNewEncrypter_publicKey() {
+	var publicKey *rsa.PublicKey
+
+	// Instantiate an encrypter using RSA-OAEP with AES128-GCM.
+	NewEncrypter(RSA_OAEP, A128GCM, publicKey)
+
+	// Instantiate an encrypter using RSA-PKCS1v1.5 with AES128-CBC+HMAC.
+	NewEncrypter(RSA1_5, A128CBC_HS256, publicKey)
+}
+
+func ExampleNewEncrypter_symmetric() {
+	var sharedKey []byte
+
+	// Instantiate an encrypter using AES128-GCM with AES-GCM key wrap.
+	NewEncrypter(A128GCMKW, A128GCM, sharedKey)
+
+	// Instantiate an encrypter using AES256-GCM directly, w/o key wrapping.
+	NewEncrypter(DIRECT, A256GCM, sharedKey)
+}
+
+func ExampleNewSigner_publicKey() {
+	var rsaPrivateKey *rsa.PrivateKey
+	var ecdsaPrivateKey *ecdsa.PrivateKey
+
+	// Instantiate a signer using RSA-PKCS#1v1.5 with SHA-256.
+	NewSigner(RS256, rsaPrivateKey)
+
+	// Instantiate a signer using ECDSA with SHA-384.
+	NewSigner(ES384, ecdsaPrivateKey)
+}
+
+func ExampleNewSigner_symmetric() {
+	var sharedKey []byte
+
+	// Instantiate an signer using HMAC-SHA256.
+	NewSigner(HS256, sharedKey)
+
+	// Instantiate an signer using HMAC-SHA512.
+	NewSigner(HS512, sharedKey)
+}
+
+func ExampleNewMultiEncrypter() {
+	var publicKey *rsa.PublicKey
+	var sharedKey []byte
+
+	// Instantiate an encrypter using AES-GCM.
+	encrypter, err := NewMultiEncrypter(A128GCM)
+	if err != nil {
+		panic(err)
+	}
+
+	// Add a recipient using a shared key with AES-GCM key wap
+	err = encrypter.AddRecipient(A128GCMKW, sharedKey)
+	if err != nil {
+		panic(err)
+	}
+
+	// Add a recipient using an RSA public key with RSA-OAEP
+	err = encrypter.AddRecipient(RSA_OAEP, publicKey)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func ExampleNewMultiSigner() {
+	var privateKey *rsa.PrivateKey
+	var sharedKey []byte
+
+	// Instantiate a signer for multiple recipients.
+	signer := NewMultiSigner()
+
+	// Add a recipient using a shared key with HMAC-SHA256
+	err := signer.AddRecipient(HS256, sharedKey)
+	if err != nil {
+		panic(err)
+	}
+
+	// Add a recipient using an RSA private key with RSASSA-PSS with SHA384
+	err = signer.AddRecipient(PS384, privateKey)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func ExampleEncrypter_encrypt() {
+	// Encrypt a plaintext in order to get an encrypted JWE object.
+	var plaintext = []byte("This is a secret message")
+
+	encrypter.Encrypt(plaintext)
+}
+
+func ExampleEncrypter_encryptWithAuthData() {
+	// Encrypt a plaintext in order to get an encrypted JWE object. Also attach
+	// some additional authenticated data (AAD) to the object. Note that objects
+	// with attached AAD can only be represented using full serialization.
+	var plaintext = []byte("This is a secret message")
+	var aad = []byte("This is authenticated, but public data")
+
+	encrypter.EncryptWithAuthData(plaintext, aad)
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/encoding.go b/Godeps/_workspace/src/github.com/square/go-jose/encoding.go
new file mode 100644
index 00000000..88f0284e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/encoding.go
@@ -0,0 +1,192 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"bytes"
+	"compress/flate"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/json"
+	"io"
+	"math/big"
+	"regexp"
+	"strings"
+)
+
+var stripWhitespaceRegex = regexp.MustCompile("\\s")
+
+// Url-safe base64 encode that strips padding
+func base64URLEncode(data []byte) string {
+	var result = base64.URLEncoding.EncodeToString(data)
+	return strings.TrimRight(result, "=")
+}
+
+// Url-safe base64 decoder that adds padding
+func base64URLDecode(data string) ([]byte, error) {
+	var missing = (4 - len(data)%4) % 4
+	data += strings.Repeat("=", missing)
+	return base64.URLEncoding.DecodeString(data)
+}
+
+// Helper function to serialize known-good objects.
+// Precondition: value is not a nil pointer.
+func mustSerializeJSON(value interface{}) []byte {
+	out, err := json.Marshal(value)
+	if err != nil {
+		panic(err)
+	}
+	// We never want to serialize the top-level value "null," since it's not a
+	// valid JOSE message. But if a caller passes in a nil pointer to this method,
+	// json.Marshal will happily serialize it as the top-level value "null". If
+	// that value is then embedded in another operation, for instance by being
+	// base64-encoded and fed as input to a signing algorithm
+	// (https://github.com/square/go-jose/issues/22), the result will be
+	// incorrect. Because this method is intended for known-good objects, and a nil
+	// pointer is not a known-good object, we are free to panic in this case.
+	// Note: It's not possible to directly check whether the data pointed at by an
+	// interface is a nil pointer, so we do this hacky workaround.
+	// https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I
+	if string(out) == "null" {
+		panic("Tried to serialize a nil pointer.")
+	}
+	return out
+}
+
+// Strip all newlines and whitespace
+func stripWhitespace(data string) string {
+	return stripWhitespaceRegex.ReplaceAllString(data, "")
+}
+
+// Perform compression based on algorithm
+func compress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
+	switch algorithm {
+	case DEFLATE:
+		return deflate(input)
+	default:
+		return nil, ErrUnsupportedAlgorithm
+	}
+}
+
+// Perform decompression based on algorithm
+func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
+	switch algorithm {
+	case DEFLATE:
+		return inflate(input)
+	default:
+		return nil, ErrUnsupportedAlgorithm
+	}
+}
+
+// Compress with DEFLATE
+func deflate(input []byte) ([]byte, error) {
+	output := new(bytes.Buffer)
+
+	// Writing to byte buffer, err is always nil
+	writer, _ := flate.NewWriter(output, 1)
+	_, _ = io.Copy(writer, bytes.NewBuffer(input))
+
+	err := writer.Close()
+	return output.Bytes(), err
+}
+
+// Decompress with DEFLATE
+func inflate(input []byte) ([]byte, error) {
+	output := new(bytes.Buffer)
+	reader := flate.NewReader(bytes.NewBuffer(input))
+
+	_, err := io.Copy(output, reader)
+	if err != nil {
+		return nil, err
+	}
+
+	err = reader.Close()
+	return output.Bytes(), err
+}
+
+// byteBuffer represents a slice of bytes that can be serialized to url-safe base64.
+type byteBuffer struct {
+	data []byte
+}
+
+func newBuffer(data []byte) *byteBuffer {
+	if data == nil {
+		return nil
+	}
+	return &byteBuffer{
+		data: data,
+	}
+}
+
+func newFixedSizeBuffer(data []byte, length int) *byteBuffer {
+	if len(data) > length {
+		panic("square/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
+	}
+	pad := make([]byte, length-len(data))
+	return newBuffer(append(pad, data...))
+}
+
+func newBufferFromInt(num uint64) *byteBuffer {
+	data := make([]byte, 8)
+	binary.BigEndian.PutUint64(data, num)
+	return newBuffer(bytes.TrimLeft(data, "\x00"))
+}
+
+func (b *byteBuffer) MarshalJSON() ([]byte, error) {
+	return json.Marshal(b.base64())
+}
+
+func (b *byteBuffer) UnmarshalJSON(data []byte) error {
+	var encoded string
+	err := json.Unmarshal(data, &encoded)
+	if err != nil {
+		return err
+	}
+
+	if encoded == "" {
+		return nil
+	}
+
+	decoded, err := base64URLDecode(encoded)
+	if err != nil {
+		return err
+	}
+
+	*b = *newBuffer(decoded)
+
+	return nil
+}
+
+func (b *byteBuffer) base64() string {
+	return base64URLEncode(b.data)
+}
+
+func (b *byteBuffer) bytes() []byte {
+	// Handling nil here allows us to transparently handle nil slices when serializing.
+	if b == nil {
+		return nil
+	}
+	return b.data
+}
+
+func (b byteBuffer) bigInt() *big.Int {
+	return new(big.Int).SetBytes(b.data)
+}
+
+func (b byteBuffer) toInt() int {
+	return int(b.bigInt().Int64())
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/encoding_test.go b/Godeps/_workspace/src/github.com/square/go-jose/encoding_test.go
new file mode 100644
index 00000000..e2f8d979
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/encoding_test.go
@@ -0,0 +1,173 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+func TestBase64URLEncode(t *testing.T) {
+	// Test arrays with various sizes
+	if base64URLEncode([]byte{}) != "" {
+		t.Error("failed to encode empty array")
+	}
+
+	if base64URLEncode([]byte{0}) != "AA" {
+		t.Error("failed to encode [0x00]")
+	}
+
+	if base64URLEncode([]byte{0, 1}) != "AAE" {
+		t.Error("failed to encode [0x00, 0x01]")
+	}
+
+	if base64URLEncode([]byte{0, 1, 2}) != "AAEC" {
+		t.Error("failed to encode [0x00, 0x01, 0x02]")
+	}
+
+	if base64URLEncode([]byte{0, 1, 2, 3}) != "AAECAw" {
+		t.Error("failed to encode [0x00, 0x01, 0x02, 0x03]")
+	}
+}
+
+func TestBase64URLDecode(t *testing.T) {
+	// Test arrays with various sizes
+	val, err := base64URLDecode("")
+	if err != nil || !bytes.Equal(val, []byte{}) {
+		t.Error("failed to decode empty array")
+	}
+
+	val, err = base64URLDecode("AA")
+	if err != nil || !bytes.Equal(val, []byte{0}) {
+		t.Error("failed to decode [0x00]")
+	}
+
+	val, err = base64URLDecode("AAE")
+	if err != nil || !bytes.Equal(val, []byte{0, 1}) {
+		t.Error("failed to decode [0x00, 0x01]")
+	}
+
+	val, err = base64URLDecode("AAEC")
+	if err != nil || !bytes.Equal(val, []byte{0, 1, 2}) {
+		t.Error("failed to decode [0x00, 0x01, 0x02]")
+	}
+
+	val, err = base64URLDecode("AAECAw")
+	if err != nil || !bytes.Equal(val, []byte{0, 1, 2, 3}) {
+		t.Error("failed to decode [0x00, 0x01, 0x02, 0x03]")
+	}
+}
+
+func TestDeflateRoundtrip(t *testing.T) {
+	original := []byte("Lorem ipsum dolor sit amet")
+
+	compressed, err := deflate(original)
+	if err != nil {
+		panic(err)
+	}
+
+	output, err := inflate(compressed)
+	if err != nil {
+		panic(err)
+	}
+
+	if bytes.Compare(output, original) != 0 {
+		t.Error("Input and output do not match")
+	}
+}
+
+func TestInvalidCompression(t *testing.T) {
+	_, err := compress("XYZ", []byte{})
+	if err == nil {
+		t.Error("should not accept invalid algorithm")
+	}
+
+	_, err = decompress("XYZ", []byte{})
+	if err == nil {
+		t.Error("should not accept invalid algorithm")
+	}
+
+	_, err = decompress(DEFLATE, []byte{1, 2, 3, 4})
+	if err == nil {
+		t.Error("should not accept invalid data")
+	}
+}
+
+func TestByteBufferTrim(t *testing.T) {
+	buf := newBufferFromInt(1)
+	if !bytes.Equal(buf.data, []byte{1}) {
+		t.Error("Byte buffer for integer '1' should contain [0x01]")
+	}
+
+	buf = newBufferFromInt(65537)
+	if !bytes.Equal(buf.data, []byte{1, 0, 1}) {
+		t.Error("Byte buffer for integer '65537' should contain [0x01, 0x00, 0x01]")
+	}
+}
+
+func TestFixedSizeBuffer(t *testing.T) {
+	data0 := []byte{}
+	data1 := []byte{1}
+	data2 := []byte{1, 2}
+	data3 := []byte{1, 2, 3}
+	data4 := []byte{1, 2, 3, 4}
+
+	buf0 := newFixedSizeBuffer(data0, 4)
+	buf1 := newFixedSizeBuffer(data1, 4)
+	buf2 := newFixedSizeBuffer(data2, 4)
+	buf3 := newFixedSizeBuffer(data3, 4)
+	buf4 := newFixedSizeBuffer(data4, 4)
+
+	if !bytes.Equal(buf0.data, []byte{0, 0, 0, 0}) {
+		t.Error("Invalid padded buffer for buf0")
+	}
+	if !bytes.Equal(buf1.data, []byte{0, 0, 0, 1}) {
+		t.Error("Invalid padded buffer for buf1")
+	}
+	if !bytes.Equal(buf2.data, []byte{0, 0, 1, 2}) {
+		t.Error("Invalid padded buffer for buf2")
+	}
+	if !bytes.Equal(buf3.data, []byte{0, 1, 2, 3}) {
+		t.Error("Invalid padded buffer for buf3")
+	}
+	if !bytes.Equal(buf4.data, []byte{1, 2, 3, 4}) {
+		t.Error("Invalid padded buffer for buf4")
+	}
+}
+
+func TestSerializeJSONRejectsNil(t *testing.T) {
+	defer func() {
+		r := recover()
+		if r == nil || !strings.Contains(r.(string), "nil pointer") {
+			t.Error("serialize function should not accept nil pointer")
+		}
+	}()
+
+	mustSerializeJSON(nil)
+}
+
+func TestFixedSizeBufferTooLarge(t *testing.T) {
+	defer func() {
+		r := recover()
+		if r == nil {
+			t.Error("should not be able to create fixed size buffer with oversized data")
+		}
+	}()
+
+	newFixedSizeBuffer(make([]byte, 2), 1)
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/jose-util/README.md b/Godeps/_workspace/src/github.com/square/go-jose/jose-util/README.md
new file mode 100644
index 00000000..8ce0f113
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/jose-util/README.md
@@ -0,0 +1,59 @@
+# JOSE CLI
+
+The `jose-util` command line utility allows for encryption, decryption, signing
+and verification of JWE/JWS messages. Its main purpose is to facilitate dealing
+with JWE/JWS messages when testing or debugging.
+
+## Usage
+
+The utility includes the subcommands `encrypt`, `decrypt`, `sign`, `verify` and
+`expand`. Examples for each command can be found below.
+
+Algorithms are selected via the `--alg` and `--enc` flags, which influence the
+`alg` and `enc` headers in respectively. For JWE, `--alg` specifies the key
+managment algorithm (e.g. `RSA-OAEP`) and `--enc` specifies the content
+encryption (e.g. `A128GCM`). For JWS, `--alg` specifies the signature algorithm
+(e.g. `PS256`).
+
+Input and output files can be specified via the `--in` and `--out` flags.
+Either flag can be omitted, in which case `jose-util` uses stdin/stdout for
+input/output respectively. By default each command will output a compact
+message, but it's possible to get the full serialization by supplying the
+`--full` flag.
+
+Keys are specified via the `--key` flag. Supported key types are naked RSA/EC
+keys and X.509 certificates with embedded RSA/EC keys. Keys must be in PEM
+or DER formats.
+
+## Examples
+
+### Encrypt
+
+Takes a plaintext as input, encrypts, and prints the encrypted message.
+
+    jose-util encrypt -k public-key.pem --alg RSA-OAEP --enc A128GCM
+
+### Decrypt
+
+Takes an encrypted message (JWE) as input, decrypts, and prints the plaintext.
+
+    jose-util decrypt -k private-key.pem
+
+### Sign
+
+Takes a payload as input, signs it, and prints the signed message with the embedded payload.
+
+    jose-util sign -k private-key.pem --alg PS256
+
+### Verify
+
+Reads a signed message (JWS), verifies it, and extracts the payload.
+
+    jose-util verify -k public-key.pem
+
+### Expand
+
+Expands a compact message to the full serialization format.
+
+    jose-util expand --format JWE   # Expands a compact JWE to full format
+    jose-util expand --format JWS   # Expands a compact JWS to full format
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/jose-util/jose-util.t b/Godeps/_workspace/src/github.com/square/go-jose/jose-util/jose-util.t
new file mode 100644
index 00000000..5e42ba4d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/jose-util/jose-util.t
@@ -0,0 +1,88 @@
+Set up test keys.
+
+  $ cat > rsa.pub <<EOF
+  > -----BEGIN PUBLIC KEY-----
+  > MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAslWybuiNYR7uOgKuvaBw
+  > qVk8saEutKhOAaW+3hWF65gJei+ZV8QFfYDxs9ZaRZlWAUMtncQPnw7ZQlXO9ogN
+  > 5cMcN50C6qMOOZzghK7danalhF5lUETC4Hk3Eisbi/PR3IfVyXaRmqL6X66MKj/J
+  > AKyD9NFIDVy52K8A198Jojnrw2+XXQW72U68fZtvlyl/BTBWQ9Re5JSTpEcVmpCR
+  > 8FrFc0RPMBm+G5dRs08vvhZNiTT2JACO5V+J5ZrgP3s5hnGFcQFZgDnXLInDUdoi
+  > 1MuCjaAU0ta8/08pHMijNix5kFofdPEB954MiZ9k4kQ5/utt02I9x2ssHqw71ojj
+  > vwIDAQAB
+  > -----END PUBLIC KEY-----
+  > EOF
+
+  $ cat > rsa.key <<EOF
+  > -----BEGIN RSA PRIVATE KEY-----
+  > MIIEogIBAAKCAQEAslWybuiNYR7uOgKuvaBwqVk8saEutKhOAaW+3hWF65gJei+Z
+  > V8QFfYDxs9ZaRZlWAUMtncQPnw7ZQlXO9ogN5cMcN50C6qMOOZzghK7danalhF5l
+  > UETC4Hk3Eisbi/PR3IfVyXaRmqL6X66MKj/JAKyD9NFIDVy52K8A198Jojnrw2+X
+  > XQW72U68fZtvlyl/BTBWQ9Re5JSTpEcVmpCR8FrFc0RPMBm+G5dRs08vvhZNiTT2
+  > JACO5V+J5ZrgP3s5hnGFcQFZgDnXLInDUdoi1MuCjaAU0ta8/08pHMijNix5kFof
+  > dPEB954MiZ9k4kQ5/utt02I9x2ssHqw71ojjvwIDAQABAoIBABrYDYDmXom1BzUS
+  > PE1s/ihvt1QhqA8nmn5i/aUeZkc9XofW7GUqq4zlwPxKEtKRL0IHY7Fw1s0hhhCX
+  > LA0uE7F3OiMg7lR1cOm5NI6kZ83jyCxxrRx1DUSO2nxQotfhPsDMbaDiyS4WxEts
+  > 0cp2SYJhdYd/jTH9uDfmt+DGwQN7Jixio1Dj3vwB7krDY+mdre4SFY7Gbk9VxkDg
+  > LgCLMoq52m+wYufP8CTgpKFpMb2/yJrbLhuJxYZrJ3qd/oYo/91k6v7xlBKEOkwD
+  > 2veGk9Dqi8YPNxaRktTEjnZb6ybhezat93+VVxq4Oem3wMwou1SfXrSUKtgM/p2H
+  > vfw/76ECgYEA2fNL9tC8u9M0wjA+kvvtDG96qO6O66Hksssy6RWInD+Iqk3MtHQt
+  > LeoCjvX+zERqwOb6SI6empk5pZ9E3/9vJ0dBqkxx3nqn4M/nRWnExGgngJsL959t
+  > f50cdxva8y1RjNhT4kCwTrupX/TP8lAG8SfG1Alo2VFR8iWd8hDQcTECgYEA0Xfj
+  > EgqAsVh4U0s3lFxKjOepEyp0G1Imty5J16SvcOEAD1Mrmz94aSSp0bYhXNVdbf7n
+  > Rk77htWC7SE29fGjOzZRS76wxj/SJHF+rktHB2Zt23k1jBeZ4uLMPMnGLY/BJ099
+  > 5DTGo0yU0rrPbyXosx+ukfQLAHFuggX4RNeM5+8CgYB7M1J/hGMLcUpjcs4MXCgV
+  > XXbiw2c6v1r9zmtK4odEe42PZ0cNwpY/XAZyNZAAe7Q0stxL44K4NWEmxC80x7lX
+  > ZKozz96WOpNnO16qGC3IMHAT/JD5Or+04WTT14Ue7UEp8qcIQDTpbJ9DxKk/eglS
+  > jH+SIHeKULOXw7fSu7p4IQKBgBnyVchIUMSnBtCagpn4DKwDjif3nEY+GNmb/D2g
+  > ArNiy5UaYk5qwEmV5ws5GkzbiSU07AUDh5ieHgetk5dHhUayZcOSLWeBRFCLVnvU
+  > i0nZYEZNb1qZGdDG8zGcdNXz9qMd76Qy/WAA/nZT+Zn1AiweAovFxQ8a/etRPf2Z
+  > DbU1AoGAHpCgP7B/4GTBe49H0AQueQHBn4RIkgqMy9xiMeR+U+U0vaY0TlfLhnX+
+  > 5PkNfkPXohXlfL7pxwZNYa6FZhCAubzvhKCdUASivkoGaIEk6g1VTVYS/eDVQ4CA
+  > slfl+elXtLq/l1kQ8C14jlHrQzSXx4PQvjDEnAmaHSJNz4mP9Fg=
+  > -----END RSA PRIVATE KEY-----
+  > EOF
+
+  $ cat > ec.pub <<EOF
+  > -----BEGIN PUBLIC KEY-----
+  > MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE9yoUEAgxTd9svwe9oPqjhcP+f2jcdTL2
+  > Wq8Aw2v9ht1dBy00tFRPNrCxFCkvMcJFhSPoDUV5NL7zfh3/psiSNYziGPrWEJYf
+  > gmYihjSeoOf0ru1erpBrTflImPrMftCy
+  > -----END PUBLIC KEY-----
+  > EOF
+
+  $ cat > ec.key <<EOF
+  > -----BEGIN EC PRIVATE KEY-----
+  > MIGkAgEBBDDvoj/bM1HokUjYWO/IDFs26Jo0GIFtU3tMQQu7ZabKscDMK3dZA0mK
+  > v97ij7BBFbCgBwYFK4EEACKhZANiAAT3KhQQCDFN32y/B72g+qOFw/5/aNx1MvZa
+  > rwDDa/2G3V0HLTS0VE82sLEUKS8xwkWFI+gNRXk0vvN+Hf+myJI1jOIY+tYQlh+C
+  > ZiKGNJ6g5/Su7V6ukGtN+UiY+sx+0LI=
+  > -----END EC PRIVATE KEY-----
+  > EOF
+
+Encrypt and then decrypt a test message (RSA).
+
+  $ echo "Lorem ipsum dolor sit amet" | 
+  > jose-util encrypt --alg RSA-OAEP --enc A128GCM --key rsa.pub |
+  > jose-util decrypt --key rsa.key
+  Lorem ipsum dolor sit amet
+
+Encrypt and then decrypt a test message (EC).
+
+  $ echo "Lorem ipsum dolor sit amet" | 
+  > jose-util encrypt --alg ECDH-ES+A128KW --enc A128GCM --key ec.pub |
+  > jose-util decrypt --key ec.key
+  Lorem ipsum dolor sit amet
+
+Sign and verify a test message (RSA).
+
+  $ echo "Lorem ipsum dolor sit amet" | 
+  > jose-util sign --alg PS256 --key rsa.key |
+  > jose-util verify --key rsa.pub
+  Lorem ipsum dolor sit amet
+
+Sign and verify a test message (EC).
+
+  $ echo "Lorem ipsum dolor sit amet" | 
+  > jose-util sign --alg ES384 --key ec.key |
+  > jose-util verify --key ec.pub
+  Lorem ipsum dolor sit amet
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/jose-util/main.go b/Godeps/_workspace/src/github.com/square/go-jose/jose-util/main.go
new file mode 100644
index 00000000..0eef53a5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/jose-util/main.go
@@ -0,0 +1,300 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/codegangsta/cli"
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/square/go-jose"
+)
+
+func main() {
+	app := cli.NewApp()
+	app.Name = "jose-util"
+	app.Usage = "command-line utility to deal with JOSE objects"
+	app.Version = "0.0.2"
+	app.Author = ""
+	app.Email = ""
+
+	app.Commands = []cli.Command{
+		{
+			Name:  "encrypt",
+			Usage: "encrypt a plaintext",
+			Flags: []cli.Flag{
+				cli.StringFlag{
+					Name:  "key, k",
+					Usage: "Path to key file (PEM/DER)",
+				},
+				cli.StringFlag{
+					Name:  "input, in",
+					Usage: "Path to input file (stdin if missing)",
+				},
+				cli.StringFlag{
+					Name:  "output, out",
+					Usage: "Path to output file (stdout if missing)",
+				},
+				cli.StringFlag{
+					Name:  "algorithm, alg",
+					Usage: "Key management algorithm (e.g. RSA-OAEP)",
+				},
+				cli.StringFlag{
+					Name:  "encryption, enc",
+					Usage: "Content encryption algorithm (e.g. A128GCM)",
+				},
+				cli.BoolFlag{
+					Name:  "full, f",
+					Usage: "Use full serialization format (instead of compact)",
+				},
+			},
+			Action: func(c *cli.Context) {
+				keyBytes, err := ioutil.ReadFile(requiredFlag(c, "key"))
+				exitOnError(err, "unable to read key file")
+
+				pub, err := jose.LoadPublicKey(keyBytes)
+				exitOnError(err, "unable to read public key")
+
+				alg := jose.KeyAlgorithm(requiredFlag(c, "alg"))
+				enc := jose.ContentEncryption(requiredFlag(c, "enc"))
+
+				crypter, err := jose.NewEncrypter(alg, enc, pub)
+				exitOnError(err, "unable to instantiate encrypter")
+
+				obj, err := crypter.Encrypt(readInput(c.String("input")))
+				exitOnError(err, "unable to encrypt")
+
+				var msg string
+				if c.Bool("full") {
+					msg = obj.FullSerialize()
+				} else {
+					msg, err = obj.CompactSerialize()
+					exitOnError(err, "unable to serialize message")
+				}
+
+				writeOutput(c.String("output"), []byte(msg))
+			},
+		},
+		{
+			Name:  "decrypt",
+			Usage: "decrypt a ciphertext",
+			Flags: []cli.Flag{
+				cli.StringFlag{
+					Name:  "key, k",
+					Usage: "Path to key file (PEM/DER)",
+				},
+				cli.StringFlag{
+					Name:  "input, in",
+					Usage: "Path to input file (stdin if missing)",
+				},
+				cli.StringFlag{
+					Name:  "output, out",
+					Usage: "Path to output file (stdout if missing)",
+				},
+			},
+			Action: func(c *cli.Context) {
+				keyBytes, err := ioutil.ReadFile(requiredFlag(c, "key"))
+				exitOnError(err, "unable to read private key")
+
+				priv, err := jose.LoadPrivateKey(keyBytes)
+				exitOnError(err, "unable to read private key")
+
+				obj, err := jose.ParseEncrypted(string(readInput(c.String("input"))))
+				exitOnError(err, "unable to parse message")
+
+				plaintext, err := obj.Decrypt(priv)
+				exitOnError(err, "unable to decrypt message")
+
+				writeOutput(c.String("output"), plaintext)
+			},
+		},
+		{
+			Name:  "sign",
+			Usage: "sign a plaintext",
+			Flags: []cli.Flag{
+				cli.StringFlag{
+					Name:  "algorithm, alg",
+					Usage: "Signing algorithm (e.g. PS256)",
+				},
+				cli.StringFlag{
+					Name:  "key, k",
+					Usage: "Path to key file (PEM/DER)",
+				},
+				cli.StringFlag{
+					Name:  "input, in",
+					Usage: "Path to input file (stdin if missing)",
+				},
+				cli.StringFlag{
+					Name:  "output, out",
+					Usage: "Path to output file (stdout if missing)",
+				},
+				cli.BoolFlag{
+					Name:  "full, f",
+					Usage: "Use full serialization format (instead of compact)",
+				},
+			},
+			Action: func(c *cli.Context) {
+				keyBytes, err := ioutil.ReadFile(requiredFlag(c, "key"))
+				exitOnError(err, "unable to read key file")
+
+				signingKey, err := jose.LoadPrivateKey(keyBytes)
+				exitOnError(err, "unable to read private key")
+
+				alg := jose.SignatureAlgorithm(requiredFlag(c, "algorithm"))
+				signer, err := jose.NewSigner(alg, signingKey)
+				exitOnError(err, "unable to make signer")
+
+				obj, err := signer.Sign(readInput(c.String("input")))
+				exitOnError(err, "unable to sign")
+
+				var msg string
+				if c.Bool("full") {
+					msg = obj.FullSerialize()
+				} else {
+					msg, err = obj.CompactSerialize()
+					exitOnError(err, "unable to serialize message")
+				}
+
+				writeOutput(c.String("output"), []byte(msg))
+			},
+		},
+		{
+			Name:  "verify",
+			Usage: "verify a signature",
+			Flags: []cli.Flag{
+				cli.StringFlag{
+					Name:  "key, k",
+					Usage: "Path to key file (PEM/DER)",
+				},
+				cli.StringFlag{
+					Name:  "input, in",
+					Usage: "Path to input file (stdin if missing)",
+				},
+				cli.StringFlag{
+					Name:  "output, out",
+					Usage: "Path to output file (stdout if missing)",
+				},
+			},
+			Action: func(c *cli.Context) {
+				keyBytes, err := ioutil.ReadFile(requiredFlag(c, "key"))
+				exitOnError(err, "unable to read key file")
+
+				verificationKey, err := jose.LoadPublicKey(keyBytes)
+				exitOnError(err, "unable to read private key")
+
+				obj, err := jose.ParseSigned(string(readInput(c.String("input"))))
+				exitOnError(err, "unable to parse message")
+
+				plaintext, err := obj.Verify(verificationKey)
+				exitOnError(err, "invalid signature")
+
+				writeOutput(c.String("output"), plaintext)
+			},
+		},
+		{
+			Name:  "expand",
+			Usage: "expand compact message to full format",
+			Flags: []cli.Flag{
+				cli.StringFlag{
+					Name:  "input, in",
+					Usage: "Path to input file (stdin if missing)",
+				},
+				cli.StringFlag{
+					Name:  "output, out",
+					Usage: "Path to output file (stdout if missing)",
+				},
+				cli.StringFlag{
+					Name:  "format, f",
+					Usage: "Message format (JWE/JWS, defaults to JWE)",
+				},
+			},
+			Action: func(c *cli.Context) {
+				input := string(readInput(c.String("input")))
+
+				var serialized string
+				var err error
+				switch c.String("format") {
+				case "", "JWE":
+					var jwe *jose.JsonWebEncryption
+					jwe, err = jose.ParseEncrypted(input)
+					if err == nil {
+						serialized = jwe.FullSerialize()
+					}
+				case "JWS":
+					var jws *jose.JsonWebSignature
+					jws, err = jose.ParseSigned(input)
+					if err == nil {
+						serialized = jws.FullSerialize()
+					}
+				}
+
+				exitOnError(err, "unable to expand message")
+				writeOutput(c.String("output"), []byte(serialized))
+			},
+		},
+	}
+
+	err := app.Run(os.Args)
+	exitOnError(err, "unable to run application")
+}
+
+// Retrieve value of a required flag
+func requiredFlag(c *cli.Context, flag string) string {
+	value := c.String(flag)
+	if value == "" {
+		fmt.Fprintf(os.Stderr, "missing required flag --%s\n", flag)
+		os.Exit(1)
+	}
+	return value
+}
+
+// Exit and print error message if we encountered a problem
+func exitOnError(err error, msg string) {
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%s: %s\n", msg, err)
+		os.Exit(1)
+	}
+}
+
+// Read input from file or stdin
+func readInput(path string) []byte {
+	var bytes []byte
+	var err error
+
+	if path != "" {
+		bytes, err = ioutil.ReadFile(path)
+	} else {
+		bytes, err = ioutil.ReadAll(os.Stdin)
+	}
+
+	exitOnError(err, "unable to read input")
+	return bytes
+}
+
+// Write output to file or stdin
+func writeOutput(path string, data []byte) {
+	var err error
+
+	if path != "" {
+		err = ioutil.WriteFile(path, data, 0644)
+	} else {
+		_, err = os.Stdout.Write(data)
+	}
+
+	exitOnError(err, "unable to write output")
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/jwe.go b/Godeps/_workspace/src/github.com/square/go-jose/jwe.go
new file mode 100644
index 00000000..4de1ab07
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/jwe.go
@@ -0,0 +1,279 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+)
+
+// rawJsonWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
+type rawJsonWebEncryption struct {
+	Protected    *byteBuffer        `json:"protected,omitempty"`
+	Unprotected  *rawHeader         `json:"unprotected,omitempty"`
+	Header       *rawHeader         `json:"header,omitempty"`
+	Recipients   []rawRecipientInfo `json:"recipients,omitempty"`
+	Aad          *byteBuffer        `json:"aad,omitempty"`
+	EncryptedKey *byteBuffer        `json:"encrypted_key,omitempty"`
+	Iv           *byteBuffer        `json:"iv,omitempty"`
+	Ciphertext   *byteBuffer        `json:"ciphertext,omitempty"`
+	Tag          *byteBuffer        `json:"tag,omitempty"`
+}
+
+// rawRecipientInfo represents a raw JWE Per-Recipient header JSON object. Used for parsing/serializing.
+type rawRecipientInfo struct {
+	Header       *rawHeader `json:"header,omitempty"`
+	EncryptedKey string     `json:"encrypted_key,omitempty"`
+}
+
+// JsonWebEncryption represents an encrypted JWE object after parsing.
+type JsonWebEncryption struct {
+	Header                   JoseHeader
+	protected, unprotected   *rawHeader
+	recipients               []recipientInfo
+	aad, iv, ciphertext, tag []byte
+	original                 *rawJsonWebEncryption
+}
+
+// recipientInfo represents a raw JWE Per-Recipient header JSON object after parsing.
+type recipientInfo struct {
+	header       *rawHeader
+	encryptedKey []byte
+}
+
+// GetAuthData retrieves the (optional) authenticated data attached to the object.
+func (obj JsonWebEncryption) GetAuthData() []byte {
+	if obj.aad != nil {
+		out := make([]byte, len(obj.aad))
+		copy(out, obj.aad)
+		return out
+	}
+
+	return nil
+}
+
+// Get the merged header values
+func (obj JsonWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
+	out := rawHeader{}
+	out.merge(obj.protected)
+	out.merge(obj.unprotected)
+
+	if recipient != nil {
+		out.merge(recipient.header)
+	}
+
+	return out
+}
+
+// Get the additional authenticated data from a JWE object.
+func (obj JsonWebEncryption) computeAuthData() []byte {
+	var protected string
+
+	if obj.original != nil {
+		protected = obj.original.Protected.base64()
+	} else {
+		protected = base64URLEncode(mustSerializeJSON((obj.protected)))
+	}
+
+	output := []byte(protected)
+	if obj.aad != nil {
+		output = append(output, '.')
+		output = append(output, []byte(base64URLEncode(obj.aad))...)
+	}
+
+	return output
+}
+
+// ParseEncrypted parses an encrypted message in compact or full serialization format.
+func ParseEncrypted(input string) (*JsonWebEncryption, error) {
+	input = stripWhitespace(input)
+	if strings.HasPrefix(input, "{") {
+		return parseEncryptedFull(input)
+	}
+
+	return parseEncryptedCompact(input)
+}
+
+// parseEncryptedFull parses a message in compact format.
+func parseEncryptedFull(input string) (*JsonWebEncryption, error) {
+	var parsed rawJsonWebEncryption
+	err := json.Unmarshal([]byte(input), &parsed)
+	if err != nil {
+		return nil, err
+	}
+
+	return parsed.sanitized()
+}
+
+// sanitized produces a cleaned-up JWE object from the raw JSON.
+func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
+	obj := &JsonWebEncryption{
+		original:    parsed,
+		unprotected: parsed.Unprotected,
+	}
+
+	// Check that there is not a nonce in the unprotected headers
+	if (parsed.Unprotected != nil && parsed.Unprotected.Nonce != "") ||
+		(parsed.Header != nil && parsed.Header.Nonce != "") {
+		return nil, ErrUnprotectedNonce
+	}
+
+	if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
+		err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
+		if err != nil {
+			return nil, fmt.Errorf("square/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
+		}
+	}
+
+	// Note: this must be called _after_ we parse the protected header,
+	// otherwise fields from the protected header will not get picked up.
+	obj.Header = obj.mergedHeaders(nil).sanitized()
+
+	if len(parsed.Recipients) == 0 {
+		obj.recipients = []recipientInfo{
+			recipientInfo{
+				header:       parsed.Header,
+				encryptedKey: parsed.EncryptedKey.bytes(),
+			},
+		}
+	} else {
+		obj.recipients = make([]recipientInfo, len(parsed.Recipients))
+		for r := range parsed.Recipients {
+			encryptedKey, err := base64URLDecode(parsed.Recipients[r].EncryptedKey)
+			if err != nil {
+				return nil, err
+			}
+
+			// Check that there is not a nonce in the unprotected header
+			if parsed.Recipients[r].Header != nil && parsed.Recipients[r].Header.Nonce != "" {
+				return nil, ErrUnprotectedNonce
+			}
+
+			obj.recipients[r].header = parsed.Recipients[r].Header
+			obj.recipients[r].encryptedKey = encryptedKey
+		}
+	}
+
+	for _, recipient := range obj.recipients {
+		headers := obj.mergedHeaders(&recipient)
+		if headers.Alg == "" || headers.Enc == "" {
+			return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers")
+		}
+	}
+
+	obj.iv = parsed.Iv.bytes()
+	obj.ciphertext = parsed.Ciphertext.bytes()
+	obj.tag = parsed.Tag.bytes()
+	obj.aad = parsed.Aad.bytes()
+
+	return obj, nil
+}
+
+// parseEncryptedCompact parses a message in compact format.
+func parseEncryptedCompact(input string) (*JsonWebEncryption, error) {
+	parts := strings.Split(input, ".")
+	if len(parts) != 5 {
+		return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts")
+	}
+
+	rawProtected, err := base64URLDecode(parts[0])
+	if err != nil {
+		return nil, err
+	}
+
+	encryptedKey, err := base64URLDecode(parts[1])
+	if err != nil {
+		return nil, err
+	}
+
+	iv, err := base64URLDecode(parts[2])
+	if err != nil {
+		return nil, err
+	}
+
+	ciphertext, err := base64URLDecode(parts[3])
+	if err != nil {
+		return nil, err
+	}
+
+	tag, err := base64URLDecode(parts[4])
+	if err != nil {
+		return nil, err
+	}
+
+	raw := &rawJsonWebEncryption{
+		Protected:    newBuffer(rawProtected),
+		EncryptedKey: newBuffer(encryptedKey),
+		Iv:           newBuffer(iv),
+		Ciphertext:   newBuffer(ciphertext),
+		Tag:          newBuffer(tag),
+	}
+
+	return raw.sanitized()
+}
+
+// CompactSerialize serializes an object using the compact serialization format.
+func (obj JsonWebEncryption) CompactSerialize() (string, error) {
+	if len(obj.recipients) != 1 || obj.unprotected != nil ||
+		obj.protected == nil || obj.recipients[0].header != nil {
+		return "", ErrNotSupported
+	}
+
+	serializedProtected := mustSerializeJSON(obj.protected)
+
+	return fmt.Sprintf(
+		"%s.%s.%s.%s.%s",
+		base64URLEncode(serializedProtected),
+		base64URLEncode(obj.recipients[0].encryptedKey),
+		base64URLEncode(obj.iv),
+		base64URLEncode(obj.ciphertext),
+		base64URLEncode(obj.tag)), nil
+}
+
+// FullSerialize serializes an object using the full JSON serialization format.
+func (obj JsonWebEncryption) FullSerialize() string {
+	raw := rawJsonWebEncryption{
+		Unprotected:  obj.unprotected,
+		Iv:           newBuffer(obj.iv),
+		Ciphertext:   newBuffer(obj.ciphertext),
+		EncryptedKey: newBuffer(obj.recipients[0].encryptedKey),
+		Tag:          newBuffer(obj.tag),
+		Aad:          newBuffer(obj.aad),
+		Recipients:   []rawRecipientInfo{},
+	}
+
+	if len(obj.recipients) > 1 {
+		for _, recipient := range obj.recipients {
+			info := rawRecipientInfo{
+				Header:       recipient.header,
+				EncryptedKey: base64URLEncode(recipient.encryptedKey),
+			}
+			raw.Recipients = append(raw.Recipients, info)
+		}
+	} else {
+		// Use flattened serialization
+		raw.Header = obj.recipients[0].header
+		raw.EncryptedKey = newBuffer(obj.recipients[0].encryptedKey)
+	}
+
+	if obj.protected != nil {
+		raw.Protected = newBuffer(mustSerializeJSON(obj.protected))
+	}
+
+	return string(mustSerializeJSON(raw))
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/jwe_test.go b/Godeps/_workspace/src/github.com/square/go-jose/jwe_test.go
new file mode 100644
index 00000000..ab03fd00
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/jwe_test.go
@@ -0,0 +1,537 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rsa"
+	"math/big"
+	"testing"
+)
+
+func TestCompactParseJWE(t *testing.T) {
+	// Should parse
+	msg := "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA"
+	_, err := ParseEncrypted(msg)
+	if err != nil {
+		t.Error("Unable to parse valid message:", err)
+	}
+
+	// Messages that should fail to parse
+	failures := []string{
+		// Too many parts
+		"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
+		// Not enough parts
+		"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
+		// Invalid encrypted key
+		"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
+		// Invalid IV
+		"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
+		// Invalid ciphertext
+		"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
+		// Invalid tag
+		"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
+		// Invalid header
+		"W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
+		// Invalid header
+		"######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
+		// Missing alc/enc params
+		"e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
+	}
+
+	for _, msg := range failures {
+		_, err = ParseEncrypted(msg)
+		if err == nil {
+			t.Error("Able to parse invalid message", msg)
+		}
+	}
+}
+
+func TestFullParseJWE(t *testing.T) {
+	// Messages that should succeed to parse
+	successes := []string{
+		// Flattened serialization, single recipient
+		"{\"protected\":\"eyJhbGciOiJYWVoiLCJlbmMiOiJYWVoifQo\",\"encrypted_key\":\"QUJD\",\"iv\":\"QUJD\",\"ciphertext\":\"QUJD\",\"tag\":\"QUJD\"}",
+		// Unflattened serialization, single recipient
+		"{\"protected\":\"\",\"unprotected\":{\"enc\":\"XYZ\"},\"recipients\":[{\"header\":{\"alg\":\"XYZ\"},\"encrypted_key\":\"QUJD\"}],\"iv\":\"QUJD\",\"ciphertext\":\"QUJD\",\"tag\":\"QUJD\"}",
+	}
+
+	for i := range successes {
+		_, err := ParseEncrypted(successes[i])
+		if err != nil {
+			t.Error("Unble to parse valid message", err, successes[i])
+		}
+	}
+
+	// Messages that should fail to parse
+	failures := []string{
+		// Empty
+		"{}",
+		// Invalid JSON
+		"{XX",
+		// Invalid protected header
+		"{\"protected\":\"###\"}",
+		// Invalid protected header
+		"{\"protected\":\"e1gK\"}",
+		// Invalid encrypted key
+		"{\"protected\":\"e30\",\"encrypted_key\":\"###\"}",
+		// Invalid IV
+		"{\"protected\":\"e30\",\"encrypted_key\":\"QUJD\",\"iv\":\"###\"}",
+		// Invalid ciphertext
+		"{\"protected\":\"e30\",\"encrypted_key\":\"QUJD\",\"iv\":\"QUJD\",\"ciphertext\":\"###\"}",
+		// Invalid tag
+		"{\"protected\":\"e30\",\"encrypted_key\":\"QUJD\",\"iv\":\"QUJD\",\"ciphertext\":\"QUJD\",\"tag\":\"###\"}",
+		// Invalid AAD
+		"{\"protected\":\"e30\",\"encrypted_key\":\"QUJD\",\"iv\":\"QUJD\",\"ciphertext\":\"QUJD\",\"tag\":\"QUJD\",\"aad\":\"###\"}",
+		// Missing alg/enc headers
+		"{\"protected\":\"e30\",\"encrypted_key\":\"QUJD\",\"iv\":\"QUJD\",\"ciphertext\":\"QUJD\",\"tag\":\"QUJD\"}",
+		// Missing enc header
+		"{\"protected\":\"eyJhbGciOiJYWVoifQ\",\"encrypted_key\":\"QUJD\",\"iv\":\"QUJD\",\"ciphertext\":\"QUJD\",\"tag\":\"QUJD\"}",
+		// Missing alg header
+		"{\"protected\":\"eyJlbmMiOiJYWVoifQ\",\"encrypted_key\":\"QUJD\",\"iv\":\"QUJD\",\"ciphertext\":\"QUJD\",\"tag\":\"QUJD\"}",
+		// Unflattened serialization, single recipient, invalid encrypted_key
+		"{\"protected\":\"\",\"recipients\":[{\"header\":{\"alg\":\"XYZ\", \"enc\":\"XYZ\"},\"encrypted_key\":\"###\"}],\"iv\":\"QUJD\",\"ciphertext\":\"QUJD\",\"tag\":\"QUJD\"}",
+		// Unflattened serialization, single recipient, missing alg
+		"{\"protected\":\"eyJhbGciOiJYWVoifQ\",\"recipients\":[{\"encrypted_key\":\"QUJD\"}],\"iv\":\"QUJD\",\"ciphertext\":\"QUJD\",\"tag\":\"QUJD\"}",
+	}
+
+	for i := range failures {
+		_, err := ParseEncrypted(failures[i])
+		if err == nil {
+			t.Error("Able to parse invalid message", err, failures[i])
+		}
+	}
+}
+
+func TestMissingInvalidHeaders(t *testing.T) {
+	obj := &JsonWebEncryption{
+		protected:   &rawHeader{Enc: A128GCM},
+		unprotected: &rawHeader{},
+		recipients: []recipientInfo{
+			recipientInfo{},
+		},
+	}
+
+	_, err := obj.Decrypt(nil)
+	if err != ErrUnsupportedKeyType {
+		t.Error("should detect invalid key")
+	}
+
+	obj.unprotected.Crit = []string{"1", "2"}
+
+	_, err = obj.Decrypt(nil)
+	if err == nil {
+		t.Error("should reject message with crit header")
+	}
+
+	obj.unprotected.Crit = nil
+	obj.protected = &rawHeader{Alg: string(RSA1_5)}
+
+	_, err = obj.Decrypt(rsaTestKey)
+	if err == nil || err == ErrCryptoFailure {
+		t.Error("should detect missing enc header")
+	}
+}
+
+func TestRejectUnprotectedJWENonce(t *testing.T) {
+	// No need to test compact, since that's always protected
+
+	// Flattened JSON
+	input := `{
+	"header":  {
+		"alg": "XYZ", "enc": "XYZ",
+		"nonce": "should-cause-an-error"
+	},
+	"encrypted_key": "does-not-matter",
+	"aad": "does-not-matter",
+	"iv": "does-not-matter",
+	"ciphertext": "does-not-matter",
+	"tag": "does-not-matter"
+	}`
+	_, err := ParseEncrypted(input)
+	if err == nil {
+		t.Error("JWE with an unprotected nonce parsed as valid.")
+	} else if err.Error() != "square/go-jose: Nonce parameter included in unprotected header" {
+		t.Errorf("Improper error for unprotected nonce: %v", err)
+	}
+
+	input = `{
+		"unprotected":  {
+			"alg": "XYZ", "enc": "XYZ",
+			"nonce": "should-cause-an-error"
+		},
+		"encrypted_key": "does-not-matter",
+		"aad": "does-not-matter",
+		"iv": "does-not-matter",
+		"ciphertext": "does-not-matter",
+		"tag": "does-not-matter"
+	}`
+	_, err = ParseEncrypted(input)
+	if err == nil {
+		t.Error("JWE with an unprotected nonce parsed as valid.")
+	} else if err.Error() != "square/go-jose: Nonce parameter included in unprotected header" {
+		t.Errorf("Improper error for unprotected nonce: %v", err)
+	}
+
+	// Full JSON
+	input = `{
+		"header":  { "alg": "XYZ", "enc": "XYZ" },
+		"aad": "does-not-matter",
+		"iv": "does-not-matter",
+		"ciphertext": "does-not-matter",
+		"tag": "does-not-matter",
+		"recipients": [{
+			"header": { "nonce": "should-cause-an-error" },
+			"encrypted_key": "does-not-matter"
+		}]
+	}`
+	_, err = ParseEncrypted(input)
+	if err == nil {
+		t.Error("JWS with an unprotected nonce parsed as valid.")
+	} else if err.Error() != "square/go-jose: Nonce parameter included in unprotected header" {
+		t.Errorf("Improper error for unprotected nonce: %v", err)
+	}
+}
+
+func TestCompactSerialize(t *testing.T) {
+	// Compact serialization must fail if we have unprotected headers
+	obj := &JsonWebEncryption{
+		unprotected: &rawHeader{Alg: "XYZ"},
+	}
+
+	_, err := obj.CompactSerialize()
+	if err == nil {
+		t.Error("Object with unprotected headers can't be compact serialized")
+	}
+}
+
+func TestVectorsJWE(t *testing.T) {
+	plaintext := []byte("The true sign of intelligence is not knowledge but imagination.")
+
+	publicKey := &rsa.PublicKey{
+		N: fromBase64Int(`
+			oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW
+			cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S
+			psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a
+			sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS
+			tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj
+			YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw`),
+		E: 65537,
+	}
+
+	expectedCompact := stripWhitespace(`
+		eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.ROQCfge4JPm_
+		yACxv1C1NSXmwNbL6kvmCuyxBRGpW57DvlwByjyjsb6g8m7wtLMqKEyhFCn
+		tV7sjippEePIlKln6BvVnz5ZLXHNYQgmubuNq8MC0KTwcaGJ_C0z_T8j4PZ
+		a1nfpbhSe-ePYaALrf_nIsSRKu7cWsrwOSlaRPecRnYeDd_ytAxEQWYEKFi
+		Pszc70fP9geZOB_09y9jq0vaOF0jGmpIAmgk71lCcUpSdrhNokTKo5y8MH8
+		3NcbIvmuZ51cjXQj1f0_AwM9RW3oCh2Hu0z0C5l4BujZVsDuGgMsGZsjUhS
+		RZsAQSXHCAmlJ2NlnN60U7y4SPJhKv5tKYw.48V1_ALb6US04U3b.5eym8T
+		W_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiS
+		diwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ`)
+
+	expectedFull := stripWhitespace(`
+		{ "protected":"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ",
+		"encrypted_key":
+			"ROQCfge4JPm_yACxv1C1NSXmwNbL6kvmCuyxBRGpW57DvlwByjyjsb
+			 6g8m7wtLMqKEyhFCntV7sjippEePIlKln6BvVnz5ZLXHNYQgmubuNq
+			 8MC0KTwcaGJ_C0z_T8j4PZa1nfpbhSe-ePYaALrf_nIsSRKu7cWsrw
+			 OSlaRPecRnYeDd_ytAxEQWYEKFiPszc70fP9geZOB_09y9jq0vaOF0
+			 jGmpIAmgk71lCcUpSdrhNokTKo5y8MH83NcbIvmuZ51cjXQj1f0_Aw
+			 M9RW3oCh2Hu0z0C5l4BujZVsDuGgMsGZsjUhSRZsAQSXHCAmlJ2Nln
+			 N60U7y4SPJhKv5tKYw",
+		"iv": "48V1_ALb6US04U3b",
+		"ciphertext":
+			"5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFS
+			 hS8iB7j6jiSdiwkIr3ajwQzaBtQD_A",
+		"tag":"XFBoMYUZodetZdvTiFvSkQ" }`)
+
+	// Mock random reader
+	randReader = bytes.NewReader([]byte{
+		// Encryption key
+		177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154,
+		212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122,
+		234, 64, 252,
+		// Randomness for RSA-OAEP
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+		// Initialization vector
+		227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219})
+	defer resetRandReader()
+
+	// Encrypt with a dummy key
+	encrypter, err := NewEncrypter(RSA_OAEP, A256GCM, publicKey)
+	if err != nil {
+		panic(err)
+	}
+
+	object, err := encrypter.Encrypt(plaintext)
+	if err != nil {
+		panic(err)
+	}
+
+	serialized, err := object.CompactSerialize()
+	if serialized != expectedCompact {
+		t.Error("Compact serialization is not what we expected", serialized, expectedCompact)
+	}
+
+	serialized = object.FullSerialize()
+	if serialized != expectedFull {
+		t.Error("Full serialization is not what we expected")
+	}
+}
+
+func TestVectorsJWECorrupt(t *testing.T) {
+	priv := &rsa.PrivateKey{
+		PublicKey: rsa.PublicKey{
+			N: fromHexInt(`
+				a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8
+				ae4811a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0c
+				bc64a742c6c630f533c8cc72f62ae833c40bf25842e984bb78bd
+				bf97c0107d55bdb662f5c4e0fab9845cb5148ef7392dd3aaff93
+				ae1e6b667bb3d4247616d4f5ba10d4cfd226de88d39f16fb`),
+			E: 65537,
+		},
+		D: fromHexInt(`
+				53339cfdb79fc8466a655c7316aca85c55fd8f6dd898fdaf1195
+				17ef4f52e8fd8e258df93fee180fa0e4ab29693cd83b152a553d
+				4ac4d1812b8b9fa5af0e7f55fe7304df41570926f3311f15c4d6
+				5a732c483116ee3d3d2d0af3549ad9bf7cbfb78ad884f84d5beb
+				04724dc7369b31def37d0cf539e9cfcdd3de653729ead5d1`),
+		Primes: []*big.Int{
+			fromHexInt(`
+				d32737e7267ffe1341b2d5c0d150a81b586fb3132bed2f8d5262
+				864a9cb9f30af38be448598d413a172efb802c21acf1c11c520c
+				2f26a471dcad212eac7ca39d`),
+			fromHexInt(`
+				cc8853d1d54da630fac004f471f281c7b8982d8224a490edbeb3
+				3d3e3d5cc93c4765703d1dd791642f1f116a0dd852be2419b2af
+				72bfe9a030e860b0288b5d77`),
+		},
+	}
+
+	corruptCiphertext := stripWhitespace(`
+		eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.NFl09dehy
+		IR2Oh5iSsvEa82Ps7DLjRHeo0RnuTuSR45OsaIP6U8yu7vLlWaZKSZMy
+		B2qRBSujf-5XIRoNhtyIyjk81eJRXGa_Bxaor1XBCMyyhGchW2H2P71f
+		PhDO6ufSC7kV4bNqgHR-4ziS7KXwzN83_5kogXqxUpymUoJDNc.tk-GT
+		W_VVhiTIKFF.D_BE6ImZUl9F.52a-zFnRb3YQwIC7UrhVyQ`)
+
+	corruptAuthtag := stripWhitespace(`
+		eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.NFl09dehy
+		IR2Oh5iSsvEa82Ps7DLjRHeo0RnuTuSR45OsaIP6U8yu7vLlWaZKSZMy
+		B2qRBSujf-5XIRoNhtyIyjk81eJRXGa_Bxaor1XBCMyyhGchW2H2P71f
+		PhDO6ufSC7kV4bNqgHR-4ziS7KNwzN83_5kogXqxUpymUoJDNc.tk-GT
+		W_VVhiTIKFF.D_BE6ImZUl9F.52a-zFnRb3YQwiC7UrhVyQ`)
+
+	msg, _ := ParseEncrypted(corruptCiphertext)
+	_, err := msg.Decrypt(priv)
+	if err != ErrCryptoFailure {
+		t.Error("should detect corrupt ciphertext")
+	}
+
+	msg, _ = ParseEncrypted(corruptAuthtag)
+	_, err = msg.Decrypt(priv)
+	if err != ErrCryptoFailure {
+		t.Error("should detect corrupt auth tag")
+	}
+}
+
+// Test vectors generated with nimbus-jose-jwt
+func TestSampleNimbusJWEMessagesRSA(t *testing.T) {
+	rsaPrivateKey, err := LoadPrivateKey(fromBase64Bytes(`
+		MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCNRCEmf5PlbXKuT4uwnb
+		wGKvFrtpi+bDYxOZxxqxdVkZM/bYATAnD1fg9pNvLMKeF+MWJ9kPIMmDgOh9RdnRdLvQGb
+		BzhLmxwhhcua2QYiHEZizXmiaXvNP12bzEBhebdX7ObW8izMVW0p0lqHPNzkK3K75B0Sxo
+		FMVKkZ7KtBHgepBT5yPhPPcNe5lXQeTne5bo3I60DRcN9jTBgMJOXdq0I9o4y6ZmoXdNTm
+		0EyLzn9/EYiHqBxtKFh791EHR7wYgyi/t+nOKr4sO74NbEByP0mHDil+mPvZSzFW4l7fPx
+		OclRZvpRIKIub2TroZA9s2WsshGf79eqqXYbBB9NNRAgMBAAECggEAIExbZ/nzTplfhwsY
+		3SCzRJW87OuqsJ79JPQPGM4NX7sQ94eJqM7+FKLl0yCFErjgnYGdCyiArvB+oJPdsimgke
+		h83X0hGeg03lVA3/6OsG3WifCAxulnLN44AM8KST8S9D9t5+cm5vEBLHazzAfWWTS13s+g
+		9hH8rf8NSqgZ36EutjKlvLdHx1mWcKX7SREFVHT8FWPAbdhTLEHUjoWHrfSektnczaSHnt
+		q8fFJy6Ld13QkF1ZJRUhtA24XrD+qLTc+M36IuedjeZaLHFB+KyhYR3YvXEtrbCug7dCRd
+		uG6uTlDCSaSy7xHeTPolWtWo9F202jal54otxiAJFGUHgQKBgQDRAT0s6YQZUfwE0wluXV
+		k0JdhDdCo8sC1aMmKlRKWUkBAqrDl7BI3MF56VOr4ybr90buuscshFf9TtrtBOjHSGcfDI
+		tSKfhhkW5ewQKB0YqyHzoD6UKT0/XAshFY3esc3uCxuJ/6vOiXV0og9o7eFvr51O0TfDFh
+		mcTvW4wirKlQKBgQCtB7UAu8I9Nn8czkd6oXLDRyTWYviuiqFmxR+PM9klgZtsumkeSxO1
+		lkfFoj9+G8nFaqYEBA9sPeNtJVTSROCvj/iQtoqpV2NiI/wWeVszpBwsswx2mlks4LJa8a
+		Yz9xrsfNoroKYVppefc/MCoSx4M+99RSm3FSpLGZQHAUGyzQKBgQDMQmq4JuuMF1y2lk0E
+		SESyuz21BqV0tDVOjilsHT+5hmXWXoS6nkO6L2czrrpM7YE82F6JJZBmo7zEIXHBInGLJ3
+		XLoYLZ5qNEhqYDUEDHaBCBWZ1vDTKnZlwWFEuXVavNNZvPbUhKTHq25t8qjDki/r09Vykp
+		BsM2yNBKpbBOVQKBgCJyUVd3CaFUExQyAMrqD0XPCQdhJq7gzGcAQVsp8EXmOoH3zmuIeM
+		ECzQEMXuWFNLMHm0tbX5Kl83vMHcnKioyI9ewhWxOBYTitf0ceG8j5F97SOl32NmCXzwoJ
+		55Oa0xJXfLuIvOe8hZzp4WwZmBfKBxiCR166aPQQgIawelrVAoGAEJsHomfCI4epxH4oMw
+		qYJMCGy95zloB+2+c86BZCOJAGwnfzbtc2eutWZw61/9sSO8sQCfzA8oX+5HwAgnFVzwW4
+		lNMZohppYcpwN9EyjkPaCXuALC7p5rF2o63wY7JLvnjS2aYZliknh2yW6X6fSB0PK0Cpvd
+		lAIyRw6Kud0zI=`))
+	if err != nil {
+		panic(err)
+	}
+
+	rsaSampleMessages := []string{
+		"eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiUlNBMV81In0.EW0KOhHeoAxTBnLjYhh2T6HjwI-srNs6RpcSdZvE-GJ5iww3EYWBCmeGGj1UVz6OcBfwW3wllZ6GPOHU-hxVQH5KYpVOjkmrFIYU6-8BHhxBP_PjSJEBCZzjOgsCm9Th4-zmlO7UWTdK_UtwE7nk4X-kkmEy-aZBCShA8nFe2MVvqD5F7nvEWNFBOHh8ae_juo-kvycoIzvxLV9g1B0Zn8K9FAlu8YF1KiL5NFekn76f3jvAwlExuRbFPUx4gJN6CeBDK_D57ABsY2aBVDSiQceuYZxvCIAajqSS6dMT382FNJzAiQhToOpo_1w5FnnBjzJLLEKDk_I-Eo2YCWxxsQ.5mCMuxJqLRuPXGAr.Ghe4INeBhP3MDWGvyNko7qanKdZIzKjfeiU.ja3UlVWJXKNFJ-rZsJWycw",
+		"eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBMV81In0.JsJeYoP0St1bRYNUaAmA34DAA27usE7RNuC2grGikBRmh1xrwUOpnEIXXpwr7fjVmNi52zzWkNHC8JkkRTrLcCh2VXvnOnarpH8DCr9qM6440bSrahzbxIvDds8z8q0wT1W4kjVnq1mGwGxg8RQNBWTV6Sp2FLQkZyjzt_aXsgYzr3zEmLZxB-d41lBS81Mguk_hdFJIg_WO4ao54lozvxkCn_uMiIZ8eLb8qHy0h-N21tiHGCaiC2vV8KXomwoqbJ0SXrEH4r9_R2J844H80TBZdbvNBd8whvoQNHvOX659LNs9EQ9xxvHU2kqGZekXBu7sDXXTjctMkMITobGSzw.1v5govaDvanP3LGp.llwYNBDrD7MwVLaFHesljlratfmndWs4XPQ.ZGT1zk9_yIKi2GzW6CuAyA",
+		"eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBMV81In0.fBv3fA3TMS3ML8vlsCuvwdsKvB0ym8R30jJrlOiqkWKk7WVUkjDInFzr1zw3Owla6c5BqOJNoACXt4IWbkLbkoWV3tweXlWwpafuaWPkjLOUH_K31rS2fCX5x-MTj8_hScquVQXpbz3vk2EfulRmGXZc_8JU2NqQCAsYy3a28houqP3rDe5jEAvZS2SOFvJkKW--f5S-z39t1D7fNz1N8Btd9SmXWQzjbul5YNxI9ctqxhJpkKYpxOLlvrzdA6YdJjOlDx3n6S-HnSZGM6kQd_xKtAf8l1EGwhQmhbXhMhjVxMvGwE5BX7PAb8Ccde5bzOCJx-PVbVetuLb169ZYqQ._jiZbOPRR82FEWMZ.88j68LI-K2KT6FMBEdlz6amG5nvaJU8a-90.EnEbUTJsWNqJYKzfO0x4Yw",
+		"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBMV81In0.bN6FN0qmGxhkESiVukrCaDVG3woL0xE-0bHN_Mu0WZXTQWbzzT-7jOvaN1xhGK8nzi8qpCSRgE5onONNB9i8OnJm3MMIxF7bUUEAXO9SUAFn2v--wNc4drPc5OjIu0RiJrDVDkkGjNrBDIuBaEQcke7A0v91PH58dXE7o4TLPzC8UJmRtXWhUSwjXVF3-UmYRMht2rjHJlvRbtm6Tu2LMBIopRL0zj6tlPP4Dm7I7sz9OEB3VahYAhpXnFR7D_f8RjLSXQmBvB1FiI5l_vMz2NFt2hYUmQF3EJMLIEdHvvPp3iHDGiXC1obJrDID_CCf3qs9UY7DMYL622KLvP2NIg.qb72oxECzxd_aNuHVR0aNg.Gwet9Ms8hB8rKEb0h4RGdFNRq97Qs2LQaJM0HWrCqoI.03ljVThOFvgXzMmQJ79VjQ",
+		"eyJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiUlNBMV81In0.ZbEOP6rqdiIP4g7Nl1PL5gwhgDwv9RinyiUQxZXPOmD7kwEZrZ093dJnhqI9kEd3QGFlHDpB7HgNz53d27z2zmEj1-27v6miizq6tH4sN2MoeZLwSyk16O1_n3bVdDmROawsTYYFJfHsuLwyVJxPd37duIYnbUCFO9J8lLIv-2VI50KJ1t47YfE4P-Wt9jVzxP2CVUQaJwTlcwfiDLJTagYmfyrDjf525WlQFlgfJGqsJKp8BX9gmKvAo-1iCBAM8VpEjS0u0_hW9VSye36yh8BthVV-VJkhJ-0tMpto3bbBmj7M25Xf4gbTrrVU7Nz6wb18YZuhHZWmj2Y2nHV6Jg.AjnS44blTrIIfFlqVw0_Mg.muCRgaEXNKKpW8rMfW7jf7Zpn3VwSYDz-JTRg16jZxY.qjc9OGlMaaWKDWQSIwVpR4K556Pp6SF9",
+		"eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiUlNBMV81In0.c7_F1lMlRHQQE3WbKmtHBYTosdZrG9hPfs-F9gNQYet61zKG8NXVkSy0Zf2UFHt0vhcO8hP2qrqOFsy7vmRj20xnGHQ2EE29HH6hwX5bx1Jj3uE5WT9Gvh0OewpvF9VubbwWTIObBpdEG7XdJsMAQlIxtXUmQYAtLTWcy2ZJipyJtVlWQLaPuE8BKfZH-XAsp2CpQNiRPI8Ftza3EAspiyRfVQbjKt7nF8nuZ2sESjt7Y50q4CSiiCuGT28T3diMN0_rWrH-I-xx7OQvJlrQaNGglGtu3jKUcrJDcvxW2e1OxriaTeuQ848ayuRvGUNeSv6WoVYmkiK1x_gNwUAAbw.7XtSqHJA7kjt6JrfxJMwiA.Yvi4qukAbdT-k-Fd2s4G8xzL4VFxaFC0ZIzgFDAI6n0.JSWPJ-HjOE3SK9Lm0yHclmjS7Z1ahtQga9FHGCWVRcc",
+		"eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.SYVxJbCnJ_tcR13LJpaqHQj-nGNkMxre4A1FmnUdxnvzeJwuvyrLiUdRsZR1IkP4fqLtDON2mumx39QeJQf0WIObPBYlIxycRLkwxDHRVlyTmPvdZHAxN26jPrk09wa5SgK1UF1W1VSQIPm-Tek8jNAmarF1Yxzxl-t54wZFlQiHP4TuaczugO5f-J4nlWenfla2mU1snDgdUMlEZGOAQ_gTEtwSgd1MqXmK_7LZBkoDqqoCujMZhziafJPXPDaUUqBLW3hHkkDA7GpVec3XcTtNUWQJqOpMyQhqo1KQMc8jg3fuirILp-hjvvNVtBnCRBvbrKUCPzu2_yH3HM_agA.2VsdijtonAxShNIW.QzzB3P9CxYP3foNKN0Ma1Z9tMwijAlkWo08.ZdQkIPDY_M-hxqi5fD4NGw",
+		"eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.Z2oTJXXib1u-S38Vn3DRKE3JnhnwgUa92UhsefzY2Wpdn0dmxMfYt9iRoJGFfSAcA97MOfjyvXVRCKWXGrG5AZCMAXEqU8SNQwKPRjlcqojcVzQyMucXI0ikLC4mUgeRlfKTwsBicq6JZZylzRoLGGSNJQbni3_BLsf7H3Qor0BYg0FPCLG9Z2OVvrFzvjTLmZtV6gFlVrMHBxJub_aUet9gAkxiu1Wx_Kx46TlLX2tkumXIpTGlzX6pef6jLeZ5EIg_K-Uz4tkWgWQIEkLD7qmTyk5pAGmzukHa_08jIh5-U-Sd8XGZdx4J1pVPJ5CPg0qDJGZ_cfgkgpWbP_wB6A.4qgKfokK1EwYxz20._Md82bv_KH2Vru0Ue2Eb6oAqHP2xBBP5jF8.WFRojvQpD5VmZlOr_dN0rQ",
+		"eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.JzCUgJcBJmBgByp4PBAABUfhezPvndxBIVzaoZ96DAS0HPni0OjMbsOGsz6JwNsiTr1gSn_S6R1WpZM8GJc9R2z0EKKVP67TR62ZSG0MEWyLpHmG_4ug0fAp1HWWMa9bT4ApSaOLgwlpVAb_-BPZZgIu6c8cREuMon6UBHDqW1euTBbzk8zix3-FTZ6p5b_3soDL1wXfRiRBEsxxUGMnpryx1OFb8Od0JdyGF0GgfLt6OoaujDJpo-XtLRawu1Xlg6GqRs0NQwSHZ5jXgQ6-zgCufXonAmYTiIyBXY2no9XmECTexjwrS_05nA7H-UyIZEBOCp3Yhz2zxrt5j_0pvQ.SJR-ghhaUKP4zXtZ.muiuzLfZA0y0BDNsroGTw2r2-l73SLf9lK8.XFMH1oHr1G6ByP3dWSUUPA",
+		"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBLU9BRVAifQ.U946MVfIm4Dpk_86HrnIA-QXyiUu0LZ67PL93CMLmEtJemMNDqmRd9fXyenCIhAC7jPIV1aaqW7gS194xyrrnUpBoJBdbegiPqOfquy493Iq_GQ8OXnFxFibPNQ6rU0l8BwIfh28ei_VIF2jqN6bhxFURCVW7fG6n6zkCCuEyc7IcxWafSHjH2FNttREuVj-jS-4LYDZsFzSKbpqoYF6mHt8H3btNEZDTSmy_6v0fV1foNtUKNfWopCp-iE4hNh4EzJfDuU8eXLhDb03aoOockrUiUCh-E0tQx9su4rOv-mDEOHHAQK7swm5etxoa7__9PC3Hg97_p4GM9gC9ykNgw.pnXwvoSPi0kMQP54of-HGg.RPJt1CMWs1nyotx1fOIfZ8760mYQ69HlyDp3XmdVsZ8.Yxw2iPVWaBROFE_FGbvodA",
+		"eyJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiUlNBLU9BRVAifQ.eKEOIJUJpXmO_ghH_nGCJmoEspqKyiy3D5l0P8lKutlo8AuYHPQlgOsaFYnDkypyUVWd9zi-JaQuCeo7dzoBiS1L71nAZo-SUoN0anQBkVuyuRjr-deJMhPPfq1H86tTk-4rKzPr1Ivd2RGXMtWsrUpNGk81r1v8DdMntLE7UxZQqT34ONuZg1IXnD_U6di7k07unI29zuU1ySeUr6w1YPw5aUDErMlpZcEJWrgOEYWaS2nuC8sWGlPGYEjqkACMFGn-y40UoS_JatNZO6gHK3SKZnXD7vN5NAaMo_mFNbh50e1t_zO8DaUdLtXPOBLcx_ULoteNd9H8HyDGWqwAPw.0xmtzJfeVMoIT1Cp68QrXA.841l1aA4c3uvSYfw6l180gn5JZQjL53WQ5fr8ejtvoI.lojzeWql_3gDq-AoaIbl_aGQRH_54w_f",
+		"eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiUlNBLU9BRVAifQ.D0QkvIXR1TL7dIHWuPNMybmmD8UPyQd1bRKjRDNbA2HmKGpamCtcJmpNB_EetNFe-LDmhe44BYI_XN2wIBbYURKgDK_WG9BH0LQw_nCVqQ-sKqjtj3yQeytXhLHYTDmiF0TO-uW-RFR7GbPAdARBfuf4zj82r_wDD9sD5WSCGx89iPfozDOYQ_OLwdL2WD99VvDyfwS3ZhxA-9IMSYv5pwqPkxj4C0JdjCqrN0YNrZn_1ORgjtsVmcWXsmusObTozUGA7n5GeVepfZdU1vrMulAwdRYqOYtlqKaOpFowe9xFN3ncBG7wb4f9pmzbS_Dgt-1_Ii_4SEB9GQ4NiuBZ0w.N4AZeCxMGUv52A0UVJsaZw.5eHOGbZdtahnp3l_PDY-YojYib4ft4SRmdsQ2kggrTs.WsmGH8ZDv4ctBFs7qsQvw2obe4dVToRcAQaZ3PYL34E",
+		"eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.fDTxO_ZzZ3Jdrdw-bxvg7u-xWB2q1tp3kI5zH6JfhLUm4h6rt9qDA_wZlRym8-GzEtkUjkTtQGs6HgQx_qlyy8ylCakY5GHsNhCG4m0UNhRiNfcasAs03JSXfON9-tfTJimWD9n4k5OHHhvcrsCW1G3jYeLsK9WHCGRIhNz5ULbo8HBrCTbmZ6bOEQ9mqhdssLpdV24HDpebotf3bgPJqoaTfWU6Uy7tLmPiNuuNRLQ-iTpLyNMTVvGqqZhpcV3lAEN5l77QabI5xLJYucvYjrXQhAEZ7YXO8oRYhGkdG2XXIRcwr87rBeRH-47HAyhZgF_PBPBhhrJNS9UNMqdfBw.FvU4_s7Md6vxnXWd.fw29Q4_gHt4f026DPPV-CNebQ8plJ6IVLX8._apBZrw7WsT8HOmxgCrTwA",
+		"eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.bYuorK-rHMbO4c2CRWtvyOEaM1EN-o-wLRZ0wFWRX9mCXQ-iTNarZn7ksYM1XnGmZ4u3CSowX1Hpca9Rg72_VJCmKapqCT7r3YfasN4_oeLwuSKI_gT-uVOznod97tn3Gf_EDv0y1V4H0k9BEIFGbajAcG1znTD_ODY3j2KZJxisfrsBoslc6N-HI0kKZMC2hSGuHOcOf8HN1sTE-BLqZCtoj-zxQECJK8Wh14Ih4jzzdmmiu_qmSR780K6su-4PRt3j8uY7oCiLBfwpCsCmhJgp8rKd91zoedZmamfvX38mJIfE52j4fG6HmIYw9Ov814fk9OffV6tzixjcg54Q2g.yeVJz4aSh2s-GUr9.TBzzWP5llEiDdugpP2SmPf2U4MEGG9EoPWk.g25UoWpsBaOd45J__FX7mA",
+		"eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.h9tFtmh762JuffBxlSQbJujCyI4Zs9yc3IOb1yR8g65W4ZHosIvzVGHWbShj4EY9MNrz-RbKtHfqQGGzDeo3Xb4-HcQ2ZDHyWoUg7VfA8JafJ5zIKL1npz8eUExOVMLsAaRfHg8qNfczodg3egoSmX5Q-nrx4DeidDSXYZaZjV0C72stLTPcuQ7XPV7z1tvERAkqpvcsRmJn_PiRNxIbAgoyHMJ4Gijuzt1bWZwezlxYmw0TEuwCTVC2fl9NJTZyxOntS1Lcm-WQGlPkVYeVgYTOQXLlp7tF9t-aAvYpth2oWGT6Y-hbPrjx_19WaKD0XyWCR46V32DlXEVDP3Xl2A.NUgfnzQyEaJjzt9r.k2To43B2YVWMeR-w3n4Pr2b5wYq2o87giHk.X8_QYCg0IGnn1pJqe8p_KA",
+		"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.EDq6cNP6Yp1sds5HZ4CkXYp7bs9plIYVZScKvuyxUy0H1VyBC_YWg0HvndPNb-vwh1LA6KMxRazlOwJ9iPR9YzHnYmGgPM3Je_ZzBfiPlRfq6hQBpGnNaypBI1XZ2tyFBhulsVLqyJe2SmM2Ud00kasOdMYgcN8FNFzq7IOE7E0FUQkIwLdUL1nrzepiYDp-5bGkxWRcL02cYfdqdm00G4m0GkUxAmdxa3oPNxZlt2NeBI_UVWQSgJE-DJVJQkDcyA0id27TV2RCDnmujYauNT_wYlyb0bFDx3pYzzNXfAXd4wHZxt75QaLZ5APJ0EVfiXJ0qki6kT-GRVmOimUbQA.vTULZL7LvS0WD8kR8ZUtLg.mb2f0StEmmkuuvsyz8UplMvF58FtZzlu8eEwzvPUvN0.hbhveEN40V-pgG2hSVgyKg",
+		"eyJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.DuYk92p7u-YIN-JKn-XThmlVcnhU9x5TieQ2uhsLQVNlo0iWC9JJPP6bT6aI6u_1BIS3yE8_tSGGL7eM-zyEk6LuTqSWFRaZcZC06d0MnS9eYZcw1T2D17fL-ki-NtCaTahJD7jE2s0HevRVW49YtL-_V8whnO_EyVjvXIAQlPYqhH_o-0Nzcpng9ggdAnuF2rY1_6iRPYFJ3BLQvG1oWhyJ9s6SBttlOa0i6mmFCVLHx6sRpdGAB3lbCL3wfmHq4tpIv77gfoYUNP0SNff-zNmBXF_wp3dCntLZFTjbfMpGyHlruF_uoaLqwdjYpUGNUFVUoeSiMnSbMKm9NxiDgQ.6Mdgcqz7bMU1UeoAwFC8pg.W36QWOlBaJezakUX5FMZzbAgeAu_R14AYKZCQmuhguw.5OeyIJ03olxmJft8uBmjuOFQPWNZMYLI",
+		"eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.ECulJArWFsPL2FlpCN0W8E7IseSjJg1cZqE3wz5jk9gvwgNForAUEv5KYZqhNI-p5IxkGV0f8K6Y2X8pWzbLwiPIjZe8_dVqHYJoINxqCSgWLBhz0V36qL9Nc_xARTBk4-ZteIu75NoXVeos9gNvFnkOCj4tm-jGo8z8EFO9XfODgjhiR4xv8VqUtvrkjo9GQConaga5zpV-J4JQlXbdqbDjnuwacnJAxYpFyuemqcgqsl6BnFX3tovGkmSUPqcvF1A6tiHqr-TEmcgVqo5C3xswknRBKTQRM00iAmJ92WlVdkoOCx6E6O7cVHFawZ14BLzWzm66Crb4tv0ucYvk_Q.mxolwUaoj5S5kHCfph0w8g.nFpgYdnYg3blHCCEi2XXQGkkKQBXs2OkZaH11m3PRvk.k8BAVT4EcyrUFVIKr-KOSPbF89xyL0Vri2rFTu2iIWM",
+	}
+
+	for _, msg := range rsaSampleMessages {
+		obj, err := ParseEncrypted(msg)
+		if err != nil {
+			t.Error("unable to parse message", msg, err)
+			continue
+		}
+		plaintext, err := obj.Decrypt(rsaPrivateKey)
+		if err != nil {
+			t.Error("unable to decrypt message", msg, err)
+			continue
+		}
+		if string(plaintext) != "Lorem ipsum dolor sit amet" {
+			t.Error("plaintext is not what we expected for msg", msg)
+		}
+	}
+}
+
+// Test vectors generated with nimbus-jose-jwt
+func TestSampleNimbusJWEMessagesAESKW(t *testing.T) {
+	aesTestKeys := [][]byte{
+		fromHexBytes("DF1FA4F36FFA7FC42C81D4B3C033928D"),
+		fromHexBytes("DF1FA4F36FFA7FC42C81D4B3C033928D95EC9CDC2D82233C"),
+		fromHexBytes("DF1FA4F36FFA7FC42C81D4B3C033928D95EC9CDC2D82233C333C35BA29044E90"),
+	}
+
+	aesSampleMessages := [][]string{
+		[]string{
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4R0NNIiwidGFnIjoib2ZMd2Q5NGloVWFRckJ0T1pQUDdjUSIsImFsZyI6IkExMjhHQ01LVyIsIml2IjoiV2Z3TnN5cjEwWUFjY2p2diJ9.9x3RxdqIS6P9xjh93Eu1bQ.6fs3_fSGt2jull_5.YDlzr6sWACkFg_GU5MEc-ZEWxNLwI_JMKe_jFA.f-pq-V7rlSSg_q2e1gDygw",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyR0NNIiwidGFnIjoic2RneXB1ckFjTEFzTmZJU0lkZUNpUSIsImFsZyI6IkExMjhHQ01LVyIsIml2IjoieVFMR0dCdDJFZ0c1THdyViJ9.arslKo4aKlh6f4s0z1_-U-8JbmhAoZHN.Xw2Q-GX98YXwuc4i.halTEWMWAYZbv-qOD52G6bte4x6sxlh1_VpGEA.Z1spn016v58cW6Q2o0Qxag",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2R0NNIiwidGFnIjoicTNzejF5VUlhbVBDYXJfZ05kSVJqQSIsImFsZyI6IkExMjhHQ01LVyIsIml2IjoiM0ZRM0FsLWJWdWhmcEIyQyJ9.dhVipWbzIdsINttuZM4hnjpHvwEHf0VsVrOp4GAg01g.dk7dUyt1Qj13Pipw.5Tt70ONATF0BZAS8dBkYmCV7AQUrfb8qmKNLmw.A6ton9MQjZg0b3C0QcW-hg",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwidGFnIjoiUHNpTGphZnJZNE16UlRmNlBPLTZfdyIsImFsZyI6IkExMjhHQ01LVyIsIml2IjoiSUFPbnd2ODR5YXFEaUxtbSJ9.swf92_LyCvjsvkynHTuMNXRl_MX2keU-fMDWIMezHG4.LOp9SVIXzs4yTnOtMyXZYQ.HUlXrzqJ1qXYl3vUA-ydezCg77WvJNtKdmZ3FPABoZw.8UYl1LOofQLAxHHvWqoTbg",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwidGFnIjoiWGRndHQ5dUVEMVlVeU1rVHl6M3lqZyIsImFsZyI6IkExMjhHQ01LVyIsIml2IjoiWF90V2RhSmh6X3J1SHJvQSJ9.JQ3dS1JSgzIFi5M9ig63FoFU1nHBTmPwXY_ovNE2m1JOSUvHtalmihIuraPDloCf.e920JVryUIWt7zJJQM-www.8DUrl4LmsxIEhRr9RLTHG9tBTOcwXqEbQHAJd_qMHzE.wHinoqGUhL4O7lx125kponpwNtlp8VGJ",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwidGFnIjoicGgyaTdoY0FWNlh3ZkQta1RHYlVXdyIsImFsZyI6IkExMjhHQ01LVyIsIml2IjoiaG41Smk4Wm1rUmRrSUxWVSJ9._bQlJXl22dhsBgYPhkxUyinBNi871teGWbviOueWj2PqG9OPxIc9SDS8a27YLSVDMircd5Q1Df28--vcXIABQA.DssmhrAg6w_f2VDaPpxTbQ.OGclEmqrxwvZqAfn7EgXlIfXgr0wiGvEbZz3zADnqJs.YZeP0uKVEiDl8VyC-s20YN-RbdyGNsbdtoGDP3eMof8",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.TEMcXEoY8WyqGjYs5GZgS-M_Niwu6wDY.i-26KtTt51Td6Iwd.wvhkagvPsLj3QxhPBbfH_th8OqxisUtme2UadQ.vlfvBPv3bw2Zk2H60JVNLQ",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiQTEyOEtXIn0.gPaR6mgQ9TUx05V6DRfgTQeZxl0ZSzBa5uQd-qw6yLs.MojplOD77FkMooS-.2yuD7dKR_C3sFbhgwiBccKKOF8DrSvNiwX7wPQ.qDKUbSvMnJv0qifjpWC14g",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiQTEyOEtXIn0.Fg-dgSkUW1KEaL5YDPoWHNL8fpX1WxWVLA9OOWsjIFhQVDKyUZI7BQ.mjRBpyJTZf7H-quf.YlNHezMadtaSKp23G-ozmYhHOeHwuJnvWGTtGg.YagnR7awBItUlMDo4uklvg",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTEyOEtXIn0.x1vYzUE-E2XBWva9OPuwtqfQaf9rlJCIBAyAe6N2q2kWfJrkxGxFsQ.gAwe78dyODFaoP2IOityAA.Yh5YfovkWxGBNAs1sVhvXow_2izHHsBiYEc9JYD6kVg.mio1p3ncp2wLEaEaRa7P0w",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiQTEyOEtXIn0.szGrdnmF7D5put2aRBvSSFfp0vRgkRGYaafijJIqAF6PWd1IxsysZRV8aQkQOW1cB6d0fXsTfYM.Ru25LVOOk4xhaK-cIZ0ThA.pF9Ok5zot7elVqXFW5YYHV8MuF9gVGzpQnG1XDs_g_w.-7la0uwcNPpteev185pMHZjbVDXlrec8",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiQTEyOEtXIn0.cz-hRv0xR5CnOcnoRWNK8Q9poyVYzRCVTjfmEXQN6xPOZUkJ3zKNqb8Pir_FS0o2TVvxmIbuxeISeATTR2Ttx_YGCNgMkc93.SF5rEQT94lZR-UORcMKqGw.xphygoU7zE0ZggOczXCi_ytt-Evln8CL-7WLDlWcUHg.5h99r8xCCwP2PgDbZqzCJ13oFfB2vZWetD5qZjmmVho",
+		},
+		[]string{
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4R0NNIiwidGFnIjoiVWR5WUVKdEJ5ZTA5dzdjclY0cXI1QSIsImFsZyI6IkExOTJHQ01LVyIsIml2IjoiZlBBV0QwUmdSbHlFdktQcCJ9.P1uTfTuH-imL-NJJMpuTRA.22yqZ1NIfx3KNPgc.hORWZaTSgni1FS-JT90vJly-cU37qTn-tWSqTg.gMN0ufXF92rSXupTtBNkhA",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyR0NNIiwidGFnIjoiOU9qX3B2LTJSNW5lZl9YbWVkUWltUSIsImFsZyI6IkExOTJHQ01LVyIsIml2IjoiY3BybGEwYUYzREVQNmFJTSJ9.6NVpAm_APiC7km2v-oNR8g23K9U_kf1-.jIg-p8tNwSvwxch0.1i-GPaxS4qR6Gy4tzeVtSdRFRSKQSMpmn-VhzA.qhFWPqtA6vVPl7OM3DThsA",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2R0NNIiwidGFnIjoiOVc3THg3MVhGQVJCb3NaLVZ5dXc4ZyIsImFsZyI6IkExOTJHQ01LVyIsIml2IjoiZ1N4ZE5heFdBSVBRR0tHYiJ9.3YjPz6dVQwAtCekvtXiHZrooOUlmCsMSvyfwmGwdrOA.hA_C0IDJmGaRzsB0.W4l7OPqpFxiVOZTGfAlRktquyRTo4cEOk9KurQ.l4bGxOkO_ql_jlPo3Oz3TQ",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwidGFnIjoiOHJYbWl2WXFWZjNfbHhhd2NUbHJoUSIsImFsZyI6IkExOTJHQ01LVyIsIml2IjoiVXBWeXprVTNKcjEwYXRqYyJ9.8qft-Q_xqUbo5j_aVrVNHchooeLttR4Kb6j01O8k98M.hXO-5IKBYCL9UdwBFVm0tg.EBM4lCZX_K6tfqYmfoDxVPHcf6cT--AegXTTjfSqsIw.Of8xUvEQSh3xgFT3uENnAg",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwidGFnIjoiVnItSnVaX0tqV2hSWWMzdzFwZ3cwdyIsImFsZyI6IkExOTJHQ01LVyIsIml2IjoiRGg2R3dISVBVS3ljZGNZeCJ9.YSEDjCnGWr_n9H94AvLoRnwm6bdU9w6-Q67k-QQRVcKRd6673pgH9zEF9A9Dt6o1.gcmVN4kxqBuMq6c7GrK3UQ.vWzJb0He6OY1lhYYjYS7CLh55REAAq1O7yNN-ND4R5Q.OD0B6nwyFaDr_92ysDOtlVnJaeoIqhGw",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwidGFnIjoieEtad1BGYURpQ3NqUnBqZUprZHhmZyIsImFsZyI6IkExOTJHQ01LVyIsIml2IjoieTVHRFdteXdkb2R1SDJlYyJ9.AW0gbhWqlptOQ1y9aoNVwrTIIkBfrp33C2OWJsbrDRk6lhxg_IgFhMDTE37moReySGUtttC4CXQD_7etHmd3Hw.OvKXK-aRKlXHOpJQ9ZY_YQ.Ngv7WarDDvR2uBj_DavPAR3DYuIaygvSSdcHrc8-ZqM.MJ6ElitzFCKf_0h5fIJw8uOLC6ps7dKZPozF8juQmUY",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTE5MktXIn0.8qu63pppcSvp1vv37WrZ44qcCTg7dQMA.cDp-f8dJTrDEpZW4.H6OBJYs4UvFR_IZHLYQZxB6u9a0wOdAif2LNfQ.1dB-id0UIwRSlmwHx5BJCg",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiQTE5MktXIn0._FdoKQvC8qUs7K0upriEihUwztK8gOwonXpOxdIwrfs.UO38ok8gDdpLVa1T.x1GvHdVCy4fxoQRg-OQK4Ez3jDOvu9gllLPeEA.3dLeZGIprh_nHizOTVi1xw",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiQTE5MktXIn0.uzCJskgSIK6VkjJIu-dQi18biqaY0INc_A1Ehx0oESafgtR99_n4IA.W2eKK8Y14WwTowI_.J2cJC7R6Bz6maR0s1UBMPyRi5BebNUAmof4pvw.-7w6htAlc4iUsOJ6I04rFg",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTE5MktXIn0.gImQeQETp_6dfJypFDPLlv7c5pCzuq86U16gzrLiCXth6X9XfxJpvQ.YlC4MxjtLWrsyEvlFhvsqw.Vlpvmg9F3gkz4e1xG01Yl2RXx-jG99rF5UvCxOBXSLc.RZUrU_FoR5bG3M-j3GY0Dw",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiQTE5MktXIn0.T2EfQ6Tu2wJyRMgZzfvBYmQNCCfdMudMrg86ibEMVAOUKJPtR3WMPEb_Syy9p2VjrLKRlv7nebo.GPc8VbarPPRtzIRATB8NsA.ugPCqLvVLwh55bWlwjsFkmWzJ31z5z-wuih2oJqmG_U.m7FY3EjvV6mKosEYJ5cY7ezFoVQoJS8X",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiQTE5MktXIn0.OgLMhZ-2ZhslQyHfzOfyC-qmT6bNg9AdpP59B4jtyxWkQu3eW475WCdiAjojjeyBtVRGQ5vOomwaOIFejY_IekzH6I_taii3.U9x44MF6Wyz5TIwIzwhoxQ.vK7yvSF2beKdNxNY_7n4XdF7JluCGZoxdFJyTJVkSmI.bXRlI8KL-g7gpprQxGmXjVYjYghhWJq7mlCfWI8q2uA",
+		},
+		[]string{
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4R0NNIiwidGFnIjoiR3BjX3pfbjduZjJVZlEtWGdsaTBaQSIsImFsZyI6IkEyNTZHQ01LVyIsIml2IjoiUk40eUdhOVlvYlFhUmZ1TCJ9.Q4ukD6_hZpmASAVcqWJ9Wg.Zfhny_1WNdlp4fH-.3sekDCjkExQCcv28ZW4yrcFnz0vma3vgoenSXA.g8_Ird2Y0itTCDP61du-Yg",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyR0NNIiwidGFnIjoiWC05UkNVWVh4U3NRelcwelVJS01VUSIsImFsZyI6IkEyNTZHQ01LVyIsIml2IjoiY3JNMnJfa3RrdWpyQ1h5OSJ9.c0q2jCxxV4y1h9u_Xvn7FqUDnbkmNEG4.S_noOTZKuUo9z1l6.ez0RdA25vXMUGH96iXmj3DEVox0J7TasJMnzgg.RbuSPTte_NzTtEEokbc5Ig",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2R0NNIiwidGFnIjoiWmwyaDFpUW11QWZWd2lJeVp5RHloZyIsImFsZyI6IkEyNTZHQ01LVyIsIml2Ijoib19xZmljb0N0NzNzRWo1QyJ9.NpJxRJ0aqcpekD6HU2u9e6_pL_11JXjWvjfeQnAKkZU.4c5qBcBBrMWi27Lf.NKwNIb4b6cRDJ1TwMKsPrjs7ADn6aNoBdQClVw.yNWmSSRBqQfIQObzj8zDqw",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwidGFnIjoiMXdwVEI3LWhjdzZUVXhCbVh2UzdhUSIsImFsZyI6IkEyNTZHQ01LVyIsIml2IjoiOUdIVnZJaDZ0a09vX2pHUSJ9.MFgIhp9mzlq9hoPqqKVKHJ3HL79EBYtV4iNhD63yqiU.UzW5iq8ou21VpZYJgKEN8A.1gOEzA4uAPvHP76GMfs9uLloAV10mKaxiZVAeL7iQA0.i1X_2i0bCAz-soXF9bI_zw",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwidGFnIjoiNThocUtsSk15Y1BFUEFRUlNfSzlNUSIsImFsZyI6IkEyNTZHQ01LVyIsIml2IjoiUDh3aTBWMTluVnZqNXpkOSJ9.FXidOWHNFJODO74Thq3J2cC-Z2B8UZkn7SikeosU0bUK6Jx_lzzmUZ-Lafadpdpj.iLfcDbpuBKFiSfiBzUQc7Q.VZK-aD7BFspqfvbwa0wE2wwWxdomzk2IKMetFe8bI44.7wC6rJRGa4x48xbYMd6NH9VzK8uNn4Cb",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwidGFnIjoicGcwOEpUcXdzMXdEaXBaRUlpVExoQSIsImFsZyI6IkEyNTZHQ01LVyIsIml2IjoiSlpodk9CdU1RUDFFZTZTNSJ9.wqVgTPm6TcYCTkpbwmn9sW4mgJROH2A3dIdSXo5oKIQUIVbQsmy7KXH8UYO2RS9slMGtb869C8o0My67GKg9dQ.ogrRiLlqjB1S5j-7a05OwA.2Y_LyqhU4S_RXMsB74bxcBacd23J2Sp5Lblw-sOkaUY.XGMiYoU-f3GaEzSvG41vpJP2DMGbeDFoWmkUGLUjc4M",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTI1NktXIn0.QiIZm9NYfahqYFIbiaoUhCCHjotHMkup.EsU0XLn4FjzzCILn.WuCoQkm9vzo95E7hxBtfYpt-Mooc_vmSTyzj6Q.NbeeYVy6gQPlmhoWDrZwaQ",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiQTI1NktXIn0.1ol3j_Lt0Os3UMe2Gypj0o8b77k0FSmqD7kNRNoMa9U.vZ2HMTgN2dgUd42h.JvNcy8-c8sYzOC089VtFSg2BOQx3YF8CqSTuJw.t03LRioWWKN3d7SjinU6SQ",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiQTI1NktXIn0.gbkk03l1gyrE9qGEMVtORiyyUqKsgzbqjLd8lw0RQ07WWn--TV4BgA.J8ThH4ac2UhSsMIP.g-W1piEGrdi3tNwQDJXpYm3fQjTf82mtVCrCOg.-vY05P4kiB9FgF2vwrSeXQ",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTI1NktXIn0.k86pQs7gmQIzuIWRFwesF32XY2xi1WbYxi7XUf_CYlOlehwGCTINHg.3NcC9VzfQgsECISKf4xy-g.v2amdo-rgeGsg-II_tvPukX9D-KAP27xxf2uQJ277Ws.E4LIE3fte3glAnPpnd8D9Q",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiQTI1NktXIn0.b8iN0Am3fCUvj7sBd7Z0lpfzBjh1MOgojV7J5rDfrcTU3b35RGYgEV1RdcrtUTBgUwITDjmU7jM.wsSDBFghDga_ERv36I2AOg.6uJsucCb2YReFOJGBdo4zidTIKLUmZBIXfm_M0AJpKk.YwdAfXI3HHcw2wLSnfCRtw4huZQtSKhz",
+			"eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiQTI1NktXIn0.akY9pHCbkHPh5VpXIrX0At41XnJIKBR9iMMkf301vKeJNAZYJTxWzeJhFd-DhQ47tMctc3YYkwZkQ5I_9fGYb_f0oBcw4esh.JNwuuHud78h6S99NO1oBQQ.0RwckPYATBgvw67upkAQ1AezETHc-gh3rryz19i5ryc.3XClRTScgzfMgLCHxHHoRF8mm9VVGXv_Ahtx65PskKQ",
+		},
+	}
+
+	for i, msgs := range aesSampleMessages {
+		for _, msg := range msgs {
+			obj, err := ParseEncrypted(msg)
+			if err != nil {
+				t.Error("unable to parse message", msg, err)
+				continue
+			}
+			plaintext, err := obj.Decrypt(aesTestKeys[i])
+			if err != nil {
+				t.Error("unable to decrypt message", msg, err)
+				continue
+			}
+			if string(plaintext) != "Lorem ipsum dolor sit amet" {
+				t.Error("plaintext is not what we expected for msg", msg)
+			}
+		}
+	}
+}
+
+// Test vectors generated with jose4j
+func TestSampleJose4jJWEMessagesECDH(t *testing.T) {
+	ecTestKey := &ecdsa.PrivateKey{
+		PublicKey: ecdsa.PublicKey{
+			Curve: elliptic.P256(),
+			X:     fromBase64Int("weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ"),
+			Y:     fromBase64Int("e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck"),
+		},
+		D: fromBase64Int("VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw"),
+	}
+
+	ecSampleMessages := []string{
+		"eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImVwayI6eyJrdHkiOiJFQyIsIngiOiJTQzAtRnJHUkVvVkpKSmg1TGhORmZqZnFXMC1XSUFyd3RZMzJzQmFQVVh3IiwieSI6ImFQMWlPRENveU9laTVyS1l2VENMNlRMZFN5UEdUN0djMnFsRnBwNXdiWFEiLCJjcnYiOiJQLTI1NiJ9fQ..3mifklTnTTGuA_etSUBBCw.dj8KFM8OlrQ3rT35nHcHZ7A5p84VB2OZb054ghSjS-M.KOIgnJjz87LGqMtikXGxXw",
+		"eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTE5MkNCQy1IUzM4NCIsImVwayI6eyJrdHkiOiJFQyIsIngiOiJUaHRGc0lRZ1E5MkZOYWFMbUFDQURLbE93dmNGVlRORHc4ampfWlJidUxjIiwieSI6IjJmRDZ3UXc3YmpYTm1nVThXMGpFbnl5ZUZkX3Y4ZmpDa3l1R29vTFhGM0EiLCJjcnYiOiJQLTI1NiJ9fQ..90zFayMkKc-fQC_19f6P3A.P1Y_7lMnfkUQOXW_en31lKZ3zAn1nEYn6fXLjmyVPrQ.hrgwy1cePVfhMWT0h-crKTXldglHZ-4g",
+		"eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImVwayI6eyJrdHkiOiJFQyIsIngiOiI5R1Z6c3VKNWgySl96UURVUFR3WU5zUkFzVzZfY2RzN0pELVQ2RDREQ1ZVIiwieSI6InFZVGl1dVU4aTB1WFpoaS14VGlRNlZJQm5vanFoWENPVnpmWm1pR2lRTEUiLCJjcnYiOiJQLTI1NiJ9fQ..v2reRlDkIsw3eWEsTCc1NA.0qakrFdbhtBCTSl7EREf9sxgHBP9I-Xw29OTJYnrqP8.54ozViEBYYmRkcKp7d2Ztt4hzjQ9Vb5zCeijN_RQrcI",
+		"eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkExMjhDQkMtSFMyNTYiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiOElUemg3VVFaaUthTWtfME9qX1hFaHZENXpUWjE2Ti13WVdjeTJYUC1tdyIsInkiOiJPNUJiVEk0bUFpU005ZmpCejBRU3pXaU5vbnl3cWlQLUN0RGgwdnNGYXNRIiwiY3J2IjoiUC0yNTYifX0.D3DP3wqPvJv4TYYfhnfrOG6nsM-MMH_CqGfnOGjgdXHNF7xRwEJBOA.WL9Kz3gNYA7S5Rs5mKcXmA.EmQkXhO_nFqAwxJWaM0DH4s3pmCscZovB8YWJ3Ru4N8.Bf88uzwfxiyTjpejU5B0Ng",
+		"eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkExOTJDQkMtSFMzODQiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiMjlJMk4zRkF0UlBlNGhzYjRLWlhTbmVyV0wyTVhtSUN1LXJJaXhNSHpJQSIsInkiOiJvMjY1bzFReEdmbDhzMHQ0U1JROS00RGNpc3otbXh4NlJ6WVF4SktyeWpJIiwiY3J2IjoiUC0yNTYifX0.DRmsmXz6fCnLc_njDIKdpM7Oc4jTqd_yd9J94TOUksAstEUkAl9Ie3Wg-Ji_LzbdX2xRLXIimcw.FwJOHPQhnqKJCfxt1_qRnQ.ssx3q1ZYILsMTln5q-K8HVn93BVPI5ViusstKMxZzRs.zzcfzWNYSdNDdQ4CiHfymj0bePaAbVaT",
+		"eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiRUp6bTViQnRzVXJNYTl2Y1Q2d1hZRXI3ZjNMcjB0N1V4SDZuZzdGcFF0VSIsInkiOiJRYTNDSDllVTFXYjItdFdVSDN3Sk9fTDVMZXRsRUlMQWNkNE9XR2tFd0hZIiwiY3J2IjoiUC0yNTYifX0.5WxwluZpVWAOJdVrsnDIlEc4_wfRE1gXOaQyx_rKkElNz157Ykf-JsAD7aEvXfx--NKF4js5zYyjeCtxWBhRWPOoNNZJlqV_.Iuo82-qsP2S1SgQQklAnrw.H4wB6XoLKOKWCu6Y3LPAEuHkvyvr-xAh4IBm53uRF8g._fOLKq0bqDZ8KNjni_MJ4olHNaYz376dV9eNmp9O9PU",
+		"eyJhbGciOiJFQ0RILUVTK0ExOTJLVyIsImVuYyI6IkExMjhDQkMtSFMyNTYiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiZktNSG5sRkoxajBTSnJ3WGtVWlpaX3BtWHdUQlJtcHhlaTkxdUpaczUycyIsInkiOiJLRkxKaXhEUTJQcjEybWp1aFdYb3pna2U1V3lhWnhmTWlxZkJ0OEJpbkRvIiwiY3J2IjoiUC0yNTYifX0.2LSD2Mw4tyYJyfsmpVmzBtJRd12jMEYGdlhFbaXIbKi5A33CGNQ1tg.s40aAjmZOvK8Us86FCBdHg.jpYSMAKp___oMCoWM495mTfbi_YC80ObeoCmGE3H_gs.A6V-jJJRY1yz24CaXGUbzg",
+		"eyJhbGciOiJFQ0RILUVTK0ExOTJLVyIsImVuYyI6IkExOTJDQkMtSFMzODQiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiSDRxcFUzeWtuRktWRnV4SmxLa3NZSE5ieHF3aXM0WWtCVVFHVE1Td05JQSIsInkiOiJHb0lpRUZaUGRRSHJCbVR4ZTA3akJoZmxrdWNqUjVoX1QwNWVXc3Zib0prIiwiY3J2IjoiUC0yNTYifX0.KTrwwV2uzD--gf3PGG-kjEAGgi7u0eMqZPZfa4kpyFGm3x8t2m1NHdz3t9rfiqjuaqsxPKhF4gs.cu16fEOzYaSxhHu_Ht9w4g.BRJdxVBI9spVtY5KQ6gTR4CNcKvmLUMKZap0AO-RF2I.DZyUaa2p6YCIaYtjWOjC9GN_VIYgySlZ",
+		"eyJhbGciOiJFQ0RILUVTK0ExOTJLVyIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoieDBYSGRkSGM2Q0ktSnlfbUVMOEZZRExhWnV0UkVFczR4c3BMQmcwZk1jbyIsInkiOiJEa0xzOUJGTlBkTTVTNkpLYVJ3cnV1TWMwcUFzWW9yNW9fZWp6NXBNVXFrIiwiY3J2IjoiUC0yNTYifX0.mfCxJ7JYIqTMqcAh5Vp2USF0eF7OhOeluqda7YagOUJNwxA9wC9o23DSoLUylfrZUfanZrJJJcG69awlv-LY7anOLHlp3Ht5.ec48A_JWb4qa_PVHWZaTfQ.kDAjIDb3LzJpfxNh-DiAmAuaKMYaOGSTb0rkiJLuVeY.oxGCpPlii4pr89XMk4b9s084LucTqPGU6TLbOW2MZoc",
+		"eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhDQkMtSFMyNTYiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiQXB5TnlqU2d0bmRUcFg0eENYenNDRnZva1l3X18weXg2dGRUYzdPUUhIMCIsInkiOiJYUHdHMDVDaW1vOGlhWmxZbDNsMEp3ZllhY1FZWHFuM2RRZEJUWFpldDZBIiwiY3J2IjoiUC0yNTYifX0.yTA2PwK9IPqkaGPenZ9R-gOn9m9rvcSEfuX_Nm8AkuwHIYLzzYeAEA.ZW1F1iyHYKfo-YoanNaIVg.PouKQD94DlPA5lbpfGJXY-EJhidC7l4vSayVN2vVzvA.MexquqtGaXKUvX7WBmD4bA",
+		"eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExOTJDQkMtSFMzODQiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiaDRWeGNzNVUzWk1fTlp4WmJxQ3hMTVB5UmEtR2ktSVNZa0xDTzE1RHJkZyIsInkiOiJFeVotS3dWNVE5OXlnWk5zU0lpSldpR3hqbXNLUk1WVE5sTTNSd1VYTFRvIiwiY3J2IjoiUC0yNTYifX0.wo56VISyL1QAbi2HLuVut5NGF2FvxKt7B8zHzJ3FpmavPozfbVZV08-GSYQ6jLQWJ4xsO80I4Kg.3_9Bo5ozvD96WHGhqp_tfQ.48UkJ6jk6WK70QItb2QZr0edKH7O-aMuVahTEeqyfW4.ulMlY2tbC341ct20YSmNdtc84FRz1I4g",
+		"eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiN0xZRzZZWTJkel9ZaGNvNnRCcG1IX0tPREQ2X2hwX05tajdEc1c2RXgxcyIsInkiOiI5Y2lPeDcwUkdGT0tpVnBRX0NHQXB5NVlyeThDazBmUkpwNHVrQ2tjNmQ0IiwiY3J2IjoiUC0yNTYifX0.bWwW3J80k46HG1fQAZxUroko2OO8OKkeRavr_o3AnhJDMvp78OR229x-fZUaBm4uWv27_Yjm0X9T2H2lhlIli2Rl9v1PNC77.1NmsJBDGI1fDjRzyc4mtyA.9KfCFynQj7LmJq08qxAG4c-6ZPz1Lh3h3nUbgVwB0TI.cqech0d8XHzWfkWqgKZq1SlAfmO0PUwOsNVkuByVGWk",
+	}
+
+	for _, msg := range ecSampleMessages {
+		obj, err := ParseEncrypted(msg)
+		if err != nil {
+			t.Error("unable to parse message", msg, err)
+			continue
+		}
+		plaintext, err := obj.Decrypt(ecTestKey)
+		if err != nil {
+			t.Error("unable to decrypt message", msg, err)
+			continue
+		}
+		if string(plaintext) != "Lorem ipsum dolor sit amet." {
+			t.Error("plaintext is not what we expected for msg", msg)
+		}
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/jwk.go b/Godeps/_workspace/src/github.com/square/go-jose/jwk.go
new file mode 100644
index 00000000..1f5e1347
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/jwk.go
@@ -0,0 +1,381 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rsa"
+	"encoding/json"
+	"fmt"
+	"math/big"
+	"reflect"
+	"strings"
+)
+
+// rawJsonWebKey represents a public or private key in JWK format, used for parsing/serializing.
+type rawJsonWebKey struct {
+	Use string      `json:"use,omitempty"`
+	Kty string      `json:"kty,omitempty"`
+	Kid string      `json:"kid,omitempty"`
+	Crv string      `json:"crv,omitempty"`
+	Alg string      `json:"alg,omitempty"`
+	K   *byteBuffer `json:"k,omitempty"`
+	X   *byteBuffer `json:"x,omitempty"`
+	Y   *byteBuffer `json:"y,omitempty"`
+	N   *byteBuffer `json:"n,omitempty"`
+	E   *byteBuffer `json:"e,omitempty"`
+	// -- Following fields are only used for private keys --
+	// RSA uses D, P and Q, while ECDSA uses only D. Fields Dp, Dq, and Qi are
+	// completely optional. Therefore for RSA/ECDSA, D != nil is a contract that
+	// we have a private key whereas D == nil means we have only a public key.
+	D  *byteBuffer `json:"d,omitempty"`
+	P  *byteBuffer `json:"p,omitempty"`
+	Q  *byteBuffer `json:"q,omitempty"`
+	Dp *byteBuffer `json:"dp,omitempty"`
+	Dq *byteBuffer `json:"dq,omitempty"`
+	Qi *byteBuffer `json:"qi,omitempty"`
+}
+
+// JsonWebKey represents a public or private key in JWK format.
+type JsonWebKey struct {
+	Key       interface{}
+	KeyID     string
+	Algorithm string
+	Use       string
+}
+
+// MarshalJSON serializes the given key to its JSON representation.
+func (k JsonWebKey) MarshalJSON() ([]byte, error) {
+	var raw *rawJsonWebKey
+	var err error
+
+	switch key := k.Key.(type) {
+	case *ecdsa.PublicKey:
+		raw, err = fromEcPublicKey(key)
+	case *rsa.PublicKey:
+		raw = fromRsaPublicKey(key)
+	case *ecdsa.PrivateKey:
+		raw, err = fromEcPrivateKey(key)
+	case *rsa.PrivateKey:
+		raw, err = fromRsaPrivateKey(key)
+	case []byte:
+		raw, err = fromSymmetricKey(key)
+	default:
+		return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	raw.Kid = k.KeyID
+	raw.Alg = k.Algorithm
+	raw.Use = k.Use
+
+	return json.Marshal(raw)
+}
+
+// UnmarshalJSON reads a key from its JSON representation.
+func (k *JsonWebKey) UnmarshalJSON(data []byte) (err error) {
+	var raw rawJsonWebKey
+	err = json.Unmarshal(data, &raw)
+	if err != nil {
+		return err
+	}
+
+	var key interface{}
+	switch raw.Kty {
+	case "EC":
+		if raw.D != nil {
+			key, err = raw.ecPrivateKey()
+		} else {
+			key, err = raw.ecPublicKey()
+		}
+	case "RSA":
+		if raw.D != nil {
+			key, err = raw.rsaPrivateKey()
+		} else {
+			key, err = raw.rsaPublicKey()
+		}
+	case "oct":
+		key, err = raw.symmetricKey()
+	default:
+		err = fmt.Errorf("square/go-jose: unkown json web key type '%s'", raw.Kty)
+	}
+
+	if err == nil {
+		*k = JsonWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use}
+	}
+	return
+}
+
+// JsonWebKeySet represents a JWK Set object.
+type JsonWebKeySet struct {
+	Keys []JsonWebKey `json:"keys"`
+}
+
+// Key convenience method returns keys by key ID. Specification states
+// that a JWK Set "SHOULD" use distinct key IDs, but allows for some
+// cases where they are not distinct. Hence method returns a slice
+// of JsonWebKeys.
+func (s *JsonWebKeySet) Key(kid string) []JsonWebKey {
+	var keys []JsonWebKey
+	for _, key := range s.Keys {
+		if key.KeyID == kid {
+			keys = append(keys, key)
+		}
+	}
+
+	return keys
+}
+
+const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
+const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`
+
+func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
+	coordLength := curveSize(curve)
+	crv, err := curveName(curve)
+	if err != nil {
+		return "", err
+	}
+
+	return fmt.Sprintf(ecThumbprintTemplate, crv,
+		newFixedSizeBuffer(x.Bytes(), coordLength).base64(),
+		newFixedSizeBuffer(y.Bytes(), coordLength).base64()), nil
+}
+
+func rsaThumbprintInput(n *big.Int, e int) (string, error) {
+	return fmt.Sprintf(rsaThumbprintTemplate,
+		newBufferFromInt(uint64(e)).base64(),
+		newBuffer(n.Bytes()).base64()), nil
+}
+
+// Thumbprint computes the JWK Thumbprint of a key using the
+// indicated hash algorithm.
+func (k *JsonWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
+	var input string
+	var err error
+	switch key := k.Key.(type) {
+	case *ecdsa.PublicKey:
+		input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
+	case *ecdsa.PrivateKey:
+		input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
+	case *rsa.PublicKey:
+		input, err = rsaThumbprintInput(key.N, key.E)
+	case *rsa.PrivateKey:
+		input, err = rsaThumbprintInput(key.N, key.E)
+	default:
+		return nil, fmt.Errorf("square/go-jose: unkown key type '%s'", reflect.TypeOf(key))
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	h := hash.New()
+	h.Write([]byte(input))
+	return h.Sum(nil), nil
+}
+
+func (key rawJsonWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
+	if key.N == nil || key.E == nil {
+		return nil, fmt.Errorf("square/go-jose: invalid RSA key, missing n/e values")
+	}
+
+	return &rsa.PublicKey{
+		N: key.N.bigInt(),
+		E: key.E.toInt(),
+	}, nil
+}
+
+func fromRsaPublicKey(pub *rsa.PublicKey) *rawJsonWebKey {
+	return &rawJsonWebKey{
+		Kty: "RSA",
+		N:   newBuffer(pub.N.Bytes()),
+		E:   newBufferFromInt(uint64(pub.E)),
+	}
+}
+
+func (key rawJsonWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
+	var curve elliptic.Curve
+	switch key.Crv {
+	case "P-256":
+		curve = elliptic.P256()
+	case "P-384":
+		curve = elliptic.P384()
+	case "P-521":
+		curve = elliptic.P521()
+	default:
+		return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
+	}
+
+	if key.X == nil || key.Y == nil {
+		return nil, fmt.Errorf("square/go-jose: invalid EC key, missing x/y values")
+	}
+
+	return &ecdsa.PublicKey{
+		Curve: curve,
+		X:     key.X.bigInt(),
+		Y:     key.Y.bigInt(),
+	}, nil
+}
+
+func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJsonWebKey, error) {
+	if pub == nil || pub.X == nil || pub.Y == nil {
+		return nil, fmt.Errorf("square/go-jose: invalid EC key (nil, or X/Y missing)")
+	}
+
+	name, err := curveName(pub.Curve)
+	if err != nil {
+		return nil, err
+	}
+
+	size := curveSize(pub.Curve)
+
+	xBytes := pub.X.Bytes()
+	yBytes := pub.Y.Bytes()
+
+	if len(xBytes) > size || len(yBytes) > size {
+		return nil, fmt.Errorf("square/go-jose: invalid EC key (X/Y too large)")
+	}
+
+	key := &rawJsonWebKey{
+		Kty: "EC",
+		Crv: name,
+		X:   newFixedSizeBuffer(xBytes, size),
+		Y:   newFixedSizeBuffer(yBytes, size),
+	}
+
+	return key, nil
+}
+
+func (key rawJsonWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
+	var missing []string
+	switch {
+	case key.N == nil:
+		missing = append(missing, "N")
+	case key.E == nil:
+		missing = append(missing, "E")
+	case key.D == nil:
+		missing = append(missing, "D")
+	case key.P == nil:
+		missing = append(missing, "P")
+	case key.Q == nil:
+		missing = append(missing, "Q")
+	}
+
+	if len(missing) > 0 {
+		return nil, fmt.Errorf("square/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
+	}
+
+	rv := &rsa.PrivateKey{
+		PublicKey: rsa.PublicKey{
+			N: key.N.bigInt(),
+			E: key.E.toInt(),
+		},
+		D: key.D.bigInt(),
+		Primes: []*big.Int{
+			key.P.bigInt(),
+			key.Q.bigInt(),
+		},
+	}
+
+	if key.Dp != nil {
+		rv.Precomputed.Dp = key.Dp.bigInt()
+	}
+	if key.Dq != nil {
+		rv.Precomputed.Dq = key.Dq.bigInt()
+	}
+	if key.Qi != nil {
+		rv.Precomputed.Qinv = key.Qi.bigInt()
+	}
+
+	err := rv.Validate()
+	return rv, err
+}
+
+func fromRsaPrivateKey(rsa *rsa.PrivateKey) (*rawJsonWebKey, error) {
+	if len(rsa.Primes) != 2 {
+		return nil, ErrUnsupportedKeyType
+	}
+
+	raw := fromRsaPublicKey(&rsa.PublicKey)
+
+	raw.D = newBuffer(rsa.D.Bytes())
+	raw.P = newBuffer(rsa.Primes[0].Bytes())
+	raw.Q = newBuffer(rsa.Primes[1].Bytes())
+
+	return raw, nil
+}
+
+func (key rawJsonWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
+	var curve elliptic.Curve
+	switch key.Crv {
+	case "P-256":
+		curve = elliptic.P256()
+	case "P-384":
+		curve = elliptic.P384()
+	case "P-521":
+		curve = elliptic.P521()
+	default:
+		return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
+	}
+
+	if key.X == nil || key.Y == nil || key.D == nil {
+		return nil, fmt.Errorf("square/go-jose: invalid EC private key, missing x/y/d values")
+	}
+
+	return &ecdsa.PrivateKey{
+		PublicKey: ecdsa.PublicKey{
+			Curve: curve,
+			X:     key.X.bigInt(),
+			Y:     key.Y.bigInt(),
+		},
+		D: key.D.bigInt(),
+	}, nil
+}
+
+func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJsonWebKey, error) {
+	raw, err := fromEcPublicKey(&ec.PublicKey)
+	if err != nil {
+		return nil, err
+	}
+
+	if ec.D == nil {
+		return nil, fmt.Errorf("square/go-jose: invalid EC private key")
+	}
+
+	raw.D = newBuffer(ec.D.Bytes())
+
+	return raw, nil
+}
+
+func fromSymmetricKey(key []byte) (*rawJsonWebKey, error) {
+	return &rawJsonWebKey{
+		Kty: "oct",
+		K:   newBuffer(key),
+	}, nil
+}
+
+func (key rawJsonWebKey) symmetricKey() ([]byte, error) {
+	if key.K == nil {
+		return nil, fmt.Errorf("square/go-jose: invalid OCT (symmetric) key, missing k value")
+	}
+	return key.K.bytes(), nil
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/jwk_test.go b/Godeps/_workspace/src/github.com/square/go-jose/jwk_test.go
new file mode 100644
index 00000000..ea3bf8ca
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/jwk_test.go
@@ -0,0 +1,576 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rsa"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"math/big"
+	"reflect"
+	"testing"
+)
+
+func TestCurveSize(t *testing.T) {
+	size256 := curveSize(elliptic.P256())
+	size384 := curveSize(elliptic.P384())
+	size521 := curveSize(elliptic.P521())
+	if size256 != 32 {
+		t.Error("P-256 have 32 bytes")
+	}
+	if size384 != 48 {
+		t.Error("P-384 have 48 bytes")
+	}
+	if size521 != 66 {
+		t.Error("P-521 have 66 bytes")
+	}
+}
+
+func TestRoundtripRsaPrivate(t *testing.T) {
+	jwk, err := fromRsaPrivateKey(rsaTestKey)
+	if err != nil {
+		t.Error("problem constructing JWK from rsa key", err)
+	}
+
+	rsa2, err := jwk.rsaPrivateKey()
+	if err != nil {
+		t.Error("problem converting RSA private -> JWK", err)
+	}
+
+	if rsa2.N.Cmp(rsaTestKey.N) != 0 {
+		t.Error("RSA private N mismatch")
+	}
+	if rsa2.E != rsaTestKey.E {
+		t.Error("RSA private E mismatch")
+	}
+	if rsa2.D.Cmp(rsaTestKey.D) != 0 {
+		t.Error("RSA private D mismatch")
+	}
+	if len(rsa2.Primes) != 2 {
+		t.Error("RSA private roundtrip expected two primes")
+	}
+	if rsa2.Primes[0].Cmp(rsaTestKey.Primes[0]) != 0 {
+		t.Error("RSA private P mismatch")
+	}
+	if rsa2.Primes[1].Cmp(rsaTestKey.Primes[1]) != 0 {
+		t.Error("RSA private Q mismatch")
+	}
+}
+
+func TestRsaPrivateInsufficientPrimes(t *testing.T) {
+	brokenRsaPrivateKey := rsa.PrivateKey{
+		PublicKey: rsa.PublicKey{
+			N: rsaTestKey.N,
+			E: rsaTestKey.E,
+		},
+		D:      rsaTestKey.D,
+		Primes: []*big.Int{rsaTestKey.Primes[0]},
+	}
+
+	_, err := fromRsaPrivateKey(&brokenRsaPrivateKey)
+	if err != ErrUnsupportedKeyType {
+		t.Error("expected unsupported key type error, got", err)
+	}
+}
+
+func TestRsaPrivateExcessPrimes(t *testing.T) {
+	brokenRsaPrivateKey := rsa.PrivateKey{
+		PublicKey: rsa.PublicKey{
+			N: rsaTestKey.N,
+			E: rsaTestKey.E,
+		},
+		D: rsaTestKey.D,
+		Primes: []*big.Int{
+			rsaTestKey.Primes[0],
+			rsaTestKey.Primes[1],
+			big.NewInt(3),
+		},
+	}
+
+	_, err := fromRsaPrivateKey(&brokenRsaPrivateKey)
+	if err != ErrUnsupportedKeyType {
+		t.Error("expected unsupported key type error, got", err)
+	}
+}
+
+func TestRoundtripEcPublic(t *testing.T) {
+	for i, ecTestKey := range []*ecdsa.PrivateKey{ecTestKey256, ecTestKey384, ecTestKey521} {
+		jwk, err := fromEcPublicKey(&ecTestKey.PublicKey)
+
+		ec2, err := jwk.ecPublicKey()
+		if err != nil {
+			t.Error("problem converting ECDSA private -> JWK", i, err)
+		}
+
+		if !reflect.DeepEqual(ec2.Curve, ecTestKey.Curve) {
+			t.Error("ECDSA private curve mismatch", i)
+		}
+		if ec2.X.Cmp(ecTestKey.X) != 0 {
+			t.Error("ECDSA X mismatch", i)
+		}
+		if ec2.Y.Cmp(ecTestKey.Y) != 0 {
+			t.Error("ECDSA Y mismatch", i)
+		}
+	}
+}
+
+func TestRoundtripEcPrivate(t *testing.T) {
+	for i, ecTestKey := range []*ecdsa.PrivateKey{ecTestKey256, ecTestKey384, ecTestKey521} {
+		jwk, err := fromEcPrivateKey(ecTestKey)
+
+		ec2, err := jwk.ecPrivateKey()
+		if err != nil {
+			t.Error("problem converting ECDSA private -> JWK", i, err)
+		}
+
+		if !reflect.DeepEqual(ec2.Curve, ecTestKey.Curve) {
+			t.Error("ECDSA private curve mismatch", i)
+		}
+		if ec2.X.Cmp(ecTestKey.X) != 0 {
+			t.Error("ECDSA X mismatch", i)
+		}
+		if ec2.Y.Cmp(ecTestKey.Y) != 0 {
+			t.Error("ECDSA Y mismatch", i)
+		}
+		if ec2.D.Cmp(ecTestKey.D) != 0 {
+			t.Error("ECDSA D mismatch", i)
+		}
+	}
+}
+
+func TestMarshalUnmarshal(t *testing.T) {
+	kid := "DEADBEEF"
+
+	for i, key := range []interface{}{ecTestKey256, ecTestKey384, ecTestKey521, rsaTestKey} {
+		for _, use := range []string{"", "sig", "enc"} {
+			jwk := JsonWebKey{Key: key, KeyID: kid, Algorithm: "foo"}
+			if use != "" {
+				jwk.Use = use
+			}
+
+			jsonbar, err := jwk.MarshalJSON()
+			if err != nil {
+				t.Error("problem marshaling", i, err)
+			}
+
+			var jwk2 JsonWebKey
+			err = jwk2.UnmarshalJSON(jsonbar)
+			if err != nil {
+				t.Error("problem unmarshalling", i, err)
+			}
+
+			jsonbar2, err := jwk2.MarshalJSON()
+			if err != nil {
+				t.Error("problem marshaling", i, err)
+			}
+
+			if !bytes.Equal(jsonbar, jsonbar2) {
+				t.Error("roundtrip should not lose information", i)
+			}
+			if jwk2.KeyID != kid {
+				t.Error("kid did not roundtrip JSON marshalling", i)
+			}
+
+			if jwk2.Algorithm != "foo" {
+				t.Error("alg did not roundtrip JSON marshalling", i)
+			}
+
+			if jwk2.Use != use {
+				t.Error("use did not roundtrip JSON marshalling", i)
+			}
+		}
+	}
+}
+
+func TestMarshalNonPointer(t *testing.T) {
+	type EmbedsKey struct {
+		Key JsonWebKey
+	}
+
+	keyJson := []byte(`{
+		"e": "AQAB",
+		"kty": "RSA",
+		"n": "vd7rZIoTLEe-z1_8G1FcXSw9CQFEJgV4g9V277sER7yx5Qjz_Pkf2YVth6wwwFJEmzc0hoKY-MMYFNwBE4hQHw"
+	}`)
+	var parsedKey JsonWebKey
+	err := json.Unmarshal(keyJson, &parsedKey)
+	if err != nil {
+		t.Error(fmt.Sprintf("Error unmarshalling key: %v", err))
+		return
+	}
+	ek := EmbedsKey{
+		Key: parsedKey,
+	}
+	out, err := json.Marshal(ek)
+	if err != nil {
+		t.Error(fmt.Sprintf("Error marshalling JSON: %v", err))
+		return
+	}
+	expected := "{\"Key\":{\"kty\":\"RSA\",\"n\":\"vd7rZIoTLEe-z1_8G1FcXSw9CQFEJgV4g9V277sER7yx5Qjz_Pkf2YVth6wwwFJEmzc0hoKY-MMYFNwBE4hQHw\",\"e\":\"AQAB\"}}"
+	if string(out) != expected {
+		t.Error("Failed to marshal embedded non-pointer JWK properly:", string(out))
+	}
+}
+
+func TestMarshalUnmarshalInvalid(t *testing.T) {
+	// Make an invalid curve coordinate by creating a byte array that is one
+	// byte too large, and setting the first byte to 1 (otherwise it's just zero).
+	invalidCoord := make([]byte, curveSize(ecTestKey256.Curve)+1)
+	invalidCoord[0] = 1
+
+	keys := []interface{}{
+		// Empty keys
+		&rsa.PrivateKey{},
+		&ecdsa.PrivateKey{},
+		// Invalid keys
+		&ecdsa.PrivateKey{
+			PublicKey: ecdsa.PublicKey{
+				// Missing values in pub key
+				Curve: elliptic.P256(),
+			},
+		},
+		&ecdsa.PrivateKey{
+			PublicKey: ecdsa.PublicKey{
+				// Invalid curve
+				Curve: nil,
+				X:     ecTestKey256.X,
+				Y:     ecTestKey256.Y,
+			},
+		},
+		&ecdsa.PrivateKey{
+			// Valid pub key, but missing priv key values
+			PublicKey: ecTestKey256.PublicKey,
+		},
+		&ecdsa.PrivateKey{
+			// Invalid pub key, values too large
+			PublicKey: ecdsa.PublicKey{
+				Curve: ecTestKey256.Curve,
+				X:     big.NewInt(0).SetBytes(invalidCoord),
+				Y:     big.NewInt(0).SetBytes(invalidCoord),
+			},
+			D: ecTestKey256.D,
+		},
+		nil,
+	}
+
+	for i, key := range keys {
+		jwk := JsonWebKey{Key: key}
+		_, err := jwk.MarshalJSON()
+		if err == nil {
+			t.Error("managed to serialize invalid key", i)
+		}
+	}
+}
+
+func TestWebKeyVectorsInvalid(t *testing.T) {
+	keys := []string{
+		// Invalid JSON
+		"{X",
+		// Empty key
+		"{}",
+		// Invalid RSA keys
+		`{"kty":"RSA"}`,
+		`{"kty":"RSA","e":""}`,
+		`{"kty":"RSA","e":"XXXX"}`,
+		`{"kty":"RSA","d":"XXXX"}`,
+		// Invalid EC keys
+		`{"kty":"EC","crv":"ABC"}`,
+		`{"kty":"EC","crv":"P-256"}`,
+		`{"kty":"EC","crv":"P-256","d":"XXX"}`,
+		`{"kty":"EC","crv":"ABC","d":"dGVzdA","x":"dGVzdA"}`,
+		`{"kty":"EC","crv":"P-256","d":"dGVzdA","x":"dGVzdA"}`,
+	}
+
+	for _, key := range keys {
+		var jwk2 JsonWebKey
+		err := jwk2.UnmarshalJSON([]byte(key))
+		if err == nil {
+			t.Error("managed to parse invalid key:", key)
+		}
+	}
+}
+
+// Test vectors from RFC 7520
+var cookbookJWKs = []string{
+	// EC Public
+	stripWhitespace(`{
+     "kty": "EC",
+     "kid": "bilbo.baggins@hobbiton.example",
+     "use": "sig",
+     "crv": "P-521",
+     "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9
+         A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
+     "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVy
+         SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1"
+   }`),
+
+	// EC Private
+	stripWhitespace(`{
+     "kty": "EC",
+     "kid": "bilbo.baggins@hobbiton.example",
+     "use": "sig",
+     "crv": "P-521",
+     "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9
+           A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
+     "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVy
+           SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1",
+     "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zb
+           KipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt"
+   }`),
+
+	// RSA Public
+	stripWhitespace(`{
+     "kty": "RSA",
+     "kid": "bilbo.baggins@hobbiton.example",
+     "use": "sig",
+     "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT
+         -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV
+         wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-
+         oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde
+         3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC
+         LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g
+         HdrNP5zw",
+     "e": "AQAB"
+   }`),
+
+	// RSA Private
+	stripWhitespace(`{"kty":"RSA",
+      "kid":"juliet@capulet.lit",
+      "use":"enc",
+      "n":"t6Q8PWSi1dkJj9hTP8hNYFlvadM7DflW9mWepOJhJ66w7nyoK1gPNqFMSQRy
+           O125Gp-TEkodhWr0iujjHVx7BcV0llS4w5ACGgPrcAd6ZcSR0-Iqom-QFcNP
+           8Sjg086MwoqQU_LYywlAGZ21WSdS_PERyGFiNnj3QQlO8Yns5jCtLCRwLHL0
+           Pb1fEv45AuRIuUfVcPySBWYnDyGxvjYGDSM-AqWS9zIQ2ZilgT-GqUmipg0X
+           OC0Cc20rgLe2ymLHjpHciCKVAbY5-L32-lSeZO-Os6U15_aXrk9Gw8cPUaX1
+           _I8sLGuSiVdt3C_Fn2PZ3Z8i744FPFGGcG1qs2Wz-Q",
+      "e":"AQAB",
+      "d":"GRtbIQmhOZtyszfgKdg4u_N-R_mZGU_9k7JQ_jn1DnfTuMdSNprTeaSTyWfS
+           NkuaAwnOEbIQVy1IQbWVV25NY3ybc_IhUJtfri7bAXYEReWaCl3hdlPKXy9U
+           vqPYGR0kIXTQRqns-dVJ7jahlI7LyckrpTmrM8dWBo4_PMaenNnPiQgO0xnu
+           ToxutRZJfJvG4Ox4ka3GORQd9CsCZ2vsUDmsXOfUENOyMqADC6p1M3h33tsu
+           rY15k9qMSpG9OX_IJAXmxzAh_tWiZOwk2K4yxH9tS3Lq1yX8C1EWmeRDkK2a
+           hecG85-oLKQt5VEpWHKmjOi_gJSdSgqcN96X52esAQ",
+      "p":"2rnSOV4hKSN8sS4CgcQHFbs08XboFDqKum3sc4h3GRxrTmQdl1ZK9uw-PIHf
+           QP0FkxXVrx-WE-ZEbrqivH_2iCLUS7wAl6XvARt1KkIaUxPPSYB9yk31s0Q8
+           UK96E3_OrADAYtAJs-M3JxCLfNgqh56HDnETTQhH3rCT5T3yJws",
+      "q":"1u_RiFDP7LBYh3N4GXLT9OpSKYP0uQZyiaZwBtOCBNJgQxaj10RWjsZu0c6I
+           edis4S7B_coSKB0Kj9PaPaBzg-IySRvvcQuPamQu66riMhjVtG6TlV8CLCYK
+           rYl52ziqK0E_ym2QnkwsUX7eYTB7LbAHRK9GqocDE5B0f808I4s",
+      "dp":"KkMTWqBUefVwZ2_Dbj1pPQqyHSHjj90L5x_MOzqYAJMcLMZtbUtwKqvVDq3
+           tbEo3ZIcohbDtt6SbfmWzggabpQxNxuBpoOOf_a_HgMXK_lhqigI4y_kqS1w
+           Y52IwjUn5rgRrJ-yYo1h41KR-vz2pYhEAeYrhttWtxVqLCRViD6c",
+      "dq":"AvfS0-gRxvn0bwJoMSnFxYcK1WnuEjQFluMGfwGitQBWtfZ1Er7t1xDkbN9
+           GQTB9yqpDoYaN06H7CFtrkxhJIBQaj6nkF5KKS3TQtQ5qCzkOkmxIe3KRbBy
+           mXxkb5qwUpX5ELD5xFc6FeiafWYY63TmmEAu_lRFCOJ3xDea-ots",
+      "qi":"lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEoPwmUqq
+           abu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDMeAvmj4sm-Fp0o
+           Yu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu9HCJ-UsfSOI8"}`),
+}
+
+// SHA-256 thumbprints of the above keys, hex-encoded
+var cookbookJWKThumbprints = []string{
+	"747ae2dd2003664aeeb21e4753fe7402846170a16bc8df8f23a8cf06d3cbe793",
+	"747ae2dd2003664aeeb21e4753fe7402846170a16bc8df8f23a8cf06d3cbe793",
+	"f63838e96077ad1fc01c3f8405774dedc0641f558ebb4b40dccf5f9b6d66a932",
+	"0fc478f8579325fcee0d4cbc6d9d1ce21730a6e97e435d6008fb379b0ebe47d4",
+}
+
+func TestWebKeyVectorsValid(t *testing.T) {
+	for _, key := range cookbookJWKs {
+		var jwk2 JsonWebKey
+		err := jwk2.UnmarshalJSON([]byte(key))
+		if err != nil {
+			t.Error("unable to parse valid key:", key, err)
+		}
+	}
+}
+
+func TestThumbprint(t *testing.T) {
+	for i, key := range cookbookJWKs {
+		var jwk2 JsonWebKey
+		err := jwk2.UnmarshalJSON([]byte(key))
+		if err != nil {
+			t.Error("unable to parse valid key:", key, err)
+		}
+
+		tp, err := jwk2.Thumbprint(crypto.SHA256)
+		if err != nil {
+			t.Error("unable to compute thumbprint:", key, err)
+		}
+
+		tpHex := hex.EncodeToString(tp)
+		if cookbookJWKThumbprints[i] != tpHex {
+			t.Error("incorrect thumbprint:", i, cookbookJWKThumbprints[i], tpHex)
+		}
+	}
+}
+
+func TestMarshalUnmarshalJWKSet(t *testing.T) {
+	jwk1 := JsonWebKey{Key: rsaTestKey, KeyID: "ABCDEFG", Algorithm: "foo"}
+	jwk2 := JsonWebKey{Key: rsaTestKey, KeyID: "GFEDCBA", Algorithm: "foo"}
+	var set JsonWebKeySet
+	set.Keys = append(set.Keys, jwk1)
+	set.Keys = append(set.Keys, jwk2)
+
+	jsonbar, err := json.Marshal(&set)
+	if err != nil {
+		t.Error("problem marshalling set", err)
+	}
+	var set2 JsonWebKeySet
+	err = json.Unmarshal(jsonbar, &set2)
+	if err != nil {
+		t.Error("problem unmarshalling set", err)
+	}
+	jsonbar2, err := json.Marshal(&set2)
+	if err != nil {
+		t.Error("problem marshalling set", err)
+	}
+	if !bytes.Equal(jsonbar, jsonbar2) {
+		t.Error("roundtrip should not lose information")
+	}
+}
+
+var JWKSetDuplicates = stripWhitespace(`{
+     "keys": [{
+         "kty": "RSA",
+         "kid": "exclude-me",
+         "use": "sig",
+         "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT
+             -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV
+             wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-
+             oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde
+             3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC
+             LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g
+             HdrNP5zw",
+         "e": "AQAB"
+     }],
+     "keys": [{
+         "kty": "RSA",
+         "kid": "include-me",
+         "use": "sig",
+         "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT
+             -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV
+             wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-
+             oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde
+             3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC
+             LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g
+             HdrNP5zw",
+         "e": "AQAB"
+     }],
+     "custom": "exclude-me",
+     "custom": "include-me"
+   }`)
+
+func TestDuplicateJWKSetMembersIgnored(t *testing.T) {
+	type CustomSet struct {
+		JsonWebKeySet
+		CustomMember string `json:"custom"`
+	}
+	data := []byte(JWKSetDuplicates)
+	var set CustomSet
+	json.Unmarshal(data, &set)
+	if len(set.Keys) != 1 {
+		t.Error("expected only one key in set")
+	}
+	if set.Keys[0].KeyID != "include-me" {
+		t.Errorf("expected key with kid: \"include-me\", got: %s", set.Keys[0].KeyID)
+	}
+	if set.CustomMember != "include-me" {
+		t.Errorf("expected custom member value: \"include-me\", got: %s", set.CustomMember)
+	}
+}
+
+func TestJWKSetKey(t *testing.T) {
+	jwk1 := JsonWebKey{Key: rsaTestKey, KeyID: "ABCDEFG", Algorithm: "foo"}
+	jwk2 := JsonWebKey{Key: rsaTestKey, KeyID: "GFEDCBA", Algorithm: "foo"}
+	var set JsonWebKeySet
+	set.Keys = append(set.Keys, jwk1)
+	set.Keys = append(set.Keys, jwk2)
+	k := set.Key("ABCDEFG")
+	if len(k) != 1 {
+		t.Errorf("method should return slice with one key not %d", len(k))
+	}
+	if k[0].KeyID != "ABCDEFG" {
+		t.Error("method should return key with ID ABCDEFG")
+	}
+}
+
+func TestJWKSymmetricKey(t *testing.T) {
+	sample1 := `{"kty":"oct","alg":"A128KW","k":"GawgguFyGrWKav7AX4VKUg"}`
+	sample2 := `{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow","kid":"HMAC key used in JWS spec Appendix A.1 example"}`
+
+	var jwk1 JsonWebKey
+	json.Unmarshal([]byte(sample1), &jwk1)
+
+	if jwk1.Algorithm != "A128KW" {
+		t.Errorf("expected Algorithm to be A128KW, but was '%s'", jwk1.Algorithm)
+	}
+	expected1 := fromHexBytes("19ac2082e1721ab58a6afec05f854a52")
+	if !bytes.Equal(jwk1.Key.([]byte), expected1) {
+		t.Errorf("expected Key to be '%s', but was '%s'", hex.EncodeToString(expected1), hex.EncodeToString(jwk1.Key.([]byte)))
+	}
+
+	var jwk2 JsonWebKey
+	json.Unmarshal([]byte(sample2), &jwk2)
+
+	if jwk2.KeyID != "HMAC key used in JWS spec Appendix A.1 example" {
+		t.Errorf("expected KeyID to be 'HMAC key used in JWS spec Appendix A.1 example', but was '%s'", jwk2.KeyID)
+	}
+	expected2 := fromHexBytes(`
+    0323354b2b0fa5bc837e0665777ba68f5ab328e6f054c928a90f84b2d2502ebf
+    d3fb5a92d20647ef968ab4c377623d223d2e2172052e4f08c0cd9af567d080a3`)
+	if !bytes.Equal(jwk2.Key.([]byte), expected2) {
+		t.Errorf("expected Key to be '%s', but was '%s'", hex.EncodeToString(expected2), hex.EncodeToString(jwk2.Key.([]byte)))
+	}
+}
+
+func TestJWKSymmetricRoundtrip(t *testing.T) {
+	jwk1 := JsonWebKey{Key: []byte{1, 2, 3, 4}}
+	marshaled, err := jwk1.MarshalJSON()
+	if err != nil {
+		t.Errorf("failed to marshal valid JWK object", err)
+	}
+
+	var jwk2 JsonWebKey
+	err = jwk2.UnmarshalJSON(marshaled)
+	if err != nil {
+		t.Errorf("failed to unmarshal valid JWK object", err)
+	}
+
+	if !bytes.Equal(jwk1.Key.([]byte), jwk2.Key.([]byte)) {
+		t.Error("round-trip of symmetric JWK gave different raw keys")
+	}
+}
+
+func TestJWKSymmetricInvalid(t *testing.T) {
+	invalid := JsonWebKey{}
+	_, err := invalid.MarshalJSON()
+	if err == nil {
+		t.Error("excepted error on marshaling invalid symmetric JWK object")
+	}
+
+	var jwk JsonWebKey
+	err = jwk.UnmarshalJSON([]byte(`{"kty":"oct"}`))
+	if err == nil {
+		t.Error("excepted error on unmarshaling invalid symmetric JWK object")
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/jws.go b/Godeps/_workspace/src/github.com/square/go-jose/jws.go
new file mode 100644
index 00000000..5f836acd
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/jws.go
@@ -0,0 +1,253 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+)
+
+// rawJsonWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
+type rawJsonWebSignature struct {
+	Payload    *byteBuffer        `json:"payload,omitempty"`
+	Signatures []rawSignatureInfo `json:"signatures,omitempty"`
+	Protected  *byteBuffer        `json:"protected,omitempty"`
+	Header     *rawHeader         `json:"header,omitempty"`
+	Signature  *byteBuffer        `json:"signature,omitempty"`
+}
+
+// rawSignatureInfo represents a single JWS signature over the JWS payload and protected header.
+type rawSignatureInfo struct {
+	Protected *byteBuffer `json:"protected,omitempty"`
+	Header    *rawHeader  `json:"header,omitempty"`
+	Signature *byteBuffer `json:"signature,omitempty"`
+}
+
+// JsonWebSignature represents a signed JWS object after parsing.
+type JsonWebSignature struct {
+	payload    []byte
+	Signatures []Signature
+}
+
+// Signature represents a single signature over the JWS payload and protected header.
+type Signature struct {
+	// Header fields, such as the signature algorithm
+	Header JoseHeader
+
+	// The actual signature value
+	Signature []byte
+
+	protected *rawHeader
+	header    *rawHeader
+	original  *rawSignatureInfo
+}
+
+// ParseSigned parses an encrypted message in compact or full serialization format.
+func ParseSigned(input string) (*JsonWebSignature, error) {
+	input = stripWhitespace(input)
+	if strings.HasPrefix(input, "{") {
+		return parseSignedFull(input)
+	}
+
+	return parseSignedCompact(input)
+}
+
+// Get a header value
+func (sig Signature) mergedHeaders() rawHeader {
+	out := rawHeader{}
+	out.merge(sig.protected)
+	out.merge(sig.header)
+	return out
+}
+
+// Compute data to be signed
+func (obj JsonWebSignature) computeAuthData(signature *Signature) []byte {
+	var serializedProtected string
+
+	if signature.original != nil && signature.original.Protected != nil {
+		serializedProtected = signature.original.Protected.base64()
+	} else if signature.protected != nil {
+		serializedProtected = base64URLEncode(mustSerializeJSON(signature.protected))
+	} else {
+		serializedProtected = ""
+	}
+
+	return []byte(fmt.Sprintf("%s.%s",
+		serializedProtected,
+		base64URLEncode(obj.payload)))
+}
+
+// parseSignedFull parses a message in full format.
+func parseSignedFull(input string) (*JsonWebSignature, error) {
+	var parsed rawJsonWebSignature
+	err := json.Unmarshal([]byte(input), &parsed)
+	if err != nil {
+		return nil, err
+	}
+
+	return parsed.sanitized()
+}
+
+// sanitized produces a cleaned-up JWS object from the raw JSON.
+func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
+	if parsed.Payload == nil {
+		return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
+	}
+
+	obj := &JsonWebSignature{
+		payload:    parsed.Payload.bytes(),
+		Signatures: make([]Signature, len(parsed.Signatures)),
+	}
+
+	if len(parsed.Signatures) == 0 {
+		// No signatures array, must be flattened serialization
+		signature := Signature{}
+		if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
+			signature.protected = &rawHeader{}
+			err := json.Unmarshal(parsed.Protected.bytes(), signature.protected)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		if parsed.Header != nil && parsed.Header.Nonce != "" {
+			return nil, ErrUnprotectedNonce
+		}
+
+		signature.header = parsed.Header
+		signature.Signature = parsed.Signature.bytes()
+		// Make a fake "original" rawSignatureInfo to store the unprocessed
+		// Protected header. This is necessary because the Protected header can
+		// contain arbitrary fields not registered as part of the spec. See
+		// https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4
+		// If we unmarshal Protected into a rawHeader with its explicit list of fields,
+		// we cannot marshal losslessly. So we have to keep around the original bytes.
+		// This is used in computeAuthData, which will first attempt to use
+		// the original bytes of a protected header, and fall back on marshaling the
+		// header struct only if those bytes are not available.
+		signature.original = &rawSignatureInfo{
+			Protected: parsed.Protected,
+			Header:    parsed.Header,
+			Signature: parsed.Signature,
+		}
+
+		signature.Header = signature.mergedHeaders().sanitized()
+		obj.Signatures = append(obj.Signatures, signature)
+	}
+
+	for i, sig := range parsed.Signatures {
+		if sig.Protected != nil && len(sig.Protected.bytes()) > 0 {
+			obj.Signatures[i].protected = &rawHeader{}
+			err := json.Unmarshal(sig.Protected.bytes(), obj.Signatures[i].protected)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		// Check that there is not a nonce in the unprotected header
+		if sig.Header != nil && sig.Header.Nonce != "" {
+			return nil, ErrUnprotectedNonce
+		}
+
+		obj.Signatures[i].Signature = sig.Signature.bytes()
+
+		// Copy value of sig
+		original := sig
+
+		obj.Signatures[i].header = sig.Header
+		obj.Signatures[i].original = &original
+		obj.Signatures[i].Header = obj.Signatures[i].mergedHeaders().sanitized()
+	}
+
+	return obj, nil
+}
+
+// parseSignedCompact parses a message in compact format.
+func parseSignedCompact(input string) (*JsonWebSignature, error) {
+	parts := strings.Split(input, ".")
+	if len(parts) != 3 {
+		return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
+	}
+
+	rawProtected, err := base64URLDecode(parts[0])
+	if err != nil {
+		return nil, err
+	}
+
+	payload, err := base64URLDecode(parts[1])
+	if err != nil {
+		return nil, err
+	}
+
+	signature, err := base64URLDecode(parts[2])
+	if err != nil {
+		return nil, err
+	}
+
+	raw := &rawJsonWebSignature{
+		Payload:   newBuffer(payload),
+		Protected: newBuffer(rawProtected),
+		Signature: newBuffer(signature),
+	}
+	return raw.sanitized()
+}
+
+// CompactSerialize serializes an object using the compact serialization format.
+func (obj JsonWebSignature) CompactSerialize() (string, error) {
+	if len(obj.Signatures) != 1 || obj.Signatures[0].header != nil || obj.Signatures[0].protected == nil {
+		return "", ErrNotSupported
+	}
+
+	serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
+
+	return fmt.Sprintf(
+		"%s.%s.%s",
+		base64URLEncode(serializedProtected),
+		base64URLEncode(obj.payload),
+		base64URLEncode(obj.Signatures[0].Signature)), nil
+}
+
+// FullSerialize serializes an object using the full JSON serialization format.
+func (obj JsonWebSignature) FullSerialize() string {
+	raw := rawJsonWebSignature{
+		Payload: newBuffer(obj.payload),
+	}
+
+	if len(obj.Signatures) == 1 {
+		if obj.Signatures[0].protected != nil {
+			serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
+			raw.Protected = newBuffer(serializedProtected)
+		}
+		raw.Header = obj.Signatures[0].header
+		raw.Signature = newBuffer(obj.Signatures[0].Signature)
+	} else {
+		raw.Signatures = make([]rawSignatureInfo, len(obj.Signatures))
+		for i, signature := range obj.Signatures {
+			raw.Signatures[i] = rawSignatureInfo{
+				Header:    signature.header,
+				Signature: newBuffer(signature.Signature),
+			}
+
+			if signature.protected != nil {
+				raw.Signatures[i].Protected = newBuffer(mustSerializeJSON(signature.protected))
+			}
+		}
+	}
+
+	return string(mustSerializeJSON(raw))
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/jws_test.go b/Godeps/_workspace/src/github.com/square/go-jose/jws_test.go
new file mode 100644
index 00000000..d8f94c14
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/jws_test.go
@@ -0,0 +1,302 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func TestCompactParseJWS(t *testing.T) {
+	// Should parse
+	msg := "eyJhbGciOiJYWVoifQ.cGF5bG9hZA.c2lnbmF0dXJl"
+	_, err := ParseSigned(msg)
+	if err != nil {
+		t.Error("Unable to parse valid message:", err)
+	}
+
+	// Messages that should fail to parse
+	failures := []string{
+		// Not enough parts
+		"eyJhbGciOiJYWVoifQ.cGF5bG9hZA",
+		// Invalid signature
+		"eyJhbGciOiJYWVoifQ.cGF5bG9hZA.////",
+		// Invalid payload
+		"eyJhbGciOiJYWVoifQ.////.c2lnbmF0dXJl",
+		// Invalid header
+		"////.eyJhbGciOiJYWVoifQ.c2lnbmF0dXJl",
+		// Invalid header
+		"cGF5bG9hZA.cGF5bG9hZA.c2lnbmF0dXJl",
+	}
+
+	for i := range failures {
+		_, err = ParseSigned(failures[i])
+		if err == nil {
+			t.Error("Able to parse invalid message")
+		}
+	}
+}
+
+func TestFullParseJWS(t *testing.T) {
+	// Messages that should succeed to parse
+	successes := []string{
+		"{\"payload\":\"CUJD\",\"signatures\":[{\"protected\":\"e30\",\"header\":{\"kid\":\"XYZ\"},\"signature\":\"CUJD\"},{\"protected\":\"e30\",\"signature\":\"CUJD\"}]}",
+	}
+
+	for i := range successes {
+		_, err := ParseSigned(successes[i])
+		if err != nil {
+			t.Error("Unble to parse valid message", err, successes[i])
+		}
+	}
+
+	// Messages that should fail to parse
+	failures := []string{
+		// Empty
+		"{}",
+		// Invalid JSON
+		"{XX",
+		// Invalid protected header
+		"{\"payload\":\"CUJD\",\"signatures\":[{\"protected\":\"CUJD\",\"header\":{\"kid\":\"XYZ\"},\"signature\":\"CUJD\"}]}",
+		// Invalid protected header
+		"{\"payload\":\"CUJD\",\"protected\":\"CUJD\",\"header\":{\"kid\":\"XYZ\"},\"signature\":\"CUJD\"}",
+		// Invalid protected header
+		"{\"payload\":\"CUJD\",\"signatures\":[{\"protected\":\"###\",\"header\":{\"kid\":\"XYZ\"},\"signature\":\"CUJD\"}]}",
+		// Invalid payload
+		"{\"payload\":\"###\",\"signatures\":[{\"protected\":\"CUJD\",\"header\":{\"kid\":\"XYZ\"},\"signature\":\"CUJD\"}]}",
+		// Invalid payload
+		"{\"payload\":\"CUJD\",\"signatures\":[{\"protected\":\"e30\",\"header\":{\"kid\":\"XYZ\"},\"signature\":\"###\"}]}",
+	}
+
+	for i := range failures {
+		_, err := ParseSigned(failures[i])
+		if err == nil {
+			t.Error("Able to parse invalid message", err, failures[i])
+		}
+	}
+}
+
+func TestRejectUnprotectedJWSNonce(t *testing.T) {
+	// No need to test compact, since that's always protected
+
+	// Flattened JSON
+	input := `{
+		"header": { "nonce": "should-cause-an-error" },
+		"payload": "does-not-matter",
+		"signature": "does-not-matter"
+	}`
+	_, err := ParseSigned(input)
+	if err == nil {
+		t.Error("JWS with an unprotected nonce parsed as valid.")
+	} else if err != ErrUnprotectedNonce {
+		t.Errorf("Improper error for unprotected nonce: %v", err)
+	}
+
+	// Full JSON
+	input = `{
+		"payload": "does-not-matter",
+ 		"signatures": [{
+ 			"header": { "nonce": "should-cause-an-error" },
+			"signature": "does-not-matter"
+		}]
+	}`
+	_, err = ParseSigned(input)
+	if err == nil {
+		t.Error("JWS with an unprotected nonce parsed as valid.")
+	} else if err != ErrUnprotectedNonce {
+		t.Errorf("Improper error for unprotected nonce: %v", err)
+	}
+}
+
+func TestVerifyFlattenedWithIncludedUnprotectedKey(t *testing.T) {
+	input := `{
+			"header": {
+					"alg": "RS256",
+					"jwk": {
+							"e": "AQAB",
+							"kty": "RSA",
+							"n": "tSwgy3ORGvc7YJI9B2qqkelZRUC6F1S5NwXFvM4w5-M0TsxbFsH5UH6adigV0jzsDJ5imAechcSoOhAh9POceCbPN1sTNwLpNbOLiQQ7RD5mY_pSUHWXNmS9R4NZ3t2fQAzPeW7jOfF0LKuJRGkekx6tXP1uSnNibgpJULNc4208dgBaCHo3mvaE2HV2GmVl1yxwWX5QZZkGQGjNDZYnjFfa2DKVvFs0QbAk21ROm594kAxlRlMMrvqlf24Eq4ERO0ptzpZgm_3j_e4hGRD39gJS7kAzK-j2cacFQ5Qi2Y6wZI2p-FCq_wiYsfEAIkATPBiLKl_6d_Jfcvs_impcXQ"
+					}
+			},
+			"payload": "Zm9vCg",
+			"signature": "hRt2eYqBd_MyMRNIh8PEIACoFtmBi7BHTLBaAhpSU6zyDAFdEBaX7us4VB9Vo1afOL03Q8iuoRA0AT4akdV_mQTAQ_jhTcVOAeXPr0tB8b8Q11UPQ0tXJYmU4spAW2SapJIvO50ntUaqU05kZd0qw8-noH1Lja-aNnU-tQII4iYVvlTiRJ5g8_CADsvJqOk6FcHuo2mG643TRnhkAxUtazvHyIHeXMxydMMSrpwUwzMtln4ZJYBNx4QGEq6OhpAD_VSp-w8Lq5HOwGQoNs0bPxH1SGrArt67LFQBfjlVr94E1sn26p4vigXm83nJdNhWAMHHE9iV67xN-r29LT-FjA"
+	}`
+
+	jws, err := ParseSigned(input)
+	if err != nil {
+		t.Error("Unable to parse valid message.")
+	}
+	if len(jws.Signatures) != 1 {
+		t.Error("Too many or too few signatures.")
+	}
+	sig := jws.Signatures[0]
+	if sig.Header.JsonWebKey == nil {
+		t.Error("No JWK in signature header.")
+	}
+	payload, err := jws.Verify(sig.Header.JsonWebKey)
+	if err != nil {
+		t.Error(fmt.Sprintf("Signature did not validate: %v", err))
+	}
+	if string(payload) != "foo\n" {
+		t.Error(fmt.Sprintf("Payload was incorrect: '%s' should have been 'foo\\n'", string(payload)))
+	}
+}
+
+func TestVerifyFlattenedWithPrivateProtected(t *testing.T) {
+	// The protected field contains a Private Header Parameter name, per
+	// https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4
+	// Base64-decoded, it's '{"nonce":"8HIepUNFZUa-exKTrXVf4g"}'
+	input := `{"header":{"alg":"RS256","jwk":{"kty":"RSA","n":"7ixeydcbxxppzxrBphrW1atUiEZqTpiHDpI-79olav5XxAgWolHmVsJyxzoZXRxmtED8PF9-EICZWBGdSAL9ZTD0hLUCIsPcpdgT_LqNW3Sh2b2caPL2hbMF7vsXvnCGg9varpnHWuYTyRrCLUF9vM7ES-V3VCYTa7LcCSRm56Gg9r19qar43Z9kIKBBxpgt723v2cC4bmLmoAX2s217ou3uCpCXGLOeV_BesG4--Nl3pso1VhCfO85wEWjmW6lbv7Kg4d7Jdkv5DjDZfJ086fkEAYZVYGRpIgAvJBH3d3yKDCrSByUEud1bWuFjQBmMaeYOrVDXO_mbYg5PwUDMhw","e":"AQAB"}},"protected":"eyJub25jZSI6IjhISWVwVU5GWlVhLWV4S1RyWFZmNGcifQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzpmb29AYmFyLmNvbSJdfQ","signature":"AyvVGMgXsQ1zTdXrZxE_gyO63pQgotL1KbI7gv6Wi8I7NRy0iAOkDAkWcTQT9pcCYApJ04lXfEDZfP5i0XgcFUm_6spxi5mFBZU-NemKcvK9dUiAbXvb4hB3GnaZtZiuVnMQUb_ku4DOaFFKbteA6gOYCnED_x7v0kAPHIYrQnvIa-KZ6pTajbV9348zgh9TL7NgGIIsTcMHd-Jatr4z1LQ0ubGa8tS300hoDhVzfoDQaEetYjCo1drR1RmdEN1SIzXdHOHfubjA3ZZRbrF_AJnNKpRRoIwzu1VayOhRmdy1qVSQZq_tENF4VrQFycEL7DhG7JLoXC4T2p1urwMlsw"}`
+
+	jws, err := ParseSigned(input)
+	if err != nil {
+		t.Error("Unable to parse valid message.")
+	}
+	if len(jws.Signatures) != 1 {
+		t.Error("Too many or too few signatures.")
+	}
+	sig := jws.Signatures[0]
+	if sig.Header.JsonWebKey == nil {
+		t.Error("No JWK in signature header.")
+	}
+	payload, err := jws.Verify(sig.Header.JsonWebKey)
+	if err != nil {
+		t.Error(fmt.Sprintf("Signature did not validate: %v", err))
+	}
+	expected := "{\"contact\":[\"mailto:foo@bar.com\"]}"
+	if string(payload) != expected {
+		t.Error(fmt.Sprintf("Payload was incorrect: '%s' should have been '%s'", string(payload), expected))
+	}
+}
+
+// Test vectors generated with nimbus-jose-jwt
+func TestSampleNimbusJWSMessagesRSA(t *testing.T) {
+	rsaPublicKey, err := LoadPublicKey(fromBase64Bytes(`
+		MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3aLSGwbeX0ZA2Ha+EvELaIFGzO
+		91+Q15JQc/tdGdCgGW3XAbrh7ZUhDh1XKzbs+UOQxqn3Eq4YOx18IG0WsJSuCaHQIxnDlZ
+		t/GP8WLwjMC0izlJLm2SyfM/EEoNpmTC3w6MQ2dHK7SZ9Zoq+sKijQd+V7CYdr8zHMpDrd
+		NKoEcR0HjmvzzdMoUChhkGH5TaNbZyollULTggepaYUKS8QphqdSDMWiSetKG+g6V87lv6
+		CVYyK1FF6g7Esp5OOj5pNn3/bmF+7V+b7TvK91NCIlURCjE9toRgNoIP4TDnWRn/vvfZ3G
+		zNrtWmlizqz3r5KdvIs71ahWgMUSD4wfazrwIDAQAB`))
+	if err != nil {
+		panic(err)
+	}
+
+	rsaSampleMessages := []string{
+		"eyJhbGciOiJSUzI1NiJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.YHX849fvekz6wJGeyqnQhFqyHFcUXNJKj3o2w3ddR46YLlsCopUJrlifRU_ZuTWzpYxt5oC--T2eoqMhlCvltSWrE5_1_EumqiMfAYsZULx9E6Jns7q3w7mttonYFSIh7aR3-yg2HMMfTCgoAY1y_AZ4VjXwHDcZ5gu1oZDYgvZF4uXtCmwT6e5YtR1m8abiWPF8BgoTG_BD3KV6ClLj_QQiNFdfdxAMDw7vKVOKG1T7BFtz6cDs2Q3ILS4To5E2IjcVSSYS8mi77EitCrWmrqbK_G3WCdKeUFGnMnyuKXaCDy_7FLpAZ6Z5RomRr5iskXeJZdZqIKcJV8zl4fpsPA",
+		"eyJhbGciOiJSUzM4NCJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.meyfoOTjAAjXHFYiNlU7EEnsYtbeUYeEglK6BL_cxISEr2YAGLr1Gwnn2HnucTnH6YilyRio7ZC1ohy_ZojzmaljPHqpr8kn1iqNFu9nFE2M16ZPgJi38-PGzppcDNliyzOQO-c7L-eA-v8Gfww5uyRaOJdiWg-hUJmeGBIngPIeLtSVmhJtz8oTeqeNdUOqQv7f7VRCuvagLhW1PcEM91VUS-gS0WEUXoXWZ2lp91No0v1O24izgX3__FKiX_16XhrOfAgJ82F61vjbTIQYwhexHPZyYTlXYt_scNRzFGhSKeGFin4zVdFLOXWJqKWdUd5IrDP5Nya3FSoWbWDXAg",
+		"eyJhbGciOiJSUzUxMiJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.rQPz0PDh8KyE2AX6JorgI0MLwv-qi1tcWlz6tuZuWQG1hdrlzq5tR1tQg1evYNc_SDDX87DWTSKXT7JEqhKoFixLfZa13IJrOc7FB8r5ZLx7OwOBC4F--OWrvxMA9Y3MTJjPN3FemQePUo-na2vNUZv-YgkcbuOgbO3hTxwQ7j1JGuqy-YutXOFnccdXvntp3t8zYZ4Mg1It_IyL9pzgGqHIEmMV1pCFGHsDa-wStB4ffmdhrADdYZc0q_SvxUdobyC_XzZCz9ENzGIhgwYxyyrqg7kjqUGoKmCLmoSlUFW7goTk9IC5SXdUyLPuESxOWNfHoRClGav230GYjPFQFA",
+		"eyJhbGciOiJQUzI1NiJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.UTtxjsv_6x4CdlAmZfAW6Lun3byMjJbcwRp_OlPH2W4MZaZar7aql052mIB_ddK45O9VUz2aphYVRvKPZY8WHmvlTUU30bk0z_cDJRYB9eIJVMOiRCYj0oNkz1iEZqsP0YgngxwuUDv4Q4A6aJ0Bo5E_rZo3AnrVHMHUjPp_ZRRSBFs30tQma1qQ0ApK4Gxk0XYCYAcxIv99e78vldVRaGzjEZmQeAVZx4tGcqZP20vG1L84nlhSGnOuZ0FhR8UjRFLXuob6M7EqtMRoqPgRYw47EI3fYBdeSivAg98E5S8R7R1NJc7ef-l03RvfUSY0S3_zBq_4PlHK6A-2kHb__w",
+		"eyJhbGciOiJSUzM4NCJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.meyfoOTjAAjXHFYiNlU7EEnsYtbeUYeEglK6BL_cxISEr2YAGLr1Gwnn2HnucTnH6YilyRio7ZC1ohy_ZojzmaljPHqpr8kn1iqNFu9nFE2M16ZPgJi38-PGzppcDNliyzOQO-c7L-eA-v8Gfww5uyRaOJdiWg-hUJmeGBIngPIeLtSVmhJtz8oTeqeNdUOqQv7f7VRCuvagLhW1PcEM91VUS-gS0WEUXoXWZ2lp91No0v1O24izgX3__FKiX_16XhrOfAgJ82F61vjbTIQYwhexHPZyYTlXYt_scNRzFGhSKeGFin4zVdFLOXWJqKWdUd5IrDP5Nya3FSoWbWDXAg",
+		"eyJhbGciOiJSUzUxMiJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.rQPz0PDh8KyE2AX6JorgI0MLwv-qi1tcWlz6tuZuWQG1hdrlzq5tR1tQg1evYNc_SDDX87DWTSKXT7JEqhKoFixLfZa13IJrOc7FB8r5ZLx7OwOBC4F--OWrvxMA9Y3MTJjPN3FemQePUo-na2vNUZv-YgkcbuOgbO3hTxwQ7j1JGuqy-YutXOFnccdXvntp3t8zYZ4Mg1It_IyL9pzgGqHIEmMV1pCFGHsDa-wStB4ffmdhrADdYZc0q_SvxUdobyC_XzZCz9ENzGIhgwYxyyrqg7kjqUGoKmCLmoSlUFW7goTk9IC5SXdUyLPuESxOWNfHoRClGav230GYjPFQFA",
+	}
+
+	for _, msg := range rsaSampleMessages {
+		obj, err := ParseSigned(msg)
+		if err != nil {
+			t.Error("unable to parse message", msg, err)
+			continue
+		}
+		payload, err := obj.Verify(rsaPublicKey)
+		if err != nil {
+			t.Error("unable to verify message", msg, err)
+			continue
+		}
+		if string(payload) != "Lorem ipsum dolor sit amet" {
+			t.Error("payload is not what we expected for msg", msg)
+		}
+	}
+}
+
+// Test vectors generated with nimbus-jose-jwt
+func TestSampleNimbusJWSMessagesEC(t *testing.T) {
+	ecPublicKeyP256, err := LoadPublicKey(fromBase64Bytes("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIg62jq6FyL1otEj9Up7S35BUrwGF9TVrAzrrY1rHUKZqYIGEg67u/imjgadVcr7y9Q32I0gB8W8FHqbqt696rA=="))
+	if err != nil {
+		panic(err)
+	}
+	ecPublicKeyP384, err := LoadPublicKey(fromBase64Bytes("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEPXsVlqCtN2oTY+F+hFZm3M0ldYpb7IeeJM5wYmT0k1RaqzBFDhDMNnYK5Q5x+OyssZrAtHgYDFw02AVJhhng/eHRp7mqmL/vI3wbxJtrLKYldIbBA+9fYBQcKeibjlu5"))
+	if err != nil {
+		panic(err)
+	}
+	ecPublicKeyP521, err := LoadPublicKey(fromBase64Bytes("MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAa2w3MMJ5FWD6tSf68G+Wy5jIhWXOD3IA7pE5IC/myQzo1lWcD8KS57SM6nm4POtPcxyLmDhL7FLuh8DKoIZyvtAAdK8+tOQP7XXRlT2bkvzIuazp05It3TAPu00YzTIpKfDlc19Y1lvf7etrbFqhShD92B+hHmhT4ddrdbPCBDW8hvU="))
+	if err != nil {
+		panic(err)
+	}
+
+	ecPublicKeys := []interface{}{ecPublicKeyP256, ecPublicKeyP384, ecPublicKeyP521}
+
+	ecSampleMessages := []string{
+		"eyJhbGciOiJFUzI1NiJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.MEWJVlvGRQyzMEGOYm4rwuiwxrX-6LjnlbaRDAuhwmnBm2Gtn7pRpGXRTMFZUXsSGDz2L1p-Hz1qn8j9bFIBtQ",
+		"eyJhbGciOiJFUzM4NCJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.nbdjPnJPYQtVNNdBIx8-KbFKplTxrz-hnW5UNhYUY7SBkwHK4NZnqc2Lv4DXoA0aWHq9eiypgOh1kmyPWGEmqKAHUx0xdIEkBoHk3ZsbmhOQuq2jL_wcMUG6nTWNhLrB",
+		"eyJhbGciOiJFUzUxMiJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.AeYNFC1rwIgQv-5fwd8iRyYzvTaSCYTEICepgu9gRId-IW99kbSVY7yH0MvrQnqI-a0L8zwKWDR35fW5dukPAYRkADp3Y1lzqdShFcEFziUVGo46vqbiSajmKFrjBktJcCsfjKSaLHwxErF-T10YYPCQFHWb2nXJOOI3CZfACYqgO84g",
+	}
+
+	for i, msg := range ecSampleMessages {
+		obj, err := ParseSigned(msg)
+		if err != nil {
+			t.Error("unable to parse message", msg, err)
+			continue
+		}
+		payload, err := obj.Verify(ecPublicKeys[i])
+		if err != nil {
+			t.Error("unable to verify message", msg, err)
+			continue
+		}
+		if string(payload) != "Lorem ipsum dolor sit amet" {
+			t.Error("payload is not what we expected for msg", msg)
+		}
+	}
+}
+
+// Test vectors generated with nimbus-jose-jwt
+func TestSampleNimbusJWSMessagesHMAC(t *testing.T) {
+	hmacTestKey := fromHexBytes("DF1FA4F36FFA7FC42C81D4B3C033928D")
+
+	hmacSampleMessages := []string{
+		"eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.W5tc_EUhxexcvLYEEOckyyvdb__M5DQIVpg6Nmk1XGM",
+		"eyJhbGciOiJIUzM4NCJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.sBu44lXOJa4Nd10oqOdYH2uz3lxlZ6o32QSGHaoGdPtYTDG5zvSja6N48CXKqdAh",
+		"eyJhbGciOiJIUzUxMiJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ.M0yR4tmipsORIix-BitIbxEPGaxPchDfj8UNOpKuhDEfnb7URjGvCKn4nOlyQ1z9mG1FKbwnqR1hOVAWSzAU_w",
+	}
+
+	for _, msg := range hmacSampleMessages {
+		obj, err := ParseSigned(msg)
+		if err != nil {
+			t.Error("unable to parse message", msg, err)
+			continue
+		}
+		payload, err := obj.Verify(hmacTestKey)
+		if err != nil {
+			t.Error("unable to verify message", msg, err)
+			continue
+		}
+		if string(payload) != "Lorem ipsum dolor sit amet" {
+			t.Error("payload is not what we expected for msg", msg)
+		}
+	}
+}
+
+// Test vectors generated with nimbus-jose-jwt
+func TestErrorMissingPayloadJWS(t *testing.T) {
+	_, err := (&rawJsonWebSignature{}).sanitized()
+	if err == nil {
+		t.Error("was able to parse message with missing payload")
+	}
+	if !strings.Contains(err.Error(), "missing payload") {
+		t.Errorf("unexpected error message, should contain 'missing payload': %s", err)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/shared.go b/Godeps/_workspace/src/github.com/square/go-jose/shared.go
new file mode 100644
index 00000000..9d895a91
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/shared.go
@@ -0,0 +1,224 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"crypto/elliptic"
+	"errors"
+	"fmt"
+)
+
+// KeyAlgorithm represents a key management algorithm.
+type KeyAlgorithm string
+
+// SignatureAlgorithm represents a signature (or MAC) algorithm.
+type SignatureAlgorithm string
+
+// ContentEncryption represents a content encryption algorithm.
+type ContentEncryption string
+
+// CompressionAlgorithm represents an algorithm used for plaintext compression.
+type CompressionAlgorithm string
+
+var (
+	// ErrCryptoFailure represents an error in cryptographic primitive. This
+	// occurs when, for example, a message had an invalid authentication tag or
+	// could not be decrypted.
+	ErrCryptoFailure = errors.New("square/go-jose: error in cryptographic primitive")
+
+	// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
+	// supported. This occurs when trying to instantiate an encrypter for an
+	// algorithm that is not yet implemented.
+	ErrUnsupportedAlgorithm = errors.New("square/go-jose: unknown/unsupported algorithm")
+
+	// ErrUnsupportedKeyType indicates that the given key type/format is not
+	// supported. This occurs when trying to instantiate an encrypter and passing
+	// it a key of an unrecognized type or with unsupported parameters, such as
+	// an RSA private key with more than two primes.
+	ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
+
+	// ErrNotSupported serialization of object is not supported. This occurs when
+	// trying to compact-serialize an object which can't be represented in
+	// compact form.
+	ErrNotSupported = errors.New("square/go-jose: compact serialization not supported for object")
+
+	// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
+	// nonce header parameter was included in an unprotected header object.
+	ErrUnprotectedNonce = errors.New("square/go-jose: Nonce parameter included in unprotected header")
+)
+
+// Key management algorithms
+const (
+	RSA1_5             = KeyAlgorithm("RSA1_5")             // RSA-PKCS1v1.5
+	RSA_OAEP           = KeyAlgorithm("RSA-OAEP")           // RSA-OAEP-SHA1
+	RSA_OAEP_256       = KeyAlgorithm("RSA-OAEP-256")       // RSA-OAEP-SHA256
+	A128KW             = KeyAlgorithm("A128KW")             // AES key wrap (128)
+	A192KW             = KeyAlgorithm("A192KW")             // AES key wrap (192)
+	A256KW             = KeyAlgorithm("A256KW")             // AES key wrap (256)
+	DIRECT             = KeyAlgorithm("dir")                // Direct encryption
+	ECDH_ES            = KeyAlgorithm("ECDH-ES")            // ECDH-ES
+	ECDH_ES_A128KW     = KeyAlgorithm("ECDH-ES+A128KW")     // ECDH-ES + AES key wrap (128)
+	ECDH_ES_A192KW     = KeyAlgorithm("ECDH-ES+A192KW")     // ECDH-ES + AES key wrap (192)
+	ECDH_ES_A256KW     = KeyAlgorithm("ECDH-ES+A256KW")     // ECDH-ES + AES key wrap (256)
+	A128GCMKW          = KeyAlgorithm("A128GCMKW")          // AES-GCM key wrap (128)
+	A192GCMKW          = KeyAlgorithm("A192GCMKW")          // AES-GCM key wrap (192)
+	A256GCMKW          = KeyAlgorithm("A256GCMKW")          // AES-GCM key wrap (256)
+	PBES2_HS256_A128KW = KeyAlgorithm("PBES2-HS256+A128KW") // PBES2 + HMAC-SHA256 + AES key wrap (128)
+	PBES2_HS384_A192KW = KeyAlgorithm("PBES2-HS384+A192KW") // PBES2 + HMAC-SHA384 + AES key wrap (192)
+	PBES2_HS512_A256KW = KeyAlgorithm("PBES2-HS512+A256KW") // PBES2 + HMAC-SHA512 + AES key wrap (256)
+)
+
+// Signature algorithms
+const (
+	HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
+	HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
+	HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
+	RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256
+	RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384
+	RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512
+	ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256
+	ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384
+	ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512
+	PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256
+	PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384
+	PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512
+)
+
+// Content encryption algorithms
+const (
+	A128CBC_HS256 = ContentEncryption("A128CBC-HS256") // AES-CBC + HMAC-SHA256 (128)
+	A192CBC_HS384 = ContentEncryption("A192CBC-HS384") // AES-CBC + HMAC-SHA384 (192)
+	A256CBC_HS512 = ContentEncryption("A256CBC-HS512") // AES-CBC + HMAC-SHA512 (256)
+	A128GCM       = ContentEncryption("A128GCM")       // AES-GCM (128)
+	A192GCM       = ContentEncryption("A192GCM")       // AES-GCM (192)
+	A256GCM       = ContentEncryption("A256GCM")       // AES-GCM (256)
+)
+
+// Compression algorithms
+const (
+	NONE    = CompressionAlgorithm("")    // No compression
+	DEFLATE = CompressionAlgorithm("DEF") // DEFLATE (RFC 1951)
+)
+
+// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
+type rawHeader struct {
+	Alg   string               `json:"alg,omitempty"`
+	Enc   ContentEncryption    `json:"enc,omitempty"`
+	Zip   CompressionAlgorithm `json:"zip,omitempty"`
+	Crit  []string             `json:"crit,omitempty"`
+	Apu   *byteBuffer          `json:"apu,omitempty"`
+	Apv   *byteBuffer          `json:"apv,omitempty"`
+	Epk   *JsonWebKey          `json:"epk,omitempty"`
+	Iv    *byteBuffer          `json:"iv,omitempty"`
+	Tag   *byteBuffer          `json:"tag,omitempty"`
+	Jwk   *JsonWebKey          `json:"jwk,omitempty"`
+	Kid   string               `json:"kid,omitempty"`
+	Nonce string               `json:"nonce,omitempty"`
+}
+
+// JoseHeader represents the read-only JOSE header for JWE/JWS objects.
+type JoseHeader struct {
+	KeyID      string
+	JsonWebKey *JsonWebKey
+	Algorithm  string
+	Nonce      string
+}
+
+// sanitized produces a cleaned-up header object from the raw JSON.
+func (parsed rawHeader) sanitized() JoseHeader {
+	return JoseHeader{
+		KeyID:      parsed.Kid,
+		JsonWebKey: parsed.Jwk,
+		Algorithm:  parsed.Alg,
+		Nonce:      parsed.Nonce,
+	}
+}
+
+// Merge headers from src into dst, giving precedence to headers from l.
+func (dst *rawHeader) merge(src *rawHeader) {
+	if src == nil {
+		return
+	}
+
+	if dst.Alg == "" {
+		dst.Alg = src.Alg
+	}
+	if dst.Enc == "" {
+		dst.Enc = src.Enc
+	}
+	if dst.Zip == "" {
+		dst.Zip = src.Zip
+	}
+	if dst.Crit == nil {
+		dst.Crit = src.Crit
+	}
+	if dst.Crit == nil {
+		dst.Crit = src.Crit
+	}
+	if dst.Apu == nil {
+		dst.Apu = src.Apu
+	}
+	if dst.Apv == nil {
+		dst.Apv = src.Apv
+	}
+	if dst.Epk == nil {
+		dst.Epk = src.Epk
+	}
+	if dst.Iv == nil {
+		dst.Iv = src.Iv
+	}
+	if dst.Tag == nil {
+		dst.Tag = src.Tag
+	}
+	if dst.Kid == "" {
+		dst.Kid = src.Kid
+	}
+	if dst.Jwk == nil {
+		dst.Jwk = src.Jwk
+	}
+	if dst.Nonce == "" {
+		dst.Nonce = src.Nonce
+	}
+}
+
+// Get JOSE name of curve
+func curveName(crv elliptic.Curve) (string, error) {
+	switch crv {
+	case elliptic.P256():
+		return "P-256", nil
+	case elliptic.P384():
+		return "P-384", nil
+	case elliptic.P521():
+		return "P-521", nil
+	default:
+		return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
+	}
+}
+
+// Get size of curve in bytes
+func curveSize(crv elliptic.Curve) int {
+	bits := crv.Params().BitSize
+
+	div := bits / 8
+	mod := bits % 8
+
+	if mod == 0 {
+		return div
+	}
+
+	return div + 1
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/signing.go b/Godeps/_workspace/src/github.com/square/go-jose/signing.go
new file mode 100644
index 00000000..7dbf534f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/signing.go
@@ -0,0 +1,205 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"fmt"
+)
+
+// NonceSource represents a source of random nonces to go into JWS objects
+type NonceSource interface {
+	Nonce() (string, error)
+}
+
+// Signer represents a signer which takes a payload and produces a signed JWS object.
+type Signer interface {
+	Sign(payload []byte) (*JsonWebSignature, error)
+	SetNonceSource(source NonceSource)
+}
+
+// MultiSigner represents a signer which supports multiple recipients.
+type MultiSigner interface {
+	Sign(payload []byte) (*JsonWebSignature, error)
+	SetNonceSource(source NonceSource)
+	AddRecipient(alg SignatureAlgorithm, signingKey interface{}) error
+}
+
+type payloadSigner interface {
+	signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error)
+}
+
+type payloadVerifier interface {
+	verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
+}
+
+type genericSigner struct {
+	recipients  []recipientSigInfo
+	nonceSource NonceSource
+}
+
+type recipientSigInfo struct {
+	sigAlg    SignatureAlgorithm
+	publicKey *JsonWebKey
+	signer    payloadSigner
+}
+
+// NewSigner creates an appropriate signer based on the key type
+func NewSigner(alg SignatureAlgorithm, signingKey interface{}) (Signer, error) {
+	// NewMultiSigner never fails (currently)
+	signer := NewMultiSigner()
+
+	err := signer.AddRecipient(alg, signingKey)
+	if err != nil {
+		return nil, err
+	}
+
+	return signer, nil
+}
+
+// NewMultiSigner creates a signer for multiple recipients
+func NewMultiSigner() MultiSigner {
+	return &genericSigner{
+		recipients: []recipientSigInfo{},
+	}
+}
+
+// newVerifier creates a verifier based on the key type
+func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
+	switch verificationKey := verificationKey.(type) {
+	case *rsa.PublicKey:
+		return &rsaEncrypterVerifier{
+			publicKey: verificationKey,
+		}, nil
+	case *ecdsa.PublicKey:
+		return &ecEncrypterVerifier{
+			publicKey: verificationKey,
+		}, nil
+	case []byte:
+		return &symmetricMac{
+			key: verificationKey,
+		}, nil
+	case *JsonWebKey:
+		return newVerifier(verificationKey.Key)
+	default:
+		return nil, ErrUnsupportedKeyType
+	}
+}
+
+func (ctx *genericSigner) AddRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
+	recipient, err := makeJWSRecipient(alg, signingKey)
+	if err != nil {
+		return err
+	}
+
+	ctx.recipients = append(ctx.recipients, recipient)
+	return nil
+}
+
+func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipientSigInfo, error) {
+	switch signingKey := signingKey.(type) {
+	case *rsa.PrivateKey:
+		return newRSASigner(alg, signingKey)
+	case *ecdsa.PrivateKey:
+		return newECDSASigner(alg, signingKey)
+	case []byte:
+		return newSymmetricSigner(alg, signingKey)
+	case *JsonWebKey:
+		recipient, err := makeJWSRecipient(alg, signingKey.Key)
+		if err != nil {
+			return recipientSigInfo{}, err
+		}
+		recipient.publicKey.KeyID = signingKey.KeyID
+		return recipient, nil
+	default:
+		return recipientSigInfo{}, ErrUnsupportedKeyType
+	}
+}
+
+func (ctx *genericSigner) Sign(payload []byte) (*JsonWebSignature, error) {
+	obj := &JsonWebSignature{}
+	obj.payload = payload
+	obj.Signatures = make([]Signature, len(ctx.recipients))
+
+	for i, recipient := range ctx.recipients {
+		protected := &rawHeader{
+			Alg: string(recipient.sigAlg),
+		}
+
+		if recipient.publicKey != nil {
+			protected.Jwk = recipient.publicKey
+			protected.Kid = recipient.publicKey.KeyID
+		}
+
+		if ctx.nonceSource != nil {
+			nonce, err := ctx.nonceSource.Nonce()
+			if err != nil {
+				return nil, fmt.Errorf("square/go-jose: Error generating nonce: %v", err)
+			}
+			protected.Nonce = nonce
+		}
+
+		serializedProtected := mustSerializeJSON(protected)
+
+		input := []byte(fmt.Sprintf("%s.%s",
+			base64URLEncode(serializedProtected),
+			base64URLEncode(payload)))
+
+		signatureInfo, err := recipient.signer.signPayload(input, recipient.sigAlg)
+		if err != nil {
+			return nil, err
+		}
+
+		signatureInfo.protected = protected
+		obj.Signatures[i] = signatureInfo
+	}
+
+	return obj, nil
+}
+
+// SetNonceSource provides or updates a nonce pool to the first recipients.
+// After this method is called, the signer will consume one nonce per
+// signature, returning an error it is unable to get a nonce.
+func (ctx *genericSigner) SetNonceSource(source NonceSource) {
+	ctx.nonceSource = source
+}
+
+// Verify validates the signature on the object and returns the payload.
+func (obj JsonWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
+	verifier, err := newVerifier(verificationKey)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, signature := range obj.Signatures {
+		headers := signature.mergedHeaders()
+		if len(headers.Crit) > 0 {
+			// Unsupported crit header
+			continue
+		}
+
+		input := obj.computeAuthData(&signature)
+		alg := SignatureAlgorithm(headers.Alg)
+		err := verifier.verifyPayload(input, signature.Signature, alg)
+		if err == nil {
+			return obj.payload, nil
+		}
+	}
+
+	return nil, ErrCryptoFailure
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/signing_test.go b/Godeps/_workspace/src/github.com/square/go-jose/signing_test.go
new file mode 100644
index 00000000..5dec7863
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/signing_test.go
@@ -0,0 +1,379 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"encoding/json"
+	"fmt"
+	"io"
+	"testing"
+)
+
+type staticNonceSource string
+
+func (sns staticNonceSource) Nonce() (string, error) {
+	return string(sns), nil
+}
+
+func RoundtripJWS(sigAlg SignatureAlgorithm, serializer func(*JsonWebSignature) (string, error), corrupter func(*JsonWebSignature), signingKey interface{}, verificationKey interface{}, nonce string) error {
+	signer, err := NewSigner(sigAlg, signingKey)
+	if err != nil {
+		return fmt.Errorf("error on new signer: %s", err)
+	}
+
+	if nonce != "" {
+		signer.SetNonceSource(staticNonceSource(nonce))
+	}
+
+	input := []byte("Lorem ipsum dolor sit amet")
+	obj, err := signer.Sign(input)
+	if err != nil {
+		return fmt.Errorf("error on sign: %s", err)
+	}
+
+	msg, err := serializer(obj)
+	if err != nil {
+		return fmt.Errorf("error on serialize: %s", err)
+	}
+
+	obj, err = ParseSigned(msg)
+	if err != nil {
+		return fmt.Errorf("error on parse: %s", err)
+	}
+
+	// (Maybe) mangle the object
+	corrupter(obj)
+
+	output, err := obj.Verify(verificationKey)
+	if err != nil {
+		return fmt.Errorf("error on verify: %s", err)
+	}
+
+	// Check that verify works with embedded keys (if present)
+	for i, sig := range obj.Signatures {
+		if sig.Header.JsonWebKey != nil {
+			_, err = obj.Verify(sig.Header.JsonWebKey)
+			if err != nil {
+				return fmt.Errorf("error on verify with embedded key %d: %s", i, err)
+			}
+		}
+
+		// Check that the nonce correctly round-tripped (if present)
+		if sig.Header.Nonce != nonce {
+			return fmt.Errorf("Incorrect nonce returned: [%s]", sig.Header.Nonce)
+		}
+	}
+
+	if bytes.Compare(output, input) != 0 {
+		return fmt.Errorf("input/output do not match, got '%s', expected '%s'", output, input)
+	}
+
+	return nil
+}
+
+func TestRoundtripsJWS(t *testing.T) {
+	// Test matrix
+	sigAlgs := []SignatureAlgorithm{RS256, RS384, RS512, PS256, PS384, PS512, HS256, HS384, HS512, ES256, ES384, ES512}
+
+	serializers := []func(*JsonWebSignature) (string, error){
+		func(obj *JsonWebSignature) (string, error) { return obj.CompactSerialize() },
+		func(obj *JsonWebSignature) (string, error) { return obj.FullSerialize(), nil },
+	}
+
+	corrupter := func(obj *JsonWebSignature) {}
+
+	for _, alg := range sigAlgs {
+		signingKey, verificationKey := GenerateSigningTestKey(alg)
+
+		for i, serializer := range serializers {
+			err := RoundtripJWS(alg, serializer, corrupter, signingKey, verificationKey, "test_nonce")
+			if err != nil {
+				t.Error(err, alg, i)
+			}
+		}
+	}
+}
+
+func TestRoundtripsJWSCorruptSignature(t *testing.T) {
+	// Test matrix
+	sigAlgs := []SignatureAlgorithm{RS256, RS384, RS512, PS256, PS384, PS512, HS256, HS384, HS512, ES256, ES384, ES512}
+
+	serializers := []func(*JsonWebSignature) (string, error){
+		func(obj *JsonWebSignature) (string, error) { return obj.CompactSerialize() },
+		func(obj *JsonWebSignature) (string, error) { return obj.FullSerialize(), nil },
+	}
+
+	corrupters := []func(*JsonWebSignature){
+		func(obj *JsonWebSignature) {
+			// Changes bytes in signature
+			obj.Signatures[0].Signature[10]++
+		},
+		func(obj *JsonWebSignature) {
+			// Set totally invalid signature
+			obj.Signatures[0].Signature = []byte("###")
+		},
+	}
+
+	// Test all different configurations
+	for _, alg := range sigAlgs {
+		signingKey, verificationKey := GenerateSigningTestKey(alg)
+
+		for i, serializer := range serializers {
+			for j, corrupter := range corrupters {
+				err := RoundtripJWS(alg, serializer, corrupter, signingKey, verificationKey, "test_nonce")
+				if err == nil {
+					t.Error("failed to detect corrupt signature", err, alg, i, j)
+				}
+			}
+		}
+	}
+}
+
+func TestSignerWithBrokenRand(t *testing.T) {
+	sigAlgs := []SignatureAlgorithm{RS256, RS384, RS512, PS256, PS384, PS512}
+
+	serializer := func(obj *JsonWebSignature) (string, error) { return obj.CompactSerialize() }
+	corrupter := func(obj *JsonWebSignature) {}
+
+	// Break rand reader
+	readers := []func() io.Reader{
+		// Totally broken
+		func() io.Reader { return bytes.NewReader([]byte{}) },
+		// Not enough bytes
+		func() io.Reader { return io.LimitReader(rand.Reader, 20) },
+	}
+
+	defer resetRandReader()
+
+	for _, alg := range sigAlgs {
+		signingKey, verificationKey := GenerateSigningTestKey(alg)
+		for i, getReader := range readers {
+			randReader = getReader()
+			err := RoundtripJWS(alg, serializer, corrupter, signingKey, verificationKey, "test_nonce")
+			if err == nil {
+				t.Error("signer should fail if rand is broken", alg, i)
+			}
+		}
+	}
+}
+
+func TestJWSInvalidKey(t *testing.T) {
+	signingKey0, verificationKey0 := GenerateSigningTestKey(RS256)
+	_, verificationKey1 := GenerateSigningTestKey(ES256)
+
+	signer, err := NewSigner(RS256, signingKey0)
+	if err != nil {
+		panic(err)
+	}
+
+	input := []byte("Lorem ipsum dolor sit amet")
+	obj, err := signer.Sign(input)
+	if err != nil {
+		panic(err)
+	}
+
+	// Must work with correct key
+	_, err = obj.Verify(verificationKey0)
+	if err != nil {
+		t.Error("error on verify", err)
+	}
+
+	// Must not work with incorrect key
+	_, err = obj.Verify(verificationKey1)
+	if err == nil {
+		t.Error("verification should fail with incorrect key")
+	}
+
+	// Must not work with invalid key
+	_, err = obj.Verify("")
+	if err == nil {
+		t.Error("verification should fail with incorrect key")
+	}
+}
+
+func TestMultiRecipientJWS(t *testing.T) {
+	signer := NewMultiSigner()
+
+	sharedKey := []byte{
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+	}
+
+	signer.AddRecipient(RS256, rsaTestKey)
+	signer.AddRecipient(HS384, sharedKey)
+
+	input := []byte("Lorem ipsum dolor sit amet")
+	obj, err := signer.Sign(input)
+	if err != nil {
+		t.Error("error on sign: ", err)
+		return
+	}
+
+	_, err = obj.CompactSerialize()
+	if err == nil {
+		t.Error("message with multiple recipient was compact serialized")
+	}
+
+	msg := obj.FullSerialize()
+
+	obj, err = ParseSigned(msg)
+	if err != nil {
+		t.Error("error on parse: ", err)
+		return
+	}
+
+	output, err := obj.Verify(&rsaTestKey.PublicKey)
+	if err != nil {
+		t.Error("error on verify: ", err)
+		return
+	}
+
+	if bytes.Compare(output, input) != 0 {
+		t.Error("input/output do not match", output, input)
+		return
+	}
+
+	output, err = obj.Verify(sharedKey)
+	if err != nil {
+		t.Error("error on verify: ", err)
+		return
+	}
+
+	if bytes.Compare(output, input) != 0 {
+		t.Error("input/output do not match", output, input)
+		return
+	}
+}
+
+func GenerateSigningTestKey(sigAlg SignatureAlgorithm) (sig, ver interface{}) {
+	switch sigAlg {
+	case RS256, RS384, RS512, PS256, PS384, PS512:
+		sig = rsaTestKey
+		ver = &rsaTestKey.PublicKey
+	case HS256, HS384, HS512:
+		sig, _, _ = randomKeyGenerator{size: 16}.genKey()
+		ver = sig
+	case ES256:
+		key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+		sig = key
+		ver = &key.PublicKey
+	case ES384:
+		key, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
+		sig = key
+		ver = &key.PublicKey
+	case ES512:
+		key, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
+		sig = key
+		ver = &key.PublicKey
+	default:
+		panic("Must update test case")
+	}
+
+	return
+}
+
+func TestInvalidSignerAlg(t *testing.T) {
+	_, err := NewSigner("XYZ", nil)
+	if err == nil {
+		t.Error("should not accept invalid algorithm")
+	}
+
+	_, err = NewSigner("XYZ", []byte{})
+	if err == nil {
+		t.Error("should not accept invalid algorithm")
+	}
+}
+
+func TestInvalidJWS(t *testing.T) {
+	signer, err := NewSigner(PS256, rsaTestKey)
+	if err != nil {
+		panic(err)
+	}
+
+	obj, err := signer.Sign([]byte("Lorem ipsum dolor sit amet"))
+	obj.Signatures[0].header = &rawHeader{
+		Crit: []string{"TEST"},
+	}
+
+	_, err = obj.Verify(&rsaTestKey.PublicKey)
+	if err == nil {
+		t.Error("should not verify message with unknown crit header")
+	}
+
+	// Try without alg header
+	obj.Signatures[0].protected = &rawHeader{}
+	obj.Signatures[0].header = &rawHeader{}
+
+	_, err = obj.Verify(&rsaTestKey.PublicKey)
+	if err == nil {
+		t.Error("should not verify message with missing headers")
+	}
+}
+
+func TestSignerKid(t *testing.T) {
+	kid := "DEADBEEF"
+	payload := []byte("Lorem ipsum dolor sit amet")
+
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Error("problem generating test signing key", err)
+	}
+
+	basejwk := JsonWebKey{Key: key}
+	jsonbar, err := basejwk.MarshalJSON()
+	if err != nil {
+		t.Error("problem marshalling base JWK", err)
+	}
+
+	var jsonmsi map[string]interface{}
+	err = json.Unmarshal(jsonbar, &jsonmsi)
+	if err != nil {
+		t.Error("problem unmarshalling base JWK", err)
+	}
+	jsonmsi["kid"] = kid
+	jsonbar2, err := json.Marshal(jsonmsi)
+	if err != nil {
+		t.Error("problem marshalling kided JWK", err)
+	}
+
+	var jwk JsonWebKey
+	err = jwk.UnmarshalJSON(jsonbar2)
+	if err != nil {
+		t.Error("problem unmarshalling kided JWK", err)
+	}
+
+	signer, err := NewSigner(ES256, &jwk)
+	if err != nil {
+		t.Error("problem creating signer", err)
+	}
+	signed, err := signer.Sign(payload)
+
+	serialized := signed.FullSerialize()
+
+	parsed, err := ParseSigned(serialized)
+	if err != nil {
+		t.Error("problem parsing signed object", err)
+	}
+
+	if parsed.Signatures[0].Header.KeyID != kid {
+		t.Error("KeyID did not survive trip")
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/symmetric.go b/Godeps/_workspace/src/github.com/square/go-jose/symmetric.go
new file mode 100644
index 00000000..fa013e9c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/symmetric.go
@@ -0,0 +1,348 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"crypto/sha512"
+	"crypto/subtle"
+	"errors"
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/square/go-jose/cipher"
+	"hash"
+	"io"
+)
+
+// Random reader (stubbed out in tests)
+var randReader = rand.Reader
+
+// Dummy key cipher for shared symmetric key mode
+type symmetricKeyCipher struct {
+	key []byte // Pre-shared content-encryption key
+}
+
+// Signer/verifier for MAC modes
+type symmetricMac struct {
+	key []byte
+}
+
+// Input/output from an AEAD operation
+type aeadParts struct {
+	iv, ciphertext, tag []byte
+}
+
+// A content cipher based on an AEAD construction
+type aeadContentCipher struct {
+	keyBytes     int
+	authtagBytes int
+	getAead      func(key []byte) (cipher.AEAD, error)
+}
+
+// Random key generator
+type randomKeyGenerator struct {
+	size int
+}
+
+// Static key generator
+type staticKeyGenerator struct {
+	key []byte
+}
+
+// Create a new content cipher based on AES-GCM
+func newAESGCM(keySize int) contentCipher {
+	return &aeadContentCipher{
+		keyBytes:     keySize,
+		authtagBytes: 16,
+		getAead: func(key []byte) (cipher.AEAD, error) {
+			aes, err := aes.NewCipher(key)
+			if err != nil {
+				return nil, err
+			}
+
+			return cipher.NewGCM(aes)
+		},
+	}
+}
+
+// Create a new content cipher based on AES-CBC+HMAC
+func newAESCBC(keySize int) contentCipher {
+	return &aeadContentCipher{
+		keyBytes:     keySize * 2,
+		authtagBytes: 16,
+		getAead: func(key []byte) (cipher.AEAD, error) {
+			return josecipher.NewCBCHMAC(key, aes.NewCipher)
+		},
+	}
+}
+
+// Get an AEAD cipher object for the given content encryption algorithm
+func getContentCipher(alg ContentEncryption) contentCipher {
+	switch alg {
+	case A128GCM:
+		return newAESGCM(16)
+	case A192GCM:
+		return newAESGCM(24)
+	case A256GCM:
+		return newAESGCM(32)
+	case A128CBC_HS256:
+		return newAESCBC(16)
+	case A192CBC_HS384:
+		return newAESCBC(24)
+	case A256CBC_HS512:
+		return newAESCBC(32)
+	default:
+		return nil
+	}
+}
+
+// newSymmetricRecipient creates a JWE encrypter based on AES-GCM key wrap.
+func newSymmetricRecipient(keyAlg KeyAlgorithm, key []byte) (recipientKeyInfo, error) {
+	switch keyAlg {
+	case DIRECT, A128GCMKW, A192GCMKW, A256GCMKW, A128KW, A192KW, A256KW:
+	default:
+		return recipientKeyInfo{}, ErrUnsupportedAlgorithm
+	}
+
+	return recipientKeyInfo{
+		keyAlg: keyAlg,
+		keyEncrypter: &symmetricKeyCipher{
+			key: key,
+		},
+	}, nil
+}
+
+// newSymmetricSigner creates a recipientSigInfo based on the given key.
+func newSymmetricSigner(sigAlg SignatureAlgorithm, key []byte) (recipientSigInfo, error) {
+	// Verify that key management algorithm is supported by this encrypter
+	switch sigAlg {
+	case HS256, HS384, HS512:
+	default:
+		return recipientSigInfo{}, ErrUnsupportedAlgorithm
+	}
+
+	return recipientSigInfo{
+		sigAlg: sigAlg,
+		signer: &symmetricMac{
+			key: key,
+		},
+	}, nil
+}
+
+// Generate a random key for the given content cipher
+func (ctx randomKeyGenerator) genKey() ([]byte, rawHeader, error) {
+	key := make([]byte, ctx.size)
+	_, err := io.ReadFull(randReader, key)
+	if err != nil {
+		return nil, rawHeader{}, err
+	}
+
+	return key, rawHeader{}, nil
+}
+
+// Key size for random generator
+func (ctx randomKeyGenerator) keySize() int {
+	return ctx.size
+}
+
+// Generate a static key (for direct mode)
+func (ctx staticKeyGenerator) genKey() ([]byte, rawHeader, error) {
+	cek := make([]byte, len(ctx.key))
+	copy(cek, ctx.key)
+	return cek, rawHeader{}, nil
+}
+
+// Key size for static generator
+func (ctx staticKeyGenerator) keySize() int {
+	return len(ctx.key)
+}
+
+// Get key size for this cipher
+func (ctx aeadContentCipher) keySize() int {
+	return ctx.keyBytes
+}
+
+// Encrypt some data
+func (ctx aeadContentCipher) encrypt(key, aad, pt []byte) (*aeadParts, error) {
+	// Get a new AEAD instance
+	aead, err := ctx.getAead(key)
+	if err != nil {
+		return nil, err
+	}
+
+	// Initialize a new nonce
+	iv := make([]byte, aead.NonceSize())
+	_, err = io.ReadFull(randReader, iv)
+	if err != nil {
+		return nil, err
+	}
+
+	ciphertextAndTag := aead.Seal(nil, iv, pt, aad)
+	offset := len(ciphertextAndTag) - ctx.authtagBytes
+
+	return &aeadParts{
+		iv:         iv,
+		ciphertext: ciphertextAndTag[:offset],
+		tag:        ciphertextAndTag[offset:],
+	}, nil
+}
+
+// Decrypt some data
+func (ctx aeadContentCipher) decrypt(key, aad []byte, parts *aeadParts) ([]byte, error) {
+	aead, err := ctx.getAead(key)
+	if err != nil {
+		return nil, err
+	}
+
+	return aead.Open(nil, parts.iv, append(parts.ciphertext, parts.tag...), aad)
+}
+
+// Encrypt the content encryption key.
+func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
+	switch alg {
+	case DIRECT:
+		return recipientInfo{
+			header: &rawHeader{},
+		}, nil
+	case A128GCMKW, A192GCMKW, A256GCMKW:
+		aead := newAESGCM(len(ctx.key))
+
+		parts, err := aead.encrypt(ctx.key, []byte{}, cek)
+		if err != nil {
+			return recipientInfo{}, err
+		}
+
+		return recipientInfo{
+			header: &rawHeader{
+				Iv:  newBuffer(parts.iv),
+				Tag: newBuffer(parts.tag),
+			},
+			encryptedKey: parts.ciphertext,
+		}, nil
+	case A128KW, A192KW, A256KW:
+		block, err := aes.NewCipher(ctx.key)
+		if err != nil {
+			return recipientInfo{}, err
+		}
+
+		jek, err := josecipher.KeyWrap(block, cek)
+		if err != nil {
+			return recipientInfo{}, err
+		}
+
+		return recipientInfo{
+			encryptedKey: jek,
+			header:       &rawHeader{},
+		}, nil
+	}
+
+	return recipientInfo{}, ErrUnsupportedAlgorithm
+}
+
+// Decrypt the content encryption key.
+func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
+	switch KeyAlgorithm(headers.Alg) {
+	case DIRECT:
+		cek := make([]byte, len(ctx.key))
+		copy(cek, ctx.key)
+		return cek, nil
+	case A128GCMKW, A192GCMKW, A256GCMKW:
+		aead := newAESGCM(len(ctx.key))
+
+		parts := &aeadParts{
+			iv:         headers.Iv.bytes(),
+			ciphertext: recipient.encryptedKey,
+			tag:        headers.Tag.bytes(),
+		}
+
+		cek, err := aead.decrypt(ctx.key, []byte{}, parts)
+		if err != nil {
+			return nil, err
+		}
+
+		return cek, nil
+	case A128KW, A192KW, A256KW:
+		block, err := aes.NewCipher(ctx.key)
+		if err != nil {
+			return nil, err
+		}
+
+		cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey)
+		if err != nil {
+			return nil, err
+		}
+		return cek, nil
+	}
+
+	return nil, ErrUnsupportedAlgorithm
+}
+
+// Sign the given payload
+func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
+	mac, err := ctx.hmac(payload, alg)
+	if err != nil {
+		return Signature{}, errors.New("square/go-jose: failed to compute hmac")
+	}
+
+	return Signature{
+		Signature: mac,
+		protected: &rawHeader{},
+	}, nil
+}
+
+// Verify the given payload
+func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureAlgorithm) error {
+	expected, err := ctx.hmac(payload, alg)
+	if err != nil {
+		return errors.New("square/go-jose: failed to compute hmac")
+	}
+
+	if len(mac) != len(expected) {
+		return errors.New("square/go-jose: invalid hmac")
+	}
+
+	match := subtle.ConstantTimeCompare(mac, expected)
+	if match != 1 {
+		return errors.New("square/go-jose: invalid hmac")
+	}
+
+	return nil
+}
+
+// Compute the HMAC based on the given alg value
+func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) {
+	var hash func() hash.Hash
+
+	switch alg {
+	case HS256:
+		hash = sha256.New
+	case HS384:
+		hash = sha512.New384
+	case HS512:
+		hash = sha512.New
+	default:
+		return nil, ErrUnsupportedAlgorithm
+	}
+
+	hmac := hmac.New(hash, ctx.key)
+
+	// According to documentation, Write() on hash never fails
+	_, _ = hmac.Write(payload)
+	return hmac.Sum(nil), nil
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/symmetric_test.go b/Godeps/_workspace/src/github.com/square/go-jose/symmetric_test.go
new file mode 100644
index 00000000..67f535e3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/symmetric_test.go
@@ -0,0 +1,131 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"bytes"
+	"crypto/cipher"
+	"crypto/rand"
+	"io"
+	"testing"
+)
+
+func TestInvalidSymmetricAlgorithms(t *testing.T) {
+	_, err := newSymmetricRecipient("XYZ", []byte{})
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should not accept invalid algorithm")
+	}
+
+	enc := &symmetricKeyCipher{}
+	_, err = enc.encryptKey([]byte{}, "XYZ")
+	if err != ErrUnsupportedAlgorithm {
+		t.Error("should not accept invalid algorithm")
+	}
+}
+
+func TestAeadErrors(t *testing.T) {
+	aead := &aeadContentCipher{
+		keyBytes:     16,
+		authtagBytes: 16,
+		getAead: func(key []byte) (cipher.AEAD, error) {
+			return nil, ErrCryptoFailure
+		},
+	}
+
+	parts, err := aead.encrypt([]byte{}, []byte{}, []byte{})
+	if err != ErrCryptoFailure {
+		t.Error("should handle aead failure")
+	}
+
+	_, err = aead.decrypt([]byte{}, []byte{}, parts)
+	if err != ErrCryptoFailure {
+		t.Error("should handle aead failure")
+	}
+}
+
+func TestInvalidKey(t *testing.T) {
+	gcm := newAESGCM(16).(*aeadContentCipher)
+	_, err := gcm.getAead([]byte{})
+	if err == nil {
+		t.Error("should not accept invalid key")
+	}
+}
+
+func TestStaticKeyGen(t *testing.T) {
+	key := make([]byte, 32)
+	io.ReadFull(rand.Reader, key)
+
+	gen := &staticKeyGenerator{key: key}
+	if gen.keySize() != len(key) {
+		t.Error("static key generator reports incorrect size")
+	}
+
+	generated, _, err := gen.genKey()
+	if err != nil {
+		t.Error("static key generator should always succeed", err)
+	}
+	if !bytes.Equal(generated, key) {
+		t.Error("static key generator returns different data")
+	}
+}
+
+func TestVectorsAESGCM(t *testing.T) {
+	// Source: http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-29#appendix-A.1
+	plaintext := []byte{
+		84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32,
+		111, 102, 32, 105, 110, 116, 101, 108, 108, 105, 103, 101, 110, 99,
+		101, 32, 105, 115, 32, 110, 111, 116, 32, 107, 110, 111, 119, 108,
+		101, 100, 103, 101, 32, 98, 117, 116, 32, 105, 109, 97, 103, 105,
+		110, 97, 116, 105, 111, 110, 46}
+
+	aad := []byte{
+		101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69,
+		116, 84, 48, 70, 70, 85, 67, 73, 115, 73, 109, 86, 117, 89, 121, 73,
+		54, 73, 107, 69, 121, 78, 84, 90, 72, 81, 48, 48, 105, 102, 81}
+
+	expectedCiphertext := []byte{
+		229, 236, 166, 241, 53, 191, 115, 196, 174, 43, 73, 109, 39, 122,
+		233, 96, 140, 206, 120, 52, 51, 237, 48, 11, 190, 219, 186, 80, 111,
+		104, 50, 142, 47, 167, 59, 61, 181, 127, 196, 21, 40, 82, 242, 32,
+		123, 143, 168, 226, 73, 216, 176, 144, 138, 247, 106, 60, 16, 205,
+		160, 109, 64, 63, 192}
+
+	expectedAuthtag := []byte{
+		92, 80, 104, 49, 133, 25, 161, 215, 173, 101, 219, 211, 136, 91, 210, 145}
+
+	// Mock random reader
+	randReader = bytes.NewReader([]byte{
+		177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154,
+		212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122,
+		234, 64, 252, 227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219})
+	defer resetRandReader()
+
+	enc := newAESGCM(32)
+	key, _, _ := randomKeyGenerator{size: 32}.genKey()
+	out, err := enc.encrypt(key, aad, plaintext)
+	if err != nil {
+		t.Error("Unable to encrypt:", err)
+		return
+	}
+
+	if bytes.Compare(out.ciphertext, expectedCiphertext) != 0 {
+		t.Error("Ciphertext did not match")
+	}
+	if bytes.Compare(out.tag, expectedAuthtag) != 0 {
+		t.Error("Auth tag did not match")
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/utils.go b/Godeps/_workspace/src/github.com/square/go-jose/utils.go
new file mode 100644
index 00000000..4ca2bc06
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/utils.go
@@ -0,0 +1,74 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+)
+
+// LoadPublicKey loads a public key from PEM/DER-encoded data.
+func LoadPublicKey(data []byte) (interface{}, error) {
+	input := data
+
+	block, _ := pem.Decode(data)
+	if block != nil {
+		input = block.Bytes
+	}
+
+	// Try to load SubjectPublicKeyInfo
+	pub, err0 := x509.ParsePKIXPublicKey(input)
+	if err0 == nil {
+		return pub, nil
+	}
+
+	cert, err1 := x509.ParseCertificate(input)
+	if err1 == nil {
+		return cert.PublicKey, nil
+	}
+
+	return nil, fmt.Errorf("square/go-jose: parse error, got '%s' and '%s'", err0, err1)
+}
+
+// LoadPrivateKey loads a private key from PEM/DER-encoded data.
+func LoadPrivateKey(data []byte) (interface{}, error) {
+	input := data
+
+	block, _ := pem.Decode(data)
+	if block != nil {
+		input = block.Bytes
+	}
+
+	var priv interface{}
+	priv, err0 := x509.ParsePKCS1PrivateKey(input)
+	if err0 == nil {
+		return priv, nil
+	}
+
+	priv, err1 := x509.ParsePKCS8PrivateKey(input)
+	if err1 == nil {
+		return priv, nil
+	}
+
+	priv, err2 := x509.ParseECPrivateKey(input)
+	if err2 == nil {
+		return priv, nil
+	}
+
+	return nil, fmt.Errorf("square/go-jose: parse error, got '%s', '%s' and '%s'", err0, err1, err2)
+}
diff --git a/Godeps/_workspace/src/github.com/square/go-jose/utils_test.go b/Godeps/_workspace/src/github.com/square/go-jose/utils_test.go
new file mode 100644
index 00000000..6ad622da
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/square/go-jose/utils_test.go
@@ -0,0 +1,225 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+	"crypto/ecdsa"
+	"crypto/rand"
+	"crypto/rsa"
+	"encoding/base64"
+	"encoding/hex"
+	"math/big"
+	"regexp"
+	"testing"
+)
+
+// Reset random reader to original value
+func resetRandReader() {
+	randReader = rand.Reader
+}
+
+// Build big int from hex-encoded string. Strips whitespace (for testing).
+func fromHexInt(base16 string) *big.Int {
+	re := regexp.MustCompile(`\s+`)
+	val, ok := new(big.Int).SetString(re.ReplaceAllString(base16, ""), 16)
+	if !ok {
+		panic("Invalid test data")
+	}
+	return val
+}
+
+// Build big int from base64-encoded string. Strips whitespace (for testing).
+func fromBase64Int(base64 string) *big.Int {
+	re := regexp.MustCompile(`\s+`)
+	val, err := base64URLDecode(re.ReplaceAllString(base64, ""))
+	if err != nil {
+		panic("Invalid test data")
+	}
+	return new(big.Int).SetBytes(val)
+}
+
+// Decode hex-encoded string into byte array. Strips whitespace (for testing).
+func fromHexBytes(base16 string) []byte {
+	re := regexp.MustCompile(`\s+`)
+	val, err := hex.DecodeString(re.ReplaceAllString(base16, ""))
+	if err != nil {
+		panic("Invalid test data")
+	}
+	return val
+}
+
+// Decode base64-encoded string into byte array. Strips whitespace (for testing).
+func fromBase64Bytes(b64 string) []byte {
+	re := regexp.MustCompile(`\s+`)
+	val, err := base64.StdEncoding.DecodeString(re.ReplaceAllString(b64, ""))
+	if err != nil {
+		panic("Invalid test data")
+	}
+	return val
+}
+
+// Test vectors below taken from crypto/x509/x509_test.go in the Go std lib.
+
+var pkixPublicKey = `-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3VoPN9PKUjKFLMwOge6+
+wnDi8sbETGIx2FKXGgqtAKpzmem53kRGEQg8WeqRmp12wgp74TGpkEXsGae7RS1k
+enJCnma4fii+noGH7R0qKgHvPrI2Bwa9hzsH8tHxpyM3qrXslOmD45EH9SxIDUBJ
+FehNdaPbLP1gFyahKMsdfxFJLUvbUycuZSJ2ZnIgeVxwm4qbSvZInL9Iu4FzuPtg
+fINKcbbovy1qq4KvPIrXzhbY3PWDc6btxCf3SE0JdE1MCPThntB62/bLMSQ7xdDR
+FF53oIpvxe/SCOymfWq/LW849Ytv3Xwod0+wzAP8STXG4HSELS4UedPYeHJJJYcZ
++QIDAQAB
+-----END PUBLIC KEY-----`
+
+var pkcs1PrivateKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0
+fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu
+/ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu
+RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/
+EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A
+IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS
+tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
+-----END RSA PRIVATE KEY-----`
+
+var ecdsaSHA256p384CertPem = `
+-----BEGIN CERTIFICATE-----
+MIICSjCCAdECCQDje/no7mXkVzAKBggqhkjOPQQDAjCBjjELMAkGA1UEBhMCVVMx
+EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDAS
+BgNVBAoMC0dvb2dsZSwgSW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEG
+CSqGSIb3DQEJARYUZ29sYW5nLWRldkBnbWFpbC5jb20wHhcNMTIwNTIxMDYxMDM0
+WhcNMjIwNTE5MDYxMDM0WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlm
+b3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDASBgNVBAoMC0dvb2dsZSwg
+SW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEGCSqGSIb3DQEJARYUZ29s
+YW5nLWRldkBnbWFpbC5jb20wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARRuzRNIKRK
+jIktEmXanNmrTR/q/FaHXLhWRZ6nHWe26Fw7Rsrbk+VjGy4vfWtNn7xSFKrOu5ze
+qxKnmE0h5E480MNgrUiRkaGO2GMJJVmxx20aqkXOk59U8yGA4CghE6MwCgYIKoZI
+zj0EAwIDZwAwZAIwBZEN8gvmRmfeP/9C1PRLzODIY4JqWub2PLRT4mv9GU+yw3Gr
+PU9A3CHMdEcdw/MEAjBBO1lId8KOCh9UZunsSMfqXiVurpzmhWd6VYZ/32G+M+Mh
+3yILeYQzllt/g0rKVRk=
+-----END CERTIFICATE-----`
+
+var ecdsaSHA256p384CertDer = fromBase64Bytes(`
+MIICSjCCAdECCQDje/no7mXkVzAKBggqhkjOPQQDAjCBjjELMAkGA1UEBhMCVVMx
+EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDAS
+BgNVBAoMC0dvb2dsZSwgSW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEG
+CSqGSIb3DQEJARYUZ29sYW5nLWRldkBnbWFpbC5jb20wHhcNMTIwNTIxMDYxMDM0
+WhcNMjIwNTE5MDYxMDM0WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlm
+b3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDASBgNVBAoMC0dvb2dsZSwg
+SW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEGCSqGSIb3DQEJARYUZ29s
+YW5nLWRldkBnbWFpbC5jb20wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARRuzRNIKRK
+jIktEmXanNmrTR/q/FaHXLhWRZ6nHWe26Fw7Rsrbk+VjGy4vfWtNn7xSFKrOu5ze
+qxKnmE0h5E480MNgrUiRkaGO2GMJJVmxx20aqkXOk59U8yGA4CghE6MwCgYIKoZI
+zj0EAwIDZwAwZAIwBZEN8gvmRmfeP/9C1PRLzODIY4JqWub2PLRT4mv9GU+yw3Gr
+PU9A3CHMdEcdw/MEAjBBO1lId8KOCh9UZunsSMfqXiVurpzmhWd6VYZ/32G+M+Mh
+3yILeYQzllt/g0rKVRk=`)
+
+var pkcs8ECPrivateKey = `
+-----BEGIN PRIVATE KEY-----
+MIHtAgEAMBAGByqGSM49AgEGBSuBBAAjBIHVMIHSAgEBBEHqkl65VsjYDQWIHfgv
+zQLPa0JZBsaJI16mjiH8k6VA4lgfK/KNldlEsY433X7wIzo43u8OpX7Nv7n8pVRH
+15XWK6GBiQOBhgAEAfDuikMI4bWsyse7t8iSCmjt9fneW/qStZuIPuVLo7mSJdud
+Cs3J/x9wOnnhLv1u+0atnq5HKKdL4ff3itJPlhmSAQzByKQ5LTvB7d6fn95GJVK/
+hNuS5qGBpB7qeMXVFoki0/2RZIOway8/fXjmNYwe4v/XB5LLn4hcTvEUGYcF8M9K
+-----END PRIVATE KEY-----`
+
+var ecPrivateKey = `
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIBv2rdY9mWGD/UgiuXB0LJcUzgaB6TXq/Ra1jrZKBV3IGSacM5QDFu
+N8yrywiQaTDEqn1zVcLwrnqoQux3gWN1jxugBwYFK4EEACOhgYkDgYYABAFJgaM/
+2a3+gE6Khm/1PYftqNwAzQ21HSLp27q2lTN+GBFho691ARFRkr9UzlQ8gRnhkTbu
+yGfASamlHsYlr3Tv+gFc4BY8SU0q8kzpQ0dOHWFk7dfGFmKwhJrSFIIOeRn/LY03
+XsVFctNDsGhobS2JguQrxhGx8Ll7vQCakV/PEmCQJA==
+-----END EC PRIVATE KEY-----`
+
+var ecPrivateKeyDer = fromBase64Bytes(`
+MIHcAgEBBEIBv2rdY9mWGD/UgiuXB0LJcUzgaB6TXq/Ra1jrZKBV3IGSacM5QDFu
+N8yrywiQaTDEqn1zVcLwrnqoQux3gWN1jxugBwYFK4EEACOhgYkDgYYABAFJgaM/
+2a3+gE6Khm/1PYftqNwAzQ21HSLp27q2lTN+GBFho691ARFRkr9UzlQ8gRnhkTbu
+yGfASamlHsYlr3Tv+gFc4BY8SU0q8kzpQ0dOHWFk7dfGFmKwhJrSFIIOeRn/LY03
+XsVFctNDsGhobS2JguQrxhGx8Ll7vQCakV/PEmCQJA==`)
+
+var invalidPemKey = `
+-----BEGIN PUBLIC KEY-----
+MIHcAgEBBEIBv2rdY9mWGD/UgiuXB0LJcUzgaB6TXq/Ra1jrZKBV3IGSacM5QDFu
+XsVFctNDsGhobS2JguQrxhGx8Ll7vQCakV/PEmCQJA==
+-----END PUBLIC KEY-----`
+
+func TestLoadPublicKey(t *testing.T) {
+	pub, err := LoadPublicKey([]byte(pkixPublicKey))
+	switch pub.(type) {
+	case *rsa.PublicKey:
+	default:
+		t.Error("failed to parse RSA PKIX public key:", err)
+	}
+
+	pub, err = LoadPublicKey([]byte(ecdsaSHA256p384CertPem))
+	switch pub.(type) {
+	case *ecdsa.PublicKey:
+	default:
+		t.Error("failed to parse ECDSA X.509 cert:", err)
+	}
+
+	pub, err = LoadPublicKey([]byte(ecdsaSHA256p384CertDer))
+	switch pub.(type) {
+	case *ecdsa.PublicKey:
+	default:
+		t.Error("failed to parse ECDSA X.509 cert:", err)
+	}
+
+	pub, err = LoadPublicKey([]byte("###"))
+	if err == nil {
+		t.Error("should not parse invalid key")
+	}
+
+	pub, err = LoadPublicKey([]byte(invalidPemKey))
+	if err == nil {
+		t.Error("should not parse invalid key")
+	}
+}
+
+func TestLoadPrivateKey(t *testing.T) {
+	priv, err := LoadPrivateKey([]byte(pkcs1PrivateKey))
+	switch priv.(type) {
+	case *rsa.PrivateKey:
+	default:
+		t.Error("failed to parse RSA PKCS1 private key:", err)
+	}
+
+	priv, err = LoadPrivateKey([]byte(pkcs8ECPrivateKey))
+	if _, ok := priv.(*ecdsa.PrivateKey); !ok {
+		t.Error("failed to parse EC PKCS8 private key:", err)
+	}
+
+	priv, err = LoadPrivateKey([]byte(ecPrivateKey))
+	if _, ok := priv.(*ecdsa.PrivateKey); !ok {
+		t.Error("failed to parse EC private key:", err)
+	}
+
+	priv, err = LoadPrivateKey([]byte(ecPrivateKeyDer))
+	if _, ok := priv.(*ecdsa.PrivateKey); !ok {
+		t.Error("failed to parse EC private key:", err)
+	}
+
+	priv, err = LoadPrivateKey([]byte("###"))
+	if err == nil {
+		t.Error("should not parse invalid key")
+	}
+
+	priv, err = LoadPrivateKey([]byte(invalidPemKey))
+	if err == nil {
+		t.Error("should not parse invalid key")
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/client.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/client.go
new file mode 100644
index 00000000..a9438cbd
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/client.go
@@ -0,0 +1,628 @@
+package acme
+
+import (
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var (
+	// Logger is an optional custom logger.
+	Logger *log.Logger
+)
+
+// logf writes a log entry. It uses Logger if not
+// nil, otherwise it uses the default log.Logger.
+func logf(format string, args ...interface{}) {
+	if Logger != nil {
+		Logger.Printf(format, args...)
+	} else {
+		log.Printf(format, args...)
+	}
+}
+
+// User interface is to be implemented by users of this library.
+// It is used by the client type to get user specific information.
+type User interface {
+	GetEmail() string
+	GetRegistration() *RegistrationResource
+	GetPrivateKey() *rsa.PrivateKey
+}
+
+// Interface for all challenge solvers to implement.
+type solver interface {
+	Solve(challenge challenge, domain string) error
+}
+
+type validateFunc func(j *jws, domain, uri string, chlng challenge) error
+
+// Client is the user-friendy way to ACME
+type Client struct {
+	directory  directory
+	user       User
+	jws        *jws
+	keyBits    int
+	issuerCert []byte
+	solvers    map[string]solver
+}
+
+// NewClient creates a new ACME client on behalf of the user. The client will depend on
+// the ACME directory located at caDirURL for the rest of its actions. It will
+// generate private keys for certificates of size keyBits.
+func NewClient(caDirURL string, user User, keyBits int) (*Client, error) {
+	privKey := user.GetPrivateKey()
+	if privKey == nil {
+		return nil, errors.New("private key was nil")
+	}
+
+	if err := privKey.Validate(); err != nil {
+		return nil, fmt.Errorf("invalid private key: %v", err)
+	}
+
+	var dir directory
+	if _, err := getJSON(caDirURL, &dir); err != nil {
+		return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
+	}
+
+	if dir.NewRegURL == "" {
+		return nil, errors.New("directory missing new registration URL")
+	}
+	if dir.NewAuthzURL == "" {
+		return nil, errors.New("directory missing new authz URL")
+	}
+	if dir.NewCertURL == "" {
+		return nil, errors.New("directory missing new certificate URL")
+	}
+	if dir.RevokeCertURL == "" {
+		return nil, errors.New("directory missing revoke certificate URL")
+	}
+
+	jws := &jws{privKey: privKey, directoryURL: caDirURL}
+
+	// REVIEW: best possibility?
+	// Add all available solvers with the right index as per ACME
+	// spec to this map. Otherwise they won`t be found.
+	solvers := make(map[string]solver)
+	solvers["http-01"] = &httpChallenge{jws: jws, validate: validate}
+	solvers["tls-sni-01"] = &tlsSNIChallenge{jws: jws, validate: validate}
+
+	return &Client{directory: dir, user: user, jws: jws, keyBits: keyBits, solvers: solvers}, nil
+}
+
+// SetHTTPPort specifies a custom port to be used for HTTP based challenges.
+// If this option is not used, the default port 80 will be used.
+func (c *Client) SetHTTPPort(port string) {
+	if chlng, ok := c.solvers["http-01"]; ok {
+		chlng.(*httpChallenge).optPort = port
+	}
+}
+
+// SetTLSPort specifies a custom port to be used for TLS based challenges.
+// If this option is not used, the default port 443 will be used.
+func (c *Client) SetTLSPort(port string) {
+	if chlng, ok := c.solvers["tls-sni-01"]; ok {
+		chlng.(*tlsSNIChallenge).optPort = port
+	}
+}
+
+// ExcludeChallenges explicitly removes challenges from the pool for solving.
+func (c *Client) ExcludeChallenges(challenges []string) {
+	// Loop through all challenges and delete the requested one if found.
+	for _, challenge := range challenges {
+		if _, ok := c.solvers[challenge]; ok {
+			delete(c.solvers, challenge)
+		}
+	}
+}
+
+// Register the current account to the ACME server.
+func (c *Client) Register() (*RegistrationResource, error) {
+	if c == nil || c.user == nil {
+		return nil, errors.New("acme: cannot register a nil client or user")
+	}
+	logf("[INFO] acme: Registering account for %s", c.user.GetEmail())
+
+	regMsg := registrationMessage{
+		Resource: "new-reg",
+	}
+	if c.user.GetEmail() != "" {
+		regMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
+	} else {
+		regMsg.Contact = []string{}
+	}
+
+	var serverReg Registration
+	hdr, err := postJSON(c.jws, c.directory.NewRegURL, regMsg, &serverReg)
+	if err != nil {
+		return nil, err
+	}
+
+	reg := &RegistrationResource{Body: serverReg}
+
+	links := parseLinks(hdr["Link"])
+	reg.URI = hdr.Get("Location")
+	if links["terms-of-service"] != "" {
+		reg.TosURL = links["terms-of-service"]
+	}
+
+	if links["next"] != "" {
+		reg.NewAuthzURL = links["next"]
+	} else {
+		return nil, errors.New("acme: The server did not return 'next' link to proceed")
+	}
+
+	return reg, nil
+}
+
+// AgreeToTOS updates the Client registration and sends the agreement to
+// the server.
+func (c *Client) AgreeToTOS() error {
+	c.user.GetRegistration().Body.Agreement = c.user.GetRegistration().TosURL
+	c.user.GetRegistration().Body.Resource = "reg"
+	_, err := postJSON(c.jws, c.user.GetRegistration().URI, c.user.GetRegistration().Body, nil)
+	return err
+}
+
+// ObtainCertificate tries to obtain a single certificate using all domains passed into it.
+// The first domain in domains is used for the CommonName field of the certificate, all other
+// domains are added using the Subject Alternate Names extension.
+// If bundle is true, the []byte contains both the issuer certificate and
+// your issued certificate as a bundle.
+// This function will never return a partial certificate. If one domain in the list fails,
+// the whole certificate will fail.
+func (c *Client) ObtainCertificate(domains []string, bundle bool) (CertificateResource, map[string]error) {
+	if bundle {
+		logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
+	} else {
+		logf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
+	}
+
+	challenges, failures := c.getChallenges(domains)
+	// If any challenge fails - return. Do not generate partial SAN certificates.
+	if len(failures) > 0 {
+		return CertificateResource{}, failures
+	}
+
+	errs := c.solveChallenges(challenges)
+	// If any challenge fails - return. Do not generate partial SAN certificates.
+	if len(errs) > 0 {
+		return CertificateResource{}, errs
+	}
+
+	logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
+
+	cert, err := c.requestCertificate(challenges, bundle)
+	if err != nil {
+		for _, chln := range challenges {
+			failures[chln.Domain] = err
+		}
+	}
+
+	return cert, failures
+}
+
+// RevokeCertificate takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
+func (c *Client) RevokeCertificate(certificate []byte) error {
+	certificates, err := parsePEMBundle(certificate)
+	if err != nil {
+		return err
+	}
+
+	x509Cert := certificates[0]
+	if x509Cert.IsCA {
+		return fmt.Errorf("Certificate bundle starts with a CA certificate")
+	}
+
+	encodedCert := base64.URLEncoding.EncodeToString(x509Cert.Raw)
+
+	_, err = postJSON(c.jws, c.directory.RevokeCertURL, revokeCertMessage{Resource: "revoke-cert", Certificate: encodedCert}, nil)
+	return err
+}
+
+// RenewCertificate takes a CertificateResource and tries to renew the certificate.
+// If the renewal process succeeds, the new certificate will ge returned in a new CertResource.
+// Please be aware that this function will return a new certificate in ANY case that is not an error.
+// If the server does not provide us with a new cert on a GET request to the CertURL
+// this function will start a new-cert flow where a new certificate gets generated.
+// If bundle is true, the []byte contains both the issuer certificate and
+// your issued certificate as a bundle.
+func (c *Client) RenewCertificate(cert CertificateResource, bundle bool) (CertificateResource, error) {
+	// Input certificate is PEM encoded. Decode it here as we may need the decoded
+	// cert later on in the renewal process. The input may be a bundle or a single certificate.
+	certificates, err := parsePEMBundle(cert.Certificate)
+	if err != nil {
+		return CertificateResource{}, err
+	}
+
+	x509Cert := certificates[0]
+	if x509Cert.IsCA {
+		return CertificateResource{}, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", cert.Domain)
+	}
+
+	// This is just meant to be informal for the user.
+	timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
+	logf("[INFO][%s] acme: Trying renewal with %d hours remaining", cert.Domain, int(timeLeft.Hours()))
+
+	// The first step of renewal is to check if we get a renewed cert
+	// directly from the cert URL.
+	resp, err := http.Get(cert.CertURL)
+	if err != nil {
+		return CertificateResource{}, err
+	}
+	defer resp.Body.Close()
+	serverCertBytes, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return CertificateResource{}, err
+	}
+
+	serverCert, err := x509.ParseCertificate(serverCertBytes)
+	if err != nil {
+		return CertificateResource{}, err
+	}
+
+	// If the server responds with a different certificate we are effectively renewed.
+	// TODO: Further test if we can actually use the new certificate (Our private key works)
+	if !x509Cert.Equal(serverCert) {
+		logf("[INFO][%s] acme: Server responded with renewed certificate", cert.Domain)
+		issuedCert := pemEncode(derCertificateBytes(serverCertBytes))
+		// If bundle is true, we want to return a certificate bundle.
+		// To do this, we need the issuer certificate.
+		if bundle {
+			// The issuer certificate link is always supplied via an "up" link
+			// in the response headers of a new certificate.
+			links := parseLinks(resp.Header["Link"])
+			issuerCert, err := c.getIssuerCertificate(links["up"])
+			if err != nil {
+				// If we fail to aquire the issuer cert, return the issued certificate - do not fail.
+				logf("[ERROR][%s] acme: Could not bundle issuer certificate: %v", cert.Domain, err)
+			} else {
+				// Success - append the issuer cert to the issued cert.
+				issuerCert = pemEncode(derCertificateBytes(issuerCert))
+				issuedCert = append(issuedCert, issuerCert...)
+				cert.Certificate = issuedCert
+			}
+		}
+
+		cert.Certificate = issuedCert
+		return cert, nil
+	}
+
+	newCert, failures := c.ObtainCertificate([]string{cert.Domain}, bundle)
+	return newCert, failures[cert.Domain]
+}
+
+// Looks through the challenge combinations to find a solvable match.
+// Then solves the challenges in series and returns.
+func (c *Client) solveChallenges(challenges []authorizationResource) map[string]error {
+	// loop through the resources, basically through the domains.
+	failures := make(map[string]error)
+	for _, authz := range challenges {
+		// no solvers - no solving
+		if solvers := c.chooseSolvers(authz.Body, authz.Domain); solvers != nil {
+			for i, solver := range solvers {
+				// TODO: do not immediately fail if one domain fails to validate.
+				err := solver.Solve(authz.Body.Challenges[i], authz.Domain)
+				if err != nil {
+					failures[authz.Domain] = err
+				}
+			}
+		} else {
+			failures[authz.Domain] = fmt.Errorf("[%s] acme: Could not determine solvers", authz.Domain)
+		}
+	}
+
+	return failures
+}
+
+// Checks all combinations from the server and returns an array of
+// solvers which should get executed in series.
+func (c *Client) chooseSolvers(auth authorization, domain string) map[int]solver {
+	for _, combination := range auth.Combinations {
+		solvers := make(map[int]solver)
+		for _, idx := range combination {
+			if solver, ok := c.solvers[auth.Challenges[idx].Type]; ok {
+				solvers[idx] = solver
+			} else {
+				logf("[INFO][%s] acme: Could not find solver for: %s", domain, auth.Challenges[idx].Type)
+			}
+		}
+
+		// If we can solve the whole combination, return the solvers
+		if len(solvers) == len(combination) {
+			return solvers
+		}
+	}
+	return nil
+}
+
+// Get the challenges needed to proof our identifier to the ACME server.
+func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[string]error) {
+	resc, errc := make(chan authorizationResource), make(chan domainError)
+
+	for _, domain := range domains {
+		go func(domain string) {
+			authMsg := authorization{Resource: "new-authz", Identifier: identifier{Type: "dns", Value: domain}}
+			var authz authorization
+			hdr, err := postJSON(c.jws, c.user.GetRegistration().NewAuthzURL, authMsg, &authz)
+			if err != nil {
+				errc <- domainError{Domain: domain, Error: err}
+				return
+			}
+
+			links := parseLinks(hdr["Link"])
+			if links["next"] == "" {
+				logf("[ERROR][%s] acme: Server did not provide next link to proceed", domain)
+				return
+			}
+
+			resc <- authorizationResource{Body: authz, NewCertURL: links["next"], AuthURL: hdr.Get("Location"), Domain: domain}
+		}(domain)
+	}
+
+	responses := make(map[string]authorizationResource)
+	failures := make(map[string]error)
+	for i := 0; i < len(domains); i++ {
+		select {
+		case res := <-resc:
+			responses[res.Domain] = res
+		case err := <-errc:
+			failures[err.Domain] = err.Error
+		}
+	}
+
+	challenges := make([]authorizationResource, 0, len(responses))
+	for _, domain := range domains {
+		if challenge, ok := responses[domain]; ok {
+			challenges = append(challenges, challenge)
+		}
+	}
+
+	close(resc)
+	close(errc)
+
+	return challenges, failures
+}
+
+func (c *Client) requestCertificate(authz []authorizationResource, bundle bool) (CertificateResource, error) {
+	if len(authz) == 0 {
+		return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
+	}
+
+	commonName := authz[0]
+	privKey, err := generatePrivateKey(rsakey, c.keyBits)
+	if err != nil {
+		return CertificateResource{}, err
+	}
+
+	var san []string
+	var authURLs []string
+	for _, auth := range authz[1:] {
+		san = append(san, auth.Domain)
+		authURLs = append(authURLs, auth.AuthURL)
+	}
+
+	// TODO: should the CSR be customizable?
+	csr, err := generateCsr(privKey.(*rsa.PrivateKey), commonName.Domain, san)
+	if err != nil {
+		return CertificateResource{}, err
+	}
+
+	csrString := base64.URLEncoding.EncodeToString(csr)
+	jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: authURLs})
+	if err != nil {
+		return CertificateResource{}, err
+	}
+
+	resp, err := c.jws.post(commonName.NewCertURL, jsonBytes)
+	if err != nil {
+		return CertificateResource{}, err
+	}
+
+	privateKeyPem := pemEncode(privKey)
+	cerRes := CertificateResource{
+		Domain:     commonName.Domain,
+		CertURL:    resp.Header.Get("Location"),
+		PrivateKey: privateKeyPem}
+
+	for {
+
+		switch resp.StatusCode {
+		case 202:
+		case 201:
+
+			cert, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
+			resp.Body.Close()
+			if err != nil {
+				return CertificateResource{}, err
+			}
+
+			// The server returns a body with a length of zero if the
+			// certificate was not ready at the time this request completed.
+			// Otherwise the body is the certificate.
+			if len(cert) > 0 {
+
+				cerRes.CertStableURL = resp.Header.Get("Content-Location")
+
+				issuedCert := pemEncode(derCertificateBytes(cert))
+				// If bundle is true, we want to return a certificate bundle.
+				// To do this, we need the issuer certificate.
+				if bundle {
+					// The issuer certificate link is always supplied via an "up" link
+					// in the response headers of a new certificate.
+					links := parseLinks(resp.Header["Link"])
+					issuerCert, err := c.getIssuerCertificate(links["up"])
+					if err != nil {
+						// If we fail to aquire the issuer cert, return the issued certificate - do not fail.
+						logf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", commonName.Domain, err)
+					} else {
+						// Success - append the issuer cert to the issued cert.
+						issuerCert = pemEncode(derCertificateBytes(issuerCert))
+						issuedCert = append(issuedCert, issuerCert...)
+					}
+				}
+
+				cerRes.Certificate = issuedCert
+				logf("[INFO][%s] Server responded with a certificate.", commonName.Domain)
+				return cerRes, nil
+			}
+
+			// The certificate was granted but is not yet issued.
+			// Check retry-after and loop.
+			ra := resp.Header.Get("Retry-After")
+			retryAfter, err := strconv.Atoi(ra)
+			if err != nil {
+				return CertificateResource{}, err
+			}
+
+			logf("[INFO][%s] acme: Server responded with status 202; retrying after %ds", commonName.Domain, retryAfter)
+			time.Sleep(time.Duration(retryAfter) * time.Second)
+
+			break
+		default:
+			return CertificateResource{}, handleHTTPError(resp)
+		}
+
+		resp, err = http.Get(cerRes.CertURL)
+		if err != nil {
+			return CertificateResource{}, err
+		}
+	}
+}
+
+// getIssuerCertificate requests the issuer certificate and caches it for
+// subsequent requests.
+func (c *Client) getIssuerCertificate(url string) ([]byte, error) {
+	logf("[INFO] acme: Requesting issuer cert from %s", url)
+	if c.issuerCert != nil {
+		return c.issuerCert, nil
+	}
+
+	resp, err := http.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	issuerBytes, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
+	if err != nil {
+		return nil, err
+	}
+
+	_, err = x509.ParseCertificate(issuerBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	c.issuerCert = issuerBytes
+	return issuerBytes, err
+}
+
+func parseLinks(links []string) map[string]string {
+	aBrkt := regexp.MustCompile("[<>]")
+	slver := regexp.MustCompile("(.+) *= *\"(.+)\"")
+	linkMap := make(map[string]string)
+
+	for _, link := range links {
+
+		link = aBrkt.ReplaceAllString(link, "")
+		parts := strings.Split(link, ";")
+
+		matches := slver.FindStringSubmatch(parts[1])
+		if len(matches) > 0 {
+			linkMap[matches[2]] = parts[0]
+		}
+	}
+
+	return linkMap
+}
+
+// validate makes the ACME server start validating a
+// challenge response, only returning once it is done.
+func validate(j *jws, domain, uri string, chlng challenge) error {
+	var challengeResponse challenge
+
+	hdr, err := postJSON(j, uri, chlng, &challengeResponse)
+	if err != nil {
+		return err
+	}
+
+	// After the path is sent, the ACME server will access our server.
+	// Repeatedly check the server for an updated status on our request.
+	for {
+		switch challengeResponse.Status {
+		case "valid":
+			logf("[INFO][%s] The server validated our request", domain)
+			return nil
+		case "pending":
+			break
+		case "invalid":
+			return handleChallengeError(challengeResponse)
+		default:
+			return errors.New("The server returned an unexpected state.")
+		}
+
+		ra, err := strconv.Atoi(hdr.Get("Retry-After"))
+		if err != nil {
+			// The ACME server MUST return a Retry-After.
+			// If it doesn't, we'll just poll hard.
+			ra = 1
+		}
+		time.Sleep(time.Duration(ra) * time.Second)
+
+		hdr, err = getJSON(uri, &challengeResponse)
+		if err != nil {
+			return err
+		}
+	}
+}
+
+// getJSON performs an HTTP GET request and parses the response body
+// as JSON, into the provided respBody object.
+func getJSON(uri string, respBody interface{}) (http.Header, error) {
+	resp, err := http.Get(uri)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get %q: %v", uri, err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode >= http.StatusBadRequest {
+		return resp.Header, handleHTTPError(resp)
+	}
+
+	return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
+}
+
+// postJSON performs an HTTP POST request and parses the response body
+// as JSON, into the provided respBody object.
+func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
+	jsonBytes, err := json.Marshal(reqBody)
+	if err != nil {
+		return nil, errors.New("Failed to marshal network message...")
+	}
+
+	resp, err := j.post(uri, jsonBytes)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to post JWS message. -> %v", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode >= http.StatusBadRequest {
+		return resp.Header, handleHTTPError(resp)
+	}
+
+	if respBody == nil {
+		return resp.Header, nil
+	}
+
+	return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/client_test.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/client_test.go
new file mode 100644
index 00000000..cd89b257
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/client_test.go
@@ -0,0 +1,176 @@
+package acme
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"encoding/json"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+)
+
+func TestNewClient(t *testing.T) {
+	keyBits := 32 // small value keeps test fast
+	key, err := rsa.GenerateKey(rand.Reader, keyBits)
+	if err != nil {
+		t.Fatal("Could not generate test key:", err)
+	}
+	user := mockUser{
+		email:      "test@test.com",
+		regres:     new(RegistrationResource),
+		privatekey: key,
+	}
+
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		data, _ := json.Marshal(directory{NewAuthzURL: "http://test", NewCertURL: "http://test", NewRegURL: "http://test", RevokeCertURL: "http://test"})
+		w.Write(data)
+	}))
+
+	client, err := NewClient(ts.URL, user, keyBits)
+	if err != nil {
+		t.Fatalf("Could not create client: %v", err)
+	}
+
+	if client.jws == nil {
+		t.Fatalf("Expected client.jws to not be nil")
+	}
+	if expected, actual := key, client.jws.privKey; actual != expected {
+		t.Errorf("Expected jws.privKey to be %p but was %p", expected, actual)
+	}
+
+	if client.keyBits != keyBits {
+		t.Errorf("Expected keyBits to be %d but was %d", keyBits, client.keyBits)
+	}
+
+	if expected, actual := 2, len(client.solvers); actual != expected {
+		t.Fatalf("Expected %d solver(s), got %d", expected, actual)
+	}
+}
+
+func TestClientOptPort(t *testing.T) {
+	keyBits := 32 // small value keeps test fast
+	key, err := rsa.GenerateKey(rand.Reader, keyBits)
+	if err != nil {
+		t.Fatal("Could not generate test key:", err)
+	}
+	user := mockUser{
+		email:      "test@test.com",
+		regres:     new(RegistrationResource),
+		privatekey: key,
+	}
+
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		data, _ := json.Marshal(directory{NewAuthzURL: "http://test", NewCertURL: "http://test", NewRegURL: "http://test", RevokeCertURL: "http://test"})
+		w.Write(data)
+	}))
+
+	optPort := "1234"
+	client, err := NewClient(ts.URL, user, keyBits)
+	if err != nil {
+		t.Fatalf("Could not create client: %v", err)
+	}
+	client.SetHTTPPort(optPort)
+	client.SetTLSPort(optPort)
+
+	httpSolver, ok := client.solvers["http-01"].(*httpChallenge)
+	if !ok {
+		t.Fatal("Expected http-01 solver to be httpChallenge type")
+	}
+	if httpSolver.jws != client.jws {
+		t.Error("Expected http-01 to have same jws as client")
+	}
+	if httpSolver.optPort != optPort {
+		t.Errorf("Expected http-01 to have optPort %s but was %s", optPort, httpSolver.optPort)
+	}
+
+	httpsSolver, ok := client.solvers["tls-sni-01"].(*tlsSNIChallenge)
+	if !ok {
+		t.Fatal("Expected tls-sni-01 solver to be httpChallenge type")
+	}
+	if httpsSolver.jws != client.jws {
+		t.Error("Expected tls-sni-01 to have same jws as client")
+	}
+	if httpsSolver.optPort != optPort {
+		t.Errorf("Expected tls-sni-01 to have optPort %s but was %s", optPort, httpSolver.optPort)
+	}
+}
+
+func TestValidate(t *testing.T) {
+	var statuses []string
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Minimal stub ACME server for validation.
+		w.Header().Add("Replay-Nonce", "12345")
+		w.Header().Add("Retry-After", "0")
+		switch r.Method {
+		case "HEAD":
+		case "POST":
+			st := statuses[0]
+			statuses = statuses[1:]
+			writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URI: "http://example.com/", Token: "token"})
+
+		case "GET":
+			st := statuses[0]
+			statuses = statuses[1:]
+			writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URI: "http://example.com/", Token: "token"})
+
+		default:
+			http.Error(w, r.Method, http.StatusMethodNotAllowed)
+		}
+	}))
+	defer ts.Close()
+
+	privKey, _ := generatePrivateKey(rsakey, 512)
+	j := &jws{privKey: privKey.(*rsa.PrivateKey), directoryURL: ts.URL}
+
+	tsts := []struct {
+		name     string
+		statuses []string
+		want     string
+	}{
+		{"POST-unexpected", []string{"weird"}, "unexpected"},
+		{"POST-valid", []string{"valid"}, ""},
+		{"POST-invalid", []string{"invalid"}, "Error Detail"},
+		{"GET-unexpected", []string{"pending", "weird"}, "unexpected"},
+		{"GET-valid", []string{"pending", "valid"}, ""},
+		{"GET-invalid", []string{"pending", "invalid"}, "Error Detail"},
+	}
+
+	for _, tst := range tsts {
+		statuses = tst.statuses
+		if err := validate(j, "example.com", ts.URL, challenge{Type: "http-01", Token: "token"}); err == nil && tst.want != "" {
+			t.Errorf("[%s] validate: got error %v, want something with %q", tst.name, err, tst.want)
+		} else if err != nil && !strings.Contains(err.Error(), tst.want) {
+			t.Errorf("[%s] validate: got error %v, want something with %q", tst.name, err, tst.want)
+		}
+	}
+}
+
+// writeJSONResponse marshals the body as JSON and writes it to the response.
+func writeJSONResponse(w http.ResponseWriter, body interface{}) {
+	bs, err := json.Marshal(body)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	if _, err := w.Write(bs); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+// stubValidate is like validate, except it does nothing.
+func stubValidate(j *jws, domain, uri string, chlng challenge) error {
+	return nil
+}
+
+type mockUser struct {
+	email      string
+	regres     *RegistrationResource
+	privatekey *rsa.PrivateKey
+}
+
+func (u mockUser) GetEmail() string                       { return u.email }
+func (u mockUser) GetRegistration() *RegistrationResource { return u.regres }
+func (u mockUser) GetPrivateKey() *rsa.PrivateKey         { return u.privatekey }
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/crypto.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/crypto.go
new file mode 100644
index 00000000..ee0fcdd6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/crypto.go
@@ -0,0 +1,320 @@
+package acme
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math/big"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/golang.org/x/crypto/ocsp"
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/golang.org/x/crypto/sha3"
+)
+
+type keyType int
+type derCertificateBytes []byte
+
+const (
+	eckey keyType = iota
+	rsakey
+)
+
+const (
+	// OCSPGood means that the certificate is valid.
+	OCSPGood = ocsp.Good
+	// OCSPRevoked means that the certificate has been deliberately revoked.
+	OCSPRevoked = ocsp.Revoked
+	// OCSPUnknown means that the OCSP responder doesn't know about the certificate.
+	OCSPUnknown = ocsp.Unknown
+	// OCSPServerFailed means that the OCSP responder failed to process the request.
+	OCSPServerFailed = ocsp.ServerFailed
+)
+
+// GetOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response,
+// the parsed response, and an error, if any. The returned []byte can be passed directly
+// into the OCSPStaple property of a tls.Certificate. If the bundle only contains the
+// issued certificate, this function will try to get the issuer certificate from the
+// IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return
+// values are nil, the OCSP status may be assumed OCSPUnknown.
+func GetOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
+	certificates, err := parsePEMBundle(bundle)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// We only got one certificate, means we have no issuer certificate - get it.
+	if len(certificates) == 1 {
+		// TODO: build fallback. If this fails, check the remaining array entries.
+		if len(certificates[0].IssuingCertificateURL) == 0 {
+			return nil, nil, errors.New("no issuing certificate URL")
+		}
+
+		resp, err := http.Get(certificates[0].IssuingCertificateURL[0])
+		if err != nil {
+			return nil, nil, err
+		}
+		defer resp.Body.Close()
+
+		issuerBytes, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
+		if err != nil {
+			return nil, nil, err
+		}
+
+		issuerCert, err := x509.ParseCertificate(issuerBytes)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		// Insert it into the slice on position 0
+		// We want it ordered right SRV CRT -> CA
+		certificates = append(certificates, issuerCert)
+	}
+
+	// We expect the certificate slice to be ordered downwards the chain.
+	// SRV CRT -> CA. We need to pull the cert and issuer cert out of it,
+	// which should always be the last two certificates.
+	issuedCert := certificates[0]
+	issuerCert := certificates[1]
+
+	// Finally kick off the OCSP request.
+	ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	reader := bytes.NewReader(ocspReq)
+	req, err := http.Post(issuedCert.OCSPServer[0], "application/ocsp-request", reader)
+	if err != nil {
+		return nil, nil, err
+	}
+	defer req.Body.Close()
+
+	ocspResBytes, err := ioutil.ReadAll(limitReader(req.Body, 1024*1024))
+	ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	if ocspRes.Certificate == nil {
+		err = ocspRes.CheckSignatureFrom(issuerCert)
+		if err != nil {
+			return nil, nil, err
+		}
+	}
+
+	return ocspResBytes, ocspRes, nil
+}
+
+func getKeyAuthorization(token string, key interface{}) (string, error) {
+	// Generate the Key Authorization for the challenge
+	jwk := keyAsJWK(key)
+	if jwk == nil {
+		return "", errors.New("Could not generate JWK from key.")
+	}
+	thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
+	if err != nil {
+		return "", err
+	}
+
+	// unpad the base64URL
+	keyThumb := base64.URLEncoding.EncodeToString(thumbBytes)
+	index := strings.Index(keyThumb, "=")
+	if index != -1 {
+		keyThumb = keyThumb[:index]
+	}
+
+	return token + "." + keyThumb, nil
+}
+
+// Derive the shared secret according to acme spec 5.6
+func performECDH(priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, outLen int, label string) []byte {
+	// Derive Z from the private and public keys according to SEC 1 Ver. 2.0 - 3.3.1
+	Z, _ := priv.PublicKey.ScalarMult(pub.X, pub.Y, priv.D.Bytes())
+
+	if len(Z.Bytes())+len(label)+4 > 384 {
+		return nil
+	}
+
+	if outLen < 384*(2^32-1) {
+		return nil
+	}
+
+	// Derive the shared secret key using the ANS X9.63 KDF - SEC 1 Ver. 2.0 - 3.6.1
+	hasher := sha3.New384()
+	buffer := make([]byte, outLen)
+	bufferLen := 0
+	for i := 0; i < outLen/384; i++ {
+		hasher.Reset()
+
+		// Ki = Hash(Z || Counter || [SharedInfo])
+		hasher.Write(Z.Bytes())
+		binary.Write(hasher, binary.BigEndian, i)
+		hasher.Write([]byte(label))
+
+		hash := hasher.Sum(nil)
+		copied := copy(buffer[bufferLen:], hash)
+		bufferLen += copied
+	}
+
+	return buffer
+}
+
+// parsePEMBundle parses a certificate bundle from top to bottom and returns
+// a slice of x509 certificates. This function will error if no certificates are found.
+func parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
+	var certificates []*x509.Certificate
+
+	remaining := bundle
+	for len(remaining) != 0 {
+		certBlock, rem := pem.Decode(remaining)
+		// Thanks golang for having me do this :[
+		remaining = rem
+		if certBlock == nil {
+			return nil, errors.New("Could not decode certificate.")
+		}
+
+		cert, err := x509.ParseCertificate(certBlock.Bytes)
+		if err != nil {
+			return nil, err
+		}
+
+		certificates = append(certificates, cert)
+	}
+
+	if len(certificates) == 0 {
+		return nil, errors.New("No certificates were found while parsing the bundle.")
+	}
+
+	return certificates, nil
+}
+
+func generatePrivateKey(t keyType, keyLength int) (crypto.PrivateKey, error) {
+	switch t {
+	case eckey:
+		return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
+	case rsakey:
+		return rsa.GenerateKey(rand.Reader, keyLength)
+	}
+
+	return nil, fmt.Errorf("Invalid keytype: %d", t)
+}
+
+func generateCsr(privateKey *rsa.PrivateKey, domain string, san []string) ([]byte, error) {
+	template := x509.CertificateRequest{
+		Subject: pkix.Name{
+			CommonName: domain,
+		},
+	}
+
+	if len(san) > 0 {
+		template.DNSNames = san
+	}
+
+	return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
+}
+
+func pemEncode(data interface{}) []byte {
+	var pemBlock *pem.Block
+	switch key := data.(type) {
+	case *rsa.PrivateKey:
+		pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
+		break
+	case derCertificateBytes:
+		pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(derCertificateBytes))}
+	}
+
+	return pem.EncodeToMemory(pemBlock)
+}
+
+func pemDecode(data []byte) (*pem.Block, error) {
+	pemBlock, _ := pem.Decode(data)
+	if pemBlock == nil {
+		return nil, fmt.Errorf("Pem decode did not yield a valid block. Is the certificate in the right format?")
+	}
+
+	return pemBlock, nil
+}
+
+func pemDecodeTox509(pem []byte) (*x509.Certificate, error) {
+	pemBlock, err := pemDecode(pem)
+	if pemBlock == nil {
+		return nil, err
+	}
+
+	return x509.ParseCertificate(pemBlock.Bytes)
+}
+
+// GetPEMCertExpiration returns the "NotAfter" date of a PEM encoded certificate.
+// The certificate has to be PEM encoded. Any other encodings like DER will fail.
+func GetPEMCertExpiration(cert []byte) (time.Time, error) {
+	pemBlock, err := pemDecode(cert)
+	if pemBlock == nil {
+		return time.Time{}, err
+	}
+
+	return getCertExpiration(pemBlock.Bytes)
+}
+
+// getCertExpiration returns the "NotAfter" date of a DER encoded certificate.
+func getCertExpiration(cert []byte) (time.Time, error) {
+	pCert, err := x509.ParseCertificate(cert)
+	if err != nil {
+		return time.Time{}, err
+	}
+
+	return pCert.NotAfter, nil
+}
+
+func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) {
+	derBytes, err := generateDerCert(privKey, time.Time{}, domain)
+	if err != nil {
+		return nil, err
+	}
+
+	return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
+}
+
+func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) {
+	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+	if err != nil {
+		return nil, err
+	}
+
+	if expiration.IsZero() {
+		expiration = time.Now().Add(365)
+	}
+
+	template := x509.Certificate{
+		SerialNumber: serialNumber,
+		Subject: pkix.Name{
+			CommonName: "ACME Challenge TEMP",
+		},
+		NotBefore: time.Now(),
+		NotAfter:  expiration,
+
+		KeyUsage:              x509.KeyUsageKeyEncipherment,
+		BasicConstraintsValid: true,
+		DNSNames:              []string{domain},
+	}
+
+	return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
+}
+
+func limitReader(rd io.ReadCloser, numBytes int64) io.ReadCloser {
+	return http.MaxBytesReader(nil, rd, numBytes)
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/crypto_test.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/crypto_test.go
new file mode 100644
index 00000000..81ab287e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/crypto_test.go
@@ -0,0 +1,92 @@
+package acme
+
+import (
+	"bytes"
+	"crypto/rsa"
+	"testing"
+	"time"
+)
+
+func TestGeneratePrivateKey(t *testing.T) {
+	key, err := generatePrivateKey(rsakey, 32)
+	if err != nil {
+		t.Error("Error generating private key:", err)
+	}
+	if key == nil {
+		t.Error("Expected key to not be nil, but it was")
+	}
+}
+
+func TestGenerateCSR(t *testing.T) {
+	key, err := generatePrivateKey(rsakey, 512)
+	if err != nil {
+		t.Fatal("Error generating private key:", err)
+	}
+
+	csr, err := generateCsr(key.(*rsa.PrivateKey), "fizz.buzz", nil)
+	if err != nil {
+		t.Error("Error generating CSR:", err)
+	}
+	if csr == nil || len(csr) == 0 {
+		t.Error("Expected CSR with data, but it was nil or length 0")
+	}
+}
+
+func TestPEMEncode(t *testing.T) {
+	buf := bytes.NewBufferString("TestingRSAIsSoMuchFun")
+
+	reader := MockRandReader{b: buf}
+	key, err := rsa.GenerateKey(reader, 32)
+	if err != nil {
+		t.Fatal("Error generating private key:", err)
+	}
+
+	data := pemEncode(key)
+
+	if data == nil {
+		t.Fatal("Expected result to not be nil, but it was")
+	}
+	if len(data) != 127 {
+		t.Errorf("Expected PEM encoding to be length 127, but it was %d", len(data))
+	}
+}
+
+func TestPEMCertExpiration(t *testing.T) {
+	privKey, err := generatePrivateKey(rsakey, 2048)
+	if err != nil {
+		t.Fatal("Error generating private key:", err)
+	}
+
+	expiration := time.Now().Add(365)
+	expiration = expiration.Round(time.Second)
+	certBytes, err := generateDerCert(privKey.(*rsa.PrivateKey), expiration, "test.com")
+	if err != nil {
+		t.Fatal("Error generating cert:", err)
+	}
+
+	buf := bytes.NewBufferString("TestingRSAIsSoMuchFun")
+
+	// Some random string should return an error.
+	if ctime, err := GetPEMCertExpiration(buf.Bytes()); err == nil {
+		t.Errorf("Expected getCertExpiration to return an error for garbage string but returned %v", ctime)
+	}
+
+	// A DER encoded certificate should return an error.
+	if _, err := GetPEMCertExpiration(certBytes); err == nil {
+		t.Errorf("Expected getCertExpiration to return an error for DER certificates but returned none.")
+	}
+
+	// A PEM encoded certificate should work ok.
+	pemCert := pemEncode(derCertificateBytes(certBytes))
+	if ctime, err := GetPEMCertExpiration(pemCert); err != nil || !ctime.Equal(expiration.UTC()) {
+		t.Errorf("Expected getCertExpiration to return %v but returned %v. Error: %v", expiration, ctime, err)
+	}
+}
+
+type MockRandReader struct {
+	b *bytes.Buffer
+}
+
+func (r MockRandReader) Read(p []byte) (int, error) {
+	return r.b.Read(p)
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/dns_challenge.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/dns_challenge.go
new file mode 100644
index 00000000..8d2a213b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/dns_challenge.go
@@ -0,0 +1 @@
+package acme
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/error.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/error.go
new file mode 100644
index 00000000..b32561a3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/error.go
@@ -0,0 +1,73 @@
+package acme
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+)
+
+const (
+	tosAgreementError = "Must agree to subscriber agreement before any further actions"
+)
+
+// RemoteError is the base type for all errors specific to the ACME protocol.
+type RemoteError struct {
+	StatusCode int    `json:"status,omitempty"`
+	Type       string `json:"type"`
+	Detail     string `json:"detail"`
+}
+
+func (e RemoteError) Error() string {
+	return fmt.Sprintf("acme: Error %d - %s - %s", e.StatusCode, e.Type, e.Detail)
+}
+
+// TOSError represents the error which is returned if the user needs to
+// accept the TOS.
+// TODO: include the new TOS url if we can somehow obtain it.
+type TOSError struct {
+	RemoteError
+}
+
+type domainError struct {
+	Domain string
+	Error  error
+}
+
+type challengeError struct {
+	RemoteError
+	records []validationRecord
+}
+
+func (c challengeError) Error() string {
+
+	var errStr string
+	for _, validation := range c.records {
+		errStr = errStr + fmt.Sprintf("\tValidation for %s:%s\n\tResolved to:\n\t\t%s\n\tUsed: %s\n\n",
+			validation.Hostname, validation.Port, strings.Join(validation.ResolvedAddresses, "\n\t\t"), validation.UsedAddress)
+	}
+
+	return fmt.Sprintf("%s\nError Detail:\n%s", c.RemoteError.Error(), errStr)
+}
+
+func handleHTTPError(resp *http.Response) error {
+	var errorDetail RemoteError
+	decoder := json.NewDecoder(resp.Body)
+	err := decoder.Decode(&errorDetail)
+	if err != nil {
+		return err
+	}
+
+	errorDetail.StatusCode = resp.StatusCode
+
+	// Check for errors we handle specifically
+	if errorDetail.StatusCode == http.StatusForbidden && errorDetail.Detail == tosAgreementError {
+		return TOSError{errorDetail}
+	}
+
+	return errorDetail
+}
+
+func handleChallengeError(chlng challenge) error {
+	return challengeError{chlng.Error, chlng.ValidationRecords}
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/http_challenge.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/http_challenge.go
new file mode 100644
index 00000000..00ad5895
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/http_challenge.go
@@ -0,0 +1,61 @@
+package acme
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"strings"
+)
+
+type httpChallenge struct {
+	jws      *jws
+	validate validateFunc
+	optPort  string
+}
+
+func (s *httpChallenge) Solve(chlng challenge, domain string) error {
+
+	logf("[INFO][%s] acme: Trying to solve HTTP-01", domain)
+
+	// Generate the Key Authorization for the challenge
+	keyAuth, err := getKeyAuthorization(chlng.Token, &s.jws.privKey.PublicKey)
+	if err != nil {
+		return err
+	}
+
+	// Allow for CLI port override
+	port := ":80"
+	if s.optPort != "" {
+		port = ":" + s.optPort
+	}
+
+	listener, err := net.Listen("tcp", domain+port)
+	if err != nil {
+		// if the domain:port bind failed, fall back to :port bind and try that instead.
+		listener, err = net.Listen("tcp", port)
+		if err != nil {
+			return fmt.Errorf("Could not start HTTP server for challenge -> %v", err)
+		}
+	}
+	defer listener.Close()
+
+	path := "/.well-known/acme-challenge/" + chlng.Token
+
+	// The handler validates the HOST header and request type.
+	// For validation it then writes the token the server returned with the challenge
+	mux := http.NewServeMux()
+	mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
+		if strings.HasPrefix(r.Host, domain) && r.Method == "GET" {
+			w.Header().Add("Content-Type", "text/plain")
+			w.Write([]byte(keyAuth))
+			logf("[INFO][%s] Served key authentication", domain)
+		} else {
+			logf("[INFO] Received request for domain %s with method %s", r.Host, r.Method)
+			w.Write([]byte("TEST"))
+		}
+	})
+
+	go http.Serve(listener, mux)
+
+	return s.validate(s.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/http_challenge_test.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/http_challenge_test.go
new file mode 100644
index 00000000..97a9d979
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/http_challenge_test.go
@@ -0,0 +1,57 @@
+package acme
+
+import (
+	"crypto/rsa"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"testing"
+)
+
+func TestHTTPChallenge(t *testing.T) {
+	privKey, _ := generatePrivateKey(rsakey, 512)
+	j := &jws{privKey: privKey.(*rsa.PrivateKey)}
+	clientChallenge := challenge{Type: "http-01", Token: "http1"}
+	mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
+		uri := "http://localhost:23457/.well-known/acme-challenge/" + chlng.Token
+		resp, err := http.Get(uri)
+		if err != nil {
+			return err
+		}
+		defer resp.Body.Close()
+
+		if want := "text/plain"; resp.Header.Get("Content-Type") != want {
+			t.Errorf("Get(%q) Content-Type: got %q, want %q", uri, resp.Header.Get("Content-Type"), want)
+		}
+
+		body, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		bodyStr := string(body)
+
+		if bodyStr != chlng.KeyAuthorization {
+			t.Errorf("Get(%q) Body: got %q, want %q", uri, bodyStr, chlng.KeyAuthorization)
+		}
+
+		return nil
+	}
+	solver := &httpChallenge{jws: j, validate: mockValidate, optPort: "23457"}
+
+	if err := solver.Solve(clientChallenge, "localhost:23457"); err != nil {
+		t.Errorf("Solve error: got %v, want nil", err)
+	}
+}
+
+func TestHTTPChallengeInvalidPort(t *testing.T) {
+	privKey, _ := generatePrivateKey(rsakey, 128)
+	j := &jws{privKey: privKey.(*rsa.PrivateKey)}
+	clientChallenge := challenge{Type: "http-01", Token: "http2"}
+	solver := &httpChallenge{jws: j, validate: stubValidate, optPort: "123456"}
+
+	if err := solver.Solve(clientChallenge, "localhost:123456"); err == nil {
+		t.Error("Solve error: got %v, want error", err)
+	} else if want := "invalid port 123456"; !strings.HasSuffix(err.Error(), want) {
+		t.Errorf("Solve error: got %q, want suffix %q", err.Error(), want)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/jws.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/jws.go
new file mode 100644
index 00000000..10ddb1c3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/jws.go
@@ -0,0 +1,93 @@
+package acme
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"fmt"
+	"net/http"
+
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/square/go-jose"
+)
+
+type jws struct {
+	directoryURL string
+	privKey      *rsa.PrivateKey
+	nonces       []string
+}
+
+func keyAsJWK(key interface{}) *jose.JsonWebKey {
+	switch k := key.(type) {
+	case *ecdsa.PublicKey:
+		return &jose.JsonWebKey{Key: k, Algorithm: "EC"}
+	case *rsa.PublicKey:
+		return &jose.JsonWebKey{Key: k, Algorithm: "RSA"}
+
+	default:
+		return nil
+	}
+}
+
+// Posts a JWS signed message to the specified URL
+func (j *jws) post(url string, content []byte) (*http.Response, error) {
+	signedContent, err := j.signContent(content)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := http.Post(url, "application/jose+json", bytes.NewBuffer([]byte(signedContent.FullSerialize())))
+	if err != nil {
+		return nil, err
+	}
+
+	j.getNonceFromResponse(resp)
+
+	return resp, err
+}
+
+func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
+	// TODO: support other algorithms - RS512
+	signer, err := jose.NewSigner(jose.RS256, j.privKey)
+	if err != nil {
+		return nil, err
+	}
+	signer.SetNonceSource(j)
+
+	signed, err := signer.Sign(content)
+	if err != nil {
+		return nil, err
+	}
+	return signed, nil
+}
+
+func (j *jws) getNonceFromResponse(resp *http.Response) error {
+	nonce := resp.Header.Get("Replay-Nonce")
+	if nonce == "" {
+		return fmt.Errorf("Server did not respond with a proper nonce header.")
+	}
+
+	j.nonces = append(j.nonces, nonce)
+	return nil
+}
+
+func (j *jws) getNonce() error {
+	resp, err := http.Head(j.directoryURL)
+	if err != nil {
+		return err
+	}
+
+	return j.getNonceFromResponse(resp)
+}
+
+func (j *jws) Nonce() (string, error) {
+	nonce := ""
+	if len(j.nonces) == 0 {
+		err := j.getNonce()
+		if err != nil {
+			return nonce, err
+		}
+	}
+
+	nonce, j.nonces = j.nonces[len(j.nonces)-1], j.nonces[:len(j.nonces)-1]
+	return nonce, nil
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/messages.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/messages.go
new file mode 100644
index 00000000..ab883942
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/messages.go
@@ -0,0 +1,118 @@
+package acme
+
+import (
+	"time"
+
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/square/go-jose"
+)
+
+type directory struct {
+	NewAuthzURL   string `json:"new-authz"`
+	NewCertURL    string `json:"new-cert"`
+	NewRegURL     string `json:"new-reg"`
+	RevokeCertURL string `json:"revoke-cert"`
+}
+
+type recoveryKeyMessage struct {
+	Length int             `json:"length,omitempty"`
+	Client jose.JsonWebKey `json:"client,omitempty"`
+	Server jose.JsonWebKey `json:"client,omitempty"`
+}
+
+type registrationMessage struct {
+	Resource string   `json:"resource"`
+	Contact  []string `json:"contact"`
+	//	RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"`
+}
+
+// Registration is returned by the ACME server after the registration
+// The client implementation should save this registration somewhere.
+type Registration struct {
+	Resource string `json:"resource,omitempty"`
+	ID       int    `json:"id"`
+	Key      struct {
+		Kty string `json:"kty"`
+		N   string `json:"n"`
+		E   string `json:"e"`
+	} `json:"key"`
+	Contact        []string `json:"contact"`
+	Agreement      string   `json:"agreement,omitempty"`
+	Authorizations string   `json:"authorizations,omitempty"`
+	Certificates   string   `json:"certificates,omitempty"`
+	//	RecoveryKey    recoveryKeyMessage `json:"recoveryKey,omitempty"`
+}
+
+// RegistrationResource represents all important informations about a registration
+// of which the client needs to keep track itself.
+type RegistrationResource struct {
+	Body        Registration `json:"body,omitempty"`
+	URI         string       `json:"uri,omitempty"`
+	NewAuthzURL string       `json:"new_authzr_uri,omitempty"`
+	TosURL      string       `json:"terms_of_service,omitempty"`
+}
+
+type authorizationResource struct {
+	Body       authorization
+	Domain     string
+	NewCertURL string
+	AuthURL    string
+}
+
+type authorization struct {
+	Resource     string      `json:"resource,omitempty"`
+	Identifier   identifier  `json:"identifier"`
+	Status       string      `json:"status,omitempty"`
+	Expires      time.Time   `json:"expires,omitempty"`
+	Challenges   []challenge `json:"challenges,omitempty"`
+	Combinations [][]int     `json:"combinations,omitempty"`
+}
+
+type identifier struct {
+	Type  string `json:"type"`
+	Value string `json:"value"`
+}
+
+type validationRecord struct {
+	URI               string   `json:"url,omitempty"`
+	Hostname          string   `json:"hostname,omitempty"`
+	Port              string   `json:"port,omitempty"`
+	ResolvedAddresses []string `json:"addressesResolved,omitempty"`
+	UsedAddress       string   `json:"addressUsed,omitempty"`
+}
+
+type challenge struct {
+	Resource          string             `json:"resource,omitempty"`
+	Type              string             `json:"type,omitempty"`
+	Status            string             `json:"status,omitempty"`
+	URI               string             `json:"uri,omitempty"`
+	Token             string             `json:"token,omitempty"`
+	KeyAuthorization  string             `json:"keyAuthorization,omitempty"`
+	TLS               bool               `json:"tls,omitempty"`
+	Iterations        int                `json:"n,omitempty"`
+	Error             RemoteError        `json:"error,omitempty"`
+	ValidationRecords []validationRecord `json:"validationRecord,omitempty"`
+}
+
+type csrMessage struct {
+	Resource       string   `json:"resource,omitempty"`
+	Csr            string   `json:"csr"`
+	Authorizations []string `json:"authorizations"`
+}
+
+type revokeCertMessage struct {
+	Resource    string `json:"resource"`
+	Certificate string `json:"certificate"`
+}
+
+// CertificateResource represents a CA issued certificate.
+// PrivateKey and Certificate are both already PEM encoded
+// and can be directly written to disk. Certificate may
+// be a certificate bundle, depending on the options supplied
+// to create it.
+type CertificateResource struct {
+	Domain        string `json:"domain"`
+	CertURL       string `json:"certUrl"`
+	CertStableURL string `json:"certStableUrl"`
+	PrivateKey    []byte `json:"-"`
+	Certificate   []byte `json:"-"`
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/pop_challenge.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/pop_challenge.go
new file mode 100644
index 00000000..8d2a213b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/pop_challenge.go
@@ -0,0 +1 @@
+package acme
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/tls_sni_challenge.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/tls_sni_challenge.go
new file mode 100644
index 00000000..ad099d54
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/tls_sni_challenge.go
@@ -0,0 +1,80 @@
+package acme
+
+import (
+	"crypto/rsa"
+	"crypto/sha256"
+	"crypto/tls"
+	"encoding/hex"
+	"fmt"
+	"net/http"
+)
+
+type tlsSNIChallenge struct {
+	jws      *jws
+	validate validateFunc
+	optPort  string
+}
+
+func (t *tlsSNIChallenge) Solve(chlng challenge, domain string) error {
+	// FIXME: https://github.com/ietf-wg-acme/acme/pull/22
+	// Currently we implement this challenge to track boulder, not the current spec!
+
+	logf("[INFO][%s] acme: Trying to solve TLS-SNI-01", domain)
+
+	// Generate the Key Authorization for the challenge
+	keyAuth, err := getKeyAuthorization(chlng.Token, &t.jws.privKey.PublicKey)
+	if err != nil {
+		return err
+	}
+
+	cert, err := t.generateCertificate(keyAuth)
+	if err != nil {
+		return err
+	}
+
+	// Allow for CLI port override
+	port := ":443"
+	if t.optPort != "" {
+		port = ":" + t.optPort
+	}
+
+	tlsConf := new(tls.Config)
+	tlsConf.Certificates = []tls.Certificate{cert}
+
+	listener, err := tls.Listen("tcp", port, tlsConf)
+	if err != nil {
+		return fmt.Errorf("Could not start HTTPS server for challenge -> %v", err)
+	}
+	defer listener.Close()
+
+	go http.Serve(listener, nil)
+
+	return t.validate(t.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
+}
+
+func (t *tlsSNIChallenge) generateCertificate(keyAuth string) (tls.Certificate, error) {
+
+	zBytes := sha256.Sum256([]byte(keyAuth))
+	z := hex.EncodeToString(zBytes[:sha256.Size])
+
+	// generate a new RSA key for the certificates
+	tempPrivKey, err := generatePrivateKey(rsakey, 2048)
+	if err != nil {
+		return tls.Certificate{}, err
+	}
+	rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
+	rsaPrivPEM := pemEncode(rsaPrivKey)
+
+	domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
+	tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
+	if err != nil {
+		return tls.Certificate{}, err
+	}
+
+	certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
+	if err != nil {
+		return tls.Certificate{}, err
+	}
+
+	return certificate, nil
+}
diff --git a/Godeps/_workspace/src/github.com/xenolf/lego/acme/tls_sni_challenge_test.go b/Godeps/_workspace/src/github.com/xenolf/lego/acme/tls_sni_challenge_test.go
new file mode 100644
index 00000000..8f3ccbe1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/xenolf/lego/acme/tls_sni_challenge_test.go
@@ -0,0 +1,64 @@
+package acme
+
+import (
+	"crypto/rsa"
+	"crypto/sha256"
+	"crypto/tls"
+	"encoding/hex"
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func TestTLSSNIChallenge(t *testing.T) {
+	privKey, _ := generatePrivateKey(rsakey, 512)
+	j := &jws{privKey: privKey.(*rsa.PrivateKey)}
+	clientChallenge := challenge{Type: "tls-sni-01", Token: "tlssni1"}
+	mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
+		conn, err := tls.Dial("tcp", "localhost:23457", &tls.Config{
+			InsecureSkipVerify: true,
+		})
+		if err != nil {
+			t.Errorf("Expected to connect to challenge server without an error. %s", err.Error())
+		}
+
+		// Expect the server to only return one certificate
+		connState := conn.ConnectionState()
+		if count := len(connState.PeerCertificates); count != 1 {
+			t.Errorf("Expected the challenge server to return exactly one certificate but got %d", count)
+		}
+
+		remoteCert := connState.PeerCertificates[0]
+		if count := len(remoteCert.DNSNames); count != 1 {
+			t.Errorf("Expected the challenge certificate to have exactly one DNSNames entry but had %d", count)
+		}
+
+		zBytes := sha256.Sum256([]byte(chlng.KeyAuthorization))
+		z := hex.EncodeToString(zBytes[:sha256.Size])
+		domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
+
+		if remoteCert.DNSNames[0] != domain {
+			t.Errorf("Expected the challenge certificate DNSName to match %s but was %s", domain, remoteCert.DNSNames[0])
+		}
+
+		return nil
+	}
+	solver := &tlsSNIChallenge{jws: j, validate: mockValidate, optPort: "23457"}
+
+	if err := solver.Solve(clientChallenge, "localhost:23457"); err != nil {
+		t.Errorf("Solve error: got %v, want nil", err)
+	}
+}
+
+func TestTLSSNIChallengeInvalidPort(t *testing.T) {
+	privKey, _ := generatePrivateKey(rsakey, 128)
+	j := &jws{privKey: privKey.(*rsa.PrivateKey)}
+	clientChallenge := challenge{Type: "tls-sni-01", Token: "tlssni2"}
+	solver := &tlsSNIChallenge{jws: j, validate: stubValidate, optPort: "123456"}
+
+	if err := solver.Solve(clientChallenge, "localhost:123456"); err == nil {
+		t.Error("Solve error: got %v, want error", err)
+	} else if want := "invalid port 123456"; !strings.HasSuffix(err.Error(), want) {
+		t.Errorf("Solve error: got %q, want suffix %q", err.Error(), want)
+	}
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ocsp/ocsp.go b/Godeps/_workspace/src/golang.org/x/crypto/ocsp/ocsp.go
new file mode 100644
index 00000000..e963863f
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/ocsp/ocsp.go
@@ -0,0 +1,573 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ocsp parses OCSP responses as specified in RFC 2560. OCSP responses
+// are signed messages attesting to the validity of a certificate for a small
+// period of time. This is used to manage revocation for X.509 certificates.
+package ocsp
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha1"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
+	"errors"
+	"math/big"
+	"time"
+)
+
+var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})
+
+// These are internal structures that reflect the ASN.1 structure of an OCSP
+// response. See RFC 2560, section 4.2.
+
+const (
+	ocspSuccess       = 0
+	ocspMalformed     = 1
+	ocspInternalError = 2
+	ocspTryLater      = 3
+	ocspSigRequired   = 4
+	ocspUnauthorized  = 5
+)
+
+type certID struct {
+	HashAlgorithm pkix.AlgorithmIdentifier
+	NameHash      []byte
+	IssuerKeyHash []byte
+	SerialNumber  *big.Int
+}
+
+// https://tools.ietf.org/html/rfc2560#section-4.1.1
+type ocspRequest struct {
+	TBSRequest tbsRequest
+}
+
+type tbsRequest struct {
+	Version       int              `asn1:"explicit,tag:0,default:0"`
+	RequestorName pkix.RDNSequence `asn1:"explicit,tag:1,optional"`
+	RequestList   []request
+}
+
+type request struct {
+	Cert certID
+}
+
+type responseASN1 struct {
+	Status   asn1.Enumerated
+	Response responseBytes `asn1:"explicit,tag:0"`
+}
+
+type responseBytes struct {
+	ResponseType asn1.ObjectIdentifier
+	Response     []byte
+}
+
+type basicResponse struct {
+	TBSResponseData    responseData
+	SignatureAlgorithm pkix.AlgorithmIdentifier
+	Signature          asn1.BitString
+	Certificates       []asn1.RawValue `asn1:"explicit,tag:0,optional"`
+}
+
+type responseData struct {
+	Raw           asn1.RawContent
+	Version       int              `asn1:"optional,default:1,explicit,tag:0"`
+	ResponderName pkix.RDNSequence `asn1:"optional,explicit,tag:1"`
+	KeyHash       []byte           `asn1:"optional,explicit,tag:2"`
+	ProducedAt    time.Time
+	Responses     []singleResponse
+}
+
+type singleResponse struct {
+	CertID     certID
+	Good       asn1.Flag   `asn1:"explicit,tag:0,optional"`
+	Revoked    revokedInfo `asn1:"explicit,tag:1,optional"`
+	Unknown    asn1.Flag   `asn1:"explicit,tag:2,optional"`
+	ThisUpdate time.Time
+	NextUpdate time.Time `asn1:"explicit,tag:0,optional"`
+}
+
+type revokedInfo struct {
+	RevocationTime time.Time
+	Reason         int `asn1:"explicit,tag:0,optional"`
+}
+
+var (
+	oidSignatureMD2WithRSA      = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2}
+	oidSignatureMD5WithRSA      = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4}
+	oidSignatureSHA1WithRSA     = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
+	oidSignatureSHA256WithRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
+	oidSignatureSHA384WithRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
+	oidSignatureSHA512WithRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
+	oidSignatureDSAWithSHA1     = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3}
+	oidSignatureDSAWithSHA256   = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 4, 3, 2}
+	oidSignatureECDSAWithSHA1   = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1}
+	oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
+	oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
+	oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
+)
+
+var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{
+	crypto.SHA1:   asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}),
+	crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}),
+	crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}),
+	crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}),
+}
+
+// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below
+var signatureAlgorithmDetails = []struct {
+	algo       x509.SignatureAlgorithm
+	oid        asn1.ObjectIdentifier
+	pubKeyAlgo x509.PublicKeyAlgorithm
+	hash       crypto.Hash
+}{
+	{x509.MD2WithRSA, oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */},
+	{x509.MD5WithRSA, oidSignatureMD5WithRSA, x509.RSA, crypto.MD5},
+	{x509.SHA1WithRSA, oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1},
+	{x509.SHA256WithRSA, oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256},
+	{x509.SHA384WithRSA, oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384},
+	{x509.SHA512WithRSA, oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512},
+	{x509.DSAWithSHA1, oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1},
+	{x509.DSAWithSHA256, oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256},
+	{x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1},
+	{x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256},
+	{x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384},
+	{x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512},
+}
+
+// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below
+func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) {
+	var pubType x509.PublicKeyAlgorithm
+
+	switch pub := pub.(type) {
+	case *rsa.PublicKey:
+		pubType = x509.RSA
+		hashFunc = crypto.SHA256
+		sigAlgo.Algorithm = oidSignatureSHA256WithRSA
+		sigAlgo.Parameters = asn1.RawValue{
+			Tag: 5,
+		}
+
+	case *ecdsa.PublicKey:
+		pubType = x509.ECDSA
+
+		switch pub.Curve {
+		case elliptic.P224(), elliptic.P256():
+			hashFunc = crypto.SHA256
+			sigAlgo.Algorithm = oidSignatureECDSAWithSHA256
+		case elliptic.P384():
+			hashFunc = crypto.SHA384
+			sigAlgo.Algorithm = oidSignatureECDSAWithSHA384
+		case elliptic.P521():
+			hashFunc = crypto.SHA512
+			sigAlgo.Algorithm = oidSignatureECDSAWithSHA512
+		default:
+			err = errors.New("x509: unknown elliptic curve")
+		}
+
+	default:
+		err = errors.New("x509: only RSA and ECDSA keys supported")
+	}
+
+	if err != nil {
+		return
+	}
+
+	if requestedSigAlgo == 0 {
+		return
+	}
+
+	found := false
+	for _, details := range signatureAlgorithmDetails {
+		if details.algo == requestedSigAlgo {
+			if details.pubKeyAlgo != pubType {
+				err = errors.New("x509: requested SignatureAlgorithm does not match private key type")
+				return
+			}
+			sigAlgo.Algorithm, hashFunc = details.oid, details.hash
+			if hashFunc == 0 {
+				err = errors.New("x509: cannot sign with hash function requested")
+				return
+			}
+			found = true
+			break
+		}
+	}
+
+	if !found {
+		err = errors.New("x509: unknown SignatureAlgorithm")
+	}
+
+	return
+}
+
+// TODO(agl): this is taken from crypto/x509 and so should probably be exported
+// from crypto/x509 or crypto/x509/pkix.
+func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) x509.SignatureAlgorithm {
+	for _, details := range signatureAlgorithmDetails {
+		if oid.Equal(details.oid) {
+			return details.algo
+		}
+	}
+	return x509.UnknownSignatureAlgorithm
+}
+
+// TODO(rlb): This is not taken from crypto/x509, but it's of the same general form.
+func getHashAlgorithmFromOID(target asn1.ObjectIdentifier) crypto.Hash {
+	for hash, oid := range hashOIDs {
+		if oid.Equal(target) {
+			return hash
+		}
+	}
+	return crypto.Hash(0)
+}
+
+// This is the exposed reflection of the internal OCSP structures.
+
+const (
+	// Good means that the certificate is valid.
+	Good = iota
+	// Revoked means that the certificate has been deliberately revoked.
+	Revoked = iota
+	// Unknown means that the OCSP responder doesn't know about the certificate.
+	Unknown = iota
+	// ServerFailed means that the OCSP responder failed to process the request.
+	ServerFailed = iota
+)
+
+// Request represents an OCSP request. See RFC 2560.
+type Request struct {
+	HashAlgorithm  crypto.Hash
+	IssuerNameHash []byte
+	IssuerKeyHash  []byte
+	SerialNumber   *big.Int
+}
+
+// Response represents an OCSP response. See RFC 2560.
+type Response struct {
+	// Status is one of {Good, Revoked, Unknown, ServerFailed}
+	Status                                        int
+	SerialNumber                                  *big.Int
+	ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time
+	RevocationReason                              int
+	Certificate                                   *x509.Certificate
+	// TBSResponseData contains the raw bytes of the signed response. If
+	// Certificate is nil then this can be used to verify Signature.
+	TBSResponseData    []byte
+	Signature          []byte
+	SignatureAlgorithm x509.SignatureAlgorithm
+}
+
+// CheckSignatureFrom checks that the signature in resp is a valid signature
+// from issuer. This should only be used if resp.Certificate is nil. Otherwise,
+// the OCSP response contained an intermediate certificate that created the
+// signature. That signature is checked by ParseResponse and only
+// resp.Certificate remains to be validated.
+func (resp *Response) CheckSignatureFrom(issuer *x509.Certificate) error {
+	return issuer.CheckSignature(resp.SignatureAlgorithm, resp.TBSResponseData, resp.Signature)
+}
+
+// ParseError results from an invalid OCSP response.
+type ParseError string
+
+func (p ParseError) Error() string {
+	return string(p)
+}
+
+// ParseRequest parses an OCSP request in DER form. It only supports
+// requests for a single certificate. Signed requests are not supported.
+// If a request includes a signature, it will result in a ParseError.
+func ParseRequest(bytes []byte) (*Request, error) {
+	var req ocspRequest
+	rest, err := asn1.Unmarshal(bytes, &req)
+	if err != nil {
+		return nil, err
+	}
+	if len(rest) > 0 {
+		return nil, ParseError("trailing data in OCSP request")
+	}
+
+	if len(req.TBSRequest.RequestList) == 0 {
+		return nil, ParseError("OCSP request contains no request body")
+	}
+	innerRequest := req.TBSRequest.RequestList[0]
+
+	hashFunc := getHashAlgorithmFromOID(innerRequest.Cert.HashAlgorithm.Algorithm)
+	if hashFunc == crypto.Hash(0) {
+		return nil, ParseError("OCSP request uses unknown hash function")
+	}
+
+	return &Request{
+		HashAlgorithm:  hashFunc,
+		IssuerNameHash: innerRequest.Cert.NameHash,
+		IssuerKeyHash:  innerRequest.Cert.IssuerKeyHash,
+		SerialNumber:   innerRequest.Cert.SerialNumber,
+	}, nil
+}
+
+// ParseResponse parses an OCSP response in DER form. It only supports
+// responses for a single certificate. If the response contains a certificate
+// then the signature over the response is checked. If issuer is not nil then
+// it will be used to validate the signature or embedded certificate. Invalid
+// signatures or parse failures will result in a ParseError.
+func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
+	var resp responseASN1
+	rest, err := asn1.Unmarshal(bytes, &resp)
+	if err != nil {
+		return nil, err
+	}
+	if len(rest) > 0 {
+		return nil, ParseError("trailing data in OCSP response")
+	}
+
+	ret := new(Response)
+	if resp.Status != ocspSuccess {
+		ret.Status = ServerFailed
+		return ret, nil
+	}
+
+	if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) {
+		return nil, ParseError("bad OCSP response type")
+	}
+
+	var basicResp basicResponse
+	rest, err = asn1.Unmarshal(resp.Response.Response, &basicResp)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(basicResp.Certificates) > 1 {
+		return nil, ParseError("OCSP response contains bad number of certificates")
+	}
+
+	if len(basicResp.TBSResponseData.Responses) != 1 {
+		return nil, ParseError("OCSP response contains bad number of responses")
+	}
+
+	ret.TBSResponseData = basicResp.TBSResponseData.Raw
+	ret.Signature = basicResp.Signature.RightAlign()
+	ret.SignatureAlgorithm = getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm)
+
+	if len(basicResp.Certificates) > 0 {
+		ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes)
+		if err != nil {
+			return nil, err
+		}
+
+		if err := ret.CheckSignatureFrom(ret.Certificate); err != nil {
+			return nil, ParseError("bad OCSP signature")
+		}
+
+		if issuer != nil {
+			if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil {
+				return nil, ParseError("bad signature on embedded certificate")
+			}
+		}
+	} else if issuer != nil {
+		if err := ret.CheckSignatureFrom(issuer); err != nil {
+			return nil, ParseError("bad OCSP signature")
+		}
+	}
+
+	r := basicResp.TBSResponseData.Responses[0]
+
+	ret.SerialNumber = r.CertID.SerialNumber
+
+	switch {
+	case bool(r.Good):
+		ret.Status = Good
+	case bool(r.Unknown):
+		ret.Status = Unknown
+	default:
+		ret.Status = Revoked
+		ret.RevokedAt = r.Revoked.RevocationTime
+		ret.RevocationReason = r.Revoked.Reason
+	}
+
+	ret.ProducedAt = basicResp.TBSResponseData.ProducedAt
+	ret.ThisUpdate = r.ThisUpdate
+	ret.NextUpdate = r.NextUpdate
+
+	return ret, nil
+}
+
+// RequestOptions contains options for constructing OCSP requests.
+type RequestOptions struct {
+	// Hash contains the hash function that should be used when
+	// constructing the OCSP request. If zero, SHA-1 will be used.
+	Hash crypto.Hash
+}
+
+func (opts *RequestOptions) hash() crypto.Hash {
+	if opts == nil || opts.Hash == 0 {
+		// SHA-1 is nearly universally used in OCSP.
+		return crypto.SHA1
+	}
+	return opts.Hash
+}
+
+// CreateRequest returns a DER-encoded, OCSP request for the status of cert. If
+// opts is nil then sensible defaults are used.
+func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte, error) {
+	hashFunc := opts.hash()
+
+	// OCSP seems to be the only place where these raw hash identifiers are
+	// used. I took the following from
+	// http://msdn.microsoft.com/en-us/library/ff635603.aspx
+	var hashOID asn1.ObjectIdentifier
+	hashOID, ok := hashOIDs[hashFunc]
+	if !ok {
+		return nil, x509.ErrUnsupportedAlgorithm
+	}
+
+	if !hashFunc.Available() {
+		return nil, x509.ErrUnsupportedAlgorithm
+	}
+	h := opts.hash().New()
+
+	var publicKeyInfo struct {
+		Algorithm pkix.AlgorithmIdentifier
+		PublicKey asn1.BitString
+	}
+	if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
+		return nil, err
+	}
+
+	h.Write(publicKeyInfo.PublicKey.RightAlign())
+	issuerKeyHash := h.Sum(nil)
+
+	h.Reset()
+	h.Write(issuer.RawSubject)
+	issuerNameHash := h.Sum(nil)
+
+	return asn1.Marshal(ocspRequest{
+		tbsRequest{
+			Version: 0,
+			RequestList: []request{
+				{
+					Cert: certID{
+						pkix.AlgorithmIdentifier{
+							Algorithm:  hashOID,
+							Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
+						},
+						issuerNameHash,
+						issuerKeyHash,
+						cert.SerialNumber,
+					},
+				},
+			},
+		},
+	})
+}
+
+// CreateResponse returns a DER-encoded OCSP response with the specified contents.
+// The fields in the response are populated as follows:
+//
+// The responder cert is used to populate the ResponderName field, and the certificate
+// itself is provided alongside the OCSP response signature.
+//
+// The issuer cert is used to puplate the IssuerNameHash and IssuerKeyHash fields.
+// (SHA-1 is used for the hash function; this is not configurable.)
+//
+// The template is used to populate the SerialNumber, RevocationStatus, RevokedAt,
+// RevocationReason, ThisUpdate, and NextUpdate fields.
+//
+// The ProducedAt date is automatically set to the current date, to the nearest minute.
+func CreateResponse(issuer, responderCert *x509.Certificate, template Response, priv crypto.Signer) ([]byte, error) {
+	var publicKeyInfo struct {
+		Algorithm pkix.AlgorithmIdentifier
+		PublicKey asn1.BitString
+	}
+	if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
+		return nil, err
+	}
+
+	h := sha1.New()
+	h.Write(publicKeyInfo.PublicKey.RightAlign())
+	issuerKeyHash := h.Sum(nil)
+
+	h.Reset()
+	h.Write(issuer.RawSubject)
+	issuerNameHash := h.Sum(nil)
+
+	innerResponse := singleResponse{
+		CertID: certID{
+			HashAlgorithm: pkix.AlgorithmIdentifier{
+				Algorithm:  hashOIDs[crypto.SHA1],
+				Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
+			},
+			NameHash:      issuerNameHash,
+			IssuerKeyHash: issuerKeyHash,
+			SerialNumber:  template.SerialNumber,
+		},
+		ThisUpdate: template.ThisUpdate.UTC(),
+		NextUpdate: template.NextUpdate.UTC(),
+	}
+
+	switch template.Status {
+	case Good:
+		innerResponse.Good = true
+	case Unknown:
+		innerResponse.Unknown = true
+	case Revoked:
+		innerResponse.Revoked = revokedInfo{
+			RevocationTime: template.RevokedAt,
+			Reason:         template.RevocationReason,
+		}
+	}
+
+	tbsResponseData := responseData{
+		ResponderName: responderCert.Subject.ToRDNSequence(),
+		ProducedAt:    time.Now().Truncate(time.Minute),
+		Responses:     []singleResponse{innerResponse},
+	}
+
+	tbsResponseDataDER, err := asn1.Marshal(tbsResponseData)
+	if err != nil {
+		return nil, err
+	}
+
+	hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(priv.Public(), template.SignatureAlgorithm)
+	if err != nil {
+		return nil, err
+	}
+
+	responseHash := hashFunc.New()
+	responseHash.Write(tbsResponseDataDER)
+	signature, err := priv.Sign(rand.Reader, responseHash.Sum(nil), hashFunc)
+	if err != nil {
+		return nil, err
+	}
+
+	response := basicResponse{
+		TBSResponseData:    tbsResponseData,
+		SignatureAlgorithm: signatureAlgorithm,
+		Signature: asn1.BitString{
+			Bytes:     signature,
+			BitLength: 8 * len(signature),
+		},
+	}
+	if template.Certificate != nil {
+		response.Certificates = []asn1.RawValue{
+			asn1.RawValue{FullBytes: template.Certificate.Raw},
+		}
+	}
+	responseDER, err := asn1.Marshal(response)
+	if err != nil {
+		return nil, err
+	}
+
+	return asn1.Marshal(responseASN1{
+		Status: ocspSuccess,
+		Response: responseBytes{
+			ResponseType: idPKIXOCSPBasic,
+			Response:     responseDER,
+		},
+	})
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ocsp/ocsp_test.go b/Godeps/_workspace/src/golang.org/x/crypto/ocsp/ocsp_test.go
new file mode 100644
index 00000000..449588cb
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/ocsp/ocsp_test.go
@@ -0,0 +1,453 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ocsp
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/sha1"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
+	"encoding/hex"
+	"math/big"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestOCSPDecode(t *testing.T) {
+	responseBytes, _ := hex.DecodeString(ocspResponseHex)
+	resp, err := ParseResponse(responseBytes, nil)
+	if err != nil {
+		t.Error(err)
+	}
+
+	expected := Response{
+		Status:           0,
+		SerialNumber:     big.NewInt(0x1d0fa),
+		RevocationReason: 0,
+		ThisUpdate:       time.Date(2010, 7, 7, 15, 1, 5, 0, time.UTC),
+		NextUpdate:       time.Date(2010, 7, 7, 18, 35, 17, 0, time.UTC),
+	}
+
+	if !reflect.DeepEqual(resp.ThisUpdate, expected.ThisUpdate) {
+		t.Errorf("resp.ThisUpdate: got %d, want %d", resp.ThisUpdate, expected.ThisUpdate)
+	}
+
+	if !reflect.DeepEqual(resp.NextUpdate, expected.NextUpdate) {
+		t.Errorf("resp.NextUpdate: got %d, want %d", resp.NextUpdate, expected.NextUpdate)
+	}
+
+	if resp.Status != expected.Status {
+		t.Errorf("resp.Status: got %d, want %d", resp.Status, expected.Status)
+	}
+
+	if resp.SerialNumber.Cmp(expected.SerialNumber) != 0 {
+		t.Errorf("resp.SerialNumber: got %x, want %x", resp.SerialNumber, expected.SerialNumber)
+	}
+
+	if resp.RevocationReason != expected.RevocationReason {
+		t.Errorf("resp.RevocationReason: got %d, want %d", resp.RevocationReason, expected.RevocationReason)
+	}
+}
+
+func TestOCSPDecodeWithoutCert(t *testing.T) {
+	responseBytes, _ := hex.DecodeString(ocspResponseWithoutCertHex)
+	_, err := ParseResponse(responseBytes, nil)
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+func TestOCSPSignature(t *testing.T) {
+	issuerCert, _ := hex.DecodeString(startComHex)
+	issuer, err := x509.ParseCertificate(issuerCert)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	response, _ := hex.DecodeString(ocspResponseHex)
+	if _, err := ParseResponse(response, issuer); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestOCSPRequest(t *testing.T) {
+	leafCert, _ := hex.DecodeString(leafCertHex)
+	cert, err := x509.ParseCertificate(leafCert)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	issuerCert, _ := hex.DecodeString(issuerCertHex)
+	issuer, err := x509.ParseCertificate(issuerCert)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	request, err := CreateRequest(cert, issuer, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expectedBytes, _ := hex.DecodeString(ocspRequestHex)
+	if !bytes.Equal(request, expectedBytes) {
+		t.Errorf("request: got %x, wanted %x", request, expectedBytes)
+	}
+
+	decodedRequest, err := ParseRequest(expectedBytes)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if decodedRequest.HashAlgorithm != crypto.SHA1 {
+		t.Errorf("request.HashAlgorithm: got %v, want %v", decodedRequest.HashAlgorithm, crypto.SHA1)
+	}
+
+	var publicKeyInfo struct {
+		Algorithm pkix.AlgorithmIdentifier
+		PublicKey asn1.BitString
+	}
+	_, err = asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	h := sha1.New()
+	h.Write(publicKeyInfo.PublicKey.RightAlign())
+	issuerKeyHash := h.Sum(nil)
+
+	h.Reset()
+	h.Write(issuer.RawSubject)
+	issuerNameHash := h.Sum(nil)
+
+	if got := decodedRequest.IssuerKeyHash; !bytes.Equal(got, issuerKeyHash) {
+		t.Errorf("request.IssuerKeyHash: got %x, want %x", got, issuerKeyHash)
+	}
+
+	if got := decodedRequest.IssuerNameHash; !bytes.Equal(got, issuerNameHash) {
+		t.Errorf("request.IssuerKeyHash: got %x, want %x", got, issuerNameHash)
+	}
+
+	if got := decodedRequest.SerialNumber; got.Cmp(cert.SerialNumber) != 0 {
+		t.Errorf("request.SerialNumber: got %x, want %x", got, cert.SerialNumber)
+	}
+}
+
+func TestOCSPResponse(t *testing.T) {
+	leafCert, _ := hex.DecodeString(leafCertHex)
+	leaf, err := x509.ParseCertificate(leafCert)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	issuerCert, _ := hex.DecodeString(issuerCertHex)
+	issuer, err := x509.ParseCertificate(issuerCert)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	responderCert, _ := hex.DecodeString(responderCertHex)
+	responder, err := x509.ParseCertificate(responderCert)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	responderPrivateKeyDER, _ := hex.DecodeString(responderPrivateKeyHex)
+	responderPrivateKey, err := x509.ParsePKCS1PrivateKey(responderPrivateKeyDER)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	producedAt := time.Now().Truncate(time.Minute)
+	thisUpdate := time.Date(2010, 7, 7, 15, 1, 5, 0, time.UTC)
+	nextUpdate := time.Date(2010, 7, 7, 18, 35, 17, 0, time.UTC)
+	template := Response{
+		Status:           Revoked,
+		SerialNumber:     leaf.SerialNumber,
+		ThisUpdate:       thisUpdate,
+		NextUpdate:       nextUpdate,
+		RevokedAt:        thisUpdate,
+		RevocationReason: 1, // keyCompromise
+		Certificate:      responder,
+	}
+
+	responseBytes, err := CreateResponse(issuer, responder, template, responderPrivateKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	resp, err := ParseResponse(responseBytes, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !reflect.DeepEqual(resp.ThisUpdate, template.ThisUpdate) {
+		t.Errorf("resp.ThisUpdate: got %d, want %d", resp.ThisUpdate, template.ThisUpdate)
+	}
+
+	if !reflect.DeepEqual(resp.NextUpdate, template.NextUpdate) {
+		t.Errorf("resp.NextUpdate: got %d, want %d", resp.NextUpdate, template.NextUpdate)
+	}
+
+	if !reflect.DeepEqual(resp.RevokedAt, template.RevokedAt) {
+		t.Errorf("resp.NextUpdate: got %d, want %d", resp.NextUpdate, template.NextUpdate)
+	}
+
+	if !reflect.DeepEqual(resp.ProducedAt, producedAt) {
+		t.Errorf("resp.NextUpdate: got %d, want %d", resp.NextUpdate, template.NextUpdate)
+	}
+
+	if resp.Status != template.Status {
+		t.Errorf("resp.Status: got %d, want %d", resp.Status, template.Status)
+	}
+
+	if resp.SerialNumber.Cmp(template.SerialNumber) != 0 {
+		t.Errorf("resp.SerialNumber: got %x, want %x", resp.SerialNumber, template.SerialNumber)
+	}
+
+	if resp.RevocationReason != template.RevocationReason {
+		t.Errorf("resp.RevocationReason: got %d, want %d", resp.RevocationReason, template.RevocationReason)
+	}
+}
+
+// This OCSP response was taken from Thawte's public OCSP responder.
+// To recreate:
+//   $ openssl s_client -tls1 -showcerts -servername www.google.com -connect www.google.com:443
+// Copy and paste the first certificate into /tmp/cert.crt and the second into
+// /tmp/intermediate.crt
+//   $ openssl ocsp -issuer /tmp/intermediate.crt -cert /tmp/cert.crt -url http://ocsp.thawte.com -resp_text -respout /tmp/ocsp.der
+// Then hex encode the result:
+//   $ python -c 'print file("/tmp/ocsp.der", "r").read().encode("hex")'
+
+const ocspResponseHex = "308206bc0a0100a08206b5308206b106092b0601050507300101048206a23082069e3081" +
+	"c9a14e304c310b300906035504061302494c31163014060355040a130d5374617274436f" +
+	"6d204c74642e312530230603550403131c5374617274436f6d20436c6173732031204f43" +
+	"5350205369676e6572180f32303130303730373137333531375a30663064303c30090605" +
+	"2b0e03021a050004146568874f40750f016a3475625e1f5c93e5a26d580414eb4234d098" +
+	"b0ab9ff41b6b08f7cc642eef0e2c45020301d0fa8000180f323031303037303731353031" +
+	"30355aa011180f32303130303730373138333531375a300d06092a864886f70d01010505" +
+	"000382010100ab557ff070d1d7cebbb5f0ec91a15c3fed22eb2e1b8244f1b84545f013a4" +
+	"fb46214c5e3fbfbebb8a56acc2b9db19f68fd3c3201046b3824d5ba689f99864328710cb" +
+	"467195eb37d84f539e49f859316b32964dc3e47e36814ce94d6c56dd02733b1d0802f7ff" +
+	"4eebdbbd2927dcf580f16cbc290f91e81b53cb365e7223f1d6e20a88ea064104875e0145" +
+	"672b20fc14829d51ca122f5f5d77d3ad6c83889c55c7dc43680ba2fe3cef8b05dbcabdc0" +
+	"d3e09aaf9725597f8c858c2fa38c0d6aed2e6318194420dd1a1137445d13e1c97ab47896" +
+	"17a4e08925f46f867b72e3a4dc1f08cb870b2b0717f7207faa0ac512e628a029aba7457a" +
+	"e63dcf3281e2162d9349a08204ba308204b6308204b23082039aa003020102020101300d" +
+	"06092a864886f70d010105050030818c310b300906035504061302494c31163014060355" +
+	"040a130d5374617274436f6d204c74642e312b3029060355040b13225365637572652044" +
+	"69676974616c204365727469666963617465205369676e696e6731383036060355040313" +
+	"2f5374617274436f6d20436c6173732031205072696d61727920496e7465726d65646961" +
+	"746520536572766572204341301e170d3037313032353030323330365a170d3132313032" +
+	"333030323330365a304c310b300906035504061302494c31163014060355040a130d5374" +
+	"617274436f6d204c74642e312530230603550403131c5374617274436f6d20436c617373" +
+	"2031204f435350205369676e657230820122300d06092a864886f70d0101010500038201" +
+	"0f003082010a0282010100b9561b4c45318717178084e96e178df2255e18ed8d8ecc7c2b" +
+	"7b51a6c1c2e6bf0aa3603066f132fe10ae97b50e99fa24b83fc53dd2777496387d14e1c3" +
+	"a9b6a4933e2ac12413d085570a95b8147414a0bc007c7bcf222446ef7f1a156d7ea1c577" +
+	"fc5f0facdfd42eb0f5974990cb2f5cefebceef4d1bdc7ae5c1075c5a99a93171f2b0845b" +
+	"4ff0864e973fcfe32f9d7511ff87a3e943410c90a4493a306b6944359340a9ca96f02b66" +
+	"ce67f028df2980a6aaee8d5d5d452b8b0eb93f923cc1e23fcccbdbe7ffcb114d08fa7a6a" +
+	"3c404f825d1a0e715935cf623a8c7b59670014ed0622f6089a9447a7a19010f7fe58f841" +
+	"29a2765ea367824d1c3bb2fda308530203010001a382015c30820158300c0603551d1301" +
+	"01ff04023000300b0603551d0f0404030203a8301e0603551d250417301506082b060105" +
+	"0507030906092b0601050507300105301d0603551d0e0416041445e0a36695414c5dd449" +
+	"bc00e33cdcdbd2343e173081a80603551d230481a030819d8014eb4234d098b0ab9ff41b" +
+	"6b08f7cc642eef0e2c45a18181a47f307d310b300906035504061302494c311630140603" +
+	"55040a130d5374617274436f6d204c74642e312b3029060355040b132253656375726520" +
+	"4469676974616c204365727469666963617465205369676e696e67312930270603550403" +
+	"13205374617274436f6d2043657274696669636174696f6e20417574686f726974798201" +
+	"0a30230603551d12041c301a8618687474703a2f2f7777772e737461727473736c2e636f" +
+	"6d2f302c06096086480186f842010d041f161d5374617274436f6d205265766f63617469" +
+	"6f6e20417574686f72697479300d06092a864886f70d01010505000382010100182d2215" +
+	"8f0fc0291324fa8574c49bb8ff2835085adcbf7b7fc4191c397ab6951328253fffe1e5ec" +
+	"2a7da0d50fca1a404e6968481366939e666c0a6209073eca57973e2fefa9ed1718e8176f" +
+	"1d85527ff522c08db702e3b2b180f1cbff05d98128252cf0f450f7dd2772f4188047f19d" +
+	"c85317366f94bc52d60f453a550af58e308aaab00ced33040b62bf37f5b1ab2a4f7f0f80" +
+	"f763bf4d707bc8841d7ad9385ee2a4244469260b6f2bf085977af9074796048ecc2f9d48" +
+	"a1d24ce16e41a9941568fec5b42771e118f16c106a54ccc339a4b02166445a167902e75e" +
+	"6d8620b0825dcd18a069b90fd851d10fa8effd409deec02860d26d8d833f304b10669b42"
+
+const startComHex = "308206343082041ca003020102020118300d06092a864886f70d0101050500307d310b30" +
+	"0906035504061302494c31163014060355040a130d5374617274436f6d204c74642e312b" +
+	"3029060355040b1322536563757265204469676974616c20436572746966696361746520" +
+	"5369676e696e6731293027060355040313205374617274436f6d20436572746966696361" +
+	"74696f6e20417574686f72697479301e170d3037313032343230353431375a170d313731" +
+	"3032343230353431375a30818c310b300906035504061302494c31163014060355040a13" +
+	"0d5374617274436f6d204c74642e312b3029060355040b13225365637572652044696769" +
+	"74616c204365727469666963617465205369676e696e67313830360603550403132f5374" +
+	"617274436f6d20436c6173732031205072696d61727920496e7465726d65646961746520" +
+	"53657276657220434130820122300d06092a864886f70d01010105000382010f00308201" +
+	"0a0282010100b689c6acef09527807ac9263d0f44418188480561f91aee187fa3250b4d3" +
+	"4706f0e6075f700e10f71dc0ce103634855a0f92ac83c6ac58523fba38e8fce7a724e240" +
+	"a60876c0926e9e2a6d4d3f6e61200adb59ded27d63b33e46fefa215118d7cd30a6ed076e" +
+	"3b7087b4f9faebee823c056f92f7a4dc0a301e9373fe07cad75f809d225852ae06da8b87" +
+	"2369b0e42ad8ea83d2bdf371db705a280faf5a387045123f304dcd3baf17e50fcba0a95d" +
+	"48aab16150cb34cd3c5cc30be810c08c9bf0030362feb26c3e720eee1c432ac9480e5739" +
+	"c43121c810c12c87fe5495521f523c31129b7fe7c0a0a559d5e28f3ef0d5a8e1d77031a9" +
+	"c4b3cfaf6d532f06f4a70203010001a38201ad308201a9300f0603551d130101ff040530" +
+	"030101ff300e0603551d0f0101ff040403020106301d0603551d0e04160414eb4234d098" +
+	"b0ab9ff41b6b08f7cc642eef0e2c45301f0603551d230418301680144e0bef1aa4405ba5" +
+	"17698730ca346843d041aef2306606082b06010505070101045a3058302706082b060105" +
+	"05073001861b687474703a2f2f6f6373702e737461727473736c2e636f6d2f6361302d06" +
+	"082b060105050730028621687474703a2f2f7777772e737461727473736c2e636f6d2f73" +
+	"667363612e637274305b0603551d1f045430523027a025a0238621687474703a2f2f7777" +
+	"772e737461727473736c2e636f6d2f73667363612e63726c3027a025a023862168747470" +
+	"3a2f2f63726c2e737461727473736c2e636f6d2f73667363612e63726c3081800603551d" +
+	"20047930773075060b2b0601040181b5370102013066302e06082b060105050702011622" +
+	"687474703a2f2f7777772e737461727473736c2e636f6d2f706f6c6963792e7064663034" +
+	"06082b060105050702011628687474703a2f2f7777772e737461727473736c2e636f6d2f" +
+	"696e7465726d6564696174652e706466300d06092a864886f70d01010505000382020100" +
+	"2109493ea5886ee00b8b48da314d8ff75657a2e1d36257e9b556f38545753be5501f048b" +
+	"e6a05a3ee700ae85d0fbff200364cbad02e1c69172f8a34dd6dee8cc3fa18aa2e37c37a7" +
+	"c64f8f35d6f4d66e067bdd21d9cf56ffcb302249fe8904f385e5aaf1e71fe875904dddf9" +
+	"46f74234f745580c110d84b0c6da5d3ef9019ee7e1da5595be741c7bfc4d144fac7e5547" +
+	"7d7bf4a50d491e95e8f712c1ccff76a62547d0f37535be97b75816ebaa5c786fec5330af" +
+	"ea044dcca902e3f0b60412f630b1113d904e5664d7dc3c435f7339ef4baf87ebf6fe6888" +
+	"4472ead207c669b0c1a18bef1749d761b145485f3b2021e95bb2ccf4d7e931f50b15613b" +
+	"7a94e3ebd9bc7f94ae6ae3626296a8647cb887f399327e92a252bebbf865cfc9f230fc8b" +
+	"c1c2a696d75f89e15c3480f58f47072fb491bfb1a27e5f4b5ad05b9f248605515a690365" +
+	"434971c5e06f94346bf61bd8a9b04c7e53eb8f48dfca33b548fa364a1a53a6330cd089cd" +
+	"4915cd89313c90c072d7654b52358a461144b93d8e2865a63e799e5c084429adb035112e" +
+	"214eb8d2e7103e5d8483b3c3c2e4d2c6fd094b7409ddf1b3d3193e800da20b19f038e7c5" +
+	"c2afe223db61e29d5c6e2089492e236ab262c145b49faf8ba7f1223bf87de290d07a19fb" +
+	"4a4ce3d27d5f4a8303ed27d6239e6b8db459a2d9ef6c8229dd75193c3f4c108defbb7527" +
+	"d2ae83a7a8ce5ba7"
+
+const ocspResponseWithoutCertHex = "308201d40a0100a08201cd308201c906092b0601050507300101048201ba3082" +
+	"01b630819fa2160414884451ff502a695e2d88f421bad90cf2cecbea7c180f3230313330" +
+	"3631383037323434335a30743072304a300906052b0e03021a0500041448b60d38238df8" +
+	"456e4ee5843ea394111802979f0414884451ff502a695e2d88f421bad90cf2cecbea7c02" +
+	"1100f78b13b946fc9635d8ab49de9d2148218000180f3230313330363138303732343433" +
+	"5aa011180f32303133303632323037323434335a300d06092a864886f70d010105050003" +
+	"82010100103e18b3d297a5e7a6c07a4fc52ac46a15c0eba96f3be17f0ffe84de5b8c8e05" +
+	"5a8f577586a849dc4abd6440eb6fedde4622451e2823c1cbf3558b4e8184959c9fe96eff" +
+	"8bc5f95866c58c6d087519faabfdae37e11d9874f1bc0db292208f645dd848185e4dd38b" +
+	"6a8547dfa7b74d514a8470015719064d35476b95bebb03d4d2845c5ca15202d2784878f2" +
+	"0f904c24f09736f044609e9c271381713400e563023d212db422236440c6f377bbf24b2b" +
+	"9e7dec8698e36a8df68b7592ad3489fb2937afb90eb85d2aa96b81c94c25057dbd4759d9" +
+	"20a1a65c7f0b6427a224b3c98edd96b9b61f706099951188b0289555ad30a216fb774651" +
+	"5a35fca2e054dfa8"
+
+const ocspRequestHex = "30563054a003020100304d304b3049300906052b0e03021a05000414c0fe0278fc991888" +
+	"91b3f212e9c7e1b21ab7bfc004140dfc1df0a9e0f01ce7f2b213177e6f8d157cd4f60210" +
+	"017f77deb3bcbb235d44ccc7dba62e72"
+
+const leafCertHex = "308203c830820331a0030201020210017f77deb3bcbb235d44ccc7dba62e72300d06092a" +
+	"864886f70d01010505003081ba311f301d060355040a1316566572695369676e20547275" +
+	"7374204e6574776f726b31173015060355040b130e566572695369676e2c20496e632e31" +
+	"333031060355040b132a566572695369676e20496e7465726e6174696f6e616c20536572" +
+	"766572204341202d20436c617373203331493047060355040b13407777772e7665726973" +
+	"69676e2e636f6d2f43505320496e636f72702e6279205265662e204c494142494c495459" +
+	"204c54442e286329393720566572695369676e301e170d3132303632313030303030305a" +
+	"170d3133313233313233353935395a3068310b3009060355040613025553311330110603" +
+	"550408130a43616c69666f726e6961311230100603550407130950616c6f20416c746f31" +
+	"173015060355040a130e46616365626f6f6b2c20496e632e311730150603550403140e2a" +
+	"2e66616365626f6f6b2e636f6d30819f300d06092a864886f70d010101050003818d0030" +
+	"818902818100ae94b171e2deccc1693e051063240102e0689ae83c39b6b3e74b97d48d7b" +
+	"23689100b0b496ee62f0e6d356bcf4aa0f50643402f5d1766aa972835a7564723f39bbef" +
+	"5290ded9bcdbf9d3d55dfad23aa03dc604c54d29cf1d4b3bdbd1a809cfae47b44c7eae17" +
+	"c5109bee24a9cf4a8d911bb0fd0415ae4c3f430aa12a557e2ae10203010001a382011e30" +
+	"82011a30090603551d130402300030440603551d20043d303b3039060b6086480186f845" +
+	"01071703302a302806082b06010505070201161c68747470733a2f2f7777772e76657269" +
+	"7369676e2e636f6d2f727061303c0603551d1f043530333031a02fa02d862b687474703a" +
+	"2f2f535652496e746c2d63726c2e766572697369676e2e636f6d2f535652496e746c2e63" +
+	"726c301d0603551d250416301406082b0601050507030106082b06010505070302300b06" +
+	"03551d0f0404030205a0303406082b0601050507010104283026302406082b0601050507" +
+	"30018618687474703a2f2f6f6373702e766572697369676e2e636f6d30270603551d1104" +
+	"20301e820e2a2e66616365626f6f6b2e636f6d820c66616365626f6f6b2e636f6d300d06" +
+	"092a864886f70d0101050500038181005b6c2b75f8ed30aa51aad36aba595e555141951f" +
+	"81a53b447910ac1f76ff78fc2781616b58f3122afc1c87010425e9ed43df1a7ba6498060" +
+	"67e2688af03db58c7df4ee03309a6afc247ccb134dc33e54c6bc1d5133a532a73273b1d7" +
+	"9cadc08e7e1a83116d34523340b0305427a21742827c98916698ee7eaf8c3bdd71700817"
+
+const issuerCertHex = "30820383308202eca003020102021046fcebbab4d02f0f926098233f93078f300d06092a" +
+	"864886f70d0101050500305f310b300906035504061302555331173015060355040a130e" +
+	"566572695369676e2c20496e632e31373035060355040b132e436c617373203320507562" +
+	"6c6963205072696d6172792043657274696669636174696f6e20417574686f7269747930" +
+	"1e170d3937303431373030303030305a170d3136313032343233353935395a3081ba311f" +
+	"301d060355040a1316566572695369676e205472757374204e6574776f726b3117301506" +
+	"0355040b130e566572695369676e2c20496e632e31333031060355040b132a5665726953" +
+	"69676e20496e7465726e6174696f6e616c20536572766572204341202d20436c61737320" +
+	"3331493047060355040b13407777772e766572697369676e2e636f6d2f43505320496e63" +
+	"6f72702e6279205265662e204c494142494c495459204c54442e28632939372056657269" +
+	"5369676e30819f300d06092a864886f70d010101050003818d0030818902818100d88280" +
+	"e8d619027d1f85183925a2652be1bfd405d3bce6363baaf04c6c5bb6e7aa3c734555b2f1" +
+	"bdea9742ed9a340a15d4a95cf54025ddd907c132b2756cc4cabba3fe56277143aa63f530" +
+	"3e9328e5faf1093bf3b74d4e39f75c495ab8c11dd3b28afe70309542cbfe2b518b5a3c3a" +
+	"f9224f90b202a7539c4f34e7ab04b27b6f0203010001a381e33081e0300f0603551d1304" +
+	"0830060101ff02010030440603551d20043d303b3039060b6086480186f8450107010130" +
+	"2a302806082b06010505070201161c68747470733a2f2f7777772e766572697369676e2e" +
+	"636f6d2f43505330340603551d25042d302b06082b0601050507030106082b0601050507" +
+	"030206096086480186f8420401060a6086480186f845010801300b0603551d0f04040302" +
+	"0106301106096086480186f842010104040302010630310603551d1f042a30283026a024" +
+	"a0228620687474703a2f2f63726c2e766572697369676e2e636f6d2f706361332e63726c" +
+	"300d06092a864886f70d010105050003818100408e4997968a73dd8e4def3e61b7caa062" +
+	"adf40e0abb753de26ed82cc7bff4b98c369bcaa2d09c724639f6a682036511c4bcbf2da6" +
+	"f5d93b0ab598fab378b91ef22b4c62d5fdb27a1ddf33fd73f9a5d82d8c2aead1fcb028b6" +
+	"e94948134b838a1b487b24f738de6f4154b8ab576b06dfc7a2d4a9f6f136628088f28b75" +
+	"d68071"
+
+// Key and certificate for the OCSP responder were not taken from the Thawte
+// responder, since CreateResponse requires that we have the private key.
+// Instead, they were generated randomly.
+const responderPrivateKeyHex = "308204a40201000282010100e8155f2d3e6f2e8d14c62a788bd462f9f844e7a6977c83ef" +
+	"1099f0f6616ec5265b56f356e62c5400f0b06a2e7945a82752c636df32a895152d6074df" +
+	"1701dc6ccfbcbec75a70bd2b55ae2be7e6cad3b5fd4cd5b7790ab401a436d3f5f346074f" +
+	"fde8a99d5b723350f0a112076614b12ef79c78991b119453445acf2416ab0046b540db14" +
+	"c9fc0f27b8989ad0f63aa4b8aefc91aa8a72160c36307c60fec78a93d3fddf4259902aa7" +
+	"7e7332971c7d285b6a04f648993c6922a3e9da9adf5f81508c3228791843e5d49f24db2f" +
+	"1290bafd97e655b1049a199f652cd603c4fafa330c390b0da78fbbc67e8fa021cbd74eb9" +
+	"6222b12ace31a77dcf920334dc94581b02030100010282010100bcf0b93d7238bda329a8" +
+	"72e7149f61bcb37c154330ccb3f42a85c9002c2e2bdea039d77d8581cd19bed94078794e" +
+	"56293d601547fc4bf6a2f9002fe5772b92b21b254403b403585e3130cc99ccf08f0ef81a" +
+	"575b38f597ba4660448b54f44bfbb97072b5a2bf043bfeca828cf7741d13698e3f38162b" +
+	"679faa646b82abd9a72c5c7d722c5fc577a76d2c2daac588accad18516d1bbad10b0dfa2" +
+	"05cfe246b59e28608a43942e1b71b0c80498075121de5b900d727c31c42c78cf1db5c0aa" +
+	"5b491e10ea4ed5c0962aaf2ae025dd81fa4ce490d9d6b4a4465411d8e542fc88617e5695" +
+	"1aa4fc8ea166f2b4d0eb89ef17f2b206bd5f1014bf8fe0e71fe62f2cccf102818100f2dc" +
+	"ddf878d553286daad68bac4070a82ffec3dc4666a2750f47879eec913f91836f1d976b60" +
+	"daf9356e078446dafab5bd2e489e5d64f8572ba24a4ba4f3729b5e106c4dd831cc2497a7" +
+	"e6c7507df05cb64aeb1bbc81c1e340d58b5964cf39cff84ea30c29ec5d3f005ee1362698" +
+	"07395037955955655292c3e85f6187fa1f9502818100f4a33c102630840705f8c778a47b" +
+	"87e8da31e68809af981ac5e5999cf1551685d761cdf0d6520361b99aebd5777a940fa64d" +
+	"327c09fa63746fbb3247ec73a86edf115f1fe5c83598db803881ade71c33c6e956118345" +
+	"497b98b5e07bb5be75971465ec78f2f9467e1b74956ca9d4c7c3e314e742a72d8b33889c" +
+	"6c093a466cef0281801d3df0d02124766dd0be98349b19eb36a508c4e679e793ba0a8bef" +
+	"4d786888c1e9947078b1ea28938716677b4ad8c5052af12eb73ac194915264a913709a0b" +
+	"7b9f98d4a18edd781a13d49899f91c20dbd8eb2e61d991ba19b5cdc08893f5cb9d39e5a6" +
+	"0629ea16d426244673b1b3ee72bd30e41fac8395acac40077403de5efd028180050731dd" +
+	"d71b1a2b96c8d538ba90bb6b62c8b1c74c03aae9a9f59d21a7a82b0d572ef06fa9c807bf" +
+	"c373d6b30d809c7871df96510c577421d9860c7383fda0919ece19996b3ca13562159193" +
+	"c0c246471e287f975e8e57034e5136aaf44254e2650def3d51292474c515b1588969112e" +
+	"0a85cc77073e9d64d2c2fc497844284b02818100d71d63eabf416cf677401ebf965f8314" +
+	"120b568a57dd3bd9116c629c40dc0c6948bab3a13cc544c31c7da40e76132ef5dd3f7534" +
+	"45a635930c74326ae3df0edd1bfb1523e3aa259873ac7cf1ac31151ec8f37b528c275622" +
+	"48f99b8bed59fd4da2576aa6ee20d93a684900bf907e80c66d6e2261ae15e55284b4ed9d" +
+	"6bdaa059"
+
+const responderCertHex = "308202e2308201caa003020102020101300d06092a864886f70d01010b05003019311730" +
+	"150603550403130e4f43535020526573706f6e646572301e170d31353031333031353530" +
+	"33335a170d3136303133303135353033335a3019311730150603550403130e4f43535020" +
+	"526573706f6e64657230820122300d06092a864886f70d01010105000382010f00308201" +
+	"0a0282010100e8155f2d3e6f2e8d14c62a788bd462f9f844e7a6977c83ef1099f0f6616e" +
+	"c5265b56f356e62c5400f0b06a2e7945a82752c636df32a895152d6074df1701dc6ccfbc" +
+	"bec75a70bd2b55ae2be7e6cad3b5fd4cd5b7790ab401a436d3f5f346074ffde8a99d5b72" +
+	"3350f0a112076614b12ef79c78991b119453445acf2416ab0046b540db14c9fc0f27b898" +
+	"9ad0f63aa4b8aefc91aa8a72160c36307c60fec78a93d3fddf4259902aa77e7332971c7d" +
+	"285b6a04f648993c6922a3e9da9adf5f81508c3228791843e5d49f24db2f1290bafd97e6" +
+	"55b1049a199f652cd603c4fafa330c390b0da78fbbc67e8fa021cbd74eb96222b12ace31" +
+	"a77dcf920334dc94581b0203010001a3353033300e0603551d0f0101ff04040302078030" +
+	"130603551d25040c300a06082b06010505070309300c0603551d130101ff04023000300d" +
+	"06092a864886f70d01010b05000382010100718012761b5063e18f0dc44644d8e6ab8612" +
+	"31c15fd5357805425d82aec1de85bf6d3e30fce205e3e3b8b795bbe52e40a439286d2288" +
+	"9064f4aeeb150359b9425f1da51b3a5c939018555d13ac42c565a0603786a919328f3267" +
+	"09dce52c22ad958ecb7873b9771d1148b1c4be2efe80ba868919fc9f68b6090c2f33c156" +
+	"d67156e42766a50b5d51e79637b7e58af74c2a951b1e642fa7741fec982cc937de37eff5" +
+	"9e2005d5939bfc031589ca143e6e8ab83f40ee08cc20a6b4a95a318352c28d18528dcaf9" +
+	"66705de17afa19d6e8ae91ddf33179d16ebb6ac2c69cae8373d408ebf8c55308be6c04d9" +
+	"3a25439a94299a65a709756c7a3e568be049d5c38839"
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/doc.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/doc.go
new file mode 100644
index 00000000..3dab530f
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/doc.go
@@ -0,0 +1,66 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package sha3 implements the SHA-3 fixed-output-length hash functions and
+// the SHAKE variable-output-length hash functions defined by FIPS-202.
+//
+// Both types of hash function use the "sponge" construction and the Keccak
+// permutation. For a detailed specification see http://keccak.noekeon.org/
+//
+//
+// Guidance
+//
+// If you aren't sure what function you need, use SHAKE256 with at least 64
+// bytes of output. The SHAKE instances are faster than the SHA3 instances;
+// the latter have to allocate memory to conform to the hash.Hash interface.
+//
+// If you need a secret-key MAC (message authentication code), prepend the
+// secret key to the input, hash with SHAKE256 and read at least 32 bytes of
+// output.
+//
+//
+// Security strengths
+//
+// The SHA3-x (x equals 224, 256, 384, or 512) functions have a security
+// strength against preimage attacks of x bits. Since they only produce "x"
+// bits of output, their collision-resistance is only "x/2" bits.
+//
+// The SHAKE-256 and -128 functions have a generic security strength of 256 and
+// 128 bits against all attacks, provided that at least 2x bits of their output
+// is used.  Requesting more than 64 or 32 bytes of output, respectively, does
+// not increase the collision-resistance of the SHAKE functions.
+//
+//
+// The sponge construction
+//
+// A sponge builds a pseudo-random function from a public pseudo-random
+// permutation, by applying the permutation to a state of "rate + capacity"
+// bytes, but hiding "capacity" of the bytes.
+//
+// A sponge starts out with a zero state. To hash an input using a sponge, up
+// to "rate" bytes of the input are XORed into the sponge's state. The sponge
+// is then "full" and the permutation is applied to "empty" it. This process is
+// repeated until all the input has been "absorbed". The input is then padded.
+// The digest is "squeezed" from the sponge in the same way, except that output
+// output is copied out instead of input being XORed in.
+//
+// A sponge is parameterized by its generic security strength, which is equal
+// to half its capacity; capacity + rate is equal to the permutation's width.
+// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means
+// that the security strength of a sponge instance is equal to (1600 - bitrate) / 2.
+//
+//
+// Recommendations
+//
+// The SHAKE functions are recommended for most new uses. They can produce
+// output of arbitrary length. SHAKE256, with an output length of at least
+// 64 bytes, provides 256-bit security against all attacks.  The Keccak team
+// recommends it for most applications upgrading from SHA2-512. (NIST chose a
+// much stronger, but much slower, sponge instance for SHA3-512.)
+//
+// The SHA-3 functions are "drop-in" replacements for the SHA-2 functions.
+// They produce output of the same length, with the same security strengths
+// against all attacks. This means, in particular, that SHA3-256 only has
+// 128-bit collision resistance, because its output length is 32 bytes.
+package sha3
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/hashes.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/hashes.go
new file mode 100644
index 00000000..2b51cf4e
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/hashes.go
@@ -0,0 +1,65 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+// This file provides functions for creating instances of the SHA-3
+// and SHAKE hash functions, as well as utility functions for hashing
+// bytes.
+
+import (
+	"hash"
+)
+
+// New224 creates a new SHA3-224 hash.
+// Its generic security strength is 224 bits against preimage attacks,
+// and 112 bits against collision attacks.
+func New224() hash.Hash { return &state{rate: 144, outputLen: 28, dsbyte: 0x06} }
+
+// New256 creates a new SHA3-256 hash.
+// Its generic security strength is 256 bits against preimage attacks,
+// and 128 bits against collision attacks.
+func New256() hash.Hash { return &state{rate: 136, outputLen: 32, dsbyte: 0x06} }
+
+// New384 creates a new SHA3-384 hash.
+// Its generic security strength is 384 bits against preimage attacks,
+// and 192 bits against collision attacks.
+func New384() hash.Hash { return &state{rate: 104, outputLen: 48, dsbyte: 0x06} }
+
+// New512 creates a new SHA3-512 hash.
+// Its generic security strength is 512 bits against preimage attacks,
+// and 256 bits against collision attacks.
+func New512() hash.Hash { return &state{rate: 72, outputLen: 64, dsbyte: 0x06} }
+
+// Sum224 returns the SHA3-224 digest of the data.
+func Sum224(data []byte) (digest [28]byte) {
+	h := New224()
+	h.Write(data)
+	h.Sum(digest[:0])
+	return
+}
+
+// Sum256 returns the SHA3-256 digest of the data.
+func Sum256(data []byte) (digest [32]byte) {
+	h := New256()
+	h.Write(data)
+	h.Sum(digest[:0])
+	return
+}
+
+// Sum384 returns the SHA3-384 digest of the data.
+func Sum384(data []byte) (digest [48]byte) {
+	h := New384()
+	h.Write(data)
+	h.Sum(digest[:0])
+	return
+}
+
+// Sum512 returns the SHA3-512 digest of the data.
+func Sum512(data []byte) (digest [64]byte) {
+	h := New512()
+	h.Write(data)
+	h.Sum(digest[:0])
+	return
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/keccakf.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/keccakf.go
new file mode 100644
index 00000000..13e7058f
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/keccakf.go
@@ -0,0 +1,410 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+// rc stores the round constants for use in the ι step.
+var rc = [24]uint64{
+	0x0000000000000001,
+	0x0000000000008082,
+	0x800000000000808A,
+	0x8000000080008000,
+	0x000000000000808B,
+	0x0000000080000001,
+	0x8000000080008081,
+	0x8000000000008009,
+	0x000000000000008A,
+	0x0000000000000088,
+	0x0000000080008009,
+	0x000000008000000A,
+	0x000000008000808B,
+	0x800000000000008B,
+	0x8000000000008089,
+	0x8000000000008003,
+	0x8000000000008002,
+	0x8000000000000080,
+	0x000000000000800A,
+	0x800000008000000A,
+	0x8000000080008081,
+	0x8000000000008080,
+	0x0000000080000001,
+	0x8000000080008008,
+}
+
+// keccakF1600 applies the Keccak permutation to a 1600b-wide
+// state represented as a slice of 25 uint64s.
+func keccakF1600(a *[25]uint64) {
+	// Implementation translated from Keccak-inplace.c
+	// in the keccak reference code.
+	var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64
+
+	for i := 0; i < 24; i += 4 {
+		// Combines the 5 steps in each round into 2 steps.
+		// Unrolls 4 rounds per loop and spreads some steps across rounds.
+
+		// Round 1
+		bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
+		bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
+		bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
+		bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
+		bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
+		d0 = bc4 ^ (bc1<<1 | bc1>>63)
+		d1 = bc0 ^ (bc2<<1 | bc2>>63)
+		d2 = bc1 ^ (bc3<<1 | bc3>>63)
+		d3 = bc2 ^ (bc4<<1 | bc4>>63)
+		d4 = bc3 ^ (bc0<<1 | bc0>>63)
+
+		bc0 = a[0] ^ d0
+		t = a[6] ^ d1
+		bc1 = t<<44 | t>>(64-44)
+		t = a[12] ^ d2
+		bc2 = t<<43 | t>>(64-43)
+		t = a[18] ^ d3
+		bc3 = t<<21 | t>>(64-21)
+		t = a[24] ^ d4
+		bc4 = t<<14 | t>>(64-14)
+		a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i]
+		a[6] = bc1 ^ (bc3 &^ bc2)
+		a[12] = bc2 ^ (bc4 &^ bc3)
+		a[18] = bc3 ^ (bc0 &^ bc4)
+		a[24] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[10] ^ d0
+		bc2 = t<<3 | t>>(64-3)
+		t = a[16] ^ d1
+		bc3 = t<<45 | t>>(64-45)
+		t = a[22] ^ d2
+		bc4 = t<<61 | t>>(64-61)
+		t = a[3] ^ d3
+		bc0 = t<<28 | t>>(64-28)
+		t = a[9] ^ d4
+		bc1 = t<<20 | t>>(64-20)
+		a[10] = bc0 ^ (bc2 &^ bc1)
+		a[16] = bc1 ^ (bc3 &^ bc2)
+		a[22] = bc2 ^ (bc4 &^ bc3)
+		a[3] = bc3 ^ (bc0 &^ bc4)
+		a[9] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[20] ^ d0
+		bc4 = t<<18 | t>>(64-18)
+		t = a[1] ^ d1
+		bc0 = t<<1 | t>>(64-1)
+		t = a[7] ^ d2
+		bc1 = t<<6 | t>>(64-6)
+		t = a[13] ^ d3
+		bc2 = t<<25 | t>>(64-25)
+		t = a[19] ^ d4
+		bc3 = t<<8 | t>>(64-8)
+		a[20] = bc0 ^ (bc2 &^ bc1)
+		a[1] = bc1 ^ (bc3 &^ bc2)
+		a[7] = bc2 ^ (bc4 &^ bc3)
+		a[13] = bc3 ^ (bc0 &^ bc4)
+		a[19] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[5] ^ d0
+		bc1 = t<<36 | t>>(64-36)
+		t = a[11] ^ d1
+		bc2 = t<<10 | t>>(64-10)
+		t = a[17] ^ d2
+		bc3 = t<<15 | t>>(64-15)
+		t = a[23] ^ d3
+		bc4 = t<<56 | t>>(64-56)
+		t = a[4] ^ d4
+		bc0 = t<<27 | t>>(64-27)
+		a[5] = bc0 ^ (bc2 &^ bc1)
+		a[11] = bc1 ^ (bc3 &^ bc2)
+		a[17] = bc2 ^ (bc4 &^ bc3)
+		a[23] = bc3 ^ (bc0 &^ bc4)
+		a[4] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[15] ^ d0
+		bc3 = t<<41 | t>>(64-41)
+		t = a[21] ^ d1
+		bc4 = t<<2 | t>>(64-2)
+		t = a[2] ^ d2
+		bc0 = t<<62 | t>>(64-62)
+		t = a[8] ^ d3
+		bc1 = t<<55 | t>>(64-55)
+		t = a[14] ^ d4
+		bc2 = t<<39 | t>>(64-39)
+		a[15] = bc0 ^ (bc2 &^ bc1)
+		a[21] = bc1 ^ (bc3 &^ bc2)
+		a[2] = bc2 ^ (bc4 &^ bc3)
+		a[8] = bc3 ^ (bc0 &^ bc4)
+		a[14] = bc4 ^ (bc1 &^ bc0)
+
+		// Round 2
+		bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
+		bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
+		bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
+		bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
+		bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
+		d0 = bc4 ^ (bc1<<1 | bc1>>63)
+		d1 = bc0 ^ (bc2<<1 | bc2>>63)
+		d2 = bc1 ^ (bc3<<1 | bc3>>63)
+		d3 = bc2 ^ (bc4<<1 | bc4>>63)
+		d4 = bc3 ^ (bc0<<1 | bc0>>63)
+
+		bc0 = a[0] ^ d0
+		t = a[16] ^ d1
+		bc1 = t<<44 | t>>(64-44)
+		t = a[7] ^ d2
+		bc2 = t<<43 | t>>(64-43)
+		t = a[23] ^ d3
+		bc3 = t<<21 | t>>(64-21)
+		t = a[14] ^ d4
+		bc4 = t<<14 | t>>(64-14)
+		a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1]
+		a[16] = bc1 ^ (bc3 &^ bc2)
+		a[7] = bc2 ^ (bc4 &^ bc3)
+		a[23] = bc3 ^ (bc0 &^ bc4)
+		a[14] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[20] ^ d0
+		bc2 = t<<3 | t>>(64-3)
+		t = a[11] ^ d1
+		bc3 = t<<45 | t>>(64-45)
+		t = a[2] ^ d2
+		bc4 = t<<61 | t>>(64-61)
+		t = a[18] ^ d3
+		bc0 = t<<28 | t>>(64-28)
+		t = a[9] ^ d4
+		bc1 = t<<20 | t>>(64-20)
+		a[20] = bc0 ^ (bc2 &^ bc1)
+		a[11] = bc1 ^ (bc3 &^ bc2)
+		a[2] = bc2 ^ (bc4 &^ bc3)
+		a[18] = bc3 ^ (bc0 &^ bc4)
+		a[9] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[15] ^ d0
+		bc4 = t<<18 | t>>(64-18)
+		t = a[6] ^ d1
+		bc0 = t<<1 | t>>(64-1)
+		t = a[22] ^ d2
+		bc1 = t<<6 | t>>(64-6)
+		t = a[13] ^ d3
+		bc2 = t<<25 | t>>(64-25)
+		t = a[4] ^ d4
+		bc3 = t<<8 | t>>(64-8)
+		a[15] = bc0 ^ (bc2 &^ bc1)
+		a[6] = bc1 ^ (bc3 &^ bc2)
+		a[22] = bc2 ^ (bc4 &^ bc3)
+		a[13] = bc3 ^ (bc0 &^ bc4)
+		a[4] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[10] ^ d0
+		bc1 = t<<36 | t>>(64-36)
+		t = a[1] ^ d1
+		bc2 = t<<10 | t>>(64-10)
+		t = a[17] ^ d2
+		bc3 = t<<15 | t>>(64-15)
+		t = a[8] ^ d3
+		bc4 = t<<56 | t>>(64-56)
+		t = a[24] ^ d4
+		bc0 = t<<27 | t>>(64-27)
+		a[10] = bc0 ^ (bc2 &^ bc1)
+		a[1] = bc1 ^ (bc3 &^ bc2)
+		a[17] = bc2 ^ (bc4 &^ bc3)
+		a[8] = bc3 ^ (bc0 &^ bc4)
+		a[24] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[5] ^ d0
+		bc3 = t<<41 | t>>(64-41)
+		t = a[21] ^ d1
+		bc4 = t<<2 | t>>(64-2)
+		t = a[12] ^ d2
+		bc0 = t<<62 | t>>(64-62)
+		t = a[3] ^ d3
+		bc1 = t<<55 | t>>(64-55)
+		t = a[19] ^ d4
+		bc2 = t<<39 | t>>(64-39)
+		a[5] = bc0 ^ (bc2 &^ bc1)
+		a[21] = bc1 ^ (bc3 &^ bc2)
+		a[12] = bc2 ^ (bc4 &^ bc3)
+		a[3] = bc3 ^ (bc0 &^ bc4)
+		a[19] = bc4 ^ (bc1 &^ bc0)
+
+		// Round 3
+		bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
+		bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
+		bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
+		bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
+		bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
+		d0 = bc4 ^ (bc1<<1 | bc1>>63)
+		d1 = bc0 ^ (bc2<<1 | bc2>>63)
+		d2 = bc1 ^ (bc3<<1 | bc3>>63)
+		d3 = bc2 ^ (bc4<<1 | bc4>>63)
+		d4 = bc3 ^ (bc0<<1 | bc0>>63)
+
+		bc0 = a[0] ^ d0
+		t = a[11] ^ d1
+		bc1 = t<<44 | t>>(64-44)
+		t = a[22] ^ d2
+		bc2 = t<<43 | t>>(64-43)
+		t = a[8] ^ d3
+		bc3 = t<<21 | t>>(64-21)
+		t = a[19] ^ d4
+		bc4 = t<<14 | t>>(64-14)
+		a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2]
+		a[11] = bc1 ^ (bc3 &^ bc2)
+		a[22] = bc2 ^ (bc4 &^ bc3)
+		a[8] = bc3 ^ (bc0 &^ bc4)
+		a[19] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[15] ^ d0
+		bc2 = t<<3 | t>>(64-3)
+		t = a[1] ^ d1
+		bc3 = t<<45 | t>>(64-45)
+		t = a[12] ^ d2
+		bc4 = t<<61 | t>>(64-61)
+		t = a[23] ^ d3
+		bc0 = t<<28 | t>>(64-28)
+		t = a[9] ^ d4
+		bc1 = t<<20 | t>>(64-20)
+		a[15] = bc0 ^ (bc2 &^ bc1)
+		a[1] = bc1 ^ (bc3 &^ bc2)
+		a[12] = bc2 ^ (bc4 &^ bc3)
+		a[23] = bc3 ^ (bc0 &^ bc4)
+		a[9] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[5] ^ d0
+		bc4 = t<<18 | t>>(64-18)
+		t = a[16] ^ d1
+		bc0 = t<<1 | t>>(64-1)
+		t = a[2] ^ d2
+		bc1 = t<<6 | t>>(64-6)
+		t = a[13] ^ d3
+		bc2 = t<<25 | t>>(64-25)
+		t = a[24] ^ d4
+		bc3 = t<<8 | t>>(64-8)
+		a[5] = bc0 ^ (bc2 &^ bc1)
+		a[16] = bc1 ^ (bc3 &^ bc2)
+		a[2] = bc2 ^ (bc4 &^ bc3)
+		a[13] = bc3 ^ (bc0 &^ bc4)
+		a[24] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[20] ^ d0
+		bc1 = t<<36 | t>>(64-36)
+		t = a[6] ^ d1
+		bc2 = t<<10 | t>>(64-10)
+		t = a[17] ^ d2
+		bc3 = t<<15 | t>>(64-15)
+		t = a[3] ^ d3
+		bc4 = t<<56 | t>>(64-56)
+		t = a[14] ^ d4
+		bc0 = t<<27 | t>>(64-27)
+		a[20] = bc0 ^ (bc2 &^ bc1)
+		a[6] = bc1 ^ (bc3 &^ bc2)
+		a[17] = bc2 ^ (bc4 &^ bc3)
+		a[3] = bc3 ^ (bc0 &^ bc4)
+		a[14] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[10] ^ d0
+		bc3 = t<<41 | t>>(64-41)
+		t = a[21] ^ d1
+		bc4 = t<<2 | t>>(64-2)
+		t = a[7] ^ d2
+		bc0 = t<<62 | t>>(64-62)
+		t = a[18] ^ d3
+		bc1 = t<<55 | t>>(64-55)
+		t = a[4] ^ d4
+		bc2 = t<<39 | t>>(64-39)
+		a[10] = bc0 ^ (bc2 &^ bc1)
+		a[21] = bc1 ^ (bc3 &^ bc2)
+		a[7] = bc2 ^ (bc4 &^ bc3)
+		a[18] = bc3 ^ (bc0 &^ bc4)
+		a[4] = bc4 ^ (bc1 &^ bc0)
+
+		// Round 4
+		bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
+		bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
+		bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
+		bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
+		bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
+		d0 = bc4 ^ (bc1<<1 | bc1>>63)
+		d1 = bc0 ^ (bc2<<1 | bc2>>63)
+		d2 = bc1 ^ (bc3<<1 | bc3>>63)
+		d3 = bc2 ^ (bc4<<1 | bc4>>63)
+		d4 = bc3 ^ (bc0<<1 | bc0>>63)
+
+		bc0 = a[0] ^ d0
+		t = a[1] ^ d1
+		bc1 = t<<44 | t>>(64-44)
+		t = a[2] ^ d2
+		bc2 = t<<43 | t>>(64-43)
+		t = a[3] ^ d3
+		bc3 = t<<21 | t>>(64-21)
+		t = a[4] ^ d4
+		bc4 = t<<14 | t>>(64-14)
+		a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3]
+		a[1] = bc1 ^ (bc3 &^ bc2)
+		a[2] = bc2 ^ (bc4 &^ bc3)
+		a[3] = bc3 ^ (bc0 &^ bc4)
+		a[4] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[5] ^ d0
+		bc2 = t<<3 | t>>(64-3)
+		t = a[6] ^ d1
+		bc3 = t<<45 | t>>(64-45)
+		t = a[7] ^ d2
+		bc4 = t<<61 | t>>(64-61)
+		t = a[8] ^ d3
+		bc0 = t<<28 | t>>(64-28)
+		t = a[9] ^ d4
+		bc1 = t<<20 | t>>(64-20)
+		a[5] = bc0 ^ (bc2 &^ bc1)
+		a[6] = bc1 ^ (bc3 &^ bc2)
+		a[7] = bc2 ^ (bc4 &^ bc3)
+		a[8] = bc3 ^ (bc0 &^ bc4)
+		a[9] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[10] ^ d0
+		bc4 = t<<18 | t>>(64-18)
+		t = a[11] ^ d1
+		bc0 = t<<1 | t>>(64-1)
+		t = a[12] ^ d2
+		bc1 = t<<6 | t>>(64-6)
+		t = a[13] ^ d3
+		bc2 = t<<25 | t>>(64-25)
+		t = a[14] ^ d4
+		bc3 = t<<8 | t>>(64-8)
+		a[10] = bc0 ^ (bc2 &^ bc1)
+		a[11] = bc1 ^ (bc3 &^ bc2)
+		a[12] = bc2 ^ (bc4 &^ bc3)
+		a[13] = bc3 ^ (bc0 &^ bc4)
+		a[14] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[15] ^ d0
+		bc1 = t<<36 | t>>(64-36)
+		t = a[16] ^ d1
+		bc2 = t<<10 | t>>(64-10)
+		t = a[17] ^ d2
+		bc3 = t<<15 | t>>(64-15)
+		t = a[18] ^ d3
+		bc4 = t<<56 | t>>(64-56)
+		t = a[19] ^ d4
+		bc0 = t<<27 | t>>(64-27)
+		a[15] = bc0 ^ (bc2 &^ bc1)
+		a[16] = bc1 ^ (bc3 &^ bc2)
+		a[17] = bc2 ^ (bc4 &^ bc3)
+		a[18] = bc3 ^ (bc0 &^ bc4)
+		a[19] = bc4 ^ (bc1 &^ bc0)
+
+		t = a[20] ^ d0
+		bc3 = t<<41 | t>>(64-41)
+		t = a[21] ^ d1
+		bc4 = t<<2 | t>>(64-2)
+		t = a[22] ^ d2
+		bc0 = t<<62 | t>>(64-62)
+		t = a[23] ^ d3
+		bc1 = t<<55 | t>>(64-55)
+		t = a[24] ^ d4
+		bc2 = t<<39 | t>>(64-39)
+		a[20] = bc0 ^ (bc2 &^ bc1)
+		a[21] = bc1 ^ (bc3 &^ bc2)
+		a[22] = bc2 ^ (bc4 &^ bc3)
+		a[23] = bc3 ^ (bc0 &^ bc4)
+		a[24] = bc4 ^ (bc1 &^ bc0)
+	}
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/register.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/register.go
new file mode 100644
index 00000000..3cf6a22e
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/register.go
@@ -0,0 +1,18 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.4
+
+package sha3
+
+import (
+	"crypto"
+)
+
+func init() {
+	crypto.RegisterHash(crypto.SHA3_224, New224)
+	crypto.RegisterHash(crypto.SHA3_256, New256)
+	crypto.RegisterHash(crypto.SHA3_384, New384)
+	crypto.RegisterHash(crypto.SHA3_512, New512)
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/sha3.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/sha3.go
new file mode 100644
index 00000000..c8fd31cb
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/sha3.go
@@ -0,0 +1,193 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+// spongeDirection indicates the direction bytes are flowing through the sponge.
+type spongeDirection int
+
+const (
+	// spongeAbsorbing indicates that the sponge is absorbing input.
+	spongeAbsorbing spongeDirection = iota
+	// spongeSqueezing indicates that the sponge is being squeezed.
+	spongeSqueezing
+)
+
+const (
+	// maxRate is the maximum size of the internal buffer. SHAKE-256
+	// currently needs the largest buffer.
+	maxRate = 168
+)
+
+type state struct {
+	// Generic sponge components.
+	a    [25]uint64 // main state of the hash
+	buf  []byte     // points into storage
+	rate int        // the number of bytes of state to use
+
+	// dsbyte contains the "domain separation" bits and the first bit of
+	// the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the
+	// SHA-3 and SHAKE functions by appending bitstrings to the message.
+	// Using a little-endian bit-ordering convention, these are "01" for SHA-3
+	// and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the
+	// padding rule from section 5.1 is applied to pad the message to a multiple
+	// of the rate, which involves adding a "1" bit, zero or more "0" bits, and
+	// a final "1" bit. We merge the first "1" bit from the padding into dsbyte,
+	// giving 00000110b (0x06) and 00011111b (0x1f).
+	// [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf
+	//     "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and
+	//      Extendable-Output Functions (May 2014)"
+	dsbyte  byte
+	storage [maxRate]byte
+
+	// Specific to SHA-3 and SHAKE.
+	fixedOutput bool            // whether this is a fixed-ouput-length instance
+	outputLen   int             // the default output size in bytes
+	state       spongeDirection // whether the sponge is absorbing or squeezing
+}
+
+// BlockSize returns the rate of sponge underlying this hash function.
+func (d *state) BlockSize() int { return d.rate }
+
+// Size returns the output size of the hash function in bytes.
+func (d *state) Size() int { return d.outputLen }
+
+// Reset clears the internal state by zeroing the sponge state and
+// the byte buffer, and setting Sponge.state to absorbing.
+func (d *state) Reset() {
+	// Zero the permutation's state.
+	for i := range d.a {
+		d.a[i] = 0
+	}
+	d.state = spongeAbsorbing
+	d.buf = d.storage[:0]
+}
+
+func (d *state) clone() *state {
+	ret := *d
+	if ret.state == spongeAbsorbing {
+		ret.buf = ret.storage[:len(ret.buf)]
+	} else {
+		ret.buf = ret.storage[d.rate-cap(d.buf) : d.rate]
+	}
+
+	return &ret
+}
+
+// permute applies the KeccakF-1600 permutation. It handles
+// any input-output buffering.
+func (d *state) permute() {
+	switch d.state {
+	case spongeAbsorbing:
+		// If we're absorbing, we need to xor the input into the state
+		// before applying the permutation.
+		xorIn(d, d.buf)
+		d.buf = d.storage[:0]
+		keccakF1600(&d.a)
+	case spongeSqueezing:
+		// If we're squeezing, we need to apply the permutatin before
+		// copying more output.
+		keccakF1600(&d.a)
+		d.buf = d.storage[:d.rate]
+		copyOut(d, d.buf)
+	}
+}
+
+// pads appends the domain separation bits in dsbyte, applies
+// the multi-bitrate 10..1 padding rule, and permutes the state.
+func (d *state) padAndPermute(dsbyte byte) {
+	if d.buf == nil {
+		d.buf = d.storage[:0]
+	}
+	// Pad with this instance's domain-separator bits. We know that there's
+	// at least one byte of space in d.buf because, if it were full,
+	// permute would have been called to empty it. dsbyte also contains the
+	// first one bit for the padding. See the comment in the state struct.
+	d.buf = append(d.buf, dsbyte)
+	zerosStart := len(d.buf)
+	d.buf = d.storage[:d.rate]
+	for i := zerosStart; i < d.rate; i++ {
+		d.buf[i] = 0
+	}
+	// This adds the final one bit for the padding. Because of the way that
+	// bits are numbered from the LSB upwards, the final bit is the MSB of
+	// the last byte.
+	d.buf[d.rate-1] ^= 0x80
+	// Apply the permutation
+	d.permute()
+	d.state = spongeSqueezing
+	d.buf = d.storage[:d.rate]
+	copyOut(d, d.buf)
+}
+
+// Write absorbs more data into the hash's state. It produces an error
+// if more data is written to the ShakeHash after writing
+func (d *state) Write(p []byte) (written int, err error) {
+	if d.state != spongeAbsorbing {
+		panic("sha3: write to sponge after read")
+	}
+	if d.buf == nil {
+		d.buf = d.storage[:0]
+	}
+	written = len(p)
+
+	for len(p) > 0 {
+		if len(d.buf) == 0 && len(p) >= d.rate {
+			// The fast path; absorb a full "rate" bytes of input and apply the permutation.
+			xorIn(d, p[:d.rate])
+			p = p[d.rate:]
+			keccakF1600(&d.a)
+		} else {
+			// The slow path; buffer the input until we can fill the sponge, and then xor it in.
+			todo := d.rate - len(d.buf)
+			if todo > len(p) {
+				todo = len(p)
+			}
+			d.buf = append(d.buf, p[:todo]...)
+			p = p[todo:]
+
+			// If the sponge is full, apply the permutation.
+			if len(d.buf) == d.rate {
+				d.permute()
+			}
+		}
+	}
+
+	return
+}
+
+// Read squeezes an arbitrary number of bytes from the sponge.
+func (d *state) Read(out []byte) (n int, err error) {
+	// If we're still absorbing, pad and apply the permutation.
+	if d.state == spongeAbsorbing {
+		d.padAndPermute(d.dsbyte)
+	}
+
+	n = len(out)
+
+	// Now, do the squeezing.
+	for len(out) > 0 {
+		n := copy(out, d.buf)
+		d.buf = d.buf[n:]
+		out = out[n:]
+
+		// Apply the permutation if we've squeezed the sponge dry.
+		if len(d.buf) == 0 {
+			d.permute()
+		}
+	}
+
+	return
+}
+
+// Sum applies padding to the hash state and then squeezes out the desired
+// number of output bytes.
+func (d *state) Sum(in []byte) []byte {
+	// Make a copy of the original hash so that caller can keep writing
+	// and summing.
+	dup := d.clone()
+	hash := make([]byte, dup.outputLen)
+	dup.Read(hash)
+	return append(in, hash...)
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/sha3_test.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/sha3_test.go
new file mode 100644
index 00000000..caf72f27
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/sha3_test.go
@@ -0,0 +1,306 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+// Tests include all the ShortMsgKATs provided by the Keccak team at
+// https://github.com/gvanas/KeccakCodePackage
+//
+// They only include the zero-bit case of the bitwise testvectors
+// published by NIST in the draft of FIPS-202.
+
+import (
+	"bytes"
+	"compress/flate"
+	"encoding/hex"
+	"encoding/json"
+	"hash"
+	"os"
+	"strings"
+	"testing"
+)
+
+const (
+	testString  = "brekeccakkeccak koax koax"
+	katFilename = "testdata/keccakKats.json.deflate"
+)
+
+// Internal-use instances of SHAKE used to test against KATs.
+func newHashShake128() hash.Hash {
+	return &state{rate: 168, dsbyte: 0x1f, outputLen: 512}
+}
+func newHashShake256() hash.Hash {
+	return &state{rate: 136, dsbyte: 0x1f, outputLen: 512}
+}
+
+// testDigests contains functions returning hash.Hash instances
+// with output-length equal to the KAT length for both SHA-3 and
+// SHAKE instances.
+var testDigests = map[string]func() hash.Hash{
+	"SHA3-224": New224,
+	"SHA3-256": New256,
+	"SHA3-384": New384,
+	"SHA3-512": New512,
+	"SHAKE128": newHashShake128,
+	"SHAKE256": newHashShake256,
+}
+
+// testShakes contains functions that return ShakeHash instances for
+// testing the ShakeHash-specific interface.
+var testShakes = map[string]func() ShakeHash{
+	"SHAKE128": NewShake128,
+	"SHAKE256": NewShake256,
+}
+
+// decodeHex converts a hex-encoded string into a raw byte string.
+func decodeHex(s string) []byte {
+	b, err := hex.DecodeString(s)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+// structs used to marshal JSON test-cases.
+type KeccakKats struct {
+	Kats map[string][]struct {
+		Digest  string `json:"digest"`
+		Length  int64  `json:"length"`
+		Message string `json:"message"`
+	}
+}
+
+func testUnalignedAndGeneric(t *testing.T, testf func(impl string)) {
+	xorInOrig, copyOutOrig := xorIn, copyOut
+	xorIn, copyOut = xorInGeneric, copyOutGeneric
+	testf("generic")
+	if xorImplementationUnaligned != "generic" {
+		xorIn, copyOut = xorInUnaligned, copyOutUnaligned
+		testf("unaligned")
+	}
+	xorIn, copyOut = xorInOrig, copyOutOrig
+}
+
+// TestKeccakKats tests the SHA-3 and Shake implementations against all the
+// ShortMsgKATs from https://github.com/gvanas/KeccakCodePackage
+// (The testvectors are stored in keccakKats.json.deflate due to their length.)
+func TestKeccakKats(t *testing.T) {
+	testUnalignedAndGeneric(t, func(impl string) {
+		// Read the KATs.
+		deflated, err := os.Open(katFilename)
+		if err != nil {
+			t.Errorf("error opening %s: %s", katFilename, err)
+		}
+		file := flate.NewReader(deflated)
+		dec := json.NewDecoder(file)
+		var katSet KeccakKats
+		err = dec.Decode(&katSet)
+		if err != nil {
+			t.Errorf("error decoding KATs: %s", err)
+		}
+
+		// Do the KATs.
+		for functionName, kats := range katSet.Kats {
+			d := testDigests[functionName]()
+			for _, kat := range kats {
+				d.Reset()
+				in, err := hex.DecodeString(kat.Message)
+				if err != nil {
+					t.Errorf("error decoding KAT: %s", err)
+				}
+				d.Write(in[:kat.Length/8])
+				got := strings.ToUpper(hex.EncodeToString(d.Sum(nil)))
+				if got != kat.Digest {
+					t.Errorf("function=%s, implementation=%s, length=%d\nmessage:\n  %s\ngot:\n  %s\nwanted:\n %s",
+						functionName, impl, kat.Length, kat.Message, got, kat.Digest)
+					t.Logf("wanted %+v", kat)
+					t.FailNow()
+				}
+				continue
+			}
+		}
+	})
+}
+
+// TestUnalignedWrite tests that writing data in an arbitrary pattern with
+// small input buffers.
+func testUnalignedWrite(t *testing.T) {
+	testUnalignedAndGeneric(t, func(impl string) {
+		buf := sequentialBytes(0x10000)
+		for alg, df := range testDigests {
+			d := df()
+			d.Reset()
+			d.Write(buf)
+			want := d.Sum(nil)
+			d.Reset()
+			for i := 0; i < len(buf); {
+				// Cycle through offsets which make a 137 byte sequence.
+				// Because 137 is prime this sequence should exercise all corner cases.
+				offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
+				for _, j := range offsets {
+					if v := len(buf) - i; v < j {
+						j = v
+					}
+					d.Write(buf[i : i+j])
+					i += j
+				}
+			}
+			got := d.Sum(nil)
+			if !bytes.Equal(got, want) {
+				t.Errorf("Unaligned writes, implementation=%s, alg=%s\ngot %q, want %q", impl, alg, got, want)
+			}
+		}
+	})
+}
+
+// TestAppend checks that appending works when reallocation is necessary.
+func TestAppend(t *testing.T) {
+	testUnalignedAndGeneric(t, func(impl string) {
+		d := New224()
+
+		for capacity := 2; capacity <= 66; capacity += 64 {
+			// The first time around the loop, Sum will have to reallocate.
+			// The second time, it will not.
+			buf := make([]byte, 2, capacity)
+			d.Reset()
+			d.Write([]byte{0xcc})
+			buf = d.Sum(buf)
+			expected := "0000DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39"
+			if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected {
+				t.Errorf("got %s, want %s", got, expected)
+			}
+		}
+	})
+}
+
+// TestAppendNoRealloc tests that appending works when no reallocation is necessary.
+func TestAppendNoRealloc(t *testing.T) {
+	testUnalignedAndGeneric(t, func(impl string) {
+		buf := make([]byte, 1, 200)
+		d := New224()
+		d.Write([]byte{0xcc})
+		buf = d.Sum(buf)
+		expected := "00DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39"
+		if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected {
+			t.Errorf("%s: got %s, want %s", impl, got, expected)
+		}
+	})
+}
+
+// TestSqueezing checks that squeezing the full output a single time produces
+// the same output as repeatedly squeezing the instance.
+func TestSqueezing(t *testing.T) {
+	testUnalignedAndGeneric(t, func(impl string) {
+		for functionName, newShakeHash := range testShakes {
+			d0 := newShakeHash()
+			d0.Write([]byte(testString))
+			ref := make([]byte, 32)
+			d0.Read(ref)
+
+			d1 := newShakeHash()
+			d1.Write([]byte(testString))
+			var multiple []byte
+			for _ = range ref {
+				one := make([]byte, 1)
+				d1.Read(one)
+				multiple = append(multiple, one...)
+			}
+			if !bytes.Equal(ref, multiple) {
+				t.Errorf("%s (%s): squeezing %d bytes one at a time failed", functionName, impl, len(ref))
+			}
+		}
+	})
+}
+
+// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing.
+func sequentialBytes(size int) []byte {
+	result := make([]byte, size)
+	for i := range result {
+		result[i] = byte(i)
+	}
+	return result
+}
+
+// BenchmarkPermutationFunction measures the speed of the permutation function
+// with no input data.
+func BenchmarkPermutationFunction(b *testing.B) {
+	b.SetBytes(int64(200))
+	var lanes [25]uint64
+	for i := 0; i < b.N; i++ {
+		keccakF1600(&lanes)
+	}
+}
+
+// benchmarkHash tests the speed to hash num buffers of buflen each.
+func benchmarkHash(b *testing.B, h hash.Hash, size, num int) {
+	b.StopTimer()
+	h.Reset()
+	data := sequentialBytes(size)
+	b.SetBytes(int64(size * num))
+	b.StartTimer()
+
+	var state []byte
+	for i := 0; i < b.N; i++ {
+		for j := 0; j < num; j++ {
+			h.Write(data)
+		}
+		state = h.Sum(state[:0])
+	}
+	b.StopTimer()
+	h.Reset()
+}
+
+// benchmarkShake is specialized to the Shake instances, which don't
+// require a copy on reading output.
+func benchmarkShake(b *testing.B, h ShakeHash, size, num int) {
+	b.StopTimer()
+	h.Reset()
+	data := sequentialBytes(size)
+	d := make([]byte, 32)
+
+	b.SetBytes(int64(size * num))
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		h.Reset()
+		for j := 0; j < num; j++ {
+			h.Write(data)
+		}
+		h.Read(d)
+	}
+}
+
+func BenchmarkSha3_512_MTU(b *testing.B) { benchmarkHash(b, New512(), 1350, 1) }
+func BenchmarkSha3_384_MTU(b *testing.B) { benchmarkHash(b, New384(), 1350, 1) }
+func BenchmarkSha3_256_MTU(b *testing.B) { benchmarkHash(b, New256(), 1350, 1) }
+func BenchmarkSha3_224_MTU(b *testing.B) { benchmarkHash(b, New224(), 1350, 1) }
+
+func BenchmarkShake128_MTU(b *testing.B)  { benchmarkShake(b, NewShake128(), 1350, 1) }
+func BenchmarkShake256_MTU(b *testing.B)  { benchmarkShake(b, NewShake256(), 1350, 1) }
+func BenchmarkShake256_16x(b *testing.B)  { benchmarkShake(b, NewShake256(), 16, 1024) }
+func BenchmarkShake256_1MiB(b *testing.B) { benchmarkShake(b, NewShake256(), 1024, 1024) }
+
+func BenchmarkSha3_512_1MiB(b *testing.B) { benchmarkHash(b, New512(), 1024, 1024) }
+
+func Example_sum() {
+	buf := []byte("some data to hash")
+	// A hash needs to be 64 bytes long to have 256-bit collision resistance.
+	h := make([]byte, 64)
+	// Compute a 64-byte hash of buf and put it in h.
+	ShakeSum256(h, buf)
+}
+
+func Example_mac() {
+	k := []byte("this is a secret key; you should generate a strong random key that's at least 32 bytes long")
+	buf := []byte("and this is some data to authenticate")
+	// A MAC with 32 bytes of output has 256-bit security strength -- if you use at least a 32-byte-long key.
+	h := make([]byte, 32)
+	d := NewShake256()
+	// Write the key into the hash.
+	d.Write(k)
+	// Now write the data.
+	d.Write(buf)
+	// Read 32 bytes of output from the hash into h.
+	d.Read(h)
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/shake.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/shake.go
new file mode 100644
index 00000000..841f9860
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/shake.go
@@ -0,0 +1,60 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+// This file defines the ShakeHash interface, and provides
+// functions for creating SHAKE instances, as well as utility
+// functions for hashing bytes to arbitrary-length output.
+
+import (
+	"io"
+)
+
+// ShakeHash defines the interface to hash functions that
+// support arbitrary-length output.
+type ShakeHash interface {
+	// Write absorbs more data into the hash's state. It panics if input is
+	// written to it after output has been read from it.
+	io.Writer
+
+	// Read reads more output from the hash; reading affects the hash's
+	// state. (ShakeHash.Read is thus very different from Hash.Sum)
+	// It never returns an error.
+	io.Reader
+
+	// Clone returns a copy of the ShakeHash in its current state.
+	Clone() ShakeHash
+
+	// Reset resets the ShakeHash to its initial state.
+	Reset()
+}
+
+func (d *state) Clone() ShakeHash {
+	return d.clone()
+}
+
+// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash.
+// Its generic security strength is 128 bits against all attacks if at
+// least 32 bytes of its output are used.
+func NewShake128() ShakeHash { return &state{rate: 168, dsbyte: 0x1f} }
+
+// NewShake256 creates a new SHAKE128 variable-output-length ShakeHash.
+// Its generic security strength is 256 bits against all attacks if
+// at least 64 bytes of its output are used.
+func NewShake256() ShakeHash { return &state{rate: 136, dsbyte: 0x1f} }
+
+// ShakeSum128 writes an arbitrary-length digest of data into hash.
+func ShakeSum128(hash, data []byte) {
+	h := NewShake128()
+	h.Write(data)
+	h.Read(hash)
+}
+
+// ShakeSum256 writes an arbitrary-length digest of data into hash.
+func ShakeSum256(hash, data []byte) {
+	h := NewShake256()
+	h.Write(data)
+	h.Read(hash)
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/testdata/keccakKats.json.deflate b/Godeps/_workspace/src/golang.org/x/crypto/sha3/testdata/keccakKats.json.deflate
new file mode 100644
index 00000000..62e85ae2
Binary files /dev/null and b/Godeps/_workspace/src/golang.org/x/crypto/sha3/testdata/keccakKats.json.deflate differ
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/xor.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/xor.go
new file mode 100644
index 00000000..d622979c
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/xor.go
@@ -0,0 +1,16 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !amd64,!386 appengine
+
+package sha3
+
+var (
+	xorIn            = xorInGeneric
+	copyOut          = copyOutGeneric
+	xorInUnaligned   = xorInGeneric
+	copyOutUnaligned = copyOutGeneric
+)
+
+const xorImplementationUnaligned = "generic"
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/xor_generic.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/xor_generic.go
new file mode 100644
index 00000000..fd35f02e
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/xor_generic.go
@@ -0,0 +1,28 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+import "encoding/binary"
+
+// xorInGeneric xors the bytes in buf into the state; it
+// makes no non-portable assumptions about memory layout
+// or alignment.
+func xorInGeneric(d *state, buf []byte) {
+	n := len(buf) / 8
+
+	for i := 0; i < n; i++ {
+		a := binary.LittleEndian.Uint64(buf)
+		d.a[i] ^= a
+		buf = buf[8:]
+	}
+}
+
+// copyOutGeneric copies ulint64s to a byte buffer.
+func copyOutGeneric(d *state, b []byte) {
+	for i := 0; len(b) >= 8; i++ {
+		binary.LittleEndian.PutUint64(b, d.a[i])
+		b = b[8:]
+	}
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/sha3/xor_unaligned.go b/Godeps/_workspace/src/golang.org/x/crypto/sha3/xor_unaligned.go
new file mode 100644
index 00000000..c7851a1d
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/sha3/xor_unaligned.go
@@ -0,0 +1,58 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build amd64 386
+// +build !appengine
+
+package sha3
+
+import "unsafe"
+
+func xorInUnaligned(d *state, buf []byte) {
+	bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))
+	n := len(buf)
+	if n >= 72 {
+		d.a[0] ^= bw[0]
+		d.a[1] ^= bw[1]
+		d.a[2] ^= bw[2]
+		d.a[3] ^= bw[3]
+		d.a[4] ^= bw[4]
+		d.a[5] ^= bw[5]
+		d.a[6] ^= bw[6]
+		d.a[7] ^= bw[7]
+		d.a[8] ^= bw[8]
+	}
+	if n >= 104 {
+		d.a[9] ^= bw[9]
+		d.a[10] ^= bw[10]
+		d.a[11] ^= bw[11]
+		d.a[12] ^= bw[12]
+	}
+	if n >= 136 {
+		d.a[13] ^= bw[13]
+		d.a[14] ^= bw[14]
+		d.a[15] ^= bw[15]
+		d.a[16] ^= bw[16]
+	}
+	if n >= 144 {
+		d.a[17] ^= bw[17]
+	}
+	if n >= 168 {
+		d.a[18] ^= bw[18]
+		d.a[19] ^= bw[19]
+		d.a[20] ^= bw[20]
+	}
+}
+
+func copyOutUnaligned(d *state, buf []byte) {
+	ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0]))
+	copy(buf, ab[:])
+}
+
+var (
+	xorIn   = xorInUnaligned
+	copyOut = copyOutUnaligned
+)
+
+const xorImplementationUnaligned = "unaligned"
diff --git a/README.md b/README.md
index 391de8c3..98dfdcfb 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
 * Searchable history
 * Persistent connections
 * Multiple users
+* Automatic HTTPS through Let's Encrypt
 
 ## Usage
 There is a few different ways of getting it:
diff --git a/assets/bindata.go b/assets/bindata.go
index ad4a09f8..77166e9e 100644
--- a/assets/bindata.go
+++ b/assets/bindata.go
@@ -69,7 +69,7 @@ func bundle_css_gz() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindata_file_info{name: "bundle.css.gz", size: 1706, mode: os.FileMode(436), modTime: time.Unix(1451510920, 0)}
+	info := bindata_file_info{name: "bundle.css.gz", size: 1706, mode: os.FileMode(436), modTime: time.Unix(1451931845, 0)}
 	a := &asset{bytes: bytes, info:  info}
 	return a, nil
 }
@@ -89,12 +89,12 @@ func bundle_js_gz() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindata_file_info{name: "bundle.js.gz", size: 114026, mode: os.FileMode(436), modTime: time.Unix(1451510920, 0)}
+	info := bindata_file_info{name: "bundle.js.gz", size: 114026, mode: os.FileMode(436), modTime: time.Unix(1451931845, 0)}
 	a := &asset{bytes: bytes, info:  info}
 	return a, nil
 }
 
-var _config_default_toml = "\x70\x6f\x72\x74\x20\x3d\x20\x31\x33\x33\x37\x0a\x23\x20\x53\x70\x65\x63\x69\x66\x79\x69\x6e\x67\x20\x61\x20\x64\x6f\x6d\x61\x69\x6e\x20\x65\x6e\x61\x62\x6c\x65\x73\x20\x61\x75\x74\x6f\x6d\x61\x74\x69\x63\x20\x53\x53\x4c\x20\x74\x68\x72\x6f\x75\x67\x68\x20\x4c\x65\x74\x27\x73\x20\x45\x6e\x63\x72\x79\x70\x74\x0a\x64\x6f\x6d\x61\x69\x6e\x20\x3d\x20\x22\x77\x77\x77\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x22\x0a\x0a\x5b\x68\x74\x74\x70\x73\x5d\x0a\x70\x6f\x72\x74\x20\x3d\x20\x34\x34\x33\x0a\x23\x20\x52\x65\x64\x69\x72\x65\x63\x74\x20\x61\x6c\x6c\x20\x68\x74\x74\x70\x20\x74\x72\x61\x66\x66\x69\x63\x20\x74\x6f\x20\x68\x74\x74\x70\x73\x0a\x72\x65\x64\x69\x72\x65\x63\x74\x20\x3d\x20\x74\x72\x75\x65\x0a"
+var _config_default_toml = "\x70\x6f\x72\x74\x20\x3d\x20\x38\x30\x0a\x0a\x5b\x68\x74\x74\x70\x73\x5d\x0a\x65\x6e\x61\x62\x6c\x65\x64\x20\x3d\x20\x66\x61\x6c\x73\x65\x0a\x70\x6f\x72\x74\x20\x3d\x20\x34\x34\x33\x0a\x23\x20\x52\x65\x64\x69\x72\x65\x63\x74\x20\x61\x6c\x6c\x20\x68\x74\x74\x70\x20\x74\x72\x61\x66\x66\x69\x63\x20\x74\x6f\x20\x68\x74\x74\x70\x73\x0a\x72\x65\x64\x69\x72\x65\x63\x74\x20\x3d\x20\x74\x72\x75\x65\x0a\x23\x20\x50\x61\x74\x68\x20\x74\x6f\x20\x79\x6f\x75\x72\x20\x63\x65\x72\x74\x20\x61\x6e\x64\x20\x70\x72\x69\x76\x61\x74\x65\x20\x6b\x65\x79\x20\x69\x66\x20\x79\x6f\x75\x20\x61\x72\x65\x20\x6e\x6f\x74\x20\x75\x73\x69\x6e\x67\x0a\x23\x20\x74\x68\x65\x20\x4c\x65\x74\x27\x73\x20\x45\x6e\x63\x72\x79\x70\x74\x20\x69\x6e\x74\x65\x67\x72\x61\x74\x69\x6f\x6e\x0a\x63\x65\x72\x74\x20\x3d\x20\x22\x22\x0a\x6b\x65\x79\x20\x3d\x20\x22\x22\x0a\x0a\x5b\x6c\x65\x74\x73\x65\x6e\x63\x72\x79\x70\x74\x5d\x0a\x23\x20\x59\x6f\x75\x72\x20\x64\x6f\x6d\x61\x69\x6e\x20\x6f\x72\x20\x73\x75\x62\x64\x6f\x6d\x61\x69\x6e\x0a\x64\x6f\x6d\x61\x69\x6e\x20\x3d\x20\x22\x22\x0a\x23\x20\x41\x6e\x20\x65\x6d\x61\x69\x6c\x20\x61\x64\x64\x72\x65\x73\x73\x20\x6c\x65\x74\x73\x20\x79\x6f\x75\x20\x72\x65\x63\x6f\x76\x65\x72\x20\x79\x6f\x75\x72\x20\x61\x63\x63\x6f\x75\x6e\x74\x73\x20\x70\x72\x69\x76\x61\x74\x65\x20\x6b\x65\x79\x0a\x65\x6d\x61\x69\x6c\x20\x3d\x20\x22\x22\x0a\x23\x20\x54\x68\x65\x20\x70\x6f\x72\x74\x20\x4c\x65\x74\x27\x73\x20\x45\x6e\x63\x72\x79\x70\x74\x20\x6c\x69\x73\x74\x65\x6e\x73\x20\x6f\x6e\x2c\x20\x63\x6f\x6d\x6d\x65\x6e\x74\x20\x74\x68\x69\x73\x20\x6f\x75\x74\x20\x74\x6f\x20\x6c\x65\x74\x20\x69\x74\x20\x62\x69\x6e\x64\x0a\x23\x20\x74\x6f\x20\x70\x6f\x72\x74\x20\x38\x30\x20\x61\x73\x20\x6e\x65\x65\x64\x65\x64\x2c\x20\x64\x6f\x69\x6e\x67\x20\x73\x6f\x20\x6d\x65\x61\x6e\x73\x20\x64\x69\x73\x70\x61\x74\x63\x68\x20\x69\x74\x73\x65\x6c\x66\x20\x63\x61\x6e\x6e\x6f\x74\x20\x75\x73\x65\x20\x70\x6f\x72\x74\x20\x38\x30\x0a\x70\x6f\x72\x74\x20\x3d\x20\x35\x30\x30\x31\x0a\x23\x20\x48\x61\x76\x65\x20\x64\x69\x73\x70\x61\x74\x63\x68\x20\x70\x72\x6f\x78\x79\x20\x74\x72\x61\x66\x66\x69\x63\x20\x66\x72\x6f\x6d\x20\x70\x6f\x72\x74\x20\x38\x30\x20\x74\x6f\x20\x74\x68\x65\x20\x4c\x65\x74\x27\x73\x20\x45\x6e\x63\x72\x79\x70\x74\x20\x70\x6f\x72\x74\x0a\x70\x72\x6f\x78\x79\x20\x3d\x20\x74\x72\x75\x65\x0a\x0a\x23\x20\x4e\x6f\x74\x20\x69\x6d\x70\x6c\x65\x6d\x65\x6e\x74\x65\x64\x0a\x5b\x61\x75\x74\x68\x5d\x0a\x23\x20\x41\x6c\x6c\x6f\x77\x20\x75\x73\x61\x67\x65\x20\x77\x69\x74\x68\x6f\x75\x74\x20\x62\x65\x69\x6e\x67\x20\x6c\x6f\x67\x67\x65\x64\x20\x69\x6e\x2c\x20\x61\x6c\x6c\x20\x63\x68\x61\x6e\x6e\x65\x6c\x73\x20\x61\x6e\x64\x20\x73\x65\x74\x74\x69\x6e\x67\x73\x20\x67\x65\x74\x0a\x23\x20\x74\x72\x61\x6e\x73\x66\x65\x72\x72\x65\x64\x20\x77\x68\x65\x6e\x20\x6c\x6f\x67\x67\x69\x6e\x67\x20\x69\x6e\x20\x6f\x72\x20\x72\x65\x67\x69\x73\x74\x65\x72\x69\x6e\x67\x0a\x61\x6e\x6f\x6e\x79\x6d\x6f\x75\x73\x20\x3d\x20\x74\x72\x75\x65\x0a\x23\x20\x45\x6e\x61\x62\x6c\x65\x20\x75\x73\x65\x72\x6e\x61\x6d\x65\x2f\x70\x61\x73\x73\x77\x6f\x72\x64\x20\x6c\x6f\x67\x69\x6e\x0a\x6c\x6f\x67\x69\x6e\x20\x3d\x20\x74\x72\x75\x65\x0a\x23\x20\x45\x6e\x61\x62\x6c\x65\x20\x75\x73\x65\x72\x6e\x61\x6d\x65\x2f\x70\x61\x73\x73\x77\x6f\x72\x64\x20\x72\x65\x67\x69\x73\x74\x72\x61\x74\x69\x6f\x6e\x0a\x72\x65\x67\x69\x73\x74\x72\x61\x74\x69\x6f\x6e\x20\x3d\x20\x74\x72\x75\x65\x0a\x0a\x5b\x61\x75\x74\x68\x2e\x67\x69\x74\x68\x75\x62\x5d\x0a\x6b\x65\x79\x20\x3d\x20\x22\x22\x0a\x73\x65\x63\x72\x65\x74\x20\x3d\x20\x22\x22\x0a\x0a\x5b\x61\x75\x74\x68\x2e\x66\x61\x63\x65\x62\x6f\x6f\x6b\x5d\x0a\x6b\x65\x79\x20\x3d\x20\x22\x22\x0a\x73\x65\x63\x72\x65\x74\x20\x3d\x20\x22\x22\x0a\x0a\x5b\x61\x75\x74\x68\x2e\x67\x6f\x6f\x67\x6c\x65\x5d\x0a\x6b\x65\x79\x20\x3d\x20\x22\x22\x0a\x73\x65\x63\x72\x65\x74\x20\x3d\x20\x22\x22\x0a\x0a\x5b\x61\x75\x74\x68\x2e\x74\x77\x69\x74\x74\x65\x72\x5d\x0a\x6b\x65\x79\x20\x3d\x20\x22\x22\x0a\x73\x65\x63\x72\x65\x74\x20\x3d\x20\x22\x22\x0a"
 
 func config_default_toml_bytes() ([]byte, error) {
 	return bindata_read(
@@ -109,7 +109,7 @@ func config_default_toml() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindata_file_info{name: "config.default.toml", size: 178, mode: os.FileMode(436), modTime: time.Unix(1451510900, 0)}
+	info := bindata_file_info{name: "config.default.toml", size: 981, mode: os.FileMode(436), modTime: time.Unix(1451931816, 0)}
 	a := &asset{bytes: bytes, info:  info}
 	return a, nil
 }
@@ -129,7 +129,7 @@ func font_fontello_eot_gz() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindata_file_info{name: "font/fontello.eot.gz", size: 3543, mode: os.FileMode(436), modTime: time.Unix(1451510920, 0)}
+	info := bindata_file_info{name: "font/fontello.eot.gz", size: 3543, mode: os.FileMode(436), modTime: time.Unix(1451931845, 0)}
 	a := &asset{bytes: bytes, info:  info}
 	return a, nil
 }
@@ -149,7 +149,7 @@ func font_fontello_svg_gz() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindata_file_info{name: "font/fontello.svg.gz", size: 1707, mode: os.FileMode(436), modTime: time.Unix(1451510920, 0)}
+	info := bindata_file_info{name: "font/fontello.svg.gz", size: 1707, mode: os.FileMode(436), modTime: time.Unix(1451931845, 0)}
 	a := &asset{bytes: bytes, info:  info}
 	return a, nil
 }
@@ -169,7 +169,7 @@ func font_fontello_ttf_gz() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindata_file_info{name: "font/fontello.ttf.gz", size: 3490, mode: os.FileMode(436), modTime: time.Unix(1451510920, 0)}
+	info := bindata_file_info{name: "font/fontello.ttf.gz", size: 3490, mode: os.FileMode(436), modTime: time.Unix(1451931845, 0)}
 	a := &asset{bytes: bytes, info:  info}
 	return a, nil
 }
@@ -189,7 +189,7 @@ func font_fontello_woff_gz() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindata_file_info{name: "font/fontello.woff.gz", size: 3701, mode: os.FileMode(436), modTime: time.Unix(1451510920, 0)}
+	info := bindata_file_info{name: "font/fontello.woff.gz", size: 3701, mode: os.FileMode(436), modTime: time.Unix(1451931845, 0)}
 	a := &asset{bytes: bytes, info:  info}
 	return a, nil
 }
@@ -209,7 +209,7 @@ func index_html_gz() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindata_file_info{name: "index.html.gz", size: 252, mode: os.FileMode(436), modTime: time.Unix(1451510920, 0)}
+	info := bindata_file_info{name: "index.html.gz", size: 252, mode: os.FileMode(436), modTime: time.Unix(1451931845, 0)}
 	a := &asset{bytes: bytes, info:  info}
 	return a, nil
 }
diff --git a/commands/clear.go b/commands/clear.go
index facad355..04fbc254 100644
--- a/commands/clear.go
+++ b/commands/clear.go
@@ -10,6 +10,6 @@ var clearCmd = &cobra.Command{
 	Use:   "clear",
 	Short: "Clear all application data",
 	Run: func(cmd *cobra.Command, args []string) {
-		storage.Clear(appDir)
+		storage.Clear()
 	},
 }
diff --git a/commands/config.go b/commands/config.go
index c70a0cb7..6dc91e97 100644
--- a/commands/config.go
+++ b/commands/config.go
@@ -4,9 +4,10 @@ import (
 	"log"
 	"os"
 	"os/exec"
-	"path"
 
 	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
+
+	"github.com/khlieng/dispatch/storage"
 )
 
 var (
@@ -15,7 +16,7 @@ var (
 		Short: "Edit config file",
 		Run: func(cmd *cobra.Command, args []string) {
 			if editor := findEditor(); editor != "" {
-				process := exec.Command(editor, path.Join(appDir, "config.toml"))
+				process := exec.Command(editor, storage.Path.Config())
 				process.Stdin = os.Stdin
 				process.Stdout = os.Stdout
 				process.Stderr = os.Stderr
diff --git a/commands/dispatch.go b/commands/dispatch.go
index 21d646d3..8ad0883f 100644
--- a/commands/dispatch.go
+++ b/commands/dispatch.go
@@ -1,12 +1,11 @@
 package commands
 
 import (
+	"fmt"
 	"io/ioutil"
 	"log"
 	"os"
-	"path"
 
-	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/mitchellh/go-homedir"
 	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/viper"
 
@@ -15,49 +14,61 @@ import (
 	"github.com/khlieng/dispatch/storage"
 )
 
-var (
-	rootCmd = &cobra.Command{
-		Use:   "dispatch",
-		Short: "Web-based IRC client in Go.",
-		Run: func(cmd *cobra.Command, args []string) {
-			storage.Initialize(appDir)
-			server.Run(viper.GetInt("port"))
-		},
+const logo = `
+    ____   _                     _         _
+   |  _ \ (_) ___  _ __    __ _ | |_  ___ | |__
+   | | | || |/ __|| '_ \  / _  || __|/ __|| '_ \
+   | |_| || |\__ \| |_) || (_| || |_| (__ | | | |
+   |____/ |_||___/| .__/  \__,_| \__|\___||_| |_|
+                  |_|
+                       v0.1
+`
 
-		PersistentPreRun: func(cmd *cobra.Command, args []string) {
-			appDir = viper.GetString("dir")
+var rootCmd = &cobra.Command{
+	Use:   "dispatch",
+	Short: "Web-based IRC client in Go.",
+	Run: func(cmd *cobra.Command, args []string) {
+		storage.Initialize()
+		server.Run()
+	},
 
-			os.Mkdir(appDir, 0777)
-			os.Mkdir(path.Join(appDir, "logs"), 0777)
+	PersistentPreRun: func(cmd *cobra.Command, args []string) {
+		if cmd.Use == "dispatch" {
+			fmt.Println(logo)
+		}
 
-			initConfig()
+		storage.SetDirectory(viper.GetString("dir"))
 
-			viper.SetConfigName("config")
-			viper.AddConfigPath(appDir)
-			viper.ReadInConfig()
-		},
-	}
+		err := os.MkdirAll(storage.Path.Logs(), 0700)
+		if err != nil {
+			log.Fatal(err)
+		}
 
-	appDir string
-)
+		initConfig()
 
-func init() {
-	rootCmd.AddCommand(clearCmd)
-	rootCmd.AddCommand(configCmd)
-
-	rootCmd.Flags().IntP("port", "p", 1337, "port to listen on")
-	rootCmd.PersistentFlags().String("dir", defaultDir(), "directory to store config and data in")
-
-	viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
-	viper.BindPFlag("dir", rootCmd.PersistentFlags().Lookup("dir"))
+		viper.SetConfigName("config")
+		viper.AddConfigPath(storage.Path.Root())
+		viper.ReadInConfig()
+	},
 }
 
 func Execute() {
 	rootCmd.Execute()
 }
 
+func init() {
+	rootCmd.AddCommand(clearCmd)
+	rootCmd.AddCommand(configCmd)
+
+	rootCmd.Flags().IntP("port", "p", 80, "port to listen on")
+	rootCmd.PersistentFlags().String("dir", storage.DefaultDirectory(), "directory to store config and data in")
+
+	viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
+	viper.BindPFlag("dir", rootCmd.PersistentFlags().Lookup("dir"))
+}
+
 func initConfig() {
-	configPath := path.Join(appDir, "config.toml")
+	configPath := storage.Path.Config()
 
 	if _, err := os.Stat(configPath); os.IsNotExist(err) {
 		config, err := assets.Asset("config.default.toml")
@@ -66,18 +77,11 @@ func initConfig() {
 			return
 		}
 
+		log.Println("Writing default config to", configPath)
+
 		err = ioutil.WriteFile(configPath, config, 0600)
 		if err != nil {
 			log.Println(err)
 		}
 	}
 }
-
-func defaultDir() string {
-	dir, err := homedir.Dir()
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	return path.Join(dir, ".dispatch")
-}
diff --git a/config.default.toml b/config.default.toml
index e2b1fd0f..5b53b16c 100644
--- a/config.default.toml
+++ b/config.default.toml
@@ -1 +1,48 @@
-port = 1337
\ No newline at end of file
+port = 80
+
+[https]
+enabled = false
+port = 443
+# Redirect all http traffic to https
+redirect = true
+# Path to your cert and private key if you are not using
+# the Let's Encrypt integration
+cert = ""
+key = ""
+
+[letsencrypt]
+# Your domain or subdomain
+domain = ""
+# An email address lets you recover your accounts private key
+email = ""
+# The port Let's Encrypt listens on, comment this out to let it bind
+# to port 80 as needed, doing so means dispatch itself cannot use port 80
+port = 5001
+# Have dispatch proxy traffic from port 80 to the Let's Encrypt port
+proxy = true
+
+# Not implemented
+[auth]
+# Allow usage without being logged in, all channels and settings get
+# transferred when logging in or registering
+anonymous = true
+# Enable username/password login
+login = true
+# Enable username/password registration
+registration = true
+
+[auth.github]
+key = ""
+secret = ""
+
+[auth.facebook]
+key = ""
+secret = ""
+
+[auth.google]
+key = ""
+secret = ""
+
+[auth.twitter]
+key = ""
+secret = ""
diff --git a/letsencrypt/directory.go b/letsencrypt/directory.go
new file mode 100644
index 00000000..83acd4ea
--- /dev/null
+++ b/letsencrypt/directory.go
@@ -0,0 +1,38 @@
+package letsencrypt
+
+import (
+	"path/filepath"
+)
+
+type Directory string
+
+func (d Directory) Domain(domain string) string {
+	return filepath.Join(string(d), "certs", domain)
+}
+
+func (d Directory) Cert(domain string) string {
+	return filepath.Join(d.Domain(domain), "cert.pem")
+}
+
+func (d Directory) Key(domain string) string {
+	return filepath.Join(d.Domain(domain), "key.pem")
+}
+
+func (d Directory) Meta(domain string) string {
+	return filepath.Join(d.Domain(domain), "metadata.json")
+}
+
+func (d Directory) User(email string) string {
+	if email == "" {
+		email = defaultUser
+	}
+	return filepath.Join(string(d), "users", email)
+}
+
+func (d Directory) UserRegistration(email string) string {
+	return filepath.Join(d.User(email), "registration.json")
+}
+
+func (d Directory) UserKey(email string) string {
+	return filepath.Join(d.User(email), "key.pem")
+}
diff --git a/letsencrypt/letsencrypt.go b/letsencrypt/letsencrypt.go
new file mode 100644
index 00000000..394bc662
--- /dev/null
+++ b/letsencrypt/letsencrypt.go
@@ -0,0 +1,183 @@
+package letsencrypt
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"time"
+
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/xenolf/lego/acme"
+)
+
+const URL = "https://acme-v01.api.letsencrypt.org/directory"
+const KeySize = 2048
+
+var directory Directory
+
+func Run(dir, domain, email, port string, onChange func()) (string, string, error) {
+	directory = Directory(dir)
+
+	user, err := getUser(email)
+	if err != nil {
+		return "", "", nil
+	}
+
+	client, err := acme.NewClient(URL, &user, KeySize)
+	client.ExcludeChallenges([]string{"tls-sni-01"})
+	client.SetHTTPPort(port)
+
+	if user.Registration == nil {
+		user.Registration, err = client.Register()
+		if err != nil {
+			return "", "", err
+		}
+
+		err = client.AgreeToTOS()
+		if err != nil {
+			return "", "", err
+		}
+
+		err = saveUser(user)
+		if err != nil {
+			return "", "", err
+		}
+	}
+
+	if certExists(domain) {
+		renew(client, domain)
+	} else {
+		err = obtain(client, domain)
+		if err != nil {
+			return "", "", err
+		}
+	}
+
+	go keepRenewed(client, domain, onChange)
+
+	return directory.Cert(domain), directory.Key(domain), nil
+}
+
+func obtain(client *acme.Client, domain string) error {
+	cert, errors := client.ObtainCertificate([]string{domain}, false)
+	if err := errors[domain]; err != nil {
+		if _, ok := err.(acme.TOSError); ok {
+			err := client.AgreeToTOS()
+			if err != nil {
+				return err
+			}
+			return obtain(client, domain)
+		}
+
+		return err
+	}
+
+	err := saveCert(cert)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func renew(client *acme.Client, domain string) bool {
+	cert, err := ioutil.ReadFile(directory.Cert(domain))
+	if err != nil {
+		return false
+	}
+
+	exp, err := acme.GetPEMCertExpiration(cert)
+	if err != nil {
+		return false
+	}
+
+	daysLeft := int(exp.Sub(time.Now().UTC()).Hours() / 24)
+
+	if daysLeft <= 30 {
+		metaBytes, err := ioutil.ReadFile(directory.Meta(domain))
+		if err != nil {
+			return false
+		}
+
+		key, err := ioutil.ReadFile(directory.Key(domain))
+		if err != nil {
+			return false
+		}
+
+		var meta acme.CertificateResource
+		err = json.Unmarshal(metaBytes, &meta)
+		if err != nil {
+			return false
+		}
+		meta.Certificate = cert
+		meta.PrivateKey = key
+
+	Renew:
+		newMeta, err := client.RenewCertificate(meta, false)
+		if err != nil {
+			if _, ok := err.(acme.TOSError); ok {
+				err := client.AgreeToTOS()
+				if err != nil {
+					return false
+				}
+				goto Renew
+			}
+			return false
+		}
+
+		err = saveCert(newMeta)
+		if err != nil {
+			return false
+		}
+
+		return true
+	}
+
+	return false
+}
+
+func keepRenewed(client *acme.Client, domain string, onChange func()) {
+	for {
+		time.Sleep(24 * time.Hour)
+		if renew(client, domain) {
+			onChange()
+		}
+	}
+}
+
+func certExists(domain string) bool {
+	if _, err := os.Stat(directory.Cert(domain)); err != nil {
+		return false
+	}
+	if _, err := os.Stat(directory.Key(domain)); err != nil {
+		return false
+	}
+	return true
+}
+
+func saveCert(cert acme.CertificateResource) error {
+	err := os.MkdirAll(directory.Domain(cert.Domain), 0700)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(directory.Cert(cert.Domain), cert.Certificate, 0600)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(directory.Key(cert.Domain), cert.PrivateKey, 0600)
+	if err != nil {
+		return err
+	}
+
+	jsonBytes, err := json.MarshalIndent(&cert, "", "  ")
+	if err != nil {
+		return err
+	}
+	err = ioutil.WriteFile(directory.Meta(cert.Domain), jsonBytes, 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/letsencrypt/user.go b/letsencrypt/user.go
new file mode 100644
index 00000000..a53c9285
--- /dev/null
+++ b/letsencrypt/user.go
@@ -0,0 +1,106 @@
+package letsencrypt
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/json"
+	"encoding/pem"
+	"io/ioutil"
+	"os"
+
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/xenolf/lego/acme"
+)
+
+const defaultUser = "default"
+
+type User struct {
+	Email        string
+	Registration *acme.RegistrationResource
+	key          *rsa.PrivateKey
+}
+
+func (u User) GetEmail() string {
+	return u.Email
+}
+
+func (u User) GetRegistration() *acme.RegistrationResource {
+	return u.Registration
+}
+
+func (u User) GetPrivateKey() *rsa.PrivateKey {
+	return u.key
+}
+
+func newUser(email string) (User, error) {
+	var err error
+	user := User{Email: email}
+	user.key, err = rsa.GenerateKey(rand.Reader, KeySize)
+	if err != nil {
+		return user, err
+	}
+	return user, nil
+}
+
+func getUser(email string) (User, error) {
+	var user User
+
+	reg, err := os.Open(directory.UserRegistration(email))
+	if err != nil {
+		if os.IsNotExist(err) {
+			return newUser(email)
+		}
+		return user, err
+	}
+	defer reg.Close()
+
+	err = json.NewDecoder(reg).Decode(&user)
+	if err != nil {
+		return user, err
+	}
+
+	user.key, err = loadRSAPrivateKey(directory.UserKey(email))
+	if err != nil {
+		return user, err
+	}
+
+	return user, nil
+}
+
+func saveUser(user User) error {
+	err := os.MkdirAll(directory.User(user.Email), 0700)
+	if err != nil {
+		return err
+	}
+
+	err = saveRSAPrivateKey(user.key, directory.UserKey(user.Email))
+	if err != nil {
+		return err
+	}
+
+	jsonBytes, err := json.MarshalIndent(&user, "", "  ")
+	if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(directory.UserRegistration(user.Email), jsonBytes, 0600)
+}
+
+func loadRSAPrivateKey(file string) (*rsa.PrivateKey, error) {
+	keyBytes, err := ioutil.ReadFile(file)
+	if err != nil {
+		return nil, err
+	}
+	keyBlock, _ := pem.Decode(keyBytes)
+	return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
+}
+
+func saveRSAPrivateKey(key *rsa.PrivateKey, file string) error {
+	pemKey := pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
+	keyOut, err := os.Create(file)
+	if err != nil {
+		return err
+	}
+	defer keyOut.Close()
+	return pem.Encode(keyOut, &pemKey)
+}
diff --git a/server/https.go b/server/https.go
new file mode 100644
index 00000000..8be88a5c
--- /dev/null
+++ b/server/https.go
@@ -0,0 +1,82 @@
+package server
+
+import (
+	"crypto/tls"
+	"net"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/viper"
+)
+
+type restartableHTTPS struct {
+	listener net.Listener
+	handler  http.Handler
+	addr     string
+	cert     string
+	key      string
+}
+
+func (r *restartableHTTPS) start() error {
+	var err error
+
+	config := &tls.Config{
+		NextProtos:   []string{"http/1.1"},
+		Certificates: make([]tls.Certificate, 1),
+	}
+
+	config.Certificates[0], err = tls.LoadX509KeyPair(r.cert, r.key)
+	if err != nil {
+		return err
+	}
+
+	ln, err := net.Listen("tcp", r.addr)
+	if err != nil {
+		return err
+	}
+
+	r.listener = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
+	return http.Serve(r.listener, r.handler)
+}
+
+func (r *restartableHTTPS) stop() {
+	r.listener.Close()
+}
+
+func (r *restartableHTTPS) restart() {
+	r.stop()
+	go r.start()
+}
+
+type tcpKeepAliveListener struct {
+	*net.TCPListener
+}
+
+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
+	tc, err := ln.AcceptTCP()
+	if err != nil {
+		return
+	}
+	tc.SetKeepAlive(true)
+	tc.SetKeepAlivePeriod(3 * time.Minute)
+	return tc, nil
+}
+
+func certExists() bool {
+	cert := viper.GetString("https.cert")
+	key := viper.GetString("https.key")
+
+	if cert == "" || key == "" {
+		return false
+	}
+
+	if _, err := os.Stat(cert); err != nil {
+		return false
+	}
+	if _, err := os.Stat(key); err != nil {
+		return false
+	}
+
+	return true
+}
diff --git a/server/irc.go b/server/irc.go
new file mode 100644
index 00000000..121c49ac
--- /dev/null
+++ b/server/irc.go
@@ -0,0 +1,36 @@
+package server
+
+import (
+	"github.com/khlieng/dispatch/irc"
+	"github.com/khlieng/dispatch/storage"
+)
+
+func reconnectIRC() {
+	for _, user := range storage.LoadUsers() {
+		session := NewSession()
+		session.user = user
+		sessions[user.UUID] = session
+		go session.write()
+
+		channels := user.GetChannels()
+
+		for _, server := range user.GetServers() {
+			i := irc.NewClient(server.Nick, server.Username)
+			i.TLS = server.TLS
+			i.Password = server.Password
+			i.Realname = server.Realname
+
+			i.Connect(server.Address)
+			session.setIRC(i.Host, i)
+			go newIRCHandler(i, session).run()
+
+			var joining []string
+			for _, channel := range channels {
+				if channel.Server == server.Address {
+					joining = append(joining, channel.Name)
+				}
+			}
+			i.Join(joining...)
+		}
+	}
+}
diff --git a/server/irc_handler_test.go b/server/irc_handler_test.go
index ad4ef476..34ef1689 100644
--- a/server/irc_handler_test.go
+++ b/server/irc_handler_test.go
@@ -4,7 +4,6 @@ import (
 	"io/ioutil"
 	"log"
 	"os"
-	"path"
 	"testing"
 
 	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/stretchr/testify/assert"
@@ -21,8 +20,9 @@ func TestMain(m *testing.M) {
 		log.Fatal(err)
 	}
 
-	os.Mkdir(path.Join(tempdir, "logs"), 0777)
-	storage.Initialize(tempdir)
+	storage.SetDirectory(tempdir)
+	os.MkdirAll(storage.Path.Logs(), 0700)
+	storage.Initialize()
 	user = storage.NewUser("uuid")
 	channelStore = storage.NewChannelStore()
 
diff --git a/server/server.go b/server/server.go
index 181ad757..268d7c19 100644
--- a/server/server.go
+++ b/server/server.go
@@ -2,13 +2,17 @@ package server
 
 import (
 	"log"
+	"net"
 	"net/http"
-	"strconv"
+	"net/http/httputil"
+	"net/url"
+	"strings"
 	"sync"
 
 	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/gorilla/websocket"
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/viper"
 
-	"github.com/khlieng/dispatch/irc"
+	"github.com/khlieng/dispatch/letsencrypt"
 	"github.com/khlieng/dispatch/storage"
 )
 
@@ -26,21 +30,66 @@ var (
 	}
 )
 
-func Run(port int) {
+func Run() {
 	defer storage.Close()
 
 	channelStore = storage.NewChannelStore()
 	sessions = make(map[string]*Session)
 
-	reconnect()
+	reconnectIRC()
+	startHTTP()
 
-	log.Println("Listening on port", port)
-	log.Fatal(http.ListenAndServe(":"+strconv.Itoa(port), handler{}))
+	select {}
 }
 
-type handler struct{}
+func startHTTP() {
+	port := viper.GetString("port")
 
-func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	if viper.GetBool("https.enabled") {
+		var err error
+		portHTTPS := viper.GetString("https.port")
+		redirect := viper.GetBool("https.redirect")
+
+		https := restartableHTTPS{
+			addr:    ":" + portHTTPS,
+			handler: http.HandlerFunc(serve),
+		}
+
+		if viper.GetBool("https.redirect") {
+			log.Println("[HTTP] Listening on port", port, "(HTTPS Redirect)")
+			go http.ListenAndServe(":"+port, createHTTPSRedirect(portHTTPS))
+		}
+
+		if certExists() {
+			https.cert = viper.GetString("https.cert")
+			https.key = viper.GetString("https.key")
+		} else if domain := viper.GetString("letsencrypt.domain"); domain != "" {
+			dir := storage.Path.LetsEncrypt()
+			email := viper.GetString("letsencrypt.email")
+			lePort := viper.GetString("letsencrypt.port")
+
+			if viper.GetBool("letsencrypt.proxy") && lePort != "" && (port != "80" || !redirect) {
+				log.Println("[HTTP] Listening on port 80 (Let's Encrypt Proxy))")
+				go http.ListenAndServe(":80", http.HandlerFunc(letsEncryptProxy))
+			}
+
+			https.cert, https.key, err = letsencrypt.Run(dir, domain, email, lePort, https.restart)
+			if err != nil {
+				log.Fatal(err)
+			}
+		} else {
+			log.Fatal("Could not locate SSL certificate or private key")
+		}
+
+		log.Println("[HTTPS] Listening on port", portHTTPS)
+		https.start()
+	} else {
+		log.Println("[HTTP] Listening on port", port)
+		log.Fatal(http.ListenAndServe(":"+port, http.HandlerFunc(serve)))
+	}
+}
+
+func serve(w http.ResponseWriter, r *http.Request) {
 	if r.Method != "GET" {
 		return
 	}
@@ -65,32 +114,39 @@ func upgradeWS(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func reconnect() {
-	for _, user := range storage.LoadUsers() {
-		session := NewSession()
-		session.user = user
-		sessions[user.UUID] = session
-		go session.write()
-
-		channels := user.GetChannels()
-
-		for _, server := range user.GetServers() {
-			i := irc.NewClient(server.Nick, server.Username)
-			i.TLS = server.TLS
-			i.Password = server.Password
-			i.Realname = server.Realname
-
-			i.Connect(server.Address)
-			session.setIRC(i.Host, i)
-			go newIRCHandler(i, session).run()
-
-			var joining []string
-			for _, channel := range channels {
-				if channel.Server == server.Address {
-					joining = append(joining, channel.Name)
-				}
-			}
-			i.Join(joining...)
+func createHTTPSRedirect(portHTTPS string) http.HandlerFunc {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge") {
+			letsEncryptProxy(w, r)
+			return
 		}
-	}
+
+		host, _, err := net.SplitHostPort(r.Host)
+		if err != nil {
+			host = r.Host
+		}
+
+		u := url.URL{
+			Scheme: "https",
+			Host:   net.JoinHostPort(host, portHTTPS),
+			Path:   r.RequestURI,
+		}
+
+		w.Header().Set("Location", u.String())
+		w.WriteHeader(http.StatusMovedPermanently)
+	})
+}
+
+func letsEncryptProxy(w http.ResponseWriter, r *http.Request) {
+	host, _, err := net.SplitHostPort(r.Host)
+	if err != nil {
+		host = r.Host
+	}
+
+	upstream := &url.URL{
+		Scheme: "http",
+		Host:   net.JoinHostPort(host, viper.GetString("letsencrypt.port")),
+	}
+
+	httputil.NewSingleHostReverseProxy(upstream).ServeHTTP(w, r)
 }
diff --git a/storage/directory.go b/storage/directory.go
new file mode 100644
index 00000000..f83e7135
--- /dev/null
+++ b/storage/directory.go
@@ -0,0 +1,48 @@
+package storage
+
+import (
+	"path/filepath"
+
+	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/mitchellh/go-homedir"
+)
+
+var Path directory
+
+func SetDirectory(dir string) {
+	Path = directory(dir)
+}
+
+func DefaultDirectory() string {
+	home, _ := homedir.Dir()
+	return filepath.Join(home, ".dispatch")
+}
+
+type directory string
+
+func (d directory) Root() string {
+	return string(d)
+}
+
+func (d directory) LetsEncrypt() string {
+	return filepath.Join(d.Root(), "letsencrypt")
+}
+
+func (d directory) Logs() string {
+	return filepath.Join(d.Root(), "logs")
+}
+
+func (d directory) Log(userID string) string {
+	return filepath.Join(d.Logs(), userID+".log")
+}
+
+func (d directory) Index(userID string) string {
+	return filepath.Join(d.Logs(), userID+".idx")
+}
+
+func (d directory) Config() string {
+	return filepath.Join(d.Root(), "config.toml")
+}
+
+func (d directory) Database() string {
+	return filepath.Join(d.Root(), "dispatch.db")
+}
diff --git a/storage/storage.go b/storage/storage.go
index 257fcb77..a76b1887 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -3,7 +3,6 @@ package storage
 import (
 	"log"
 	"os"
-	"path"
 
 	"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
 )
@@ -18,15 +17,13 @@ var (
 	bucketMessages = []byte("Messages")
 )
 
-func Initialize(dir string) {
+func Initialize() {
+	log.Println("Storing data at", Path.Root())
+
 	var err error
-	appDir = dir
-
-	log.Println("Storing data at", dir)
-
-	db, err = bolt.Open(path.Join(dir, "data.db"), 0600, nil)
+	db, err = bolt.Open(Path.Database(), 0600, nil)
 	if err != nil {
-		log.Fatal("Could not open database file")
+		log.Fatal("Could not open database:", err)
 	}
 
 	db.Update(func(tx *bolt.Tx) error {
@@ -42,7 +39,7 @@ func Close() {
 	db.Close()
 }
 
-func Clear(dir string) {
-	os.RemoveAll(path.Join(dir, "logs"))
-	os.Remove(path.Join(dir, "data.db"))
+func Clear() {
+	os.RemoveAll(Path.Logs())
+	os.Remove(Path.Database())
 }
diff --git a/storage/user.go b/storage/user.go
index bf20ce78..70fdb3c5 100644
--- a/storage/user.go
+++ b/storage/user.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"encoding/json"
 	"log"
-	"path"
 	"strconv"
 	"strings"
 	"time"
@@ -313,7 +312,7 @@ func (u *User) Close() {
 func (u *User) openMessageLog() {
 	var err error
 
-	u.messageLog, err = bolt.Open(path.Join(appDir, "logs", u.UUID+"_log"), 0600, nil)
+	u.messageLog, err = bolt.Open(Path.Log(u.UUID), 0600, nil)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -324,7 +323,7 @@ func (u *User) openMessageLog() {
 		return nil
 	})
 
-	indexPath := path.Join(appDir, "logs", u.UUID+"_index")
+	indexPath := Path.Index(u.UUID)
 	u.messageIndex, err = bleve.Open(indexPath)
 	if err == bleve.ErrorIndexPathDoesNotExist {
 		mapping := bleve.NewIndexMapping()