Certificate pinning - public/private key for license distribution - go

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?

Related

Correct way to conduct private key operation from PKCS11 for mTLS connection in Go

I have successfully imported the private key into the PKCS11 token. The token object looks like this:
Private Key Object; RSA
label: #####
ID: #####
Usage: decrypt, sign, unwrap
Access: sensitive
Public Key Object; RSA 2048 bits
label: #####
ID: #####
Usage: encrypt, verify, wrap
Access: none
I learned that CKA_VALUE usually could not be extracted. And I would like to know what is the correct procedure to create mTLS connection by using pkcs#11 private key and certificate.
I learned that CKA_VALUE usually could not be extracted
Just in case, check out ThalesIgnite/crypto11 exportDSAPublicKey() which does export pkcs11.Attribute, including pcs11.CKA_VALUE, using the public key.
(CKA_VALUE is one of the ECDSA private key objects)
For mTLS, check if miekg/pkcs11 can help (not tested).
It can at least help creating a tlsConfig based on private key and certificate.
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{
{
PrivateKey: privateKey,
Cert: cert,
},
},
}
Those should be extracted from your pkcs11 file:
privateKey, err := p.FindObject(session, []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
pkcs11.NewAttribute(pkcs11.CKA_LABEL, "private_key_label"),
})
if err != nil {
panic(err)
}
cert, err := p.FindObject(session, []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
pkcs11.NewAttribute(pkcs11.CKA_LABEL, "certificate_label"),
})
if err != nil {
panic(err)
}
That does not seem to require CKA_VALUE

Generate DSA Keys for OpenSSH in Golang

