Converting JWK json into a public key golang (lestrrat-go) - go

I'm using JWKS format to provide from an authentication service the public key that can be used to validate tokens coming from that authentication service. However, to perform validation I need to rebuild the public key from the JWK. How can I convert it?
type JWKeys struct {
Keys []JWKey `json:"keys"`
}
type JWKey struct {
Kty string `json:"kty"`
Use string `json:"use,omitempty"`
Kid string `json:"kid,omitempty"`
Alg string `json:"alg,omitempty"`
Crv string `json:"crv,omitempty"`
X string `json:"x,omitempty"`
Y string `json:"y,omitempty"`
D string `json:"d,omitempty"`
N string `json:"n,omitempty"`
E string `json:"e,omitempty"`
K string `json:"k,omitempty"`
}
var PublicKey *rsa.PublicKey
func SetUpExternalAuth() {
res, err := http.Get("my_url")
if err != nil {
log.Fatal("Can't retrieve the key for authentication")
}
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}
var keys JWKeys
json.Unmarshal(bodyBytes, &keys)
//CONVERT JWK TO *rsa.PUBLICKEY???
}
UPDATE
I tried to parse the JWKs using github.com/lestrrat-go/jwx/jwk library, however I couldn't find how to continue:
set,err := jwk.Parse(bodyBytes)
key,err2 := set.Get(0)
//HOW TO CONVERT KEY INTO A *rsa.PublicKey?
At the end I've manually converted it:
if singleJWK.Kty != "RSA" {
log.Fatal("invalid key type:", singleJWK.Kty)
}
// decode the base64 bytes for n
nb, err := base64.RawURLEncoding.DecodeString(singleJWK.N)
if err != nil {
log.Fatal(err)
}
e := 0
// The default exponent is usually 65537, so just compare the
// base64 for [1,0,1] or [0,1,0,1]
if singleJWK.E == "AQAB" || singleJWK.E == "AAEAAQ" {
e = 65537
} else {
// need to decode "e" as a big-endian int
log.Fatal("need to deocde e:", singleJWK.E)
}
PublicKey = &rsa.PublicKey{
N: new(big.Int).SetBytes(nb),
E: e,
}

Understand you have a solution but as you were making the attempt using github.com/lestrrat-go/jwx/jwk here is an approach with that package (pretty much what is in the example):
package main
import (
"context"
"crypto/rsa"
"fmt"
"log"
"github.com/lestrrat-go/jwx/jwk"
)
func main() {
// Example jwk from https://www.googleapis.com/oauth2/v3/certs (but with only one cert for simplicity)
jwkJSON := `{
"keys": [
{
"kty": "RSA",
"n": "o76AudS2rsCvlz_3D47sFkpuz3NJxgLbXr1cHdmbo9xOMttPMJI97f0rHiSl9stltMi87KIOEEVQWUgMLaWQNaIZThgI1seWDAGRw59AO5sctgM1wPVZYt40fj2Qw4KT7m4RLMsZV1M5NYyXSd1lAAywM4FT25N0RLhkm3u8Hehw2Szj_2lm-rmcbDXzvjeXkodOUszFiOqzqBIS0Bv3c2zj2sytnozaG7aXa14OiUMSwJb4gmBC7I0BjPv5T85CH88VOcFDV51sO9zPJaBQnNBRUWNLh1vQUbkmspIANTzj2sN62cTSoxRhSdnjZQ9E_jraKYEW5oizE9Dtow4EvQ",
"use": "sig",
"alg": "RS256",
"e": "AQAB",
"kid": "6a8ba5652a7044121d4fedac8f14d14c54e4895b"
}
]
}
`
set, err := jwk.Parse([]byte(jwkJSON))
if err != nil {
panic(err)
}
fmt.Println(set)
for it := set.Iterate(context.Background()); it.Next(context.Background()); {
pair := it.Pair()
key := pair.Value.(jwk.Key)
var rawkey interface{} // This is the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey
if err := key.Raw(&rawkey); err != nil {
log.Printf("failed to create public key: %s", err)
return
}
// We know this is an RSA Key so...
rsa, ok := rawkey.(*rsa.PublicKey)
if !ok {
panic(fmt.Sprintf("expected ras key, got %T", rawkey))
}
// As this is a demo just dump the key to the console
fmt.Println(rsa)
}
}

