auth: publish passkey foundations preview
verify / verify (push) Successful in 3m40s

This commit is contained in:
2026-08-21 17:33:00 -04:00
parent fb6bbd0dad
commit bfe6cfd29e
230 changed files with 44547 additions and 17 deletions
@@ -0,0 +1,41 @@
package metadata
const (
// ProductionMDSRoot is the root certificate for the MDS.
//
// See: https://secure.globalsign.com/cacert/root-r3.crt
ProductionMDSRoot = "MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpHWD9f"
// ProductionMDSURL is the Production MDS URL.
ProductionMDSURL = "https://mds.fidoalliance.org"
// ConformanceMDSRoot is the root certificate for the MDS Conformance Suite.
//
// See: https://mds3.fido.tools/pki/MDS3ROOT.crt
ConformanceMDSRoot = "MIICaDCCAe6gAwIBAgIPBCqih0DiJLW7+UHXx/o1MAoGCCqGSM49BAMDMGcxCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMScwJQYDVQQLDB5GQUtFIE1ldGFkYXRhIDMgQkxPQiBST09UIEZBS0UxFzAVBgNVBAMMDkZBS0UgUm9vdCBGQUtFMB4XDTE3MDIwMTAwMDAwMFoXDTQ1MDEzMTIzNTk1OVowZzELMAkGA1UEBhMCVVMxFjAUBgNVBAoMDUZJRE8gQWxsaWFuY2UxJzAlBgNVBAsMHkZBS0UgTWV0YWRhdGEgMyBCTE9CIFJPT1QgRkFLRTEXMBUGA1UEAwwORkFLRSBSb290IEZBS0UwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASKYiz3YltC6+lmxhPKwA1WFZlIqnX8yL5RybSLTKFAPEQeTD9O6mOz+tg8wcSdnVxHzwnXiQKJwhrav70rKc2ierQi/4QUrdsPes8TEirZOkCVJurpDFbXZOgs++pa4XmjYDBeMAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGcfeCs0Y8D+lh6U5B2xSrR74eHTAfBgNVHSMEGDAWgBQGcfeCs0Y8D+lh6U5B2xSrR74eHTAKBggqhkjOPQQDAwNoADBlAjEA/xFsgri0xubSa3y3v5ormpPqCwfqn9s0MLBAtzCIgxQ/zkzPKctkiwoPtDzI51KnAjAmeMygX2S5Ht8+e+EQnezLJBJXtnkRWY+Zt491wgt/AwSs5PHHMv5QgjELOuMxQBc="
// ExampleMDSRoot is the example root certificate for the MDS.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1-ps-20250521.html#sctn-examples
ExampleMDSRoot = "MIIGGTCCBAGgAwIBAgIUdT9qLX0sVMRe8l0sLmHd3mZovQ0wDQYJKoZIhvcNAQELBQAwgZsxHzAdBgNVBAMMFkVYQU1QTEUgTURTMyBURVNUIFJPT1QxIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20xFDASBgNVBAoMC0V4YW1wbGUgT1JHMRAwDgYDVQQLDAdFeGFtcGxlMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQBgNVBAcMCVdha2VmaWVsZDAeFw0yMTA0MTkxMTM1MDdaFw00ODA5MDQxMTM1MDdaMIGbMR8wHQYDVQQDDBZFWEFNUExFIE1EUzMgVEVTVCBST09UMSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1wbGUuY29tMRQwEgYDVQQKDAtFeGFtcGxlIE9SRzEQMA4GA1UECwwHRXhhbXBsZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1ZMRIwEAYDVQQHDAlXYWtlZmllbGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDDjF5wyEWuhwDHsZosGdGFTCcI677rW881vV+UfW38J+K2ioFFNeGVsxbcebK6AVOiCDPFj0974IpeD9SFOhwAHoDu/LCfXdQWp8ZgQ91ULYWoW8o7NNSp01nbN9zmaO6/xKNCa0bzjmXoGqglqnP1AtRcWYvXOSKZy1rcPeDv4Dhcpdp6W72fBw0eWIqOhsrItuY2/N8ItBPiG03EX72nACq4nZJ/nAIcUbER8STSFPPzvE97TvShsi1FD8aO6l1WkR/QkreAGjMI++GbB2Qc1nN9Y/VEDbMDhQtxXQRdpFwubTjejkN9hKOtF3B71YrwIrng3V9RoPMFdapWMzSlI+WWHog0oTj1PqwJDDg7+z1I6vSDeVWAMKr9mq1w1OGNzgBopIjd9lRWkRtt2kQSPX9XxqS4E1gDDr8MKbpM3JuubQtNCg9D7Ljvbz6vwvUrbPHH+oREvucsp0PZ5PpizloepGIcLFxDQqCulGY2n7Ahl0JOFXJqOFCaK3TWHwBvZsaY5DgBuUvdUrwtgZNg2eg2omWXEepiVFQn3Fvj43Wh2npPMgIe5P0rwncXvROxaczd4rtajKS1ucoB9b9iKqM2+M1y/FDIgVf1fWEHwK7YdzxMlgOeLdeV/kqRU5PEUlLU9a2EwdOErrPbPKZmIfbs/L4B3k4zejMDH3Y+ZwIDAQABo1MwUTAdBgNVHQ4EFgQU8sWwq1TrurK7xMTwO1dKfeJBbCMwHwYDVR0jBBgwFoAU8sWwq1TrurK7xMTwO1dKfeJBbCMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAFw6M1PiIfCPIBQ5EBUPNmRvRFuDpolOmDofnf/+mv63LqwQZAdo/W8tzZ9kOFhq24SiLw0H7fsdG/jeREXiIZMNoW/rA6Uac8sU+FYF7Q+qp6CQLlSQbDcpVMifTQjcBk2xh+aLK9SrrXBqnTAhwS+offGtAW8DpoLuH4tAcQmIjlgMlN65jnELCuqNR/wpA+zch8LZW8saQ2cwRCwdr8mAzZoLbsDSVCHxQF3/kQjPT7Nao1q2iWcY3OYcRmKrieHDP67yeLUbVmetfZis2d6ZlkqHLB4ZW1xX4otsEFkuTJA3HWDRsNyhTwx1YoCLsYut5Zp0myqPNBq28w6qGMyyoJN0Z4RzMEO3R6i/MQNfhK55/8O2HciM6xb5t/aBSuHPKlBDrFWhpRnKYkaNtlUo35qV5IbKGKau3SdZdSRciaXUd/p81YmoF01UlhhMz/Rqr1k2gyA0a9tF8+awCeanYt5izl8YO0FlrOU1SQ5UQw4szqqZqbrf4e8fRuU2TXNx4zk+ImE7WRB44f6mSD746ZCBRogZ/SA5jUBu+OPe4/sEtERWRcQD+fXgce9ZEN0+peyJIKAsl5Rm2Bmgyg5IoyWwSG5W+WekGyEokpslou2Yc6EjUj5ndZWz5EiHAiQ74hNfDoCZIxVVLU3Qbp8a0S1bmsoT2JOsspIbtZUg="
)
const (
HeaderX509URI = "x5u"
HeaderX509Certificate = "x5c"
)
var (
errIntermediateCertRevoked = &Error{
Type: "intermediate_revoked",
Details: "Intermediate certificate is on issuers revocation list",
}
errLeafCertRevoked = &Error{
Type: "leaf_revoked",
Details: "Leaf certificate is on issuers revocation list",
}
errCRLUnavailable = &Error{
Type: "crl_unavailable",
Details: "Certificate revocation list is unavailable",
}
)
@@ -0,0 +1,290 @@
package metadata
import (
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/go-viper/mapstructure/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/go-webauthn/x/revoke"
)
// NewDecoder returns a new metadata decoder.
func NewDecoder(opts ...DecoderOption) (decoder *Decoder, err error) {
decoder = &Decoder{
client: &http.Client{},
parser: jwt.NewParser(),
hook: mapstructure.ComposeDecodeHookFunc(),
}
for _, opt := range opts {
if err = opt(decoder); err != nil {
return nil, fmt.Errorf("failed to apply decoder option: %w", err)
}
}
if decoder.root == "" {
decoder.root = ProductionMDSRoot
}
return decoder, nil
}
// Decoder handles decoding and specialized parsing of the metadata blob.
type Decoder struct {
client *http.Client
parser *jwt.Parser
hook mapstructure.DecodeHookFunc
root string
ignoreEntryParsingErrors bool
}
// Parse handles parsing of the raw JSON values of the metadata blob. Should be used after using [Decoder.Decode] or
// [Decoder.DecodeBytes].
func (d *Decoder) Parse(payload *PayloadJSON) (metadata *Metadata, err error) {
metadata = &Metadata{
Parsed: Parsed{
LegalHeader: payload.LegalHeader,
Number: payload.Number,
},
}
if metadata.Parsed.NextUpdate, err = time.Parse(time.DateOnly, payload.NextUpdate); err != nil {
return nil, fmt.Errorf("error occurred parsing next update value '%s': %w", payload.NextUpdate, err)
}
var parsed Entry
for _, entry := range payload.Entries {
if parsed, err = entry.Parse(); err != nil {
metadata.Unparsed = append(metadata.Unparsed, EntryError{
Error: err,
EntryJSON: entry,
})
continue
}
metadata.Parsed.Entries = append(metadata.Parsed.Entries, parsed)
}
if n := len(metadata.Unparsed); n != 0 && !d.ignoreEntryParsingErrors {
return metadata, fmt.Errorf("error occurred parsing metadata: %d entries had errors during parsing", n)
}
return metadata, nil
}
// Decode the blob from an [io.Reader]. This function will close the [io.ReadCloser] after completing.
func (d *Decoder) Decode(r io.Reader) (payload *PayloadJSON, err error) {
bytes, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return d.DecodeBytes(bytes)
}
// DecodeBytes handles decoding raw bytes. If you have a read closer it's suggested to use [Decoder.Decode].
func (d *Decoder) DecodeBytes(bytes []byte) (payload *PayloadJSON, err error) {
var token *jwt.Token
if token, err = d.parser.Parse(string(bytes), func(token *jwt.Token) (any, error) {
// 2. If the x5u attribute is present in the JWT Header.
if _, ok := token.Header[HeaderX509URI].([]any); ok {
// Never seen an x5u here, although it is in the spec.
return nil, errors.New("x5u encountered in header of metadata TOC payload")
}
// 3. If the x5u attribute is missing, the chain should be retrieved from the x5c attribute.
var (
x5c, chain []any
ok, valid bool
)
if x5c, ok = token.Header[HeaderX509Certificate].([]any); !ok {
// If that attribute is missing as well, Metadata TOC signing trust anchor is considered the TOC signing certificate chain.
chain = []any{d.root}
} else {
chain = x5c
}
// The certificate chain MUST be verified to properly chain to the metadata TOC signing trust anchor.
if valid, err = validateChain(d.root, chain); !valid || err != nil {
return nil, err
}
// Chain validated, extract the TOC signing certificate from the chain. Create a buffer large enough to hold the
// certificate bytes.
o := make([]byte, base64.StdEncoding.DecodedLen(len(chain[0].(string))))
var (
n int
cert *x509.Certificate
)
// Decode the base64 certificate into the buffer.
if n, err = base64.StdEncoding.Decode(o, []byte(chain[0].(string))); err != nil {
return nil, err
}
// Parse the certificate from the buffer.
if cert, err = x509.ParseCertificate(o[:n]); err != nil {
return nil, err
}
// 4. Verify the signature of the Metadata TOC object using the TOC signing certificate chain
// jwt.Parse() uses the TOC signing certificate public key internally to verify the signature.
return cert.PublicKey, err
}); err != nil {
return nil, err
}
var decoder *mapstructure.Decoder
payload = &PayloadJSON{}
if decoder, err = mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Metadata: nil,
Result: payload,
DecodeHook: d.hook,
TagName: "json",
}); err != nil {
return nil, err
}
if err = decoder.Decode(token.Claims); err != nil {
return payload, err
}
return payload, nil
}
// DecoderOption is a representation of a function that can set options within a decoder.
type DecoderOption func(decoder *Decoder) (err error)
// WithIgnoreEntryParsingErrors is a DecoderOption which ignores errors when parsing individual entries. The values for
// these entries will exist as an unparsed entry.
func WithIgnoreEntryParsingErrors() DecoderOption {
return func(decoder *Decoder) (err error) {
decoder.ignoreEntryParsingErrors = true
return nil
}
}
// WithRootCertificate overrides the root certificate used to validate the authenticity of the metadata payload.
func WithRootCertificate(value string) DecoderOption {
return func(decoder *Decoder) (err error) {
decoder.root = value
return nil
}
}
func validateChain(root string, chain []any) (bool, error) {
oRoot := make([]byte, base64.StdEncoding.DecodedLen(len(root)))
nRoot, err := base64.StdEncoding.Decode(oRoot, []byte(root))
if err != nil {
return false, err
}
rootcert, err := x509.ParseCertificate(oRoot[:nRoot])
if err != nil {
return false, err
}
roots := x509.NewCertPool()
roots.AddCert(rootcert)
o := make([]byte, base64.StdEncoding.DecodedLen(len(chain[1].(string))))
n, err := base64.StdEncoding.Decode(o, []byte(chain[1].(string)))
if err != nil {
return false, err
}
intcert, err := x509.ParseCertificate(o[:n])
if err != nil {
return false, err
}
if revoked, ok := revoke.VerifyCertificate(intcert); !ok {
issuer := intcert.IssuingCertificateURL
if issuer != nil {
return false, errCRLUnavailable
}
} else if revoked {
return false, errIntermediateCertRevoked
}
ints := x509.NewCertPool()
ints.AddCert(intcert)
l := make([]byte, base64.StdEncoding.DecodedLen(len(chain[0].(string))))
n, err = base64.StdEncoding.Decode(l, []byte(chain[0].(string)))
if err != nil {
return false, err
}
leafcert, err := x509.ParseCertificate(l[:n])
if err != nil {
return false, err
}
if revoked, ok := revoke.VerifyCertificate(leafcert); !ok {
return false, errCRLUnavailable
} else if revoked {
return false, errLeafCertRevoked
}
opts := x509.VerifyOptions{
Roots: roots,
Intermediates: ints,
}
_, err = leafcert.Verify(opts)
return err == nil, err
}
func mdsParseX509Certificate(value string) (certificate *x509.Certificate, err error) {
var n int
raw := make([]byte, base64.StdEncoding.DecodedLen(len(value)))
if n, err = base64.StdEncoding.Decode(raw, []byte(strings.TrimSpace(value))); err != nil {
return nil, fmt.Errorf("error occurred parsing *x509.certificate: error occurred decoding base64 data: %w", err)
}
if certificate, err = x509.ParseCertificate(raw[:n]); err != nil {
return nil, err
}
return certificate, nil
}
func mdsParseTimePointer(format, value string) (parsed *time.Time, err error) {
if value == "" {
return nil, nil
}
var p time.Time
if p, err = time.Parse(format, value); err != nil {
return nil, err
}
return &p, nil
}
@@ -0,0 +1,2 @@
// Package metadata handles metadata validation instrumentation.
package metadata
@@ -0,0 +1,1322 @@
package metadata
import (
"crypto/x509"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/google/uuid"
)
// Fetch creates a new HTTP client and gets the production metadata, decodes it, and parses it. This is an
// instrumentation simplification that makes it easier to either just grab the latest metadata or for implementers to
// see the rough process of retrieving it to implement any of their own logic.
func Fetch() (metadata *Metadata, err error) {
var (
decoder *Decoder
payload *PayloadJSON
resp *http.Response
)
client := &http.Client{}
if resp, err = client.Get(ProductionMDSURL); err != nil {
return nil, err
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error occurred fetching metadata: status code %d", resp.StatusCode)
}
if decoder, err = NewDecoder(WithIgnoreEntryParsingErrors()); err != nil {
return nil, err
}
if payload, err = decoder.Decode(resp.Body); err != nil {
return nil, err
}
return decoder.Parse(payload)
}
// Metadata represents a FIDO Metadata Service BLOB in either a fully parsed or partially parsed state.
type Metadata struct {
// Parsed contains the successfully parsed BLOB payload entries.
Parsed Parsed
// Unparsed contains entries that failed to parse, along with their errors.
Unparsed []EntryError
}
func (m *Metadata) ToMap() (metadata map[uuid.UUID]*Entry) {
metadata = make(map[uuid.UUID]*Entry)
for _, entry := range m.Parsed.Entries {
if entry.AaGUID != uuid.Nil {
metadata[entry.AaGUID] = &entry
}
}
return metadata
}
// Parsed is a structure representing the Metadata BLOB Payload dictionary.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-mds-blob-payload
type Parsed struct {
// The legalHeader, which MUST be in each BLOB, is an indication of the acceptance of the relevant legal agreement
// for using the MDS.
LegalHeader string
// The serial number of this Metadata BLOB Payload. This serial number MUST be incremented whenever the contents
// of the BLOB changes. Serial numbers MUST be consecutive and strictly monotonic, i.e. the successor BLOB will
// have a no value exactly incremented by one.
Number int
// ISO-8601 formatted date when the next update will be provided at latest. The use of this field is discouraged
// and may be removed in a future version of the spec.
NextUpdate time.Time
// List of zero or more MetadataBLOBPayloadEntry objects.
Entries []Entry
}
// PayloadJSON is an intermediary JSON/JWT representation of the Metadata BLOB Payload dictionary and the JSON
// representation of the [Parsed] struct.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-mds-blob-payload
type PayloadJSON struct {
// LegalHeader is an indication of the acceptance of the relevant legal agreement for using the MDS.
LegalHeader string `json:"legalHeader"`
// Number is the serial number of this Metadata BLOB Payload.
Number int `json:"no"`
// NextUpdate is an ISO-8601 formatted date when the next update will be provided at latest.
NextUpdate string `json:"nextUpdate"`
// Entries is a list of zero or more MetadataBLOBPayloadEntry objects.
Entries []EntryJSON `json:"entries"`
}
func (j PayloadJSON) Parse() (payload Parsed, err error) {
var update time.Time
if update, err = time.Parse(time.DateOnly, j.NextUpdate); err != nil {
return payload, fmt.Errorf("error occurred parsing next update value '%s': %w", j.NextUpdate, err)
}
n := len(j.Entries)
entries := make([]Entry, n)
for i := 0; i < n; i++ {
if entries[i], err = j.Entries[i].Parse(); err != nil {
return payload, fmt.Errorf("error occurred parsing entry %d: %w", i, err)
}
}
return Parsed{
LegalHeader: j.LegalHeader,
Number: j.Number,
NextUpdate: update,
Entries: entries,
}, nil
}
// Entry is a structure representing the Metadata BLOB Payload Entry dictionary.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-mds-blob-pe
type Entry struct {
// Aaid is the AAID of the authenticator this metadata BLOB payload entry relates to. This field MUST be set if
// the authenticator implements FIDO UAF.
Aaid string
// AaGUID is the Authenticator Attestation GUID. This field MUST be set if the authenticator implements FIDO2.
AaGUID uuid.UUID
// AttestationCertificateKeyIdentifiers is a list of the attestation certificate public key identifiers encoded as
// hex string. This field MUST be set if neither aaid nor aaguid are set.
AttestationCertificateKeyIdentifiers []string
// MetadataStatement is the metadataStatement JSON object as defined in FIDOMetadataStatement.
MetadataStatement Statement
// BiometricStatusReports is the status of the FIDO Biometric Certification of one or more biometric components of
// the Authenticator.
BiometricStatusReports []BiometricStatusReport
// StatusReports is an array of status reports applicable to this authenticator.
StatusReports []StatusReport
// TimeOfLastStatusChange is an ISO-8601 formatted date since when the status report array was set to the current
// value.
TimeOfLastStatusChange time.Time
// RogueListURL is a URL of a list of rogue (i.e. untrusted) individual authenticators.
RogueListURL *url.URL
// RogueListHash is the hash value computed over the Base64url encoding of the UTF-8 representation of the JSON
// encoded rogueList available at rogueListURL (with type rogueListEntry[]). This hash value MUST be present and
// non-empty whenever rogueListURL is present.
RogueListHash string
}
// EntryJSON is an intermediary JSON/JWT structure representing the Metadata BLOB Payload Entry dictionary and
// the JSON representation of the [Entry] struct.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-mds-blob-pe
type EntryJSON struct {
// Aaid is the AAID of the authenticator. Set if the authenticator implements FIDO UAF.
Aaid string `json:"aaid"`
// AaGUID is the Authenticator Attestation GUID. Set if the authenticator implements FIDO2.
AaGUID string `json:"aaguid"`
// AttestationCertificateKeyIdentifiers is a list of attestation certificate public key identifiers (hex).
AttestationCertificateKeyIdentifiers []string `json:"attestationCertificateKeyIdentifiers"`
// MetadataStatement is the metadataStatement JSON object as defined in FIDOMetadataStatement.
MetadataStatement StatementJSON `json:"metadataStatement"`
// BiometricStatusReports is the biometric certification status of one or more biometric components.
BiometricStatusReports []BiometricStatusReportJSON `json:"biometricStatusReports"`
// StatusReports is an array of status reports applicable to this authenticator.
StatusReports []StatusReportJSON `json:"statusReports"`
// TimeOfLastStatusChange is an ISO-8601 formatted date since when the status report array was set.
TimeOfLastStatusChange string `json:"timeOfLastStatusChange"`
// RogueListURL is a URL of a list of rogue (i.e. untrusted) individual authenticators.
RogueListURL string `json:"rogueListURL"`
// RogueListHash is the hash value computed over the Base64url encoding of the rogueList at rogueListURL.
RogueListHash string `json:"rogueListHash"`
}
func (j EntryJSON) Parse() (entry Entry, err error) {
var aaguid uuid.UUID
if len(j.AaGUID) != 0 {
if aaguid, err = uuid.Parse(j.AaGUID); err != nil {
return entry, fmt.Errorf("error occurred parsing metadata entry with AAGUID '%s': error parsing AAGUID: %w", j.AaGUID, err)
}
}
var statement Statement
if statement, err = j.MetadataStatement.Parse(); err != nil {
return entry, fmt.Errorf("error occurred parsing metadata entry with AAGUID '%s': %w", j.AaGUID, err)
}
var i, n int
n = len(j.BiometricStatusReports)
bsrs := make([]BiometricStatusReport, n)
for i = 0; i < n; i++ {
if bsrs[i], err = j.BiometricStatusReports[i].Parse(); err != nil {
return entry, fmt.Errorf("error occurred parsing metadata entry with AAGUID '%s': error occurred parsing biometric status report %d: %w", j.AaGUID, i, err)
}
}
n = len(j.StatusReports)
srs := make([]StatusReport, n)
for i = 0; i < n; i++ {
if srs[i], err = j.StatusReports[i].Parse(); err != nil {
return entry, fmt.Errorf("error occurred parsing metadata entry with AAGUID '%s': error occurred parsing status report %d: %w", j.AaGUID, i, err)
}
}
var change time.Time
if change, err = time.Parse(time.DateOnly, j.TimeOfLastStatusChange); err != nil {
return entry, fmt.Errorf("error occurred parsing metadata entry with AAGUID '%s': error occurred parsing time of last status change value: %w", j.AaGUID, err)
}
var rogues *url.URL
if len(j.RogueListURL) != 0 {
if rogues, err = url.ParseRequestURI(j.RogueListURL); err != nil {
return entry, fmt.Errorf("error occurred parsing metadata entry with AAGUID '%s': error occurred parsing rogue list URL value: %w", j.AaGUID, err)
}
if len(j.RogueListHash) == 0 {
return entry, fmt.Errorf("error occurred parsing metadata entry with AAGUID '%s': error occurred validating rogue list URL value: the rogue list hash was absent", j.AaGUID)
}
}
return Entry{
Aaid: j.Aaid,
AaGUID: aaguid,
AttestationCertificateKeyIdentifiers: j.AttestationCertificateKeyIdentifiers,
MetadataStatement: statement,
BiometricStatusReports: bsrs,
StatusReports: srs,
TimeOfLastStatusChange: change,
RogueListURL: rogues,
RogueListHash: j.RogueListHash,
}, nil
}
// Statement is a structure representing the Metadata Statement dictionary. Authenticator metadata statements are used
// directly by the FIDO server at a relying party, but the information contained in the authoritative statement is used
// in several other places.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-md-keys
type Statement struct {
// The LegalHeader, if present, contains a legal guide for accessing and using metadata, which itself MAY contain
// URL(s) pointing to further information, such as a full Terms and Conditions statement.
LegalHeader string
// Aaid is the Authenticator Attestation ID.
Aaid string
// AaGUID is the Authenticator Attestation GUID.
AaGUID uuid.UUID
// AttestationCertificateKeyIdentifiers is a list of the attestation certificate public key identifiers encoded as
// hex string.
AttestationCertificateKeyIdentifiers []string
// FriendlyNames contains friendly names (i.e., public trade name) of the authenticator in multiple languages.
FriendlyNames map[string]string
// Description is a human-readable, short description of the authenticator, in English.
Description string
// AlternativeDescriptions is a list of human-readable short descriptions of the authenticator in different
// languages.
AlternativeDescriptions map[string]string
// AuthenticatorVersion is the earliest (i.e. lowest) trustworthy authenticatorVersion meeting the requirements
// specified in this metadata statement.
AuthenticatorVersion uint32
// ProtocolFamily is the FIDO protocol family. The values "uaf", "u2f", and "fido2" are supported.
ProtocolFamily string
// Schema is the Metadata Schema version.
Schema uint16
// Upv is the FIDO unified protocol version(s) (related to the specific protocol family) supported by this
// authenticator.
Upv []Version
// AuthenticationAlgorithms is the list of authentication algorithms supported by the authenticator.
AuthenticationAlgorithms []AuthenticationAlgorithm
// PublicKeyAlgAndEncodings is the list of public key formats supported by the authenticator during registration
// operations.
PublicKeyAlgAndEncodings []PublicKeyAlgAndEncoding
// AttestationTypes is the supported attestation type(s).
AttestationTypes AuthenticatorAttestationTypes
// UserVerificationDetails is a list of alternative VerificationMethodANDCombinations.
UserVerificationDetails [][]VerificationMethodDescriptor
// KeyProtection is a 16-bit number representing the bit fields defined by the KEY_PROTECTION constants in the FIDO
// Registry of Predefined Values.
KeyProtection []string
// IsKeyRestricted is set to true or it is omitted, if the Uauth private key is restricted by the authenticator to
// only sign valid FIDO signature assertions. This entry is set to false, if the authenticator doesn't restrict the
// Uauth key to only sign valid FIDO signature assertions.
IsKeyRestricted bool
// IsFreshUserVerificationRequired is set to true or it is omitted, if Uauth key usage always requires a fresh user
// verification. This entry is set to false, if the Uauth key can be used without requiring a fresh user
// verification, i.e. without any additional user interaction, if the user was verified a (potentially configurable)
// caching time ago.
IsFreshUserVerificationRequired bool
// MatcherProtection is a 16-bit number representing the bit fields defined by the MATCHER_PROTECTION constants in
// the FIDO Registry of Predefined Values.
MatcherProtection []string
// CryptoStrength is the authenticator's overall claimed cryptographic strength in bits (sometimes also called
// security strength or security level).
CryptoStrength uint16
// AttachmentHint is a 32-bit number representing the bit fields defined by the ATTACHMENT_HINT constants in the
// FIDO Registry of Predefined Values.
AttachmentHint []string
// TcDisplay is a 16-bit number representing a combination of the bit flags defined by the
// TRANSACTION_CONFIRMATION_DISPLAY constants in the FIDO Registry of Predefined Values.
TcDisplay []string
// TcDisplayContentType is the supported MIME content type [RFC2049] for the transaction confirmation display, such
// as text/plain or image/png.
TcDisplayContentType string
// TcDisplayPNGCharacteristics is a list of alternative [DisplayPNGCharacteristicsDescriptor]. Each of these entries
// is one alternative of supported image characteristics for displaying a PNG image.
TcDisplayPNGCharacteristics []DisplayPNGCharacteristicsDescriptor
// AttestationRootCertificates is a list of root certificates. Each element of this array represents a PKIX
// [RFC5280] X.509 certificate that is a valid trust anchor for this authenticator model.
// Multiple certificates might be used for different batches of the same model.
// The array does not represent a certificate chain, but only the trust anchor of that chain.
// A trust anchor can be a root certificate, an intermediate CA certificate, or even the attestation certificate
// itself.
AttestationRootCertificates []*x509.Certificate
// EcdaaTrustAnchors is a list of trust anchors used for ECDAA attestation. This entry MUST be present if and only
// if attestationType includes ATTESTATION_ECDAA.
EcdaaTrustAnchors []EcdaaTrustAnchor
// Icon is a 'data:' url [RFC2397] encoded [PNG] or [SVG11] (light mode) icon for the Authenticator (i.e., depicting
// the security key). This icon is intended to be shown to users by RPs. Use of [SVG11] format is mandatory if any
// of the iconDark, providerLogoLight and/or providerLogoDark is used in addition to icon. Use of [SVG11] is
// recommended if only icon is used. The icon is more specific than the provider logo and should be shown if
// present.
Icon *url.URL
// IconDark is a 'data:' url [RFC2397] encoded [SVG11] dark mode icon for the Authenticator (i.e., depicting the
// security key). This icon is intended to be shown to users by RPs. The icon is more specific than the provider
// logo and should be shown if present.
IconDark *url.URL
// ProviderLogoLight is a 'data:' url [RFC2397] encoded [SVG11] light mode icon for the provider (i.e., logomark of
// the passkey provider). The SVG MUST meet all of the requirements defined in § 4.1 SVG requirements. This icon
// is intended to be shown to users by RPs.
ProviderLogoLight *url.URL
// ProviderLogoDark is a 'data:' url [RFC2397] encoded [SVG11] dark mode icon for the provider (i.e., logomark of
// the passkey provider). The SVG MUST meet all of the requirements defined in § 4.1 SVG requirements. This icon
// is intended to be shown to users by RPs.
ProviderLogoDark *url.URL
// SupportedExtensions is a list of extensions supported by the authenticator.
SupportedExtensions []ExtensionDescriptor
// KeyScope of keys generated and maintained by this authenticator model.
KeyScope KeyScope
// MultiDeviceCredentialSupport describes the support for multi-device credentials.
MultiDeviceCredentialSupport MultiDeviceCredentialSupport
// AuthenticatorGetInfo describes supported versions, extensions, AAGUID of the device and its capabilities.
AuthenticatorGetInfo AuthenticatorGetInfo
// CredentialExportProtocolConfigURL specifies the URL for retrieving the configuration details for the credential
// export protocol (CXP).
CredentialExportProtocolConfigURL *url.URL
}
func (s *Statement) Verifier(x5cis []*x509.Certificate) (opts x509.VerifyOptions) {
roots := x509.NewCertPool()
for _, root := range s.AttestationRootCertificates {
roots.AddCert(root)
}
var intermediates *x509.CertPool
if len(x5cis) > 0 {
intermediates = x509.NewCertPool()
for _, x5c := range x5cis {
intermediates.AddCert(x5c)
}
}
return x509.VerifyOptions{
Roots: roots,
Intermediates: intermediates,
}
}
// StatementJSON is the JSON representation of the [Statement] struct.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-md-keys
type StatementJSON struct {
// LegalHeader contains a legal guide for accessing and using metadata.
LegalHeader string `json:"legalHeader"`
// Aaid is the Authenticator Attestation ID. Set if the authenticator implements FIDO UAF.
Aaid string `json:"aaid"`
// AaGUID is the Authenticator Attestation GUID. Set if the authenticator implements FIDO2.
AaGUID string `json:"aaguid"`
// AttestationCertificateKeyIdentifiers is a list of attestation certificate public key identifiers (hex).
AttestationCertificateKeyIdentifiers []string `json:"attestationCertificateKeyIdentifiers"`
// FriendlyNames contains friendly names of the authenticator in multiple languages.
FriendlyNames map[string]string `json:"friendlyNames"`
// Description is a human-readable, short description of the authenticator, in English.
Description string `json:"description"`
// AlternativeDescriptions is a list of human-readable short descriptions in different languages.
AlternativeDescriptions map[string]string `json:"alternativeDescriptions"`
// AuthenticatorVersion is the earliest trustworthy authenticatorVersion meeting the requirements in this statement.
AuthenticatorVersion uint32 `json:"authenticatorVersion"`
// ProtocolFamily is the FIDO protocol family. The values "uaf", "u2f", and "fido2" are supported.
ProtocolFamily string `json:"protocolFamily"`
// Schema is the Metadata Schema version.
Schema uint16 `json:"schema"`
// Upv is the FIDO unified protocol version(s) supported by this authenticator.
Upv []Version `json:"upv"`
// AuthenticationAlgorithms is the list of authentication algorithms supported by the authenticator.
AuthenticationAlgorithms []AuthenticationAlgorithm `json:"authenticationAlgorithms"`
// PublicKeyAlgAndEncodings is the list of public key formats supported during registration operations.
PublicKeyAlgAndEncodings []PublicKeyAlgAndEncoding `json:"publicKeyAlgAndEncodings"`
// AttestationTypes is the supported attestation type(s).
AttestationTypes []AuthenticatorAttestationType `json:"attestationTypes"`
// UserVerificationDetails is a list of alternative VerificationMethodANDCombinations.
UserVerificationDetails [][]VerificationMethodDescriptor `json:"userVerificationDetails"`
// KeyProtection is the key protection type(s).
KeyProtection []string `json:"keyProtection"`
// IsKeyRestricted indicates if the Uauth private key is restricted to only sign valid FIDO signature assertions.
IsKeyRestricted bool `json:"isKeyRestricted"`
// IsFreshUserVerificationRequired indicates if Uauth key usage always requires a fresh user verification.
IsFreshUserVerificationRequired bool `json:"isFreshUserVerificationRequired"`
// MatcherProtection is the matcher protection type(s).
MatcherProtection []string `json:"matcherProtection"`
// CryptoStrength is the authenticator's overall claimed cryptographic strength in bits.
CryptoStrength uint16 `json:"cryptoStrength"`
// AttachmentHint is the attachment hint(s).
AttachmentHint []string `json:"attachmentHint"`
// TcDisplay is the transaction confirmation display type(s).
TcDisplay []string `json:"tcDisplay"`
// TcDisplayContentType is the supported MIME content type for the transaction confirmation display.
TcDisplayContentType string `json:"tcDisplayContentType"`
// TcDisplayPNGCharacteristics is a list of alternative DisplayPNGCharacteristicsDescriptor.
TcDisplayPNGCharacteristics []DisplayPNGCharacteristicsDescriptor `json:"tcDisplayPNGCharacteristics"`
// AttestationRootCertificates is a list of base64-encoded trust anchor certificates for this authenticator model.
AttestationRootCertificates []string `json:"attestationRootCertificates"`
// EcdaaTrustAnchors is a list of trust anchors used for ECDAA attestation.
EcdaaTrustAnchors []EcdaaTrustAnchor `json:"ecdaaTrustAnchors"`
// Icon is a data: URL encoded PNG or SVG (light mode) icon for the Authenticator.
Icon string `json:"icon"`
// IconDark is a data: URL encoded SVG dark mode icon for the Authenticator.
IconDark string `json:"iconDark"`
// ProviderLogoLight is a data: URL encoded SVG light mode icon for the provider.
ProviderLogoLight string `json:"providerLogoLight"`
// ProviderLogoDark is a data: URL encoded SVG dark mode icon for the provider.
ProviderLogoDark string `json:"providerLogoDark"`
// SupportedExtensions is a list of extensions supported by the authenticator.
SupportedExtensions []ExtensionDescriptor `json:"supportedExtensions"`
// KeyScope of keys generated and maintained by this authenticator model.
KeyScope KeyScope `json:"keyScope"`
// MultiDeviceCredentialSupport describes the support for multi-device credentials.
MultiDeviceCredentialSupport MultiDeviceCredentialSupport `json:"multiDeviceCredentialSupport"`
// AuthenticatorGetInfo describes supported versions, extensions, AAGUID of the device and its capabilities.
AuthenticatorGetInfo AuthenticatorGetInfoJSON `json:"authenticatorGetInfo"`
// CredentialExportProtocolConfigURL specifies the URL for the credential export protocol (CXP) configuration.
CredentialExportProtocolConfigURL string `json:"cxpConfigURL"`
}
// Parse converts StatementJSON into a [Statement] object, validating and parsing its fields. Returns an error on failure.
//
//nolint:gocyclo
func (j StatementJSON) Parse() (statement Statement, err error) {
var aaguid uuid.UUID
if len(j.AaGUID) != 0 {
if aaguid, err = uuid.Parse(j.AaGUID); err != nil {
return statement, fmt.Errorf("error occurred parsing statement with description '%s': error occurred parsing AAGUID value: %w", j.Description, err)
}
}
n := len(j.AttestationRootCertificates)
certificates := make([]*x509.Certificate, n)
for i := 0; i < n; i++ {
if certificates[i], err = mdsParseX509Certificate(j.AttestationRootCertificates[i]); err != nil {
return statement, fmt.Errorf("error occurred parsing statement with description '%s': error occurred parsing attestation root certificate %d value: %w", j.Description, i, err)
}
}
var (
icon, iconDark *url.URL
logoLight, logoDark *url.URL
cxpConfigURL *url.URL
)
if len(j.Icon) != 0 {
if icon, err = url.ParseRequestURI(j.Icon); err != nil {
return statement, fmt.Errorf("error occurred parsing statement with description '%s': error occurred parsing icon value: %w", j.Description, err)
}
}
if len(j.IconDark) != 0 {
if iconDark, err = url.ParseRequestURI(j.IconDark); err != nil {
return statement, fmt.Errorf("error occurred parsing statement with description '%s': error occurred parsing icon dark value: %w", j.Description, err)
}
}
if len(j.ProviderLogoLight) != 0 {
if logoLight, err = url.ParseRequestURI(j.ProviderLogoLight); err != nil {
return statement, fmt.Errorf("error occurred parsing statement with description '%s': error occurred parsing provider logo light value: %w", j.Description, err)
}
}
if len(j.ProviderLogoDark) != 0 {
if logoDark, err = url.ParseRequestURI(j.ProviderLogoDark); err != nil {
return statement, fmt.Errorf("error occurred parsing statement with description '%s': error occurred parsing provider logo dark value: %w", j.Description, err)
}
}
if len(j.CredentialExportProtocolConfigURL) != 0 {
if cxpConfigURL, err = url.ParseRequestURI(j.CredentialExportProtocolConfigURL); err != nil {
return statement, fmt.Errorf("error occurred parsing statement with description '%s': error occurred parsing cxp config url value: %w", j.Description, err)
}
}
var info AuthenticatorGetInfo
if info, err = j.AuthenticatorGetInfo.Parse(); err != nil {
return statement, fmt.Errorf("error occurred parsing statement with description '%s': error occurred parsing authenticator get info value: %w", j.Description, err)
}
return Statement{
LegalHeader: j.LegalHeader,
Aaid: j.Aaid,
AaGUID: aaguid,
AttestationCertificateKeyIdentifiers: j.AttestationCertificateKeyIdentifiers,
FriendlyNames: j.FriendlyNames,
Description: j.Description,
AlternativeDescriptions: j.AlternativeDescriptions,
AuthenticatorVersion: j.AuthenticatorVersion,
ProtocolFamily: j.ProtocolFamily,
Schema: j.Schema,
Upv: j.Upv,
AuthenticationAlgorithms: j.AuthenticationAlgorithms,
PublicKeyAlgAndEncodings: j.PublicKeyAlgAndEncodings,
AttestationTypes: j.AttestationTypes,
UserVerificationDetails: j.UserVerificationDetails,
KeyProtection: j.KeyProtection,
IsKeyRestricted: j.IsKeyRestricted,
IsFreshUserVerificationRequired: j.IsFreshUserVerificationRequired,
MatcherProtection: j.MatcherProtection,
CryptoStrength: j.CryptoStrength,
AttachmentHint: j.AttachmentHint,
TcDisplay: j.TcDisplay,
TcDisplayContentType: j.TcDisplayContentType,
TcDisplayPNGCharacteristics: j.TcDisplayPNGCharacteristics,
AttestationRootCertificates: certificates,
EcdaaTrustAnchors: j.EcdaaTrustAnchors,
Icon: icon,
IconDark: iconDark,
ProviderLogoLight: logoLight,
ProviderLogoDark: logoDark,
SupportedExtensions: j.SupportedExtensions,
KeyScope: j.KeyScope,
MultiDeviceCredentialSupport: j.MultiDeviceCredentialSupport,
AuthenticatorGetInfo: info,
CredentialExportProtocolConfigURL: cxpConfigURL,
}, nil
}
// BiometricStatusReport is a structure representing the BiometricStatusReport dictionary. Contains the current
// BiometricStatusReport of one of the authenticator's biometric component.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-bio-stat-rep
type BiometricStatusReport struct {
// CertLevel is the achieved level of the biometric certification of this biometric component of the authenticator.
CertLevel uint16
// Modality is a single USER_VERIFY short form case-sensitive string name constant, representing biometric modality.
Modality string
// EffectiveDate is an ISO-8601 formatted date since when the certLevel achieved, if applicable. If no date is
// given, the status is assumed to be effective while present.
EffectiveDate time.Time
// CertificationDescriptor describes the externally visible aspects of the Biometric Certification evaluation.
CertificationDescriptor string
// CertificateNumber is the unique identifier for the issued Biometric Certification.
CertificateNumber string
// CertificationPolicyVersion is the version of the Biometric Certification Policy the implementation is Certified
// to, i.e. "1.0.0".
CertificationPolicyVersion string
// CertificationRequirementsVersion is the version of the Biometric Requirements [FIDOBiometricsRequirements] the
// implementation is certified to, i.e. "1.0.0".
CertificationRequirementsVersion string
}
// BiometricStatusReportJSON is the JSON representation of the [BiometricStatusReport] struct.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-bio-stat-rep
type BiometricStatusReportJSON struct {
// CertLevel is the achieved level of the biometric certification of this biometric component.
CertLevel uint16 `json:"certLevel"`
// Modality is a single USER_VERIFY short form string constant representing the biometric modality.
Modality string `json:"modality"`
// EffectiveDate is an ISO-8601 formatted date since when the certLevel was achieved.
EffectiveDate string `json:"effectiveDate"`
// CertificationDescriptor describes the externally visible aspects of the Biometric Certification evaluation.
CertificationDescriptor string `json:"certificationDescriptor"`
// CertificateNumber is the unique identifier for the issued Biometric Certification.
CertificateNumber string `json:"certificateNumber"`
// CertificationPolicyVersion is the version of the Biometric Certification Policy, i.e. "1.0.0".
CertificationPolicyVersion string `json:"certificationPolicyVersion"`
// CertificationRequirementsVersion is the version of the Biometric Requirements, i.e. "1.0.0".
CertificationRequirementsVersion string `json:"certificationRequirementsVersion"`
}
func (j BiometricStatusReportJSON) Parse() (report BiometricStatusReport, err error) {
var effective time.Time
if effective, err = time.Parse(time.DateOnly, j.EffectiveDate); err != nil {
return report, fmt.Errorf("error occurred parsing effective date value: %w", err)
}
return BiometricStatusReport{
CertLevel: j.CertLevel,
Modality: j.Modality,
EffectiveDate: effective,
CertificationDescriptor: j.CertificationDescriptor,
CertificateNumber: j.CertificateNumber,
CertificationPolicyVersion: j.CertificationPolicyVersion,
CertificationRequirementsVersion: j.CertificationRequirementsVersion,
}, nil
}
// StatusReport is a structure representing the StatusReport dictionary. Contains an [AuthenticatorStatus] and additional
// data associated with it, if any.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-stat-rep
type StatusReport struct {
// Status of the authenticator. Additional fields MAY be set depending on this value.
Status AuthenticatorStatus
// EffectiveDate is an ISO-8601 formatted date since when the status code was set, if applicable. If no date is
// given, the status is assumed to be effective while present.
EffectiveDate time.Time
// AuthenticatorVersion is the authenticator version (firmware version) that this status report relates to. In the
// case of FIDO_CERTIFIED* status values, the status applies to higher authenticatorVersions until there is a new
// statusReport.
AuthenticatorVersion uint32
// BatchCertificate is a Base64-encoded [RFC4648] (not base64url!) DER [ITU-X690-2008] PKIX certificate value
// related to the current status, if applicable.
BatchCertificate *x509.Certificate
// Certificate is a Base64-encoded [RFC4648] (not base64url!) DER [ITU-X690-2008] PKIX certificate value related to
// the current status, if applicable. This field will typically not be present if field batchCertificate is present.
Certificate *x509.Certificate
// URL is a HTTPS URL where additional information may be found related to the current status, if applicable.
URL *url.URL
// CertificationDescriptor describes the externally visible aspects of the Authenticator Certification evaluation.
CertificationDescriptor string
// CertificateNumber is the unique identifier for the issued Certification.
CertificateNumber string
// CertificationPolicyVersion is the version of the Authenticator Certification Policy the implementation is
// Certified to, i.e. "1.0.0".
CertificationPolicyVersion string
// CertificationProfiles is a list of certification profile strings. Each entry represents a supported
// certification profile, i.e. "consumer" or "enterprise".
CertificationProfiles []string
// CertificationRequirementsVersion is the Document Version of the Authenticator Security Requirements (DV)
// [FIDOAuthenticatorSecurityRequirements] the implementation is certified to, i.e. "1.2.0".
CertificationRequirementsVersion string
// SunsetDate is an ISO-8601 formatted date since when the status will expire, if applicable. If no date is given,
// the status is assumed to not have a scheduled expiry.
SunsetDate *time.Time
// FIPSRevision is the revision number of the FIPS 140 specification, i.e. "3" in the case of FIPS 140-3. This
// entry MUST be present if and only if the status entry is one of FIPS140_CERTIFIED_L*.
FIPSRevision uint32
// FIPSPhysicalSecurityLevel is the "physical security level" of the FIPS certification. This entry MUST be present
// if and only if the status entry is one of FIPS140_CERTIFIED_L*. It MUST reflect the physical security level
// which might deviate from the overall level.
FIPSPhysicalSecurityLevel uint32
}
// StatusReportJSON is the JSON representation of the [StatusReport] struct.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-stat-rep
type StatusReportJSON struct {
// Status of the authenticator. Additional fields MAY be set depending on this value.
Status AuthenticatorStatus `json:"status"`
// EffectiveDate is an ISO-8601 formatted date since when the status code was set.
EffectiveDate string `json:"effectiveDate"`
// AuthenticatorVersion is the authenticator version (firmware version) that this status report relates to.
AuthenticatorVersion uint32 `json:"authenticatorVersion"`
// BatchCertificate is a Base64-encoded DER PKIX certificate related to the current status.
BatchCertificate string `json:"batchCertificate"`
// Certificate is a Base64-encoded DER PKIX certificate related to the current status.
Certificate string `json:"certificate"`
// URL is a HTTPS URL where additional information may be found related to the current status.
URL string `json:"url"`
// CertificationDescriptor describes the externally visible aspects of the Authenticator Certification evaluation.
CertificationDescriptor string `json:"certificationDescriptor"`
// CertificateNumber is the unique identifier for the issued Certification.
CertificateNumber string `json:"certificateNumber"`
// CertificationPolicyVersion is the version of the Authenticator Certification Policy, i.e. "1.0.0".
CertificationPolicyVersion string `json:"certificationPolicyVersion"`
// CertificationProfiles is a list of supported certification profiles, i.e. "consumer" or "enterprise".
CertificationProfiles []string `json:"certificationProfiles"`
// CertificationRequirementsVersion is the Document Version of the Authenticator Security Requirements, i.e. "1.2.0".
CertificationRequirementsVersion string `json:"certificationRequirementsVersion"`
// SunsetDate is an ISO-8601 formatted date when the status will expire.
SunsetDate string `json:"sunsetDate"`
// FIPSRevision is the revision number of the FIPS 140 specification, i.e. "3" for FIPS 140-3.
FIPSRevision uint32 `json:"fipsRevision"`
// FIPSPhysicalSecurityLevel is the physical security level of the FIPS certification.
FIPSPhysicalSecurityLevel uint32 `json:"fipsPhysicalSecurityLevel"`
}
func (j StatusReportJSON) Parse() (report StatusReport, err error) {
var (
certificate, batchCertificate *x509.Certificate
)
if len(j.Certificate) != 0 {
if certificate, err = mdsParseX509Certificate(j.Certificate); err != nil {
return report, fmt.Errorf("error occurred parsing certificate value: %w", err)
}
}
if len(j.BatchCertificate) != 0 {
if batchCertificate, err = mdsParseX509Certificate(j.BatchCertificate); err != nil {
return report, fmt.Errorf("error occurred parsing batch certificate value: %w", err)
}
}
var (
effective time.Time
sunset *time.Time
)
if effective, err = time.Parse(time.DateOnly, j.EffectiveDate); err != nil {
return report, fmt.Errorf("error occurred parsing effective date value: %w", err)
}
if sunset, err = mdsParseTimePointer(time.DateOnly, j.SunsetDate); err != nil {
return report, fmt.Errorf("error occurred parsing sunset date value: %w", err)
}
var uri *url.URL
if len(j.URL) != 0 {
if uri, err = url.ParseRequestURI(j.URL); err != nil {
if !strings.HasPrefix(j.URL, "http") {
var e error
if uri, e = url.ParseRequestURI(fmt.Sprintf("https://%s", j.URL)); e != nil {
return report, fmt.Errorf("error occurred parsing URL value: %w", err)
}
}
}
}
return StatusReport{
Status: j.Status,
EffectiveDate: effective,
AuthenticatorVersion: j.AuthenticatorVersion,
BatchCertificate: batchCertificate,
Certificate: certificate,
URL: uri,
CertificationDescriptor: j.CertificationDescriptor,
CertificateNumber: j.CertificateNumber,
CertificationPolicyVersion: j.CertificationPolicyVersion,
CertificationProfiles: j.CertificationProfiles,
CertificationRequirementsVersion: j.CertificationRequirementsVersion,
SunsetDate: sunset,
FIPSRevision: j.FIPSRevision,
FIPSPhysicalSecurityLevel: j.FIPSPhysicalSecurityLevel,
}, nil
}
// RogueListEntry is a structure representing the RogueListEntry dictionary.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-rogue-list-entry
type RogueListEntry struct {
// Sk is the base64url encoding of the rogue authenticator's secret key.
Sk string `json:"sk"`
// Data is the ISO-8601 formatted date since when this entry is effective.
Date string `json:"date"`
}
// CodeAccuracyDescriptor is a structure representing the CodeAccuracyDescriptor dictionary.
// It describes the relevant accuracy/complexity aspects of passcode user verification methods.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-cad
type CodeAccuracyDescriptor struct {
// Base is the numeric system base (radix) of the code, i.e. 10 in the case of decimal digits.
Base uint16 `json:"base"`
// MinLength is the minimum number of digits of the given base required for that code, i.e. 4 in the case of 4
// digits.
MinLength uint16 `json:"minLength"`
// MaxRetries is the maximum number of false attempts before the authenticator will block this method (at least for
// some time). 0 means it will never block.
MaxRetries uint16 `json:"maxRetries"`
// BlockSlowdown is the enforced minimum number of seconds wait time after blocking (i.e. due to forced reboot or
// similar). 0 means this user verification method will be blocked, either permanently, or until an alternative user
// verification method method succeeded. All alternative user verification methods MUST be specified appropriately
// in the Metadata in userVerificationDetails.
BlockSlowdown uint16 `json:"blockSlowdown"`
}
// BiometricAccuracyDescriptor is a structure representing the BiometricAccuracyDescriptor dictionary.
// It describes relevant accuracy/complexity aspects in the case of a biometric user verification method.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-bad
type BiometricAccuracyDescriptor struct {
// SelfAttestedFRR is the false rejection rate [ISO19795-1] for a single template, i.e. the percentage of
// verification transactions with truthful claims of identity that are incorrectly denied.
SelfAttestedFRR float64 `json:"selfAttestedFRR"`
// SelfAttestedFAR is the false acceptance rate [ISO19795-1] for a single template, i.e. the percentage of
// verification transactions with wrongful claims of identity that are incorrectly confirmed.
SelfAttestedFAR float64 `json:"selfAttestedFAR"`
// ImposterAttackPresentationAcceptRateThreshold is the threshold for Impostor Attack Presentation Accept Rate
// (IAPAR) is the proportion of impostor attack presentations using the same presentation attack instrument (PAI)
// species that result in accept [isoiec-30107-3]. For biometric certification requirements
// [FIDOBiometricsRequirements], certification can be achieved for an IAPAR threshold of less than 7% OR less than
// 15% for each of the PAI species tested.
ImposterAttackPresentationAcceptRateThreshold float64 `json:"iAPARThreshold"`
// MaxTemplates is the maximum number of alternative templates from different fingers allowed.
MaxTemplates uint16 `json:"maxTemplates"`
// MaxRetries is the maximum number of false attempts before the authenticator will block this method (at least for
// some time). 0 means it will never block.
MaxRetries uint16 `json:"maxRetries"`
// BlockSlowdown is the enforced minimum number of seconds wait time after blocking (i.e. due to forced reboot or
// similar).0 means that this user verification method will be blocked either permanently or until an alternative
// user verification method succeeded. All alternative user verification methods MUST be specified appropriately in
// the metadata in userVerificationDetails.
BlockSlowdown uint16 `json:"blockSlowdown"`
}
// PatternAccuracyDescriptor is a structure representing the PatternAccuracyDescriptor dictionary.
// It describes relevant accuracy/complexity aspects in the case that a pattern is used as the user verification method.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-pad
type PatternAccuracyDescriptor struct {
// MinComplexity is the number of possible patterns (having the minimum length) out of which exactly one would be
// the right one, i.e. 1/probability in the case of equal distribution.
MinComplexity uint32 `json:"minComplexity"`
// MaxRetries is the maximum number of false attempts before the authenticator will block authentication using this
// method (at least temporarily). 0 means it will never block.
MaxRetries uint16 `json:"maxRetries"`
// BlockSlowdown is the enforced minimum number of seconds wait time after blocking (due to forced reboot or similar
// mechanism). 0 means this user verification method will be blocked, either permanently, or until an alternative
// user verification method method succeeded. All alternative user verification methods MUST be specified
// appropriately in the metadata under userVerificationDetails.
BlockSlowdown uint16 `json:"blockSlowdown"`
}
// VerificationMethodDescriptor is a structure representing the VerificationMethodDescriptor dictionary.
// It describes a descriptor for a specific base user verification method as implemented by the authenticator.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-vmd
type VerificationMethodDescriptor struct {
// UserVerificationMethod is a single USER_VERIFY constant (see [FIDORegistry]), not a bit flag combination. This
// value MUST be non-zero.
UserVerificationMethod string `json:"userVerificationMethod"`
// CaDesc nay optionally be used in the case of method USER_VERIFY_PASSCODE.
CaDesc CodeAccuracyDescriptor `json:"caDesc"`
// BaDesc may optionally be used in the case of method USER_VERIFY_FINGERPRINT, USER_VERIFY_VOICEPRINT,
// USER_VERIFY_FACEPRINT, USER_VERIFY_EYEPRINT, or USER_VERIFY_HANDPRINT.
BaDesc BiometricAccuracyDescriptor `json:"baDesc"`
// PaDesc may optionally be used in case of method USER_VERIFY_PATTERN.
PaDesc PatternAccuracyDescriptor `json:"paDesc"`
}
// RGBPaletteEntry is a structure representing the RGBPaletteEntry dictionary.
// It describes an RGB three-sample tuple palette entry.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-rgbpe
type RGBPaletteEntry struct {
// R is the red channel sample value.
R uint16 `json:"r"`
// G is the green channel sample value.
G uint16 `json:"g"`
// B is the blue channel sample value.
B uint16 `json:"b"`
}
// DisplayPNGCharacteristicsDescriptor is a structure representing the DisplayPNGCharacteristicsDescriptor MDS3.1
// dictionary. It describes a PNG image characteristics as defined in the PNG [PNG] spec for IHDR (image header) and
// PLTE (palette table).
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-dpngcd
type DisplayPNGCharacteristicsDescriptor struct {
// Width of the image.
Width uint32 `json:"width"`
// Height of the image.
Height uint32 `json:"height"`
// BitDepth is bits per sample or per palette index.
BitDepth byte `json:"bitDepth"`
// ColorType defines the PNG image type.
ColorType byte `json:"colorType"`
// Compression method used to compress the image data.
Compression byte `json:"compression"`
// Filter method is the preprocessing method applied to the image data before compression.
Filter byte `json:"filter"`
// Interlace method is the transmission order of the image data.
Interlace byte `json:"interlace"`
// Plte is a number 1 to 256 representing palette entries.
Plte []RGBPaletteEntry `json:"plte"`
}
// EcdaaTrustAnchor is a structure representing the EcdaaTrustAnchor dictionary.
// In the case of ECDAA attestation, the ECDAA-Issuer's trust anchor MUST be specified in this field.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-ecdaata
type EcdaaTrustAnchor struct {
// X is the base64url encoding of the result of ECPoint2ToB of the ECPoint2 X.
X string `json:"X"`
// Y is the base64url encoding of the result of ECPoint2ToB of the ECPoint2 Y.
Y string `json:"Y"`
// C is the base64url encoding of the result of BigNumberToB(c).
C string `json:"c"`
// SX is the base64url encoding of the result of BigNumberToB(sx).
SX string `json:"sx"`
// SY is the base64url encoding of the result of BigNumberToB(sy).
SY string `json:"sy"`
// G1Curve is the name of the Barreto-Naehrig elliptic curve for G1. "BN_P256", "BN_P638", "BN_ISOP256", and
// "BN_ISOP512" are supported.
G1Curve string `json:"G1Curve"`
}
// ExtensionDescriptor is a structure representing the ExtensionDescriptor dictionary.
// This descriptor contains an extension supported by the authenticator.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-ed
type ExtensionDescriptor struct {
// ID identifies the extension.
ID string `json:"id"`
// Tag of the extension if this was assigned. TAGs are assigned to extensions if they could appear in an assertion.
Tag uint16 `json:"tag"`
// Data contains arbitrary data further describing the extension and/or data needed to correctly process the
// extension.
Data string `json:"data"`
// FailIfUnknown indicates whether unknown extensions must be ignored (false) or must lead to an error (true) when
// the extension is to be processed by the FIDO Server, FIDO Client, ASM, or FIDO Authenticator.
FailIfUnknown bool `json:"fail_if_unknown"`
}
// Version is a structure representing the Version FIDO UAF Protocol 1.2 dictionary and represents a generic version
// with major and minor fields.
//
// See: https://fidoalliance.org/specs/fido-uaf-v1.2-ps-20201020/fido-uaf-protocol-v1.2-ps-20201020.html#version-interface
type Version struct {
// Major version.
Major uint16 `json:"major"`
// Minor version.
Minor uint16 `json:"minor"`
}
// AuthenticatorGetInfo is a structure representing the AuthenticatorGetInfo dictionary.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-agid
type AuthenticatorGetInfo struct {
// Versions is a list of supported versions.
Versions []string
// Extensions is a list of supported extensions.
Extensions []string
// AaGUID is the claimed AAGUID.
AaGUID uuid.UUID
// Options is a list of supported options.
Options map[string]bool
// MaxMsgSize is the maximum message size supported by the authenticator.
MaxMsgSize uint
// PivUvAuthProtocols is a list of supported PIN/UV auth protocols in order of decreasing authenticator preference.
PivUvAuthProtocols []uint
// MaxCredentialCountInList is the maximum number of credentials supported in credentialID list at a time by the
// authenticator.
MaxCredentialCountInList uint
// MaxCredentialIdLength is the maximum Credential ID Length supported by the authenticator.
MaxCredentialIdLength uint
// Transports is the list of supported transports.
Transports []string
// Algorithms is the list of supported algorithms for credential generation, as specified in WebAuthn.
Algorithms []PublicKeyCredentialParameters
// MaxSerializedLargeBlobArray is the maximum size, in bytes, of the serialized large-blob array that this
// authenticator can store.
MaxSerializedLargeBlobArray uint
// ForcePINChange indicates if the PIN must be changed.
ForcePINChange bool
// MinPINLength specifies the current minimum PIN length, in Unicode code points, the authenticator enforces for ClientPIN.
MinPINLength uint
// FirmwareVersion indicates the firmware version of the authenticator model identified by AAGUID.
FirmwareVersion uint
// MaxCredBlobLength indicates the maximum credential blob length in bytes supported by the authenticator.
MaxCredBlobLength uint
// MaxRPIDsForSetMinPINLength specifies the max number of RP IDs that authenticator can set via setMinPINLength
// subcommand.
MaxRPIDsForSetMinPINLength uint
// PreferredPlatformUvAttempts specifies the preferred number of invocations of the
// getPinUvAuthTokenUsingUvWithPermissions subCommand the platform may attempt before falling back to the
// getPinUvAuthTokenUsingPinWithPermissions subCommand or displaying an error.
PreferredPlatformUvAttempts uint
// UvModality specifies the user verification modality supported by the authenticator via authenticatorClientPIN's
// getPinUvAuthTokenUsingUvWithPermissions subcommand.
UvModality uint
// Certifications specifies a list of authenticator certifications.
Certifications map[string]float64
// RemainingDiscoverableCredentials if present indicates the estimated number of additional discoverable credentials
// that can be stored.
RemainingDiscoverableCredentials uint
// VendorPrototypeConfigCommands if present the authenticator supports the authenticatorConfig vendorPrototype
// subcommand, and its value is a list of authenticatorConfig vendorCommandId values supported, which MAY be empty.
VendorPrototypeConfigCommands []uint
}
// AuthenticatorGetInfoJSON is the JSON representation of the [AuthenticatorGetInfo] struct. The members mirror the
// fields returned by the CTAP authenticatorGetInfo command.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-type-agid
type AuthenticatorGetInfoJSON struct {
// Versions is a list of supported CTAP versions.
Versions []string `json:"versions"`
// Extensions is a list of supported extensions.
Extensions []string `json:"extensions"`
// AaGUID is the claimed AAGUID.
AaGUID string `json:"aaguid"`
// Options is a map of supported options.
Options map[string]bool `json:"options"`
// MaxMsgSize is the maximum message size supported by the authenticator.
MaxMsgSize uint `json:"maxMsgSize"`
// PivUvAuthProtocols is a list of supported PIN/UV auth protocols in order of decreasing authenticator preference.
PivUvAuthProtocols []uint `json:"pinUvAuthProtocols"`
// MaxCredentialCountInList is the maximum number of credentials supported in credentialID list at a time.
MaxCredentialCountInList uint `json:"maxCredentialCountInList"`
// MaxCredentialIdLength is the maximum Credential ID Length supported by the authenticator.
MaxCredentialIdLength uint `json:"maxCredentialIdLength"`
// Transports is the list of supported transports.
Transports []string `json:"transports"`
// Algorithms is the list of supported algorithms for credential generation.
Algorithms []PublicKeyCredentialParameters `json:"algorithms"`
// MaxSerializedLargeBlobArray is the maximum size, in bytes, of the serialized large-blob array.
MaxSerializedLargeBlobArray uint `json:"maxSerializedLargeBlobArray"`
// ForcePINChange indicates if the PIN must be changed.
ForcePINChange bool `json:"forcePINChange"`
// MinPINLength specifies the current minimum PIN length, in Unicode code points.
MinPINLength uint `json:"minPINLength"`
// FirmwareVersion indicates the firmware version of the authenticator model identified by AAGUID.
FirmwareVersion uint `json:"firmwareVersion"`
// MaxCredBlobLength indicates the maximum credential blob length in bytes.
MaxCredBlobLength uint `json:"maxCredBlobLength"`
// MaxRPIDsForSetMinPINLength specifies the max number of RP IDs that can be set via setMinPINLength subcommand.
MaxRPIDsForSetMinPINLength uint `json:"maxRPIDsForSetMinPINLength"`
// PreferredPlatformUvAttempts specifies the preferred number of UV attempts before falling back to PIN.
PreferredPlatformUvAttempts uint `json:"preferredPlatformUvAttempts"`
// UvModality specifies the user verification modality supported by the authenticator.
UvModality uint `json:"uvModality"`
// Certifications specifies a map of authenticator certifications.
Certifications map[string]float64 `json:"certifications"`
// RemainingDiscoverableCredentials indicates the estimated number of additional discoverable credentials that
// can be stored.
RemainingDiscoverableCredentials uint `json:"remainingDiscoverableCredentials"`
// VendorPrototypeConfigCommands is a list of supported authenticatorConfig vendorCommandId values.
VendorPrototypeConfigCommands []uint `json:"vendorPrototypeConfigCommands"`
}
func (j AuthenticatorGetInfoJSON) Parse() (info AuthenticatorGetInfo, err error) {
var aaguid uuid.UUID
if len(j.AaGUID) != 0 {
if aaguid, err = uuid.Parse(j.AaGUID); err != nil {
return info, fmt.Errorf("error occurred parsing AAGUID value: %w", err)
}
}
return AuthenticatorGetInfo{
Versions: j.Versions,
Extensions: j.Extensions,
AaGUID: aaguid,
Options: j.Options,
MaxMsgSize: j.MaxMsgSize,
PivUvAuthProtocols: j.PivUvAuthProtocols,
MaxCredentialCountInList: j.MaxCredentialCountInList,
MaxCredentialIdLength: j.MaxCredentialIdLength,
Transports: j.Transports,
Algorithms: j.Algorithms,
MaxSerializedLargeBlobArray: j.MaxSerializedLargeBlobArray,
ForcePINChange: j.ForcePINChange,
MinPINLength: j.MinPINLength,
FirmwareVersion: j.FirmwareVersion,
MaxCredBlobLength: j.MaxCredBlobLength,
MaxRPIDsForSetMinPINLength: j.MaxRPIDsForSetMinPINLength,
PreferredPlatformUvAttempts: j.PreferredPlatformUvAttempts,
UvModality: j.UvModality,
Certifications: j.Certifications,
RemainingDiscoverableCredentials: j.RemainingDiscoverableCredentials,
VendorPrototypeConfigCommands: j.VendorPrototypeConfigCommands,
}, nil
}
// MDSGetEndpointsRequest is the request sent to the conformance metadata getEndpoints endpoint.
type MDSGetEndpointsRequest struct {
// Endpoint is the URL of the local server endpoint, i.e. https://webauthn.io/
Endpoint string `json:"endpoint"`
}
// MDSGetEndpointsResponse is the response received from a conformance metadata getEndpoints request.
type MDSGetEndpointsResponse struct {
// Status is the status of the response.
Status string `json:"status"`
// Result is an array of urls, each pointing to a MetadataTOCPayload.
Result []string `json:"result"`
}
// DefaultUndesiredAuthenticatorStatuses returns a copy of the defaultUndesiredAuthenticatorStatus slice.
func DefaultUndesiredAuthenticatorStatuses() []AuthenticatorStatus {
undesired := make([]AuthenticatorStatus, len(defaultUndesiredAuthenticatorStatus))
copy(undesired, defaultUndesiredAuthenticatorStatus[:])
return undesired
}
// EntryError represents an [EntryJSON] that failed to parse, along with the error that occurred.
type EntryError struct {
// Error is the parsing error that occurred.
Error error
// EntryJSON is the raw JSON entry that failed to parse.
EntryJSON
}
@@ -0,0 +1,16 @@
package metadata
// PasskeyAuthenticator is a type that represents the schema from the Passkey Developer AAGUID listing.
//
// See: https://github.com/passkeydeveloper/passkey-authenticator-aaguids
type PasskeyAuthenticator map[string]PassKeyAuthenticatorAAGUID
// PassKeyAuthenticatorAAGUID is a type that represents the individual schema entry from the Passkey Developer AAGUID
// listing. Used with [PasskeyAuthenticator].
//
// See: https://github.com/passkeydeveloper/passkey-authenticator-aaguids
type PassKeyAuthenticatorAAGUID struct {
Name string `json:"name"`
IconDark string `json:"icon_dark,omitempty"`
IconLight string `json:"icon_light,omitempty"`
}
@@ -0,0 +1,64 @@
package metadata
import (
"fmt"
"strings"
)
// ValidateStatusReports checks a list of [StatusReport] structs against a list of desired and undesired [AuthenticatorStatus]
// values. If the reports contain all of the desired and none of the undesired status reports then no error is returned
// otherwise an error describing the issue is returned.
//
//nolint:gocyclo
func ValidateStatusReports(reports []StatusReport, desired, undesired []AuthenticatorStatus) (err error) {
if len(desired) == 0 && (len(undesired) == 0 || len(reports) == 0) {
return nil
}
var present, absent []string
if len(undesired) != 0 {
for _, report := range reports {
for _, status := range undesired {
if report.Status == status {
present = append(present, string(status))
continue
}
}
}
}
if len(desired) != 0 {
desired:
for _, status := range desired {
for _, report := range reports {
if report.Status == status {
continue desired
}
}
absent = append(absent, string(status))
}
}
switch {
case len(present) == 0 && len(absent) == 0:
return nil
case len(present) != 0 && len(absent) == 0:
return &Error{
Type: "invalid_status",
Details: fmt.Sprintf("The following undesired status reports were present: %s", strings.Join(present, ", ")),
}
case len(present) == 0 && len(absent) != 0:
return &Error{
Type: "invalid_status",
Details: fmt.Sprintf("The following desired status reports were absent: %s", strings.Join(absent, ", ")),
}
default:
return &Error{
Type: "invalid_status",
Details: fmt.Sprintf("The following undesired status reports were present: %s; the following desired status reports were absent: %s", strings.Join(present, ", "), strings.Join(absent, ", ")),
}
}
}
+420
View File
@@ -0,0 +1,420 @@
package metadata
import (
"context"
"errors"
"reflect"
"time"
"github.com/google/uuid"
"gamertan.com/web/internal/webauthnvendored/protocol/webauthncose"
)
// The Provider is an interface which describes the elements required to satisfy validation of metadata.
type Provider interface {
// GetEntry returns a MDS3 payload entry given a AAGUID.
GetEntry(ctx context.Context, aaguid uuid.UUID) (entry *Entry, err error)
// GetValidateEntry returns true if this provider requires an entry to exist with a AAGUID matching the attestation
// statement during registration.
GetValidateEntry(ctx context.Context) (validate bool)
// GetValidateEntryPermitZeroAAGUID returns true if attestation statements with zerod AAGUID should be permitted
// when considering the result from GetValidateEntry. i.e. if the AAGUID is zeroed, and GetValidateEntry returns
// true, and this implementation returns true, the attestation statement will pass validation.
GetValidateEntryPermitZeroAAGUID(ctx context.Context) (skip bool)
// GetValidateTrustAnchor returns true if trust anchor validation of attestation statements is enforced during
// registration.
GetValidateTrustAnchor(ctx context.Context) (validate bool)
// GetValidateStatus returns true if the status reports for an authenticator should be validated against desired and
// undesired statuses.
GetValidateStatus(ctx context.Context) (validate bool)
// GetValidateAttestationTypes if true will enforce checking that the provided attestation is possible with the
// given authenticator.
GetValidateAttestationTypes(ctx context.Context) (validate bool)
// ValidateStatusReports returns nil if the provided authenticator status reports are desired.
ValidateStatusReports(ctx context.Context, reports []StatusReport) (err error)
}
var (
ErrNotInitialized = errors.New("metadata: not initialized")
)
// PublicKeyCredentialParameters describes a credential type and algorithm pair per the WebAuthn specification. It is
// used in [AuthenticatorGetInfo] to describe the algorithms supported by an authenticator.
//
// See: https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialparameters
type PublicKeyCredentialParameters struct {
// Type is the credential type, typically "public-key".
Type string `json:"type"`
// Alg is the COSE algorithm identifier.
Alg webauthncose.COSEAlgorithmIdentifier `json:"alg"`
}
type AuthenticatorAttestationTypes []AuthenticatorAttestationType
func (t AuthenticatorAttestationTypes) HasBasicFull() bool {
for _, a := range t {
if a == BasicFull || a == AttCA {
return true
}
}
return false
}
// AuthenticatorAttestationType represents the attestation type supported by an authenticator. Each constant has a
// case-sensitive string representation used in the authoritative metadata for FIDO authenticators.
//
// See: https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#authenticator-attestation-types
type AuthenticatorAttestationType string
const (
// BasicFull - Indicates full basic attestation, based on an attestation private key shared among a class of authenticators (i.e. same model). Authenticators must provide its attestation signature during the registration process for the same reason. The attestation trust anchor is shared with FIDO Servers out of band (as part of the Metadata). This sharing process should be done according to [UAFMetadataService].
BasicFull AuthenticatorAttestationType = "basic_full"
// BasicSurrogate - Just syntactically a Basic Attestation. The attestation object self-signed, i.e. it is signed using the UAuth.priv key, i.e. the key corresponding to the UAuth.pub key included in the attestation object. As a consequence it does not provide a cryptographic proof of the security characteristics. But it is the best thing we can do if the authenticator is not able to have an attestation private key.
BasicSurrogate AuthenticatorAttestationType = "basic_surrogate"
// Ecdaa - Indicates use of elliptic curve based direct anonymous attestation as defined in [FIDOEcdaaAlgorithm]. Support for this attestation type is optional at this time. It might be required by FIDO Certification.
Ecdaa AuthenticatorAttestationType = "ecdaa"
// AttCA - Indicates PrivacyCA attestation as defined in [TCG-CMCProfile-AIKCertEnroll]. Support for this attestation type is optional at this time. It might be required by FIDO Certification.
AttCA AuthenticatorAttestationType = "attca"
// AnonCA In this case, the authenticator uses an Anonymization CA which dynamically generates per-credential attestation certificates such that the attestation statements presented to Relying Parties do not provide uniquely identifiable information, i.e., that might be used for tracking purposes. The applicable [WebAuthn] attestation formats "fmt" are Google SafetyNet Attestation "android-safetynet", Android Keystore Attestation "android-key", Apple Anonymous Attestation "apple", and Apple Application Attestation "apple-appattest".
AnonCA AuthenticatorAttestationType = "anonca"
// None - Indicates absence of attestation.
None AuthenticatorAttestationType = "none"
)
// KeyScope represents the scope of keys generated and maintained by an authenticator model.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-md-keys
type KeyScope string
const (
// KeyScopeNone is the zero value indicating the field is absent (defaults to PublicKeyCredentialSource).
KeyScopeNone KeyScope = ""
// PublicKeyCredentialSource indicates the authenticator only generates/maintains main FIDO credentials.
PublicKeyCredentialSource KeyScope = "public-key-credential-source" //nolint:gosec
// DeviceSupplementalPublicKeys indicates the authenticator only generates/maintains device-scoped supplemental
// public keys (SPK extension).
DeviceSupplementalPublicKeys KeyScope = "device-spk"
// ProviderSupplementalPublicKeys indicates the authenticator only generates/maintains provider-scoped supplemental
// public keys (SPK extension).
ProviderSupplementalPublicKeys KeyScope = "provider-spk"
)
// MultiDeviceCredentialSupport describes whether an authenticator supports multi-device credentials (passkeys).
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.1-ps-20250521.html#sctn-md-keys
type MultiDeviceCredentialSupport string
const (
// MultiDeviceCredentialUnsupported indicates all private keys are designed to stay within the authenticator
// boundary. This is the implicit default when the field is absent.
MultiDeviceCredentialUnsupported MultiDeviceCredentialSupport = "unsupported"
// MultiDeviceCredentialExplicit indicates the authenticator explicitly marks keys as multi-device or single-device
// via the Backup Eligibility flag.
MultiDeviceCredentialExplicit MultiDeviceCredentialSupport = "explicit"
// MultiDeviceCredentialImplicit indicates all private keys relating to Public Key Credential Source may be backed
// up.
MultiDeviceCredentialImplicit MultiDeviceCredentialSupport = "implicit"
)
// AuthenticatorStatus describes the status of an authenticator model as identified by its AAID/AAGUID and potentially
// some additional information (such as a specific attestation key).
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#sctn-authnr-stat
type AuthenticatorStatus string
const (
// NotFidoCertified - This authenticator is not FIDO certified.
NotFidoCertified AuthenticatorStatus = "NOT_FIDO_CERTIFIED"
// FidoCertified - This authenticator has passed FIDO functional certification. This certification scheme is phased out and will be replaced by FIDO_CERTIFIED_L1.
FidoCertified AuthenticatorStatus = "FIDO_CERTIFIED"
// UserVerificationBypass - Indicates that malware is able to bypass the user verification. This means that the authenticator could be used without the user's consent and potentially even without the user's knowledge.
//nolint:gosec
UserVerificationBypass AuthenticatorStatus = "USER_VERIFICATION_BYPASS"
// AttestationKeyCompromise - Indicates that an attestation key for this authenticator is known to be compromised. Additional data should be supplied, including the key identifier and the date of compromise, if known.
AttestationKeyCompromise AuthenticatorStatus = "ATTESTATION_KEY_COMPROMISE"
// UserKeyRemoteCompromise - This authenticator has identified weaknesses that allow registered keys to be compromised and should not be trusted. This would include both, i.e. weak entropy that causes predictable keys to be generated or side channels that allow keys or signatures to be forged, guessed or extracted.
UserKeyRemoteCompromise AuthenticatorStatus = "USER_KEY_REMOTE_COMPROMISE"
// UserKeyPhysicalCompromise - This authenticator has known weaknesses in its key protection mechanism(s) that allow user keys to be extracted by an adversary in physical possession of the device.
UserKeyPhysicalCompromise AuthenticatorStatus = "USER_KEY_PHYSICAL_COMPROMISE"
// UpdateAvailable - A software or firmware update is available for the device. Additional data should be supplied including a URL where users can obtain an update and the date the update was published.
UpdateAvailable AuthenticatorStatus = "UPDATE_AVAILABLE"
// Retired - The authenticator vendor has decided to retire the product, and this authenticator should not be
// accepted any longer.
//
// See: https://fidoalliance.org/specs/mds/fido-metadata-service-v3.1.1-rd-20251016.html#dom-authenticatorstatus-retired
Retired AuthenticatorStatus = "RETIRED"
// Revoked - The FIDO Alliance has determined that this authenticator should not be trusted for any reason, for example if it is known to be a fraudulent product or contain a deliberate backdoor.
Revoked AuthenticatorStatus = "REVOKED"
// SelfAssertionSubmitted - The authenticator vendor has completed and submitted the self-certification checklist to the FIDO Alliance. If this completed checklist is publicly available, the URL will be specified in StatusReportJSON.url.
SelfAssertionSubmitted AuthenticatorStatus = "SELF_ASSERTION_SUBMITTED"
// FidoCertifiedL1 - The authenticator has passed FIDO Authenticator certification at level 1. This level is the more strict successor of FIDO_CERTIFIED.
FidoCertifiedL1 AuthenticatorStatus = "FIDO_CERTIFIED_L1"
// FidoCertifiedL1plus - The authenticator has passed FIDO Authenticator certification at level 1+. This level is the more than level 1.
FidoCertifiedL1plus AuthenticatorStatus = "FIDO_CERTIFIED_L1plus"
// FidoCertifiedL2 - The authenticator has passed FIDO Authenticator certification at level 2. This level is more strict than level 1+.
FidoCertifiedL2 AuthenticatorStatus = "FIDO_CERTIFIED_L2"
// FidoCertifiedL2plus - The authenticator has passed FIDO Authenticator certification at level 2+. This level is more strict than level 2.
FidoCertifiedL2plus AuthenticatorStatus = "FIDO_CERTIFIED_L2plus"
// FidoCertifiedL3 - The authenticator has passed FIDO Authenticator certification at level 3. This level is more strict than level 2+.
FidoCertifiedL3 AuthenticatorStatus = "FIDO_CERTIFIED_L3"
// FidoCertifiedL3plus - The authenticator has passed FIDO Authenticator certification at level 3+. This level is more strict than level 3.
FidoCertifiedL3plus AuthenticatorStatus = "FIDO_CERTIFIED_L3plus"
// FIPS140CertifiedL1 - The authenticator has passed FIPS 140 certification at overall level 1.
FIPS140CertifiedL1 AuthenticatorStatus = "FIPS140_CERTIFIED_L1"
// FIPS140CertifiedL2 - The authenticator has passed FIPS 140 certification at overall level 2.
FIPS140CertifiedL2 AuthenticatorStatus = "FIPS140_CERTIFIED_L2"
// FIPS140CertifiedL3 - The authenticator has passed FIPS 140 certification at overall level 3.
FIPS140CertifiedL3 AuthenticatorStatus = "FIPS140_CERTIFIED_L3"
// FIPS140CertifiedL4 - The authenticator has passed FIPS 140 certification at overall level 4.
FIPS140CertifiedL4 AuthenticatorStatus = "FIPS140_CERTIFIED_L4"
)
// defaultUndesiredAuthenticatorStatus is an array of undesirable authenticator statuses.
var defaultUndesiredAuthenticatorStatus = [...]AuthenticatorStatus{
AttestationKeyCompromise,
UserVerificationBypass,
UserKeyRemoteCompromise,
UserKeyPhysicalCompromise,
Retired,
Revoked,
}
// IsUndesiredAuthenticatorStatus returns whether the supplied authenticator status is desirable or not.
func IsUndesiredAuthenticatorStatus(status AuthenticatorStatus) bool {
for _, s := range defaultUndesiredAuthenticatorStatus {
if s == status {
return true
}
}
return false
}
// IsUndesiredAuthenticatorStatusSlice returns whether the supplied authenticator status is desirable or not.
func IsUndesiredAuthenticatorStatusSlice(status AuthenticatorStatus, values []AuthenticatorStatus) bool {
for _, s := range values {
if s == status {
return true
}
}
return false
}
// IsUndesiredAuthenticatorStatusMap returns whether the supplied authenticator status is desirable or not.
func IsUndesiredAuthenticatorStatusMap(status AuthenticatorStatus, values map[AuthenticatorStatus]bool) bool {
_, ok := values[status]
return ok
}
// AuthenticationAlgorithm represents the authentication algorithm supported by an authenticator.
//
// See: https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#authentication-algorithms
type AuthenticationAlgorithm string
const (
// ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW is an ECDSA signature on the NIST secp256r1 curve which must have raw R and
// S buffers, encoded in big-endian order.
ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW AuthenticationAlgorithm = "secp256r1_ecdsa_sha256_raw"
// ALG_SIGN_SECP256R1_ECDSA_SHA256_DER is a DER ITU-X690-2008 encoded ECDSA signature RFC5480 on the NIST secp256r1
// curve.
ALG_SIGN_SECP256R1_ECDSA_SHA256_DER AuthenticationAlgorithm = "secp256r1_ecdsa_sha256_der"
// ALG_SIGN_RSASSA_PSS_SHA256_RAW is a RSASSA-PSS RFC3447 signature must have raw S buffers, encoded in big-endian
// order RFC4055 RFC4056.
ALG_SIGN_RSASSA_PSS_SHA256_RAW AuthenticationAlgorithm = "rsassa_pss_sha256_raw"
// ALG_SIGN_RSASSA_PSS_SHA256_DER is a DER ITU-X690-2008 encoded OCTET STRING (not BIT STRING!) containing the
// RSASSA-PSS RFC3447 signature RFC4055 RFC4056.
ALG_SIGN_RSASSA_PSS_SHA256_DER AuthenticationAlgorithm = "rsassa_pss_sha256_der"
// ALG_SIGN_SECP256K1_ECDSA_SHA256_RAW is an ECDSA signature on the secp256k1 curve which must have raw R and S
// buffers, encoded in big-endian order.
ALG_SIGN_SECP256K1_ECDSA_SHA256_RAW AuthenticationAlgorithm = "secp256k1_ecdsa_sha256_raw"
// ALG_SIGN_SECP256K1_ECDSA_SHA256_DER is a DER ITU-X690-2008 encoded ECDSA signature RFC5480 on the secp256k1 curve.
ALG_SIGN_SECP256K1_ECDSA_SHA256_DER AuthenticationAlgorithm = "secp256k1_ecdsa_sha256_der"
// ALG_SIGN_SM2_SM3_RAW is a Chinese SM2 elliptic curve based signature algorithm combined with SM3 hash algorithm
// OSCCA-SM2 OSCCA-SM3.
ALG_SIGN_SM2_SM3_RAW AuthenticationAlgorithm = "sm2_sm3_raw"
// ALG_SIGN_RSA_EMSA_PKCS1_SHA256_RAW is the EMSA-PKCS1-v1_5 signature as defined in RFC3447.
ALG_SIGN_RSA_EMSA_PKCS1_SHA256_RAW AuthenticationAlgorithm = "rsa_emsa_pkcs1_sha256_raw"
// ALG_SIGN_RSA_EMSA_PKCS1_SHA256_DER is a DER ITU-X690-2008 encoded OCTET STRING (not BIT STRING!) containing the
// EMSA-PKCS1-v1_5 signature as defined in RFC3447.
ALG_SIGN_RSA_EMSA_PKCS1_SHA256_DER AuthenticationAlgorithm = "rsa_emsa_pkcs1_sha256_der"
// ALG_SIGN_RSASSA_PSS_SHA384_RAW is a RSASSA-PSS RFC3447 signature must have raw S buffers, encoded in big-endian
// order RFC4055 RFC4056.
ALG_SIGN_RSASSA_PSS_SHA384_RAW AuthenticationAlgorithm = "rsassa_pss_sha384_raw"
// ALG_SIGN_RSASSA_PSS_SHA512_RAW is a RSASSA-PSS RFC3447 signature must have raw S buffers, encoded in big-endian
// order RFC4055 RFC4056.
ALG_SIGN_RSASSA_PSS_SHA512_RAW AuthenticationAlgorithm = "rsassa_pss_sha512_raw"
// ALG_SIGN_RSASSA_PKCSV15_SHA256_RAW is a RSASSA-PKCS1-v1_5 RFC3447 with SHA256(aka RS256) signature must have raw
// S buffers, encoded in big-endian order RFC8017 RFC4056.
ALG_SIGN_RSASSA_PKCSV15_SHA256_RAW AuthenticationAlgorithm = "rsassa_pkcsv15_sha256_raw"
// ALG_SIGN_RSASSA_PKCSV15_SHA384_RAW is a RSASSA-PKCS1-v1_5 RFC3447 with SHA384(aka RS384) signature must have raw S buffers, encoded in big-endian order RFC8017 RFC4056.
ALG_SIGN_RSASSA_PKCSV15_SHA384_RAW AuthenticationAlgorithm = "rsassa_pkcsv15_sha384_raw"
// ALG_SIGN_RSASSA_PKCSV15_SHA512_RAW is a RSASSA-PKCS1-v1_5 RFC3447 with SHA512(aka RS512) signature must have raw
// S buffers, encoded in big-endian order RFC8017 RFC4056.
ALG_SIGN_RSASSA_PKCSV15_SHA512_RAW AuthenticationAlgorithm = "rsassa_pkcsv15_sha512_raw"
// ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW is a RSASSA-PKCS1-v1_5 RFC3447 with SHA1(aka RS1) signature must have raw S
// buffers, encoded in big-endian order RFC8017 RFC4056.
ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW AuthenticationAlgorithm = "rsassa_pkcsv15_sha1_raw"
// ALG_SIGN_SECP384R1_ECDSA_SHA384_RAW is an ECDSA signature on the NIST secp384r1 curve with SHA384(aka: ES384)
// which must have raw R and S buffers, encoded in big-endian order.
ALG_SIGN_SECP384R1_ECDSA_SHA384_RAW AuthenticationAlgorithm = "secp384r1_ecdsa_sha384_raw"
// ALG_SIGN_SECP521R1_ECDSA_SHA512_RAW is an ECDSA signature on the NIST secp512r1 curve with SHA512(aka: ES512)
// which must have raw R and S buffers, encoded in big-endian order.
ALG_SIGN_SECP521R1_ECDSA_SHA512_RAW AuthenticationAlgorithm = "secp521r1_ecdsa_sha512_raw"
// ALG_SIGN_ED25519_EDDSA_SHA512_RAW is an EdDSA signature on the curve 25519, which must have raw R and S buffers,
// encoded in big-endian order.
ALG_SIGN_ED25519_EDDSA_SHA512_RAW AuthenticationAlgorithm = "ed25519_eddsa_sha512_raw"
// ALG_SIGN_ED448_EDDSA_SHA512_RAW is an EdDSA signature on the curve Ed448, which must have raw R and S buffers,
// encoded in big-endian order.
ALG_SIGN_ED448_EDDSA_SHA512_RAW AuthenticationAlgorithm = "ed448_eddsa_sha512_raw"
)
// TODO: this goes away after webauthncose.CredentialPublicKey gets implemented.
type algKeyCose struct {
KeyType webauthncose.COSEKeyType
Algorithm webauthncose.COSEAlgorithmIdentifier
Curve webauthncose.COSEEllipticCurve
}
func algKeyCoseDictionary() func(AuthenticationAlgorithm) algKeyCose {
mapping := map[AuthenticationAlgorithm]algKeyCose{
ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW: {KeyType: webauthncose.EllipticKey, Algorithm: webauthncose.AlgES256, Curve: webauthncose.P256},
ALG_SIGN_SECP256R1_ECDSA_SHA256_DER: {KeyType: webauthncose.EllipticKey, Algorithm: webauthncose.AlgES256, Curve: webauthncose.P256},
ALG_SIGN_RSASSA_PSS_SHA256_RAW: {KeyType: webauthncose.RSAKey, Algorithm: webauthncose.AlgPS256},
ALG_SIGN_RSASSA_PSS_SHA256_DER: {KeyType: webauthncose.RSAKey, Algorithm: webauthncose.AlgPS256},
ALG_SIGN_SECP256K1_ECDSA_SHA256_RAW: {KeyType: webauthncose.EllipticKey, Algorithm: webauthncose.AlgES256K, Curve: webauthncose.Secp256k1},
ALG_SIGN_SECP256K1_ECDSA_SHA256_DER: {KeyType: webauthncose.EllipticKey, Algorithm: webauthncose.AlgES256K, Curve: webauthncose.Secp256k1},
ALG_SIGN_RSASSA_PSS_SHA384_RAW: {KeyType: webauthncose.RSAKey, Algorithm: webauthncose.AlgPS384},
ALG_SIGN_RSASSA_PSS_SHA512_RAW: {KeyType: webauthncose.RSAKey, Algorithm: webauthncose.AlgPS512},
ALG_SIGN_RSASSA_PKCSV15_SHA256_RAW: {KeyType: webauthncose.RSAKey, Algorithm: webauthncose.AlgRS256},
ALG_SIGN_RSASSA_PKCSV15_SHA384_RAW: {KeyType: webauthncose.RSAKey, Algorithm: webauthncose.AlgRS384},
ALG_SIGN_RSASSA_PKCSV15_SHA512_RAW: {KeyType: webauthncose.RSAKey, Algorithm: webauthncose.AlgRS512},
ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW: {KeyType: webauthncose.RSAKey, Algorithm: webauthncose.AlgRS1},
ALG_SIGN_SECP384R1_ECDSA_SHA384_RAW: {KeyType: webauthncose.EllipticKey, Algorithm: webauthncose.AlgES384, Curve: webauthncose.P384},
ALG_SIGN_SECP521R1_ECDSA_SHA512_RAW: {KeyType: webauthncose.EllipticKey, Algorithm: webauthncose.AlgES512, Curve: webauthncose.P521},
ALG_SIGN_ED25519_EDDSA_SHA512_RAW: {KeyType: webauthncose.OctetKey, Algorithm: webauthncose.AlgEdDSA, Curve: webauthncose.Ed25519},
ALG_SIGN_ED448_EDDSA_SHA512_RAW: {KeyType: webauthncose.OctetKey, Algorithm: webauthncose.AlgEdDSA, Curve: webauthncose.Ed448},
}
return func(key AuthenticationAlgorithm) algKeyCose {
return mapping[key]
}
}
func AlgKeyMatch(key algKeyCose, algs []AuthenticationAlgorithm) bool {
for _, alg := range algs {
if reflect.DeepEqual(algKeyCoseDictionary()(alg), key) {
return true
}
}
return false
}
// PublicKeyAlgAndEncoding represents the public key format supported by an authenticator during registration.
//
// See: https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#public-key-representation-formats
type PublicKeyAlgAndEncoding string
const (
// ALG_KEY_ECC_X962_RAW is a raw ANSI X9.62 formatted Elliptic Curve public key.
ALG_KEY_ECC_X962_RAW PublicKeyAlgAndEncoding = "ecc_x962_raw"
// ALG_KEY_ECC_X962_DER is a DER ITU-X690-2008 encoded ANSI X.9.62 formatted SubjectPublicKeyInfo RFC5480 specifying an elliptic curve public key.
ALG_KEY_ECC_X962_DER PublicKeyAlgAndEncoding = "ecc_x962_der"
// ALG_KEY_RSA_2048_RAW is a raw encoded 2048-bit RSA public key RFC3447.
ALG_KEY_RSA_2048_RAW PublicKeyAlgAndEncoding = "rsa_2048_raw"
// ALG_KEY_RSA_2048_DER is a ASN.1 DER [ITU-X690-2008] encoded 2048-bit RSA RFC3447 public key RFC4055.
ALG_KEY_RSA_2048_DER PublicKeyAlgAndEncoding = "rsa_2048_der"
// ALG_KEY_COSE is a COSE_Key format, as defined in Section 7 of RFC8152. This encoding includes its own field for indicating the public key algorithm.
ALG_KEY_COSE PublicKeyAlgAndEncoding = "cose"
)
type Error struct {
// Short name for the type of error that has occurred.
Type string `json:"type"`
// Additional details about the error.
Details string `json:"error"`
// Information to help debug the error.
DevInfo string `json:"debug"`
}
func (e *Error) Error() string {
return e.Details
}
// Clock is an interface used to implement clock functionality in various metadata areas.
type Clock interface {
// Now returns the current time.
Now() time.Time
}
// RealClock is just a real clock.
type RealClock struct{}
// Now returns the current time.
func (RealClock) Now() time.Time {
return time.Now()
}