AWS SNS signature verification with GO - go

I want to implement AWS SNS signature verification in GO. Here is the signature verification tutorial provided by AWS.
However, there are some points I can not get it.
7: Generate the derived hash value of the Amazon SNS message. Submit the Amazon SNS message, in canonical format, to the same hash function used to generate the signature.
How to derived the hash value? Which hash function should I use?
8: Generate the asserted hash value of the Amazon SNS message. The asserted hash value is the result of using the public key value (from step 3) to decrypt the signature delivered with the Amazon SNS message.
How to get the asserted hash value?
Here is my code, I have a struct for notification:
type Notification struct {
Message string
MessageId string
Signature string
SignatureVersion string
SigningCertURL string
SubscribeURL string
Subject string
Timestamp string
TopicArn string
Type string
UnsubscribeURL string
}
and I've already generated the canonical string:
signString := fmt.Sprintf(`Message
%v
MessageId
%v`, self.Message, self.MessageId)
if self.Subject != "" {
signString = signString + fmt.Sprintf(`
Subject
%v`, self.Subject)
}
signString = signString + fmt.Sprintf(`
Timestamp
%v
TopicArn
%v
Type
%v`, self.Timestamp, self.TopicArn, self.Type)
Decode signature from base64
signed, err := base64.StdEncoding.DecodeString(self.Signature)
Get the certificate from .pem
resp, _ := http.Get(self.SigningCertURL)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
p, _ := pem.Decode(body)
cert, err := x509.ParseCertificate(p.Bytes)
Now, how can I verify the signature with my canonical string? Is the following code right?
cert.CheckSignature(x509.SHA1WithRSA, signed, []byte(signString))
I always get crypto/rsa: verification error from above code.
Thanks!

I know this is a really old question, but I had the same problems as the reporter so I took a day to figure this out with the assistance of AWS. I have open sourced my work as an external library, now available here.
You can use it like this (notificationJson is a JSON string):
import (
"encoding/json"
"fmt"
"github.com/robbiet480/go.sns"
)
var notificationPayload sns.Payload
err := json.Unmarshal([]byte(notificationJson), &notificationPayload)
if err != nil {
fmt.Print(err)
}
verifyErr := notificationPayload.VerifyPayload()
if verifyErr != nil {
fmt.Print(verifyErr)
}
fmt.Print("Payload is valid!")
Thanks for your initial work on this lazywei, I based my library on your above code!

In the context of this discussion about Amazon SNS message signature verification, it’s also important to notice that Amazon SNS now supports message signatures based on SHA256 hashing:
https://aws.amazon.com/about-aws/whats-new/2022/09/amazon-sns-supports-message-signatures-based-sha256-hashing/
Here's the launch blog post:
https://aws.amazon.com/blogs/security/sign-amazon-sns-messages-with-sha256-hashing-for-http-subscriptions/

Related

How to decode hex to ASN.1 in golang

I have an ECDSA public key that that is returned to me from an HSM in ASN.1 DER format. I need to create a bitcoin compatible key 33 byte. When I print out key in hex.EncodeToString(pubkey) I get the following output:
3056301006072a8648ce3d020106052b8104000a034200049bb8e80670371f45508b5f8f59946a7c4dea4b3a23a036cf24c1f40993f4a1daad1716de8bd664ecb4596648d722a4685293de208c1d2da9361b9cba74c3d1ec
I use an online decoder here: https://holtstrom.com/michael/tools/asn1decoder.php
And it outputs:
0x049bb8e80670371f45508b5f8f59946a7c4dea4b3a23a036cf24c1f40993f4a1daad1716de8bd664ecb4596648d722a4685293de208c1d2da9361b9cba74c3d1ec
I can then take that and hex.DecodeString(str) which gives me the necessary format to input this into addrPubKey, err := btcutil.NewAddressPubKey(bs, &chaincfg.TestNet3Params).
How do I decode this in golang to get the 0x049... output?
Thanks
The first thing we need is to use the encoding/asn1 package from the standard library.
You only have to give go the right struct to decode into. From your link we can see that we have a SEQUENCE that contains another SEQUENCE with two OBJECTIDENTIFIER and a BITSTRING. In go this will be:
type Ids struct {
OBi1 asn1.ObjectIdentifier
OBi2 asn1.ObjectIdentifier
}
type PubKey struct {
Id Ids
Bs asn1.BitString
}
Now we only have to UnMarshall the data to this structure:
str := `3056301006072a8648ce3d020106052b8104000a034200049bb8e80670371f45508b5f8f59946a7c4dea4b3a23a036cf24c1f40993f4a1daad1716de8bd664ecb4596648d722a4685293de208c1d2da9361b9cba74c3d1ec`
bstring, err := hex.DecodeString(str)
if (err != nil) {
panic(err)
}
var decode PubKey
_, err = asn1.Unmarshal(bstring, &decode)
if (err != nil) {
panic(err)
}
fmt.Println(hex.EncodeToString(decode.Bs.Bytes))
Note that you don't have to encode the string to hex and back again, since Unmarshall accepts a byte array
This will print the expected result:
049bb8e80670371f45508b5f8f59946a7c4dea4b3a23a036cf24c1f40993f4a1daad1716de8bd664ecb4596648d722a4685293de208c1d2da9361b9cba74c3d1ec
Once again you probably don't need to encode to string.