I wrote a Go package exactly for this purpose: github.com/MicahParks/keyfunc
Converting to a *rsa.PublicKey
In this pacakge a JSON Web Key (JWK) looks like this Go struct. It supports both ECDSA and RSA JWK.
// JSONKey represents a raw key inside a JWKS.
type JSONKey struct {
Curve string `json:"crv"`
Exponent string `json:"e"`
ID string `json:"kid"`
Modulus string `json:"n"`
X string `json:"x"`
Y string `json:"y"`
precomputed interface{}
}
After the raw JSON message is unmarshaled into the above struct, this method converts it to an *rsa.PublicKey.
package keyfunc
import (
"crypto/rsa"
"encoding/base64"
"fmt"
"math/big"
)
const (
// rs256 represents a public cryptography key generated by a 256 bit RSA algorithm.
rs256 = "RS256"
// rs384 represents a public cryptography key generated by a 384 bit RSA algorithm.
rs384 = "RS384"
// rs512 represents a public cryptography key generated by a 512 bit RSA algorithm.
rs512 = "RS512"
// ps256 represents a public cryptography key generated by a 256 bit RSA algorithm.
ps256 = "PS256"
// ps384 represents a public cryptography key generated by a 384 bit RSA algorithm.
ps384 = "PS384"
// ps512 represents a public cryptography key generated by a 512 bit RSA algorithm.
ps512 = "PS512"
)
// RSA parses a JSONKey and turns it into an RSA public key.
func (j *JSONKey) RSA() (publicKey *rsa.PublicKey, err error) {
// Check if the key has already been computed.
if j.precomputed != nil {
return j.precomputed.(*rsa.PublicKey), nil
}
// Confirm everything needed is present.
if j.Exponent == "" || j.Modulus == "" {
return nil, fmt.Errorf("%w: rsa", ErrMissingAssets)
}
// Decode the exponent from Base64.
//
// According to RFC 7518, this is a Base64 URL unsigned integer.
// https://tools.ietf.org/html/rfc7518#section-6.3
var exponent []byte
if exponent, err = base64.RawURLEncoding.DecodeString(j.Exponent); err != nil {
return nil, err
}
// Decode the modulus from Base64.
var modulus []byte
if modulus, err = base64.RawURLEncoding.DecodeString(j.Modulus); err != nil {
return nil, err
}
// Create the RSA public key.
publicKey = &rsa.PublicKey{}
// Turn the exponent into an integer.
//
// According to RFC 7517, these numbers are in big-endian format.
// https://tools.ietf.org/html/rfc7517#appendix-A.1
publicKey.E = int(big.NewInt(0).SetBytes(exponent).Uint64())
// Turn the modulus into a *big.Int.
publicKey.N = big.NewInt(0).SetBytes(modulus)
// Keep the public key so it won't have to be computed every time.
j.precomputed = publicKey
return publicKey, nil
}
Parsing and validating a JWT from a JSON Web Key Set (JWKS).
I made this package to work with github.com/dgrijalva/jwt-go to more easily parse and validate JWTs with the most popular package.
Using your example URL, my_url, here's an example of how to parse and validate JWTs.
package main
import (
"log"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/MicahParks/keyfunc"
)
func main() {
// Get the JWKS URL.
jwksURL := "my_url"
// Create the keyfunc options. Refresh the JWKS every hour and log errors.
refreshInterval := time.Hour
options := keyfunc.Options{
RefreshInterval: &refreshInterval,
RefreshErrorHandler: func(err error) {
log.Printf("There was an error with the jwt.KeyFunc\nError: %s", err.Error())
},
}
// Create the JWKS from the resource at the given URL.
jwks, err := keyfunc.Get(jwksURL, options)
if err != nil {
log.Fatalf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
}
// Get a JWT to parse.
jwtB64 := "eyJhbGciOiJQUzM4NCIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMeDFGbWF5UDJZQnR4YXFTMVNLSlJKR2lYUktudzJvdjVXbVlJTUctQkxFIn0.eyJleHAiOjE2MTU0MDY5ODIsImlhdCI6MTYxNTQwNjkyMiwianRpIjoiMGY2NGJjYTktYjU4OC00MWFhLWFkNDEtMmFmZDM2OGRmNTFkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJhZDEyOGRmMS0xMTQwLTRlNGMtYjA5Ny1hY2RjZTcwNWJkOWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0b2tlbmRlbG1lIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjE3Mi4yMC4wLjEiLCJjbGllbnRJZCI6InRva2VuZGVsbWUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC10b2tlbmRlbG1lIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4yMC4wLjEifQ.Rxrq41AxbWKIQHWv-Tkb7rqwel3sKT_R_AGvn9mPIHqhw1m7nsQWcL9t2a_8MI2hCwgWtYdgTF1xxBNmb2IW3CZkML5nGfcRrFvNaBHd3UQEqbFKZgnIX29h5VoxekyiwFaGD-0RXL83jF7k39hytEzTatwoVjZ-frga0KFl-nLce3OwncRXVCGmxoFzUsyu9TQFS2Mm_p0AMX1y1MAX1JmLC3WFhH3BohhRqpzBtjSfs_f46nE1-HKjqZ1ERrAc2fmiVJjmG7sT702JRuuzrgUpHlMy2juBG4DkVcMlj4neJUmCD1vZyZBRggfaIxNkwUhHtmS2Cp9tOcwNu47tSg"
// Parse the JWT.
token, err := jwt.Parse(jwtB64, jwks.KeyFunc)
if err != nil {
log.Fatalf("Failed to parse the JWT.\nError: %s", err.Error())
}
// Check if the token is valid.
if !token.Valid {
log.Fatalf("The token is not valid.")
}
log.Println("The token is valid.")
}

