a685e3fc98
Vndr has a simpler configuration and allows pointing to forked packages. Additionally other docker projects are now using vndr making vendoring in distribution more consistent. Updates letsencrypt to use fork. No longer uses sub-vendored packages. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
196 lines
5.2 KiB
Go
196 lines
5.2 KiB
Go
/*-
|
|
* 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
|
|
}
|