Merge branch 'master' into store-ips

This commit is contained in:
Roland Shoemaker 2015-07-29 12:24:20 -07:00
commit 6777b276a7
39 changed files with 1868 additions and 235 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
*.o
*.a
*.so
*.pyc
# Folders
_obj

2
Godeps/Godeps.json generated
View File

@ -74,7 +74,7 @@
},
{
"ImportPath": "github.com/letsencrypt/go-jose",
"Rev": "924fcf0aaa98274fd0d586edf64a517ebe24797e"
"Rev": "1e94de308e9f6348370af5e409ce552aeb8f1019"
},
{
"ImportPath": "github.com/mattn/go-sqlite3",

View File

@ -13,15 +13,21 @@ go:
- 1.4
- 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

View File

@ -17,10 +17,10 @@ US maintained blocked list.
## Overview
The implementation follows the
[JSON Web Encryption](http://www.ietf.org/id/draft-ietf-jose-json-web-encryption-40.txt)
standard (as of version 40) and
[JSON Web Signature](http://www.ietf.org/id/draft-ietf-jose-json-web-signature-41.txt)
standard (as of version 41). Tables of supported algorithms are shown below.
[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
@ -30,7 +30,7 @@ shell.
See below for a table of supported algorithms. Algorithm identifiers match
the names in the
[JSON Web Algorithms](http://www.ietf.org/id/draft-ietf-jose-json-web-algorithms-40.txt)
[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.

View File

@ -0,0 +1,189 @@
/*-
* 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)
buffer := []byte(ciphertext[:offset])
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 > 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
}

View File

@ -0,0 +1,458 @@
/*-
* 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/rand"
"io"
"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 TestInvalidCiphertext(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)
// Mutated ciphertext, but with correct auth tag
ct[len(ct)-ctx.authtagBytes-1] ^= 0xFF
tag := ctx.computeAuthTag(nil, nonce, ct[:len(ct)-ctx.authtagBytes])
copy(ct[len(ct)-ctx.authtagBytes:], tag)
// Open should fail (b/c of invalid padding, even though tag matches)
_, err = aead.Open(nil, nonce, ct, nil)
if err == nil {
t.Error("open on mutated ciphertext should fail")
}
}
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 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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -20,6 +20,7 @@ import (
"bytes"
"compress/flate"
"encoding/base64"
"encoding/binary"
"encoding/json"
"io"
"math/big"
@ -131,6 +132,12 @@ func newBuffer(data []byte) *byteBuffer {
}
}
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())
}

View File

@ -106,3 +106,15 @@ func TestInvalidCompression(t *testing.T) {
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]")
}
}

View File

@ -10,10 +10,10 @@ 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 JWE/JWS messages respectively. For JWE, `--alg`
specified the key managment algorithm (e.g. RSA-OAEP) and `--enc` specifies the
content encryption (e.g. A128GCM). For JWS, `--alg` specified the signature
algorithm (e.g. `PS256`).
`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

View File

@ -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

View File

@ -20,7 +20,6 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"encoding/binary"
"encoding/json"
"fmt"
"math/big"
@ -128,13 +127,10 @@ func (key rawJsonWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
}
func fromRsaPublicKey(pub *rsa.PublicKey) *rawJsonWebKey {
e := make([]byte, 4)
binary.BigEndian.PutUint32(e, uint32(pub.E))
return &rawJsonWebKey{
Kty: "RSA",
N: newBuffer(pub.N.Bytes()),
E: newBuffer(e),
E: newBufferFromInt(uint64(pub.E)),
}
}

View File

@ -18,11 +18,11 @@ package jose
import (
"bytes"
"fmt"
"encoding/json"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"encoding/json"
"fmt"
"math/big"
"reflect"
"testing"
@ -178,7 +178,7 @@ func TestMarshalNonPointer(t *testing.T) {
t.Error(fmt.Sprintf("Error marshalling JSON: %v", err))
return
}
expected := "{\"Key\":{\"kty\":\"RSA\",\"n\":\"vd7rZIoTLEe-z1_8G1FcXSw9CQFEJgV4g9V277sER7yx5Qjz_Pkf2YVth6wwwFJEmzc0hoKY-MMYFNwBE4hQHw\",\"e\":\"AAEAAQ\"}}"
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))
}

View File

@ -474,7 +474,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
return cert, nil
}
ca.SA.UpdateOCSP(serial, ocspResponse)
err = ca.SA.UpdateOCSP(serial, ocspResponse)
if err != nil {
ca.log.Warning(fmt.Sprintf("Post-Issuance OCSP failed storing: %s", err))
return cert, nil

View File

@ -436,8 +436,9 @@ func TestRevoke(t *testing.T) {
test.AssertNotError(t, err, "Failed to get cert status")
test.AssertEquals(t, status.Status, core.OCSPStatusRevoked)
test.Assert(t, time.Now().Sub(status.OCSPLastUpdated) > time.Second,
fmt.Sprintf("OCSP LastUpdated was wrong: %v", status.OCSPLastUpdated))
secondAgo := time.Now().Add(-time.Second)
test.Assert(t, status.OCSPLastUpdated.After(secondAgo),
fmt.Sprintf("OCSP LastUpdated was more than a second old: %v", status.OCSPLastUpdated))
}
func TestIssueCertificate(t *testing.T) {

View File

@ -122,13 +122,18 @@ func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.
return addrs, aRtt, aaaaRtt, nil
}
// LookupCNAME sends a DNS query to find a CNAME record associated hostname and returns the
// record target.
// LookupCNAME returns the target name if a CNAME record exists for
// the given domain name. If the CNAME does not exist (NXDOMAIN,
// NXRRSET, or a successful response with no CNAME records), it
// returns the empty string and a nil error.
func (dnsResolver *DNSResolverImpl) LookupCNAME(hostname string) (string, time.Duration, error) {
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeCNAME)
if err != nil {
return "", 0, err
}
if r.Rcode == dns.RcodeNXRrset || r.Rcode == dns.RcodeNameError {
return "", rtt, nil
}
if r.Rcode != dns.RcodeSuccess {
err = fmt.Errorf("DNS failure: %d-%s for CNAME query", r.Rcode, dns.RcodeToString[r.Rcode])
return "", rtt, err
@ -143,9 +148,32 @@ func (dnsResolver *DNSResolverImpl) LookupCNAME(hostname string) (string, time.D
return "", rtt, nil
}
// LookupDNAME is LookupCNAME, but for DNAME.
func (dnsResolver *DNSResolverImpl) LookupDNAME(hostname string) (string, time.Duration, error) {
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeDNAME)
if err != nil {
return "", 0, err
}
if r.Rcode == dns.RcodeNXRrset || r.Rcode == dns.RcodeNameError {
return "", rtt, nil
}
if r.Rcode != dns.RcodeSuccess {
err = fmt.Errorf("DNS failure: %d-%s for DNAME query", r.Rcode, dns.RcodeToString[r.Rcode])
return "", rtt, err
}
for _, answer := range r.Answer {
if cname, ok := answer.(*dns.DNAME); ok {
return cname.Target, rtt, nil
}
}
return "", rtt, nil
}
// LookupCAA sends a DNS query to find all CAA records associated with
// the provided hostname. If the response code from the resolver is SERVFAIL
// an empty slice of CAA records is returned.
// the provided hostname. If the response code from the resolver is
// SERVFAIL an empty slice of CAA records is returned.
func (dnsResolver *DNSResolverImpl) LookupCAA(hostname string) ([]*dns.CAA, time.Duration, error) {
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeCAA)
if err != nil {

View File

@ -9,6 +9,7 @@ import (
"fmt"
"net"
"os"
"strings"
"testing"
"time"
@ -25,11 +26,14 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) {
m.SetReply(r)
m.Compress = false
appendAnswer := func(rr dns.RR) {
m.Answer = append(m.Answer, rr)
}
for _, q := range r.Question {
q.Name = strings.ToLower(q.Name)
if q.Name == "servfail.com." {
m.Rcode = dns.RcodeServerFailure
w.WriteMsg(m)
return
break
}
switch q.Qtype {
case dns.TypeSOA:
@ -42,31 +46,50 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) {
record.Retry = 1
record.Expire = 1
record.Minttl = 1
m.Answer = append(m.Answer, record)
w.WriteMsg(m)
return
appendAnswer(record)
case dns.TypeA:
if q.Name == "cps.letsencrypt.org." {
record := new(dns.A)
record.Hdr = dns.RR_Header{Name: "cps.letsencrypt.org.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}
record.A = net.ParseIP("127.0.0.1")
m.Answer = append(m.Answer, record)
w.WriteMsg(m)
return
appendAnswer(record)
}
case dns.TypeCNAME:
if q.Name == "cname.letsencrypt.org." {
record := new(dns.CNAME)
record.Hdr = dns.RR_Header{Name: "cname.letsencrypt.org.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 30}
record.Target = "cps.letsencrypt.org."
appendAnswer(record)
}
if q.Name == "cname.example.com." {
record := new(dns.CNAME)
record.Hdr = dns.RR_Header{Name: "cname.example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 30}
record.Target = "CAA.example.com."
appendAnswer(record)
}
case dns.TypeDNAME:
if q.Name == "dname.letsencrypt.org." {
record := new(dns.DNAME)
record.Hdr = dns.RR_Header{Name: "dname.letsencrypt.org.", Rrtype: dns.TypeDNAME, Class: dns.ClassINET, Ttl: 30}
record.Target = "cps.letsencrypt.org."
appendAnswer(record)
}
case dns.TypeCAA:
if q.Name == "bracewel.net." {
if q.Name == "bracewel.net." || q.Name == "caa.example.com." {
record := new(dns.CAA)
record.Hdr = dns.RR_Header{Name: "bracewel.net.", Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0}
record.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0}
record.Tag = "issue"
record.Value = "letsencrypt.org"
record.Flag = 1
m.Answer = append(m.Answer, record)
w.WriteMsg(m)
return
appendAnswer(record)
}
if q.Name == "cname.example.com." {
record := new(dns.CAA)
record.Hdr = dns.RR_Header{Name: "caa.example.com.", Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0}
record.Tag = "issue"
record.Value = "letsencrypt.org"
record.Flag = 1
appendAnswer(record)
}
}
}
@ -157,7 +180,7 @@ func TestDNSServFail(t *testing.T) {
test.AssertError(t, err, "LookupCNAME didn't return an error")
_, _, _, err = obj.LookupHost(bad)
test.AssertError(t, err, "LookupCNAME didn't return an error")
test.AssertError(t, err, "LookupHost didn't return an error")
// CAA lookup ignores validation failures from the resolver for now
// and returns an empty list of CAA records.
@ -204,4 +227,32 @@ func TestDNSLookupCAA(t *testing.T) {
caas, _, err = obj.LookupCAA("nonexistent.letsencrypt.org")
test.AssertNotError(t, err, "CAA lookup failed")
test.Assert(t, len(caas) == 0, "Shouldn't have CAA records")
caas, _, err = obj.LookupCAA("cname.example.com")
test.AssertNotError(t, err, "CAA lookup failed")
test.Assert(t, len(caas) > 0, "Should follow CNAME to find CAA")
}
func TestDNSLookupCNAME(t *testing.T) {
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
target, _, err := obj.LookupCNAME("cps.letsencrypt.org")
test.AssertNotError(t, err, "CNAME lookup failed")
test.AssertEquals(t, target, "")
target, _, err = obj.LookupCNAME("cname.letsencrypt.org")
test.AssertNotError(t, err, "CNAME lookup failed")
test.AssertEquals(t, target, "cps.letsencrypt.org.")
}
func TestDNSLookupDNAME(t *testing.T) {
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
target, _, err := obj.LookupDNAME("cps.letsencrypt.org")
test.AssertNotError(t, err, "DNAME lookup failed")
test.AssertEquals(t, target, "")
target, _, err = obj.LookupDNAME("dname.letsencrypt.org")
test.AssertNotError(t, err, "DNAME lookup failed")
test.AssertEquals(t, target, "cps.letsencrypt.org.")
}

View File

@ -144,6 +144,7 @@ type DNSResolver interface {
LookupTXT(string) ([]string, time.Duration, error)
LookupHost(string) ([]net.IP, time.Duration, time.Duration, error)
LookupCNAME(string) (string, time.Duration, error)
LookupDNAME(string) (string, time.Duration, error)
LookupCAA(string) ([]*dns.CAA, time.Duration, error)
LookupMX(string) ([]string, time.Duration, error)
}

View File

@ -450,11 +450,11 @@ type Certificate struct {
// * "revoked" - revoked
Status AcmeStatus `db:"status"`
Serial string `db:"serial"`
Digest string `db:"digest"`
DER JSONBuffer `db:"der"`
Issued time.Time `db:"issued"`
Expires time.Time `db:"expires"`
Serial string `db:"serial"`
Digest string `db:"digest"`
DER []byte `db:"der"`
Issued time.Time `db:"issued"`
Expires time.Time `db:"expires"`
}
// MatchesCSR tests the contents of a generated certificate to make sure

View File

@ -54,13 +54,13 @@ func TestBuildID(t *testing.T) {
const JWK1JSON = `{
"kty": "RSA",
"n": "vuc785P8lBj3fUxyZchF_uZw6WtbxcorqgTyq-qapF5lrO1U82Tp93rpXlmctj6fyFHBVVB5aXnUHJ7LZeVPod7Wnfl8p5OyhlHQHC8BnzdzCqCMKmWZNX5DtETDId0qzU7dPzh0LP0idt5buU7L9QNaabChw3nnaL47iu_1Di5Wp264p2TwACeedv2hfRDjDlJmaQXuS8Rtv9GnRWyC9JBu7XmGvGDziumnJH7Hyzh3VNu-kSPQD3vuAFgMZS6uUzOztCkT0fpOalZI6hqxtWLvXUMj-crXrn-Maavz8qRhpAyp5kcYk3jiHGgQIi7QSK2JIdRJ8APyX9HlmTN5AQ",
"e": "AAEAAQ"
"e": "AQAB"
}`
const JWK1Digest = `ul04Iq07ulKnnrebv2hv3yxCGgVvoHs8hjq2tVKx3mc=`
const JWK2JSON = `{
"kty":"RSA",
"n":"yTsLkI8n4lg9UuSKNRC0UPHsVjNdCYk8rGXIqeb_rRYaEev3D9-kxXY8HrYfGkVt5CiIVJ-n2t50BKT8oBEMuilmypSQqJw0pCgtUm-e6Z0Eg3Ly6DMXFlycyikegiZ0b-rVX7i5OCEZRDkENAYwFNX4G7NNCwEZcH7HUMUmty9dchAqDS9YWzPh_dde1A9oy9JMH07nRGDcOzIh1rCPwc71nwfPPYeeS4tTvkjanjeigOYBFkBLQuv7iBB4LPozsGF1XdoKiIIi-8ye44McdhOTPDcQp3xKxj89aO02pQhBECv61rmbPinvjMG9DYxJmZvjsKF4bN2oy0DxdC1jDw",
"e":"AAEAAQ"
"e":"AQAB"
}`
func TestKeyDigest(t *testing.T) {

View File

@ -9,6 +9,7 @@ import (
"database/sql"
"fmt"
"net"
"strings"
"time"
// Load SQLite3 for test purposes
@ -75,14 +76,56 @@ func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, time.
// LookupCNAME is a mock
func (mock *MockDNS) LookupCNAME(domain string) (string, time.Duration, error) {
return "hostname", 0, nil
switch strings.TrimRight(domain, ".") {
case "cname-absent.com":
return "absent.com.", 30, nil
case "cname-critical.com":
return "critical.com.", 30, nil
case "cname-present.com", "cname-and-dname.com":
return "cname-target.present.com.", 30, nil
case "cname2-present.com":
return "cname-present.com.", 30, nil
case "a.cname-loop.com":
return "b.cname-loop.com.", 30, nil
case "b.cname-loop.com":
return "a.cname-loop.com.", 30, nil
case "www.caa-loop.com":
// nothing wrong with CNAME, but prevents CAA algorithm from terminating
return "oops.www.caa-loop.com.", 30, nil
case "cname2servfail.com":
return "servfail.com.", 30, nil
case "cname-servfail.com":
return "", 0, fmt.Errorf("SERVFAIL")
case "cname2dname.com":
return "dname2cname.com.", 30, nil
default:
return "", 0, nil
}
}
// LookupDNAME is a mock
func (mock *MockDNS) LookupDNAME(domain string) (string, time.Duration, error) {
switch strings.TrimRight(domain, ".") {
case "cname-and-dname.com", "dname-present.com":
return "dname-target.present.com.", time.Minute, nil
case "a.dname-loop.com":
return "b.dname-loop.com.", time.Minute, nil
case "b.dname-loop.com":
return "a.dname-loop.com.", time.Minute, nil
case "dname2cname.com":
return "cname2-present.com.", time.Minute, nil
case "dname-servfail.com":
return "", time.Minute, fmt.Errorf("SERVFAIL")
default:
return "", 0, nil
}
}
// LookupCAA is a mock
func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) {
var results []*dns.CAA
var record dns.CAA
switch domain {
switch strings.TrimRight(domain, ".") {
case "reserved.com":
record.Tag = "issue"
record.Value = "symantec.com"
@ -104,7 +147,7 @@ func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error)
// LookupMX is a mock
func (mock *MockDNS) LookupMX(domain string) ([]string, time.Duration, error) {
switch domain {
switch strings.TrimRight(domain, ".") {
case "letsencrypt.org":
fallthrough
case "email.com":

View File

@ -57,14 +57,14 @@ var (
AccountKeyJSONB = []byte(`{
"kty":"RSA",
"n":"z8bp-jPtHt4lKBqepeKF28g_QAEOuEsCIou6sZ9ndsQsEjxEOQxQ0xNOQezsKa63eogw8YS3vzjUcPP5BJuVzfPfGd5NVUdT-vSSwxk3wvk_jtNqhrpcoG0elRPQfMVsQWmxCAXCVRz3xbcFI8GTe-syynG3l-g1IzYIIZVNI6jdljCZML1HOMTTW4f7uJJ8mM-08oQCeHbr5ejK7O2yMSSYxW03zY-Tj1iVEebROeMv6IEEJNFSS4yM-hLpNAqVuQxFGetwtwjDMC1Drs1dTWrPuUAAjKGrP151z1_dE74M5evpAhZUmpKv1hY-x85DC6N0hFPgowsanmTNNiV75w",
"e":"AAEAAQ"
"e":"AQAB"
}`)
AccountKeyB = jose.JsonWebKey{}
AccountKeyJSONC = []byte(`{
"kty":"RSA",
"n":"rFH5kUBZrlPj73epjJjyCxzVzZuV--JjKgapoqm9pOuOt20BUTdHqVfC2oDclqM7HFhkkX9OSJMTHgZ7WaVqZv9u1X2yjdx9oVmMLuspX7EytW_ZKDZSzL-sCOFCuQAuYKkLbsdcA3eHBK_lwc4zwdeHFMKIulNvLqckkqYB9s8GpgNXBDIQ8GjR5HuJke_WUNjYHSd8jY1LU9swKWsLQe2YoQUz_ekQvBvBCoaFEtrtRaSJKNLIVDObXFr2TLIiFiM0Em90kK01-eQ7ZiruZTKomll64bRFPoNo4_uwubddg3xTqur2vdF3NyhTrYdvAgTem4uC0PFjEQ1bK_djBQ",
"e":"AAEAAQ"
"e":"AQAB"
}`)
AccountKeyC = jose.JsonWebKey{}

View File

@ -21,7 +21,7 @@ var log = mocks.UseMockLog()
const JWK1JSON = `{
"kty": "RSA",
"n": "vuc785P8lBj3fUxyZchF_uZw6WtbxcorqgTyq-qapF5lrO1U82Tp93rpXlmctj6fyFHBVVB5aXnUHJ7LZeVPod7Wnfl8p5OyhlHQHC8BnzdzCqCMKmWZNX5DtETDId0qzU7dPzh0LP0idt5buU7L9QNaabChw3nnaL47iu_1Di5Wp264p2TwACeedv2hfRDjDlJmaQXuS8Rtv9GnRWyC9JBu7XmGvGDziumnJH7Hyzh3VNu-kSPQD3vuAFgMZS6uUzOztCkT0fpOalZI6hqxtWLvXUMj-crXrn-Maavz8qRhpAyp5kcYk3jiHGgQIi7QSK2JIdRJ8APyX9HlmTN5AQ",
"e": "AAEAAQ"
"e": "AQAB"
}`
type MockRPCClient struct {

View File

@ -266,7 +266,7 @@ func (ssa *SQLStorageAuthority) UpdateOCSP(serial string, ocspResponse []byte) (
// Reset the update clock
status.OCSPLastUpdated = timeStamp
_, err = tx.Update(status)
_, err = tx.Update(&status)
if err != nil {
tx.Rollback()
return err

View File

@ -211,3 +211,32 @@ func TestDeniedCSR(t *testing.T) {
test.AssertNotError(t, err, "AlreadyDeniedCSR failed")
test.Assert(t, !exists, "Found non-existent CSR")
}
func TestUpdateOCSP(t *testing.T) {
sa := initSA(t)
// Add a cert to the DB to test with.
certDER, err := ioutil.ReadFile("www.eff.org.der")
test.AssertNotError(t, err, "Couldn't read example cert DER")
_, err = sa.AddCertificate(certDER, 1)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
serial := "00000000000000000000000000021bd4"
const ocspResponse = "this is a fake OCSP response"
err = sa.UpdateOCSP(serial, []byte(ocspResponse))
test.AssertNotError(t, err, "UpdateOCSP failed")
certificateStatusObj, err := sa.dbMap.Get(core.CertificateStatus{}, serial)
certificateStatus := certificateStatusObj.(*core.CertificateStatus)
test.AssertNotError(t, err, "Failed to fetch certificate status")
test.Assert(t,
certificateStatus.OCSPLastUpdated.After(time.Now().Add(-time.Second)),
"OCSP last updated too old.")
var fetchedOcspResponse core.OCSPResponse
err = sa.dbMap.SelectOne(&fetchedOcspResponse,
`SELECT * from ocspResponses where serial = ? order by createdAt DESC limit 1;`,
serial)
test.AssertNotError(t, err, "Failed to fetch OCSP response")
test.AssertEquals(t, ocspResponse, string(fetchedOcspResponse.Response))
}

View File

@ -18,7 +18,7 @@ import (
const JWK1JSON = `{
"kty": "RSA",
"n": "vuc785P8lBj3fUxyZchF_uZw6WtbxcorqgTyq-qapF5lrO1U82Tp93rpXlmctj6fyFHBVVB5aXnUHJ7LZeVPod7Wnfl8p5OyhlHQHC8BnzdzCqCMKmWZNX5DtETDId0qzU7dPzh0LP0idt5buU7L9QNaabChw3nnaL47iu_1Di5Wp264p2TwACeedv2hfRDjDlJmaQXuS8Rtv9GnRWyC9JBu7XmGvGDziumnJH7Hyzh3VNu-kSPQD3vuAFgMZS6uUzOztCkT0fpOalZI6hqxtWLvXUMj-crXrn-Maavz8qRhpAyp5kcYk3jiHGgQIi7QSK2JIdRJ8APyX9HlmTN5AQ",
"e": "AAEAAQ"
"e": "AQAB"
}`
func TestAcmeIdentifier(t *testing.T) {

View File

@ -3,46 +3,29 @@
Run a local instance of Boulder for testing purposes.
This runs in non-monolithic mode and requires RabbitMQ on localhost.
Keeps servers alive until ^C. Exit non-zero if any servers fail to
start, or die before ^C.
"""
import os
import shutil
import socket
import subprocess
import sys
import tempfile
import time
tempdir = tempfile.mkdtemp()
sys.path.append('./test')
import startservers
config = os.environ.get('BOULDER_CONFIG')
if config is None:
config = 'test/boulder-config.json'
def run(path):
binary = os.path.join(tempdir, os.path.basename(path))
cmd = 'go build -o %s %s' % (binary, path)
print(cmd)
if subprocess.Popen(cmd, shell=True).wait() != 0:
sys.exit(1)
return subprocess.Popen('''
exec %s --config %s
''' % (binary, config), shell=True)
processes = []
def start():
global processes
processes = [
run('./cmd/boulder-wfe'),
run('./cmd/boulder-ra'),
run('./cmd/boulder-sa'),
run('./cmd/boulder-ca'),
run('./cmd/boulder-va')]
time.sleep(100000)
if not startservers.start():
sys.exit(1)
try:
start()
finally:
for p in processes:
if p.poll() is None:
p.kill()
time.sleep(1)
print("All servers are running. To stop, hit ^C.")
os.wait()
# If we reach here, a child died early. Log what died:
startservers.check()
sys.exit(1)
except KeyboardInterrupt:
print "\nstopping servers."

View File

@ -1,26 +1,18 @@
#!/usr/bin/env python2.7
import atexit
import os
import shutil
import socket
import subprocess
import sys
import tempfile
import time
tempdir = tempfile.mkdtemp()
import startservers
class ExitStatus:
OK, PythonFailure, NodeFailure, Error = range(4)
exit_status = 0
def die(status):
global exit_status
exit_status = status
sys.exit(1)
processes = []
class ProcInfo:
"""
@ -33,31 +25,18 @@ class ProcInfo:
self.cmd = cmd
self.proc = proc
def run(path, args=""):
global processes
binary = os.path.join(tempdir, os.path.basename(path))
cmd = 'GORACE="halt_on_error=1" go build -race -o %s %s' % (binary, path)
print(cmd)
if subprocess.Popen(cmd, shell=True).wait() != 0:
die(ExitStatus.Error)
runCmd = "exec %s %s" % (binary, args)
print(runCmd)
info = ProcInfo(runCmd, subprocess.Popen(runCmd, shell=True))
processes.append(info)
return info
def start():
run('./cmd/boulder-wfe', '--config test/boulder-test-config.json')
run('./cmd/boulder-ra', '--config test/boulder-test-config.json')
run('./cmd/boulder-sa', '--config test/boulder-test-config.json')
run('./cmd/boulder-ca', '--config test/boulder-test-config.json')
run('./cmd/boulder-va', '--config test/boulder-test-config.json')
run('./test/dns-test-srv')
def die(status):
global exit_status
# Set exit_status so cleanup handler knows what to report.
exit_status = status
sys.exit(exit_status)
def run_node_test():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(('localhost', 4300))
s.connect(('localhost', 4000))
except socket.error, e:
print("Cannot connect to WFE")
die(ExitStatus.Error)
@ -69,57 +48,46 @@ def run_node_test():
die(ExitStatus.Error)
if subprocess.Popen('''
node test.js --email foo@letsencrypt.org --agree true \
--domains foo.com --new-reg http://localhost:4300/acme/new-reg \
--domains foo.com --new-reg http://localhost:4000/acme/new-reg \
--certKey %s/key.pem --cert %s/cert.der
''' % (tempdir, tempdir), shell=True).wait() != 0:
print("\nIssuing failed")
die(ExitStatus.NodeFailure)
if subprocess.Popen('''
node revoke.js %s/cert.der %s/key.pem http://localhost:4300/acme/revoke-cert
node revoke.js %s/cert.der %s/key.pem http://localhost:4000/acme/revoke-cert
''' % (tempdir, tempdir), shell=True).wait() != 0:
print("\nRevoking failed")
die(ExitStatus.NodeFailure)
return 0
def run_client_tests():
root = os.environ.get("LETSENCRYPT_PATH")
assert root is not None, (
"Please set LETSENCRYPT_PATH env variable to point at "
"initialized (virtualenv) client repo root")
os.environ['SERVER'] = 'http://localhost:4300/acme/new-reg'
os.environ['SERVER'] = 'http://localhost:4000/acme/new-reg'
test_script_path = os.path.join(root, 'tests', 'boulder-integration.sh')
if subprocess.Popen(test_script_path, shell=True, cwd=root).wait() != 0:
die(ExitStatus.PythonFailure)
try:
start()
busted = []
for pinfo in processes:
if pinfo.proc.poll() is not None:
busted.append(pinfo)
if len(busted) != 0:
print "\n\nThese processes didn't start up successfully (check above for their output):"
for pinfo in busted:
print "\t'%s' exited with %d" % (pinfo.cmd, pinfo.proc.returncode)
sys.exit(1)
run_node_test()
run_client_tests()
except Exception as e:
exit_status = ExitStatus.Error
print e
finally:
for pinfo in processes:
if pinfo.proc.poll() is None:
pinfo.proc.kill()
else:
exit_status = 1
@atexit.register
def cleanup():
import shutil
shutil.rmtree(tempdir)
if exit_status == 0:
if exit_status == ExitStatus.OK:
print("\n\nSUCCESS")
else:
print("\n\nFAILURE")
sys.exit(exit_status)
exit_status = ExitStatus.OK
tempdir = tempfile.mkdtemp()
if not startservers.start():
die(ExitStatus.Error)
run_node_test()
run_client_tests()
if not startservers.check():
die(ExitStatus.Error)

View File

@ -172,5 +172,5 @@
"dnsTimeout": "10s"
},
"subscriberAgreementURL": "http://localhost:4300/terms"
"subscriberAgreementURL": "http://localhost:4001/terms/v1"
}

97
test/startservers.py Normal file
View File

@ -0,0 +1,97 @@
import atexit
import BaseHTTPServer
import os
import shutil
import signal
import subprocess
import tempfile
import threading
class ToSServerThread(threading.Thread):
class ToSHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write("Do What Ye Will (An it Harm None).\n")
def run(self):
BaseHTTPServer.HTTPServer(("localhost", 4001), self.ToSHandler).serve_forever()
config = os.environ.get('BOULDER_CONFIG')
if config is None:
config = 'test/boulder-config.json'
processes = []
tempdir = tempfile.mkdtemp()
def run(path):
binary = os.path.join(tempdir, os.path.basename(path))
buildcmd = 'GORACE="halt_on_error=1" go build -race -o %s ./%s' % (binary, path)
print(buildcmd)
subprocess.check_call(buildcmd, shell=True)
srvcmd = [binary, '--config', config]
p = subprocess.Popen(srvcmd)
p.cmd = srvcmd
print('started %s with pid %d' % (p.cmd, p.pid))
return p
def start():
"""Return True if everything builds and starts.
Give up and return False if anything fails to build, or dies at
startup. Anything that did start before this point can be cleaned
up explicitly by calling stop(), or automatically atexit.
"""
global processes
t = ToSServerThread()
t.daemon = True
t.start()
for prog in [
'cmd/boulder-wfe',
'cmd/boulder-ra',
'cmd/boulder-sa',
'cmd/boulder-ca',
'cmd/boulder-va',
'test/dns-test-srv']:
try:
processes.append(run(prog))
except Exception as e:
print(e)
return False
if not check():
# Don't keep building stuff if a server has already died.
return False
return True
def check():
"""Return true if all started processes are still alive.
Log about anything that died.
"""
global processes
busted = []
stillok = []
for p in processes:
if p.poll() is None:
stillok.append(p)
else:
busted.append(p)
if busted:
print "\n\nThese processes exited early (check above for their output):"
for p in busted:
print "\t'%s' with pid %d exited %d" % (p.cmd, p.pid, p.returncode)
processes = stillok
return not busted
@atexit.register
def stop():
for p in processes:
if p.poll() is None:
p.kill()
shutil.rmtree(tempdir)

View File

@ -9,6 +9,7 @@ import (
"crypto/sha256"
"crypto/subtle"
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"net"
@ -24,6 +25,12 @@ import (
"github.com/letsencrypt/boulder/policy"
)
const maxCNAME = 16 // Prevents infinite loops. Same limit as BIND.
// Returned by CheckCAARecords if it has to follow too many
// consecutive CNAME lookups.
var ErrTooManyCNAME = errors.New("too many CNAME/DNAME lookups")
// ValidationAuthorityImpl represents a VA
type ValidationAuthorityImpl struct {
RA core.RegistrationAuthority
@ -510,36 +517,50 @@ func newCAASet(CAAs []*dns.CAA) *CAASet {
}
func (va *ValidationAuthorityImpl) getCAASet(hostname string) (*CAASet, error) {
hostname = strings.TrimRight(hostname, ".")
splitDomain := strings.Split(hostname, ".")
// RFC 6844 CAA set query sequence, 'x.y.z.com' => ['x.y.z.com', 'y.z.com', 'z.com']
for i := range splitDomain {
queryDomain := strings.Join(splitDomain[i:], ".")
// Don't query a public suffix
if _, present := policy.PublicSuffixList[queryDomain]; present {
label := strings.TrimRight(hostname, ".")
cnames := 0
// See RFC 6844 "Certification Authority Processing" for pseudocode.
for {
if strings.IndexRune(label, '.') == -1 {
// Reached TLD
break
}
// Query CAA records for domain and its alias if it has a CNAME
for _, alias := range []bool{false, true} {
if alias {
target, _, err := va.DNSResolver.LookupCNAME(queryDomain)
if err != nil {
return nil, err
}
queryDomain = target
}
CAAs, _, err := va.DNSResolver.LookupCAA(queryDomain)
if err != nil {
return nil, err
}
if len(CAAs) > 0 {
return newCAASet(CAAs), nil
}
if _, present := policy.PublicSuffixList[label]; present {
break
}
CAAs, _, err := va.DNSResolver.LookupCAA(label)
if err != nil {
return nil, err
}
if len(CAAs) > 0 {
return newCAASet(CAAs), nil
}
cname, _, err := va.DNSResolver.LookupCNAME(label)
if err != nil {
return nil, err
}
dname, _, err := va.DNSResolver.LookupDNAME(label)
if err != nil {
return nil, err
}
if cname == "" && dname == "" {
// Try parent domain (note we confirmed
// earlier that label contains '.')
label = label[strings.IndexRune(label, '.')+1:]
continue
}
if cname != "" && dname != "" && cname != dname {
return nil, errors.New("both CNAME and DNAME exist for " + label)
}
if cname != "" {
label = cname
} else {
label = dname
}
if cnames++; cnames > maxCNAME {
return nil, ErrTooManyCNAME
}
}
// no CAA records found
return nil, nil
}

View File

@ -591,10 +591,23 @@ func TestCAAChecking(t *testing.T) {
CAATest{"reserved.com", true, false},
// Critical
CAATest{"critical.com", true, false},
CAATest{"nx.critical.com", true, false},
CAATest{"cname-critical.com", true, false},
CAATest{"nx.cname-critical.com", true, false},
// Good (absent)
CAATest{"absent.com", false, true},
CAATest{"cname-absent.com", false, true},
CAATest{"nx.cname-absent.com", false, true},
CAATest{"cname-nx.com", false, true},
CAATest{"example.co.uk", false, true},
// Good (present)
CAATest{"present.com", true, true},
CAATest{"cname-present.com", true, true},
CAATest{"cname2-present.com", true, true},
CAATest{"nx.cname2-present.com", true, true},
CAATest{"dname-present.com", true, true},
CAATest{"dname2cname.com", true, true},
// CNAME to critical
}
va := NewValidationAuthorityImpl(true)
@ -612,6 +625,20 @@ func TestCAAChecking(t *testing.T) {
test.AssertError(t, err, "servfail.com")
test.Assert(t, !present, "Present should be false")
test.Assert(t, !valid, "Valid should be false")
for _, name := range []string{
"www.caa-loop.com",
"a.cname-loop.com",
"a.dname-loop.com",
"cname-servfail.com",
"cname2servfail.com",
"dname-servfail.com",
"cname-and-dname.com",
"servfail.com",
} {
_, _, err = va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: name})
test.AssertError(t, err, name)
}
}
func TestDNSValidationFailure(t *testing.T) {

View File

@ -17,7 +17,6 @@ import (
"net/http"
"net/url"
"regexp"
"runtime"
"strconv"
"strings"
"time"
@ -220,7 +219,7 @@ func (wfe *WebFrontEndImpl) Handler() (http.Handler, error) {
wfe.HandleFunc(m, NewCertPath, wfe.NewCertificate, "POST")
wfe.HandleFunc(m, RegPath, wfe.Registration, "POST")
wfe.HandleFunc(m, AuthzPath, wfe.Authorization, "GET", "POST")
wfe.HandleFunc(m, CertPath, wfe.Certificate, "GET", "POST")
wfe.HandleFunc(m, CertPath, wfe.Certificate, "GET")
wfe.HandleFunc(m, RevokeCertPath, wfe.RevokeCertificate, "POST")
wfe.HandleFunc(m, TermsPath, wfe.Terms, "GET")
wfe.HandleFunc(m, IssuerPath, wfe.Issuer, "GET")
@ -911,6 +910,10 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
return
}
response.Header().Set("Content-Type", "application/json")
response.Header().Add("Link", link(wfe.NewAuthz, "next"))
if len(wfe.SubscriberAgreementURL) > 0 {
response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service"))
}
response.WriteHeader(http.StatusAccepted)
response.Write(jsonReply)
}
@ -942,9 +945,9 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
return
}
// Blank out ID and regID
switch request.Method {
case "GET":
// Blank out ID and regID
authz.ID = ""
authz.RegistrationID = 0
@ -974,54 +977,47 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
defer wfe.logRequestDetails(&logEvent)
path := request.URL.Path
switch request.Method {
case "GET":
// Certificate paths consist of the CertBase path, plus exactly sixteen hex
// digits.
if !strings.HasPrefix(path, CertPath) {
logEvent.Error = "Certificate not found"
wfe.sendError(response, logEvent.Error, path, http.StatusNotFound)
addNoCacheHeader(response)
return
}
serial := path[len(CertPath):]
if len(serial) != 16 || !allHex.Match([]byte(serial)) {
logEvent.Error = "Certificate not found"
wfe.sendError(response, logEvent.Error, serial, http.StatusNotFound)
addNoCacheHeader(response)
return
}
wfe.log.Debug(fmt.Sprintf("Requested certificate ID %s", serial))
logEvent.Extra["RequestedSerial"] = serial
cert, err := wfe.SA.GetCertificateByShortSerial(serial)
if err != nil {
logEvent.Error = err.Error()
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusConflict)
} else {
addNoCacheHeader(response)
wfe.sendError(response, "Certificate not found", err, http.StatusNotFound)
}
return
}
addCacheHeader(response, wfe.CertCacheDuration.Seconds())
// TODO Content negotiation
response.Header().Set("Content-Type", "application/pkix-cert")
response.Header().Add("Link", link(IssuerPath, "up"))
response.WriteHeader(http.StatusOK)
if _, err = response.Write(cert.DER); err != nil {
logEvent.Error = err.Error()
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
return
case "POST":
logEvent.Error = "Not yet supported"
wfe.sendError(response, logEvent.Error, "", http.StatusNotFound)
// Certificate paths consist of the CertBase path, plus exactly sixteen hex
// digits.
if !strings.HasPrefix(path, CertPath) {
logEvent.Error = "Certificate not found"
wfe.sendError(response, logEvent.Error, path, http.StatusNotFound)
addNoCacheHeader(response)
return
}
serial := path[len(CertPath):]
if len(serial) != 16 || !allHex.Match([]byte(serial)) {
logEvent.Error = "Certificate not found"
wfe.sendError(response, logEvent.Error, serial, http.StatusNotFound)
addNoCacheHeader(response)
return
}
wfe.log.Debug(fmt.Sprintf("Requested certificate ID %s", serial))
logEvent.Extra["RequestedSerial"] = serial
cert, err := wfe.SA.GetCertificateByShortSerial(serial)
if err != nil {
logEvent.Error = err.Error()
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusConflict)
} else {
addNoCacheHeader(response)
wfe.sendError(response, "Certificate not found", err, http.StatusNotFound)
}
return
}
addCacheHeader(response, wfe.CertCacheDuration.Seconds())
// TODO Content negotiation
response.Header().Set("Content-Type", "application/pkix-cert")
response.Header().Add("Link", link(IssuerPath, "up"))
response.WriteHeader(http.StatusOK)
if _, err = response.Write(cert.DER); err != nil {
logEvent.Error = err.Error()
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
return
}
// Terms is used by the client to obtain the current Terms of Service /
@ -1056,7 +1052,7 @@ func (wfe *WebFrontEndImpl) BuildID(response http.ResponseWriter, request *http.
response.Header().Set("Content-Type", "text/plain")
response.WriteHeader(http.StatusOK)
detailsString := fmt.Sprintf("Boulder=(%s %s) Golang=(%s) BuildHost=(%s)", core.GetBuildID(), core.GetBuildTime(), runtime.Version(), core.GetBuildHost())
detailsString := fmt.Sprintf("Boulder=(%s %s)", core.GetBuildID(), core.GetBuildTime())
if _, err := fmt.Fprintln(response, detailsString); err != nil {
logEvent.Error = err.Error()
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))

View File

@ -43,7 +43,7 @@ const (
{
"kty":"RSA",
"n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
"e":"AAEAAQ"
"e":"AQAB"
}`
test1KeyPrivatePEM = `
@ -79,7 +79,7 @@ eROL1ve1vmQF3kjrMPhhK2kr6qdWnTE5XlPllVSZFQenSTzj98AO
test2KeyPublicJSON = `{
"kty":"RSA",
"n":"qnARLrT7Xz4gRcKyLdydmCr-ey9OuPImX4X40thk3on26FkMznR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBrhR6uIoO4jAzJZR-ChzZuSDt7iHN-3xUVspu5XGwXU_MVJZshTwp4TaFx5elHIT_ObnTvTOU3Xhish07AbgZKmWsVbXh5s-CrIicU4OexJPgunWZ_YJJueOKmTvnLlTV4MzKR2oZlBKZ27S0-SfdV_QDx_ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY_2Uzi5eX0lTc7MPRwz6qR1kip-i59VcGcUQgqHV6Fyqw",
"e":"AAEAAQ"
"e":"AQAB"
}`
test2KeyPrivatePEM = `
@ -306,7 +306,7 @@ func makeBody(s string) io.ReadCloser {
}
func signRequest(t *testing.T, req string, nonceService *core.NonceService) string {
accountKeyJSON := []byte(`{"kty":"RSA","n":"z2NsNdHeqAiGdPP8KuxfQXat_uatOK9y12SyGpfKw1sfkizBIsNxERjNDke6Wp9MugN9srN3sr2TDkmQ-gK8lfWo0v1uG_QgzJb1vBdf_hH7aejgETRGLNJZOdaKDsyFnWq1WGJq36zsHcd0qhggTk6zVwqczSxdiWIAZzEakIUZ13KxXvoepYLY0Q-rEEQiuX71e4hvhfeJ4l7m_B-awn22UUVvo3kCqmaRlZT-36vmQhDGoBsoUo1KBEU44jfeK5PbNRk7vDJuH0B7qinr_jczHcvyD-2TtPzKaCioMtNh_VZbPNDaG67sYkQlC15-Ff3HPzKKJW2XvkVG91qMvQ","e":"AAEAAQ","d":"BhAmDbzBAbCeHbU0Xhzi_Ar4M0eTMOEQPnPXMSfW6bc0SRW938JO_-z1scEvFY8qsxV_C0Zr7XHVZsmHz4dc9BVmhiSan36XpuOS85jLWaY073e7dUVN9-l-ak53Ys9f6KZB_v-BmGB51rUKGB70ctWiMJ1C0EzHv0h6Moog-LCd_zo03uuZD5F5wtnPrAB3SEM3vRKeZHzm5eiGxNUsaCEzGDApMYgt6YkQuUlkJwD8Ky2CkAE6lLQSPwddAfPDhsCug-12SkSIKw1EepSHz86ZVfJEnvY-h9jHIdI57mR1v7NTCDcWqy6c6qIzxwh8n2X94QTbtWT3vGQ6HXM5AQ","p":"2uhvZwNS5i-PzeI9vGx89XbdsVmeNjVxjH08V3aRBVY0dzUzwVDYk3z7sqBIj6de53Lx6W1hjmhPIqAwqQgjIKH5Z3uUCinGguKkfGDL3KgLCzYL2UIvZMvTzr9NWLc0AHMZdee5utxWKCGnZBOqy1Rd4V-6QrqjEDBvanoqA60","q":"8odNkMEiriaDKmvwDv-vOOu3LaWbu03yB7VhABu-hK5Xx74bHcvDP2HuCwDGGJY2H-xKdMdUPs0HPwbfHMUicD2vIEUDj6uyrMMZHtbcZ3moh3-WESg3TaEaJ6vhwcWXWG7Wc46G-HbCChkuVenFYYkoi68BAAjloqEUl1JBT1E"}`)
accountKeyJSON := []byte(`{"kty":"RSA","n":"z2NsNdHeqAiGdPP8KuxfQXat_uatOK9y12SyGpfKw1sfkizBIsNxERjNDke6Wp9MugN9srN3sr2TDkmQ-gK8lfWo0v1uG_QgzJb1vBdf_hH7aejgETRGLNJZOdaKDsyFnWq1WGJq36zsHcd0qhggTk6zVwqczSxdiWIAZzEakIUZ13KxXvoepYLY0Q-rEEQiuX71e4hvhfeJ4l7m_B-awn22UUVvo3kCqmaRlZT-36vmQhDGoBsoUo1KBEU44jfeK5PbNRk7vDJuH0B7qinr_jczHcvyD-2TtPzKaCioMtNh_VZbPNDaG67sYkQlC15-Ff3HPzKKJW2XvkVG91qMvQ","e":"AQAB","d":"BhAmDbzBAbCeHbU0Xhzi_Ar4M0eTMOEQPnPXMSfW6bc0SRW938JO_-z1scEvFY8qsxV_C0Zr7XHVZsmHz4dc9BVmhiSan36XpuOS85jLWaY073e7dUVN9-l-ak53Ys9f6KZB_v-BmGB51rUKGB70ctWiMJ1C0EzHv0h6Moog-LCd_zo03uuZD5F5wtnPrAB3SEM3vRKeZHzm5eiGxNUsaCEzGDApMYgt6YkQuUlkJwD8Ky2CkAE6lLQSPwddAfPDhsCug-12SkSIKw1EepSHz86ZVfJEnvY-h9jHIdI57mR1v7NTCDcWqy6c6qIzxwh8n2X94QTbtWT3vGQ6HXM5AQ","p":"2uhvZwNS5i-PzeI9vGx89XbdsVmeNjVxjH08V3aRBVY0dzUzwVDYk3z7sqBIj6de53Lx6W1hjmhPIqAwqQgjIKH5Z3uUCinGguKkfGDL3KgLCzYL2UIvZMvTzr9NWLc0AHMZdee5utxWKCGnZBOqy1Rd4V-6QrqjEDBvanoqA60","q":"8odNkMEiriaDKmvwDv-vOOu3LaWbu03yB7VhABu-hK5Xx74bHcvDP2HuCwDGGJY2H-xKdMdUPs0HPwbfHMUicD2vIEUDj6uyrMMZHtbcZ3moh3-WESg3TaEaJ6vhwcWXWG7Wc46G-HbCChkuVenFYYkoi68BAAjloqEUl1JBT1E"}`)
var accountKey jose.JsonWebKey
err := json.Unmarshal(accountKeyJSON, &accountKey)
test.AssertNotError(t, err, "Failed to unmarshal key")
@ -425,7 +425,7 @@ func TestStandardHeaders(t *testing.T) {
{wfe.NewAuthz, []string{"POST"}},
{wfe.AuthzBase, []string{"GET", "POST"}},
{wfe.NewCert, []string{"POST"}},
{wfe.CertBase, []string{"GET", "POST"}},
{wfe.CertBase, []string{"GET"}},
{wfe.SubscriberAgreementURL, []string{"GET"}},
}
@ -788,7 +788,7 @@ func TestNewRegistration(t *testing.T) {
Body: makeBody(result.FullSerialize()),
})
test.AssertEquals(t, responseWriter.Body.String(), `{"id":0,"key":{"kty":"RSA","n":"qnARLrT7Xz4gRcKyLdydmCr-ey9OuPImX4X40thk3on26FkMznR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBrhR6uIoO4jAzJZR-ChzZuSDt7iHN-3xUVspu5XGwXU_MVJZshTwp4TaFx5elHIT_ObnTvTOU3Xhish07AbgZKmWsVbXh5s-CrIicU4OexJPgunWZ_YJJueOKmTvnLlTV4MzKR2oZlBKZ27S0-SfdV_QDx_ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY_2Uzi5eX0lTc7MPRwz6qR1kip-i59VcGcUQgqHV6Fyqw","e":"AAEAAQ"},"recoveryToken":"","contact":["tel:123456789"],"agreement":"http://example.invalid/terms"}`)
test.AssertEquals(t, responseWriter.Body.String(), `{"id":0,"key":{"kty":"RSA","n":"qnARLrT7Xz4gRcKyLdydmCr-ey9OuPImX4X40thk3on26FkMznR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBrhR6uIoO4jAzJZR-ChzZuSDt7iHN-3xUVspu5XGwXU_MVJZshTwp4TaFx5elHIT_ObnTvTOU3Xhish07AbgZKmWsVbXh5s-CrIicU4OexJPgunWZ_YJJueOKmTvnLlTV4MzKR2oZlBKZ27S0-SfdV_QDx_ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY_2Uzi5eX0lTc7MPRwz6qR1kip-i59VcGcUQgqHV6Fyqw","e":"AQAB"},"recoveryToken":"","contact":["tel:123456789"],"agreement":"http://example.invalid/terms"}`)
var reg core.Registration
err = json.Unmarshal([]byte(responseWriter.Body.String()), &reg)
test.AssertNotError(t, err, "Couldn't unmarshal returned registration object")
@ -799,6 +799,10 @@ func TestNewRegistration(t *testing.T) {
test.AssertEquals(
t, responseWriter.Header().Get("Location"),
"/acme/reg/0")
links := responseWriter.Header()["Link"]
test.AssertEquals(t, contains(links, "</acme/new-authz>;rel=\"next\""), true)
test.AssertEquals(t, contains(links, "<"+agreementURL+">;rel=\"terms-of-service\""), true)
test.AssertEquals(
t, responseWriter.Header().Get("Link"),
"</acme/new-authz>;rel=\"next\"")
@ -1019,6 +1023,15 @@ func TestAuthorization(t *testing.T) {
test.AssertNotError(t, err, "Couldn't unmarshal returned authorization object")
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func TestRegistration(t *testing.T) {
wfe := setupWFE(t)
mux, err := wfe.Handler()
@ -1121,6 +1134,10 @@ func TestRegistration(t *testing.T) {
URL: path,
})
test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error")
links := responseWriter.Header()["Link"]
test.AssertEquals(t, contains(links, "</acme/new-authz>;rel=\"next\""), true)
test.AssertEquals(t, contains(links, "<"+agreementURL+">;rel=\"terms-of-service\""), true)
responseWriter.Body.Reset()
}
@ -1246,5 +1263,5 @@ func TestLogCsrPem(t *testing.T) {
test.Assert(t, len(matches) == 1,
"Incorrect number of certificate request log entries")
test.AssertEquals(t, matches[0].Priority, syslog.LOG_NOTICE)
test.AssertEquals(t, matches[0].Message, `[AUDIT] Certificate request JSON={"RemoteAddr":"12.34.98.76","CsrBase64":"MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycX3ca+fViOuRWF38mssORISFxbJvspDfhPGRBZDxJ63NIqQzupB+6dp48xkcX7Z/KDaRJStcpJT2S0u33moNT4FHLklQBETLhExDk66cmlz6Xibp3LGZAwhWuec7wJoEwIgY8oq4rxihIyGq7HVIJoq9DqZGrUgfZMDeEJqbphukQOaXGEop7mD+eeu8+z5EVkB1LiJ6Yej6R8MAhVPHzG5fyOu6YVo6vY6QgwjRLfZHNj5XthxgPIEETZlUbiSoI6J19GYHvLURBTy5Ys54lYAPIGfNwcIBAH4gtH9FrYcDY68R22rp4iuxdvkf03ZWiT0F2W1y7/C9B2jayTzvQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAHd6Do9DIZ2hvdt1GwBXYjsqprZidT/DYOMfYcK17KlvdkFT58XrBH88ulLZ72NXEpiFMeTyzfs3XEyGq/Bbe7TBGVYZabUEh+LOskYwhgcOuThVN7tHnH5rhN+gb7cEdysjTb1QL+vOUwYgV75CB6PE5JVYK+cQsMIVvo0Kz4TpNgjJnWzbcH7h0mtvub+fCv92vBPjvYq8gUDLNrok6rbg05tdOJkXsF2G/W+Q6sf2Fvx0bK5JeH4an7P7cXF9VG9nd4sRt5zd+L3IcyvHVKxNhIJXZVH0AOqh/1YrKI9R0QKQiZCEy0xN1okPlcaIVaFhb7IKAHPxTI3r5f72LXY=","Registration":{"id":789,"key":{"kty":"RSA","n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ","e":"AAEAAQ"},"recoveryToken":"","agreement":"http://example.invalid/terms"}}`)
test.AssertEquals(t, matches[0].Message, `[AUDIT] Certificate request JSON={"RemoteAddr":"12.34.98.76","CsrBase64":"MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycX3ca+fViOuRWF38mssORISFxbJvspDfhPGRBZDxJ63NIqQzupB+6dp48xkcX7Z/KDaRJStcpJT2S0u33moNT4FHLklQBETLhExDk66cmlz6Xibp3LGZAwhWuec7wJoEwIgY8oq4rxihIyGq7HVIJoq9DqZGrUgfZMDeEJqbphukQOaXGEop7mD+eeu8+z5EVkB1LiJ6Yej6R8MAhVPHzG5fyOu6YVo6vY6QgwjRLfZHNj5XthxgPIEETZlUbiSoI6J19GYHvLURBTy5Ys54lYAPIGfNwcIBAH4gtH9FrYcDY68R22rp4iuxdvkf03ZWiT0F2W1y7/C9B2jayTzvQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAHd6Do9DIZ2hvdt1GwBXYjsqprZidT/DYOMfYcK17KlvdkFT58XrBH88ulLZ72NXEpiFMeTyzfs3XEyGq/Bbe7TBGVYZabUEh+LOskYwhgcOuThVN7tHnH5rhN+gb7cEdysjTb1QL+vOUwYgV75CB6PE5JVYK+cQsMIVvo0Kz4TpNgjJnWzbcH7h0mtvub+fCv92vBPjvYq8gUDLNrok6rbg05tdOJkXsF2G/W+Q6sf2Fvx0bK5JeH4an7P7cXF9VG9nd4sRt5zd+L3IcyvHVKxNhIJXZVH0AOqh/1YrKI9R0QKQiZCEy0xN1okPlcaIVaFhb7IKAHPxTI3r5f72LXY=","Registration":{"id":789,"key":{"kty":"RSA","n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ","e":"AQAB"},"recoveryToken":"","agreement":"http://example.invalid/terms"}}`)
}