I read through a few examples to generate DSA keys for OpenSSH in Go. And my clean code snippet and outputs are listed below.
It has two problems:
for 2048-bit length, the public key can't be loaded via ssh.ParseAuthorizedKey, for error: "ssh: no key found".
OpenSSH client and GitHub SSH can't accept it.
Code snippets:
// GenerateDSAKeys generates DSA public and private key pair with given size for SSH.
func GenerateDSAKeys(bitSize int, passphrase string) (pubKey string, privKey string, err error) {
params := new(dsa.Parameters)
// see http://golang.org/pkg/crypto/dsa/#ParameterSizes
if err = dsa.GenerateParameters(params, rand.Reader, dsaSizeFromLength(bitSize)); err != nil {
return
}
var privateKey dsa.PrivateKey
privateKey.PublicKey.Parameters = *params
// this generates a public & private key pair
if err = dsa.GenerateKey(&privateKey, rand.Reader); err != nil {
return
}
// generate public key
var publicKey ssh.PublicKey
if publicKey, err = ssh.NewPublicKey(&privateKey.PublicKey); err != nil {
return
}
// encode public key
pubBytes := ssh.MarshalAuthorizedKey(publicKey)
// encode private key
var (
bytes []byte
privBytes []byte
)
if bytes, err = asn1.Marshal(privateKey); err != nil {
return
}
privBytes, err = encodePEMBlock(&pem.Block{
Type: "DSA PRIVATE KEY",
Bytes: bytes,
}, passphrase)
if err != nil {
return
}
return string(pubBytes), string(privBytes), nil
}
func dsaSizeFromLength(l int) dsa.ParameterSizes {
switch l {
case 1024:
return dsa.L1024N160
case 2048:
return dsa.L2048N224
case 3072:
return dsa.L3072N256
default:
return dsa.L2048N256
}
}
Output for 1024:
bash-3.2$ cat id_dsa.pub
ssh-dss AAAAB3NzaC1kc3MAAACBAIbuW6wh5hU2W96tEyo7xPDZmslWnsyQBYtf4SSTeHOMTNTxznlMujjkmCuqKJ04BlHFi+ner2qzCd1GkzGhHrretQw1z5Ew8ysAGsmbb9Yc6BMTyhJkrQ2lIR5Vmqa9Ukx0PM/HdXa6GieYZWxyabN5IjN4SESXZ6G7Jb4StwCfAAAAFQCnj+WurTgW5aXGYePwhBYAWX01WwAAAIBHQpZSsJGKsEII7Oe6RMz5ek27ydHPnrLDqVOmEKqHrIrsYXoTHaMOLW+fvsDBC3q0EWMBNgu4IAZe3eL2Gx2r94+DS+GTfECWiJ3+O76DTAwDcB6fXxdYxG88nJMzCkcMZEmOjbIpuiGP5NC+OOqBv2DEHw7YEWs8Kx4T71mE0AAAAIEAhg/OB98FTcYX/gXEclKqqDByHzHIt71Y7DFZnxnmd4DFEZp/WzJ6VT2jTNMyWKRT9h0SITORLKNxTCM3ZLhQYVBinOsjIzZ8YlYYOxTn2mWntWzmsdhUeBx+aNCwuhz/cMHjbhWJWVXRDfgzPf9IHtF1FLgjtMiz6/xwzAMEPlE= vej#Vej-Work-MBP.local
bash-3.2$ cat id_dsa
-----BEGIN DSA PRIVATE KEY-----
MIIBwDCCAaYwggEeAoGBAIbuW6wh5hU2W96tEyo7xPDZmslWnsyQBYtf4SSTeHOM
TNTxznlMujjkmCuqKJ04BlHFi+ner2qzCd1GkzGhHrretQw1z5Ew8ysAGsmbb9Yc
6BMTyhJkrQ2lIR5Vmqa9Ukx0PM/HdXa6GieYZWxyabN5IjN4SESXZ6G7Jb4StwCf
AhUAp4/lrq04FuWlxmHj8IQWAFl9NVsCgYBHQpZSsJGKsEII7Oe6RMz5ek27ydHP
nrLDqVOmEKqHrIrsYXoTHaMOLW+fvsDBC3q0EWMBNgu4IAZe3eL2Gx2r94+DS+GT
fECWiJ3+O76DTAwDcB6fXxdYxG88nJMzCkcMZEmOjbIpuiGP5NC+OOqBv2DEHw7Y
EWs8Kx4T71mE0AKBgQCGD84H3wVNxhf+BcRyUqqoMHIfMci3vVjsMVmfGeZ3gMUR
mn9bMnpVPaNM0zJYpFP2HRIhM5Eso3FMIzdkuFBhUGKc6yMjNnxiVhg7FOfaZae1
bOax2FR4HH5o0LC6HP9wweNuFYlZVdEN+DM9/0ge0XUUuCO0yLPr/HDMAwQ+UQIU
RhBIeYLqxL4PJgENdgtjfjxDX80=
-----END DSA PRIVATE KEY-----
For 2048, output:
bash-3.2$ cat id_dsa.pub
ssh-dss AAAAB3NzaC1kc3MAAAEBAKbfrtje1KqerXL0DFg35Ouou2NP08vsuqCbv/cr65X2/AMaDy/Zdikq1rHw1ScWzre0vGTDwnGsFJgNDf3p0ckJqxMN9kop+8a+M1nuQ39LnkHATM+x58jgJaC06VZisLQuMoJvzdOlMPKb1v9TKfSiI0XZujhS5RVl4DkqABEXFsNXOHTIGDXToq80IPf0KV01qvOGBnICzhPkXw5HcCF9rmVD5aPfADjHZfUTt9PJfFcPk06mmfvpOEBfWgaluIDp0Wf5DsKu0RxlZVm4x3iP43hQfJ8NaFRyOKhNxd0r5t5SDUubF2Z37v0QNIvLZPrKsE0AywNkDZ66bf3HigMAAAAdAMjEhWBLtQ+nWfJIO16rpLQZQKBpXePefLrztpUAAAEALJsfHywD5PSfeFUGs901I1Z/yvOuyHQFxJGXEAQJ08CBAGvwcGA1M41sDnbrbOFm8ol91xcnUqW0HMlwDpdHIBxgytlnKlsEw69GqCx1dJAv8LnDokK/zV406I0LLwXzQ3QvfSMtM3VDsIA/jInd273LlXFEG4dmkG1mIP7SBgbzy7jZ4LpH5y8ZSUMFlZ4dXTURj4TAe/ByScaXoVO8QDLaZq5EiSRAomUxLvnwTNJHt/YqQog5EWhQNBUKp5HbPS7pk+KujaUpYwXNZZuhqjp4dmaBYppwCZnrqvsHKq6p8BCt4ZQ/589q0D1Mx0pbgI/sQLvib5jSHJ0jKq7xrAAAAQBzxG2gTr89VeVJBMsconclvXw7vYDxQGBGhZBmKQ6e/s8swxJzzUbpRzoeYGEL1nE28e4Apw6pQFY3XkuvklYTwNHPLwG/5FJV7fc2HJgwVg8/wf5hUrzqlKK9Xhjm2369iKnGig/u28e79oOWgdbqfrjWpNih/aVL60ApdWgxmxC61PGppJ2AXNubaDynhMOx1q+lSH+unu+kLml+c+lbqwZYXmI0t+gFzyyu+pL8DpVEhAG8P/AMCXMDR3JmQhWF7fKiP1QdDMidUODM8keVQaK5txIp9EUAThk0ErigG5ZsIL5V2Z4UistlwQ4WiZYJmz/IlD50+PtkjwBFiA6z vej#Vej-Work-MBP.local
bash-3.2$ cat id_dsa
-----BEGIN DSA PRIVATE KEY-----
MIIDUzCCAzAwggIoAoIBAQCm367Y3tSqnq1y9AxYN+TrqLtjT9PL7Lqgm7/3K+uV
9vwDGg8v2XYpKtax8NUnFs63tLxkw8JxrBSYDQ396dHJCasTDfZKKfvGvjNZ7kN/
S55BwEzPsefI4CWgtOlWYrC0LjKCb83TpTDym9b/Uyn0oiNF2bo4UuUVZeA5KgAR
FxbDVzh0yBg106KvNCD39CldNarzhgZyAs4T5F8OR3Ahfa5lQ+Wj3wA4x2X1E7fT
yXxXD5NOppn76ThAX1oGpbiA6dFn+Q7CrtEcZWVZuMd4j+N4UHyfDWhUcjioTcXd
K+beUg1Lmxdmd+79EDSLy2T6yrBNAMsDZA2eum39x4oDAh0AyMSFYEu1D6dZ8kg7
XquktBlAoGld4958uvO2lQKCAQAsmx8fLAPk9J94VQaz3TUjVn/K867IdAXEkZcQ
BAnTwIEAa/BwYDUzjWwOduts4WbyiX3XFydSpbQcyXAOl0cgHGDK2WcqWwTDr0ao
LHV0kC/wucOiQr/NXjTojQsvBfNDdC99Iy0zdUOwgD+Mid3bvcuVcUQbh2aQbWYg
/tIGBvPLuNngukfnLxlJQwWVnh1dNRGPhMB78HJJxpehU7xAMtpmrkSJJECiZTEu
+fBM0ke39ipCiDkRaFA0FQqnkds9LumT4q6NpSljBc1lm6GqOnh2ZoFimnAJmeuq
+wcqrqnwEK3hlD/nz2rQPUzHSluAj+xAu+JvmNIcnSMqrvGsAoIBAHPEbaBOvz1V
5UkEyxyidyW9fDu9gPFAYEaFkGYpDp7+zyzDEnPNRulHOh5gYQvWcTbx7gCnDqlA
VjdeS6+SVhPA0c8vAb/kUlXt9zYcmDBWDz/B/mFSvOqUor1eGObbfr2IqcaKD+7b
x7v2g5aB1up+uNak2KH9pUvrQCl1aDGbELrU8amknYBc25toPKeEw7HWr6VIf66e
76QuaX5z6VurBlheYjS36AXPLK76kvwOlUSEAbw/8AwJcwNHcmZCFYXt8qI/VB0M
yJ1Q4MzyR5VBorm3Ein0RQBOGTQSuKAblmwgvlXZnhSKy2XBDhaJlgmbP8iUPnT4
+2SPAEWIDrMCHQDGpe828eiBg56q0tQup2r3UpgiXevRNEuF0Gbw
-----END DSA PRIVATE KEY-----
Can you please tell me how resolve it, and make it works?
OpenSSH stopped supporting DSA (aka ssh-dss) by default over 5 years ago; see
https://security.stackexchange.com/questions/112802/why-openssh-deprecated-dsa-keys
https://security.stackexchange.com/questions/146379/does-ssh-support-dsa-with-2048-bit-keys
https://superuser.com/questions/1016989/ssh-dsa-keys-no-longer-work-
https://unix.stackexchange.com/questions/247612/ssh-keeps-skipping-my-pubkey-
You can reenable it on your client following instructions on Qs like those or the openssh website or documentation, but github won't accept it so you can't make that work. (You could set up your own server, and use it there.)
I don't know why some go code (library?) doesn't accept the 2048-bit publickey -- if you give a reference I could try to loook -- but (both) your privatekey files are incorrect, I'm guessing due to the structuring used in crypto/dsa shown at your link. The key is being marshalled (serialized) to ASN.1 as nested sequences, namely SEQUENCE { pub = SEQUENCE { params = SEQUENCE {p,q,g}, y }, x } which conceptually is a reasonable structure, but the PEM (or pseudo-PEM) type DSA PRIVATE KEY is de-facto defined by SSLeay-now-OpenSSL as using a single level: SEQUENCE { p,q,g, y, x } .

