Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/crypto/openpgp/armor: armored output differs from gpg or sks armored output #7241

Closed
gopherbot opened this issue Jan 31, 2014 · 8 comments
Closed

Comments

@gopherbot
Copy link

by pruthvirajsinh7:

While using armor.Encode to encode a valid public key to output ascii armor the output
starts with

xsBNBFLih........rest of the key

The armor gets accepted without any problem in gpg --import and sks, and doesn't show
any anomalies in key extracted from armor.But when we do gpg --export -a  or  get key
from SKS,the key armor starts with following.

mQENBFLih.......rest of the key

Can anyone explain me why is it happening.As armor on the server/gpg and armor of the
golang differs it alerts the users.(though key remains same)
@davecheney
Copy link
Contributor

Comment 1:

Hello,
Thank you for your report, could you please include a short sample program that
demonstrates the issue.

Labels changed: added release-none, repo-crypto.

Status changed to WaitingForReply.

@gopherbot
Copy link
Author

Comment 2 by pruthvirajsinh7:

Greetings!
Please Checkout the output of hockeypuck(Developed in golang) and SKS for same key.
e.g.
output of 
http://hockeypuck.gazzang.net/pks/lookup?op=get&search=0x4f283d2f192189397419c3a61c2402a6529472b4
and
http://ha.pool.sks-keyservers.net/pks/lookup?op=get&search=0x1C2402A6529472B4
differs though they are the same key.
I want to sign a public key from ascii armor with a private key in go language using
following code.The armor produced by following code gets parsed by gpg but the
signature,when checked is reported as bad signature.Is it also a bug or I am doing
something wrong,please tell me.Thank you.
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"
)
// 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)
   usrIdstring := ""
   for _, uIds := range pubEnt.Identities {
       usrIdstring = uIds.Name
    }
   pubEnt.SignIdentity(usrIdstring, &priEnt, nil) //This will generate signature and add it to pubEnt
   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, _ := openpgp.ReadArmoredKeyRing(read1)
   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, _ := openpgp.ReadArmoredKeyRing(read1)
    for _, can_pri := range entityList {
       smPr := can_pri.PrivateKey
       retEntity := can_pri
       if smPr == nil {
          return
       }
       priKey = *smPr
       priKey.Decrypt([]byte(pripwd))
       retEntity.PrivateKey.Decrypt([]byte(pripwd))
       retEntity.PrivateKey = &priKey
       priEnt = *retEntity
    }
    return
}
//Create ASscii Armor from openpgp.Entity
func PubEntToAsciiArmor(pubEnt openpgp.Entity) (asciiEntity string) {
    gotWriter := bytes.NewBuffer(nil)
    wr, _ := armor.Encode(gotWriter, openpgp.PublicKeyType, nil)
    pubEnt.Serialize(wr)
    wr.Close()
    asciiEntity = gotWriter.String()
    return
}

@davecheney
Copy link
Contributor

Comment 3:

Thanks for providing the sample code. 
You are ignoring many errors in that code, please check them and supply an updated
sample.

@gopherbot
Copy link
Author

Comment 4 by pruthvirajsinh7:

Here you go sir.Following code has all the errors checked.The code still doesn't give
any errors but every time it is giving different output in armor and hash of the armor
changes i.e. Each time new 4 character at the end of the armor.e.g. in one run its
===7lfn and in next its ===OFlB.
Thanks a lot for such quick replies , Please forgive if I didn't report bug in well
formatted manner as this is my first bug report (I am a grad student).Also did you check
the outputs of the hockeypuck and sks? It is showing different output for same key as
well.
// 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
}

@gopherbot
Copy link
Author

Comment 5 by pruthvirajsinh7:

