blob: 5d893f4cc995c615826e3c2a753975023f07c820 [file] [log] [blame] [edit]
// Copyright 2024 The Update Framework Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"flag"
"fmt"
"os"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/options"
"github.com/theupdateframework/go-tuf/v2/metadata"
)
type tufSigner interface {
Sign(s signature.Signer) (*metadata.Signature, error)
}
/*
Run this to sign test data, like this:
~/git/go-tuf/internal/testutils $ go run \
signer/signer.go \
-k repository_data/keystore/timestamp_key \
-s rsassa-pss-sha256 \
-f repository_data/repository/metadata/timestamp.json
*/
func main() {
var scheme = flag.String("s", "", "set scheme to use for key")
var key = flag.String("k", "", "key file to load")
var f = flag.String("f", "", "file to sign")
flag.Parse()
if *scheme == "" {
fmt.Println("no scheme is set")
os.Exit(1)
}
if *key == "" {
fmt.Println("no key provided")
os.Exit(1)
}
if *f == "" {
fmt.Println("no metadata file provided")
os.Exit(1)
}
t, err := getTUFMDRole(*f)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
s, err := loadSigner(*key, *scheme)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var ts tufSigner
switch t {
case metadata.ROOT:
var rmd metadata.Metadata[metadata.RootType]
if _, err = rmd.FromFile(*f); err != nil {
fmt.Println(err)
os.Exit(1)
}
rmd.Signatures = []metadata.Signature{}
ts = &rmd
case metadata.TARGETS:
var tmd metadata.Metadata[metadata.TargetsType]
if _, err = tmd.FromFile(*f); err != nil {
fmt.Println(err)
os.Exit(1)
}
tmd.Signatures = []metadata.Signature{}
ts = &tmd
case metadata.SNAPSHOT:
var smd metadata.Metadata[metadata.SnapshotType]
if _, err = smd.FromFile(*f); err != nil {
fmt.Println(err)
os.Exit(1)
}
smd.Signatures = []metadata.Signature{}
ts = &smd
case metadata.TIMESTAMP:
var tsmd metadata.Metadata[metadata.TimestampType]
if _, err = tsmd.FromFile(*f); err != nil {
fmt.Println(err)
os.Exit(1)
}
tsmd.Signatures = []metadata.Signature{}
ts = &tsmd
}
if _, err = ts.Sign(s); err != nil {
fmt.Printf("failed to sign metadata of type %s: %s\n",
t, err)
os.Exit(1)
}
if err = persist(*f, ts); err != nil {
fmt.Printf("failed to persist updated metadata %s: %s\n",
*f, err)
}
}
func persist(p string, md any) error {
jsonBytes, err := json.MarshalIndent(md, "", " ")
if err != nil {
return err
}
err = os.WriteFile(p, jsonBytes, 0600)
return err
}
func loadSigner(k, s string) (signature.Signer, error) {
var pk any
var err error
var opts []signature.LoadOption
var rawKey []byte
switch s {
case metadata.KeySchemeRSASSA_PSS_SHA256:
if rawKey, err = getPemBytes(k); err != nil {
return nil, err
}
if pk, err = x509.ParsePKCS1PrivateKey(rawKey); err != nil {
return nil, err
}
var pssOpt = rsa.PSSOptions{Hash: crypto.SHA256}
opts = append(opts, options.WithRSAPSS(&pssOpt))
default:
return nil, fmt.Errorf("unsupported key scheme %s\n", s)
}
return signature.LoadSignerWithOpts(pk, opts...)
}
func getPemBytes(p string) ([]byte, error) {
var b []byte
var block *pem.Block
var err error
if b, err = os.ReadFile(p); err != nil {
return nil, err
}
if block, _ = pem.Decode(b); len(block.Bytes) == 0 {
return nil, errors.New("empty PEM block")
}
return block.Bytes, nil
}
func getTUFMDRole(p string) (string, error) {
var m map[string]any
mdBytes, err := os.ReadFile(p)
if err != nil {
return "", fmt.Errorf("failed to read file %s: %w", p, err)
}
if err := json.Unmarshal(mdBytes, &m); err != nil {
return "", fmt.Errorf("failed to parse TUF metadata: %w", err)
}
signedType := m["signed"].(map[string]any)["_type"].(string)
switch signedType {
case metadata.ROOT:
fallthrough
case metadata.TARGETS:
fallthrough
case metadata.SNAPSHOT:
fallthrough
case metadata.TIMESTAMP:
return signedType, nil
default:
return "", fmt.Errorf("unsupported role '%s'", signedType)
}
}