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)
}
Related
I am trying to distribute an application - and need to add a software license to let the user use the application. I need some information to be distributed in the license regarding authorization(this works great using only public/private key)
Pseudo steps
Generate the public/private key
Embed the public key in the code
Create license using encoding
Distribute the license file
Application verifies the license using the embedded public key
6. Problem(using certificate to verify the origin of the public key)
Actual steps:
1 . Generate the public/private key
openssl genrsa -out private_key.pem 3072
openssl rsa -in "private_key.pem -pubout -out public_key.pem
2 - 5 And then embed the private key in code, and read a license file like(code from here):
func VerifyAndExtractPayload(publicKey *rsa.PublicKey, str string) (interface{}, error) {
var err error
str = strings.TrimSpace(str)
if !strings.HasPrefix(str, header) || !strings.HasSuffix(str, footer) {
return nil, errors.New("invalid license key")
}
b64 := strings.Replace(str[len(header):len(str)-len(footer)], "\n", "", -1)
var l license
b, err := base64.StdEncoding.DecodeString(b64)
licenseBuffer := bytes.Buffer{}
licenseBuffer.Write(b)
licenseDecoder := gob.NewDecoder(&licenseBuffer)
if err = licenseDecoder.Decode(&l); err != nil {
return nil, err
}
hashed := sha256.Sum256(l.Payload)
if err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], l.Signature); err != nil {
return nil, err
}
var payload interface{}
payloadBuffer := bytes.Buffer{}
payloadBuffer.Write(l.Payload)
payloadDecoder := gob.NewDecoder(&payloadBuffer)
if err = payloadDecoder.Decode(&payload); err != nil {
return nil, err
}
return payload, nil
}
Then I realised that maybe I should use certificate pinning, so no one can replace the public key.
Pseudo code:
Generate a root certificate using private key
Embed certificate
Generate license like above
Read public key from the certificate
Application verifies the license using the embedded public key
Questions:
How to verify read the public key from the certificate?
And is the public key now verified?
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.")
}
When integrating Sign in with Apple you generate a key in your apple developer account.
It's a file that is named like AuthKey_3JMD5K6.p8 and looks like
-----BEGIN PRIVATE KEY-----
MasdfjalskdasdflaASDFAadsflkjaADSFAewfljasdfljkasefasdflkjasdf
asdfljkasdfASDFASDFoiqretasdoiyjlfsbgREtaREGSDFBREtafsrgAREGfdsgaregR
LKJIOEWFNLasdflkawefjoiasdflk
-----END PRIVATE KEY-----
so I made a var appleKey := MasdfjalskdasdflaASDFAadsflkjaADSFAewfljasdfljkasefasdflkjasdf asdfljkasdfASDFASDFoiqretasdoiyjlfsbgREtaREGSDFBREtafsrgAREGfdsgaregRLKJIOEWFNLasdflkawefjoiasdflk
I've signed jwt with the HMAC-SHA method before which is fairly straightforward but I don't know how to sign a jwt with the ECDSA method.
I wrote my code the same way I did for the HMAC-SHA method but get an error key is of invalid type
So using the the jwt library for golang how can I sign my jwt with ECDSA method?
My code
// generate client secret jwt using apple key
expirationTime := time.Now().Add(5 * time.Minute)
claims := &Claims{
StandardClaims: jwt.StandardClaims {
Audience: "https://appleid.apple.com",
Subject: "com.app.ios",
Issuer: string(appleTeamId),
ExpiresAt: expirationTime.Unix(),
IssuedAt: time.Now().Unix(),
},
}
appleToken := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
appleToken.Header["kid"] = appleKid
signedAppleToken, err := appleToken.SignedString(appleKey)
I now know this isn't how you do it and it's a little bit more complex than that but what is the way to do it?
I found this article that tells you how to manually do it:
http://p.agnihotry.com/post/validating_sign_in_with_apple_authorization_code/
But I'm already using the jwt library for golang for the other part of the token:
https://godoc.org/github.com/dgrijalva/jwt-go
In the github.com/dgrijalva/jwt-go's SigningMethodECDSA.Sign docs you can find:
[...] For this signing method, key must be an ecdsa.PrivateKey struct
So, to put together an example:
p8bytes, err := ioutil.ReadFile("SomeAppleKey.p8")
if err != nil {
log.Println(err)
return
}
// Here you need to decode the Apple private key, which is in pem format
block, _ := pem.Decode(p8bytes)
// Check if it's a private key
if block == nil || block.Type != "PRIVATE KEY" {
log.Println("Failed to decode PEM block containing private key")
return
}
// Get the encoded bytes
x509Encoded := block.Bytes
token := jwt.NewWithClaims(
jwt.SigningMethodES256, // specific instance of `*SigningMethodECDSA`
jwt.StandardClaims{
// ...
},
)
// Now you need an instance of *ecdsa.PrivateKey
parsedKey, err := x509.ParsePKCS8PrivateKey(x509Encoded) // EDIT to x509Encoded from p8bytes
if err != nil {
panic(err)
}
ecdsaPrivateKey, ok := parsedKey.(*ecdsa.PrivateKey)
if !ok {
panic("not ecdsa private key")
}
// Finally sign the token with the value of type *ecdsa.PrivateKey
signed, err := token.SignedString(ecdsaPrivateKey)
if err != nil {
panic(err)
}
fmt.Println(signed) // the signed JWT
Note: as shown in the code snippet, because the key file from Apple is in PEM format, it needs to be decoded first
Warning!
Please be aware that github.com/dgrijalva/jwt-go has been unmaintained for a long time and has critical unfixed bugs. And doesn't support Go modules, before the version 4 (which is just a preview anyway). I strongly recommend to choose an different library for dealing with JWT.
Update June 2021
There is now an official community fork of the library: golang-jwt/jwt blessed by the owner of the original project.
Is it possible to read entity details like name/comment and email from a given armored private / public key using golang openpgp package ?
The following code implements a test that generate a new armored key pair, it then tries to get entity details from it.
However the resulting entity does not contain any identity information.
package main
import (
"log"
"testing"
"github.com/jchavannes/go-pgp/pgp"
"golang.org/x/crypto/openpgp"
)
func TestID(t *testing.T) {
keypair, err := pgp.GenerateKeyPair("test", "tst comment", "test#email.com")
if err != nil {
t.Fatalf("failed to generate keypair: %s", err)
}
var entity *openpgp.Entity
entity, err = pgp.GetEntity([]byte(keypair.PublicKey), []byte(keypair.PrivateKey))
if err != nil {
t.Fatalf("failed to read entity: %s", err)
}
log.Printf("%#v\n", entity.Identities)
}
outputs
2019/05/19 00:02:54 map[string]*openpgp.Identity{"":(*openpgp.Identity)(0xc00006edc0)}
It was expected that entity.Identities contains test (tst comment) <test#email.com>.
I have an rsa.PublicKey which consists of a modulus and a public exponenet in a struct. I want to verify an ssh.Signature which is signed with that key, and I figure if I have an ssh.PublicKey I can call the Verify method on that interface. However, I can't find any classes that implement ssh.PublicKey and support converting from rsa.PublicKey. Do I need to write a proprietary method to do this, or is there some class I'm not finding, or a better way to go about solving my problem?
For context, I got the rsa.PublicKey from an x509.Certificate which came from a yubikey attesting a key in one of its PIV slots.
The NewPublicKey function from the crypto/ssh package http://godoc.org/golang.org/x/crypto/ssh#NewPublicKey can take an *rsa.PublicKey as parameter and returns an instance of the PublicKey interface that includes a Verify method (Verify(data []byte, sig *Signature) error) - http://godoc.org/golang.org/x/crypto/ssh#PublicKey.
The program below illustrates it - we create a new *rsa.PrivateKey and sign a message with it, then convert *rsa.PublicKey to ssh.PublicKey using NewPublicKey function and verify the signature; also check that the signature verification fails if the original data is modified. The error checking is elided for brevity.
package main
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"log"
"golang.org/x/crypto/ssh"
)
func main() {
data := []byte("Hello, world!")
// create a new key as *rsa.PrivateKey
priv, _ := rsa.GenerateKey(rand.Reader, 512)
signer, _ := ssh.NewSignerFromKey(priv)
sig, _ := signer.Sign(rand.Reader, data)
// extract the ssh.PublicKey from *rsa.PublicKey to verify the signature
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
if err := pub.Verify(data, sig); err != nil {
log.Fatalf("publicKey.Verify failed: %v", err)
}
fmt.Printf("Signature OK\n")
// modify the data and make sure we get a failure
data[0]++
if err := pub.Verify(data, sig); err == nil {
log.Printf("publicKey.Verify passed on bad data/signature, expect failure")
}
}
Output:
Signature OK