@ -34,6 +34,10 @@ import (
xnet "github.com/minio/minio/pkg/net"
)
// ErrKESKeyNotFound is the error returned a KES server
// when a master key does not exist.
var ErrKESKeyNotFound = NewKESError ( http . StatusNotFound , "key does not exist" )
// KesConfig contains the configuration required
// to initialize and connect to a kes server.
type KesConfig struct {
@ -154,6 +158,12 @@ func (kes *kesService) GenerateKey(keyID string, ctx Context) (key [32]byte, sea
var plainKey [ ] byte
plainKey , sealedKey , err = kes . client . GenerateDataKey ( keyID , context . Bytes ( ) )
if err == ErrKESKeyNotFound { // Try to create the key if it does not exist.
if err = kes . client . CreateKey ( keyID ) ; err != nil {
return key , nil , err
}
plainKey , sealedKey , err = kes . client . GenerateDataKey ( keyID , context . Bytes ( ) )
}
if err != nil {
return key , nil , err
}
@ -214,16 +224,19 @@ type kesClient struct {
httpClient http . Client
}
// Response KES response struct
type response struct {
Plaintext [ ] byte ` json:"plaintext" `
Ciphertext [ ] byte ` json:"ciphertext,omitempty" `
}
// Request KES request struct
type request struct {
Ciphertext [ ] byte ` json:"ciphertext,omitempty" `
Context [ ] byte ` json:"context" `
// CreateKey tries to create a new cryptographic key with
// the specified name.
//
// The key will be generated by the server. The client
// application does not have the cryptographic key at
// any point in time.
func ( c * kesClient ) CreateKey ( name string ) error {
url := fmt . Sprintf ( "%s/v1/key/create/%s" , c . addr , url . PathEscape ( name ) )
_ , err := c . postRetry ( url , nil , 0 ) // No request body and no response expected
if err != nil {
return err
}
return nil
}
// GenerateDataKey requests a new data key from the KES server.
@ -235,48 +248,155 @@ type request struct {
// such that you have to provide the same context when decrypting
// the data key.
func ( c * kesClient ) GenerateDataKey ( name string , context [ ] byte ) ( [ ] byte , [ ] byte , error ) {
body , err := json . Marshal ( request {
type Request struct {
Context [ ] byte ` json:"context" `
}
type Response struct {
Plaintext [ ] byte ` json:"plaintext" `
Ciphertext [ ] byte ` json:"ciphertext" `
}
body , err := json . Marshal ( Request {
Context : context ,
} )
if err != nil {
return nil , nil , err
}
url := fmt . Sprintf ( "%s/v1/key/generate/%s" , c . addr , url . PathEscape ( name ) )
const limit = 1 << 20 // A plaintext/ciphertext key pair will never be larger than 1 MB
url := fmt . Sprintf ( "%s/v1/key/generate/%s" , c . addr , url . PathEscape ( name ) )
resp , err := c . postRetry ( url , bytes . NewReader ( body ) , limit )
if err != nil {
return nil , nil , err
}
return resp . Plaintext , resp . Ciphertext , nil
var response Response
if err = json . NewDecoder ( resp ) . Decode ( & response ) ; err != nil {
return nil , nil , err
}
return response . Plaintext , response . Ciphertext , nil
}
func ( c * kesClient ) post ( url string , body io . Reader , limit int64 ) ( * response , error ) {
resp , err := c . httpClient . Post ( url , "application/json" , body )
// GenerateDataKey decrypts an encrypted data key with the key
// specified by name by talking to the KES server.
// On success, the KES server will respond with the plaintext key.
//
// The optional context must match the value you provided when
// generating the data key.
func ( c * kesClient ) DecryptDataKey ( name string , ciphertext , context [ ] byte ) ( [ ] byte , error ) {
type Request struct {
Ciphertext [ ] byte ` json:"ciphertext" `
Context [ ] byte ` json:"context,omitempty" `
}
type Response struct {
Plaintext [ ] byte ` json:"plaintext" `
}
body , err := json . Marshal ( Request {
Ciphertext : ciphertext ,
Context : context ,
} )
if err != nil {
return nil , err
}
const limit = 1 << 20 // A data key will never be larger than 1 MiB
url := fmt . Sprintf ( "%s/v1/key/decrypt/%s" , c . addr , url . PathEscape ( name ) )
resp , err := c . postRetry ( url , bytes . NewReader ( body ) , limit )
if err != nil {
return nil , err
}
var response Response
if err = json . NewDecoder ( resp ) . Decode ( & response ) ; err != nil {
return nil , err
}
return response . Plaintext , nil
}
// NewKESError returns a new KES API error with the given
// HTTP status code and error message.
//
// Two errors with the same status code and
// error message are equal:
// e1 == e2 // true.
func NewKESError ( code int , text string ) error {
return kesError {
code : code ,
message : text ,
}
}
type kesError struct {
code int
message string
}
// Status returns the HTTP status code of the error.
func ( e kesError ) Status ( ) int { return e . code }
// Status returns the error message of the error.
func ( e kesError ) Error ( ) string { return e . message }
func parseErrorResponse ( resp * http . Response ) error {
if resp == nil || resp . StatusCode < 400 {
return nil
}
if resp . Body == nil {
return NewKESError ( resp . StatusCode , "" )
}
defer resp . Body . Close ( )
const MaxBodySize = 1 << 20
var size = resp . ContentLength
if size < 0 || size > MaxBodySize {
size = MaxBodySize
}
contentType := strings . TrimSpace ( resp . Header . Get ( "Content-Type" ) )
if strings . HasPrefix ( contentType , "application/json" ) {
type Response struct {
Message string ` json:"message" `
}
var response Response
if err := json . NewDecoder ( io . LimitReader ( resp . Body , size ) ) . Decode ( & response ) ; err != nil {
return err
}
return NewKESError ( resp . StatusCode , response . Message )
}
var sb strings . Builder
if _ , err := io . Copy ( & sb , io . LimitReader ( resp . Body , size ) ) ; err != nil {
return err
}
return NewKESError ( resp . StatusCode , sb . String ( ) )
}
func ( c * kesClient ) post ( url string , body io . Reader , limit int64 ) ( io . Reader , error ) {
resp , err := c . httpClient . Post ( url , "application/json" , body )
if err != nil {
return nil , err
}
// Drain the entire body to make sure we have re-use connections
defer xhttp . DrainBody ( resp . Body )
if resp . StatusCode != http . StatusOK {
return nil , c . parseErrorResponse ( resp )
return nil , parseErrorResponse ( resp )
}
response := & response { }
if err = json . NewDecoder ( io . LimitReader ( resp . Body , limit ) ) . Decode ( response ) ; err != nil {
// We have to copy the response body due to draining.
var respBody bytes . Buffer
if _ , err = io . Copy ( & respBody , io . LimitReader ( resp . Body , limit ) ) ; err != nil {
return nil , err
}
return response , nil
return & respBody , nil
}
func ( c * kesClient ) postRetry ( url string , body io . ReadSeeker , limit int64 ) ( * response , error ) {
func ( c * kesClient ) postRetry ( url string , body io . ReadSeeker , limit int64 ) ( io . Reader , error ) {
for i := 0 ; ; i ++ {
body . Seek ( 0 , io . SeekStart ) // seek to the beginning of the body.
if body != nil {
body . Seek ( 0 , io . SeekStart ) // seek to the beginning of the body.
}
response , err := c . post ( url , body , limit )
if err == nil {
return response , nil
@ -296,45 +416,6 @@ func (c *kesClient) postRetry(url string, body io.ReadSeeker, limit int64) (*res
}
}
// GenerateDataKey decrypts an encrypted data key with the key
// specified by name by talking to the KES server.
// On success, the KES server will respond with the plaintext key.
//
// The optional context must match the value you provided when
// generating the data key.
func ( c * kesClient ) DecryptDataKey ( name string , ciphertext , context [ ] byte ) ( [ ] byte , error ) {
body , err := json . Marshal ( request {
Ciphertext : ciphertext ,
Context : context ,
} )
if err != nil {
return nil , err
}
url := fmt . Sprintf ( "%s/v1/key/decrypt/%s" , c . addr , url . PathEscape ( name ) )
const limit = 32 * 1024 // A data key will never be larger than 32 KB
resp , err := c . postRetry ( url , bytes . NewReader ( body ) , limit )
if err != nil {
return nil , err
}
return resp . Plaintext , nil
}
func ( c * kesClient ) parseErrorResponse ( resp * http . Response ) error {
if resp . Body == nil {
return Errorf ( "%s: no body" , http . StatusText ( resp . StatusCode ) )
}
const limit = 32 * 1024 // A (valid) error response will not be greater than 32 KB
var errMsg strings . Builder
if _ , err := io . Copy ( & errMsg , io . LimitReader ( resp . Body , limit ) ) ; err != nil {
return Errorf ( "%s: %s" , http . StatusText ( resp . StatusCode ) , err )
}
return Errorf ( "%s: %s" , http . StatusText ( resp . StatusCode ) , errMsg . String ( ) )
}
// loadCACertificates returns a new CertPool
// that contains all system root CA certificates
// and any PEM-encoded certificate(s) found at