git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/zign/verify.go (about)

     1  package zign
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/binary"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  
    11  	"git.sr.ht/~pingoo/stdx/crypto"
    12  	"github.com/zeebo/blake3"
    13  )
    14  
    15  type VerifyInput struct {
    16  	Reader     io.Reader
    17  	HashBlake3 []byte
    18  	Signature  []byte
    19  }
    20  
    21  func Verify(base64PublicKey string, input VerifyInput) (err error) {
    22  	err = VerifyMany(base64PublicKey, []VerifyInput{input})
    23  	if err != nil {
    24  		return
    25  	}
    26  
    27  	return
    28  }
    29  
    30  func VerifyMany(base64PublicKey string, input []VerifyInput) (err error) {
    31  	publicKeyBytes, err := base64.StdEncoding.DecodeString(base64PublicKey)
    32  	if err != nil {
    33  		err = fmt.Errorf("zign.Verify: decoding public key (%s): %w", base64PublicKey, err)
    34  		return
    35  	}
    36  
    37  	publicKey, err := crypto.NewEd25519PublicKeyFromBytes(publicKeyBytes)
    38  	if err != nil {
    39  		err = fmt.Errorf("zign.Verify: parsing public key (%s): %w", base64PublicKey, err)
    40  		return
    41  	}
    42  
    43  	for _, file := range input {
    44  		err = hashDataAndVerifySignature(publicKey, file)
    45  		if err != nil {
    46  			return
    47  		}
    48  	}
    49  
    50  	return
    51  }
    52  
    53  func hashDataAndVerifySignature(publicKey crypto.Ed25519PublicKey, file VerifyInput) (err error) {
    54  	hasher := blake3.New()
    55  	var size int64
    56  
    57  	size, err = io.Copy(hasher, file.Reader)
    58  	if err != nil {
    59  		err = fmt.Errorf("zign.Verify: hashing file: %w", err)
    60  		return
    61  	}
    62  
    63  	hash := hasher.Sum(nil)
    64  
    65  	if !crypto.ConstantTimeCompare(hash, file.HashBlake3) {
    66  		err = errors.New("zign.Verify: hash is not valid")
    67  		return
    68  	}
    69  
    70  	// size of an uint64 and hash
    71  	sizeUint64 := uint64(size)
    72  	message := bytes.NewBuffer(make([]byte, 0, 8+crypto.HashSize256))
    73  	err = binary.Write(message, binary.BigEndian, sizeUint64)
    74  	if err != nil {
    75  		err = fmt.Errorf("zign.Verify: writing size: %w", err)
    76  		return
    77  	}
    78  
    79  	_, err = message.Write(hash)
    80  	if err != nil {
    81  		err = fmt.Errorf("zign.Verify: writing hash: %w", err)
    82  		return
    83  	}
    84  
    85  	verified := false
    86  	verified, err = publicKey.Verify(message.Bytes(), file.Signature)
    87  	if err != nil {
    88  		err = fmt.Errorf("zign.Verify: verifying signature (%s): %w", base64.StdEncoding.EncodeToString(file.Signature), err)
    89  		return
    90  	}
    91  	if verified {
    92  		return
    93  	}
    94  
    95  	err = errors.New("zign.Verify: signature is not valid")
    96  
    97  	return
    98  }