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 + +[](https://godoc.org/github.com/square/go-jose) [](https://raw.githubusercontent.com/square/go-jose/master/LICENSE) [](https://travis-ci.org/square/go-jose) [](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()