Files
Aditya R 64ff149324 vendor: bump buildah to v1.32.1-0.20231012130144-244170240d85
Signed-off-by: Aditya R <arajan@redhat.com>
2023-10-16 11:55:53 +05:30

256 lines
9.2 KiB
Go

package luksy
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"strconv"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/pbkdf2"
)
// ReaderAtSeekCloser is a combination of io.ReaderAt, io.Seeker, and io.Closer,
// which is all we really need from an encrypted file.
type ReaderAtSeekCloser interface {
io.ReaderAt
io.Seeker
io.Closer
}
// Decrypt attempts to verify the specified password using information from the
// header and read from the specified file.
//
// Returns a function which will decrypt payload blocks in succession, the size
// of chunks of data that the function expects, the offset in the file where
// the payload begins, and the size of the payload, assuming the payload runs
// to the end of the file.
func (h V1Header) Decrypt(password string, f ReaderAtSeekCloser) (func([]byte) ([]byte, error), int, int64, int64, error) {
size, err := f.Seek(0, io.SeekEnd)
if err != nil {
return nil, -1, -1, -1, err
}
hasher, err := hasherByName(h.HashSpec())
if err != nil {
return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", h.HashSpec(), err)
}
activeKeys := 0
for k := 0; k < v1NumKeys; k++ {
keyslot, err := h.KeySlot(k)
if err != nil {
return nil, -1, -1, -1, fmt.Errorf("reading key slot %d: %w", k, err)
}
active, err := keyslot.Active()
if err != nil {
return nil, -1, -1, -1, fmt.Errorf("checking if key slot %d is active: %w", k, err)
}
if !active {
continue
}
activeKeys++
passwordDerived := pbkdf2.Key([]byte(password), keyslot.KeySlotSalt(), int(keyslot.Iterations()), int(h.KeyBytes()), hasher)
striped := make([]byte, h.KeyBytes()*keyslot.Stripes())
n, err := f.ReadAt(striped, int64(keyslot.KeyMaterialOffset())*V1SectorSize)
if err != nil {
return nil, -1, -1, -1, fmt.Errorf("reading diffuse material for keyslot %d: %w", k, err)
}
if n != len(striped) {
return nil, -1, -1, -1, fmt.Errorf("short read while reading diffuse material for keyslot %d: expected %d, got %d", k, len(striped), n)
}
splitKey, err := v1decrypt(h.CipherName(), h.CipherMode(), 0, passwordDerived, striped, V1SectorSize, false)
if err != nil {
fmt.Fprintf(os.Stderr, "error attempting to decrypt main key: %v\n", err)
continue
}
mkCandidate, err := afMerge(splitKey, hasher(), int(h.KeyBytes()), int(keyslot.Stripes()))
if err != nil {
fmt.Fprintf(os.Stderr, "error attempting to compute main key: %v\n", err)
continue
}
mkcandidateDerived := pbkdf2.Key(mkCandidate, h.MKDigestSalt(), int(h.MKDigestIter()), v1DigestSize, hasher)
ivTweak := 0
decryptStream := func(ciphertext []byte) ([]byte, error) {
plaintext, err := v1decrypt(h.CipherName(), h.CipherMode(), ivTweak, mkCandidate, ciphertext, V1SectorSize, false)
ivTweak += len(ciphertext) / V1SectorSize
return plaintext, err
}
if bytes.Equal(mkcandidateDerived, h.MKDigest()) {
payloadOffset := int64(h.PayloadOffset() * V1SectorSize)
return decryptStream, V1SectorSize, payloadOffset, size - payloadOffset, nil
}
}
if activeKeys == 0 {
return nil, -1, -1, -1, errors.New("no passwords set on LUKS1 volume")
}
return nil, -1, -1, -1, errors.New("decryption error: incorrect password")
}
// Decrypt attempts to verify the specified password using information from the
// header, JSON block, and read from the specified file.
//
// Returns a function which will decrypt payload blocks in succession, the size
// of chunks of data that the function expects, the offset in the file where
// the payload begins, and the size of the payload, assuming the payload runs
// to the end of the file.
func (h V2Header) Decrypt(password string, f ReaderAtSeekCloser, j V2JSON) (func([]byte) ([]byte, error), int, int64, int64, error) {
foundDigests := 0
for d, digest := range j.Digests {
if digest.Type != "pbkdf2" {
continue
}
if digest.V2JSONDigestPbkdf2 == nil {
return nil, -1, -1, -1, fmt.Errorf("digest %q is corrupt: no pbkdf2 parameters", d)
}
foundDigests++
if len(digest.Segments) == 0 || len(digest.Digest) == 0 {
continue
}
payloadOffset := int64(-1)
payloadSectorSize := V1SectorSize
payloadEncryption := ""
payloadSize := int64(0)
ivTweak := 0
for _, segmentID := range digest.Segments {
segment, ok := j.Segments[segmentID]
if !ok {
continue // well, that was misleading
}
if segment.Type != "crypt" {
continue
}
tmp, err := strconv.ParseInt(segment.Offset, 10, 64)
if err != nil {
continue
}
payloadOffset = tmp
if segment.Size == "dynamic" {
size, err := f.Seek(0, io.SeekEnd)
if err != nil {
continue
}
payloadSize = size - payloadOffset
} else {
payloadSize, err = strconv.ParseInt(segment.Size, 10, 64)
if err != nil {
continue
}
}
payloadSectorSize = segment.SectorSize
payloadEncryption = segment.Encryption
ivTweak = segment.IVTweak
break
}
if payloadEncryption == "" {
continue
}
activeKeys := 0
for k, keyslot := range j.Keyslots {
if keyslot.Priority != nil && *keyslot.Priority == V2JSONKeyslotPriorityIgnore {
continue
}
applicable := true
if len(digest.Keyslots) > 0 {
applicable = false
for i := 0; i < len(digest.Keyslots); i++ {
if k == digest.Keyslots[i] {
applicable = true
break
}
}
}
if !applicable {
continue
}
if keyslot.Type != "luks2" {
continue
}
if keyslot.V2JSONKeyslotLUKS2 == nil {
return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt", k)
}
if keyslot.V2JSONKeyslotLUKS2.AF.Type != "luks1" {
continue
}
if keyslot.V2JSONKeyslotLUKS2.AF.V2JSONAFLUKS1 == nil {
return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no AF parameters", k)
}
if keyslot.Area.Type != "raw" {
return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: key data area is not raw", k)
}
if keyslot.Area.KeySize*V2SectorSize < keyslot.KeySize*keyslot.AF.Stripes {
return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: key data area is too small (%d < %d)", k, keyslot.Area.KeySize*V2SectorSize, keyslot.KeySize*keyslot.AF.Stripes)
}
var passwordDerived []byte
switch keyslot.V2JSONKeyslotLUKS2.Kdf.Type {
default:
continue
case "pbkdf2":
if keyslot.V2JSONKeyslotLUKS2.Kdf.V2JSONKdfPbkdf2 == nil {
return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no pbkdf2 parameters", k)
}
hasher, err := hasherByName(keyslot.Kdf.Hash)
if err != nil {
return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", keyslot.Kdf.Hash, err)
}
passwordDerived = pbkdf2.Key([]byte(password), keyslot.Kdf.Salt, keyslot.Kdf.Iterations, keyslot.KeySize, hasher)
case "argon2i":
if keyslot.V2JSONKeyslotLUKS2.Kdf.V2JSONKdfArgon2i == nil {
return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no argon2i parameters", k)
}
passwordDerived = argon2.Key([]byte(password), keyslot.Kdf.Salt, uint32(keyslot.Kdf.Time), uint32(keyslot.Kdf.Memory), uint8(keyslot.Kdf.CPUs), uint32(keyslot.KeySize))
case "argon2id":
if keyslot.V2JSONKeyslotLUKS2.Kdf.V2JSONKdfArgon2i == nil {
return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no argon2id parameters", k)
}
passwordDerived = argon2.IDKey([]byte(password), keyslot.Kdf.Salt, uint32(keyslot.Kdf.Time), uint32(keyslot.Kdf.Memory), uint8(keyslot.Kdf.CPUs), uint32(keyslot.KeySize))
}
striped := make([]byte, keyslot.KeySize*keyslot.AF.Stripes)
n, err := f.ReadAt(striped, int64(keyslot.Area.Offset))
if err != nil {
return nil, -1, -1, -1, fmt.Errorf("reading diffuse material for keyslot %q: %w", k, err)
}
if n != len(striped) {
return nil, -1, -1, -1, fmt.Errorf("short read while reading diffuse material for keyslot %q: expected %d, got %d", k, len(striped), n)
}
splitKey, err := v2decrypt(keyslot.Area.Encryption, 0, passwordDerived, striped, V1SectorSize, false)
if err != nil {
fmt.Fprintf(os.Stderr, "error attempting to decrypt main key: %v\n", err)
continue
}
afhasher, err := hasherByName(keyslot.AF.Hash)
if err != nil {
return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", keyslot.AF.Hash, err)
}
mkCandidate, err := afMerge(splitKey, afhasher(), int(keyslot.KeySize), int(keyslot.AF.Stripes))
if err != nil {
fmt.Fprintf(os.Stderr, "error attempting to compute main key: %v\n", err)
continue
}
digester, err := hasherByName(digest.Hash)
if err != nil {
return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", digest.Hash, err)
}
mkcandidateDerived := pbkdf2.Key(mkCandidate, digest.Salt, digest.Iterations, len(digest.Digest), digester)
decryptStream := func(ciphertext []byte) ([]byte, error) {
plaintext, err := v2decrypt(payloadEncryption, ivTweak, mkCandidate, ciphertext, payloadSectorSize, true)
ivTweak += len(ciphertext) / payloadSectorSize
return plaintext, err
}
if bytes.Equal(mkcandidateDerived, digest.Digest) {
return decryptStream, payloadSectorSize, payloadOffset, payloadSize, nil
}
activeKeys++
}
if activeKeys == 0 {
return nil, -1, -1, -1, fmt.Errorf("no passwords set on LUKS2 volume for digest %q", d)
}
}
if foundDigests == 0 {
return nil, -1, -1, -1, errors.New("no usable password-verification digests set on LUKS2 volume")
}
return nil, -1, -1, -1, errors.New("decryption error: incorrect password")
}