Giter Club home page Giter Club logo

android-keystore's Introduction

Android keystore (credential storage) access

Direct access to Android's credential storage (keystore). Sample code for the 'Storing application secrets in Android's credential storage' blog post: http://nelenkov.blogspot.com/2012/05/storing-application-secrets-in-androids.html.

Accesses the credential storage directly using a private API. Not guaranteed to work on all Android versions, but tested with 2.1 to 4.0.

2013/8/21: updated for Android 4.3. Blog post: http://nelenkov.blogspot.com/2013/08/credential-storage-enhancements-android-43.html

2014/1/16: updated for Android 4.4. Major changes in Android are support for EC and DSA keys. Added ECDSA sign/verify sample.

android-keystore's People

Contributors

nelenkov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

android-keystore's Issues

FC from KeyStore ln 75 AssertionError 1 and 5

I just posted a longer explanation on your blog, but here is log if at all helpful. Thanks!

09-10 08:08:01.346: I/ActivityManager(59): Displayed activity org.nick.androidkeystore/.KeystoreActivity: 445 ms (total 445 ms)
09-10 08:08:01.466: I/keystore(36): uid: 10051 action: t -> 1 state: 1 -> 1 retry: 4
09-10 08:08:01.466: I/keystore(36): uid: 10051 action: t -> 1 state: 1 -> 1 retry: 4
09-10 08:08:01.596: D/KeystoreActivity(4894): Keystore state: UNLOCKED
09-10 08:08:01.706: I/keystore(36): uid: 10051 action: s -> 1 state: 1 -> 1 retry: 4
09-10 08:08:01.716: I/keystore(36): uid: 10051 action: t -> 1 state: 1 -> 1 retry: 4
09-10 08:08:01.716: D/KeystoreActivity(4894): Keys: 
09-10 08:08:01.726: I/keystore(36): uid: 10051 action: g -> 1 state: 1 -> 1 retry: 4
09-10 08:08:01.726: D/KeystoreActivity(4894):   test_key1: -8825863414354876680663630784384295312183551205364498841532107818494149774506
09-10 08:08:01.736: I/keystore(36): uid: 10051 action: g -> 1 state: 1 -> 1 retry: 4
09-10 08:08:01.736: D/KeystoreActivity(4894):   test_key0: -19378007423384393247659212638159071070138594974322013632346596321676577597408
09-10 08:08:01.825: W/dalvikvm(4894): threadid=12: thread exiting with uncaught exception (group=0x4001d800)
09-10 08:08:01.885: E/AndroidRuntime(4894): FATAL EXCEPTION: AsyncTask #5
09-10 08:08:01.885: E/AndroidRuntime(4894): java.lang.RuntimeException: An error occured while executing doInBackground()
09-10 08:08:01.885: E/AndroidRuntime(4894):     at android.os.AsyncTask$3.done(AsyncTask.java:200)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at java.util.concurrent.FutureTask.run(FutureTask.java:137)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at java.lang.Thread.run(Thread.java:1096)
09-10 08:08:01.885: E/AndroidRuntime(4894): Caused by: java.lang.AssertionError: 1
09-10 08:08:01.885: E/AndroidRuntime(4894):     at org.nick.androidkeystore.android.security.KeyStore.state(KeyStore.java:75)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at org.nick.androidkeystore.KeystoreActivity$1.doWork(KeystoreActivity.java:139)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at org.nick.androidkeystore.KeystoreActivity$KeystoreTask.doInBackground(KeystoreActivity.java:96)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at org.nick.androidkeystore.KeystoreActivity$KeystoreTask.doInBackground(KeystoreActivity.java:1)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at android.os.AsyncTask$2.call(AsyncTask.java:185)
09-10 08:08:01.885: E/AndroidRuntime(4894):     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
09-10 08:08:01.885: E/AndroidRuntime(4894):     ... 4 more
09-10 08:08:01.905: W/ActivityManager(59):   Force finishing activity org.nick.androidkeystore/.KeystoreActivity

...

09-10 08:18:08.966: I/ActivityManager(59): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=org.nick.androidkeystore/.KeystoreActivity }
09-10 08:18:09.166: I/keystore(36): uid: 10051 action: t -> 1 state: 1 -> 1 retry: 4
09-10 08:18:09.206: I/keystore(36): uid: 10051 action: t -> 1 state: 1 -> 1 retry: 4
09-10 08:18:09.206: I/keystore(36): uid: 10051 action: t -> 1 state: 1 -> 1 retry: 4
09-10 08:18:09.246: D/KeystoreActivity(27660): Keystore state: UNLOCKED
09-10 08:18:09.435: I/keystore(36): uid: 10051 action: t -> 1 state: 1 -> 1 retry: 4
09-10 08:18:09.475: I/keystore(36): uid: 10051 action: s -> 1 state: 1 -> 1 retry: 4
09-10 08:18:09.495: D/KeystoreActivity(27660): Keys: 
09-10 08:18:09.495: I/keystore(36): uid: 10051 action: g -> 1 state: 1 -> 1 retry: 4
09-10 08:18:09.505: D/KeystoreActivity(27660):  test_key1: -48453358114406951772358788725542992193076986772767435236646674819192049096916
09-10 08:18:09.505: I/keystore(36): uid: 10051 action: g -> 1 state: 1 -> 1 retry: 4
09-10 08:18:09.505: D/KeystoreActivity(27660):  test_key0: 12715778555170410776424802206206477859550137287931188221243440516653640148217
09-10 08:18:09.625: W/dalvikvm(27660): threadid=7: thread exiting with uncaught exception (group=0x4001d800)
09-10 08:18:09.655: E/AndroidRuntime(27660): FATAL EXCEPTION: AsyncTask #1
09-10 08:18:09.655: E/AndroidRuntime(27660): java.lang.RuntimeException: An error occured while executing doInBackground()
09-10 08:18:09.655: E/AndroidRuntime(27660):    at android.os.AsyncTask$3.done(AsyncTask.java:200)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at java.util.concurrent.FutureTask.run(FutureTask.java:137)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at java.lang.Thread.run(Thread.java:1096)
09-10 08:18:09.655: E/AndroidRuntime(27660): Caused by: java.lang.AssertionError: 5
09-10 08:18:09.655: E/AndroidRuntime(27660):    at org.nick.androidkeystore.android.security.KeyStore.state(KeyStore.java:75)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at org.nick.androidkeystore.KeystoreActivity$1.doWork(KeystoreActivity.java:139)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at org.nick.androidkeystore.KeystoreActivity$KeystoreTask.doInBackground(KeystoreActivity.java:96)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at org.nick.androidkeystore.KeystoreActivity$KeystoreTask.doInBackground(KeystoreActivity.java:1)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at android.os.AsyncTask$2.call(AsyncTask.java:185)
09-10 08:18:09.655: E/AndroidRuntime(27660):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
09-10 08:18:09.655: E/AndroidRuntime(27660):    ... 4 more
09-10 08:18:09.665: W/ActivityManager(59):   Force finishing activity org.nick.androidkeystore/.KeystoreActivity