Related

JWT not return SigningKey

I am trying to implement JWT for REST
I'm just curious on this code below
var mySigningKey = []byte("mysecret")
token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("There was an error")
}
return mySigningKey, nil
})
fmt.Println(token)
I expect token will return mySigningKey value but its return Token value
that shoud return value inside mySigningKey right?
Or did I misunderstand some concept?
By calling Parse method on your header Token value you are creating the token struct. If you want to get your SigningString you need to call SigningString() method on your token variable.
// Generate the signing string. This is the
// most expensive part of the whole deal. Unless you
// need this for something special, just go straight for
// the SignedString.
func (t *Token) SigningString() (string, error) {
var err error
parts := make([]string, 2)
for i, _ := range parts {
var source map[string]interface{}
if i == 0 {
source = t.Header
} else {
source = t.Claims
}
var jsonValue []byte
if jsonValue, err = json.Marshal(source); err != nil {
return "", err
}
parts[i] = EncodeSegment(jsonValue)
}
return strings.Join(parts, "."), nil
}
The function which is the second argument to Parse is responsible for providing the signing key which will be used to verify the JWT signature is correct.
In the case illustrated in the question, the signing key is a constant because HMAC is used to sign it.
However, if you had a RSA keypair, the token header would be useful to pick the right public key to check the signature, perhaps pulling it down from a well known JWKS endpoint.

Marshal AppleID's Public Key to rsa.PublicKey

