pax_global_header00006660000000000000000000000064147126667640014535gustar00rootroot0000000000000052 comment=84bf97b30cc4a1634d8a70aedd7e17a36bc991f7 tkeysign-1.0.1/000077500000000000000000000000001471266676400133715ustar00rootroot00000000000000tkeysign-1.0.1/.editorconfig000066400000000000000000000000361471266676400160450ustar00rootroot00000000000000[*.{md}] max_line_length = 70 tkeysign-1.0.1/.github/000077500000000000000000000000001471266676400147315ustar00rootroot00000000000000tkeysign-1.0.1/.github/workflows/000077500000000000000000000000001471266676400167665ustar00rootroot00000000000000tkeysign-1.0.1/.github/workflows/ci.yaml000066400000000000000000000015301471266676400202440ustar00rootroot00000000000000 name: ci on: push: branches: - 'main' pull_request: {} # allow manual runs: workflow_dispatch: {} jobs: ci: runs-on: ubuntu-latest container: image: ghcr.io/tillitis/tkey-builder:4 steps: - name: checkout uses: actions/checkout@v4 with: # fetch-depth: 0 persist-credentials: false - name: fix # https://github.com/actions/runner-images/issues/6775 run: | git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: make run: make - name: check for SPDX tags run: ./tools/spdx-ensure reuse-compliance-check: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: REUSE Compliance Check uses: fsfe/reuse-action@v4 with: args: lint tkeysign-1.0.1/.github/workflows/golangci-lint.yml000066400000000000000000000037011471266676400222410ustar00rootroot00000000000000name: golangci-lint on: push: branches: - main pull_request: permissions: contents: read # Optional: allow read access to pull request. Use with `only-new-issues` option. # pull-requests: read jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: '1.21' cache: false - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: # Require: The version of golangci-lint to use. # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. version: v1.61.0 # Optional: working directory, useful for monorepos # working-directory: somedir # Optional: golangci-lint command line arguments. # # Note: By default, the `.golangci.yml` file should be at the root of the repository. # The location of the configuration file can be changed by using `--config=` # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true # Optional: if set to true, then all caching functionality will be completely disabled, # takes precedence over all other caching options. # skip-cache: true # Optional: if set to true, then the action won't cache or restore ~/go/pkg. # skip-pkg-cache: true # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build. # skip-build-cache: true # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. # install-mode: "goinstall" tkeysign-1.0.1/.gitignore000066400000000000000000000004331471266676400153610ustar00rootroot00000000000000*.a *.o *.bin *.elf /tkey-ssh-agent /tkey-ssh-agent.exe /cmd/tkey-ssh-agent/rsrc_windows_amd64.syso /tkey-ssh-agent-tray.exe /cmd/tkey-ssh-agent-tray/rsrc_windows_amd64.syso /tkey-runapp /tkey-sign /runsign.sh /runtimer /runrandom /gotools/golangci-lint /gotools/go-winres test/venv tkeysign-1.0.1/.golangci.yml000066400000000000000000000010711471266676400157540ustar00rootroot00000000000000linters: presets: # found in: golangci-lint help linters - bugs - comment - complexity - error - format - import - metalinter - module - performance - sql # - style # turned off, can be too much - test - unused disable: - cyclop - depguard - funlen - gocognit - nestif - exhaustruct # TODO? annoying for now - err113 # TODO enable later - godot - perfsprint issues: max-issues-per-linter: 0 max-same-issues: 0 linters-settings: govet: enable: - shadow tkeysign-1.0.1/LICENSE000066400000000000000000000024261471266676400144020ustar00rootroot00000000000000 BSD 2-Clause License Copyright 2022 Tillitis AB Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. tkeysign-1.0.1/LICENSES/000077500000000000000000000000001471266676400145765ustar00rootroot00000000000000tkeysign-1.0.1/LICENSES/BSD-2-Clause.txt000066400000000000000000000023761471266676400173300ustar00rootroot00000000000000Copyright 2022 Tillitis AB Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. tkeysign-1.0.1/Makefile000066400000000000000000000001401471266676400150240ustar00rootroot00000000000000all: go build .PHONY: lint lint: golangci-lint run .PHONY: spdx spdx: ./tools/spdx-ensure tkeysign-1.0.1/README.md000066400000000000000000000034651471266676400146600ustar00rootroot00000000000000[![ci](https://github.com/tillitis/tkeysign/actions/workflows/ci.yaml/badge.svg?branch=main&event=push)](https://github.com/tillitis/tkeysign/actions/workflows/ci.yaml) [![Go Reference](https://pkg.go.dev/badge/github.com/tillitis/tkeysign.svg)](https://pkg.go.dev/github.com/tillitis/tkeysign) # Tillitis TKey Sign package A Go package for communicating with the [`signer` device app](https://github.com/tillitis/tkey-device-signer) on a [Tillitis](https://tillitis.se/) TKey to get cryptographic signatures over a message. See the [Go doc](https://pkg.go.dev/github.com/tillitis/tkeysign) for `tkeysign` for details on how to call the functions. See [tkey-ssh-agent](https://github.com/tillitis/tkey-ssh-agent) and [tkey-sign-cli](https://github.com/tillitis/tkey-sign-cli) for client applications using this go package. Release notes in [RELEASE.md](RELEASE.md). ## Licenses and SPDX tags Unless otherwise noted, the project sources are copyright Tillitis AB, licensed under the terms and conditions of the "BSD-2-Clause" license. See [LICENSE](LICENSE) for the full license text. Until Oct 25, 2024, the license was GPL-2.0 Only. External source code we have imported are isolated in their own directories. They may be released under other licenses. This is noted with a similar `LICENSE` file in every directory containing imported sources. The project uses single-line references to Unique License Identifiers as defined by the Linux Foundation's [SPDX project](https://spdx.org/) on its own source files, but not necessarily imported files. The line in each individual source file identifies the license applicable to that file. The current set of valid, predefined SPDX identifiers can be found on the SPDX License List at: https://spdx.org/licenses/ We attempt to follow the [REUSE specification](https://reuse.software/). tkeysign-1.0.1/RELEASE.md000066400000000000000000000012201471266676400147660ustar00rootroot00000000000000# Release notes ## v1.0.1 - Change license to BSD-2-Clause - Follow REUSE specification, see https://reuse.software/ - Reset read timeout to disabled in GetAppNameVersion - Update dependency tkeyclient to v1.1.0 - Bump go dependencies - Go lint using golangci-lint-action in CI - Remove unused dependency go-winres Complete [changelog](https://github.com/tillitis/tkeysign/compare/v1.0.0...v1.0.1). ## v1.0.0 - Remove (unreleased) PH signature command. - Add get firmware digest command. - Update CI to use tkey-builder:4. - Bump tkeyclient to 1.0.0. ## v0.0.7 Just ripped from https://github.com/tillitis/tillitis-key1-apps No semantic changes. tkeysign-1.0.1/REUSE.toml000066400000000000000000000010371471266676400151520ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2024 Tillitis AB # SPDX-License-Identifier: BSD-2-Clause version = 1 [[annotations]] path = ".github/workflows/*" SPDX-FileCopyrightText = "2022 Tillitis AB " SPDX-License-Identifier = "BSD-2-Clause" [[annotations]] path = [ ".editorconfig", ".gitignore", ".golangci.yml", "LICENSE", "Makefile", "README.md", "RELEASE.md", "go.mod", "go.sum" ] SPDX-FileCopyrightText = "2022 Tillitis AB " SPDX-License-Identifier = "BSD-2-Clause" tkeysign-1.0.1/go.mod000066400000000000000000000006251471266676400145020ustar00rootroot00000000000000module github.com/tillitis/tkeysign go 1.21 require github.com/tillitis/tkeyclient v1.1.0 require ( github.com/ccoveille/go-safecast v1.1.0 // indirect github.com/creack/goselect v0.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect go.bug.st/serial v1.6.2 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) tkeysign-1.0.1/go.sum000066400000000000000000000033601471266676400145260ustar00rootroot00000000000000github.com/ccoveille/go-safecast v1.1.0 h1:iHKNWaZm+OznO7Eh6EljXPjGfGQsSfa6/sxPlIEKO+g= github.com/ccoveille/go-safecast v1.1.0/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tillitis/tkeyclient v1.1.0 h1:/RTIGMZGR8cZ6bUkgZLUmDvTNH5C7qSU2qaSDl3nxhA= github.com/tillitis/tkeyclient v1.1.0/go.mod h1:K3b/zU58CLtP5iaOMypc4c7PAAmNqyw7F3bs5iNYtG0= go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8= go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= tkeysign-1.0.1/tkeysign.go000066400000000000000000000167111471266676400155630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Tillitis AB // SPDX-License-Identifier: BSD-2-Clause // Package tkeysign provides a connection to the ed25519 signer app // running on the TKey. You're expected to pass an existing connection // to it, so use it like this: // // tk := tkeyclient.New() // err := tk.Connect(port) // signer := tkeysign.New(tk) // // Then use it like this to get the public key of the TKey: // // pubkey, err := signer.GetPubkey() // // And like this to sign a message: // // signature, err := signer.Sign(message) package tkeysign import ( "fmt" "github.com/tillitis/tkeyclient" ) var ( cmdGetPubkey = appCmd{0x01, "cmdGetPubkey", tkeyclient.CmdLen1} rspGetPubkey = appCmd{0x02, "rspGetPubkey", tkeyclient.CmdLen128} cmdSetSize = appCmd{0x03, "cmdSetSize", tkeyclient.CmdLen32} rspSetSize = appCmd{0x04, "rspSetSize", tkeyclient.CmdLen4} cmdSignData = appCmd{0x05, "cmdSignData", tkeyclient.CmdLen128} rspSignData = appCmd{0x06, "rspSignData", tkeyclient.CmdLen4} cmdGetSig = appCmd{0x07, "cmdGetSig", tkeyclient.CmdLen1} rspGetSig = appCmd{0x08, "rspGetSig", tkeyclient.CmdLen128} cmdGetNameVersion = appCmd{0x09, "cmdGetNameVersion", tkeyclient.CmdLen1} rspGetNameVersion = appCmd{0x0a, "rspGetNameVersion", tkeyclient.CmdLen32} cmdGetFirmwareHash = appCmd{0x0b, "cmdGetFirmwareHash", tkeyclient.CmdLen32} rspGetFirmwareHash = appCmd{0x0c, "rspGetFirmwareHash", tkeyclient.CmdLen128} ) const MaxSignSize = 4096 type appCmd struct { code byte name string cmdLen tkeyclient.CmdLen } func (c appCmd) Code() byte { return c.code } func (c appCmd) CmdLen() tkeyclient.CmdLen { return c.cmdLen } func (c appCmd) Endpoint() tkeyclient.Endpoint { return tkeyclient.DestApp } func (c appCmd) String() string { return c.name } type Signer struct { tk *tkeyclient.TillitisKey // A connection to a TKey } // New allocates a struct for communicating with the ed25519 signer // app running on the TKey. You're expected to pass an existing // connection to it, so use it like this: // // tk := tkeyclient.New() // err := tk.Connect(port) // signer := tk1sign.New(tk) func New(tk *tkeyclient.TillitisKey) Signer { var signer Signer signer.tk = tk return signer } // Close closes the connection to the TKey func (s Signer) Close() error { if err := s.tk.Close(); err != nil { return fmt.Errorf("tk.Close: %w", err) } return nil } // GetAppNameVersion gets the name and version of the running app in // the same style as the stick itself. func (s Signer) GetAppNameVersion() (*tkeyclient.NameVersion, error) { id := 2 tx, err := tkeyclient.NewFrameBuf(cmdGetNameVersion, id) if err != nil { return nil, fmt.Errorf("NewFrameBuf: %w", err) } tkeyclient.Dump("GetAppNameVersion tx", tx) if err = s.tk.Write(tx); err != nil { return nil, fmt.Errorf("Write: %w", err) } s.tk.SetReadTimeoutNoErr(2) defer s.tk.SetReadTimeoutNoErr(0) rx, _, err := s.tk.ReadFrame(rspGetNameVersion, id) if err != nil { return nil, fmt.Errorf("ReadFrame: %w", err) } nameVer := &tkeyclient.NameVersion{} nameVer.Unpack(rx[2:]) return nameVer, nil } // GetPubkey fetches the public key of the signer. func (s Signer) GetPubkey() ([]byte, error) { id := 2 tx, err := tkeyclient.NewFrameBuf(cmdGetPubkey, id) if err != nil { return nil, fmt.Errorf("NewFrameBuf: %w", err) } tkeyclient.Dump("GetPubkey tx", tx) if err = s.tk.Write(tx); err != nil { return nil, fmt.Errorf("Write: %w", err) } rx, _, err := s.tk.ReadFrame(rspGetPubkey, id) tkeyclient.Dump("GetPubKey rx", rx) if err != nil { return nil, fmt.Errorf("ReadFrame: %w", err) } // Skip frame header & app header, returning size of ed25519 pubkey return rx[2 : 2+32], nil } // Sign signs the message in data and returns an ed25519 signature. func (s Signer) Sign(data []byte) ([]byte, error) { err := s.setSize(len(data)) if err != nil { return nil, fmt.Errorf("setSize: %w", err) } var offset int for nsent := 0; offset < len(data); offset += nsent { nsent, err = s.signLoad(data[offset:]) if err != nil { return nil, fmt.Errorf("signLoad: %w", err) } } if offset > len(data) { return nil, fmt.Errorf("transmitted more than expected") } signature, err := s.getSig() if err != nil { return nil, fmt.Errorf("getSig: %w", err) } return signature, nil } // SetSize sets the size of the data to sign. func (s Signer) setSize(size int) error { id := 2 tx, err := tkeyclient.NewFrameBuf(cmdSetSize, id) if err != nil { return fmt.Errorf("NewFrameBuf: %w", err) } // Set size tx[2] = byte(size) tx[3] = byte(size >> 8) tx[4] = byte(size >> 16) tx[5] = byte(size >> 24) tkeyclient.Dump("SetSignSize tx", tx) if err = s.tk.Write(tx); err != nil { return fmt.Errorf("Write: %w", err) } rx, _, err := s.tk.ReadFrame(rspSetSize, id) tkeyclient.Dump("SetSignSize rx", rx) if err != nil { return fmt.Errorf("ReadFrame: %w", err) } if rx[2] != tkeyclient.StatusOK { return fmt.Errorf("SetSignSize NOK") } return nil } // signload loads a chunk of a message to sign and waits for a // response from the signer. func (s Signer) signLoad(content []byte) (int, error) { id := 2 tx, err := tkeyclient.NewFrameBuf(cmdSignData, id) if err != nil { return 0, fmt.Errorf("NewFrameBuf: %w", err) } payload := make([]byte, cmdSignData.CmdLen().Bytelen()-1) copied := copy(payload, content) // Add padding if not filling the payload buffer. if copied < len(payload) { padding := make([]byte, len(payload)-copied) copy(payload[copied:], padding) } copy(tx[2:], payload) tkeyclient.Dump("LoadSignData tx", tx) if err = s.tk.Write(tx); err != nil { return 0, fmt.Errorf("Write: %w", err) } // Wait for reply rx, _, err := s.tk.ReadFrame(rspSignData, id) if err != nil { return 0, fmt.Errorf("ReadFrame: %w", err) } if rx[2] != tkeyclient.StatusOK { return 0, fmt.Errorf("SignData NOK") } return copied, nil } // getSig gets the ed25519 signature from the signer app, if // available. func (s Signer) getSig() ([]byte, error) { id := 2 tx, err := tkeyclient.NewFrameBuf(cmdGetSig, id) if err != nil { return nil, fmt.Errorf("NewFrameBuf: %w", err) } tkeyclient.Dump("getSig tx", tx) if err = s.tk.Write(tx); err != nil { return nil, fmt.Errorf("Write: %w", err) } rx, _, err := s.tk.ReadFrame(rspGetSig, id) if err != nil { return nil, fmt.Errorf("ReadFrame: %w", err) } if rx[2] != tkeyclient.StatusOK { return nil, fmt.Errorf("getSig NOK") } // Skip frame header, app header, and status; returning size of // ed25519 signature return rx[3 : 3+64], nil } // GetFWDigest asks the signer app to hash len bytes of the firmware. // // It returns the resulting SHA512 digest or an error. func (s Signer) GetFWDigest(len int) ([]byte, error) { id := 2 tx, err := tkeyclient.NewFrameBuf(cmdGetFirmwareHash, id) if err != nil { return nil, fmt.Errorf("NewFrameBuf: %w", err) } // Set firmware length as 32 bit LE tx[2] = byte(len) tx[3] = byte(len >> 8) tx[4] = byte(len >> 16) tx[5] = byte(len >> 24) tkeyclient.Dump("GetFirmwareHash tx", tx) if err = s.tk.Write(tx); err != nil { return nil, fmt.Errorf("Write: %w", err) } // Wait for reply rx, _, err := s.tk.ReadFrame(rspGetFirmwareHash, id) if err != nil { return nil, fmt.Errorf("ReadFrame: %w", err) } tkeyclient.Dump("GetFirmwareHash rx", rx) if rx[2] != tkeyclient.StatusOK { return nil, fmt.Errorf("GetFirmwareHash NOK") } // Skip frame header, app header, and status; returning // firmware SHA512 digest return rx[3 : 3+64], nil } tkeysign-1.0.1/tools/000077500000000000000000000000001471266676400145315ustar00rootroot00000000000000tkeysign-1.0.1/tools/spdx-ensure000077500000000000000000000034261471266676400167410ustar00rootroot00000000000000#!/bin/bash # SPDX-FileCopyrightText: 2023 Tillitis AB # SPDX-License-Identifier: BSD-2-Clause set -eu # Check for the SPDX tag in all files in the repo. Exit with a non-zero code if # some is missing. The missingok arrays below contain files and directories # with files where the the tag is not required. cd "${0%/*}" cd .. tag="SPDX-License-Identifier:" missingok_dirs=( .github/workflows/ ) missingok_files=( .editorconfig .gitignore .golangci.yml LICENSE LICENSES/BSD-2-Clause.txt Makefile README.md RELEASE.md go.mod go.sum ) is_missingok() { item="$1" # ok for empty files [[ -f "$item" ]] && [[ ! -s "$item" ]] && return 0 for fileok in "${missingok_files[@]}"; do [[ "$item" = "$fileok" ]] && return 0 done for dirok in "${missingok_dirs[@]}"; do [[ "$item" =~ ^$dirok ]] && return 0 done return 1 } printf "* Checking for SPDX tags in %s\n" "$PWD" mapfile -t repofiles < <(git ls-files || true) if [[ -z "${repofiles[*]}" ]]; then printf "* No files in the repo?!\n" exit 1 fi failed=0 printed=0 for fileok in "${missingok_files[@]}"; do [[ -f "$fileok" ]] && continue if (( !printed )); then printf "* Some files in missingok_files are themselves missing:\n" printed=1 failed=1 fi printf "%s\n" "$fileok" done printed=0 for dirok in "${missingok_dirs[@]}"; do [[ -d "$dirok" ]] && continue if (( !printed )); then printf "* Some dirs in missingok_dirs are themselves missing:\n" printed=1 failed=1 fi printf "%s\n" "$dirok" done printed=0 for file in "${repofiles[@]}"; do is_missingok "$file" && continue if ! grep -q "$tag" "$file"; then if (( !printed )); then printf "* Files missing the SPDX tag:\n" printed=1 failed=1 fi printf "%s\n" "$file" fi done exit "$failed"