Skip to content

Commit

Permalink
Editorial changes: comments, docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mrtamm committed Aug 7, 2024
1 parent 0ed77cd commit b923c22
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 32 deletions.
1 change: 1 addition & 0 deletions storage/crypt4gh/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"golang.org/x/crypto/curve25519"
)

// Handles Crypt4gh decryption context per source stream.
type Crypt4gh struct {
keyPair *Crypt4ghKeyPair
stream io.Reader
Expand Down
7 changes: 6 additions & 1 deletion storage/crypt4gh/decrypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ func encryptAndDecryptContent(rangeStart, rangeLength int) (string, error) {
return buffer.String(), nil
}

// Very simplified approach for encrypting some content. Good enough for testing.
// When range is provided, an edit-list packet is added to the header so that
// receiver would look for the part of content defined by the start position
// and length. Specify start=0 and length=-1 to avoid the edit-list.
// Returns the Cryp4gh formatted encrypted data with header in the buffer.
func encryptContent(rangeStart, rangeLength int) *bytes.Buffer {
sharedKey := generateSharedKey()
aead, _ := chacha20poly1305.New(sharedKey)
Expand Down Expand Up @@ -138,6 +143,6 @@ func generateSharedKey() []byte {

func nonce() []byte {
nonce := make([]byte, 12)
_, _ = rand.Read(nonce[:])
_, _ = rand.Read(nonce)
return nonce
}
51 changes: 31 additions & 20 deletions storage/crypt4gh/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type Crypt4ghKeyPair struct {
secretKey []byte
}

// Produces BASE64-encoded public-key where the key is represented just as in
// the public-key file.
func (k *Crypt4ghKeyPair) EncodePublicKeyBase64() string {
header, footer := getKeyFileHeaderFooter("PUBLIC")

Expand All @@ -43,6 +45,8 @@ func (k *Crypt4ghKeyPair) EncodePublicKeyBase64() string {
return base64.StdEncoding.EncodeToString(content.Bytes())
}

// Saves the current key-pair to the specified files. If passphrase is not
// empty, the private key will be encrypted using the passphrase
func (k *Crypt4ghKeyPair) Save(publicKeyPath, privateKeyPath string, passphrase []byte) error {
err := saveKeyFile(publicKeyPath, "PUBLIC", k.publicKey)
if err != nil {
Expand All @@ -57,14 +61,16 @@ func (k *Crypt4ghKeyPair) Save(publicKeyPath, privateKeyPath string, passphrase
return saveKeyFile(privateKeyPath, "PRIVATE", encodedKey)
}

// Decrypts given Crypt4gh file stream (expecting the header part followed by encrypted body).
// Wraps given reader in order to decrypt the Crypt4gh file stream (expecting
// the header part followed by encrypted body).
func (k *Crypt4ghKeyPair) Decrypt(r io.Reader) (io.Reader, error) {
c := Crypt4gh{keyPair: k, stream: r}
err := c.readHeader()
return &c, err
}

// Decrypts given Crypt4gh file stream (body) using an explicitly provided header information.
// Returns a reader providing decrypted data for given Crypt4gh file stream
// (body) and explicit Crypt4gh header information.
func (k *Crypt4ghKeyPair) DecryptWithHeader(header []byte, body io.Reader) (io.Reader, error) {
c := Crypt4gh{keyPair: k, stream: bytes.NewReader(header)}
err := c.readHeader()
Expand Down Expand Up @@ -179,48 +185,48 @@ func ResolveKeyPair() (*Crypt4ghKeyPair, error) {
// On failure, it returns an empty string.
// Look up order is following:
//
// 1. When the provided file-path is not empty, use the directory of the key.
// 2. Fall back to .c4gh/ directory in the current work directory
// 1. When the provided file-path is not empty, use its directory (even if it
// does not exist yet: it will be created).
// 2. Fall back to .c4gh/ directory in the current directory, if it exists.
// 3. When user's home-directory can be resolved, fall back to the ~/.c4gh/
// directory.
// directory (creating it, if missing). When the home-directory cannot be
// resolved, fall back to .c4gh/ directory in the current directory.
// 4. When the directory does not exist and cannot be created, fail by
// returning "".
//
// To summarise the edge-cases:
// 1. If no keys are found, they will be created at ~/.c4gh/key[.pub].
// 2. When the current directory contains the .c4gh directory then that will
// 1. Explicitly provided paths will be always trusted (if the directories
// don't exist yet, they will be created)
// 2. If no explicit path is provided, keys will be created at
// ~/.c4gh/key[.pub]
// 3. When the current directory contains the .c4gh directory then that will
// override the home-directory.
// 3. explicitly provided paths will be always trusted (without explicitly
// checking whether they exist)
func resolveKeysDir(secretKeyPath string) string {
var keysDir string

if secretKeyPath != "" {
if secretKeyPath != "" { // explicit path
keysDir = path.Dir(secretKeyPath)
} else if isDir(presumedDirName) {
} else if isDir(presumedDirName) { // ./.c4gh/
keysDir = presumedDirName
} else {
} else { // attempting ~/.c4gh/
var errDir error
keysDir, errDir = os.UserHomeDir()

if errDir == nil {
// Place the keys into a private sub-directory:
keysDir = path.Join(keysDir, presumedDirName)
} else {
keysDir = presumedDirName
keysDir = presumedDirName // Fall-back: ./.c4gh/
}
}

// Check the directory exists or if it can be created:
directoryExists := isDir(keysDir)

if !directoryExists {
err := os.MkdirAll(keysDir, 0700)
directoryExists = err == nil
}

if !directoryExists {
keysDir = ""
if err := os.MkdirAll(keysDir, 0700); err != nil {
return ""
}
}

return keysDir
Expand Down Expand Up @@ -332,6 +338,10 @@ func checkLine(r io.Reader, line string) error {
return nil
}

// Extract a number of bytes from given list. The length of the bytes to be
// returned is specified by the first two bytes (big-endian) at the starting
// position. The second returned int indicates the position after the extracted
// bytes.
func readBytes(bytes []byte, startPos int) ([]byte, int) {
length := int(bytes[startPos])<<8 | int(bytes[startPos+1])
start := startPos + 2
Expand All @@ -344,6 +354,7 @@ func readString(bytes []byte, startPos int) (string, int) {
return string(b), end
}

// Returns a two-byte list holding the provided int in big-endian encoding.
func getLengthBytes(l int) []byte {
b := [2]byte{byte(l >> 8), byte(l)}
return b[:]
Expand Down Expand Up @@ -399,7 +410,7 @@ func decryptPrivateKey(
}

if kdfname == "none" || ciphername == "none" {
return nil, fmt.Errorf("Unexpected key encryption information: "+
return nil, fmt.Errorf("Invalid key encryption information: "+
"kdfname=%s, ciphername=%s", kdfname, ciphername)
}

Expand Down
6 changes: 3 additions & 3 deletions storage/crypt4gh/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,19 @@ func TestGeneratingAndSavingNewKeys(t *testing.T) {

err = c4gh.Save(pubPath, secPath, nil)
if err != nil {
t.Error("Could not generate a Crypt4gh key-pair", err)
t.Error("Could not save a Crypt4gh key-pair to file", err)
}

os.Remove(pubPath)
os.Remove(secPath)

err = c4gh.Save(pubPath, secPath, []byte("abcDEFghi"))
if err != nil {
t.Error("Could not generate a Crypt4gh key-pair", err)
t.Error("Could not save a Crypt4gh key-pair to file", err)
}

_, err = KeyPairFromFiles(pubPath, secPath, []byte("abcDEFghi"))
if err != nil {
t.Error("Could not reload saved Crypt4gh key-pair", err)
t.Error("Could not reload the saved Crypt4gh key-pair", err)
}
}
4 changes: 2 additions & 2 deletions storage/htsget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
)

func TestHTSGET(t *testing.T) {
invalidUrl := "https://google.com"
validUrl := "htsget://google.com"
invalidUrl := "https://example.org"
validUrl := "htsget://bearer:token@example.org"

store, err := NewHTSGET(config.HTSGETStorage{})
if err != nil {
Expand Down
17 changes: 11 additions & 6 deletions website/content/docs/storage/htsget.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,21 @@ default protocol is `https`, which is also presumed in the Htsget
specification. For testing purposes, it can be changed to `http`.

If the service expects a `Bearer` token, it can be specified in the URL.
For example: `htsget://bearer:your-token-here@fakedomain.com/...`.
For example: `htsget://bearer:your-token-here@example.org/...`.
Here the `bearer:` part is the required syntax to activate the
`your-token-here` value to be sent to the htsget-service as a header value:
`Authorization: Bearer your-token-here`.

Funnel always sends its public key in the header of the request to the Htsget
service. When the Htsget service supports [the content encryption using
Crypt4gh][htsget-crypt4gh], it can generate a custom Crypt4gh file header where
the Funnel instance can decrypt and find the symmetric key used for content
encryption.
Funnel always sends its public key in the header (`client-public-key`) of the
request to the Htsget service. When the Htsget service supports [the content
encryption using Crypt4gh][htsget-crypt4gh], the service can generate a custom
Crypt4gh file header containing the symmetric key for decrypting the referred
content (Crypt4gh formatted data-blocks). Funnel checks the beginning of the
received content to know whether Crypt4gh decryption can be applied. Therefore,
tasks always receive the data decrypted. For sensitive data, the deployment
environment (server) should pay attention to restricting access to the Funnel's
data directories, possibly having separate Funnel instances for different
data-projects.

Default Htsget Storage configuration should be sufficient for most cases:

Expand Down

0 comments on commit b923c22

Please sign in to comment.