6
votes

I have a binary file:

foo.bin

This file has been signed using a gpg key to create:

foo.bin.sig

I have a file containing the public key that was used to sign the binary file.

What I'd like to do is to be able to verify this signature using Go.

I was reading the go.crypto/openpgp docs and they aren't particularly helpful for this use case.

The verification will be done on a remote machine. Ideally I'd like to avoid using the keyring on the machine that will run this code. The public key can trivially be stored in the executable itself... if I can work out how to get this verification done.

The steps that I think I need to do are as follows:

  • Create an Entity that represents only the public key
  • Open both the binary file and the signature and pass it to some verification function

The question primarily is: how do I write this verification function using just a public key?

1
I'm not clear about your requirement. Can the gpg key verification be replaced by other methods like SHA256/MD5/RSA...?Reck Hou
According to wikipedia, a openpgp signature is a signature of the hash of the file. The signature is done using rsa or dsa and the hash can be done using many algorithms. I think you need to understand the .sig file to verify the signature. Then the crypto package should have all the methods you need. If you find the documentation about the definition of the .sig file (I did not find it), please place it here. I would like to see that too.user983716

1 Answers

5
votes

The openpgp API is not the most straightforward to use, but I gave it a go (pun intended), and here is what I came up with :

package main

import (
    "bytes"
    "code.google.com/p/go.crypto/openpgp/packet"
    "encoding/hex"
    "errors"
    "fmt"
    "io/ioutil"
    "os"
)

// gpg --export YOURKEYID --export-options export-minimal,no-export-attributes | hexdump /dev/stdin -v -e '/1 "%02X"'
var publicKeyHex string = "99[VERY LONG HEX STRING]B6"

func main() {
    if len(os.Args) != 3 {
        fmt.Println("Usage: " + os.Args[0] + " <file> <signature file>")
        return
    }

    err := checkSig(os.Args[1], os.Args[2])

    if err != nil {
        fmt.Println("Invalid signature : ")
        fmt.Println(err)
    } else {
        fmt.Println("Valid signature")
    }
}

func checkSig(fileName string, sigFileName string) error {
    // First, get the content of the file we have signed
    fileContent, err := ioutil.ReadFile(fileName)
    if err != nil {
        return err
    }

    // Get a Reader for the signature file
    sigFile, err := os.Open(sigFileName)
    if err != nil {
        return err
    }

    defer func() {
        if err := sigFile.Close(); err != nil {
            panic(err)
        }
    }()

    // Read the signature file
    pack, err := packet.Read(sigFile)
    if err != nil {
        return err
    }

    // Was it really a signature file ? If yes, get the Signature
    signature, ok := pack.(*packet.Signature)
    if !ok {
        return errors.New(os.Args[2] + " is not a valid signature file.")
    }

    // For convenience, we have the key in hexadecimal, convert it to binary
    publicKeyBin, err := hex.DecodeString(publicKeyHex)
    if err != nil {
        return err
    }

    // Read the key
    pack, err = packet.Read(bytes.NewReader(publicKeyBin))
    if err != nil {
        return err
    }

    // Was it really a public key file ? If yes, get the PublicKey
    publicKey, ok := pack.(*packet.PublicKey)
    if !ok {
        return errors.New("Invalid public key.")
    }

    // Get the hash method used for the signature
    hash := signature.Hash.New()

    // Hash the content of the file (if the file is big, that's where you have to change the code to avoid getting the whole file in memory, by reading and writting in small chunks)
    _, err = hash.Write(fileContent)
    if err != nil {
        return err
    }

    // Check the signature
    err = publicKey.VerifySignature(hash, signature)
    if err != nil {
        return err
    }

    return nil
}

As requested, I put the public key in the code. You can test it like that :

$ go run testpgp.go foo.bin foo.bin.sig

If the file you have signed is very big, you may want to change the code a little bit to avoid loading it in memory.