github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/signencrypt/codec.go (about) 1 // This is a construction for encrypting and signing a message, using a 2 // symmetric encryption key and a signing keypair, in a way that supports safe 3 // streaming decryption. We need this for chat attachments because we've chosen 4 // to use signing keys for authenticity in chat, and we don't want one 5 // participant to be able to modify another's attachment, even with an evil 6 // server's help. It's *almost* enough that we record the hash of the 7 // attachment along with the symmetric key used to encrypt it, but that by 8 // itself doesn't allow safe streaming decryption. Instead, we use this 9 // construction to sign each chunk of the attachment as we encrypt it. (Note 10 // that it's still possible for a sender with the server's help to modify their 11 // *own* attachments after the fact, if clients aren't checking the hash. This 12 // isn't perfect, but it's better than any participant being able to do it.) 13 // 14 // This file has 100% test coverage. Please keep it that way :-) 15 // 16 // Seal inputs: 17 // - plaintext bytes (streaming is fine) 18 // - a crypto_secretbox symmetric key 19 // - a crypto_sign private key 20 // - a globally unique (with respect to these keys) 16-byte nonce 21 // 22 // Seal steps: 23 // 1) Chunk the message into chunks exactly one megabyte long (2^20 bytes), with 24 // exactly one short chunk at the end, which might be zero bytes. 25 // 2) Compute the SHA512 hash of each plaintext chunk. 26 // 3) Concatenate the 16-byte nonce above with the 8-byte unsigned big-endian 27 // chunk number, where the first chunk is zero. This is the 24-byte chunk 28 // nonce. 29 // 4) Concatenate five things: 30 // - a signature prefix string which must contain no null bytes. 31 // for example "Keybase-Chat-Attachment-1" 32 // - a null byte terminator for the prefix string 33 // - the encryption key (why?! read below) 34 // - the chunk nonce from #3 35 // - the hash from #2. 36 // 5) Sign the concatenation from #4, giving a detached 64-byte crypto_sign 37 // signature. 38 // 6) Concatenate the signature from #5 + the plaintext chunk. 39 // 7) Encrypt the concatenation from #6 with the crypto_secretbox key and the 40 // chunk nonce from #3. 41 // 8) Concatenate all the ciphertexts from #7 into the output. 42 // 43 // Open inputs: 44 // - ciphertext bytes (streaming is fine) 45 // - the same crypto_secretbox symmetric key 46 // - the corresponding crypto_sign public key 47 // - the same nonce 48 // 49 // Open steps: 50 // 1) Chop the input stream into chunks of exactly (2^20 + 80) bytes, with 51 // exactly one short chunk at the end. If this short chunk is less than 80 52 // bytes (the size of an Ed25519 signature and a Poly1305 authenticator put 53 // together), return a truncation error. 54 // 2) Decrypt each input chunk with the crypto_secretbox key and chunk nonce as 55 // in seal step #7. 56 // 3) Split each decrypted chunk into a 64-byte signature and the following 57 // plaintext. 58 // 4) Hash that plaintext and make the concatenation from seal step #4. 59 // 5) Verify the signature against that concatenation. 60 // 6) Emit each verified plaintext chunk as output. 61 // 62 // Design Notes: 63 // 64 // Combining signing and encryption is surprisingly tricky! See 65 // http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html for lots of 66 // details about the issues that come up. (Note that "encryption" in that 67 // paper refers mostly to RSA encryption like PGP's, which doesn't involve 68 // a sender key the way Diffie-Hellman / NaCl's crypto_box does. This makes 69 // me appreciate just how many problems the crypto_box construction is 70 // solving.) 71 // 72 // Many of these issues probably don't apply to chat attachments (yet?!), 73 // because recipients will know what keys to use ahead of time. But there 74 // are other places where we use signing+encryption that have different 75 // properties, and I want to be able to use this design as a reference. The 76 // short version of the problem is that both encrypt-then-sign and 77 // sign-then-encrypt have to worry about what happens when someone reuses 78 // the inner layer with a new outer layer. 79 // 80 // Encrypt-then-sign has a "sender impersonation" problem. The 81 // man-in-the-middle can re-sign an encrypted payload with their own key 82 // and claim authorship of the message. If the message itself contains 83 // secrets, like in an auth protocol for example, the MITM can fake knowing 84 // those secrets. (Also, encrypt-then-sign has the more obvious downside 85 // that encryption is hiding only the contents of a signature and not its 86 // author.) 87 // 88 // Sign-then-encrypt has a "surreptitious forwarding" problem. A recipient 89 // can re-encrypt the signed payload to another unintended recipient. 90 // Recipients must not rely on the encryption layer to mean that the sender 91 // intended the message for them. In fact PGP is vulnerable to this attack, 92 // unless the user/application understands the very subtle difference 93 // between "I can read this" and "this was written to me". 94 // 95 // So, simply using encryption and signing together isn't good enough! The 96 // paper linked above mentions a few different solutions, but in general 97 // the fix is that the inner layer needs to assert something about the 98 // outer layer, so that the outer layer can't be changed without the inner 99 // key. We could simply include the outer key verbatim inside the inner 100 // layer, but a better approach is to mix the outer key into the inner 101 // crypto, so that it's impossible to forget to check it. 102 // 103 // We prefer sign-then-encrypt, because hiding the author of the signature 104 // is a feature. That means the inner signing layer needs to assert the 105 // encryption key. We do this by including the encryption key as 106 // "associated data" that gets signed along with the plaintext. Since we 107 // already need to do that with a nonce and a chunk number, including the 108 // the encryption key is easy. We don't need to worry about whether the 109 // signature might leak the encryption key either, because the signature 110 // gets encrypted. 111 // 112 // Apart from these signing gymnastics, all the large-encrypted-message 113 // considerations from https://www.imperialviolet.org/2015/05/16/aeads.html 114 // apply here. Namely we use a chunk number to prevent reordering, and we 115 // require a short chunk at the end to detect truncation. A globally unique 116 // nonce (for encryption *and* signing) prevents chunk swapping in between 117 // messages, and is required for encryption in any case. (It's expected 118 // that the chat client will pass in all zeroes for the nonce, because both 119 // keys are one-time-use. That's up to the client. G-d help us if we ever 120 // reuse those keys.) We also follow the "prefix signatures with an ASCII 121 // context string and a null byte" recommendation from 122 // https://www.ietf.org/mail-archive/web/tls/current/msg14734.html. 123 124 package signencrypt 125 126 import ( 127 "bytes" 128 "crypto/sha512" 129 "encoding/binary" 130 "fmt" 131 "io" 132 133 "github.com/keybase/client/go/kbcrypto" 134 "github.com/keybase/client/go/msgpack" 135 "github.com/keybase/go-crypto/ed25519" 136 "golang.org/x/crypto/nacl/secretbox" 137 ) 138 139 type Nonce *[NonceSize]byte 140 type SecretboxKey *[SecretboxKeySize]byte 141 type SecretboxNonce *[SecretboxNonceSize]byte 142 type SignKey *[ed25519.PrivateKeySize]byte 143 type VerifyKey *[ed25519.PublicKeySize]byte 144 145 const NonceSize = 16 146 const SecretboxKeySize = 32 147 const SecretboxNonceSize = 24 148 const DefaultPlaintextChunkLength int64 = 1 << 20 149 150 // =================================== 151 // single packet encoding and decoding 152 // =================================== 153 154 func makeChunkNonce(nonce Nonce, chunkNum uint64) SecretboxNonce { 155 var ret [SecretboxNonceSize]byte 156 copy(ret[0:16], nonce[:]) 157 var chunkNumBytes [8]byte 158 binary.BigEndian.PutUint64(chunkNumBytes[:], chunkNum) 159 copy(ret[16:24], chunkNumBytes[:]) 160 return &ret 161 } 162 163 func makeSignatureInput(plaintext []byte, encKey SecretboxKey, signaturePrefix kbcrypto.SignaturePrefix, chunkNonce SecretboxNonce) []byte { 164 // Check that the prefix does not include any null bytes. 165 if bytes.IndexByte([]byte(signaturePrefix), 0x00) != -1 { 166 panic(fmt.Sprintf("signature prefix contains null byte: %q", signaturePrefix)) 167 } 168 169 chunkHash := sha512.Sum512(plaintext) 170 var ret []byte 171 ret = append(ret, signaturePrefix...) 172 // We follow the "prefix signatures with an ASCII context string and a null byte" recommendation from 173 // https://www.ietf.org/mail-archive/web/tls/current/msg14734.html. 174 ret = append(ret, 0x00) 175 ret = append(ret, encKey[:]...) 176 ret = append(ret, chunkNonce[:]...) 177 ret = append(ret, chunkHash[:]...) 178 return ret 179 } 180 181 func getPacketLen(plaintextChunkLen int64) int64 { 182 return plaintextChunkLen + secretbox.Overhead + ed25519.SignatureSize 183 } 184 185 func getPlaintextPacketLen(cipherChunkLen int64) int64 { 186 return cipherChunkLen - (secretbox.Overhead + ed25519.SignatureSize) 187 } 188 189 func sealPacket(plaintext []byte, encKey SecretboxKey, signKey SignKey, signaturePrefix kbcrypto.SignaturePrefix, nonce SecretboxNonce) []byte { 190 signatureInput := makeSignatureInput(plaintext, encKey, signaturePrefix, nonce) 191 signature := ed25519.Sign(signKey[:], signatureInput) 192 signedChunk := signature 193 signedChunk = append(signedChunk, plaintext...) 194 packet := secretbox.Seal(nil, signedChunk, nonce, encKey) 195 return packet 196 } 197 198 func openPacket(packet []byte, encKey SecretboxKey, verifyKey VerifyKey, signaturePrefix kbcrypto.SignaturePrefix, nonce SecretboxNonce) ([]byte, error) { 199 signedChunk, secretboxValid := secretbox.Open(nil, packet, nonce, encKey) 200 if !secretboxValid { 201 return nil, NewError(BadSecretbox, "secretbox failed to open") 202 } 203 // Avoid panicking on signatures that are too short. 204 if len(signedChunk) < ed25519.SignatureSize { 205 return nil, NewError(ShortSignature, "signature too short") 206 } 207 signature := signedChunk[0:ed25519.SignatureSize] 208 plaintext := signedChunk[ed25519.SignatureSize:] 209 signatureInput := makeSignatureInput(plaintext, encKey, signaturePrefix, nonce) 210 signatureValid := ed25519.Verify(verifyKey[:], signatureInput, signature) 211 if !signatureValid { 212 return nil, NewError(BadSignature, "signature failed to verify") 213 } 214 return plaintext, nil 215 } 216 217 // =================== 218 // incremental encoder 219 // =================== 220 221 type Encoder struct { 222 encKey SecretboxKey 223 signKey SignKey 224 signaturePrefix kbcrypto.SignaturePrefix 225 nonce Nonce 226 buf []byte 227 chunkNum uint64 228 plaintextChunkLen int64 229 } 230 231 func NewEncoder(encKey SecretboxKey, signKey SignKey, signaturePrefix kbcrypto.SignaturePrefix, nonce Nonce) *Encoder { 232 return &Encoder{ 233 encKey: encKey, 234 signKey: signKey, 235 signaturePrefix: signaturePrefix, 236 nonce: nonce, 237 buf: nil, 238 chunkNum: 0, 239 plaintextChunkLen: DefaultPlaintextChunkLength, 240 } 241 } 242 243 func (e *Encoder) sealOnePacket(plaintextChunkLen int64) []byte { 244 // Note that this function handles the `plaintextChunkLen == 0` case. 245 if plaintextChunkLen > int64(len(e.buf)) { 246 panic("encoder tried to seal a packet that was too big") 247 } 248 plaintextChunk := e.buf[0:plaintextChunkLen] 249 chunkNonce := makeChunkNonce(e.nonce, e.chunkNum) 250 packet := sealPacket(plaintextChunk, e.encKey, e.signKey, e.signaturePrefix, chunkNonce) 251 e.buf = e.buf[plaintextChunkLen:] 252 e.chunkNum++ 253 return packet 254 } 255 256 // Write plaintext bytes into the encoder. If any output bytes are ready, 257 // return them. Callers must call Finish() when they're done, so that any 258 // remaining input bytes can be written out as a short (or empty) chunk. 259 // Otherwise you will both lose data and cause truncation errors on 260 // decoding. 261 func (e *Encoder) Write(plaintext []byte) []byte { 262 e.buf = append(e.buf, plaintext...) 263 var output []byte 264 // If buf is big enough to make new packets, make as many as we can. 265 for int64(len(e.buf)) >= e.plaintextChunkLen { 266 packet := e.sealOnePacket(e.plaintextChunkLen) 267 output = append(output, packet...) 268 } 269 return output 270 } 271 272 // Finish writes out any remaining buffered input bytes (possibly zero bytes) 273 // as a short chunk. This should only be called once, and after that you can't 274 // use this encoder again. 275 func (e *Encoder) Finish() []byte { 276 if int64(len(e.buf)) >= e.plaintextChunkLen { 277 panic("encoder buffer has more bytes than expected") 278 } 279 packet := e.sealOnePacket(int64(len(e.buf))) 280 return packet 281 } 282 283 func (e *Encoder) ChangePlaintextChunkLenForTesting(plaintextChunkLen int64) { 284 e.plaintextChunkLen = plaintextChunkLen 285 } 286 287 // =================== 288 // incremental decoder 289 // =================== 290 291 type Decoder struct { 292 encKey SecretboxKey 293 verifyKey VerifyKey 294 signaturePrefix kbcrypto.SignaturePrefix 295 nonce Nonce 296 buf []byte 297 chunkNum uint64 298 err error 299 packetLen int64 300 } 301 302 func NewDecoder(encKey SecretboxKey, verifyKey VerifyKey, signaturePrefix kbcrypto.SignaturePrefix, nonce Nonce) *Decoder { 303 return &Decoder{ 304 encKey: encKey, 305 verifyKey: verifyKey, 306 signaturePrefix: signaturePrefix, 307 nonce: nonce, 308 buf: nil, 309 chunkNum: 0, 310 err: nil, 311 packetLen: getPacketLen(DefaultPlaintextChunkLength), 312 } 313 } 314 315 func (d *Decoder) setChunkNum(num uint64) { 316 d.chunkNum = num 317 } 318 319 func (d *Decoder) openOnePacket(packetLen int64) ([]byte, error) { 320 if packetLen > int64(len(d.buf)) { 321 panic("decoder tried to open a packet that was too big") 322 } 323 packet := d.buf[0:packetLen] 324 chunkNonce := makeChunkNonce(d.nonce, d.chunkNum) 325 plaintext, err := openPacket(packet, d.encKey, d.verifyKey, d.signaturePrefix, chunkNonce) 326 if err != nil { 327 return nil, err 328 } 329 d.buf = d.buf[packetLen:] 330 d.chunkNum++ 331 return plaintext, nil 332 } 333 334 // Write ciphertext bytes into the decoder. If any packets are ready to 335 // open, open them and either return their plaintext bytes as output or any 336 // error that comes up. Callers must call Finish() when they're done, to 337 // decode the final short packet and check for truncation. If Write ever 338 // returns an error, subsequent calls to Write will always return the same 339 // error. 340 func (d *Decoder) Write(ciphertext []byte) ([]byte, error) { 341 // If we've ever seen an error, just return that again. 342 if d.err != nil { 343 return nil, d.err 344 } 345 d.buf = append(d.buf, ciphertext...) 346 // If buf is big enough to open new packets, open as many as we can. 347 // We assume that every packet other than the last (which we handle in 348 // Finish) is the same length, which makes this loop very simple. 349 var output []byte 350 for int64(len(d.buf)) >= d.packetLen { 351 var plaintext []byte 352 plaintext, d.err = d.openOnePacket(d.packetLen) 353 if d.err != nil { 354 return nil, d.err 355 } 356 output = append(output, plaintext...) 357 } 358 return output, nil 359 } 360 361 // Finish decodes any remaining bytes as a short (or empty) packet. This 362 // produces the final bytes of the plaintext, and implicitly checks for 363 // truncation. This should only be called once, and after that you can't use 364 // this decoder again. 365 func (d *Decoder) Finish() ([]byte, error) { 366 // If we've ever seen an error, just return that again. 367 if d.err != nil { 368 return nil, d.err 369 } 370 if int64(len(d.buf)) >= d.packetLen { 371 panic("decoder buffer has more bytes than expected") 372 } 373 // If we've been truncated at a packet boundary, this open will fail on a 374 // simple length check. If we've been truncated in the middle of a packet, 375 // this open will fail to validate. 376 var plaintext []byte 377 plaintext, d.err = d.openOnePacket(int64(len(d.buf))) 378 return plaintext, d.err 379 } 380 381 func (d *Decoder) ChangePlaintextChunkLenForTesting(plaintextChunkLen int64) { 382 d.packetLen = getPacketLen(plaintextChunkLen) 383 } 384 385 // =============================================== 386 // Reader-based wrappers for encoding and decoding 387 // =============================================== 388 389 type codec interface { 390 Write([]byte) ([]byte, error) 391 Finish() ([]byte, error) 392 } 393 394 // The incremental encoder never returns errors, so its type signatures are 395 // different than the decoder's. This struct trivially wraps them to fit the 396 // codec signature. 397 type encoderCodecShim struct { 398 *Encoder 399 } 400 401 func (s *encoderCodecShim) Write(b []byte) ([]byte, error) { 402 return s.Encoder.Write(b), nil 403 } 404 405 func (s *encoderCodecShim) Finish() ([]byte, error) { 406 return s.Encoder.Finish(), nil 407 } 408 409 var _ codec = (*Decoder)(nil) 410 var _ codec = (*encoderCodecShim)(nil) 411 412 type codecReadWrapper struct { 413 codec codec 414 innerReader io.Reader 415 outputBuf []byte 416 codecErr error 417 innerEOF bool 418 } 419 420 var _ io.Reader = (*codecReadWrapper)(nil) 421 422 func (r *codecReadWrapper) Read(callerBuf []byte) (int, error) { 423 // Crypto errors are unrecoverable. If we've ever seen one, just keep 424 // returning it. 425 if r.codecErr != nil { 426 return 0, r.codecErr 427 } 428 // While we need more data, keep reading from the inner reader. 429 for !r.innerEOF && len(r.outputBuf) == 0 { 430 var readBuf [4096]byte 431 n, ioErr := r.innerReader.Read(readBuf[:]) 432 // Always handle the bytes we read, regardless of errors, in accordance 433 // with https://golang.org/pkg/io/#Reader. 434 if n > 0 { 435 var newOutput []byte 436 newOutput, r.codecErr = r.codec.Write(readBuf[0:n]) 437 // For codec errors, short circuit and never read again. 438 if r.codecErr != nil { 439 return 0, r.codecErr 440 } 441 r.outputBuf = append(r.outputBuf, newOutput...) 442 } 443 // Now handle EOF or other errors. 444 if ioErr == io.EOF { 445 // When we see EOF we finish the internal codec. We won't run this 446 // loop anymore, but we might still need to return bytes from our 447 // own buffer for many subsequent reads. Also nil out the codec and 448 // the inner reader, since we shouldn't touch them again. 449 r.innerEOF = true 450 var finalOutput []byte 451 finalOutput, r.codecErr = r.codec.Finish() 452 // For codec errors, short circuit and never read again. 453 if r.codecErr != nil { 454 return 0, r.codecErr 455 } 456 r.outputBuf = append(r.outputBuf, finalOutput...) 457 r.codec = nil 458 r.innerReader = nil 459 } else if ioErr != nil { 460 // If we have a real IO error, short circuit and return it. This 461 // reader remains valid, though, and the caller is allowed to 462 // retry. 463 return 0, ioErr 464 } 465 } 466 // Now, if we have any buffered data, return as much of that as we can. 467 if len(r.outputBuf) > 0 { 468 copied := copy(callerBuf, r.outputBuf) 469 r.outputBuf = r.outputBuf[copied:] 470 return copied, nil 471 } 472 // Otherwise return EOF. 473 return 0, io.EOF 474 } 475 476 // NewEncodingReader creates a new streaming encoder. 477 // The signaturePrefix argument must not contain the null container. 478 func NewEncodingReader(encKey SecretboxKey, signKey SignKey, signaturePrefix kbcrypto.SignaturePrefix, nonce Nonce, innerReader io.Reader) io.Reader { 479 return &codecReadWrapper{ 480 innerReader: innerReader, 481 codec: &encoderCodecShim{NewEncoder(encKey, signKey, signaturePrefix, nonce)}, 482 } 483 } 484 485 func NewDecodingReader(encKey SecretboxKey, verifyKey VerifyKey, signaturePrefix kbcrypto.SignaturePrefix, nonce Nonce, innerReader io.Reader) io.Reader { 486 return &codecReadWrapper{ 487 innerReader: innerReader, 488 codec: NewDecoder(encKey, verifyKey, signaturePrefix, nonce), 489 } 490 } 491 492 // ============================= 493 // chunk helpers 494 // ============================= 495 496 type chunkSpec struct { 497 index int64 498 ptStart, ptEnd int64 499 cipherStart, cipherEnd int64 500 } 501 502 func chunkFromIndex(index int64) (res chunkSpec) { 503 res.index = index 504 res.ptStart = res.index * DefaultPlaintextChunkLength 505 res.ptEnd = res.ptStart + DefaultPlaintextChunkLength 506 res.cipherStart = res.index * getPacketLen(DefaultPlaintextChunkLength) 507 res.cipherEnd = res.cipherStart + getPacketLen(DefaultPlaintextChunkLength) 508 return res 509 } 510 511 func getChunksInRange(plaintextBegin, plaintextEnd, plaintextLen int64) (res []chunkSpec) { 512 beginChunk := chunkFromIndex(plaintextBegin / DefaultPlaintextChunkLength) 513 endChunk := chunkFromIndex(plaintextEnd / DefaultPlaintextChunkLength) 514 cipherLen := GetSealedSize(plaintextLen) 515 for i := beginChunk.index; i <= endChunk.index; i++ { 516 res = append(res, chunkFromIndex(i)) 517 } 518 if res[len(res)-1].ptEnd >= plaintextLen { 519 res[len(res)-1].ptEnd = plaintextLen 520 } 521 if res[len(res)-1].cipherEnd >= cipherLen { 522 res[len(res)-1].cipherEnd = cipherLen 523 } 524 return res 525 } 526 527 // ============================= 528 // all-at-once wrapper functions 529 // ============================= 530 531 func GetSealedSize(plaintextLen int64) int64 { 532 // All the full packets. 533 fullChunks := plaintextLen / DefaultPlaintextChunkLength 534 totalLen := fullChunks * getPacketLen(DefaultPlaintextChunkLength) 535 // Exactly one short packet, even if it's empty. 536 remainingPlaintext := plaintextLen % DefaultPlaintextChunkLength 537 totalLen += getPacketLen(remainingPlaintext) 538 return totalLen 539 } 540 541 func GetPlaintextSize(cipherLen int64) int64 { 542 fullChunks := cipherLen / getPacketLen(DefaultPlaintextChunkLength) 543 totalLen := fullChunks * DefaultPlaintextChunkLength 544 remainingCiphertext := cipherLen % getPacketLen(DefaultPlaintextChunkLength) 545 totalLen += getPlaintextPacketLen(remainingCiphertext) 546 return totalLen 547 } 548 549 // SealWhole seals all at once using the streaming encoding. 550 func SealWhole(plaintext []byte, encKey SecretboxKey, signKey SignKey, signaturePrefix kbcrypto.SignaturePrefix, nonce Nonce) []byte { 551 encoder := NewEncoder(encKey, signKey, signaturePrefix, nonce) 552 output := encoder.Write(plaintext) 553 output = append(output, encoder.Finish()...) 554 return output 555 } 556 557 func OpenWhole(sealed []byte, encKey SecretboxKey, verifyKey VerifyKey, signaturePrefix kbcrypto.SignaturePrefix, nonce Nonce) ([]byte, error) { 558 decoder := NewDecoder(encKey, verifyKey, signaturePrefix, nonce) 559 output, err := decoder.Write(sealed) 560 if err != nil { 561 return nil, err 562 } 563 moreOutput, err := decoder.Finish() 564 if err != nil { 565 return nil, err 566 } 567 return append(output, moreOutput...), nil 568 } 569 570 // include verification of associated data. see 571 // https://en.wikipedia.org/wiki/Authenticated_encryption#Authenticated_encryption_with_associated_data_(AEAD) 572 573 type AEADMessage struct { 574 Version int `codec:"v" json:"v"` 575 AssocDataHash [sha512.Size]byte `codec:"a" json:"a"` 576 Message []byte `codec:"m" json:"m"` 577 } 578 579 // SealWithAssociatedData is a wrapper around SealWhole which adds an associatedData object 580 // (see AEAD ciphers) which must be message-packable into bytes. This exact object is required 581 // to call OpenWithAssociatedData on the ciphertext. 582 func SealWithAssociatedData(msg []byte, associatedData interface{}, encKey SecretboxKey, signKey SignKey, signaturePrefix kbcrypto.SignaturePrefix, nonce Nonce) (ret []byte, err error) { 583 adEncoded, err := msgpack.Encode(associatedData) 584 if err != nil { 585 return ret, err 586 } 587 adHash := sha512.Sum512(adEncoded) 588 aeadMsg := AEADMessage{ 589 Version: 1, 590 AssocDataHash: adHash, 591 Message: msg, 592 } 593 clearBytes, err := msgpack.Encode(aeadMsg) 594 if err != nil { 595 return ret, err 596 } 597 return SealWhole(clearBytes, encKey, signKey, signaturePrefix, nonce), nil 598 } 599 600 func OpenWithAssociatedData(sealed []byte, associatedData interface{}, encKey SecretboxKey, verifyKey VerifyKey, signaturePrefix kbcrypto.SignaturePrefix, nonce Nonce) (ret []byte, err error) { 601 clearBytes, err := OpenWhole(sealed, encKey, verifyKey, signaturePrefix, nonce) 602 if err != nil { 603 return ret, err 604 } 605 var aeadMessage AEADMessage 606 err = msgpack.Decode(&aeadMessage, clearBytes) 607 if err != nil { 608 return ret, err 609 } 610 if aeadMessage.Version != 1 { 611 return ret, NewError(BadSecretbox, "can only accept AEAD messages with version 1, but got %d", aeadMessage.Version) 612 } 613 actualADHash := aeadMessage.AssocDataHash 614 msg := aeadMessage.Message 615 616 adEncoded, err := msgpack.Encode(associatedData) 617 if err != nil { 618 return ret, err 619 } 620 adHash := sha512.Sum512(adEncoded) 621 if !bytes.Equal(adHash[:], actualADHash[:]) { 622 return ret, NewError(AssociatedDataMismatch, "fingerprint of associated data did not match") 623 } 624 return msg, nil 625 } 626 627 // ====== 628 // errors 629 // ====== 630 631 type ErrorType int 632 633 const ( 634 BadSecretbox ErrorType = iota 635 ShortSignature 636 BadSignature 637 AssociatedDataMismatch 638 ) 639 640 type Error struct { 641 Type ErrorType 642 Message string 643 } 644 645 func NewError(errorType ErrorType, message string, args ...interface{}) error { 646 return Error{ 647 Type: errorType, 648 Message: fmt.Sprintf(message, args...), 649 } 650 } 651 652 func (e Error) Error() string { 653 return e.Message 654 }