Failing to create JWT token when entering a custom email claim

I am trying to generate a JWT token using Go and I created the following function. I need to add the email address in jwt but as I do this I get an error saying key is of invalid type
func GenerateUserToken(expiryHours time.Duration, email string, secretKey string) (string, error) {
// Create a new token object, specifying signing method and the claims
// you would like it to contain.
token := jwt.New(jwt.SigningMethodES256)
claims := token.Claims.(jwt.MapClaims)
claims["exp"] = time.Now().Add(time.Hour * expiryHours).Unix()
claims["email"] = email
tokenStr, err := token.SignedString([]byte(secretKey))
if err != nil {
return "", err
}
return tokenStr, nil
}
What could be the reason for this? What mistake am I making?
JWT supports many signing algorithms, and that's a challenge for this particular API: depending on the signing algorithm, it expects to see a key matching that algorithm.
If you take a look at the API docs for this particular library:
https://godoc.org/github.com/dgrijalva/jwt-go
You'll see SigningMethodXXX types. These are signers selected by the signing method you pick. For ES256, it uses SigningMethodECDSA:
https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA
If you look at the Sign method docs, you'll see that it says:
For this signing method, key must be an ecdsa.PrivateKey struct
which you can parse from a PEM file using:
https://godoc.org/github.com/dgrijalva/jwt-go#ParseECPrivateKeyFromPEM
For example:
pk, err:= jwt.ParseECPrivateKeyFromPEM(pemData)
tokenStr, err := token.SignedString(pk)
This should give you a signed token with ES256.
So, you have to first figure out what kind of key you have. If you have a PEM encoding of a ECDSA key in a string, then use this method to parse it and pass the resulting private key to the signer.
If however you simply have a string secret key (like a password) and you'll share this secret key with the users of the JWT, then you can use a HMAC key. A HMAC key is simply a byte array secret that you share with your users so they can validate that the JWT was signed by you. Simply change the SigningMethod to one of the constants in:
https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC
Then, your code as it is will work with the exception that you have to change the signing method to something like jwt.New(jwt.SigningMethodHS256)

Bip32 Keys encryption in Golang Nacl Secretbox