Following are Signatures objects. one generated by code and another is by  gpg
--sign-keys command.
Code Signed PubEnt Signature  &{16 1 5 [4 16 1 8 0 16 5 2 82 241 227 77 9 16 70 75 47
172 246 211 247 238 4 255 0 0 0 22] [234 4] 2014-02-05 12:37:57.352008112 +0530 IST
{[159 214 69 22 59 50 52 28 123 52 201 139 207 208 187 92 21 199 239 160 2 229 1 252 14
159 212 43 109 99 12 226 240 178 115 226 121 26 214 43 16 234 76 156 230 125 151 14 56
228 194 28 68 109 224 11 60 201 14 104 203 38 155 227 177 236 124 211 206 7 167 103 47
233 234 177 9 80 191 215 250 83 170 20 100 190 154 159 112 178 166 240 70 171 101 96 57
34 62 109 143 245 88 99 116 156 107 87 134 127 126 24 225 3 230 130 212 218 97 26 120
174 193 23 149 170 12 213 51 106 201 226 57 103 1 82 70 180 114 125 15 120 37 23 238 4
30 189 93 13 72 207 243 61 226 114 77 226 211 174 42 42 128 209 73 105 111 227 193 160
189 13 206 90 225 62 147 189 132 181 137 53 184 126 204 81 105 156 155 176 19 155 179
122 129 189 42 174 43 123 116 150 64 4 75 149 92 243 135 211 176 23 121 215 117 215 111
214 110 253 136 130 131 36 153 159 79 233 40 181 2 73 254 136 171 200 3 23 75 144 210 73
56 40 109 209 74 63 46 58 229 76 217 27 51 135] 2048} {[] 0} {[] 0} {[] 0} {[] 0} []
<nil> <nil> [] [] [] 0xc200070748 <nil> false false false false false
[{true 2 false [82 241 227 77]} {true 16 false [70 75 47 172 246 211 247 238]}]}
GPG Signed PubEnt Signature &{16 1 3 [4 16 1 2 0 6 5 2 82 241 226 146 4 255 0 0 0 12]
[252 35] 2014-02-05 12:34:50 +0530 IST {[159 21 206 239 129 165 253 32 213 116 68 133
109 248 118 127 105 147 112 54 118 178 237 13 0 111 254 97 174 248 98 90 193 207 185 78
69 150 167 71 251 124 84 138 171 147 134 153 91 118 213 101 139 144 24 118 126 185 202
144 110 252 94 58 242 13 135 216 114 239 86 29 73 50 225 60 156 86 225 69 115 179 7 127
113 235 223 205 196 224 136 201 190 119 245 8 112 83 80 127 106 56 228 164 108 1 175 232
108 33 88 159 122 138 61 244 100 177 172 211 46 227 191 131 214 220 244 253 119 157 134
42 67 14 247 115 223 147 128 200 96 4 160 19 134 4 47 208 201 217 149 162 196 138 166
186 80 74 28 223 19 190 182 103 111 230 230 253 241 154 254 244 250 249 220 183 209 55 6
250 36 225 230 152 100 2 248 91 83 164 147 200 205 111 235 77 70 4 255 45 184 108 89 112
55 15 144 179 76 250 229 42 62 70 204 109 229 81 227 46 110 153 95 45 60 79 43 193 76
245 148 62 84 113 50 30 51 44 33 119 34 108 19 224 179 238 23 2 80 58 113 150 209 117 60
180] 2048} {[] 0} {[] 0} {[] 0} {[] 0} [{true 2 false [82 241 226 146]} {false 16 false
[70 75 47 172 246 211 247 238]}] <nil> <nil> [] [] [] 0xc200000150
<nil> false false false false false []}
I observed following differences.
1. rawSubpackets and outSubPackets are output differently in gpg and go.Content seems to
be same but go outputs it in outSubpackets and gpg in rawSubPackets.
2. If we assume that contents are same then we can see that In go there is 
{true 16 false [70 75 47 172 246 211 247 238]}
while in gpg it is
{false 16 false [70 75 47 172 246 211 247 238]}
That means go includes some type 16 signature while calculating hash while gpg doesnt.
I may be wrong in above analysis.What do you say?

@gopherbot
Copy link
Author

Comment 6 by casey.marshall:

I've also wondered about the difference in the armored representation of a public key
between go.crypto and GnuPG. I am interested in getting to the bottom of this one, if
only to satisfy my own curiosity.
I would expect most key material to parse correctly on both GnuPG and with go.crypto,
except for edge cases like unsupported algorithms, invalid content, etc (there are a few
really weird ones floating around in the SKS global pool, let me tell you...). I would
expect reasonably valid key material to "round-trip" parsing and serializing with
identical output. If you find something otherwise, please create and share a test case.
> 1. rawSubpackets and outSubPackets are output differently in gpg and go.Content seems
to be same but go outputs it in outSubpackets and gpg in rawSubPackets.
Signature.outSubpackets is a stateful field used in serializing the packet and
calculating the hash. You would expect to see differences in outSubpackets, between a
signature that had just been parsed, vs. a signature that had been just been serialized
or created by packet.Sign.
> 2. If we assume that contents are same then we can see that In go there is 
> {true 16 false [70 75 47 172 246 211 247 238]}
> while in gpg it is
> {false 16 false [70 75 47 172 246 211 247 238]}
For new content (generating keys, adding signatures, etc.) go.crypto may differ from
GnuPG in its interpretation of RFC 4880, in ways that are perfectly valid.
Signature v4 subpacket type 16 is "Issuer" (RFC 4880, 5.2.3.1, 5.2.3.5). While it's
probably ok in most cases not to include in the hash, I can't see any harm in doing so.
It's certainly possible that some APT has developed the capability to generate keys with
colliding 8-byte key IDs on demand.

@gopherbot
Copy link
Author

Comment 7 by pruthvirajsinh7:

Thanks a lot and I am extremely sorry for not replying as I was extremely busy with my
Exams.The keys are parsed correctly with NO problems. We know that the key is not
modified its just armor but the owner doesn't know that. The owner may get alarmed
seeing his key modified. I am again back on the project and will also try to get bottom
of this when I get time.Thanks again. Checkout the following links for other problems
that I faced with openpgp.
https://golang.org/issue/7371
http://stackoverflow.com/questions/21494035/cannot-sign-a-valid-gpg-key-using-golangs-openpgp-packet

@mikioh mikioh changed the title /openpgp/armor: armored output differs from gpg or sks armored output openpgp/armor: armored output differs from gpg or sks armored output Jan 8, 2015
@rsc rsc added this to the Unplanned milestone Apr 10, 2015
@rsc rsc changed the title openpgp/armor: armored output differs from gpg or sks armored output x/crypto/openpgp/armor: armored output differs from gpg or sks armored output Apr 14, 2015
@rsc rsc modified the milestones: Unreleased, Unplanned Apr 14, 2015
@rsc rsc removed the repo-crypto label Apr 14, 2015
@FiloSottile
Copy link
Contributor

Per the accepted #44226 proposal and due to lack of maintenance, the golang.org/x/crypto/openpgp package is now frozen and deprecated. No new changes will be accepted except for security fixes. The package will not be removed.

If this is a security issue, please email security@golang.org and we will assess it and provide a fix.

If you're looking for alternatives, consider the crypto/ed25519 package for simple signatures, golang.org/x/mod/sumdb/note for inline signatures, or filippo.io/age for encryption. You can read a summary of OpenPGP issues and alternatives here.

If you are required to interoperate with OpenPGP systems and need a maintained package, we suggest considering one of multiple community forks of golang.org/x/crypto/openpgp. We don't endorse any specific one.

@golang golang locked and limited conversation to collaborators Mar 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants