1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2025 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package asserts
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"math/big"
"golang.org/x/crypto/sha3"
)
// HardwareIdentity holds a hardware identity assertion, which is a statement
// that verifies the identity of a physical piece of hardware
type HardwareIdentity struct {
assertionBase
hardwareIDKeySha3384 string
hardwareKey crypto.PublicKey
}
// IssuerID returns the Snap Store account of the issuer and signer of the voucher.
// This can be used to ensure the voucher has originated from a suitable source
// (e.g. the manufacturer or brand).
func (h *HardwareIdentity) IssuerID() string {
return h.HeaderString("issuer-id")
}
// Manufacturer returns the name of the device manufacturer.
func (h *HardwareIdentity) Manufacturer() string {
return h.HeaderString("manufacturer")
}
// HardwareName returns the designation of the hardware device model.
func (h *HardwareIdentity) HardwareName() string {
return h.HeaderString("hardware-name")
}
// HardwareID returns the identification of the individual hardware device.
// It is not called a serial number as there is no strict requirement that
// this value is the same as the serial number in a resulting serial assertion on the device.
func (h *HardwareIdentity) HardwareID() string {
return h.HeaderString("hardware-id")
}
// HardwareIDKey returns hardware identity public key,
// which is the body of a parsable form (PEM) as defined by RFC7468ยง13.
func (h *HardwareIdentity) HardwareIDKey() crypto.PublicKey {
return h.hardwareKey
}
// HardwareIDKeySha3384 returns the hash of the public key binary data encoded in the hardware-id-key header.
// This is included as it is used as part of the primary key for the assertion.
func (h *HardwareIdentity) HardwareIDKeySha3384() string {
return h.hardwareIDKeySha3384
}
func assembleHardwareIdentity(assert assertionBase) (Assertion, error) {
issuerID, err := checkStringMatches(assert.headers, "issuer-id", validAccountID)
if err != nil {
return nil, err
}
if issuerID != assert.HeaderString("authority-id") {
return nil, errors.New("issuer id must match authority id")
}
_, err = checkNotEmptyString(assert.headers, "manufacturer")
if err != nil {
return nil, err
}
_, err = checkStringMatches(assert.headers, "hardware-name", validModel)
if err != nil {
return nil, err
}
_, err = checkNotEmptyString(assert.headers, "hardware-id")
if err != nil {
return nil, err
}
hardwareIDKey, err := checkNotEmptyString(assert.headers, "hardware-id-key")
if err != nil {
return nil, err
}
pubKey, err := checkStringIsPEM([]byte(hardwareIDKey))
if err != nil {
return nil, err
}
hardwareIDKeySha3384 := assert.HeaderString("hardware-id-key-sha3-384")
hash := sha3.New384()
hash.Write([]byte(hardwareIDKey))
hashed := hash.Sum(nil)
// no error can be returned because the hash is initialized beforehand
hashedHardwareIDKey, _ := EncodeDigest(crypto.SHA3_384, hashed)
if hardwareIDKeySha3384 != hashedHardwareIDKey {
return nil, fmt.Errorf("hardware id key does not match provided hash")
}
return &HardwareIdentity{
assertionBase: assert,
hardwareIDKeySha3384: hardwareIDKeySha3384,
hardwareKey: pubKey,
}, nil
}
// checkStringIsPEM checks if string is the body of a parsable form (PEM).
// It assumes the BEGIN and END lines are omitted. The function returns a
// non-nil error if the string fails to be a PEM.
func checkStringIsPEM(data []byte) (crypto.PublicKey, error) {
// add begin and end lines to PEM body
var bb bytes.Buffer
bb.WriteString("-----BEGIN PUBLIC KEY-----\n")
bb.Write(data)
bb.WriteString("\n-----END PUBLIC KEY-----\n")
block, _ := pem.Decode(bb.Bytes())
if block == nil {
return nil, errors.New("no PEM block was found")
}
pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("cannot parse public key: %v", err)
}
return pubKey, nil
}
// VerifyNonceSignature checks the signature of a given nonce against the hardware id key.
// It is used by the model service to verify the request-id.
// It currently supports key with algorithms RSA, ECDSA, and ED25519.
// The hash algorithm used is also specified as a parameter.
func (h *HardwareIdentity) VerifyNonceSignature(nonce, signature []byte, hashAlg crypto.Hash) error {
if !hashAlg.Available() {
return fmt.Errorf("unsupported hash type: %s", hashAlg.String())
}
hash := hashAlg.New()
hash.Write(nonce)
hashed := hash.Sum(nil)
switch keyType := h.hardwareKey.(type) {
case *rsa.PublicKey:
return verifySignatureWithRSAKey(hashed, signature, h.hardwareKey.(*rsa.PublicKey), hashAlg)
case *ecdsa.PublicKey:
return verifySignatureWithECDSAKey(hashed, signature, h.hardwareKey.(*ecdsa.PublicKey))
case ed25519.PublicKey:
return verifySignatureWithED25519Key(hashed, signature, h.hardwareKey.(ed25519.PublicKey))
default:
return fmt.Errorf("unsupported algorithm type: %T", keyType)
}
}
func verifySignatureWithRSAKey(hashed, signature []byte, pubKey *rsa.PublicKey, hashAlg crypto.Hash) error {
if err := rsa.VerifyPKCS1v15(pubKey, hashAlg, hashed, signature); err != nil {
return fmt.Errorf("invalid signature: %v", err)
}
return nil
}
func verifySignatureWithECDSAKey(hashed, signature []byte, pubKey *ecdsa.PublicKey) error {
// EcdsaSignature struct defines ASN.1 layout of ECDSA signature
type EcdsaSignature struct {
R, S *big.Int
}
var sig EcdsaSignature
rest, err := asn1.Unmarshal(signature, &sig)
if err != nil {
return err
}
// Ensure all bytes were consumed
if len(rest) > 0 {
return errors.New("invalid signature: trailing bytes")
}
if !ecdsa.Verify(pubKey, hashed, sig.R, sig.S) {
return errors.New("invalid signature")
}
return nil
}
func verifySignatureWithED25519Key(hashed, signature []byte, pubKey ed25519.PublicKey) error {
if !ed25519.Verify(pubKey, hashed, signature) {
return errors.New("invalid signature")
}
return nil
}
|