如何验证appengine.SignBytes返回的签名?

Google App Engine's Go runtime has a SignBytes function, a PublicCertificates function, and a Certificate structure.

func SignBytes

func SignBytes(c Context, bytes []byte) (keyName string, signature []byte, err error) 

SignBytes signs bytes using a private key unique to your application.

func PublicCertificates

func PublicCertificates(c Context) ([]Certificate, error) 

PublicCertificates retrieves the public certificates for the app. They can be used to verify a signature returned by SignBytes.

type Certificate

type Certificate struct {
    KeyName string
    Data    []byte // PEM-encoded X.509 certificate
}

Certificate represents a public certificate for the app.

It's clear that the application is expected to iterate through the public certificates to verify the signature. But it is not clear how the signature is generate or verified. Go's rsa package has two functions to verify signatures, VerifyPKCS1v15 and VerifyPSS, and each of those functions takes a crypto.Hash identifier as a parameter. Currently, there are 15 different hash identifiers (e.g., crypto.MD5, crypto.SHA256) giving 2x15=30 combinations of verification function and hash identifier.

How is the signature produced by SignBytes verified?

VerifyPKCS1v15 using SHA256

I discovered this by trying all combinations of verification scheme and hash type; I did not find documentation guaranteeing this is the signature scheme that will be used.

But for the intrepid, below is a code sample that signs data and verifies the signature.

package yourpackage

import (
    "crypto"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "errors"
    "google.golang.org/appengine"
    "net/http"
)

func signAndVerify(request *http.Request) error {
    c := appengine.NewContext(request)
    data := []byte("test data to sign")
    _, sig, err := appengine.SignBytes(c, data)
    if err != nil {
        return err
    }

    certs, err := appengine.PublicCertificates(c)
    if err != nil {
        return err
    }

    lastErr := errors.New("ErrNoPublicCertificates")

    for _, cert := range certs {
        block, _ := pem.Decode(cert.Data)
        if block == nil {
            lastErr = errors.New("ErrPemDecodeFailure")
            continue
        }
        x509Cert, err := x509.ParseCertificate(block.Bytes)
        if err != nil {
            lastErr = err
            continue
        }
        pubkey, ok := x509Cert.PublicKey.(*rsa.PublicKey)
        if !ok {
            lastErr = errors.New("ErrNotRSAPublicKey")
            continue
        }

        signBytesHash := crypto.SHA256
        h := signBytesHash.New()
        h.Write(data)
        hashed := h.Sum(nil)
        err = rsa.VerifyPKCS1v15(pubkey, signBytesHash, hashed, sig)
        if err != nil {
            lastErr = err
            continue
        }

        return nil
    }

    return lastErr
}

I've also published the the verify step in a package on github.

Update

Google provides some sample code that verifies SignBytes. In it, there is a file app-identity-samples-read-only/python/app_identity_test.py has a method named buildjwt that creates a JWT signed by SignBytes, and the JWT alg is RS256, which is defined in RFC 7518 to be RSASSA-PKCS1-v1_5 using SHA-256.

Note: I'm using Go App Engine for Managed VMs (see "google.golang.org/appengine" import) rather than the classic Go App Engine runtime, though there isn't really much different for the purposes of SignBytes.