mirror of
https://github.com/containers/podman.git
synced 2025-10-17 19:24:04 +08:00
256 lines
9.2 KiB
Go
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")
|
|
}
|