I have created the Assymetric struct which generates a BIP39 and BIP32 Master public and private key. Now I want to use these keys and their derived children to encrypt files or strings depending upon the use case. Please correct me if I am wrong — the keys generated from Mnemonics BIP32 are Elliptic curve keys(secp256k1). I am trying to use the NACL Go implementation to encrypt text with these keys, but it only accepts keys which are 32 bytes but the keys that are being generated from Mnemonics are 256 Bytes. Please help. The bip32 implementation can be found here https://github.com/tyler-smith/go-bip32/blob/master/bip32.go#L59
After going through the implementation, I have found out that the resulting key is a combination of Key and chaincode, key.Key is a 32byte key that must be used for encryption. Just wanted to check if this is the right way to do it and guarantees security.
package encryption
import (
"github.com/tyler-smith/go-bip39"
"github.com/tyler-smith/go-bip32"
"crypto/ecdsa"
"crypto/elliptic"
"golang.org/x/crypto/nacl/secretbox"
"crypto/rand"
"encoding/hex"
"io"
"log"
)
type Encryption interface {
GenerateMnemonic() ( *bip32.Key, *bip32.Key)
}
type Assymetric struct{
RootPrivateKey *bip32.Key
RootPublicKeyKey *bip32.Key
RootMnemonic string
RootPassphrase string
}
func (c *Assymetric) GenerateMnemonic() (*bip32.Key, *bip32.Key){
entropy, _ := bip39.NewEntropy(256)
mnemonic, _ := bip39.NewMnemonic(entropy)
seed := bip39.NewSeed(mnemonic, c.RootPassphrase)
rootPrivateKey, _ := bip32.NewMasterKey(seed)
rootPublicKey := rootPrivateKey.PublicKey()
// Display mnemonic and keys
//c.RootMnemonic = mnemonic
c.RootPrivateKey = rootPrivateKey
c.RootPublicKeyKey = rootPublicKey
key, _ := rootPrivateKey.NewChildKey(0)
log.Printf("Private key 0 is %s", key)
log.Printf("Public key 0 is %s", key.PublicKey())
// You must use a different nonce for each message you encrypt with the
// same key. Since the nonce here is 192 bits long, a random value
// provides a sufficiently small probability of repeats.
log.Printf("This is the secret key %s", secretKey)
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
panic(err)
}
log.Printf("This is the nonce %s", nonce)
var a [32]byte
copy(a[:], key.Key)
log.Printf("\nlen=%d cap=%d %v\n", len(a), cap(a), a)
encrypted := secretbox.Seal(nonce[:], []byte("hello world"), &nonce, &a)
log.Println(encrypted)
return rootPrivateKey, rootPublicKey

Verifying signature of payload in Go

I am verifying the identity of the sender of a piece of data. I am provided the RSA public key in a PEM format and I know the data is passed through the SHA256 hashing function. The equivalent verification on the node.js platform:
Ticket.prototype.verify = function (ticket) {
if (!ticket) return null;
var pubkey = fs.readFileSync('/etc/SCAMP/auth/ticket_verify_public_key.pem');
var parts = ticket.split(',');
if (parts[0] != '1') return null;
var sig = new Buffer(parts.pop().replace(/-/g,'+').replace(/_/g,'/'), 'base64');
var valid = crypto.createVerify('sha256').update( new Buffer(parts.join(',')) ).verify( pubkey, sig )
Which can verify:
1,3063,21,1438783424,660,1+20+31+32+34+35+36+37+38+39+40+41+42+43+44+46+47+48+50+53+56+59+60+61+62+67+68+69+70+71+75+76+80+81+82+86+87+88+102+104+105+107+109+110+122+124,PcFNyWjoz_iiVMgEe8I3IBfzSlUcqUGtsuN7536PTiBW7KDovIqCaSi_8nZWcj-j1dfbQRA8mftwYUWMhhZ4DD78-BH8MovNVucbmTmf2Wzbx9bsI-dmUADY5Q2ol4qDXG4YQJeyZ6f6F9s_1uxHTH456QcsfNxFWh18ygo5_DVmQQSXCHN7EXM5M-u2DSol9MSROeBolYnHZyE093LgQ2veWQREbrwg5Fcp2VZ6VqIC7yu6f_xYHEvU0-ZsSSRMAMUmhLNhmFM4KDjl8blVgC134z7XfCTDDjCDiynSL6b-D-
by splitting on the last ,. The left side of the split is the ticket data I care about, the right side is the signature which I need to verify before I can use the ticket data.
I have tried to port the logic to go:
func TestSigVerification(t *testing.T) {
block, _ := pem.Decode(signingPubKey)
if block == nil {
t.Errorf("expected to block to be non-nil CERTIFICATE", block)
}
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
t.Errorf("could not parse PKIXPublicKey: `%s`", key)
}
rsaPubKey, ok := key.(*rsa.PublicKey)
if !ok {
t.Errorf("couldn't cast to rsa.PublicKey!")
}
ticket,_ := ParseTicketBytes(fullTicketBytes)
h := sha256.New()
h.Write(ticketBytes)
digest := h.Sum(nil)
err = rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA256, digest, ticket.Signature)
if err != nil {
t.Errorf("could not verify ticket: `%s` (digest: `%v`)", err, digest )
}
}
But I'm pretty sure VerifyPKCS1v15 is not equivalent to node's crypto.createVerify and this test case fails. What should I be using? How can I use the public key to decrypt the signature and get the sha256? once I have the decrypted sha256 value I could just do a basic comparison with the sha256 I have generated.
Here's a runnable playground example: http://play.golang.org/p/COx2OG-AiA
Though I couldn't get it to work, I suspect the issue is that you'll need to convert the sig from base64 into bytes via the base64 encoding. See this example here:
http://play.golang.org/p/bzpD7Pa9mr (especially lines 23 to 28, where they have to encode the sig from bytes to base64 string to print it, then feed the byte version into the sig check, indicating that you have to use the byte version and not base64 string)
Which I stumbled across on this post:
Signing and decoding with RSA-SHA in GO
I've found that golang generally expects bytes everywhere in byte encoding. I tried to decode your sig string from base64 to bytes however, even after replacing the '-' with '+' and the '_' with '/' it still won't work, for reasons unknown to me:
http://play.golang.org/p/71IiV2z_t8
At the very least this seems to indicate that maybe your sig is bad? If it isn't valid base64? I think if you can find a way to solve this the rest should work!
I tried running your code and it doesn't look like your ticket is well formed. It's not long enough. Where did you get the value from?
Double check that ticket -- that may just be enough to get you going.

