Skip to content

Commit c3430d8

Browse files
committed
Merge branch 'attest'
2 parents 15a1f0c + c8370d8 commit c3430d8

2 files changed

Lines changed: 232 additions & 54 deletions

File tree

api/firmware/attestation.go

Lines changed: 87 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"crypto/elliptic"
99
"crypto/sha256"
1010
"encoding/hex"
11+
"errors"
1112
"fmt"
1213
"math/big"
1314

@@ -134,6 +135,8 @@ var attestationPubkeys = []string{
134135
// The identifier is sha256(pubkey).
135136
var attestationPubkeysMap map[string]string
136137

138+
const attestationPayloadLength = 32 + 64 + 64 + 32 + 64
139+
137140
func unhex(s string) []byte {
138141
b, err := hex.DecodeString(s)
139142
if err != nil {
@@ -151,82 +154,114 @@ func init() {
151154
}
152155
}
153156

154-
// performAttestation sends a random challenge and verifies that the response can be verified with
155-
// Shift's root attestation pubkeys. Returns true if the verification is successful.
156-
func (device *Device) performAttestation() (bool, error) {
157-
if !device.version.AtLeast(semver.NewSemVer(2, 0, 0)) {
158-
// skip warning for v1.0.0, where attestation was not supported.
159-
return true, nil
160-
}
161-
challenge := bytesOrPanic(32)
162-
response, err := device.rawQuery(append([]byte(opAttestation), challenge...))
163-
if err != nil {
164-
device.log.Error(fmt.Sprintf("attestation: could not perform request. challenge=%x", challenge), err)
165-
return false, err
166-
}
157+
func verifyECDSASignature(pubkey *ecdsa.PublicKey, message []byte, signature []byte) bool {
158+
sigR := new(big.Int).SetBytes(signature[:32])
159+
sigS := new(big.Int).SetBytes(signature[32:])
160+
sigHash := sha256.Sum256(message)
161+
return ecdsa.Verify(pubkey, sigHash[:], sigR, sigS)
162+
}
167163

168-
// See parsing below for what the sizes mean.
169-
if len(response) < 1+32+64+64+32+64 {
170-
device.log.Error(
171-
fmt.Sprintf("attestation: response too short. challenge=%x, response=%x", challenge, response), nil)
172-
return false, nil
173-
}
174-
if string(response[:1]) != responseSuccess {
175-
device.log.Error(
176-
fmt.Sprintf("attestation: expected success. challenge=%x, response=%x", challenge, response), nil)
177-
return false, nil
164+
type invalidAttestationError struct {
165+
message string
166+
}
167+
168+
func (err invalidAttestationError) Error() string {
169+
return err.message
170+
}
171+
172+
// VerifyAttestation verifies the 256-byte attestation payload returned by the device, excluding the
173+
// first success byte, against Shift's root attestation pubkeys and the original challenge.
174+
func VerifyAttestation(challenge []byte, attestation []byte) error {
175+
if len(attestation) != attestationPayloadLength {
176+
return errp.Newf(
177+
"attestation must be %d bytes, got %d", attestationPayloadLength, len(attestation))
178178
}
179-
rsp := response[1:]
179+
180180
var bootloaderHash, devicePubkeyBytes, certificate, rootPubkeyIdentifier, challengeSignature []byte
181-
bootloaderHash, rsp = rsp[:32], rsp[32:]
182-
devicePubkeyBytes, rsp = rsp[:64], rsp[64:]
183-
certificate, rsp = rsp[:64], rsp[64:]
184-
rootPubkeyIdentifier, rsp = rsp[:32], rsp[32:]
185-
challengeSignature = rsp[:64]
181+
bootloaderHash, attestation = attestation[:32], attestation[32:]
182+
devicePubkeyBytes, attestation = attestation[:64], attestation[64:]
183+
certificate, attestation = attestation[:64], attestation[64:]
184+
rootPubkeyIdentifier, attestation = attestation[:32], attestation[32:]
185+
challengeSignature = attestation[:64]
186186

187187
pubkeyHex, ok := attestationPubkeysMap[hex.EncodeToString(rootPubkeyIdentifier)]
188188
if !ok {
189-
device.log.Error(fmt.Sprintf(
190-
"could not find root pubkey. challenge=%x, response=%x, identifier=%x",
191-
challenge,
192-
response,
193-
rootPubkeyIdentifier), nil)
194-
return false, nil
189+
return errp.Newf("could not find root pubkey. identifier=%x", rootPubkeyIdentifier)
195190
}
191+
196192
rootPubkeyBytes, err := hex.DecodeString(pubkeyHex)
197193
if err != nil {
198-
panic(errp.WithStack(err))
194+
return errp.WithStack(err)
199195
}
200196
rootPubkey, err := btcec.ParsePubKey(rootPubkeyBytes)
201197
if err != nil {
202-
panic(errp.WithStack(err))
198+
return errp.WithStack(err)
203199
}
200+
204201
devicePubkey := ecdsa.PublicKey{
205202
Curve: elliptic.P256(),
206203
X: new(big.Int).SetBytes(devicePubkeyBytes[:32]),
207204
Y: new(big.Int).SetBytes(devicePubkeyBytes[32:]),
208205
}
209206

210-
verify := func(pubkey *ecdsa.PublicKey, message []byte, signature []byte) bool {
211-
sigR := new(big.Int).SetBytes(signature[:32])
212-
sigS := new(big.Int).SetBytes(signature[32:])
213-
sigHash := sha256.Sum256(message)
214-
return ecdsa.Verify(pubkey, sigHash[:], sigR, sigS)
215-
}
216-
217-
// Verify certificate
218207
var certMsg bytes.Buffer
219208
certMsg.Write(bootloaderHash)
220209
certMsg.Write(devicePubkeyBytes)
221-
if !verify(rootPubkey.ToECDSA(), certMsg.Bytes(), certificate) {
222-
device.log.Error(
223-
fmt.Sprintf("attestation: could not verify certificate. challenge=%x, response=%x", challenge, response), nil)
224-
return false, nil
210+
if !verifyECDSASignature(rootPubkey.ToECDSA(), certMsg.Bytes(), certificate) {
211+
return errp.New("could not verify certificate")
212+
}
213+
if !verifyECDSASignature(&devicePubkey, challenge, challengeSignature) {
214+
return errp.New("could not verify challenge signature")
215+
}
216+
217+
return nil
218+
}
219+
220+
// GetAttestation sends challenge to the device and returns the 256-byte attestation payload,
221+
// excluding the first success byte.
222+
func (device *Device) GetAttestation(challenge []byte) ([]byte, error) {
223+
if !device.version.AtLeast(semver.NewSemVer(2, 0, 0)) {
224+
return nil, errp.New("attestation not supported")
225+
}
226+
227+
response, err := device.rawQuery(append([]byte(opAttestation), challenge...))
228+
if err != nil {
229+
return nil, err
230+
}
231+
232+
if len(response) < 1+attestationPayloadLength {
233+
return nil, invalidAttestationError{message: "response too short"}
234+
}
235+
if string(response[:1]) != responseSuccess {
236+
return nil, invalidAttestationError{message: "expected success"}
237+
}
238+
239+
return response[1 : 1+attestationPayloadLength], nil
240+
}
241+
242+
// performAttestation sends a random challenge and verifies that the response can be verified with
243+
// Shift's root attestation pubkeys. Returns true if the verification is successful.
244+
func (device *Device) performAttestation() (bool, error) {
245+
if !device.version.AtLeast(semver.NewSemVer(2, 0, 0)) {
246+
// skip warning for v1.0.0, where attestation was not supported.
247+
return true, nil
248+
}
249+
challenge := bytesOrPanic(32)
250+
attestation, err := device.GetAttestation(challenge)
251+
if err != nil {
252+
var invalidErr invalidAttestationError
253+
if errors.As(err, &invalidErr) {
254+
device.log.Error(fmt.Sprintf("attestation: %v. challenge=%x", err, challenge), nil)
255+
return false, nil
256+
}
257+
device.log.Error(fmt.Sprintf("attestation: could not perform request. challenge=%x", challenge), err)
258+
return false, err
225259
}
226-
// Verify challenge
227-
if !verify(&devicePubkey, challenge, challengeSignature) {
260+
261+
err = VerifyAttestation(challenge, attestation)
262+
if err != nil {
228263
device.log.Error(
229-
fmt.Sprintf("attestation: could not verify challgege signature. challenge=%x, response=%x", challenge, response), nil)
264+
fmt.Sprintf("attestation: %v. challenge=%x, attestation=%x", err, challenge, attestation), nil)
230265
return false, nil
231266
}
232267
return true, nil

api/firmware/attestation_test.go

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func p256PrivKeyFromBytes(k []byte) *ecdsa.PrivateKey {
5454
return priv
5555
}
5656

57-
func TestAttestation(t *testing.T) {
57+
func TestPerformAttestation(t *testing.T) {
5858

5959
// Arbitrary values, they do not have any special meaning.
6060
// identifier is the sha256 hash of the uncompressed pubkey.
@@ -100,7 +100,7 @@ func TestAttestation(t *testing.T) {
100100

101101
// Invalid response status code.
102102
communication.MockQuery = func([]byte) ([]byte, error) {
103-
response := make([]byte, 1+32+64+64+32+64)
103+
response := make([]byte, 1+attestationPayloadLength)
104104
response[0] = 0x01
105105
return response, nil
106106
}
@@ -192,3 +192,146 @@ func TestAttestation(t *testing.T) {
192192
require.NoError(t, err)
193193
require.True(t, success)
194194
}
195+
196+
func TestVerifyAttestation(t *testing.T) {
197+
// Arbitrary values, they do not have any special meaning.
198+
// identifier is the sha256 hash of the uncompressed pubkey.
199+
challenge := unhex("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")
200+
rootPubkeyIdentifier := unhex("11554d841e74066eebc3556ed6dea4d6ceef3940009222c77c3b966349989de1")
201+
rootPrivateKey, rootPublicKey := btcec.PrivKeyFromBytes(
202+
unhex("15608dfed8e876bed1cf2599574ce853f7a2a017d19ba0aabd4bcba033a70880"),
203+
)
204+
bootloaderHash := unhex("3fdf2ff2dcbd31d161a525a88cb57641209c7eac2bc014564a03d34a825144f0")
205+
devicePrivateKey := p256PrivKeyFromBytes(
206+
unhex("9b1a4d293a6eef1960d8afab5e58dd581b135152ec3399bde9268fa23051321b"),
207+
)
208+
devicePublicKey := devicePrivateKey.PublicKey
209+
devicePubkeyBytes := make([]byte, 64)
210+
copy(devicePubkeyBytes[:32], devicePublicKey.X.Bytes())
211+
copy(devicePubkeyBytes[32:], devicePublicKey.Y.Bytes())
212+
certificate := makeCertificate(rootPrivateKey, bootloaderHash, devicePubkeyBytes)
213+
214+
undo := addAttestationPubkey(hex.EncodeToString(rootPublicKey.SerializeUncompressed()))
215+
defer undo()
216+
217+
makeAttestation := func(
218+
certificate []byte,
219+
rootPubkeyIdentifier []byte,
220+
challengeSignature []byte,
221+
) []byte {
222+
var buf bytes.Buffer
223+
buf.Write(bootloaderHash)
224+
buf.Write(devicePubkeyBytes)
225+
buf.Write(certificate)
226+
buf.Write(rootPubkeyIdentifier)
227+
buf.Write(challengeSignature)
228+
return buf.Bytes()
229+
}
230+
makeChallengeSignature := func(challenge []byte) []byte {
231+
sigHash := sha256.Sum256(challenge)
232+
sigR, sigS, err := ecdsa.Sign(rand.Reader, devicePrivateKey, sigHash[:])
233+
if err != nil {
234+
panic(err)
235+
}
236+
signature := make([]byte, 64)
237+
sigR.FillBytes(signature[:32])
238+
sigS.FillBytes(signature[32:])
239+
return signature
240+
}
241+
242+
err := VerifyAttestation(challenge, nil)
243+
require.EqualError(t, err, "attestation must be 256 bytes, got 0")
244+
245+
err = VerifyAttestation(
246+
challenge,
247+
makeAttestation(
248+
make([]byte, 64),
249+
make([]byte, 32),
250+
make([]byte, 64),
251+
),
252+
)
253+
require.EqualError(t, err, "could not find root pubkey. identifier=0000000000000000000000000000000000000000000000000000000000000000")
254+
255+
err = VerifyAttestation(
256+
challenge,
257+
makeAttestation(
258+
make([]byte, 64),
259+
rootPubkeyIdentifier,
260+
make([]byte, 64),
261+
),
262+
)
263+
require.EqualError(t, err, "could not verify certificate")
264+
265+
err = VerifyAttestation(
266+
challenge,
267+
makeAttestation(
268+
certificate,
269+
rootPubkeyIdentifier,
270+
make([]byte, 64),
271+
),
272+
)
273+
require.EqualError(t, err, "could not verify challenge signature")
274+
275+
err = VerifyAttestation(
276+
challenge,
277+
makeAttestation(
278+
certificate,
279+
rootPubkeyIdentifier,
280+
makeChallengeSignature(challenge),
281+
),
282+
)
283+
require.NoError(t, err)
284+
}
285+
286+
func TestGetAttestation(t *testing.T) {
287+
challenge := unhex("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")
288+
communication := &mocks.Communication{}
289+
product := common.ProductBitBox02BTCOnly
290+
291+
device := NewDevice(
292+
semver.NewSemVer(1, 0, 0),
293+
&product,
294+
&mocks.Config{}, communication, &mocks.Logger{},
295+
)
296+
attestation, err := device.GetAttestation(challenge)
297+
require.EqualError(t, err, "attestation not supported")
298+
require.Nil(t, attestation)
299+
300+
device = NewDevice(
301+
semver.NewSemVer(2, 0, 0),
302+
&product,
303+
&mocks.Config{}, communication, &mocks.Logger{},
304+
)
305+
306+
expectedErr := errors.New("error")
307+
communication.MockQuery = func([]byte) ([]byte, error) {
308+
return nil, expectedErr
309+
}
310+
attestation, err = device.GetAttestation(challenge)
311+
require.Equal(t, expectedErr, err)
312+
require.Nil(t, attestation)
313+
314+
communication.MockQuery = func([]byte) ([]byte, error) {
315+
return nil, nil
316+
}
317+
attestation, err = device.GetAttestation(challenge)
318+
require.EqualError(t, err, "response too short")
319+
require.Nil(t, attestation)
320+
321+
communication.MockQuery = func([]byte) ([]byte, error) {
322+
response := make([]byte, 1+attestationPayloadLength)
323+
response[0] = 0x01
324+
return response, nil
325+
}
326+
attestation, err = device.GetAttestation(challenge)
327+
require.EqualError(t, err, "expected success")
328+
require.Nil(t, attestation)
329+
330+
expectedAttestation := bytes.Repeat([]byte{0x42}, attestationPayloadLength)
331+
communication.MockQuery = func([]byte) ([]byte, error) {
332+
return append([]byte{0x00}, expectedAttestation...), nil
333+
}
334+
attestation, err = device.GetAttestation(challenge)
335+
require.NoError(t, err)
336+
require.Equal(t, expectedAttestation, attestation)
337+
}

0 commit comments

Comments
 (0)