I am trying to validate a JWT from Apple in Go and I need their public key as Go's rsa.PublicKey to do so. I retrieved their keys from the endpoint specified in:
https://developer.apple.com/documentation/sign_in_with_apple/fetch_apple_s_public_key_for_verifying_token_signature
and got the following:
{
"keys": [
{
"kty": "RSA",
"kid": "86D88Kf",
"use": "sig",
"alg": "RS256",
"n": "iGaLqP6y-SJCCBq5Hv6pGDbG_SQ11MNjH7rWHcCFYz4hGwHC4lcSurTlV8u3avoVNM8jXevG1Iu1SY11qInqUvjJur--hghr1b56OPJu6H1iKulSxGjEIyDP6c5BdE1uwprYyr4IO9th8fOwCPygjLFrh44XEGbDIFeImwvBAGOhmMB2AD1n1KviyNsH0bEB7phQtiLk-ILjv1bORSRl8AK677-1T8isGfHKXGZ_ZGtStDe7Lu0Ihp8zoUt59kx2o9uWpROkzF56ypresiIl4WprClRCjz8x6cPZXU2qNWhu71TQvUFwvIvbkE1oYaJMb0jcOTmBRZA2QuYw-zHLwQ",
"e": "AQAB"
},
{
"kty": "RSA",
"kid": "eXaunmL",
"use": "sig",
"alg": "RS256",
"n": "4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw",
"e": "AQAB"
}
]
}
I've tried the x509.ParsePKCS1PublicKey(...) function by parsing both the individual key and the entire JSON returned using asn1.Marshall(...) function. I got an error trying to parse the der.
I then noticed that the key contains "n" and "e" string pairs, and are defined here so I tried to create the public key directly. However, N and E in the rsa.PublicKey are a *big.Int and int respectively.
I can't seem to find the encoding for "n" and "e", so I can't accurately convert the values to an rsa.PublicKey. I've tried base64, but that did not work.
Can anyone tell me how I can convert Apple's public key to a suitable rsa.PublicKey please?
The values of "n" and "e" in your JSON are just base64-encoded big-endian binary integers, so once you've decoded them you can convert them to type *big.Int with big.Int.SetBytes, and then use those to populate an *rsa.PublicKey.
You mentioned you tried base64 and it didn't work, but you need to make sure you use the right encoding and padding options- the presence of - and _ characters in the encoded string indicates that you're dealing with the RFC 4648 URL-safe encoding, and the fact that the length of the string is not divisible by 4 indicates that no padding characters are present, so therefore base64.URLEncoding.WithPadding(base64.NoPadding) is what you need to use.
Comprehensive example of a type you can directly unmarshal into and convert:
package main
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"log"
"math/big"
)
const keyJSON = `{
"kty": "RSA",
"kid": "86D88Kf",
"use": "sig",
"alg": "RS256",
"n": "4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw",
"e": "AQAB"
}`
// decodeBase64BigInt decodes a base64-encoded larger integer from Apple's key format.
func decodeBase64BigInt(s string) *big.Int {
buffer, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(s)
if err != nil {
log.Fatalf("failed to decode base64: %v", err)
}
return big.NewInt(0).SetBytes(buffer)
}
// appleKey is a type of public key.
type appleKey struct {
KTY string
KID string
Use string
Alg string
N *big.Int
E int
}
// UnmarshalJSON parses a JSON-encoded value and stores the result in the object.
func (k *appleKey) UnmarshalJSON(b []byte) error {
var tmp struct {
KTY string `json:"kty"`
KID string `json:"kid"`
Use string `json:"use"`
Alg string `json:"alg"`
N string `json:"n"`
E string `json:"e"`
}
if err := json.Unmarshal(b, &tmp); err != nil {
return err
}
*k = appleKey{
KTY: tmp.KTY,
KID: tmp.KID,
Use: tmp.Use,
Alg: tmp.Alg,
N: decodeBase64BigInt(tmp.N),
E: int(decodeBase64BigInt(tmp.E).Int64()),
}
return nil
}
// RSA returns a corresponding *rsa.PublicKey
func (k appleKey) RSA() *rsa.PublicKey {
return &rsa.PublicKey{
N: k.N,
E: k.E,
}
}
func main() {
// Decode the Apple key.
var ak appleKey
if err := json.Unmarshal([]byte(keyJSON), &ak); err != nil {
log.Fatalf("failed to unmarshal JSON: %v", err)
}
// Convert it to a normal *rsa.PublicKey.
rk := ak.RSA()
if rk.Size() != 256 {
log.Fatalf("unexpected key size: %d", rk.Size())
}
// Do what you like with the RSA key now.
}

How to verify an OpenPGP signature on a public key?

