Skip to content

Commit

Permalink
add HMAC API and use KMS secret key crypto
Browse files Browse the repository at this point in the history
This commit does two closely related things:
 - It replaces the existing AES/ChaCha20 secret key
   implementation with a newer one based on the MinIO KMS
   implementation.
 - Adds an HMAC API such that clients can compute a HMAC
   checksum over a message.

The new secret key implementation is located in the `internal/crypto`
package. It is fully backwards compatible with the previous implementation
that resided in `internal/key` and is removed by this PR.
In particular, all ciphertexts produced with existing keys can be decrypted
with the added implementation.

The new implementation cleans up some design issues in the previous
AES-256 and ChaCha20 ciphertext generation:
 - Now, ciphertexts have no structure or format. They only consist of
   the AEAD ciphertext (encrypted plaintext + auth. tag) and the 224 bit (28 byte)
   nonce.
 - Forward and backward compatibility information is no longer embedded into the
   ciphertext but instead into the key. For example, if we ever want to update the
   AES scheme, e.g. from AES-GCM or AES-SIV, (which we never did btw) we just create
   a new secret key type.

This has also the side effect of ciphertexts getting significantly smaller (half the size).
Since each MinIO object embeds at least one ciphertext in its metadata, this can give be
a small perf. improvement when listing a lot of encrypted objects.

***

The new HMAC API allows clients to compute a deterministic keyed checkusm (MAC) over some data
without having direct access to the HMAC key. Clients may use this to verify that messages are
authentic or generate the same pseudo-random secret on startup.

Signed-off-by: Andreas Auernhammer <[email protected]>
  • Loading branch information
aead committed Jan 12, 2024
1 parent bd277c4 commit d6ead91
Show file tree
Hide file tree
Showing 17 changed files with 1,438 additions and 684 deletions.
1 change: 1 addition & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
PathKeyGenerate = "/v1/key/generate/"
PathKeyEncrypt = "/v1/key/encrypt/"
PathKeyDecrypt = "/v1/key/decrypt/"
PathKeyHMAC = "/v1/key/hmac/"

PathPolicyDescribe = "/v1/policy/describe/"
PathPolicyRead = "/v1/policy/read/"
Expand Down
5 changes: 5 additions & 0 deletions internal/api/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ type DecryptKeyRequest struct {
Ciphertext []byte `json:"ciphertext"`
Context []byte `json:"context"` // optional
}

// HMACRequest is the request sent by clients when calling the HMAC API.
type HMACRequest struct {
Message []byte `json:"message"`
}
5 changes: 5 additions & 0 deletions internal/api/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ type DecryptKeyResponse struct {
Plaintext []byte `json:"plaintext"`
}

// HMACResponse is the response sent to clients by the HMAC API.
type HMACResponse struct {
Sum []byte `json:"hmac"`
}

// ReadPolicyResponse is the response sent to clients by the ReadPolicy API.
type ReadPolicyResponse struct {
Name string `json:"name"`
Expand Down
69 changes: 28 additions & 41 deletions internal/key/ciphertext.go → internal/crypto/ciphertext.go
Original file line number Diff line number Diff line change
@@ -1,43 +1,46 @@
// Copyright 2022 - MinIO, Inc. All rights reserved.
// Use of this source code is governed by the AGPLv3
// license that can be found in the LICENSE file.

package key
package crypto

import (
"encoding/json"
"slices"

"github.com/minio/kes-go"
"github.com/tinylib/msgp/msgp"
)

// decodeCiphertext parses the given bytes as
// ciphertext. If it fails to unmarshal the
// given bytes, decodeCiphertext returns
// ErrDecrypt.
func decodeCiphertext(bytes []byte) (ciphertext, error) {
if len(bytes) == 0 {
return ciphertext{}, kes.ErrDecrypt
// parseCiphertext parses and converts a ciphertext into
// the format expected by a SecretKey.
//
// Previous implementations of a SecretKey produced a structured
// ciphertext. parseCiphertext converts all previously generated
// formats into the one that SecretKey.Decrypt expects.
func parseCiphertext(b []byte) []byte {
if len(b) == 0 {
return b
}

var c ciphertext
switch bytes[0] {
switch b[0] {
case 0x95: // msgp first byte
if err := c.UnmarshalBinary(bytes); err != nil {
return ciphertext{}, kes.ErrDecrypt
if err := c.UnmarshalBinary(b); err != nil {
return b
}

b = b[:0]
b = append(b, c.Bytes...)
b = append(b, c.IV...)
b = append(b, c.Nonce...)
case 0x7b: // JSON first byte
if err := c.UnmarshalJSON(bytes); err != nil {
return ciphertext{}, kes.ErrDecrypt
}
default:
if err := c.UnmarshalBinary(bytes); err != nil {
if err = c.UnmarshalJSON(bytes); err != nil {
return ciphertext{}, kes.ErrDecrypt
}
if err := c.UnmarshalJSON(b); err != nil {
return b
}

b = b[:0]
b = append(b, c.Bytes...)
b = append(b, c.IV...)
b = append(b, c.Nonce...)
}
return c, nil
return b
}

// ciphertext is a structure that contains the encrypted
Expand All @@ -51,22 +54,6 @@ type ciphertext struct {
Bytes []byte
}

// MarshalBinary returns the ciphertext's binary representation.
func (c *ciphertext) MarshalBinary() ([]byte, error) {
// We encode a ciphertext simply as message-pack
// flat array.
const Items = 5

var b []byte
b = msgp.AppendArrayHeader(b, Items)
b = msgp.AppendString(b, c.Algorithm.String())
b = msgp.AppendString(b, c.ID)
b = msgp.AppendBytes(b, c.IV)
b = msgp.AppendBytes(b, c.Nonce)
b = msgp.AppendBytes(b, c.Bytes)
return b, nil
}

// UnmarshalBinary parses b as binary-encoded ciphertext.
func (c *ciphertext) UnmarshalBinary(b []byte) error {
const (
Expand Down Expand Up @@ -117,7 +104,7 @@ func (c *ciphertext) UnmarshalBinary(b []byte) error {
c.ID = id
c.IV = iv[:]
c.Nonce = nonce[:]
c.Bytes = clone(bytes...)
c.Bytes = slices.Clone(bytes)
return nil
}

Expand Down
Loading

0 comments on commit d6ead91

Please sign in to comment.