Safe for production code?

I'm looking for a way to add password AES encryption for API 21 and upward, is the KeyStore used in this application safe for production code even for rooted devices?

Wrapper API

Why not turn this into a nice, easy to use wrapper API? Rather than make people dig through this example?

I think a ton of people would be interested in having pre-4.3 support for KeyStore

Android M Preview 2

Hi There,

Your work on the android keystore has been really helpful. I have noticed however that it doesn't work anymore with the latest Android M Preview. I am just wondering if you have any thoughts to look into this?

I have tried creating a new KeyStore class for M however I am having no such look with it.

Thanks! :)

how to solve invalid keystore format error in android studio 2.2???????????

**here is error report-------->
error:Error:java.lang.RuntimeException:

com.android.ide.common.signing.KeytoolException: Failed to read key palash from store "D:\MyApplication3\.idea\workspace.xml": Invalid keystore format
second error:Error:com.android.ide.common.signing.KeytoolException: Failed to read key palash from store "D:\MyApplication3\.idea\workspace.xml": Invalid keystore format
third error:Error:java.io.IOException: Invalid keystore format

how to solve this three errors??????**
here is gradle console message----------->

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':app:packageRelease'.

com.android.ide.common.signing.KeytoolException: Failed to read key palash from store "D:\MyApplication3.idea\workspace.xml": Invalid keystore format

  • Try:
    Run with --info or --debug option to get more log output.

  • Exception is:
    org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:packageRelease'.
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
    at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
    at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:66)
    at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
    at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:52)
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
    at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:203)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:185)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:66)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:50)
    at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:25)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:110)
    at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
    at org.gradle.execution.DefaultBuildExecuter.access$000(DefaultBuildExecuter.java:23)
    at org.gradle.execution.DefaultBuildExecuter$1.proceed(DefaultBuildExecuter.java:43)
    at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:30)
    at org.gradle.initialization.DefaultGradleLauncher$4.run(DefaultGradleLauncher.java:153)
    at org.gradle.internal.Factories$1.create(Factories.java:22)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:53)
    at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:150)
    at org.gradle.initialization.DefaultGradleLauncher.access$200(DefaultGradleLauncher.java:32)
    at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:98)
    at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:92)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:63)
    at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:92)
    at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:83)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:99)
    at org.gradle.tooling.internal.provider.runner.BuildModelActionRunner.run(BuildModelActionRunner.java:46)
    at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    at org.gradle.tooling.internal.provider.runner.SubscribableBuildActionRunner.run(SubscribableBuildActionRunner.java:58)
    at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:48)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30)
    at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:81)
    at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:46)
    at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:52)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
    at org.gradle.util.Swapper.swap(Swapper.java:38)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.health.DaemonHealthTracker.execute(DaemonHealthTracker.java:47)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:72)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.health.HintGCAfterBuild.execute(HintGCAfterBuild.java:41)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
    at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:237)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
    at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
    Caused by: java.lang.RuntimeException: com.android.ide.common.signing.KeytoolException: Failed to read key palash from store "D:\MyApplication3.idea\workspace.xml": Invalid keystore format
    at com.android.build.gradle.tasks.PackageAndroidArtifact.doTask(PackageAndroidArtifact.java:469)
    at com.android.build.gradle.tasks.PackageAndroidArtifact.doFullTaskAction(PackageAndroidArtifact.java:321)
    at com.android.build.gradle.tasks.PackageApplication.doFullTaskAction(PackageApplication.java:75)
    at com.android.build.gradle.internal.tasks.IncrementalTask.taskAction(IncrementalTask.java:88)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:75)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.doExecute(AnnotationProcessingTaskFactory.java:245)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:221)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.execute(AnnotationProcessingTaskFactory.java:232)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:210)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
    ... 70 more
    Caused by: com.android.ide.common.signing.KeytoolException: Failed to read key palash from store "D:\MyApplication3.idea\workspace.xml": Invalid keystore format
    at com.android.ide.common.signing.KeystoreHelper.getCertificateInfo(KeystoreHelper.java:212)
    at com.android.build.gradle.tasks.PackageAndroidArtifact.doTask(PackageAndroidArtifact.java:431)
    ... 80 more
    Caused by: java.io.IOException: Invalid keystore format
    at com.android.ide.common.signing.KeystoreHelper.getCertificateInfo(KeystoreHelper.java:190)
    ... 81 more

BUILD FAILED

Total time: 1 mins 32.136 secs
**

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.