I am working on a go project that will need to verify an OpenPGP public key, to be able to use it to verify file signatures.
I've generated a root key, and another key, which I've signed with the root key (let's call the second key signed). I've exported the public part of the signed key to an armored text file, for easy distribution:
gpg --export -a signed > signed.asc
I've written this go code which illustrates what I want to do, in the end:
package main
import (
"flag"
"fmt"
"golang.org/x/crypto/openpgp"
"os"
)
func main() {
var keyringpath string
var signedkeypath string
flag.StringVar(&keyringpath, "keyring", "", "keyring")
flag.StringVar(&signedkeypath, "signedkey", "", "signed key")
flag.Parse()
// read the keyring
keyring, err := os.Open(keyringpath)
if err != nil {
panic(err)
}
el, err := openpgp.ReadKeyRing(keyring)
if err != nil {
panic(err)
}
var rootidentity *openpgp.Entity
for _, entity := range el {
if _, ok := entity.Identities["root"]; ok {
rootidentity = entity
}
}
fmt.Printf("%+v\n", rootidentity)
// read the public armored key
signedkey, err := os.Open(signedkeypath)
if err != nil {
panic(err)
}
el, err = openpgp.ReadArmoredKeyRing(signedkey)
if err != nil {
panic(err)
}
signed := el[0]
fmt.Printf("%+v\n", signed)
// there is only one signature on signed, the one produced by root
signature := signed.Identities["signed"].Signatures[0]
err = rootidentity.PrimaryKey.VerifyKeySignature(signed.PrimaryKey, signature)
if err != nil {
panic(err)
}
}
When I run it, I give keyring my public keyring (~/.gnupg/pubring.gpg) and signedkey my exported signed key (signed.asc).
In production, the idea is to also export the root public key from pubring.gpg into armored text, and embed that in the code.
The signature fails to verify with the following error:
panic: openpgp: invalid signature: hash tag doesn't match
Looking at the code of VerifyKeySignature (and especially this comment), I get the feeling that it's meant to only be used to verify signatures on subkeys, rather than other keys.
So, the question is, given two public PGP keys, one signed by the other, how do I verify that signature using the openpgp library?
Not sure whether I should close this question or not: I found the answer. It isn't very clear in the docs, but VerifyKeySignature is indeed probably only used for subkeys. For verifying the signatures on other users' public keys, use VerifyUserIdSignature, like so:
err = rootidentity.PrimaryKey.VerifyUserIdSignature("signed", signed.PrimaryKey, signature)
if err != nil {
panic(err)
}

About VerifyPKCS1v15 (base64 and rsa check )

Given "notify" ,"publicKey" and "sign" , it doesnt pass the VerifyPKCS1v15 in go . That 's mycode , is there something wrong ?
package main
import (
"crypto"
"crypto/sha1"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
)
func main() {
notify := `YFSGlJTpNYakrZuZqZ55dcA5mVUb/JQBr3hdDjODsAVSdoVVytIagk9Wt0CD/uX+7jGL9pqev8/u0I0ZBKEmz5huXp8TdZSnskCZ7GTeHNW0VPJcW8OcBxAValA0jQSv2mBP+tc1r6mdvf66GEzhvgBfTnp3Sp7V3dijJ9bNstIDyrGm/BlByhcMr3UqXjTFJaui6t5TxvZhCuSV9sg+xVVA+sR3uFI78b5lKomg5Vu31EBZvXASlFfaOc4StltRUH2aSiRqjnbXe8dlRZO0Ih44htYs2QfehzeQnPHtTwNHUvtVIVcIdI/7j9yfy5es13QeIgfKghY/ENUnB2V7iA==`
sign := `s8XIN2TyC5niX1HFPDXOQj2eRvhW2qMPOdDuuXlOspYhxkjxunV4Ytgcw8GXg761HSbk4e5QsgKpU+vM2ggLhYni2GfXhGBVj/P13B6JhMmdrucU8ktlaH+fJGUmc3rqGMU3qiQgNAh/8PV1BS/5li7qzXHc0tgKL1zRgeu1CVw=`
notifyData, err := base64.StdEncoding.DecodeString(notify)
if err != nil {
fmt.Println("error1:", err)
return
}
signData, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
fmt.Println("error2:", err)
return
}
publicKey := []byte(`-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2kcrRvxURhFijDoPpqZ/IgPlA
gppkKrek6wSrua1zBiGTwHI2f+YCa5vC1JEiIi9uw4srS0OSCB6kY3bP2DGJagBo
Egj/rYAGjtYJxJrEiTxVs5/GfPuQBYmU0XAtPXFzciZy446VPJLHMPnmTALmIOR5
Dddd1Zklod9IQBMjjwIDAQAB
-----END PUBLIC KEY-----
`)
block, _ := pem.Decode(publicKey)
if block == nil {
fmt.Println("pem error :")
return
}
public, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
fmt.Println("public key error :", err)
return
}
pub := public.(*rsa.PublicKey)
fmt.Println(pub.N)
h := sha1.New()
h.Write([]byte(notifyData))
digest := h.Sum(nil)
err = rsa.VerifyPKCS1v15(pub, crypto.SHA1, digest, signData)
if err == nil {
fmt.Println("OK")
} else {
fmt.Println("verify fail", err)
}
}
P.S.
This is php code, and it'll pass with the same data.
<?php
$pubKey = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2kcrRvxURhFijDoPpqZ/IgPlA
gppkKrek6wSrua1zBiGTwHI2f+YCa5vC1JEiIi9uw4srS0OSCB6kY3bP2DGJagBo
Egj/rYAGjtYJxJrEiTxVs5/GfPuQBYmU0XAtPXFzciZy446VPJLHMPnmTALmIOR5
Dddd1Zklod9IQBMjjwIDAQAB
-----END PUBLIC KEY-----";
$pubRes = openssl_get_publickey($pubKey);
//通知数据
$notify_data = "YFSGlJTpNYakrZuZqZ55dcA5mVUb/JQBr3hdDjODsAVSdoVVytIagk9Wt0CD/uX+7jGL9pqev8/u0I0ZBKEmz5huXp8TdZSnskCZ7GTeHNW0VPJcW8OcBxAValA0jQSv2mBP+tc1r6mdvf66GEzhvgBfTnp3Sp7V3dijJ9bNstIDyrGm/BlByhcMr3UqXjTFJaui6t5TxvZhCuSV9sg+xVVA+sR3uFI78b5lKomg5Vu31EBZvXASlFfaOc4StltRUH2aSiRqjnbXe8dlRZO0Ih44htYs2QfehzeQnPHtTwNHUvtVIVcIdI/7j9yfy5es13QeIgfKghY/ENUnB2V7iA==";
//签名
$sign = "s8XIN2TyC5niX1HFPDXOQj2eRvhW2qMPOdDuuXlOspYhxkjxunV4Ytgcw8GXg761HSbk4e5QsgKpU+vM2ggLhYni2GfXhGBVj/P13B6JhMmdrucU8ktlaH+fJGUmc3rqGMU3qiQgNAh/8PV1BS/5li7qzXHc0tgKL1zRgeu1CVw=";
$data = base64_decode($notify_data);
$maxlength = 128;
$output = '';
while ($data) {
$input = substr($data, 0, $maxlength);
$data = substr($data, $maxlength);
openssl_public_decrypt($input, $out, $pubRes, OPENSSL_PKCS1_PADDING);
$output .= $out;
}
if (openssl_verify($output, base64_decode($sign), $pubRes)) {
echo "success";
}else{
echo "fail";
}
?>
You appear to have several different problems in your code.
There is no need to truncate the data to 128 characters in your PHP code as you are not doing the same in your go code. This discrepancy will cause the bytes to be different and therefore the computed signatures to be different.
You are using the openssl_public_decrypt function to sign the data. While this does work in theory it's error prone. You are also using a public-key to sign the data, which is wrong - only private-keys can sign. It's much better to use PHP's openssl_sign function.
Another source of error could be your signing code that uses the private key, which is not shown here.
PHP's and Go's public key cryptography should be entirely compatible. To test this I've created the following identical signing scripts in both PHP and Go.
<?php
$data = "TEST DATA TO COMPUTE";
$privKeyPEM = "-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAK3ADijXKw72+YbC5QKK2y7IosCp7rWOhTf8Ph07ZA0KjdbKtfL/
7dmNKjSP6EkC/DJUWfZJNLIlGOtDLLA/AnsCAwEAAQJAQj9kJrZDuKT6ZyOQZfPD
tobRZ1xjo93/dWU72bF3aHDo4ILMy2Kigy5yhZU0ZGjOuPv5eUOLRe/yxYQf6B5J
AQIhANbhfZ4QJC8dLXAqcsxOXuLgztzbKixUre0gnhiVSd1hAiEAzv+sHJ4PMjKs
Iuf6/nUI9XFgQQRd+NGRovyHRZC18VsCIAX7AKQFjvxAs6MLi2ZkR//IgfljoCjb
snuHDN9iSEwBAiEAmAc1XCtGE+Mdg+GG+T3xn3pubDIN5oHcia0YmKIIzsMCIEy1
fWM5cIJ9bAUExKB6MV8PF+9EjDvXzbSk1/Ycta8z
-----END RSA PRIVATE KEY-----";
// Parse private key
$privkey = openssl_pkey_get_private($privKeyPEM);
if (!$privkey) {
exit("Could not parse private key");
}
// Compute the signature
$signature = '';
$ok = openssl_sign($data, $signature, $privkey, OPENSSL_ALGO_SHA1); //SHA1 of $data is computed automatically by this function
if (!$ok) {
exit("Could not compute signature");
}
// Print the output
print base64_encode($signature);
And the same thing in Go:
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"log"
)
const (
data = "TEST DATA TO COMPUTE"
privKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAK3ADijXKw72+YbC5QKK2y7IosCp7rWOhTf8Ph07ZA0KjdbKtfL/
7dmNKjSP6EkC/DJUWfZJNLIlGOtDLLA/AnsCAwEAAQJAQj9kJrZDuKT6ZyOQZfPD
tobRZ1xjo93/dWU72bF3aHDo4ILMy2Kigy5yhZU0ZGjOuPv5eUOLRe/yxYQf6B5J
AQIhANbhfZ4QJC8dLXAqcsxOXuLgztzbKixUre0gnhiVSd1hAiEAzv+sHJ4PMjKs
Iuf6/nUI9XFgQQRd+NGRovyHRZC18VsCIAX7AKQFjvxAs6MLi2ZkR//IgfljoCjb
snuHDN9iSEwBAiEAmAc1XCtGE+Mdg+GG+T3xn3pubDIN5oHcia0YmKIIzsMCIEy1
fWM5cIJ9bAUExKB6MV8PF+9EjDvXzbSk1/Ycta8z
-----END RSA PRIVATE KEY-----`
)
func main() {
// Parse private key into rsa.PrivateKey
PEMBlock, _ := pem.Decode([]byte(privKeyPEM))
if PEMBlock == nil {
log.Fatal("Could not parse Private Key PEM")
}
if PEMBlock.Type != "RSA PRIVATE KEY" {
log.Fatal("Found wrong key type")
}
privkey, err := x509.ParsePKCS1PrivateKey(PEMBlock.Bytes)
if err != nil {
log.Fatal(err)
}
// Compute the sha1
h := sha1.New()
h.Write([]byte(data))
// Sign the data
signature, err := rsa.SignPKCS1v15(rand.Reader, privkey, crypto.SHA1, h.Sum(nil))
if err != nil {
log.Fatal(err)
}
// Print the results
fmt.Print(base64.StdEncoding.EncodeToString(signature))
}
You can verify that these do, indeed, produce the same output and sign the same data in the same way.
We can also use both PHP and Go to verify the signatures. Here are a set of PHP and Go scripts that will both read a signature from standard input and verify it.
<?php
$data = "TEST DATA TO COMPUTE";
$pubKeyPEM = "-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAK3ADijXKw72+YbC5QKK2y7IosCp7rWO
hTf8Ph07ZA0KjdbKtfL/7dmNKjSP6EkC/DJUWfZJNLIlGOtDLLA/AnsCAwEAAQ==
-----END PUBLIC KEY-----";
// Parse public key
$pubkey = openssl_pkey_get_public($pubKeyPEM);
if (!$pubkey) {
exit("Could not parse public key");
}
// Read the signature from stdin
$stdin = file_get_contents("php://stdin");
$signature = base64_decode($stdin);
// Verify the signature
$ok = openssl_verify($data, $signature, $pubkey, OPENSSL_ALGO_SHA1); //SHA1 of $data is computed automatically by this function
if ($ok == 1) {
print "OK\n"; // it worked!
exit(0);
}
else if ($ok == 0) {
exit("Signature verification failed");
}
else {
exit("Error verifying signature");
}
And the same verification code in Go:
package main
import (
"crypto"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"os"
)
const (
data = "TEST DATA TO COMPUTE"
pubKeyPEM = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAK3ADijXKw72+YbC5QKK2y7IosCp7rWO
hTf8Ph07ZA0KjdbKtfL/7dmNKjSP6EkC/DJUWfZJNLIlGOtDLLA/AnsCAwEAAQ==
-----END PUBLIC KEY-----`
)
func main() {
// Parse public key into rsa.PublicKey
PEMBlock, _ := pem.Decode([]byte(pubKeyPEM))
if PEMBlock == nil {
log.Fatal("Could not parse Public Key PEM")
}
if PEMBlock.Type != "PUBLIC KEY" {
log.Fatal("Found wrong key type")
}
pubkey, err := x509.ParsePKIXPublicKey(PEMBlock.Bytes)
if err != nil {
log.Fatal(err)
}
// compute the sha1
h := sha1.New()
h.Write([]byte(data))
// Read the signature from stdin
b64 := base64.NewDecoder(base64.StdEncoding, os.Stdin)
signature, err := ioutil.ReadAll(b64)
if err != nil {
log.Fatal(err)
}
// Verify
err = rsa.VerifyPKCS1v15(pubkey.(*rsa.PublicKey), crypto.SHA1, h.Sum(nil), signature)
if err != nil {
log.Fatal(err)
}
// It verified!
fmt.Println("OK")
}
We can mix and match these different scripts together and verify that PHP and Go are indeed fully compatible:
$ go run go-sign.go | go run go-verify.go
OK
$ go run go-sign.go | php php-verify.php
OK
$ php php-sign.php | php php-verify.php
OK
$ php php-sign.php | go run go-verify.go
OK

