github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/secrets/onepasswordconnecttoken/onepasswordconnecttoken.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package onepasswordconnecttoken contains an extractor for OnePassword Connect Token files.
    16  package onepasswordconnecttoken
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"path"
    22  	"strings"
    23  
    24  	"github.com/google/osv-scalibr/extractor/filesystem"
    25  	"github.com/google/osv-scalibr/inventory"
    26  	"github.com/google/osv-scalibr/plugin"
    27  )
    28  
    29  // OnePasswordConnectToken is a Veles Secret that holds a OnePassword Connect Token
    30  // with all its encrypted credentials and metadata.
    31  type OnePasswordConnectToken struct {
    32  	DeviceUUID        string
    33  	Version           string
    34  	EncryptedData     string
    35  	EncryptionKeyID   string
    36  	IV                string
    37  	UniqueKeyID       string
    38  	VerifierSalt      string
    39  	VerifierLocalHash string
    40  }
    41  
    42  const (
    43  	// Name is the unique name of this extractor.
    44  	Name = "secrets/onepasswordconnecttoken"
    45  )
    46  
    47  // TokenData represents the structure of a OnePassword Connect Token JSON file.
    48  type TokenData struct {
    49  	Verifier struct {
    50  		Salt      string `json:"salt"`
    51  		LocalHash string `json:"localHash"`
    52  	} `json:"verifier"`
    53  	EncCredentials struct {
    54  		Kid  string `json:"kid"`
    55  		Enc  string `json:"enc"`
    56  		Cty  string `json:"cty"`
    57  		IV   string `json:"iv"`
    58  		Data string `json:"data"`
    59  	} `json:"encCredentials"`
    60  	Version    string `json:"version"`
    61  	DeviceUUID string `json:"deviceUuid"`
    62  	UniqueKey  struct {
    63  		Alg    string   `json:"alg"`
    64  		Ext    bool     `json:"ext"`
    65  		K      string   `json:"k"`
    66  		KeyOps []string `json:"key_ops"`
    67  		Kty    string   `json:"kty"`
    68  		Kid    string   `json:"kid"`
    69  	} `json:"uniqueKey"`
    70  }
    71  
    72  // Extractor extracts OnePassword Connect Token secrets.
    73  type Extractor struct{}
    74  
    75  // New returns a new instance of the extractor.
    76  func New() filesystem.Extractor { return &Extractor{} }
    77  
    78  // Name of the extractor.
    79  func (e Extractor) Name() string { return Name }
    80  
    81  // Version of the extractor.
    82  func (e Extractor) Version() int { return 0 }
    83  
    84  // Requirements of the extractor.
    85  func (e Extractor) Requirements() *plugin.Capabilities {
    86  	return &plugin.Capabilities{}
    87  }
    88  
    89  // FileRequired returns true if the file is a JSON file with "onepassword" or "1password" in its name.
    90  func (e Extractor) FileRequired(api filesystem.FileAPI) bool {
    91  	fileName := strings.ToLower(path.Base(api.Path()))
    92  
    93  	// Check if the filename contains "onepassword" or "1password"
    94  	if !strings.Contains(fileName, "onepassword") && !strings.Contains(fileName, "1password") {
    95  		return false
    96  	}
    97  
    98  	// Check if it's a JSON file
    99  	return strings.HasSuffix(fileName, ".json")
   100  }
   101  
   102  // Extract extracts OnePassword Connect Token information from JSON files.
   103  func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) {
   104  	var data TokenData
   105  
   106  	decoder := json.NewDecoder(input.Reader)
   107  	if err := decoder.Decode(&data); err != nil {
   108  		// Not a valid JSON or not a OnePassword Connect Token file, return empty inventory
   109  		//nolint:nilerr
   110  		return inventory.Inventory{}, nil
   111  	}
   112  
   113  	// Check if this looks like a OnePassword Connect Token file by verifying required fields
   114  	if data.DeviceUUID == "" || data.Version == "" || data.EncCredentials.Data == "" {
   115  		// Not a OnePassword Connect Token file, return empty inventory
   116  		return inventory.Inventory{}, nil
   117  	}
   118  
   119  	var secrets []*inventory.Secret
   120  
   121  	secret := &inventory.Secret{
   122  		Secret: OnePasswordConnectToken{
   123  			DeviceUUID:        data.DeviceUUID,
   124  			Version:           data.Version,
   125  			EncryptedData:     data.EncCredentials.Data,
   126  			EncryptionKeyID:   data.EncCredentials.Kid,
   127  			IV:                data.EncCredentials.IV,
   128  			UniqueKeyID:       data.UniqueKey.Kid,
   129  			VerifierSalt:      data.Verifier.Salt,
   130  			VerifierLocalHash: data.Verifier.LocalHash,
   131  		},
   132  		Location: input.Path,
   133  	}
   134  
   135  	secrets = append(secrets, secret)
   136  
   137  	return inventory.Inventory{Secrets: secrets}, nil
   138  }