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).
135136var attestationPubkeysMap map [string ]string
136137
138+ const attestationPayloadLength = 32 + 64 + 64 + 32 + 64
139+
137140func 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
0 commit comments