Cannot sign a valid gpg key using golang's openpgp packet

I want to sign a public key from ascii armor with a private key in go language.For that I developed following code but the problem is when I check the signature in gpg --check-sigs the signature created by code is shown as "bad Signature".Please Help as I cant figure out any way of solving it.I have already postd on golang-nuts.I am just learning golang for my college project and I am stuck here,Please help.
// signer
package main
import (
"bytes"
"code.google.com/p/go.crypto/openpgp"
"code.google.com/p/go.crypto/openpgp/armor"
"code.google.com/p/go.crypto/openpgp/packet"
"fmt"
)
// This function takes asciiarmored private key which will sign the public key
//Public key is also ascii armored,pripwd is password of private key in string
//This function will return ascii armored signed public key i.e. (pubkey+sign by prikey)
func SignPubKeyPKS(asciiPub string, asciiPri string, pripwd string) (asciiSignedKey string) {
//get Private key from armor
_, priEnt := getPri(asciiPri, pripwd) //pripwd is the password todecrypt the private key
_, pubEnt := getPub(asciiPub) //This will generate signature and add it to pubEnt
usrIdstring := ""
for _, uIds := range pubEnt.Identities {
usrIdstring = uIds.Name
}
fmt.Println(usrIdstring)
errSign := pubEnt.SignIdentity(usrIdstring, &priEnt, nil)
if errSign != nil {
fmt.Println("Signing Key ", errSign.Error())
return
}
asciiSignedKey = PubEntToAsciiArmor(pubEnt)
return
}
//get packet.PublicKey and openpgp.Entity of Public Key from ascii armor
func getPub(asciiPub string) (pubKey packet.PublicKey, retEntity openpgp.Entity) {
read1 := bytes.NewReader([]byte(asciiPub))
entityList, errReadArm := openpgp.ReadArmoredKeyRing(read1)
if errReadArm != nil {
fmt.Println("Reading Pubkey ", errReadArm.Error())
return
}
for _, pubKeyEntity := range entityList {
if pubKeyEntity.PrimaryKey != nil {
pubKey = *pubKeyEntity.PrimaryKey
retEntity = *pubKeyEntity
}
}
return
}
//get packet.PrivateKEy and openpgp.Entity of Private Key from ascii armor
func getPri(asciiPri string, pripwd string) (priKey packet.PrivateKey, priEnt openpgp.Entity) {
read1 := bytes.NewReader([]byte(asciiPri))
entityList, errReadArm := openpgp.ReadArmoredKeyRing(read1)
if errReadArm != nil {
fmt.Println("Reading PriKey ", errReadArm.Error())
return
}
for _, can_pri := range entityList {
smPr := can_pri.PrivateKey
retEntity := can_pri
if smPr == nil {
fmt.Println("No Private Key")
return
}
priKey = *smPr
errDecr := priKey.Decrypt([]byte(pripwd))
if errDecr != nil {
fmt.Println("Decrypting ", errDecr.Error())
return
}
retEntity.PrivateKey = &priKey
priEnt = *retEntity
}
return
}
//Create ASscii Armor from openpgp.Entity
func PubEntToAsciiArmor(pubEnt openpgp.Entity) (asciiEntity string) {
gotWriter := bytes.NewBuffer(nil)
wr, errEncode := armor.Encode(gotWriter, openpgp.PublicKeyType, nil)
if errEncode != nil {
fmt.Println("Encoding Armor ", errEncode.Error())
return
}
errSerial := pubEnt.Serialize(wr)
if errSerial != nil {
fmt.Println("Serializing PubKey ", errSerial.Error())
}
errClosing := wr.Close()
if errClosing != nil {
fmt.Println("Closing writer ", errClosing.Error())
}
asciiEntity = gotWriter.String()
return
}
The code looks roughly ok, except that it really should be stricter with error checking. Panicking on error is better then no error checking at all (because it will usually segfault sometimes later).
The problem is that the implementation of Signature.SignUserId() inside code.google.com/p/go.crypto/openpgp is wrong. It is using the algorithm that signs a key (which is use to certify that the subkey belongs to the primary key) instead of the algorithm that signs a user id.
In addition, while exploring this I realized that PublicKey.VerifyUserIdSignature() is implemented in such a way that it only works for self-signed user ids, because it doesn't use the right public key in the hash.
Bug report, with patch https://code.google.com/p/go/issues/detail?id=7371

Resources