in server/server.go [401:533]
func (s *SecureSessionService) Finalize(ctx context.Context, req *sspb.FinalizeRequest) (*sspb.FinalizeResponse, error) {
if err := s.verifyToken(ctx); err != nil {
return nil, fmt.Errorf("failed to verify JWT: %w", err)
}
connID := base64.StdEncoding.EncodeToString(req.SessionContext)
ch, found := s.channels[connID]
if !found {
return nil, fmt.Errorf("session with id: %v not found", connID)
}
if ch.state != ServerStateAttestationNegotiated {
return nil, fmt.Errorf("session with id: %v in unexpected state: %d. Expecting: %d", connID, ch.state, ServerStateAttestationNegotiated)
}
// Unmarshal attestation evidence if included in request.
var clientAttEvidence attpb.AttestationEvidence
if len(req.GetAttestationEvidenceRecords()) > 0 {
ch.shim.QueueReceiveBuf(req.AttestationEvidenceRecords)
buf := make([]byte, len(req.AttestationEvidenceRecords))
offset := 0
priorChunkLen := 0
/*
* Approach: for a large attestation (e.g., 11K TLS read returns the attestation
* in chunks. The attestation size in total is smaller than the length
* of the req.AttestationEvidenceRecords buffer after its decrypted via
* ch.conn.Read. If ch.conn.Read is called beyond the total size of the
* decrypted attestion it will block and hang the connection. Given that we
* do not know the attestation's exact size, the current strategy is to keep
* reading while the decrypted chunks returned by the ch.conn.Read are getting
* larger. Once a decrease in size is detected, it is treated as the last chunk.
*/
for {
chunkLen, err := ch.conn.Read(buf[offset:])
if err != nil {
ch.state = ServerStateFailed
return nil, fmt.Errorf("failed to read client's AttestationEvidenceRecords message from TLS connection : %v", err)
}
offset += chunkLen
// The multi-chunk approach described above only applies to large attestations (e.g., 11K).
if priorChunkLen == 0 && chunkLen <= minUnchunkedAttestationSize {
break
}
if chunkLen <= priorChunkLen {
break
} else {
priorChunkLen = chunkLen
}
}
if err := proto.Unmarshal(buf[:offset], &clientAttEvidence); err != nil {
ch.state = ServerStateFailed
return nil, fmt.Errorf("failed to unmarshal AttestationEvidence: %w", err)
}
}
attestationExpected := false
for _, tp := range ch.attestationEvidenceTypes {
switch tp {
case attpb.AttestationEvidenceType_TPM2_QUOTE, attpb.AttestationEvidenceType_TCG_EVENT_LOG:
attestationExpected = true
}
}
if attestationExpected {
att := clientAttEvidence.GetAttestation()
if att == nil {
return nil, fmt.Errorf("negotiated vTPM attestation but payload did not contain attestation")
}
instanceInfo := att.GetInstanceInfo()
if instanceInfo == nil {
return nil, fmt.Errorf("instanceInfo is empty; can't look up shielded instance identity")
}
client, err := compute.NewService(ctx)
if err != nil {
return nil, fmt.Errorf("unable to create GCE client: %w", err)
}
instance, err := client.Instances.GetShieldedInstanceIdentity(
instanceInfo.GetProjectId(), instanceInfo.GetZone(), instanceInfo.GetInstanceName()).Do()
if err != nil {
return nil, fmt.Errorf("couldn't retrieve shielded instance identity: %w", err)
}
// Verify quote using the signing key returned by GetShieldedInstanceIdentity.
block, _ := pem.Decode([]byte(instance.SigningKey.EkPub))
if block == nil || block.Type != "PUBLIC KEY" {
return nil, fmt.Errorf("failed to decode PEM block containing public key")
}
pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to parse EK cert from GetShieldedInstanceIdentity: %w", err)
}
// Recreate the nonce generated on the client side to validate the attestation.
tlsState := ch.conn.ConnectionState()
material, err := tlsState.ExportKeyingMaterial(constants.ExportLabel, nil, 32)
if err != nil {
return nil, fmt.Errorf("error exporting key material: %w", err)
}
nonce := []byte(constants.AttestationPrefix)
nonce = append(nonce, material...)
ms, err := server.VerifyAttestation(att, server.VerifyOpts{Nonce: nonce, TrustedAKs: []crypto.PublicKey{pubKey}})
if err != nil {
return nil, fmt.Errorf("failed to verify quote: %w", err)
}
ch.ms = ms
glog.Infof("Verified quote for instance: %v; machine state: %v", instanceInfo.String(), ms)
} else {
glog.Infof("Negotiated null attestation; skipping attestation verification")
}
rep := &sspb.FinalizeResponse{}
ch.state = ServerStateAttestationAccepted
return rep, nil
}