How to sign JWT with ECDSA method using jwt golang package - Sign in with Apple

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.

Encrypt client-private-key with RSA-2048 server-public-key

I need to encrypt client-private-key with RSA-2048 server-public-key.
I know that private key is obviously longer than the public key and I'm not sure if it's possible... but I saw a similar task was done in Python, so I want to know your opinion.
/* main */
clientPrivateKey, _ := generateRsaPair(2048)
_, serverPublicKey := generateRsaPair(2048)
clientPrivateKeyAsByte := privateKeyToBytes(clientPrivateKey)
encryptWithPublicKey(clientPrivateKeyAsByte, serverPublicKey)
Fatal error crypto/rsa: message too long for RSA public key size
/* Functions */
func generateRsaPair(bits int) (*rsa.PrivateKey, *rsa.PublicKey) {
privkey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
log.Error(err)
}
return privkey, &privkey.PublicKey
}
func encryptWithPublicKey(msg []byte, pub *rsa.PublicKey) []byte {
hash := sha512.New()
ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
checkError(err)
return ciphertext
}
func privateKeyToBytes(priv *rsa.PrivateKey) []byte {
privBytes := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
},
)
return privBytes
}
If you want to encrypt something larger than the key size then you can simply use hybrid encryption. You first encrypt (or wrap, if a specific wrapping operation is available) the encoding of the private key using a random AES key, e.g. using AES-CBC or AES-CTR (with an all zero IV). Then you encrypt that AES key using the private key. The ciphertext consists of the encrypted AES key followed by the encrypted data - in this case an RSA private key.
Note however that a private key should really be managed by one entity. It is not called a private key for nothing. Distributing private keys is generally considered bad key management practice.

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)
}

Resources