bool
RTCCertificate::ReadCertificate(JSStructuredCloneReader* aReader,
                                const nsNSSShutDownPreventionLock& /*proof*/)
{
  CryptoBuffer cert;
  if (!ReadBuffer(aReader, cert) || cert.Length() == 0) {
    return false;
  }

  SECItem der = { siBuffer, cert.Elements(),
                  static_cast<unsigned int>(cert.Length()) };
  mCertificate.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
                                             &der, nullptr, true, true));
  return !!mCertificate;
}
Exemple #2
0
NS_IMETHODIMP
U2FSignTask::Run()
{
  nsNSSShutDownPreventionLock locker;
  if (isAlreadyShutDown()) {
    ReturnError(ErrorCode::OTHER_ERROR);
    return NS_ERROR_FAILURE;
  }

  // Search the requests for one a token can fulfill
  for (size_t i = 0; i < mRegisteredKeys.Length(); i += 1) {
    RegisteredKey request(mRegisteredKeys[i]);

    // Check for required attributes
    if (!(request.mVersion.WasPassed() &&
          request.mKeyHandle.WasPassed())) {
      continue;
    }

    // Do not permit an individual RegisteredKey to assert a different AppID
    if (request.mAppId.WasPassed() && mAppId != request.mAppId.Value()) {
      continue;
    }

    // Assemble a clientData object
    CryptoBuffer clientData;
    nsresult rv = AssembleClientData(mOrigin, kGetAssertion, mChallenge,
                                     clientData);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      ReturnError(ErrorCode::OTHER_ERROR);
      return NS_ERROR_FAILURE;
    }

    // Hash the AppID and the ClientData into the AppParam and ChallengeParam
    SECStatus srv;
    nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
    CryptoBuffer appParam;
    CryptoBuffer challengeParam;
    if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
        !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
      ReturnError(ErrorCode::OTHER_ERROR);
      return NS_ERROR_FAILURE;
    }

    srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
                       reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
                       cAppId.Length());
    if (srv != SECSuccess) {
      ReturnError(ErrorCode::OTHER_ERROR);
      return NS_ERROR_FAILURE;
    }

    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
                       clientData.Elements(), clientData.Length());
    if (srv != SECSuccess) {
      ReturnError(ErrorCode::OTHER_ERROR);
      return NS_ERROR_FAILURE;
    }

    // Decode the key handle
    CryptoBuffer keyHandle;
    rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      ReturnError(ErrorCode::OTHER_ERROR);
      return NS_ERROR_FAILURE;
    }

    // Get the signature from the token
    CryptoBuffer signatureData;
    bool signSuccess = false;

    // We ignore mTransports, as it is intended to be used for sorting the
    // available devices by preference, but is not an exclusion factor.

    for (size_t a = 0; a < mAuthenticators.Length() && !signSuccess; ++a) {
      Authenticator token(mAuthenticators[a]);
      bool isCompatible = false;
      bool isRegistered = false;

      rv = token->IsCompatibleVersion(request.mVersion.Value(), &isCompatible);
      if (NS_FAILED(rv)) {
        ReturnError(ErrorCode::OTHER_ERROR);
        return NS_ERROR_FAILURE;
      }
      if (!isCompatible) {
        continue;
      }

      rv = token->IsRegistered(keyHandle.Elements(), keyHandle.Length(),
                               &isRegistered);
      if (NS_FAILED(rv)) {
        ReturnError(ErrorCode::OTHER_ERROR);
        return NS_ERROR_FAILURE;
      }

      if (isCompatible && isRegistered) {
        uint8_t* buffer;
        uint32_t bufferlen;
        nsresult rv = token->Sign(appParam.Elements(), appParam.Length(),
                                  challengeParam.Elements(), challengeParam.Length(),
                                  keyHandle.Elements(), keyHandle.Length(),
                                  &buffer, &bufferlen);
        if (NS_FAILED(rv)) {
          ReturnError(ErrorCode::OTHER_ERROR);
          return NS_ERROR_FAILURE;
        }

        MOZ_ASSERT(buffer);
        signatureData.Assign(buffer, bufferlen);
        free(buffer);
        signSuccess = true;
      }
    }

    if (!signSuccess) {
      // Try another request
      continue;
    }

    // Assemble a response object to return
    nsString clientDataBase64, signatureDataBase64;
    nsresult rvClientData =
      clientData.ToJwkBase64(clientDataBase64);
    nsresult rvSignatureData =
      signatureData.ToJwkBase64(signatureDataBase64);
    if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
        NS_WARN_IF(NS_FAILED(rvSignatureData))) {
      ReturnError(ErrorCode::OTHER_ERROR);
      return NS_ERROR_FAILURE;
    }
    SignResponse response;
    response.mKeyHandle.Construct(request.mKeyHandle.Value());
    response.mClientData.Construct(clientDataBase64);
    response.mSignatureData.Construct(signatureDataBase64);
    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));

    ErrorResult result;
    mCallback->Call(response, result);
    NS_WARN_IF(result.Failed());
    // Useful exceptions already got reported.
    result.SuppressException();
    return NS_OK;
  }

  // Nothing could satisfy
  ReturnError(ErrorCode::DEVICE_INELIGIBLE);
  return NS_ERROR_FAILURE;
}
Exemple #3
0
// NOTE: This method represents a theoretical way to use a U2F-compliant token
// to produce the result of the WebAuthn GetAssertion method. The exact mapping
// of U2F data fields to WebAuthn data fields is still a matter of ongoing
// discussion, and this should not be taken as anything but a point-in- time
// possibility.
void
WebAuthentication::U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
                    const Authenticator& aToken, CryptoBuffer& aRpIdHash,
                    const nsACString& aClientData, CryptoBuffer& aClientDataHash,
                    nsTArray<CryptoBuffer>& aAllowList,
                    const WebAuthnExtensions& aExtensions)
{
  MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthGetAssertion"));

  // 4.1.2.7.e Add an entry to issuedRequests, corresponding to this request.
  aRequest->AddActiveToken(__func__);

  // 4.1.2.8 While issuedRequests is not empty, perform the following actions
  // depending upon the adjustedTimeout timer and responses from the
  // authenticators:

  // 4.1.2.8.a If the timer for adjustedTimeout expires, then for each entry
  // in issuedRequests invoke the authenticatorCancel operation on that
  // authenticator and remove its entry from the list.

  for (CryptoBuffer& allowedCredential : aAllowList) {
    bool isRegistered = false;
    nsresult rv = aToken->IsRegistered(allowedCredential.Elements(),
                                       allowedCredential.Length(),
                                       &isRegistered);

    // 4.1.2.8.b If any authenticator returns a status indicating that the user
    // cancelled the operation, delete that authenticator’s entry from
    // issuedRequests. For each remaining entry in issuedRequests invoke the
    // authenticatorCancel operation on that authenticator, and remove its entry
    // from the list.

    // 4.1.2.8.c If any authenticator returns an error status, delete the
    // corresponding entry from issuedRequests.
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRequest->SetFailure(rv);
      return;
    }

    if (!isRegistered) {
      continue;
    }

    // Sign
    uint8_t* buffer;
    uint32_t bufferlen;
    rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
                      aClientDataHash.Elements(), aClientDataHash.Length(),
                      allowedCredential.Elements(), allowedCredential.Length(),
                      &buffer, &bufferlen);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRequest->SetFailure(rv);
      return;
    }

    MOZ_ASSERT(buffer);
    CryptoBuffer signatureData;
    if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
      free(buffer);
      aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
      return;
    }
    free(buffer);

    // 4.1.2.8.d If any authenticator returns success:

    // 4.1.2.8.d.1 Remove this authenticator’s entry from issuedRequests.

    // 4.1.2.8.d.2 Create a new WebAuthnAssertion object named value and
    // populate its fields with the values returned from the authenticator as
    // well as the clientDataJSON computed earlier.

    CryptoBuffer clientDataBuf;
    if (!clientDataBuf.Assign(aClientData)) {
      aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
      return;
    }

    CryptoBuffer authenticatorDataBuf;
    rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
                                      signatureData);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRequest->SetFailure(rv);
      return;
    }

    RefPtr<ScopedCredential> credential = new ScopedCredential(this);
    credential->SetType(ScopedCredentialType::ScopedCred);
    credential->SetId(allowedCredential);

    AssertionPtr assertion = new WebAuthnAssertion(this);
    assertion->SetCredential(credential);
    assertion->SetClientData(clientDataBuf);
    assertion->SetAuthenticatorData(authenticatorDataBuf);
    assertion->SetSignature(signatureData);

    // 4.1.2.8.d.3 For each remaining entry in issuedRequests invoke the
    // authenticatorCancel operation on that authenticator and remove its entry
    // from the list.

    // 4.1.2.8.d.4 Resolve promise with value and terminate this algorithm.
    aRequest->SetSuccess(assertion);
    return;
  }

  // 4.1.2.9 Reject promise with a DOMException whose name is "NotAllowedError",
  // and terminate this algorithm.
  aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
}
Exemple #4
0
// NOTE: This method represents a theoretical way to use a U2F-compliant token
// to produce the result of the WebAuthn MakeCredential method. The exact
// mapping of U2F data fields to WebAuthn data fields is still a matter of
// ongoing discussion, and this should not be taken as anything but a point-in-
// time possibility.
void
WebAuthentication::U2FAuthMakeCredential(
             const RefPtr<CredentialRequest>& aRequest,
             const Authenticator& aToken, CryptoBuffer& aRpIdHash,
             const nsACString& aClientData, CryptoBuffer& aClientDataHash,
             const Account& aAccount,
             const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
             const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
             const WebAuthnExtensions& aExtensions)
{
  MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthMakeCredential"));
  aRequest->AddActiveToken(__func__);

  // 5.1.1 When this operation is invoked, the authenticator must perform the
  // following procedure:

  // 5.1.1.a Check if all the supplied parameters are syntactically well-
  // formed and of the correct length. If not, return an error code equivalent
  // to UnknownError and terminate the operation.

  if ((aRpIdHash.Length() != SHA256_LENGTH) ||
      (aClientDataHash.Length() != SHA256_LENGTH)) {
    aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
    return;
  }

  // 5.1.1.b Check if at least one of the specified combinations of
  // ScopedCredentialType and cryptographic parameters is supported. If not,
  // return an error code equivalent to NotSupportedError and terminate the
  // operation.

  bool isValidCombination = false;

  for (size_t a = 0; a < aNormalizedParams.Length(); ++a) {
    if (aNormalizedParams[a].mType == ScopedCredentialType::ScopedCred &&
        aNormalizedParams[a].mAlgorithm.IsString() &&
        aNormalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
          WEBCRYPTO_NAMED_CURVE_P256)) {
      isValidCombination = true;
      break;
    }
  }
  if (!isValidCombination) {
    aRequest->SetFailure(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    return;
  }

  // 5.1.1.c Check if a credential matching any of the supplied
  // ScopedCredential identifiers is present on this authenticator. If so,
  // return an error code equivalent to NotAllowedError and terminate the
  // operation.

  if (aExcludeList.WasPassed()) {
    const Sequence<ScopedCredentialDescriptor>& list = aExcludeList.Value();

    for (const ScopedCredentialDescriptor& scd : list) {
      bool isRegistered = false;

      uint8_t *data;
      uint32_t len;

      // data is owned by the Descriptor, do don't free it here.
      if (NS_FAILED(ScopedCredentialGetData(scd, &data, &len))) {
        aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
        return;
      }

      nsresult rv = aToken->IsRegistered(data, len, &isRegistered);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        aRequest->SetFailure(rv);
        return;
      }

      if (isRegistered) {
        aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
        return;
      }
    }
  }

  // 5.1.1.d Prompt the user for consent to create a new credential. The
  // prompt for obtaining this consent is shown by the authenticator if it has
  // its own output capability, or by the user agent otherwise. If the user
  // denies consent, return an error code equivalent to NotAllowedError and
  // terminate the operation.

  // 5.1.1.d Once user consent has been obtained, generate a new credential
  // object

  // 5.1.1.e If any error occurred while creating the new credential object,
  // return an error code equivalent to UnknownError and terminate the
  // operation.

  // 5.1.1.f Process all the supported extensions requested by the client, and
  // generate an attestation statement. If no authority key is available to
  // sign such an attestation statement, then the authenticator performs self
  // attestation of the credential with its own private key. For more details
  // on attestation, see §5.3 Credential Attestation Statements.

  // No extensions are supported

  // 4.1.1.11 While issuedRequests is not empty, perform the following actions
  // depending upon the adjustedTimeout timer and responses from the
  // authenticators:

  // 4.1.1.11.a If the adjustedTimeout timer expires, then for each entry in
  // issuedRequests invoke the authenticatorCancel operation on that
  // authenticator and remove its entry from the list.

  uint8_t* buffer;
  uint32_t bufferlen;

  nsresult rv = aToken->Register(aRpIdHash.Elements(), aRpIdHash.Length(),
                                 aClientDataHash.Elements(),
                                 aClientDataHash.Length(), &buffer, &bufferlen);

  // 4.1.1.11.b If any authenticator returns a status indicating that the user
  // cancelled the operation, delete that authenticator’s entry from
  // issuedRequests. For each remaining entry in issuedRequests invoke the
  // authenticatorCancel operation on that authenticator and remove its entry
  // from the list.

  // 4.1.1.11.c If any authenticator returns an error status, delete the
  // corresponding entry from issuedRequests.
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
    return;
  }

  MOZ_ASSERT(buffer);
  CryptoBuffer regData;
  if (NS_WARN_IF(!regData.Assign(buffer, bufferlen))) {
    free(buffer);
    aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
    return;
  }
  free(buffer);

  // Decompose the U2F registration packet
  CryptoBuffer pubKeyBuf;
  CryptoBuffer keyHandleBuf;
  CryptoBuffer attestationCertBuf;
  CryptoBuffer signatureBuf;

  rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
                                        attestationCertBuf, signatureBuf);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRequest->SetFailure(rv);
    return;
  }

  // Sign the aClientDataHash explicitly to get the format needed for
  // the AuthenticatorData parameter of WebAuthnAttestation. This might
  // be temporary while the spec settles down how to incorporate U2F.
  rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
                    aClientDataHash.Elements(), aClientDataHash.Length(),
                    keyHandleBuf.Elements(), keyHandleBuf.Length(), &buffer,
                    &bufferlen);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRequest->SetFailure(rv);
    return;
  }

  MOZ_ASSERT(buffer);
  CryptoBuffer signatureData;
  if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
    free(buffer);
    aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
    return;
  }
  free(buffer);

  CryptoBuffer clientDataBuf;
  if (!clientDataBuf.Assign(aClientData)) {
    aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
    return;
  }

  CryptoBuffer authenticatorDataBuf;
  rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
                                    signatureData);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRequest->SetFailure(rv);
    return;
  }

  // 4.1.1.11.d If any authenticator indicates success:

  // 4.1.1.11.d.1 Remove this authenticator’s entry from issuedRequests.

  // 4.1.1.11.d.2 Create a new ScopedCredentialInfo object named value and
  // populate its fields with the values returned from the authenticator as well
  // as the clientDataJSON computed earlier.

  RefPtr<ScopedCredential> credential = new ScopedCredential(this);
  credential->SetType(ScopedCredentialType::ScopedCred);
  credential->SetId(keyHandleBuf);

  RefPtr<WebAuthnAttestation> attestation = new WebAuthnAttestation(this);
  attestation->SetFormat(NS_LITERAL_STRING("u2f"));
  attestation->SetClientData(clientDataBuf);
  attestation->SetAuthenticatorData(authenticatorDataBuf);
  attestation->SetAttestation(regData);

  CredentialPtr info = new ScopedCredentialInfo(this);
  info->SetCredential(credential);
  info->SetAttestation(attestation);

  // 4.1.1.11.d.3 For each remaining entry in issuedRequests invoke the
  // authenticatorCancel operation on that authenticator and remove its entry
  // from the list.

  // 4.1.1.11.d.4 Resolve promise with value and terminate this algorithm.
  aRequest->SetSuccess(info);
}
Exemple #5
0
static nsresult
U2FDecomposeRegistrationResponse(const CryptoBuffer& aResponse,
                                 /* out */ CryptoBuffer& aPubKeyBuf,
                                 /* out */ CryptoBuffer& aKeyHandleBuf,
                                 /* out */ CryptoBuffer& aAttestationCertBuf,
                                 /* out */ CryptoBuffer& aSignatureBuf)
{
  // U2F v1.1 Format via
  // http://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html
  //
  // Bytes  Value
  // 1      0x05
  // 65     public key
  // 1      key handle length
  // *      key handle
  // ASN.1  attestation certificate
  // *      attestation signature

  pkix::Input u2fResponse;
  u2fResponse.Init(aResponse.Elements(), aResponse.Length());

  pkix::Reader input(u2fResponse);

  uint8_t b;
  if (input.Read(b) != pkix::Success) {
    return NS_ERROR_DOM_UNKNOWN_ERR;
  }
  if (b != 0x05) {
    return NS_ERROR_DOM_UNKNOWN_ERR;
  }

  nsresult rv = ReadToCryptoBuffer(input, aPubKeyBuf, 65);
  if (NS_FAILED(rv)) {
    return rv;
  }

  uint8_t handleLen;
  if (input.Read(handleLen) != pkix::Success) {
    return NS_ERROR_DOM_UNKNOWN_ERR;
  }

  rv = ReadToCryptoBuffer(input, aKeyHandleBuf, handleLen);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // We have to parse the ASN.1 SEQUENCE on the outside to determine the cert's
  // length.
  pkix::Input cert;
  if (pkix::der::ExpectTagAndGetValue(input, pkix::der::SEQUENCE, cert)
        != pkix::Success) {
    return NS_ERROR_DOM_UNKNOWN_ERR;
  }

  pkix::Reader certInput(cert);
  rv = ReadToCryptoBuffer(certInput, aAttestationCertBuf, cert.GetLength());
  if (NS_FAILED(rv)) {
    return rv;
  }

  // The remainder of u2fResponse is the signature
  pkix::Input u2fSig;
  input.SkipToEnd(u2fSig);
  pkix::Reader sigInput(u2fSig);
  rv = ReadToCryptoBuffer(sigInput, aSignatureBuf, u2fSig.GetLength());
  if (NS_FAILED(rv)) {
    return rv;
  }

  return NS_OK;
}