github.com/trustbloc/kms-go@v1.1.2/kms/webkms/crypto_box.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package webkms 8 9 import ( 10 "fmt" 11 "io" 12 "io/ioutil" 13 "time" 14 15 "golang.org/x/crypto/nacl/box" 16 17 "github.com/trustbloc/kms-go/util/cryptoutil" 18 19 "github.com/trustbloc/kms-go/spi/kms" 20 21 "github.com/trustbloc/kms-go/doc/util/jwkkid" 22 ) 23 24 // TODO move CryptoBox out of webkms package. 25 // this currently only sits inside webkms so it can execute crypto with private keys remotely. See issue #511 26 // TODO delete this file and its corresponding test file when LegacyPacker is removed. 27 28 type easyReq struct { 29 Payload []byte `json:"payload"` 30 Nonce []byte `json:"nonce"` 31 TheirPub []byte `json:"their_pub"` 32 } 33 34 type easyResp struct { 35 Ciphertext []byte `json:"ciphertext"` 36 } 37 38 type easyOpenReq struct { 39 Ciphertext []byte `json:"ciphertext"` 40 Nonce []byte `json:"nonce"` 41 TheirPub []byte `json:"their_pub"` 42 MyPub []byte `json:"my_pub"` 43 } 44 45 type easyOpenResp struct { 46 Plaintext []byte `json:"plaintext"` 47 } 48 49 type sealOpenReq struct { 50 Ciphertext []byte `json:"ciphertext"` 51 MyPub []byte `json:"my_pub"` 52 } 53 54 type sealOpenResp struct { 55 Plaintext []byte `json:"plaintext"` 56 } 57 58 const ( 59 wrapURL = "/wrap" 60 unwrapURL = "/unwrap" 61 ) 62 63 // CryptoBox provides an elliptic-curve-based authenticated encryption scheme executed on a remote key server 64 // 65 // Payloads are encrypted using symmetric encryption (XChacha20Poly1305) 66 // using a shared key derived from a shared secret created by 67 // 68 // Curve25519 Elliptic Curve Diffie-Hellman key exchange. 69 // 70 // CryptoBox is created by a remote KMS, and remotely reads secret keys from the KMS 71 // 72 // for encryption/decryption, so clients do not need to see 73 // the secrets themselves. 74 type CryptoBox struct { 75 km *RemoteKMS 76 } 77 78 // NewCryptoBox creates a CryptoBox which provides remote crypto box encryption using the given KMS's key. 79 func NewCryptoBox(w kms.KeyManager) (*CryptoBox, error) { 80 lkms, ok := w.(*RemoteKMS) 81 if !ok { 82 return nil, fmt.Errorf("cannot use parameter argument as KMS") 83 } 84 85 return &CryptoBox{km: lkms}, nil 86 } 87 88 // Easy remotely seals a message with a provided nonce 89 // theirPub is used as a public key, while myPub is used to identify the private key that should be used. 90 func (b *CryptoBox) Easy(payload, nonce, theirPub []byte, myKID string) ([]byte, error) { 91 easyStart := time.Now() 92 keyURL := b.km.buildKIDURL(myKID) 93 94 destination := keyURL + wrapURL 95 96 httpReqJSON := &easyReq{ 97 Payload: payload, 98 Nonce: nonce, 99 TheirPub: theirPub, 100 } 101 102 marshaledReq, err := b.km.marshalFunc(httpReqJSON) 103 if err != nil { 104 return nil, fmt.Errorf("failed to marshal Easy request [%s, %w]", destination, err) 105 } 106 107 resp, err := b.km.postHTTPRequest(destination, marshaledReq) 108 if err != nil { 109 return nil, fmt.Errorf("posting Easy request failed [%s, %w]", destination, err) 110 } 111 112 // handle response 113 defer closeResponseBody(resp.Body, "Easy") 114 115 err = checkError(resp) 116 if err != nil { 117 return nil, err 118 } 119 120 respBody, err := ioutil.ReadAll(resp.Body) 121 if err != nil { 122 return nil, fmt.Errorf("read ciphertext response for Easy failed [%s, %w]", destination, err) 123 } 124 125 httpResp := &easyResp{} 126 127 err = b.km.unmarshalFunc(respBody, httpResp) 128 if err != nil { 129 return nil, fmt.Errorf("unmarshal ciphertext for Easy failed [%s, %w]", destination, err) 130 } 131 132 debugLogger.Printf("overall Easy duration: %s", time.Since(easyStart)) 133 134 return httpResp.Ciphertext, nil 135 } 136 137 // EasyOpen remotely unseals a message sealed with Easy, where the nonce is provided. 138 // theirPub is the public key used to decrypt directly, while myPub is used to identify the private key to be used. 139 func (b *CryptoBox) EasyOpen(cipherText, nonce, theirPub, myPub []byte) ([]byte, error) { 140 easyOpenStart := time.Now() 141 142 destination, err := b.buildUnwrapURL(myPub) 143 if err != nil { 144 return nil, err 145 } 146 147 httpReqJSON := &easyOpenReq{ 148 Ciphertext: cipherText, 149 Nonce: nonce, 150 TheirPub: theirPub, 151 MyPub: myPub, 152 } 153 154 marshaledReq, err := b.km.marshalFunc(httpReqJSON) 155 if err != nil { 156 return nil, fmt.Errorf("failed to marshal EasyOpen request [%s, %w]", destination, err) 157 } 158 159 resp, err := b.km.postHTTPRequest(destination, marshaledReq) 160 if err != nil { 161 return nil, fmt.Errorf("posting EasyOpen failed [%s, %w]", destination, err) 162 } 163 164 // handle response 165 defer closeResponseBody(resp.Body, "EasyOpen") 166 167 err = checkError(resp) 168 if err != nil { 169 return nil, err 170 } 171 172 respBody, err := ioutil.ReadAll(resp.Body) 173 if err != nil { 174 return nil, fmt.Errorf("read plaintext response for EasyOpen failed [%s, %w]", destination, err) 175 } 176 177 httpResp := &easyOpenResp{} 178 179 err = b.km.unmarshalFunc(respBody, httpResp) 180 if err != nil { 181 return nil, fmt.Errorf("unmarshal plaintext for EasyOpen failed [%s, %w]", destination, err) 182 } 183 184 debugLogger.Printf("overall easyOpen duration: %s", time.Since(easyOpenStart)) 185 186 return httpResp.Plaintext, nil 187 } 188 189 // Seal seals a payload using the equivalent of libsodium box_seal. This is an exact copy of localkms's CryptoBox.Seal() 190 // as no private key is involved and therefore it is not necessary to call the key server. 191 // 192 // Generates an ephemeral keypair to use for the sender, and includes 193 // the ephemeral sender public key in the message. 194 func (b *CryptoBox) Seal(payload, theirEncPub []byte, randSource io.Reader) ([]byte, error) { 195 sealStart := time.Now() 196 // generate ephemeral curve25519 asymmetric keys 197 epk, esk, err := box.GenerateKey(randSource) 198 if err != nil { 199 return nil, err 200 } 201 202 var recPubBytes [cryptoutil.Curve25519KeySize]byte 203 204 copy(recPubBytes[:], theirEncPub) 205 206 nonce, err := cryptoutil.Nonce(epk[:], theirEncPub) 207 if err != nil { 208 return nil, err 209 } 210 211 // now seal the msg with the ephemeral key, nonce and recPub (which is recipient's publicKey) 212 ret := box.Seal(epk[:], payload, nonce, &recPubBytes, esk) 213 214 debugLogger.Printf("overall Seal (non remote call) duration: %s", time.Since(sealStart)) 215 216 return ret, nil 217 } 218 219 // SealOpen remotely decrypts a payload encrypted with Seal. 220 // 221 // Reads the ephemeral sender public key, prepended to a properly-formatted message, 222 // and uses that along with the recipient private key corresponding to myPub to decrypt the message. 223 func (b *CryptoBox) SealOpen(cipherText, myPub []byte) ([]byte, error) { 224 sealOpenStart := time.Now() 225 226 destination, err := b.buildUnwrapURL(myPub) 227 if err != nil { 228 return nil, err 229 } 230 231 httpReqJSON := &sealOpenReq{ 232 Ciphertext: cipherText, 233 MyPub: myPub, 234 } 235 236 marshaledReq, err := b.km.marshalFunc(httpReqJSON) 237 if err != nil { 238 return nil, fmt.Errorf("failed to marshal SealOpen request [%s, %w]", destination, err) 239 } 240 241 resp, err := b.km.postHTTPRequest(destination, marshaledReq) 242 if err != nil { 243 return nil, fmt.Errorf("posting SealOpen failed [%s, %w]", destination, err) 244 } 245 246 // handle response 247 defer closeResponseBody(resp.Body, "SealOpen") 248 249 err = checkError(resp) 250 if err != nil { 251 return nil, err 252 } 253 254 respBody, err := ioutil.ReadAll(resp.Body) 255 if err != nil { 256 return nil, fmt.Errorf("read plaintext response for SealOpen failed [%s, %w]", destination, err) 257 } 258 259 httpResp := &sealOpenResp{} 260 261 err = b.km.unmarshalFunc(respBody, httpResp) 262 if err != nil { 263 return nil, fmt.Errorf("unmarshal plaintext for SealOpen failed [%s, %w]", destination, err) 264 } 265 266 debugLogger.Printf("overall SealOpen duration: %s", time.Since(sealOpenStart)) 267 268 return httpResp.Plaintext, nil 269 } 270 271 func (b *CryptoBox) buildUnwrapURL(myPub []byte) (string, error) { 272 // remote kms requires keyID in the keyURL for unwrapURL. 273 kid, err := jwkkid.CreateKID(myPub, kms.ED25519Type) 274 if err != nil { 275 return "", err 276 } 277 278 keyURL := b.km.buildKIDURL(kid) 279 280 return keyURL + unwrapURL, nil 281 }