Add Nix flake for mciasctl and mciasgrpcctl
Vendor dependencies and expose control program binaries via nix build. Uses nixpkgs-unstable for Go 1.26 support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
185
vendor/github.com/go-webauthn/webauthn/protocol/attestation_safetynet.go
generated
vendored
Normal file
185
vendor/github.com/go-webauthn/webauthn/protocol/attestation_safetynet.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
||||
"github.com/go-webauthn/webauthn/metadata"
|
||||
)
|
||||
|
||||
// attestationFormatValidationHandlerAndroidSafetyNet is the handler for the Android SafetyNet Attestation Statement
|
||||
// Format.
|
||||
//
|
||||
// When the authenticator is a platform authenticator on certain Android platforms, the attestation statement may be
|
||||
// based on the SafetyNet API. In this case the authenticator data is completely controlled by the caller of the
|
||||
// SafetyNet API (typically an application running on the Android platform) and the attestation statement provides some
|
||||
// statements about the health of the platform and the identity of the calling application (see SafetyNet Documentation
|
||||
// for more details).
|
||||
//
|
||||
// The syntax of an Android Attestation statement is defined as follows:
|
||||
//
|
||||
// $$attStmtType //= (
|
||||
// fmt: "android-safetynet",
|
||||
// attStmt: safetynetStmtFormat
|
||||
// )
|
||||
//
|
||||
// safetynetStmtFormat = {
|
||||
// ver: text,
|
||||
// response: bytes
|
||||
// }
|
||||
//
|
||||
// Specification: §8.5. Android SafetyNet Attestation Statement Format
|
||||
//
|
||||
// See: https://www.w3.org/TR/webauthn/#sctn-android-safetynet-attestation
|
||||
func attestationFormatValidationHandlerAndroidSafetyNet(att AttestationObject, clientDataHash []byte, mds metadata.Provider) (attestationType string, x5cs []any, err error) {
|
||||
// The syntax of an Android Attestation statement is defined as follows:
|
||||
// $$attStmtType //= (
|
||||
// fmt: "android-safetynet",
|
||||
// attStmt: safetynetStmtFormat
|
||||
// )
|
||||
|
||||
// safetynetStmtFormat = {
|
||||
// ver: text,
|
||||
// response: bytes
|
||||
// }
|
||||
|
||||
// §8.5.1 Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract
|
||||
// the contained fields.
|
||||
|
||||
// We have done this
|
||||
// §8.5.2 Verify that response is a valid SafetyNet response of version ver.
|
||||
version, present := att.AttStatement[stmtVersion].(string)
|
||||
if !present {
|
||||
return "", nil, ErrAttestationFormat.WithDetails("Unable to find the version of SafetyNet")
|
||||
}
|
||||
|
||||
if version == "" {
|
||||
return "", nil, ErrAttestationFormat.WithDetails("Not a proper version for SafetyNet")
|
||||
}
|
||||
|
||||
// TODO: provide user the ability to designate their supported versions.
|
||||
|
||||
response, present := att.AttStatement["response"].([]byte)
|
||||
if !present {
|
||||
return "", nil, ErrAttestationFormat.WithDetails("Unable to find the SafetyNet response")
|
||||
}
|
||||
|
||||
var token *jwt.Token
|
||||
|
||||
if token, err = jwt.Parse(string(response), keyFuncSafetyNetJWT, jwt.WithValidMethods([]string{jwt.SigningMethodRS256.Alg()})); err != nil {
|
||||
return "", nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error finding cert issued to correct hostname: %+v", err)).WithError(err)
|
||||
}
|
||||
|
||||
// marshall the JWT payload into the safetynet response json.
|
||||
var safetyNetResponse SafetyNetResponse
|
||||
|
||||
if err = mapstructure.Decode(token.Claims, &safetyNetResponse); err != nil {
|
||||
return "", nil, ErrAttestationFormat.WithDetails(fmt.Sprintf("Error parsing the SafetyNet response: %+v", err)).WithError(err)
|
||||
}
|
||||
|
||||
// §8.5.3 Verify that the nonce in the response is identical to the Base64 encoding of the SHA-256 hash of the concatenation
|
||||
// of authenticatorData and clientDataHash.
|
||||
nonceBuffer := sha256.Sum256(append(att.RawAuthData, clientDataHash...))
|
||||
|
||||
nonceBytes, err := base64.StdEncoding.DecodeString(safetyNetResponse.Nonce)
|
||||
if !bytes.Equal(nonceBuffer[:], nonceBytes) || err != nil {
|
||||
return "", nil, ErrInvalidAttestation.WithDetails("Invalid nonce for in SafetyNet response").WithError(err)
|
||||
}
|
||||
|
||||
// §8.5.4 Let attestationCert be the attestation certificate (https://www.w3.org/TR/webauthn/#attestation-certificate)
|
||||
certChain := token.Header[stmtX5C].([]any)
|
||||
l := make([]byte, base64.StdEncoding.DecodedLen(len(certChain[0].(string))))
|
||||
|
||||
n, err := base64.StdEncoding.Decode(l, []byte(certChain[0].(string)))
|
||||
if err != nil {
|
||||
return "", nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error finding cert issued to correct hostname: %+v", err)).WithError(err)
|
||||
}
|
||||
|
||||
attestationCert, err := x509.ParseCertificate(l[:n])
|
||||
if err != nil {
|
||||
return "", nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error finding cert issued to correct hostname: %+v", err)).WithError(err)
|
||||
}
|
||||
|
||||
// §8.5.5 Verify that attestationCert is issued to the hostname "attest.android.com".
|
||||
if err = attestationCert.VerifyHostname(attStatementAndroidSafetyNetHostname); err != nil {
|
||||
return "", nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error finding cert issued to correct hostname: %+v", err)).WithError(err)
|
||||
}
|
||||
|
||||
// §8.5.6 Verify that the ctsProfileMatch attribute in the payload of response is true.
|
||||
if !safetyNetResponse.CtsProfileMatch {
|
||||
return "", nil, ErrInvalidAttestation.WithDetails("ctsProfileMatch attribute of the JWT payload is false")
|
||||
}
|
||||
|
||||
if t := time.Unix(safetyNetResponse.TimestampMs/1000, 0); t.After(time.Now()) {
|
||||
// Zero tolerance for post-dated timestamps.
|
||||
return "", nil, ErrInvalidAttestation.WithDetails("SafetyNet response with timestamp after current time")
|
||||
} else if t.Before(time.Now().Add(-time.Minute)) {
|
||||
// Small tolerance for pre-dated timestamps.
|
||||
if mds != nil && mds.GetValidateEntry(context.Background()) {
|
||||
return "", nil, ErrInvalidAttestation.WithDetails("SafetyNet response with timestamp before one minute ago")
|
||||
}
|
||||
}
|
||||
|
||||
// §8.5.7 If successful, return implementation-specific values representing attestation type Basic and attestation
|
||||
// trust path attestationCert.
|
||||
return string(metadata.BasicFull), nil, nil
|
||||
}
|
||||
|
||||
func keyFuncSafetyNetJWT(token *jwt.Token) (key any, err error) {
|
||||
var (
|
||||
ok bool
|
||||
raw any
|
||||
chain []any
|
||||
first string
|
||||
der []byte
|
||||
cert *x509.Certificate
|
||||
)
|
||||
|
||||
if raw, ok = token.Header[stmtX5C]; !ok {
|
||||
return nil, fmt.Errorf("jwt header missing x5c")
|
||||
}
|
||||
|
||||
if chain, ok = raw.([]any); !ok || len(chain) == 0 {
|
||||
return nil, fmt.Errorf("jwt header x5c is not a non-empty array")
|
||||
}
|
||||
|
||||
if first, ok = chain[0].(string); !ok || first == "" {
|
||||
return nil, fmt.Errorf("jwt header x5c[0] not a base64 string")
|
||||
}
|
||||
|
||||
if der, err = base64.StdEncoding.DecodeString(first); err != nil {
|
||||
return nil, fmt.Errorf("decode x5c leaf: %w", err)
|
||||
}
|
||||
|
||||
if cert, err = x509.ParseCertificate(der); err != nil {
|
||||
if cert != nil {
|
||||
return cert.PublicKey, fmt.Errorf("parse x5c leaf: %w", err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("parse x5c leaf: %w", err)
|
||||
}
|
||||
|
||||
return cert.PublicKey, nil
|
||||
}
|
||||
|
||||
type SafetyNetResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
TimestampMs int64 `json:"timestampMs"`
|
||||
ApkPackageName string `json:"apkPackageName"`
|
||||
ApkDigestSha256 string `json:"apkDigestSha256"`
|
||||
CtsProfileMatch bool `json:"ctsProfileMatch"`
|
||||
ApkCertificateDigestSha256 []any `json:"apkCertificateDigestSha256"`
|
||||
BasicIntegrity bool `json:"basicIntegrity"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterAttestationFormat(AttestationFormatAndroidSafetyNet, attestationFormatValidationHandlerAndroidSafetyNet)
|
||||
}
|
||||
Reference in New Issue
Block a user