github.com/transparency-dev/armored-witness-os@v0.1.3-0.20240514084412-27eef7325168/cmd/witnessctl/api.go (about)

     1  // Copyright 2022 The Armored Witness OS authors. All Rights Reserved.
     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  //go:build !tamago
    16  // +build !tamago
    17  
    18  package main
    19  
    20  import (
    21  	"compress/gzip"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"log"
    26  	"net"
    27  
    28  	flynn_hid "github.com/flynn/hid"
    29  	"github.com/flynn/u2f/u2fhid"
    30  	"google.golang.org/protobuf/proto"
    31  
    32  	"github.com/transparency-dev/armored-witness-os/api"
    33  )
    34  
    35  // we use 64 as a safe guess for protobuf wire overhead
    36  const maxChunkSize = api.MaxMessageSize - 64
    37  
    38  func confirm(msg string) bool {
    39  	var res string
    40  
    41  	fmt.Printf("%s (y/n): ", msg)
    42  	fmt.Scanln(&res)
    43  
    44  	return res == "y"
    45  }
    46  
    47  type Device struct {
    48  	u2f *u2fhid.Device
    49  	usb *flynn_hid.DeviceInfo
    50  }
    51  
    52  func (d Device) status() (s *api.Status, err error) {
    53  	res, err := d.u2f.Command(api.U2FHID_ARMORY_INF, nil)
    54  
    55  	if err != nil {
    56  		return
    57  	}
    58  
    59  	s = &api.Status{}
    60  	err = proto.Unmarshal(res, s)
    61  
    62  	return
    63  }
    64  
    65  func (d Device) hab() error {
    66  	buf, err := d.u2f.Command(api.U2FHID_ARMORY_HAB, nil)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	res := &api.Response{}
    71  	if err := proto.Unmarshal(buf, res); err != nil {
    72  		return err
    73  	}
    74  	if res.Error != api.ErrorCode_NONE {
    75  		return fmt.Errorf("%v: %s", res.Error, res.Payload)
    76  	}
    77  	return nil
    78  }
    79  
    80  func (d Device) getLogMessages(cmd byte) (string, error) {
    81  	r, w := io.Pipe()
    82  	defer r.Close()
    83  
    84  	errC := make(chan error, 1)
    85  	// Kick off a goroutine to fetch chunks of log and pipe it into the
    86  	// decompressor.
    87  	go func() {
    88  		// Signal that there's no more compressed data.
    89  		defer w.Close()
    90  		defer close(errC)
    91  
    92  		req := &api.LogMessagesRequest{}
    93  		rsp := &api.LogMessagesResponse{More: true}
    94  		for rsp.More {
    95  			rb, _ := proto.Marshal(req)
    96  			buf, err := d.u2f.Command(cmd, rb)
    97  			if err != nil {
    98  				errC <- err
    99  				return
   100  			}
   101  			if err := proto.Unmarshal(buf, rsp); err != nil {
   102  				errC <- err
   103  				return
   104  			}
   105  			w.Write(rsp.GetPayload())
   106  			req.Continue = true
   107  		}
   108  	}()
   109  
   110  	gz, err := gzip.NewReader(r)
   111  	if err != nil {
   112  		log.Printf("Failed to create gzip reader: %v", err)
   113  		return "", err
   114  	}
   115  	gz.Close()
   116  
   117  	// Grab the decompressed logs, and return
   118  	s, err := io.ReadAll(gz)
   119  	if err != nil {
   120  		return "", err
   121  	}
   122  
   123  	return string(s), <-errC
   124  }
   125  
   126  func (d Device) consoleLogs() (string, error) {
   127  	return d.getLogMessages(api.U2FHID_ARMORY_CONSOLE_LOGS)
   128  }
   129  
   130  func (d Device) crashLogs() (string, error) {
   131  	return d.getLogMessages(api.U2FHID_ARMORY_CRASH_LOGS)
   132  }
   133  
   134  func (d Device) cfg(dhcp bool, ip string, mask string, gw string, dns string, ntp string) error {
   135  	if len(ip) == 0 || len(gw) == 0 || len(dns) == 0 {
   136  		return errors.New("trusted applet IP, gatewy and DNS addresses must all be specified for configuration change (flags: -a -g -r)")
   137  	}
   138  
   139  	if addr := net.ParseIP(ip); addr == nil {
   140  		return errors.New("IP address is invalid")
   141  	}
   142  
   143  	if addr := net.ParseIP(mask); addr == nil {
   144  		return errors.New("Netmask is invalid")
   145  	}
   146  
   147  	if addr := net.ParseIP(gw); addr == nil {
   148  		return errors.New("Gateway address is invalid")
   149  	}
   150  
   151  	if _, _, err := net.SplitHostPort(dns); err != nil {
   152  		return fmt.Errorf("DNS address is invalid: %v", err)
   153  	}
   154  
   155  	c := &api.Configuration{
   156  		DHCP:      dhcp,
   157  		IP:        ip,
   158  		Netmask:   mask,
   159  		Gateway:   gw,
   160  		Resolver:  dns,
   161  		NTPServer: ntp,
   162  	}
   163  
   164  	log.Printf("sending configuration update to armored witness")
   165  
   166  	buf, err := d.u2f.Command(api.U2FHID_ARMORY_CFG, c.Bytes())
   167  
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	res := &api.Response{}
   173  
   174  	if err = proto.Unmarshal(buf, res); err != nil {
   175  		return err
   176  	}
   177  
   178  	if res.Error != api.ErrorCode_NONE {
   179  		return fmt.Errorf("%+v", res)
   180  	}
   181  
   182  	return nil
   183  }