-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
For now it implements decryption for yubikey's SHA1 challenge-response mode. This commit also contains shell scripts that can be used as a extension for the upstream project. Closes #3
- Loading branch information
Showing
5 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
#!/bin/bash -e | ||
# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: | ||
# | ||
# Copyright (c) 2021 Anatol Pomazau | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
[ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2 | ||
|
||
if [ -t 0 ]; then | ||
exec >&2 | ||
echo | ||
echo "Usage: clevis decrypt yubikey < JWE > PLAINTEXT" | ||
echo | ||
exit 2 | ||
fi | ||
|
||
read -r -d . hdr | ||
|
||
if ! jhd="$(jose b64 dec -i- <<< "$hdr")"; then | ||
echo "Error decoding JWE protected header!" >&2 | ||
exit 1 | ||
fi | ||
|
||
if [ "$(jose fmt -j- -Og clevis -g pin -u- <<< "$jhd")" != "yubikey" ]; then | ||
echo "JWE pin mismatch!" >&2 | ||
exit 1 | ||
fi | ||
|
||
type=$(jose fmt -j- -Og clevis -g yubikey -g type -u- <<< "$jhd") | ||
if [ "$type" != "chalresp" ]; then | ||
echo "JWE yubikey type mismatch!" >&2 | ||
exit 1 | ||
fi | ||
|
||
slot=$(jose fmt -j- -Og clevis -g yubikey -g slot -Io- <<< "$jhd") | ||
|
||
challenge=$(jose fmt -j- -Og clevis -g yubikey -g challenge -u- <<< "$jhd" | jose b64 dec -i - | xxd -p -c 1000) | ||
response=$(echo -n $challenge | ykchalresp -x -i- -$slot) | ||
|
||
# nettle-pbkdf2 used below hardcodes pbkdf2 with sha256 | ||
kdf_type=$(jose fmt -j- -Og clevis -g yubikey -g kdf -g type -u- <<< "$jhd") | ||
if [ "$kdf_type" != "pbkdf2" ]; then | ||
echo "JWE yubikey supports pbkdf2 kdf only!" >&2 | ||
exit 1 | ||
fi | ||
|
||
kdf_hash=$(jose fmt -j- -Og clevis -g yubikey -g kdf -g hash -u- <<< "$jhd") | ||
if [ "$kdf_hash" != "sha256" ]; then | ||
echo "JWE yubikey supports sha256 hash only!" >&2 | ||
exit 1 | ||
fi | ||
|
||
salt_hex=$(jose fmt -j- -Og clevis -g yubikey -g kdf -g salt -u- <<< "$jhd" | jose b64 dec -i - | xxd -p -c 1000) | ||
iter=$(jose fmt -j- -Og clevis -g yubikey -g kdf -g iter -Io- <<< "$jhd") | ||
|
||
key=$(echo -n $response | xxd -r -p | nettle-pbkdf2 --raw --iterations $iter --length 32 --hex-salt $salt_hex | jose b64 enc -I -) | ||
|
||
jwk="$(jose jwk gen -i '{"alg":"A256GCM"}')" | ||
jwk="$(jose fmt -j "$jwk" -q "$key" -s k -Uo-)" | ||
|
||
(echo -n "$jwk$hdr."; /bin/cat) | jose jwe dec -k- -i- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
#!/bin/bash -e | ||
# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: | ||
# | ||
# Copyright (c) 2021 Anatol Pomazau. | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
# | ||
|
||
SUMMARY="Encrypts using a Yubikey binding policy" | ||
|
||
if [ "$1" == "--summary" ]; then | ||
echo "$SUMMARY" | ||
exit 0 | ||
fi | ||
|
||
if [ -t 0 ]; then | ||
exec >&2 | ||
echo | ||
echo "Usage: clevis encrypt yubikey CONFIG < PLAINTEXT > JWE" | ||
echo | ||
echo "$SUMMARY" | ||
echo | ||
echo "This command uses the following configuration properties:" | ||
echo | ||
echo " slot: <integer> Yubikey slot number (default: 1)" | ||
exit 2 | ||
fi | ||
|
||
if ! cfg="$(jose fmt -j- -Oo- <<<"$1" 2>/dev/null)"; then | ||
echo "Configuration is malformed!" >&2 | ||
exit 1 | ||
fi | ||
|
||
slot=$(jose fmt -j- -Og slot -Io- <<<"$cfg") || slot=1 | ||
|
||
CHALLENGE_SIZE=32 | ||
SALT_SIZE=32 | ||
|
||
challenge=$(openssl rand -hex $CHALLENGE_SIZE) | ||
response="$(echo -n $challenge | ykchalresp -x -$slot -i-)" | ||
|
||
iter=1000 | ||
salt_hex=$(openssl rand -hex $SALT_SIZE) | ||
key=$(echo -n "$response" | xxd -r -p | nettle-pbkdf2 --raw --iterations $iter --length 32 --hex-salt $salt_hex | jose b64 enc -I -) | ||
|
||
jwk="$(jose jwk gen -i '{"alg":"A256GCM"}')" | ||
jwk="$(jose fmt -j "$jwk" -q "$key" -s k -Uo-)" | ||
|
||
jwe='{"protected":{"clevis":{"pin":"yubikey","yubikey":{"type":"chalresp"}}}}' | ||
|
||
kdf='{"type":"pbkdf2","hash":"sha256"}' | ||
base64_salt=$(echo -n "$salt_hex" | xxd -r -p | jose b64 enc -I -) | ||
kdf="$(jose fmt -j "$kdf" -j $iter -Is iter -U -q "$base64_salt" -Ss salt -Uo-)" | ||
|
||
base64_challenge=$(echo -n $challenge | xxd -r -p | jose b64 enc -I -) | ||
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g yubikey -q "$base64_challenge" -Ss challenge -U -j $slot -Is slot -U -j $kdf -Os kdf -UUUUo-)" | ||
|
||
exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package clevis | ||
|
||
import ( | ||
"bytes" | ||
"crypto/sha1" | ||
"crypto/sha256" | ||
"encoding/base64" | ||
"encoding/hex" | ||
"fmt" | ||
"hash" | ||
"os/exec" | ||
"strconv" | ||
|
||
"github.com/lestrrat-go/jwx/jwa" | ||
"github.com/lestrrat-go/jwx/jwe" | ||
"golang.org/x/crypto/pbkdf2" | ||
) | ||
|
||
func DecryptYubikey(msg *jwe.Message, clevisNode map[string]interface{}) ([]byte, error) { | ||
yubikeyNode, ok := clevisNode["yubikey"].(map[string]interface{}) | ||
if !ok { | ||
return nil, fmt.Errorf("clevis.go/yubikey: cannot parse provided token, node 'clevis.yubikey'") | ||
} | ||
|
||
yubType, ok := yubikeyNode["type"] | ||
if !ok { | ||
return nil, fmt.Errorf("clevis.go/yubikey: cannot parse provided token, node 'clevis.yubikey.type'") | ||
} | ||
|
||
switch yubType { | ||
case "chalresp": | ||
return challengeResponse(msg, yubikeyNode) | ||
default: | ||
return nil, fmt.Errorf("clevis.go/yubikey: unknown type %s", yubType) | ||
} | ||
} | ||
|
||
func challengeResponse(msg *jwe.Message, node map[string]interface{}) ([]byte, error) { | ||
challenge, ok := node["challenge"].(string) | ||
if !ok { | ||
return nil, fmt.Errorf("clevis.go/yubikey: cannot parse provided token, node 'clevis.yubikey.challenge'") | ||
} | ||
|
||
slotNode, ok := node["slot"].(float64) | ||
if !ok { | ||
return nil, fmt.Errorf("clevis.go/yubikey: cannot parse provided token, node 'clevis.yubikey.slot'") | ||
} | ||
slot := int(slotNode) | ||
|
||
challengeBin, err := base64.RawURLEncoding.DecodeString(challenge) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(challengeBin) != 32 { | ||
return nil, fmt.Errorf("expected challenge length is 32") | ||
} | ||
|
||
var outBuffer, errBuffer bytes.Buffer | ||
cmd := exec.Command("ykchalresp", "-i-", "-"+strconv.Itoa(slot)) | ||
cmd.Stdin = bytes.NewReader(challengeBin) | ||
cmd.Stdout = &outBuffer | ||
cmd.Stderr = &errBuffer | ||
if err := cmd.Run(); err != nil { | ||
return nil, fmt.Errorf("%v: %s", err, errBuffer.String()) | ||
} | ||
// out is hex | ||
response := outBuffer.Bytes()[:40] // cut the trailing newline | ||
responseBin := make([]byte, 20) | ||
if _, err := hex.Decode(responseBin, response); err != nil { | ||
return nil, err | ||
} | ||
|
||
kdf, ok := node["kdf"].(map[string]interface{}) | ||
if !ok { | ||
return nil, fmt.Errorf("clevis.go/yubikey: cannot parse provided token, missing node 'clevis.yubikey.kdf'") | ||
} | ||
|
||
var key []byte | ||
|
||
switch kdf["type"].(string) { | ||
case "pbkdf2": | ||
iter := int(kdf["iter"].(float64)) | ||
h := hashByName(kdf["hash"].(string)) | ||
if h == nil { | ||
return nil, fmt.Errorf("clevis.go/yubikey: unknown hash specified at node 'clevis.yubikey.kdf.hash': %s", kdf["hash"].(string)) | ||
} | ||
salt, err := base64.RawURLEncoding.DecodeString(kdf["salt"].(string)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(salt) != 32 { | ||
return nil, fmt.Errorf("expected salt length is 32, got %d", len(salt)) | ||
} | ||
|
||
key = pbkdf2.Key(responseBin, salt, iter, 32, h) | ||
default: | ||
return nil, fmt.Errorf("clevis.go/yubikey: unknown kdf type specified at node 'clevis.yubikey.kdf.type': %s", kdf["type"].(string)) | ||
} | ||
|
||
return msg.Decrypt(jwa.DIRECT, key) | ||
} | ||
|
||
func hashByName(name string) func() hash.Hash { | ||
switch name { | ||
case "sha256": | ||
return sha256.New | ||
case "sha1": | ||
return sha1.New | ||
default: | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package clevis | ||
|
||
import ( | ||
"bytes" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestDecryptYubikey(t *testing.T) { | ||
inputText := "testing yubikey" | ||
|
||
clevisConfigs := []string{ | ||
`{"slot":2}`, | ||
} | ||
|
||
for _, c := range clevisConfigs { | ||
var outbuf bytes.Buffer | ||
cmd := exec.Command("./clevis-encrypt-yubikey", c) | ||
cmd.Stdin = strings.NewReader(inputText) | ||
cmd.Stdout = &outbuf | ||
cmd.Stderr = os.Stderr | ||
if err := cmd.Run(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
compactForm := outbuf.Bytes() | ||
jsonForm, err := convertToJsonForm(compactForm) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// decrypt compact form using our implementation | ||
plaintext1, err := Decrypt(compactForm) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if string(plaintext1) != inputText { | ||
t.Fatalf("tpm2 decryption failed: expected '%s', got '%s'", inputText, string(plaintext1)) | ||
} | ||
|
||
// decrypt json form using our implementation | ||
plaintext2, err := Decrypt(jsonForm) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if string(plaintext2) != inputText { | ||
t.Fatalf("tpm2 decryption failed: expected '%s', got '%s'", inputText, string(plaintext2)) | ||
} | ||
} | ||
} |