How to store ECDSA private key in Go

I am using the ecdsa.GenerateKey method to generate a private/public key pair in Go. I would like to store the private key in a file on the users computer, and load it whenever the program starts. There is a method elliptic.Marshal that marshals the public key, but nothing for the private key. Should I simply roll my own, or is there a recommended way to store the private key?
Here is a code sample that demonstrates encoding and decoding of keys in Go. It helps to know that you need to connect couple of steps. Crypto algorithm is the fist step, in this case ECDSA key. Then you need standard encoding, x509 is most commontly used standard. Finally you need a file format, PEM is again commonly used one. This is currently most commonly used combination, but feel free to substitute any other algoriths or encoding.
func encode(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) (string, string) {
x509Encoded, _ := x509.MarshalECPrivateKey(privateKey)
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509Encoded})
x509EncodedPub, _ := x509.MarshalPKIXPublicKey(publicKey)
pemEncodedPub := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: x509EncodedPub})
return string(pemEncoded), string(pemEncodedPub)
}
func decode(pemEncoded string, pemEncodedPub string) (*ecdsa.PrivateKey, *ecdsa.PublicKey) {
block, _ := pem.Decode([]byte(pemEncoded))
x509Encoded := block.Bytes
privateKey, _ := x509.ParseECPrivateKey(x509Encoded)
blockPub, _ := pem.Decode([]byte(pemEncodedPub))
x509EncodedPub := blockPub.Bytes
genericPublicKey, _ := x509.ParsePKIXPublicKey(x509EncodedPub)
publicKey := genericPublicKey.(*ecdsa.PublicKey)
return privateKey, publicKey
}
func test() {
privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
publicKey := &privateKey.PublicKey
encPriv, encPub := encode(privateKey, publicKey)
fmt.Println(encPriv)
fmt.Println(encPub)
priv2, pub2 := decode(encPriv, encPub)
if !reflect.DeepEqual(privateKey, priv2) {
fmt.Println("Private keys do not match.")
}
if !reflect.DeepEqual(publicKey, pub2) {
fmt.Println("Public keys do not match.")
}
}
I believe the standard format for those keys is to use the X.509 ASN.1 DER representation. See http://golang.org/pkg/crypto/x509/#MarshalECPrivateKey and http://golang.org/pkg/crypto/x509/#ParseECPrivateKey.
I adapted a really quick and dirty way to do it, as suggested by one of the geth team in late '15 in my library https://github.com/DaveAppleton/ether_go
it is a far simpler solution (but puts keys in plain sight)

Resources