{"action":"created","issue":{"url":"https://api.github.com/repos/golang/go/issues/9312","labels_url":"https://api.github.com/repos/golang/go/issues/9312/labels{/name}","comments_url":"https://api.github.com/repos/golang/go/issues/9312/comments","events_url":"https://api.github.com/repos/golang/go/issues/9312/events","html_url":"https://github.com/golang/go/issues/9312","id":51928048,"number":9312,"title":"openpgp: silently fail to read keyring when smartcard stub is present","user":{"login":"jvehent","id":415617,"avatar_url":"https://avatars.githubusercontent.com/u/415617?v=3","gravatar_id":"","url":"https://api.github.com/users/jvehent","html_url":"https://github.com/jvehent","followers_url":"https://api.github.com/users/jvehent/followers","following_url":"https://api.github.com/users/jvehent/following{/other_user}","gists_url":"https://api.github.com/users/jvehent/gists{/gist_id}","starred_url":"https://api.github.com/users/jvehent/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jvehent/subscriptions","organizations_url":"https://api.github.com/users/jvehent/orgs","repos_url":"https://api.github.com/users/jvehent/repos","events_url":"https://api.github.com/users/jvehent/events{/privacy}","received_events_url":"https://api.github.com/users/jvehent/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/golang/go/labels/release-none","name":"release-none","color":"ededed"},{"url":"https://api.github.com/repos/golang/go/labels/repo-crypto","name":"repo-crypto","color":"ededed"}],"state":"open","locked":false,"assignee":{"login":"agl","id":21203,"avatar_url":"https://avatars.githubusercontent.com/u/21203?v=3","gravatar_id":"","url":"https://api.github.com/users/agl","html_url":"https://github.com/agl","followers_url":"https://api.github.com/users/agl/followers","following_url":"https://api.github.com/users/agl/following{/other_user}","gists_url":"https://api.github.com/users/agl/gists{/gist_id}","starred_url":"https://api.github.com/users/agl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/agl/subscriptions","organizations_url":"https://api.github.com/users/agl/orgs","repos_url":"https://api.github.com/users/agl/repos","events_url":"https://api.github.com/users/agl/events{/privacy}","received_events_url":"https://api.github.com/users/agl/received_events","type":"User","site_admin":false},"milestone":null,"comments":3,"created_at":"2014-12-14T19:00:21Z","updated_at":"2015-03-20T16:22:54Z","closed_at":null,"body":"When a GPG secring contains a smartcard stub, the openpgp package fails to read the keyring correctly and does not return an error. A call to `openpgp.ReadKeyRing()` will returns the entities that precedes the stub, excluding the entity located right before the stub.\r\n\r\nFor example, if a secring contains 3 entities and the smartcard stub is in position 3, then `openpgp.ReadKeyRing()` will only return the first entity. The second entity and the stub are ignored. No error is returned.\r\n\r\nSteps to reproduce:\r\n\r\n**1. Create a keyring with two regular 1024 bits RSA keys**\r\n\r\n```bash\r\n$ mkdir /tmp/testgolangopenpgp\r\n```\r\n```bash\r\n$ gpg --batch --no-default-keyring --keyring /tmp/testgolangopenpgp/pubring.gpg --secret-keyring /tmp/testgolangopenpgp/secring.gpg --gen-key << EOF\r\n> Key-Type: 1\r\n> Key-Length: 1024\r\n> Subkey-Type: 1\r\n> Subkey-Length: 1024\r\n> Name-Real: test key 1\r\n> Name-Email: testkey1@example.net\r\n> Expire-Date: 12m\r\n> EOF\r\ngpg: keyring `/tmp/testgolangopenpgp/secring.gpg' created\r\ngpg: keyring `/tmp/testgolangopenpgp/pubring.gpg' created\r\n..+++++\r\n...+++++\r\n.....+++++\r\n..+++++\r\ngpg: key 4BACAA9F marked as ultimately trusted\r\n```\r\n```bash\r\n$ gpg --batch --no-default-keyring --keyring /tmp/testgolangopenpgp/pubring.gpg --secret-keyring /tmp/testgolangopenpgp/secring.gpg --gen-key << EOF\r\n> Key-Type: 1\r\n> Key-Length: 1024\r\n> Subkey-Type: 1\r\n> Subkey-Length: 1024\r\n> Name-Real: test key 2\r\n> Name-Email: testkey2@example.net\r\n> Expire-Date: 12m\r\n> EOF\r\n.+++++\r\n.+++++\r\n+++++\r\n+++++\r\ngpg: key 56CE5F62 marked as ultimately trusted\r\n```\r\n**2. Import a smartcard stub from a yubikey into the keyring**\r\n```bash\r\n$ gpg --no-default-keyring --keyring /tmp/testgolangopenpgp/pubring.gpg --secret-keyring /tmp/testgolangopenpgp/secring.gpg --allow-secret-key-import --import yubikeystub yubikeytest1_stub.asc \r\ngpg: key 4FB5544F: already in secret keyring\r\ngpg: key 59BEDBFC: already in secret keyring\r\ngpg: Total number processed: 2\r\ngpg:       secret keys read: 2\r\ngpg:  secret keys unchanged: 2\r\n\r\n/tmp/testgolangopenpgp/secring.gpg\r\n----------------------------------\r\nsec   1024R/4BACAA9F 2014-12-14 [expires: 2015-12-09]\r\nuid                  test key 1 <testkey1@example.net>\r\nssb   1024R/AEC9CD3A 2014-12-14\r\n\r\nsec   1024R/56CE5F62 2014-12-14 [expires: 2015-12-09]\r\nuid                  test key 2 <testkey2@example.net>\r\nssb   1024R/A1E03C73 2014-12-14\r\n\r\nsec>  2048R/59BEDBFC 2014-12-14 [expires: 2015-12-09]\r\n      Card serial no. = 0000 00000001\r\nuid                  Yubikey Smartcard test1 <yubikeytest1@example.net>\r\nssb>  2048R/9B633300 2014-12-14\r\nssb>  2048R/A8DE1BB7 2014-12-14\r\n```\r\n**3. Attempt to read the entities in the keyring. Only the first entity is returned.**\r\n```bash\r\n$ go run readkeyring.go \r\nfound 1 entities in keyring\r\nreading entity with fingerprint 2FA49C2800342153CA439C90ADC366D34BACAA9F\r\n```\r\n**source code:**\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"code.google.com/p/go.crypto/openpgp\"\r\n\t\"encoding/hex\"\r\n\t\"fmt\"\r\n\t\"os\"\r\n\t\"strings\"\r\n)\r\n\r\nfunc main() {\r\n\tsecringFile, err := os.Open(\"/tmp/testgolangopenpgp/secring.gpg\")\r\n\tif err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\tdefer secringFile.Close()\r\n\tkeyring, err := openpgp.ReadKeyRing(secringFile)\r\n\tif err != nil {\r\n\t\terr = fmt.Errorf(\"Keyring access failed: '%v'\", err)\r\n\t\tpanic(err)\r\n\t}\r\n\tfmt.Printf(\"found %d entities in keyring\\n\", len(keyring))\r\n\tfor _, entity := range keyring {\r\n\t\tfingerprint := strings.ToUpper(hex.EncodeToString(entity.PrimaryKey.Fingerprint[:]))\r\n\t\tfmt.Println(\"reading entity with fingerprint\", fingerprint)\r\n\t}\r\n}\r\n```"},"comment":{"url":"https://api.github.com/repos/golang/go/issues/comments/84060358","html_url":"https://github.com/golang/go/issues/9312#issuecomment-84060358","issue_url":"https://api.github.com/repos/golang/go/issues/9312","id":84060358,"user":{"login":"jvehent","id":415617,"avatar_url":"https://avatars.githubusercontent.com/u/415617?v=3","gravatar_id":"","url":"https://api.github.com/users/jvehent","html_url":"https://github.com/jvehent","followers_url":"https://api.github.com/users/jvehent/followers","following_url":"https://api.github.com/users/jvehent/following{/other_user}","gists_url":"https://api.github.com/users/jvehent/gists{/gist_id}","starred_url":"https://api.github.com/users/jvehent/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jvehent/subscriptions","organizations_url":"https://api.github.com/users/jvehent/orgs","repos_url":"https://api.github.com/users/jvehent/repos","events_url":"https://api.github.com/users/jvehent/events{/privacy}","received_events_url":"https://api.github.com/users/jvehent/received_events","type":"User","site_admin":false},"created_at":"2015-03-20T16:22:54Z","updated_at":"2015-03-20T16:22:54Z","body":"I took a peek at it, but this is my first dive into RFC4880 so some things are still unclear to me.\r\nLooking at the yubikey stub in the secring, if I skip the first 272 bytes, I get directly to the S2K value, which is set to `0xff`:\r\n```\r\n$ hexdump -s 272 -C 000015-005.secret_key\r\n00000110  ff 00 65 00 47 4e 55 02  10 d2 76 00 01 24 01 02  |..e.GNU...v..$..|\r\n00000120  00 00 00 00 00 00 01 00  00                       |.........|\r\n00000129\r\n```\r\n`0xff` indicates an encrypted private key. In the code [1], `(pk *PrivateKey) parse()` tries to read the next byte to guess the encryption algorithm, but that next byte is set to `00` which doesn't map to any valid cipher.\r\nThen, entering `s2k.Parse()`, the following 2 bytes are read (`0x6500`), and the second byte (`0x00`) is used in `s2k.HashIdToHash()`. But `0` is not a valid hash id, and the function returns error `openpgp: unsupported feature: hash for S2K function: 0`. The error is silenced is `openpgp.ReadKeyRing` [2].\r\n\r\nI could do the analysis, but I'm afraid writing a patch is beyond my understand of the OpenPGP standard at this point...\r\n\r\n[1] https://github.com/golang/crypto/blob/master/openpgp/packet/private_key.go#L67-L80\r\n[2] https://github.com/golang/crypto/blob/master/openpgp/keys.go#L258"}}