Port kotlin SDK to new Android project
This commit is contained in:
Родитель
3d8b57d164
Коммит
bd432c2224
|
@ -1,38 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -1,76 +1,14 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# vs code settings
|
||||
.vscode
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
dist
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# distribution folder
|
||||
dist
|
||||
|
||||
# nunit test results
|
||||
nunitresults.xml
|
||||
|
||||
# macOS metadata files
|
||||
.DS_Store
|
||||
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
PortableIdentityCard-ClientSDK-Android
|
|
@ -0,0 +1,122 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/PortableIdentityCard-ClientSDK" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Jasmine - Current file",
|
||||
"program": "${workspaceFolder}/node_modules/jasmine-ts/lib/index",
|
||||
"args": ["${file}"],
|
||||
"env": {
|
||||
"JASMINE_CONFIG_PATH": "${workspaceFolder}/tests/jasmine.json"
|
||||
},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Unit Test",
|
||||
"program": "${workspaceFolder}/node_modules/jasmine-ts/lib/index.js",
|
||||
"args": [
|
||||
"--config=tests/jasmine.json"
|
||||
],
|
||||
"console": "internalConsole",
|
||||
"smartStep": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"git.ignoreLimitWarning": true,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.tabSize": 2,
|
||||
"prettier.singleQuote": true
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
# Lines starting with '#' are comments.
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
|
||||
* @codeglobally
|
|
@ -1,61 +0,0 @@
|
|||
# Coding Guidelines
|
||||
|
||||
We follow a modified form of the [StandardJS](https://standardjs.com/) style guide. Additions and modifications have been made on this styleguide that will be aparent from linter output and our existing repos.
|
||||
|
||||
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
|
||||
|
||||
## Indentation & Lines
|
||||
* We use spaces (2 to be exact), not tabs
|
||||
* Lines must not be over 160 characters
|
||||
* Lines must end in semicolons
|
||||
|
||||
## Names
|
||||
* Use PascalCase for `type` names
|
||||
* Use PascalCase for `enum` values
|
||||
* Use camelCase for `function` and `method` names
|
||||
* Use camelCase for `property` names and `local variables`
|
||||
* Use whole words in names when possible
|
||||
* Avoid prefixing names with underscore
|
||||
|
||||
## Types
|
||||
* Do not export `types` or `functions` unless you need to share it across multiple components
|
||||
* Do not introduce new `types` or `values` to the global namespace
|
||||
|
||||
## Comments
|
||||
* Use JSDoc style comments for `functions`, `interfaces`, `enums`, and `classes`
|
||||
* Favour clarity over brevity
|
||||
|
||||
## Strings
|
||||
* Use "double quotes" for strings shown to the user that need to be externalized (localized)
|
||||
* Use 'single quotes' otherwise
|
||||
* All strings visible to the user need to be externalized
|
||||
|
||||
## Style
|
||||
* Use arrow functions `=>` over anonymous function expressions
|
||||
* Only surround arrow function parameters when necessary. For example, `(x) => x + x` is wrong but the following are correct:
|
||||
``` javascript
|
||||
x => x + x
|
||||
(x,y) => x + y
|
||||
<T>(x: T, y: T) => x === y
|
||||
```
|
||||
* Always surround loop and conditional bodies with curly braces
|
||||
* Open curly braces always go on the same line as whatever necessitates them
|
||||
* Parenthesized constructs should have no surrounding whitespace. A single space follows commas, colons, and semicolons in those constructs. For example:
|
||||
``` javascript
|
||||
for (var i = 0, n = str.length; i < 10; i++) { }
|
||||
if (x < 10) { }
|
||||
function f(x: number, y: string): void { }
|
||||
```
|
||||
* Keys in objects must remain consistent, but prefer no quotes.
|
||||
```typescript
|
||||
{
|
||||
foo: 1,
|
||||
bar: 'string',
|
||||
baz: true
|
||||
}
|
||||
{
|
||||
'foo': 1,
|
||||
'bar': 'string',
|
||||
'baz-1': true
|
||||
}
|
||||
```
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,34 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.3"
|
||||
defaultConfig {
|
||||
applicationId "com.microsoft.did.sdk"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.1.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,24 @@
|
|||
package com.microsoft.did.sdk
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.microsoft.did.sdk", appContext.packageName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.microsoft.did.sdk">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme" />
|
||||
</manifest>
|
|
@ -1,45 +1,45 @@
|
|||
package com.microsoft.did.sdk
|
||||
|
||||
import com.microsoft.did.sdk.crypto.plugins.AndroidSubtle
|
||||
import android.content.Context
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.keyStore.AndroidKeyStore
|
||||
import com.microsoft.did.sdk.crypto.keyStore.IKeyStore
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.SubtleCrypto
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.W3cCryptoApiConstants
|
||||
import com.microsoft.did.sdk.crypto.plugins.EllipticCurveSubtleCrypto
|
||||
import com.microsoft.did.sdk.crypto.plugins.SubtleCryptoMapItem
|
||||
import com.microsoft.did.sdk.crypto.plugins.SubtleCryptoScope
|
||||
import com.microsoft.did.sdk.utilities.ConsoleLogger
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class Agent private constructor (
|
||||
registrationUrl: String = defaultRegistrationUrl,
|
||||
resolverUrl: String = defaultResolverUrl,
|
||||
cryptoOperations: CryptoOperations,
|
||||
logger: ILogger): AbstractAgent(
|
||||
registrationUrl = registrationUrl,
|
||||
resolverUrl = resolverUrl,
|
||||
signatureKeyReference = defaultSignatureKeyReference,
|
||||
encryptionKeyReference = defaultEncryptionKeyReference,
|
||||
cryptoOperations = cryptoOperations,
|
||||
logger = logger
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun getInstance(context: Context, logger: ILogger = ConsoleLogger()): Agent {
|
||||
val keyStore = AndroidKeyStore(context, logger)
|
||||
val subtleCrypto = AndroidSubtle(keyStore, logger)
|
||||
val crypto = CryptoOperations(subtleCrypto, keyStore, logger)
|
||||
val ecSubtle = EllipticCurveSubtleCrypto(subtleCrypto, logger)
|
||||
crypto.subtleCryptoFactory.addMessageSigner(W3cCryptoApiConstants.EcDsa.value,
|
||||
SubtleCryptoMapItem(ecSubtle, SubtleCryptoScope.All)
|
||||
);
|
||||
return Agent(
|
||||
registrationUrl = "https://beta.core.ion.msidentity.com/",
|
||||
cryptoOperations = crypto,
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.microsoft.did.sdk
|
||||
|
||||
import com.microsoft.did.sdk.crypto.plugins.AndroidSubtle
|
||||
import android.content.Context
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.keyStore.AndroidKeyStore
|
||||
import com.microsoft.did.sdk.crypto.keyStore.IKeyStore
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.SubtleCrypto
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.W3cCryptoApiConstants
|
||||
import com.microsoft.did.sdk.crypto.plugins.EllipticCurveSubtleCrypto
|
||||
import com.microsoft.did.sdk.crypto.plugins.SubtleCryptoMapItem
|
||||
import com.microsoft.did.sdk.crypto.plugins.SubtleCryptoScope
|
||||
import com.microsoft.did.sdk.utilities.ConsoleLogger
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class Agent private constructor (
|
||||
registrationUrl: String = defaultRegistrationUrl,
|
||||
resolverUrl: String = defaultResolverUrl,
|
||||
cryptoOperations: CryptoOperations,
|
||||
logger: ILogger): AbstractAgent(
|
||||
registrationUrl = registrationUrl,
|
||||
resolverUrl = resolverUrl,
|
||||
signatureKeyReference = defaultSignatureKeyReference,
|
||||
encryptionKeyReference = defaultEncryptionKeyReference,
|
||||
cryptoOperations = cryptoOperations,
|
||||
logger = logger
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun getInstance(context: Context, logger: ILogger = ConsoleLogger()): Agent {
|
||||
val keyStore = AndroidKeyStore(context, logger)
|
||||
val subtleCrypto = AndroidSubtle(keyStore, logger)
|
||||
val crypto = CryptoOperations(subtleCrypto, keyStore, logger)
|
||||
val ecSubtle = EllipticCurveSubtleCrypto(subtleCrypto, logger)
|
||||
crypto.subtleCryptoFactory.addMessageSigner(W3cCryptoApiConstants.EcDsa.value,
|
||||
SubtleCryptoMapItem(ecSubtle, SubtleCryptoScope.All)
|
||||
);
|
||||
return Agent(
|
||||
registrationUrl = "https://beta.core.ion.msidentity.com/",
|
||||
cryptoOperations = crypto,
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,378 +1,378 @@
|
|||
package com.microsoft.did.sdk.crypto.keyStore
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.security.keystore.KeyProtection
|
||||
import android.util.Base64
|
||||
import com.microsoft.did.sdk.crypto.keys.*
|
||||
import com.microsoft.did.sdk.crypto.keys.ellipticCurve.EllipticCurvePrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.ellipticCurve.EllipticCurvePublicKey
|
||||
import com.microsoft.did.sdk.crypto.keys.rsa.RsaPrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.rsa.RsaPublicKey
|
||||
import com.microsoft.did.sdk.crypto.models.KeyUse
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.JsonWebKey
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.KeyUsage
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.security.KeyStore
|
||||
import java.security.interfaces.ECPublicKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import com.microsoft.did.sdk.utilities.*
|
||||
import kotlinx.serialization.parse
|
||||
import javax.crypto.KeyGenerator
|
||||
|
||||
class AndroidKeyStore(private val context: Context, logger: ILogger): IKeyStore(logger) {
|
||||
|
||||
companion object {
|
||||
const val provider = "AndroidKeyStore"
|
||||
private val regexForKeyReference = Regex("^#(.*)\\.[^.]+$")
|
||||
private val regexForKeyIndex = Regex("^#.*\\.([^.]+$)")
|
||||
|
||||
val keyStore: KeyStore = KeyStore.getInstance(provider).apply {
|
||||
load(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSecretKey(keyReference: String): KeyContainer<SecretKey> {
|
||||
val softwareKeys = listSecureData()
|
||||
val key = softwareKeys[keyReference] ?: throw logger.error("Key $keyReference not found")
|
||||
if (key.kty != KeyType.Octets) {
|
||||
throw logger.error("Key $keyReference (type ${key.kty.value}) is not a secret.")
|
||||
}
|
||||
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map{
|
||||
getSecureSecretkey(it)!!
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPrivateKey(keyReference: String): KeyContainer<PrivateKey> {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var key = nativeKeys[keyReference]
|
||||
if (key != null) {
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map{
|
||||
AndroidKeyConverter.androidPrivateKeyToPrivateKey(it, keyStore, logger)
|
||||
}
|
||||
)
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
key = softwareKeys[keyReference]
|
||||
if (key != null) {
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map{
|
||||
getSecurePrivateKey(it)!!
|
||||
}
|
||||
)
|
||||
}
|
||||
throw logger.error("Key $keyReference not found")
|
||||
}
|
||||
|
||||
override fun getPublicKey(keyReference: String): KeyContainer<PublicKey> {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var key = nativeKeys[keyReference]
|
||||
if (key != null) {
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map {
|
||||
val entry = keyStore.getCertificate(it).publicKey
|
||||
?: throw logger.error("Key $it is not a private key.")
|
||||
AndroidKeyConverter.androidPublicKeyToPublicKey(it, entry, logger)
|
||||
}
|
||||
)
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
key = softwareKeys[keyReference]
|
||||
if (key != null) {
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map{
|
||||
getSecurePublicKey(it)!!
|
||||
}
|
||||
)
|
||||
}
|
||||
throw logger.error("Key $keyReference not found")
|
||||
}
|
||||
|
||||
override fun getSecretKeyById(keyId: String): SecretKey? {
|
||||
val keyRef = findReferenceInList(this.list(), keyId)
|
||||
return if (!keyRef.isNullOrBlank()) {
|
||||
getSecretKey(keyRef).keys.firstOrNull { it.kid == keyId }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPrivateKeyById(keyId: String): PrivateKey? {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var keyRef = findReferenceInList(nativeKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) { // This keyID exists within the android keystore
|
||||
return AndroidKeyConverter.androidPrivateKeyToPrivateKey(keyId, keyStore, logger)
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
keyRef = findReferenceInList(softwareKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) {
|
||||
return getSecurePrivateKey(keyId)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getPublicKeyById(keyId: String): PublicKey? {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var keyRef = findReferenceInList(nativeKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) { // This keyID exists within the android keystore
|
||||
val entry = keyStore.getCertificate(keyId).publicKey ?: throw logger.error("Key $keyId is not a private key.")
|
||||
return AndroidKeyConverter.androidPublicKeyToPublicKey(keyId, entry, logger)
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
keyRef = findReferenceInList(softwareKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) {
|
||||
return getSecurePublicKey(keyId)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public fun deletePrivateKey(keyId: String) {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var keyRef = findReferenceInList(nativeKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) {
|
||||
AndroidKeyStore.keyStore.deleteEntry(keyId)
|
||||
return
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
keyRef = findReferenceInList(softwareKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) {
|
||||
deleteSecureData(keyId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findReferenceInList(list: Map<String, KeyStoreListItem>, keyId: String): String? {
|
||||
return list.filter {
|
||||
it.value.kids.contains(keyId)
|
||||
}.entries.firstOrNull()?.key
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
override fun save(keyReference: String, key: SecretKey) {
|
||||
val alias = checkOrCreateKeyId(keyReference, key.kid)
|
||||
var jwk = key.toJWK();
|
||||
jwk.kid = alias;
|
||||
val jwkString = MinimalJson.serializer.stringify(JsonWebKey.serializer(), jwk)
|
||||
val keyValue = stringToByteArray(jwkString)
|
||||
saveSecureData(alias, keyValue)
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
override fun save(keyReference: String, key: PrivateKey) {
|
||||
val alias = checkOrCreateKeyId(keyReference, key.kid)
|
||||
if (keyStore.containsAlias(alias)) {
|
||||
// do nothing, the key is already there.
|
||||
return
|
||||
}
|
||||
// This key is not natively supported
|
||||
var jwk = key.toJWK();
|
||||
jwk.kid = alias;
|
||||
val jwkString = MinimalJson.serializer.stringify(JsonWebKey.serializer(), jwk)
|
||||
val keyValue = stringToByteArray(jwkString)
|
||||
saveSecureData(alias, keyValue)
|
||||
}
|
||||
|
||||
override fun save(keyReference: String, key: PublicKey) {
|
||||
val alias = checkOrCreateKeyId(keyReference, key.kid)
|
||||
if (keyStore.containsAlias(alias)) {
|
||||
// do nothing, the key is already there.
|
||||
return
|
||||
}
|
||||
throw logger.error("Why are you even saving a public key; this makes no sense. Rethink your life.")
|
||||
}
|
||||
|
||||
override fun list(): Map<String, KeyStoreListItem> {
|
||||
val nativeList = listNativeKeys()
|
||||
val softwareList = listSecureData()
|
||||
return com.microsoft.did.sdk.utilities.Map.join(softwareList, nativeList)
|
||||
}
|
||||
|
||||
|
||||
private fun listNativeKeys(): Map<String, KeyStoreListItem> {
|
||||
val output = emptyMap<String, KeyStoreListItem>().toMutableMap()
|
||||
val aliases = keyStore.aliases()
|
||||
// KeyRef (as key reference) -> KeyRef.VersionNumber (as key identifier)
|
||||
for (alias in aliases) {
|
||||
if (alias.matches(regexForKeyReference)) {
|
||||
val entry = keyStore.getCertificate(alias)
|
||||
val matches = regexForKeyReference.matchEntire(alias)
|
||||
val values = matches!!.groupValues
|
||||
|
||||
// Get the keyType associated with this key.
|
||||
val kty: KeyType = AndroidKeyConverter.whatKeyTypeIs(entry.publicKey, logger)
|
||||
|
||||
// Add the key to an ListItem or make a new one
|
||||
if (output.containsKey(values[1])) {
|
||||
val listItem = output[values[1]]!!
|
||||
if (listItem.kty != kty) {
|
||||
throw logger.error("Key Container ${values[1]} contains keys of two different " +
|
||||
"types (${listItem.kty.value}, ${kty.value})")
|
||||
}
|
||||
listItem.kids.add(alias)
|
||||
} else {
|
||||
output[values[1]] = KeyStoreListItem(kty, mutableListOf(alias))
|
||||
}
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private fun listSecureData(): Map<String, KeyStoreListItem> {
|
||||
val sharedPreferences = getSharedPreferences();
|
||||
val keys = sharedPreferences.all.keys;
|
||||
// all stored keys should be in JWT format
|
||||
val keyMap = mutableMapOf<String, KeyStoreListItem>()
|
||||
keys.forEach{
|
||||
// verify that it matches the regex and grab the key reference
|
||||
val keyReferenceMatch = AndroidKeyStore.regexForKeyReference.matchEntire(it)
|
||||
if (keyReferenceMatch != null) {
|
||||
val keyRef = keyReferenceMatch.groupValues[1];
|
||||
val jwkBase64 = sharedPreferences.getString(it, null)!!
|
||||
val jwkData = Base64.decode(jwkBase64, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
val key = MinimalJson.serializer.parse(JsonWebKey.serializer(), byteArrayToString(jwkData))
|
||||
val keyType = toKeyType(key.kty, logger)
|
||||
if (!keyMap.containsKey(keyRef)) {
|
||||
keyMap[keyRef] = KeyStoreListItem(keyType, mutableListOf(it))
|
||||
} else {
|
||||
val listItem = keyMap[keyRef]!!
|
||||
if (keyType != listItem.kty) {
|
||||
throw logger.error("Key $keyRef has two different key types (${keyType.value}, ${listItem.kty.value})")
|
||||
}
|
||||
listItem.kids.add(it)
|
||||
keyMap[keyRef] = listItem
|
||||
}
|
||||
}
|
||||
}
|
||||
return keyMap
|
||||
}
|
||||
|
||||
private fun getSecurePublicKey(alias: String): PublicKey? {
|
||||
return getSecurePrivateKey(alias)?.getPublicKey()
|
||||
}
|
||||
|
||||
private fun getSecurePrivateKey(alias: String): PrivateKey? {
|
||||
val data = getSecureData(alias) ?: return null
|
||||
val jwk = MinimalJson.serializer.parse(JsonWebKey.serializer(), byteArrayToString(data))
|
||||
if (jwk.kty == KeyType.RSA.value) {
|
||||
return RsaPrivateKey(jwk, logger = logger)
|
||||
} else if (jwk.kty == KeyType.EllipticCurve.value) {
|
||||
return EllipticCurvePrivateKey(jwk, logger = logger)
|
||||
} else {
|
||||
throw logger.error("Unknown key type ${jwk.kty}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSecureSecretkey(alias: String): SecretKey? {
|
||||
val data = getSecureData(alias) ?: return null
|
||||
val jwk = MinimalJson.serializer.parse(JsonWebKey.serializer(), byteArrayToString(data))
|
||||
if (jwk.kty != KeyType.Octets.value) {
|
||||
throw logger.error("$alias is not a secret key.")
|
||||
}
|
||||
return SecretKey(jwk, logger)
|
||||
}
|
||||
|
||||
private fun getSecureData(alias: String): ByteArray? {
|
||||
val sharedPreferences = getSharedPreferences();
|
||||
val base64UrlEncodedData = sharedPreferences.getString(alias, null)
|
||||
if (base64UrlEncodedData != null) {
|
||||
return Base64.decode(base64UrlEncodedData, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun deleteSecureData(alias: String) {
|
||||
val sharedPreferences = getSharedPreferences()
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.remove(alias)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun saveSecureData(alias: String, data: ByteArray) {
|
||||
val sharedPreferences = getSharedPreferences();
|
||||
val editor = sharedPreferences.edit();
|
||||
editor.putString(alias, Base64.encodeToString(data, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP));
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun getSharedPreferences(): SharedPreferences {
|
||||
val masterKeyAlias = getSecretVaultMasterKey()
|
||||
return EncryptedSharedPreferences.create(
|
||||
"secret_shared_prefs",
|
||||
masterKeyAlias,
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
private fun getSecretVaultMasterKey(): String {
|
||||
val alias = "ms-useragent-secret-masterkey"
|
||||
|
||||
if (!keyStore.containsAlias(alias)) {
|
||||
// Generate the master key
|
||||
val generator = KeyGenerator.getInstance("AES", provider)
|
||||
generator.init(KeyGenParameterSpec.Builder(
|
||||
alias,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
||||
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setKeySize(256)
|
||||
.build())
|
||||
generator.generateKey();
|
||||
}
|
||||
|
||||
return alias
|
||||
}
|
||||
|
||||
fun checkOrCreateKeyId(keyReference: String, kid: String?): String {
|
||||
if (!kid.isNullOrBlank() && !kid.startsWith(keyReference) && !kid.startsWith("#$keyReference")) {
|
||||
throw logger.error("Key ID must begin with key reference")
|
||||
// This could be relaxed later if we flush keys and use a format of
|
||||
// KEYREFERENCE.KEYID and ensure KEYID does not contain the dot delimiter
|
||||
}
|
||||
return if (!kid.isNullOrBlank()) {
|
||||
logger.debug("Using key $kid")
|
||||
kid
|
||||
} else {
|
||||
// generate a key id
|
||||
val listItem = this.list()[keyReference]
|
||||
if (listItem == null) { // no previous keys
|
||||
logger.debug("New key reference #$keyReference.1")
|
||||
"#$keyReference.1"
|
||||
} else {
|
||||
// heuristic, find the last digit and count up
|
||||
var latestVersion = listItem.kids.reduce {
|
||||
acc: String, current: String ->
|
||||
val currentValue = regexForKeyIndex.matchEntire(current)?.groupValues?.get(1)?.toInt()
|
||||
val accValue = acc.toIntOrNull()
|
||||
if (currentValue != null && accValue == null) {
|
||||
current
|
||||
} else if (currentValue != null && accValue != null && currentValue > accValue) {
|
||||
current
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
}.toIntOrNull() ?: 0
|
||||
|
||||
latestVersion++
|
||||
logger.debug("New key reference #$keyReference.$latestVersion")
|
||||
"#$keyReference.$latestVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package com.microsoft.did.sdk.crypto.keyStore
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.security.keystore.KeyProtection
|
||||
import android.util.Base64
|
||||
import com.microsoft.did.sdk.crypto.keys.*
|
||||
import com.microsoft.did.sdk.crypto.keys.ellipticCurve.EllipticCurvePrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.ellipticCurve.EllipticCurvePublicKey
|
||||
import com.microsoft.did.sdk.crypto.keys.rsa.RsaPrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.rsa.RsaPublicKey
|
||||
import com.microsoft.did.sdk.crypto.models.KeyUse
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.JsonWebKey
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.KeyUsage
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.security.KeyStore
|
||||
import java.security.interfaces.ECPublicKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import com.microsoft.did.sdk.utilities.*
|
||||
import kotlinx.serialization.parse
|
||||
import javax.crypto.KeyGenerator
|
||||
|
||||
class AndroidKeyStore(private val context: Context, logger: ILogger): IKeyStore(logger) {
|
||||
|
||||
companion object {
|
||||
const val provider = "AndroidKeyStore"
|
||||
private val regexForKeyReference = Regex("^#(.*)\\.[^.]+$")
|
||||
private val regexForKeyIndex = Regex("^#.*\\.([^.]+$)")
|
||||
|
||||
val keyStore: KeyStore = KeyStore.getInstance(provider).apply {
|
||||
load(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSecretKey(keyReference: String): KeyContainer<SecretKey> {
|
||||
val softwareKeys = listSecureData()
|
||||
val key = softwareKeys[keyReference] ?: throw logger.error("Key $keyReference not found")
|
||||
if (key.kty != KeyType.Octets) {
|
||||
throw logger.error("Key $keyReference (type ${key.kty.value}) is not a secret.")
|
||||
}
|
||||
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map{
|
||||
getSecureSecretkey(it)!!
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPrivateKey(keyReference: String): KeyContainer<PrivateKey> {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var key = nativeKeys[keyReference]
|
||||
if (key != null) {
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map{
|
||||
AndroidKeyConverter.androidPrivateKeyToPrivateKey(it, keyStore, logger)
|
||||
}
|
||||
)
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
key = softwareKeys[keyReference]
|
||||
if (key != null) {
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map{
|
||||
getSecurePrivateKey(it)!!
|
||||
}
|
||||
)
|
||||
}
|
||||
throw logger.error("Key $keyReference not found")
|
||||
}
|
||||
|
||||
override fun getPublicKey(keyReference: String): KeyContainer<PublicKey> {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var key = nativeKeys[keyReference]
|
||||
if (key != null) {
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map {
|
||||
val entry = keyStore.getCertificate(it).publicKey
|
||||
?: throw logger.error("Key $it is not a private key.")
|
||||
AndroidKeyConverter.androidPublicKeyToPublicKey(it, entry, logger)
|
||||
}
|
||||
)
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
key = softwareKeys[keyReference]
|
||||
if (key != null) {
|
||||
return KeyContainer(
|
||||
kty = key.kty,
|
||||
keys = key.kids.map{
|
||||
getSecurePublicKey(it)!!
|
||||
}
|
||||
)
|
||||
}
|
||||
throw logger.error("Key $keyReference not found")
|
||||
}
|
||||
|
||||
override fun getSecretKeyById(keyId: String): SecretKey? {
|
||||
val keyRef = findReferenceInList(this.list(), keyId)
|
||||
return if (!keyRef.isNullOrBlank()) {
|
||||
getSecretKey(keyRef).keys.firstOrNull { it.kid == keyId }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPrivateKeyById(keyId: String): PrivateKey? {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var keyRef = findReferenceInList(nativeKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) { // This keyID exists within the android keystore
|
||||
return AndroidKeyConverter.androidPrivateKeyToPrivateKey(keyId, keyStore, logger)
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
keyRef = findReferenceInList(softwareKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) {
|
||||
return getSecurePrivateKey(keyId)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getPublicKeyById(keyId: String): PublicKey? {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var keyRef = findReferenceInList(nativeKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) { // This keyID exists within the android keystore
|
||||
val entry = keyStore.getCertificate(keyId).publicKey ?: throw logger.error("Key $keyId is not a private key.")
|
||||
return AndroidKeyConverter.androidPublicKeyToPublicKey(keyId, entry, logger)
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
keyRef = findReferenceInList(softwareKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) {
|
||||
return getSecurePublicKey(keyId)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public fun deletePrivateKey(keyId: String) {
|
||||
val nativeKeys = listNativeKeys()
|
||||
var keyRef = findReferenceInList(nativeKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) {
|
||||
AndroidKeyStore.keyStore.deleteEntry(keyId)
|
||||
return
|
||||
}
|
||||
val softwareKeys = listSecureData()
|
||||
keyRef = findReferenceInList(softwareKeys, keyId)
|
||||
if (!keyRef.isNullOrBlank()) {
|
||||
deleteSecureData(keyId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findReferenceInList(list: Map<String, KeyStoreListItem>, keyId: String): String? {
|
||||
return list.filter {
|
||||
it.value.kids.contains(keyId)
|
||||
}.entries.firstOrNull()?.key
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
override fun save(keyReference: String, key: SecretKey) {
|
||||
val alias = checkOrCreateKeyId(keyReference, key.kid)
|
||||
var jwk = key.toJWK();
|
||||
jwk.kid = alias;
|
||||
val jwkString = MinimalJson.serializer.stringify(JsonWebKey.serializer(), jwk)
|
||||
val keyValue = stringToByteArray(jwkString)
|
||||
saveSecureData(alias, keyValue)
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
override fun save(keyReference: String, key: PrivateKey) {
|
||||
val alias = checkOrCreateKeyId(keyReference, key.kid)
|
||||
if (keyStore.containsAlias(alias)) {
|
||||
// do nothing, the key is already there.
|
||||
return
|
||||
}
|
||||
// This key is not natively supported
|
||||
var jwk = key.toJWK();
|
||||
jwk.kid = alias;
|
||||
val jwkString = MinimalJson.serializer.stringify(JsonWebKey.serializer(), jwk)
|
||||
val keyValue = stringToByteArray(jwkString)
|
||||
saveSecureData(alias, keyValue)
|
||||
}
|
||||
|
||||
override fun save(keyReference: String, key: PublicKey) {
|
||||
val alias = checkOrCreateKeyId(keyReference, key.kid)
|
||||
if (keyStore.containsAlias(alias)) {
|
||||
// do nothing, the key is already there.
|
||||
return
|
||||
}
|
||||
throw logger.error("Why are you even saving a public key; this makes no sense. Rethink your life.")
|
||||
}
|
||||
|
||||
override fun list(): Map<String, KeyStoreListItem> {
|
||||
val nativeList = listNativeKeys()
|
||||
val softwareList = listSecureData()
|
||||
return com.microsoft.did.sdk.utilities.Map.join(softwareList, nativeList)
|
||||
}
|
||||
|
||||
|
||||
private fun listNativeKeys(): Map<String, KeyStoreListItem> {
|
||||
val output = emptyMap<String, KeyStoreListItem>().toMutableMap()
|
||||
val aliases = keyStore.aliases()
|
||||
// KeyRef (as key reference) -> KeyRef.VersionNumber (as key identifier)
|
||||
for (alias in aliases) {
|
||||
if (alias.matches(regexForKeyReference)) {
|
||||
val entry = keyStore.getCertificate(alias)
|
||||
val matches = regexForKeyReference.matchEntire(alias)
|
||||
val values = matches!!.groupValues
|
||||
|
||||
// Get the keyType associated with this key.
|
||||
val kty: KeyType = AndroidKeyConverter.whatKeyTypeIs(entry.publicKey, logger)
|
||||
|
||||
// Add the key to an ListItem or make a new one
|
||||
if (output.containsKey(values[1])) {
|
||||
val listItem = output[values[1]]!!
|
||||
if (listItem.kty != kty) {
|
||||
throw logger.error("Key Container ${values[1]} contains keys of two different " +
|
||||
"types (${listItem.kty.value}, ${kty.value})")
|
||||
}
|
||||
listItem.kids.add(alias)
|
||||
} else {
|
||||
output[values[1]] = KeyStoreListItem(kty, mutableListOf(alias))
|
||||
}
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private fun listSecureData(): Map<String, KeyStoreListItem> {
|
||||
val sharedPreferences = getSharedPreferences();
|
||||
val keys = sharedPreferences.all.keys;
|
||||
// all stored keys should be in JWT format
|
||||
val keyMap = mutableMapOf<String, KeyStoreListItem>()
|
||||
keys.forEach{
|
||||
// verify that it matches the regex and grab the key reference
|
||||
val keyReferenceMatch = AndroidKeyStore.regexForKeyReference.matchEntire(it)
|
||||
if (keyReferenceMatch != null) {
|
||||
val keyRef = keyReferenceMatch.groupValues[1];
|
||||
val jwkBase64 = sharedPreferences.getString(it, null)!!
|
||||
val jwkData = Base64.decode(jwkBase64, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
val key = MinimalJson.serializer.parse(JsonWebKey.serializer(), byteArrayToString(jwkData))
|
||||
val keyType = toKeyType(key.kty, logger)
|
||||
if (!keyMap.containsKey(keyRef)) {
|
||||
keyMap[keyRef] = KeyStoreListItem(keyType, mutableListOf(it))
|
||||
} else {
|
||||
val listItem = keyMap[keyRef]!!
|
||||
if (keyType != listItem.kty) {
|
||||
throw logger.error("Key $keyRef has two different key types (${keyType.value}, ${listItem.kty.value})")
|
||||
}
|
||||
listItem.kids.add(it)
|
||||
keyMap[keyRef] = listItem
|
||||
}
|
||||
}
|
||||
}
|
||||
return keyMap
|
||||
}
|
||||
|
||||
private fun getSecurePublicKey(alias: String): PublicKey? {
|
||||
return getSecurePrivateKey(alias)?.getPublicKey()
|
||||
}
|
||||
|
||||
private fun getSecurePrivateKey(alias: String): PrivateKey? {
|
||||
val data = getSecureData(alias) ?: return null
|
||||
val jwk = MinimalJson.serializer.parse(JsonWebKey.serializer(), byteArrayToString(data))
|
||||
if (jwk.kty == KeyType.RSA.value) {
|
||||
return RsaPrivateKey(jwk, logger = logger)
|
||||
} else if (jwk.kty == KeyType.EllipticCurve.value) {
|
||||
return EllipticCurvePrivateKey(jwk, logger = logger)
|
||||
} else {
|
||||
throw logger.error("Unknown key type ${jwk.kty}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSecureSecretkey(alias: String): SecretKey? {
|
||||
val data = getSecureData(alias) ?: return null
|
||||
val jwk = MinimalJson.serializer.parse(JsonWebKey.serializer(), byteArrayToString(data))
|
||||
if (jwk.kty != KeyType.Octets.value) {
|
||||
throw logger.error("$alias is not a secret key.")
|
||||
}
|
||||
return SecretKey(jwk, logger)
|
||||
}
|
||||
|
||||
private fun getSecureData(alias: String): ByteArray? {
|
||||
val sharedPreferences = getSharedPreferences();
|
||||
val base64UrlEncodedData = sharedPreferences.getString(alias, null)
|
||||
if (base64UrlEncodedData != null) {
|
||||
return Base64.decode(base64UrlEncodedData, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun deleteSecureData(alias: String) {
|
||||
val sharedPreferences = getSharedPreferences()
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.remove(alias)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun saveSecureData(alias: String, data: ByteArray) {
|
||||
val sharedPreferences = getSharedPreferences();
|
||||
val editor = sharedPreferences.edit();
|
||||
editor.putString(alias, Base64.encodeToString(data, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP));
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun getSharedPreferences(): SharedPreferences {
|
||||
val masterKeyAlias = getSecretVaultMasterKey()
|
||||
return EncryptedSharedPreferences.create(
|
||||
"secret_shared_prefs",
|
||||
masterKeyAlias,
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
private fun getSecretVaultMasterKey(): String {
|
||||
val alias = "ms-useragent-secret-masterkey"
|
||||
|
||||
if (!keyStore.containsAlias(alias)) {
|
||||
// Generate the master key
|
||||
val generator = KeyGenerator.getInstance("AES", provider)
|
||||
generator.init(KeyGenParameterSpec.Builder(
|
||||
alias,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
||||
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setKeySize(256)
|
||||
.build())
|
||||
generator.generateKey();
|
||||
}
|
||||
|
||||
return alias
|
||||
}
|
||||
|
||||
fun checkOrCreateKeyId(keyReference: String, kid: String?): String {
|
||||
if (!kid.isNullOrBlank() && !kid.startsWith(keyReference) && !kid.startsWith("#$keyReference")) {
|
||||
throw logger.error("Key ID must begin with key reference")
|
||||
// This could be relaxed later if we flush keys and use a format of
|
||||
// KEYREFERENCE.KEYID and ensure KEYID does not contain the dot delimiter
|
||||
}
|
||||
return if (!kid.isNullOrBlank()) {
|
||||
logger.debug("Using key $kid")
|
||||
kid
|
||||
} else {
|
||||
// generate a key id
|
||||
val listItem = this.list()[keyReference]
|
||||
if (listItem == null) { // no previous keys
|
||||
logger.debug("New key reference #$keyReference.1")
|
||||
"#$keyReference.1"
|
||||
} else {
|
||||
// heuristic, find the last digit and count up
|
||||
var latestVersion = listItem.kids.reduce {
|
||||
acc: String, current: String ->
|
||||
val currentValue = regexForKeyIndex.matchEntire(current)?.groupValues?.get(1)?.toInt()
|
||||
val accValue = acc.toIntOrNull()
|
||||
if (currentValue != null && accValue == null) {
|
||||
current
|
||||
} else if (currentValue != null && accValue != null && currentValue > accValue) {
|
||||
current
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
}.toIntOrNull() ?: 0
|
||||
|
||||
latestVersion++
|
||||
logger.debug("New key reference #$keyReference.$latestVersion")
|
||||
"#$keyReference.$latestVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package com.microsoft.did.sdk.crypto.keys
|
||||
|
||||
data class AndroidKeyHandle(val alias: String, val key: Any?)
|
|
@ -1,34 +1,34 @@
|
|||
package com.microsoft.did.sdk.crypto.keys
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.spec.ECFieldFp
|
||||
import java.security.spec.ECParameterSpec
|
||||
import java.security.spec.ECPoint
|
||||
import java.security.spec.EllipticCurve
|
||||
|
||||
// We'll come back to this later but this is for native curves. It may not be necessary.
|
||||
enum class EllipticCurves(spec: ECParameterSpec) {
|
||||
secp256k1(ECParameterSpec(
|
||||
EllipticCurve(
|
||||
ECFieldFp(
|
||||
BigInteger(
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" +
|
||||
"FFFFFC2F", 16
|
||||
)
|
||||
),
|
||||
BigInteger("00000000000000000000000000000000000000000000000000000000" +
|
||||
"00000000", 16),
|
||||
BigInteger("00000000000000000000000000000000000000000000000000000000" +
|
||||
"00000007", 16)
|
||||
),
|
||||
ECPoint(
|
||||
BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0" +
|
||||
"F4A13945D898C296", 16),
|
||||
BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE3357" +
|
||||
"6B315ECECBB6406837BF51F5", 16)
|
||||
),
|
||||
BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2" +
|
||||
"FC632551", 16),
|
||||
0x01
|
||||
))
|
||||
package com.microsoft.did.sdk.crypto.keys
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.spec.ECFieldFp
|
||||
import java.security.spec.ECParameterSpec
|
||||
import java.security.spec.ECPoint
|
||||
import java.security.spec.EllipticCurve
|
||||
|
||||
// We'll come back to this later but this is for native curves. It may not be necessary.
|
||||
enum class EllipticCurves(spec: ECParameterSpec) {
|
||||
secp256k1(ECParameterSpec(
|
||||
EllipticCurve(
|
||||
ECFieldFp(
|
||||
BigInteger(
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" +
|
||||
"FFFFFC2F", 16
|
||||
)
|
||||
),
|
||||
BigInteger("00000000000000000000000000000000000000000000000000000000" +
|
||||
"00000000", 16),
|
||||
BigInteger("00000000000000000000000000000000000000000000000000000000" +
|
||||
"00000007", 16)
|
||||
),
|
||||
ECPoint(
|
||||
BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0" +
|
||||
"F4A13945D898C296", 16),
|
||||
BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE3357" +
|
||||
"6B315ECECBB6406837BF51F5", 16)
|
||||
),
|
||||
BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2" +
|
||||
"FC632551", 16),
|
||||
0x01
|
||||
))
|
||||
}
|
|
@ -1,28 +1,28 @@
|
|||
package com.microsoft.did.sdk.crypto.models
|
||||
|
||||
enum class AndroidConstants(val value: String) {
|
||||
// key factory algorithm names from https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyFactory
|
||||
Rsa("RSA"),
|
||||
Ec("EC"),
|
||||
Aes("AES"),
|
||||
AesWrap("AESWRAP"),
|
||||
// Signature algorithm names from https://developer.android.com/training/articles/keystore.html#SupportedSignatures
|
||||
EcDsaSha1("SHA1withECDSA"),
|
||||
EcDsaSha224("SHA224withECDSA"),
|
||||
EcDsaSha256("SHA256withECDSA"),
|
||||
EcDsaSha384("SHA384withECDSA"),
|
||||
EcDsaSha512("SHA512withECDSA"),
|
||||
RsSha1("SHA1withRSA"),
|
||||
RsSha224("SHA224withRSA"),
|
||||
RsSha256("SHA256withRSA"),
|
||||
RsSha384("SHA384withRSA"),
|
||||
RsSha512("SHA512withRSA"),
|
||||
|
||||
|
||||
|
||||
|
||||
//////////////////////
|
||||
// Custom Constants //
|
||||
//////////////////////
|
||||
KeyReference("KeyReference")
|
||||
package com.microsoft.did.sdk.crypto.models
|
||||
|
||||
enum class AndroidConstants(val value: String) {
|
||||
// key factory algorithm names from https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyFactory
|
||||
Rsa("RSA"),
|
||||
Ec("EC"),
|
||||
Aes("AES"),
|
||||
AesWrap("AESWRAP"),
|
||||
// Signature algorithm names from https://developer.android.com/training/articles/keystore.html#SupportedSignatures
|
||||
EcDsaSha1("SHA1withECDSA"),
|
||||
EcDsaSha224("SHA224withECDSA"),
|
||||
EcDsaSha256("SHA256withECDSA"),
|
||||
EcDsaSha384("SHA384withECDSA"),
|
||||
EcDsaSha512("SHA512withECDSA"),
|
||||
RsSha1("SHA1withRSA"),
|
||||
RsSha224("SHA224withRSA"),
|
||||
RsSha256("SHA256withRSA"),
|
||||
RsSha384("SHA384withRSA"),
|
||||
RsSha512("SHA512withRSA"),
|
||||
|
||||
|
||||
|
||||
|
||||
//////////////////////
|
||||
// Custom Constants //
|
||||
//////////////////////
|
||||
KeyReference("KeyReference")
|
||||
}
|
|
@ -1,295 +1,295 @@
|
|||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import com.microsoft.did.sdk.crypto.models.AndroidConstants
|
||||
import com.microsoft.did.sdk.crypto.keyStore.AndroidKeyStore
|
||||
import com.microsoft.did.sdk.crypto.keys.AndroidKeyHandle
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.*
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithms.AesKeyGenParams
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.JwaCryptoConverter
|
||||
import com.microsoft.did.sdk.utilities.AndroidKeyConverter
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.security.spec.*
|
||||
import javax.crypto.KeyGenerator
|
||||
|
||||
class AndroidSubtle(private var keyStore: AndroidKeyStore, private val logger: ILogger): SubtleCrypto {
|
||||
override fun encrypt(algorithm: Algorithm, key: CryptoKey, data: ByteArray): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun decrypt(algorithm: Algorithm, key: CryptoKey, data: ByteArray): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun sign(algorithm: Algorithm, key: CryptoKey, data: ByteArray): ByteArray {
|
||||
// verify we're signing with a private key
|
||||
if (key.type != KeyType.Private) {
|
||||
throw logger.error("Sign must use a private key")
|
||||
}
|
||||
// key's handle should be an Android keyStore key reference.
|
||||
val handle = cryptoKeyToPrivateKey(key)
|
||||
return Signature.getInstance(signAlgorithmToAndroid(algorithm, key)).run {
|
||||
initSign(handle)
|
||||
update(data)
|
||||
sign()
|
||||
}
|
||||
}
|
||||
|
||||
override fun verify(algorithm: Algorithm, key: CryptoKey, signature: ByteArray, data: ByteArray): Boolean {
|
||||
val handle = cryptoKeyToPublicKey(key)
|
||||
val s = Signature.getInstance(signAlgorithmToAndroid(algorithm, key)).apply {
|
||||
initVerify(handle)
|
||||
update(data)
|
||||
}
|
||||
return s.verify(signature)
|
||||
}
|
||||
|
||||
override fun digest(algorithm: Algorithm, data: ByteArray): ByteArray {
|
||||
val digest = MessageDigest.getInstance(algorithm.name)
|
||||
return digest.digest(data)
|
||||
}
|
||||
|
||||
override fun generateKey(algorithm: Algorithm, extractable: Boolean, keyUsages: List<KeyUsage>): CryptoKey {
|
||||
val secret = when(algorithm.name) {
|
||||
W3cCryptoApiConstants.AesCbc.value, W3cCryptoApiConstants.AesCtr.value,
|
||||
W3cCryptoApiConstants.AesGcm.value, W3cCryptoApiConstants.AesKw.value -> {
|
||||
val generator = KeyGenerator.getInstance(AndroidConstants.Aes.value)
|
||||
val alg = algorithm as AesKeyGenParams
|
||||
generator.init(alg.length.toInt())
|
||||
generator.generateKey()
|
||||
}
|
||||
else -> throw logger.error("Unsupported symmetric key algorithm: ${algorithm.name}")
|
||||
}
|
||||
return CryptoKey(
|
||||
type = KeyType.Secret,
|
||||
extractable = extractable,
|
||||
usages = keyUsages,
|
||||
handle = secret,
|
||||
algorithm = algorithm
|
||||
)
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
override fun generateKeyPair(algorithm: Algorithm, extractable: Boolean, keyUsages: List<KeyUsage>): CryptoKeyPair {
|
||||
if (!algorithm.additionalParams.containsKey(AndroidConstants.KeyReference.value)){
|
||||
throw logger.error("Algorithm must contain an additional parameter \"${AndroidConstants.KeyReference.value}\"")
|
||||
}
|
||||
val alias = keyStore.checkOrCreateKeyId(algorithm.additionalParams[AndroidConstants.KeyReference.value] as String, null)
|
||||
logger.debug("Generating ${algorithm.name} key with alias $alias")
|
||||
val keyPairGenerator = KeyPairGenerator.getInstance(keyPairAlgorithmToAndroid(algorithm), AndroidKeyStore.provider)
|
||||
keyPairGenerator.initialize(
|
||||
KeyGenParameterSpec.Builder(
|
||||
alias,
|
||||
keyPairUsageToAndroid(keyUsages)
|
||||
).setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
|
||||
.build()
|
||||
)
|
||||
val keyPair = keyPairGenerator.genKeyPair()
|
||||
// AndroidKeyStore.keyStore.
|
||||
logger.debug("Key pair generated ($alias)")
|
||||
// convert keypair.
|
||||
return CryptoKeyPair(
|
||||
CryptoKey(
|
||||
KeyType.Public,
|
||||
extractable,
|
||||
algorithm,
|
||||
keyUsages,
|
||||
AndroidKeyHandle(
|
||||
alias,
|
||||
keyPair.public
|
||||
)
|
||||
),
|
||||
CryptoKey(
|
||||
KeyType.Private,
|
||||
false,
|
||||
algorithm,
|
||||
keyUsages,
|
||||
AndroidKeyHandle(
|
||||
alias,
|
||||
null
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun deriveKey(
|
||||
algorithm: Algorithm,
|
||||
baseKey: CryptoKey,
|
||||
derivedKeyType: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: List<KeyUsage>
|
||||
): CryptoKey {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun deriveBits(algorithm: Algorithm, baseKey: CryptoKey, length: ULong): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun importKey(
|
||||
format: KeyFormat,
|
||||
keyData: ByteArray,
|
||||
algorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: List<KeyUsage>
|
||||
): CryptoKey {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun importKey(
|
||||
format: KeyFormat,
|
||||
keyData: JsonWebKey,
|
||||
algorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: List<KeyUsage>
|
||||
): CryptoKey {
|
||||
when (keyData.kty) {
|
||||
com.microsoft.did.sdk.crypto.keys.KeyType.RSA.value -> {
|
||||
val keyFactory = KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_RSA)
|
||||
if (keyData.d != null) { // Private RSA key being imported
|
||||
if (!AndroidKeyStore.keyStore.isKeyEntry(keyData.kid ?: "")) {
|
||||
throw logger.error("Software private keys are not supported.")
|
||||
}
|
||||
val entry = AndroidKeyHandle(keyData.kid!!, null)
|
||||
return CryptoKey(
|
||||
KeyType.Private,
|
||||
extractable,
|
||||
JwaCryptoConverter.jwaAlgToWebCrypto(keyData.alg!!, logger = logger),
|
||||
keyUsages,
|
||||
entry
|
||||
)
|
||||
} else { // Public RSA key being imported
|
||||
val key = keyFactory.generatePublic(RSAPublicKeySpec(
|
||||
BigInteger(1, Base64.decode(keyData.n, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)),
|
||||
BigInteger(1, Base64.decode(keyData.e, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP))
|
||||
))
|
||||
val entry = AndroidKeyHandle(keyData.kid ?: "", key)
|
||||
return CryptoKey(
|
||||
KeyType.Public,
|
||||
extractable,
|
||||
JwaCryptoConverter.jwaAlgToWebCrypto(keyData.alg!!, logger = logger),
|
||||
keyUsages,
|
||||
entry
|
||||
)
|
||||
}
|
||||
}
|
||||
com.microsoft.did.sdk.crypto.keys.KeyType.EllipticCurve.value -> {
|
||||
TODO("Standard Elliptic Curves are not currently supported.")
|
||||
}
|
||||
else -> throw logger.error("Cannot import JWK key type ${keyData.kty}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun exportKey(format: KeyFormat, key: CryptoKey): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun exportKeyJwk(key: CryptoKey): JsonWebKey {
|
||||
val internalHandle = key.handle as? AndroidKeyHandle ?: throw logger.error("Unknown format for CryptoKey passed")
|
||||
return when (internalHandle.key) {
|
||||
is PublicKey -> {
|
||||
AndroidKeyConverter.androidPublicKeyToPublicKey(internalHandle.alias, internalHandle.key, logger).toJWK()
|
||||
}
|
||||
null -> {
|
||||
AndroidKeyConverter.androidPrivateKeyToPrivateKey(internalHandle.alias, AndroidKeyStore.keyStore, logger).toJWK()
|
||||
}
|
||||
else -> {
|
||||
throw logger.error("Unknown CryptoKey format")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun wrapKey(
|
||||
format: KeyFormat,
|
||||
key: CryptoKey,
|
||||
wrappingKey: CryptoKey,
|
||||
wrapAlgorithm: Algorithm
|
||||
): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun unwrapKey(
|
||||
format: KeyFormat,
|
||||
wrappedKey: ByteArray,
|
||||
unwrappingKey: CryptoKey,
|
||||
unwrapAlgorithm: Algorithm,
|
||||
unwrappedKeyAlgorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: List<KeyUsage>
|
||||
): CryptoKey {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
private fun cryptoKeyToPublicKey(key: CryptoKey): PublicKey {
|
||||
val internalHandle = key.handle as? AndroidKeyHandle
|
||||
?: throw logger.error("Unknown format for CryptoKey passed")
|
||||
return internalHandle.key as? PublicKey
|
||||
?: throw logger.error("Private key passed when a public key was expected")
|
||||
}
|
||||
|
||||
private fun cryptoKeyToPrivateKey(key: CryptoKey): PrivateKey {
|
||||
val internalHandle = key.handle as? AndroidKeyHandle
|
||||
?: throw logger.error("Unknown format for CryptoKey passed")
|
||||
return AndroidKeyStore.keyStore.getKey(internalHandle.alias, null) as? PrivateKey
|
||||
?: throw logger.error("Software private keys are not supported by the native Subtle.")
|
||||
}
|
||||
|
||||
|
||||
private fun signAlgorithmToAndroid(algorithm: Algorithm, cryptoKey: CryptoKey): String {
|
||||
return when (algorithm.name) {
|
||||
W3cCryptoApiConstants.EcDsa.value -> {
|
||||
val ecDsaParams = algorithm as EcdsaParams
|
||||
when (ecDsaParams.hash.name) {
|
||||
W3cCryptoApiConstants.Sha1.value -> AndroidConstants.EcDsaSha1.value
|
||||
W3cCryptoApiConstants.Sha224.value -> AndroidConstants.EcDsaSha224.value
|
||||
W3cCryptoApiConstants.Sha256.value -> AndroidConstants.EcDsaSha256.value
|
||||
W3cCryptoApiConstants.Sha384.value -> AndroidConstants.EcDsaSha384.value
|
||||
W3cCryptoApiConstants.Sha512.value -> AndroidConstants.EcDsaSha512.value
|
||||
else -> throw logger.error("Unsupported ECDSA hash algorithm: ${ecDsaParams.hash.name}")
|
||||
}
|
||||
}
|
||||
W3cCryptoApiConstants.RsaSsaPkcs1V15.value -> {
|
||||
// The hash is indicated by the key's "algorithm" slot.
|
||||
val keyAlgorithm = cryptoKey.algorithm as? RsaHashedKeyAlgorithm ?: throw logger.error("Unsupported RSA key algorithm: ${cryptoKey.algorithm.name}")
|
||||
when (keyAlgorithm.hash.name) {
|
||||
W3cCryptoApiConstants.Sha1.value -> AndroidConstants.RsSha1.value
|
||||
W3cCryptoApiConstants.Sha224.value -> AndroidConstants.RsSha224.value
|
||||
W3cCryptoApiConstants.Sha256.value -> AndroidConstants.RsSha256.value
|
||||
W3cCryptoApiConstants.Sha384.value -> AndroidConstants.RsSha384.value
|
||||
W3cCryptoApiConstants.Sha512.value -> AndroidConstants.RsSha512.value
|
||||
else -> throw logger.error("Unsupported RSA hash algorithm: ${keyAlgorithm.hash.name}")
|
||||
}
|
||||
}
|
||||
else -> throw logger.error("Unsupported algorithm: ${algorithm.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun keyPairAlgorithmToAndroid(algorithm: Algorithm): String {
|
||||
return when (algorithm.name) {
|
||||
W3cCryptoApiConstants.RsaSsaPkcs1V15.value -> AndroidConstants.Rsa.value
|
||||
W3cCryptoApiConstants.EcDsa.value -> AndroidConstants.Ec.value
|
||||
else -> throw logger.error("Unknown algorithm used: ${algorithm.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun keyPairUsageToAndroid(usages: List<KeyUsage>): Int {
|
||||
var flags = 0
|
||||
usages.forEach { usage ->
|
||||
flags = flags.or(when (usage) {
|
||||
KeyUsage.Decrypt -> KeyProperties.PURPOSE_DECRYPT
|
||||
KeyUsage.Encrypt -> KeyProperties.PURPOSE_ENCRYPT
|
||||
KeyUsage.Sign -> KeyProperties.PURPOSE_SIGN
|
||||
KeyUsage.Verify -> KeyProperties.PURPOSE_VERIFY
|
||||
KeyUsage.WrapKey -> KeyProperties.PURPOSE_ENCRYPT
|
||||
KeyUsage.UnwrapKey -> KeyProperties.PURPOSE_DECRYPT
|
||||
else -> 0
|
||||
})
|
||||
}
|
||||
return flags
|
||||
}
|
||||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import com.microsoft.did.sdk.crypto.models.AndroidConstants
|
||||
import com.microsoft.did.sdk.crypto.keyStore.AndroidKeyStore
|
||||
import com.microsoft.did.sdk.crypto.keys.AndroidKeyHandle
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.*
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithms.AesKeyGenParams
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.JwaCryptoConverter
|
||||
import com.microsoft.did.sdk.utilities.AndroidKeyConverter
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.security.spec.*
|
||||
import javax.crypto.KeyGenerator
|
||||
|
||||
class AndroidSubtle(private var keyStore: AndroidKeyStore, private val logger: ILogger): SubtleCrypto {
|
||||
override fun encrypt(algorithm: Algorithm, key: CryptoKey, data: ByteArray): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun decrypt(algorithm: Algorithm, key: CryptoKey, data: ByteArray): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun sign(algorithm: Algorithm, key: CryptoKey, data: ByteArray): ByteArray {
|
||||
// verify we're signing with a private key
|
||||
if (key.type != KeyType.Private) {
|
||||
throw logger.error("Sign must use a private key")
|
||||
}
|
||||
// key's handle should be an Android keyStore key reference.
|
||||
val handle = cryptoKeyToPrivateKey(key)
|
||||
return Signature.getInstance(signAlgorithmToAndroid(algorithm, key)).run {
|
||||
initSign(handle)
|
||||
update(data)
|
||||
sign()
|
||||
}
|
||||
}
|
||||
|
||||
override fun verify(algorithm: Algorithm, key: CryptoKey, signature: ByteArray, data: ByteArray): Boolean {
|
||||
val handle = cryptoKeyToPublicKey(key)
|
||||
val s = Signature.getInstance(signAlgorithmToAndroid(algorithm, key)).apply {
|
||||
initVerify(handle)
|
||||
update(data)
|
||||
}
|
||||
return s.verify(signature)
|
||||
}
|
||||
|
||||
override fun digest(algorithm: Algorithm, data: ByteArray): ByteArray {
|
||||
val digest = MessageDigest.getInstance(algorithm.name)
|
||||
return digest.digest(data)
|
||||
}
|
||||
|
||||
override fun generateKey(algorithm: Algorithm, extractable: Boolean, keyUsages: List<KeyUsage>): CryptoKey {
|
||||
val secret = when(algorithm.name) {
|
||||
W3cCryptoApiConstants.AesCbc.value, W3cCryptoApiConstants.AesCtr.value,
|
||||
W3cCryptoApiConstants.AesGcm.value, W3cCryptoApiConstants.AesKw.value -> {
|
||||
val generator = KeyGenerator.getInstance(AndroidConstants.Aes.value)
|
||||
val alg = algorithm as AesKeyGenParams
|
||||
generator.init(alg.length.toInt())
|
||||
generator.generateKey()
|
||||
}
|
||||
else -> throw logger.error("Unsupported symmetric key algorithm: ${algorithm.name}")
|
||||
}
|
||||
return CryptoKey(
|
||||
type = KeyType.Secret,
|
||||
extractable = extractable,
|
||||
usages = keyUsages,
|
||||
handle = secret,
|
||||
algorithm = algorithm
|
||||
)
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
override fun generateKeyPair(algorithm: Algorithm, extractable: Boolean, keyUsages: List<KeyUsage>): CryptoKeyPair {
|
||||
if (!algorithm.additionalParams.containsKey(AndroidConstants.KeyReference.value)){
|
||||
throw logger.error("Algorithm must contain an additional parameter \"${AndroidConstants.KeyReference.value}\"")
|
||||
}
|
||||
val alias = keyStore.checkOrCreateKeyId(algorithm.additionalParams[AndroidConstants.KeyReference.value] as String, null)
|
||||
logger.debug("Generating ${algorithm.name} key with alias $alias")
|
||||
val keyPairGenerator = KeyPairGenerator.getInstance(keyPairAlgorithmToAndroid(algorithm), AndroidKeyStore.provider)
|
||||
keyPairGenerator.initialize(
|
||||
KeyGenParameterSpec.Builder(
|
||||
alias,
|
||||
keyPairUsageToAndroid(keyUsages)
|
||||
).setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
|
||||
.build()
|
||||
)
|
||||
val keyPair = keyPairGenerator.genKeyPair()
|
||||
// AndroidKeyStore.keyStore.
|
||||
logger.debug("Key pair generated ($alias)")
|
||||
// convert keypair.
|
||||
return CryptoKeyPair(
|
||||
CryptoKey(
|
||||
KeyType.Public,
|
||||
extractable,
|
||||
algorithm,
|
||||
keyUsages,
|
||||
AndroidKeyHandle(
|
||||
alias,
|
||||
keyPair.public
|
||||
)
|
||||
),
|
||||
CryptoKey(
|
||||
KeyType.Private,
|
||||
false,
|
||||
algorithm,
|
||||
keyUsages,
|
||||
AndroidKeyHandle(
|
||||
alias,
|
||||
null
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun deriveKey(
|
||||
algorithm: Algorithm,
|
||||
baseKey: CryptoKey,
|
||||
derivedKeyType: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: List<KeyUsage>
|
||||
): CryptoKey {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun deriveBits(algorithm: Algorithm, baseKey: CryptoKey, length: ULong): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun importKey(
|
||||
format: KeyFormat,
|
||||
keyData: ByteArray,
|
||||
algorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: List<KeyUsage>
|
||||
): CryptoKey {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun importKey(
|
||||
format: KeyFormat,
|
||||
keyData: JsonWebKey,
|
||||
algorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: List<KeyUsage>
|
||||
): CryptoKey {
|
||||
when (keyData.kty) {
|
||||
com.microsoft.did.sdk.crypto.keys.KeyType.RSA.value -> {
|
||||
val keyFactory = KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_RSA)
|
||||
if (keyData.d != null) { // Private RSA key being imported
|
||||
if (!AndroidKeyStore.keyStore.isKeyEntry(keyData.kid ?: "")) {
|
||||
throw logger.error("Software private keys are not supported.")
|
||||
}
|
||||
val entry = AndroidKeyHandle(keyData.kid!!, null)
|
||||
return CryptoKey(
|
||||
KeyType.Private,
|
||||
extractable,
|
||||
JwaCryptoConverter.jwaAlgToWebCrypto(keyData.alg!!, logger = logger),
|
||||
keyUsages,
|
||||
entry
|
||||
)
|
||||
} else { // Public RSA key being imported
|
||||
val key = keyFactory.generatePublic(RSAPublicKeySpec(
|
||||
BigInteger(1, Base64.decode(keyData.n, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)),
|
||||
BigInteger(1, Base64.decode(keyData.e, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP))
|
||||
))
|
||||
val entry = AndroidKeyHandle(keyData.kid ?: "", key)
|
||||
return CryptoKey(
|
||||
KeyType.Public,
|
||||
extractable,
|
||||
JwaCryptoConverter.jwaAlgToWebCrypto(keyData.alg!!, logger = logger),
|
||||
keyUsages,
|
||||
entry
|
||||
)
|
||||
}
|
||||
}
|
||||
com.microsoft.did.sdk.crypto.keys.KeyType.EllipticCurve.value -> {
|
||||
TODO("Standard Elliptic Curves are not currently supported.")
|
||||
}
|
||||
else -> throw logger.error("Cannot import JWK key type ${keyData.kty}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun exportKey(format: KeyFormat, key: CryptoKey): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun exportKeyJwk(key: CryptoKey): JsonWebKey {
|
||||
val internalHandle = key.handle as? AndroidKeyHandle ?: throw logger.error("Unknown format for CryptoKey passed")
|
||||
return when (internalHandle.key) {
|
||||
is PublicKey -> {
|
||||
AndroidKeyConverter.androidPublicKeyToPublicKey(internalHandle.alias, internalHandle.key, logger).toJWK()
|
||||
}
|
||||
null -> {
|
||||
AndroidKeyConverter.androidPrivateKeyToPrivateKey(internalHandle.alias, AndroidKeyStore.keyStore, logger).toJWK()
|
||||
}
|
||||
else -> {
|
||||
throw logger.error("Unknown CryptoKey format")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun wrapKey(
|
||||
format: KeyFormat,
|
||||
key: CryptoKey,
|
||||
wrappingKey: CryptoKey,
|
||||
wrapAlgorithm: Algorithm
|
||||
): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun unwrapKey(
|
||||
format: KeyFormat,
|
||||
wrappedKey: ByteArray,
|
||||
unwrappingKey: CryptoKey,
|
||||
unwrapAlgorithm: Algorithm,
|
||||
unwrappedKeyAlgorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: List<KeyUsage>
|
||||
): CryptoKey {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
private fun cryptoKeyToPublicKey(key: CryptoKey): PublicKey {
|
||||
val internalHandle = key.handle as? AndroidKeyHandle
|
||||
?: throw logger.error("Unknown format for CryptoKey passed")
|
||||
return internalHandle.key as? PublicKey
|
||||
?: throw logger.error("Private key passed when a public key was expected")
|
||||
}
|
||||
|
||||
private fun cryptoKeyToPrivateKey(key: CryptoKey): PrivateKey {
|
||||
val internalHandle = key.handle as? AndroidKeyHandle
|
||||
?: throw logger.error("Unknown format for CryptoKey passed")
|
||||
return AndroidKeyStore.keyStore.getKey(internalHandle.alias, null) as? PrivateKey
|
||||
?: throw logger.error("Software private keys are not supported by the native Subtle.")
|
||||
}
|
||||
|
||||
|
||||
private fun signAlgorithmToAndroid(algorithm: Algorithm, cryptoKey: CryptoKey): String {
|
||||
return when (algorithm.name) {
|
||||
W3cCryptoApiConstants.EcDsa.value -> {
|
||||
val ecDsaParams = algorithm as EcdsaParams
|
||||
when (ecDsaParams.hash.name) {
|
||||
W3cCryptoApiConstants.Sha1.value -> AndroidConstants.EcDsaSha1.value
|
||||
W3cCryptoApiConstants.Sha224.value -> AndroidConstants.EcDsaSha224.value
|
||||
W3cCryptoApiConstants.Sha256.value -> AndroidConstants.EcDsaSha256.value
|
||||
W3cCryptoApiConstants.Sha384.value -> AndroidConstants.EcDsaSha384.value
|
||||
W3cCryptoApiConstants.Sha512.value -> AndroidConstants.EcDsaSha512.value
|
||||
else -> throw logger.error("Unsupported ECDSA hash algorithm: ${ecDsaParams.hash.name}")
|
||||
}
|
||||
}
|
||||
W3cCryptoApiConstants.RsaSsaPkcs1V15.value -> {
|
||||
// The hash is indicated by the key's "algorithm" slot.
|
||||
val keyAlgorithm = cryptoKey.algorithm as? RsaHashedKeyAlgorithm ?: throw logger.error("Unsupported RSA key algorithm: ${cryptoKey.algorithm.name}")
|
||||
when (keyAlgorithm.hash.name) {
|
||||
W3cCryptoApiConstants.Sha1.value -> AndroidConstants.RsSha1.value
|
||||
W3cCryptoApiConstants.Sha224.value -> AndroidConstants.RsSha224.value
|
||||
W3cCryptoApiConstants.Sha256.value -> AndroidConstants.RsSha256.value
|
||||
W3cCryptoApiConstants.Sha384.value -> AndroidConstants.RsSha384.value
|
||||
W3cCryptoApiConstants.Sha512.value -> AndroidConstants.RsSha512.value
|
||||
else -> throw logger.error("Unsupported RSA hash algorithm: ${keyAlgorithm.hash.name}")
|
||||
}
|
||||
}
|
||||
else -> throw logger.error("Unsupported algorithm: ${algorithm.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun keyPairAlgorithmToAndroid(algorithm: Algorithm): String {
|
||||
return when (algorithm.name) {
|
||||
W3cCryptoApiConstants.RsaSsaPkcs1V15.value -> AndroidConstants.Rsa.value
|
||||
W3cCryptoApiConstants.EcDsa.value -> AndroidConstants.Ec.value
|
||||
else -> throw logger.error("Unknown algorithm used: ${algorithm.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun keyPairUsageToAndroid(usages: List<KeyUsage>): Int {
|
||||
var flags = 0
|
||||
usages.forEach { usage ->
|
||||
flags = flags.or(when (usage) {
|
||||
KeyUsage.Decrypt -> KeyProperties.PURPOSE_DECRYPT
|
||||
KeyUsage.Encrypt -> KeyProperties.PURPOSE_ENCRYPT
|
||||
KeyUsage.Sign -> KeyProperties.PURPOSE_SIGN
|
||||
KeyUsage.Verify -> KeyProperties.PURPOSE_VERIFY
|
||||
KeyUsage.WrapKey -> KeyProperties.PURPOSE_ENCRYPT
|
||||
KeyUsage.UnwrapKey -> KeyProperties.PURPOSE_DECRYPT
|
||||
else -> 0
|
||||
})
|
||||
}
|
||||
return flags
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.SubtleCrypto
|
||||
import com.microsoft.did.sdk.crypto.plugins.subtleCrypto.Subtle
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class EllipticCurveSubtleCrypto(default: SubtleCrypto, logger: ILogger): Subtle(setOf(Secp256k1Provider(default, logger)), logger), SubtleCrypto {
|
||||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.SubtleCrypto
|
||||
import com.microsoft.did.sdk.crypto.plugins.subtleCrypto.Subtle
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class EllipticCurveSubtleCrypto(default: SubtleCrypto, logger: ILogger): Subtle(setOf(Secp256k1Provider(default, logger)), logger), SubtleCrypto {
|
||||
}
|
|
@ -1,215 +1,215 @@
|
|||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import android.util.Base64
|
||||
import com.microsoft.did.sdk.crypto.models.Sha
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.*
|
||||
import com.microsoft.did.sdk.crypto.plugins.subtleCrypto.Provider
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.JwaCryptoConverter
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import com.microsoft.did.sdk.utilities.printBytes
|
||||
import com.microsoft.did.sdk.utilities.stringToByteArray
|
||||
import org.bitcoin.NativeSecp256k1
|
||||
import org.bitcoin.Secp256k1Context
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
class Secp256k1Provider(val subtleCryptoSha: SubtleCrypto, logger: ILogger): Provider(logger) {
|
||||
companion object {
|
||||
init {
|
||||
if (!Secp256k1Context.isEnabled()) {
|
||||
System.loadLibrary("secp256k1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Secp256k1Handle(val alias: String, val data: ByteArray)
|
||||
|
||||
override val name: String = "ECDSA"
|
||||
override val privateKeyUsage: Set<KeyUsage> = setOf(KeyUsage.Sign)
|
||||
override val publicKeyUsage: Set<KeyUsage> = setOf(KeyUsage.Verify)
|
||||
override val symmetricKeyUsage: Set<KeyUsage>? = null
|
||||
|
||||
override fun onGenerateKeyPair(
|
||||
algorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: Set<KeyUsage>
|
||||
): CryptoKeyPair {
|
||||
val seed = ByteArray(32)
|
||||
val random = SecureRandom()
|
||||
random.nextBytes(seed)
|
||||
NativeSecp256k1.randomize(seed)
|
||||
|
||||
val secret = ByteArray(32)
|
||||
random.nextBytes(secret)
|
||||
|
||||
val publicKey = NativeSecp256k1.computePubkey(secret)
|
||||
|
||||
val signAlgorithm = EcdsaParams(
|
||||
hash = algorithm.additionalParams["hash"] as? Algorithm ?: Sha.Sha256,
|
||||
additionalParams = mapOf(
|
||||
"namedCurve" to W3cCryptoApiConstants.Secp256k1.value
|
||||
)
|
||||
)
|
||||
|
||||
val keyPair = CryptoKeyPair(
|
||||
privateKey = CryptoKey(
|
||||
KeyType.Private,
|
||||
extractable,
|
||||
signAlgorithm,
|
||||
keyUsages.toList(),
|
||||
Secp256k1Handle("", secret)
|
||||
),
|
||||
publicKey = CryptoKey(
|
||||
KeyType.Public,
|
||||
true,
|
||||
signAlgorithm,
|
||||
publicKeyUsage.toList(),
|
||||
Secp256k1Handle("", publicKey)
|
||||
))
|
||||
|
||||
return return keyPair
|
||||
}
|
||||
|
||||
override fun checkGenerateKeyParams(algorithm: Algorithm) {
|
||||
val keyGenParams = algorithm as? EcKeyGenParams ?: throw logger.error("EcKeyGenParams expected as algorithm")
|
||||
if (keyGenParams.namedCurve.toUpperCase(Locale.ROOT) != W3cCryptoApiConstants.Secp256k1.value.toUpperCase(Locale.ROOT) &&
|
||||
keyGenParams.namedCurve.toUpperCase(Locale.ROOT) != W3cCryptoApiConstants.Secp256k1.name.toUpperCase(Locale.ROOT)) {
|
||||
throw logger.error("The curve ${keyGenParams.namedCurve} is not supported by Secp256k1Provider")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSign(algorithm: Algorithm, key: CryptoKey, data: ByteArray): ByteArray {
|
||||
val keyData = (key.handle as Secp256k1Handle).data
|
||||
val ecAlgorithm = algorithm as EcdsaParams
|
||||
val hashedData = subtleCryptoSha.digest(ecAlgorithm.hash, data)
|
||||
if (hashedData.size != 32) {
|
||||
throw logger.error("Data must be 32 bytes")
|
||||
}
|
||||
return NativeSecp256k1.sign(hashedData, keyData)
|
||||
}
|
||||
|
||||
override fun onVerify(algorithm: Algorithm, key: CryptoKey, signature: ByteArray, data: ByteArray): Boolean {
|
||||
val keyData = (key.handle as Secp256k1Handle).data
|
||||
val ecAlgorithm = algorithm as EcdsaParams
|
||||
val hashedData = subtleCryptoSha.digest(ecAlgorithm.hash, data)
|
||||
if (hashedData.size != 32) {
|
||||
throw logger.error("Data must be 32 bytes")
|
||||
}
|
||||
|
||||
print("KEY DATA: ")
|
||||
printBytes(keyData)
|
||||
|
||||
return NativeSecp256k1.verify(hashedData, signature, keyData)
|
||||
}
|
||||
|
||||
override fun onImportKey(
|
||||
format: KeyFormat,
|
||||
keyData: JsonWebKey,
|
||||
algorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: Set<KeyUsage>
|
||||
): CryptoKey {
|
||||
val alias = keyData.kid ?: ""
|
||||
return if (keyData.d != null) { // import d as the private key handle
|
||||
CryptoKey(
|
||||
type = KeyType.Private,
|
||||
extractable = extractable,
|
||||
algorithm = algorithm,
|
||||
usages = keyUsages.toList(),
|
||||
handle = Secp256k1Handle(alias, Base64.decode(stringToByteArray(keyData.d!!), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP))
|
||||
)
|
||||
} else {// public key
|
||||
val x = Base64.decode(stringToByteArray(keyData.x!!), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
val y = Base64.decode(stringToByteArray(keyData.y!!), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
val xyData = ByteArray(65)
|
||||
xyData[0] = secp256k1Tag.uncompressed.byte
|
||||
x.forEachIndexed { index, byte ->
|
||||
xyData[index + 1] = byte
|
||||
}
|
||||
y.forEachIndexed { index, byte ->
|
||||
xyData[index + 33] = byte
|
||||
}
|
||||
CryptoKey(
|
||||
type = KeyType.Public,
|
||||
extractable = extractable,
|
||||
algorithm = algorithm,
|
||||
usages = keyUsages.toList(),
|
||||
handle = Secp256k1Handle(alias, xyData)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExportKeyJwk(key: CryptoKey): JsonWebKey {
|
||||
val keyOps = mutableListOf<String>()
|
||||
for (usage in key.usages) {
|
||||
keyOps.add(usage.value)
|
||||
}
|
||||
val publicKey: ByteArray
|
||||
val handle = key.handle as Secp256k1Handle
|
||||
val d: String? = if (key.type == KeyType.Private) {
|
||||
publicKey = NativeSecp256k1.computePubkey(handle.data)
|
||||
Base64.encodeToString(handle.data, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
} else {
|
||||
publicKey = handle.data
|
||||
null
|
||||
}
|
||||
val xyData = publicToXY(publicKey)
|
||||
return JsonWebKey(
|
||||
kty = com.microsoft.did.sdk.crypto.keys.KeyType.EllipticCurve.value,
|
||||
kid = handle.alias,
|
||||
crv = W3cCryptoApiConstants.Secp256k1.value,
|
||||
use = "sig",
|
||||
key_ops = keyOps,
|
||||
alg = JwaCryptoConverter.webCryptoToJwa(key.algorithm, logger),
|
||||
ext = key.extractable,
|
||||
d = d?.trim(),
|
||||
x = xyData.first.trim(),
|
||||
y = xyData.second.trim()
|
||||
)
|
||||
}
|
||||
|
||||
override fun checkCryptoKey(key: CryptoKey, keyUsage: KeyUsage) {
|
||||
super.checkCryptoKey(key, keyUsage)
|
||||
if (key.type == KeyType.Private) {
|
||||
val keyData = (key.handle as Secp256k1Handle).data
|
||||
if (!NativeSecp256k1.secKeyVerify(keyData)) {
|
||||
throw logger.error("Private key invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mapped from secp256k1_eckey_pubkey_parse
|
||||
private fun publicToXY(keyData: ByteArray): Pair<String, String> {
|
||||
if (keyData.size == 33 && (
|
||||
keyData[0] == secp256k1Tag.even.byte ||
|
||||
keyData[0] == secp256k1Tag.odd.byte)) {
|
||||
// compressed form
|
||||
return Pair(
|
||||
"",
|
||||
""
|
||||
)
|
||||
} else if (keyData.size == 65 && (
|
||||
keyData[0] == secp256k1Tag.uncompressed.byte ||
|
||||
keyData[0] == secp256k1Tag.hybridEven.byte ||
|
||||
keyData[0] == secp256k1Tag.hybridOdd.byte
|
||||
)) {
|
||||
// uncompressed, bytes 1-32, and 33-end are x and y
|
||||
val x = keyData.sliceArray(1..32)
|
||||
val y = keyData.sliceArray(33..64)
|
||||
return Pair(
|
||||
Base64.encodeToString(x, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
Base64.encodeToString(y, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
)
|
||||
} else {
|
||||
throw logger.error("Public key improperly formatted")
|
||||
}
|
||||
}
|
||||
|
||||
enum class secp256k1Tag(val byte: Byte) {
|
||||
even(0x02),
|
||||
odd(0x03),
|
||||
uncompressed(0x04),
|
||||
hybridEven(0x06),
|
||||
hybridOdd(0x07)
|
||||
}
|
||||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import android.util.Base64
|
||||
import com.microsoft.did.sdk.crypto.models.Sha
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.*
|
||||
import com.microsoft.did.sdk.crypto.plugins.subtleCrypto.Provider
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.JwaCryptoConverter
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import com.microsoft.did.sdk.utilities.printBytes
|
||||
import com.microsoft.did.sdk.utilities.stringToByteArray
|
||||
import org.bitcoin.NativeSecp256k1
|
||||
import org.bitcoin.Secp256k1Context
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
class Secp256k1Provider(val subtleCryptoSha: SubtleCrypto, logger: ILogger): Provider(logger) {
|
||||
companion object {
|
||||
init {
|
||||
if (!Secp256k1Context.isEnabled()) {
|
||||
System.loadLibrary("secp256k1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Secp256k1Handle(val alias: String, val data: ByteArray)
|
||||
|
||||
override val name: String = "ECDSA"
|
||||
override val privateKeyUsage: Set<KeyUsage> = setOf(KeyUsage.Sign)
|
||||
override val publicKeyUsage: Set<KeyUsage> = setOf(KeyUsage.Verify)
|
||||
override val symmetricKeyUsage: Set<KeyUsage>? = null
|
||||
|
||||
override fun onGenerateKeyPair(
|
||||
algorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: Set<KeyUsage>
|
||||
): CryptoKeyPair {
|
||||
val seed = ByteArray(32)
|
||||
val random = SecureRandom()
|
||||
random.nextBytes(seed)
|
||||
NativeSecp256k1.randomize(seed)
|
||||
|
||||
val secret = ByteArray(32)
|
||||
random.nextBytes(secret)
|
||||
|
||||
val publicKey = NativeSecp256k1.computePubkey(secret)
|
||||
|
||||
val signAlgorithm = EcdsaParams(
|
||||
hash = algorithm.additionalParams["hash"] as? Algorithm ?: Sha.Sha256,
|
||||
additionalParams = mapOf(
|
||||
"namedCurve" to W3cCryptoApiConstants.Secp256k1.value
|
||||
)
|
||||
)
|
||||
|
||||
val keyPair = CryptoKeyPair(
|
||||
privateKey = CryptoKey(
|
||||
KeyType.Private,
|
||||
extractable,
|
||||
signAlgorithm,
|
||||
keyUsages.toList(),
|
||||
Secp256k1Handle("", secret)
|
||||
),
|
||||
publicKey = CryptoKey(
|
||||
KeyType.Public,
|
||||
true,
|
||||
signAlgorithm,
|
||||
publicKeyUsage.toList(),
|
||||
Secp256k1Handle("", publicKey)
|
||||
))
|
||||
|
||||
return return keyPair
|
||||
}
|
||||
|
||||
override fun checkGenerateKeyParams(algorithm: Algorithm) {
|
||||
val keyGenParams = algorithm as? EcKeyGenParams ?: throw logger.error("EcKeyGenParams expected as algorithm")
|
||||
if (keyGenParams.namedCurve.toUpperCase(Locale.ROOT) != W3cCryptoApiConstants.Secp256k1.value.toUpperCase(Locale.ROOT) &&
|
||||
keyGenParams.namedCurve.toUpperCase(Locale.ROOT) != W3cCryptoApiConstants.Secp256k1.name.toUpperCase(Locale.ROOT)) {
|
||||
throw logger.error("The curve ${keyGenParams.namedCurve} is not supported by Secp256k1Provider")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSign(algorithm: Algorithm, key: CryptoKey, data: ByteArray): ByteArray {
|
||||
val keyData = (key.handle as Secp256k1Handle).data
|
||||
val ecAlgorithm = algorithm as EcdsaParams
|
||||
val hashedData = subtleCryptoSha.digest(ecAlgorithm.hash, data)
|
||||
if (hashedData.size != 32) {
|
||||
throw logger.error("Data must be 32 bytes")
|
||||
}
|
||||
return NativeSecp256k1.sign(hashedData, keyData)
|
||||
}
|
||||
|
||||
override fun onVerify(algorithm: Algorithm, key: CryptoKey, signature: ByteArray, data: ByteArray): Boolean {
|
||||
val keyData = (key.handle as Secp256k1Handle).data
|
||||
val ecAlgorithm = algorithm as EcdsaParams
|
||||
val hashedData = subtleCryptoSha.digest(ecAlgorithm.hash, data)
|
||||
if (hashedData.size != 32) {
|
||||
throw logger.error("Data must be 32 bytes")
|
||||
}
|
||||
|
||||
print("KEY DATA: ")
|
||||
printBytes(keyData)
|
||||
|
||||
return NativeSecp256k1.verify(hashedData, signature, keyData)
|
||||
}
|
||||
|
||||
override fun onImportKey(
|
||||
format: KeyFormat,
|
||||
keyData: JsonWebKey,
|
||||
algorithm: Algorithm,
|
||||
extractable: Boolean,
|
||||
keyUsages: Set<KeyUsage>
|
||||
): CryptoKey {
|
||||
val alias = keyData.kid ?: ""
|
||||
return if (keyData.d != null) { // import d as the private key handle
|
||||
CryptoKey(
|
||||
type = KeyType.Private,
|
||||
extractable = extractable,
|
||||
algorithm = algorithm,
|
||||
usages = keyUsages.toList(),
|
||||
handle = Secp256k1Handle(alias, Base64.decode(stringToByteArray(keyData.d!!), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP))
|
||||
)
|
||||
} else {// public key
|
||||
val x = Base64.decode(stringToByteArray(keyData.x!!), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
val y = Base64.decode(stringToByteArray(keyData.y!!), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
val xyData = ByteArray(65)
|
||||
xyData[0] = secp256k1Tag.uncompressed.byte
|
||||
x.forEachIndexed { index, byte ->
|
||||
xyData[index + 1] = byte
|
||||
}
|
||||
y.forEachIndexed { index, byte ->
|
||||
xyData[index + 33] = byte
|
||||
}
|
||||
CryptoKey(
|
||||
type = KeyType.Public,
|
||||
extractable = extractable,
|
||||
algorithm = algorithm,
|
||||
usages = keyUsages.toList(),
|
||||
handle = Secp256k1Handle(alias, xyData)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExportKeyJwk(key: CryptoKey): JsonWebKey {
|
||||
val keyOps = mutableListOf<String>()
|
||||
for (usage in key.usages) {
|
||||
keyOps.add(usage.value)
|
||||
}
|
||||
val publicKey: ByteArray
|
||||
val handle = key.handle as Secp256k1Handle
|
||||
val d: String? = if (key.type == KeyType.Private) {
|
||||
publicKey = NativeSecp256k1.computePubkey(handle.data)
|
||||
Base64.encodeToString(handle.data, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
} else {
|
||||
publicKey = handle.data
|
||||
null
|
||||
}
|
||||
val xyData = publicToXY(publicKey)
|
||||
return JsonWebKey(
|
||||
kty = com.microsoft.did.sdk.crypto.keys.KeyType.EllipticCurve.value,
|
||||
kid = handle.alias,
|
||||
crv = W3cCryptoApiConstants.Secp256k1.value,
|
||||
use = "sig",
|
||||
key_ops = keyOps,
|
||||
alg = JwaCryptoConverter.webCryptoToJwa(key.algorithm, logger),
|
||||
ext = key.extractable,
|
||||
d = d?.trim(),
|
||||
x = xyData.first.trim(),
|
||||
y = xyData.second.trim()
|
||||
)
|
||||
}
|
||||
|
||||
override fun checkCryptoKey(key: CryptoKey, keyUsage: KeyUsage) {
|
||||
super.checkCryptoKey(key, keyUsage)
|
||||
if (key.type == KeyType.Private) {
|
||||
val keyData = (key.handle as Secp256k1Handle).data
|
||||
if (!NativeSecp256k1.secKeyVerify(keyData)) {
|
||||
throw logger.error("Private key invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mapped from secp256k1_eckey_pubkey_parse
|
||||
private fun publicToXY(keyData: ByteArray): Pair<String, String> {
|
||||
if (keyData.size == 33 && (
|
||||
keyData[0] == secp256k1Tag.even.byte ||
|
||||
keyData[0] == secp256k1Tag.odd.byte)) {
|
||||
// compressed form
|
||||
return Pair(
|
||||
"",
|
||||
""
|
||||
)
|
||||
} else if (keyData.size == 65 && (
|
||||
keyData[0] == secp256k1Tag.uncompressed.byte ||
|
||||
keyData[0] == secp256k1Tag.hybridEven.byte ||
|
||||
keyData[0] == secp256k1Tag.hybridOdd.byte
|
||||
)) {
|
||||
// uncompressed, bytes 1-32, and 33-end are x and y
|
||||
val x = keyData.sliceArray(1..32)
|
||||
val y = keyData.sliceArray(33..64)
|
||||
return Pair(
|
||||
Base64.encodeToString(x, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
Base64.encodeToString(y, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
)
|
||||
} else {
|
||||
throw logger.error("Public key improperly formatted")
|
||||
}
|
||||
}
|
||||
|
||||
enum class secp256k1Tag(val byte: Byte) {
|
||||
even(0x02),
|
||||
odd(0x03),
|
||||
uncompressed(0x04),
|
||||
hybridEven(0x06),
|
||||
hybridOdd(0x07)
|
||||
}
|
||||
}
|
|
@ -1,24 +1,24 @@
|
|||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.KeyUsage
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.SubtleCrypto
|
||||
import com.microsoft.did.sdk.crypto.plugins.subtleCrypto.Provider
|
||||
import com.microsoft.did.sdk.crypto.plugins.subtleCrypto.Subtle
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import java.security.MessageDigest
|
||||
|
||||
class ShaSubtleCrypto(logger: ILogger): Subtle(setOf(ShaProvider(logger)), logger), SubtleCrypto {
|
||||
class ShaProvider(logger: ILogger) : Provider(logger) {
|
||||
override val name: String = "SHA-256"
|
||||
override val privateKeyUsage: Set<KeyUsage>? = null
|
||||
override val publicKeyUsage: Set<KeyUsage>? = null
|
||||
override val symmetricKeyUsage: Set<KeyUsage> = emptySet()
|
||||
|
||||
override fun onDigest(algorithm: Algorithm, data: ByteArray): ByteArray {
|
||||
val digest = MessageDigest.getInstance(algorithm.name)
|
||||
return digest.digest(data)
|
||||
}
|
||||
}
|
||||
|
||||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.KeyUsage
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.SubtleCrypto
|
||||
import com.microsoft.did.sdk.crypto.plugins.subtleCrypto.Provider
|
||||
import com.microsoft.did.sdk.crypto.plugins.subtleCrypto.Subtle
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import java.security.MessageDigest
|
||||
|
||||
class ShaSubtleCrypto(logger: ILogger): Subtle(setOf(ShaProvider(logger)), logger), SubtleCrypto {
|
||||
class ShaProvider(logger: ILogger) : Provider(logger) {
|
||||
override val name: String = "SHA-256"
|
||||
override val privateKeyUsage: Set<KeyUsage>? = null
|
||||
override val publicKeyUsage: Set<KeyUsage>? = null
|
||||
override val symmetricKeyUsage: Set<KeyUsage> = emptySet()
|
||||
|
||||
override fun onDigest(algorithm: Algorithm, data: ByteArray): ByteArray {
|
||||
val digest = MessageDigest.getInstance(algorithm.name)
|
||||
return digest.digest(data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,99 +1,99 @@
|
|||
package com.microsoft.did.sdk.utilities
|
||||
|
||||
import android.util.Base64
|
||||
import com.microsoft.did.sdk.crypto.keys.KeyType
|
||||
import com.microsoft.did.sdk.crypto.keys.PrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.PublicKey
|
||||
import com.microsoft.did.sdk.crypto.keys.SecretKey
|
||||
import com.microsoft.did.sdk.crypto.keys.ellipticCurve.EllipticCurvePrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.ellipticCurve.EllipticCurvePublicKey
|
||||
import com.microsoft.did.sdk.crypto.keys.rsa.RsaPrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.rsa.RsaPublicKey
|
||||
import com.microsoft.did.sdk.crypto.models.KeyUse
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.JsonWebKey
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.KeyUsage
|
||||
import java.security.KeyStore
|
||||
import java.security.interfaces.ECPublicKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
|
||||
object AndroidKeyConverter {
|
||||
fun androidPublicKeyToPublicKey(alias: String, publicKey: java.security.PublicKey, logger: ILogger): PublicKey {
|
||||
return when (whatKeyTypeIs(publicKey, logger)) {
|
||||
KeyType.RSA -> {
|
||||
RsaPublicKey(
|
||||
JsonWebKey(
|
||||
kty = KeyType.RSA.value,
|
||||
kid = alias,
|
||||
key_ops = listOf(KeyUsage.Encrypt.value),
|
||||
use = KeyUse.Encryption.value,
|
||||
n = Base64.encodeToString((publicKey as RSAPublicKey).modulus.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).trim(),
|
||||
e = Base64.encodeToString(publicKey.publicExponent.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).trim()
|
||||
),
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
KeyType.EllipticCurve -> {
|
||||
EllipticCurvePublicKey(
|
||||
JsonWebKey(
|
||||
kty = KeyType.EllipticCurve.value,
|
||||
kid = alias,
|
||||
key_ops = listOf(KeyUsage.Verify.value),
|
||||
use = KeyUse.Signature.value,
|
||||
x = Base64.encodeToString((publicKey as ECPublicKey).w.affineX.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).trim(),
|
||||
y = Base64.encodeToString(publicKey.w.affineY.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).trim()
|
||||
),
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
else -> throw logger.error("Cannot convert key type.")
|
||||
}
|
||||
}
|
||||
|
||||
fun androidPrivateKeyToPrivateKey(alias: String, keyStore: KeyStore, logger: ILogger): PrivateKey {
|
||||
val key = keyStore.getCertificate(alias).publicKey
|
||||
return when (whatKeyTypeIs(key, logger)) {
|
||||
KeyType.RSA -> {
|
||||
RsaPrivateKey (
|
||||
JsonWebKey(
|
||||
kty = KeyType.RSA.value,
|
||||
kid = alias,
|
||||
key_ops = listOf(KeyUsage.Decrypt.value),
|
||||
use = KeyUse.Encryption.value,
|
||||
n = Base64.encodeToString((key as RSAPublicKey).modulus.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
e = Base64.encodeToString(key.publicExponent.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
d = "0",
|
||||
p = "0",
|
||||
q = "0",
|
||||
dp = "0",
|
||||
dq = "0",
|
||||
qi = "0"
|
||||
),
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
KeyType.EllipticCurve -> {
|
||||
EllipticCurvePrivateKey (
|
||||
JsonWebKey(
|
||||
kty = KeyType.EllipticCurve.value,
|
||||
kid = alias,
|
||||
key_ops = listOf(KeyUsage.Sign.value),
|
||||
use = KeyUse.Signature.value,
|
||||
x = Base64.encodeToString((key as ECPublicKey).w.affineX.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
y = Base64.encodeToString(key.w.affineY.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
d = "0"
|
||||
),
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
else -> throw logger.error("Cannot convert key type.")
|
||||
}
|
||||
}
|
||||
|
||||
fun whatKeyTypeIs(publicKey: java.security.PublicKey, logger: ILogger): KeyType {
|
||||
return when (publicKey) {
|
||||
is RSAPublicKey -> KeyType.RSA
|
||||
is ECPublicKey -> KeyType.EllipticCurve
|
||||
else -> throw logger.error("Unknown Key Type")
|
||||
}
|
||||
}
|
||||
package com.microsoft.did.sdk.utilities
|
||||
|
||||
import android.util.Base64
|
||||
import com.microsoft.did.sdk.crypto.keys.KeyType
|
||||
import com.microsoft.did.sdk.crypto.keys.PrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.PublicKey
|
||||
import com.microsoft.did.sdk.crypto.keys.SecretKey
|
||||
import com.microsoft.did.sdk.crypto.keys.ellipticCurve.EllipticCurvePrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.ellipticCurve.EllipticCurvePublicKey
|
||||
import com.microsoft.did.sdk.crypto.keys.rsa.RsaPrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.rsa.RsaPublicKey
|
||||
import com.microsoft.did.sdk.crypto.models.KeyUse
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.JsonWebKey
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.KeyUsage
|
||||
import java.security.KeyStore
|
||||
import java.security.interfaces.ECPublicKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
|
||||
object AndroidKeyConverter {
|
||||
fun androidPublicKeyToPublicKey(alias: String, publicKey: java.security.PublicKey, logger: ILogger): PublicKey {
|
||||
return when (whatKeyTypeIs(publicKey, logger)) {
|
||||
KeyType.RSA -> {
|
||||
RsaPublicKey(
|
||||
JsonWebKey(
|
||||
kty = KeyType.RSA.value,
|
||||
kid = alias,
|
||||
key_ops = listOf(KeyUsage.Encrypt.value),
|
||||
use = KeyUse.Encryption.value,
|
||||
n = Base64.encodeToString((publicKey as RSAPublicKey).modulus.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).trim(),
|
||||
e = Base64.encodeToString(publicKey.publicExponent.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).trim()
|
||||
),
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
KeyType.EllipticCurve -> {
|
||||
EllipticCurvePublicKey(
|
||||
JsonWebKey(
|
||||
kty = KeyType.EllipticCurve.value,
|
||||
kid = alias,
|
||||
key_ops = listOf(KeyUsage.Verify.value),
|
||||
use = KeyUse.Signature.value,
|
||||
x = Base64.encodeToString((publicKey as ECPublicKey).w.affineX.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).trim(),
|
||||
y = Base64.encodeToString(publicKey.w.affineY.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).trim()
|
||||
),
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
else -> throw logger.error("Cannot convert key type.")
|
||||
}
|
||||
}
|
||||
|
||||
fun androidPrivateKeyToPrivateKey(alias: String, keyStore: KeyStore, logger: ILogger): PrivateKey {
|
||||
val key = keyStore.getCertificate(alias).publicKey
|
||||
return when (whatKeyTypeIs(key, logger)) {
|
||||
KeyType.RSA -> {
|
||||
RsaPrivateKey (
|
||||
JsonWebKey(
|
||||
kty = KeyType.RSA.value,
|
||||
kid = alias,
|
||||
key_ops = listOf(KeyUsage.Decrypt.value),
|
||||
use = KeyUse.Encryption.value,
|
||||
n = Base64.encodeToString((key as RSAPublicKey).modulus.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
e = Base64.encodeToString(key.publicExponent.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
d = "0",
|
||||
p = "0",
|
||||
q = "0",
|
||||
dp = "0",
|
||||
dq = "0",
|
||||
qi = "0"
|
||||
),
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
KeyType.EllipticCurve -> {
|
||||
EllipticCurvePrivateKey (
|
||||
JsonWebKey(
|
||||
kty = KeyType.EllipticCurve.value,
|
||||
kid = alias,
|
||||
key_ops = listOf(KeyUsage.Sign.value),
|
||||
use = KeyUse.Signature.value,
|
||||
x = Base64.encodeToString((key as ECPublicKey).w.affineX.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
y = Base64.encodeToString(key.w.affineY.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP),
|
||||
d = "0"
|
||||
),
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
else -> throw logger.error("Cannot convert key type.")
|
||||
}
|
||||
}
|
||||
|
||||
fun whatKeyTypeIs(publicKey: java.security.PublicKey, logger: ILogger): KeyType {
|
||||
return when (publicKey) {
|
||||
is RSAPublicKey -> KeyType.RSA
|
||||
is ECPublicKey -> KeyType.EllipticCurve
|
||||
else -> throw logger.error("Unknown Key Type")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
package com.microsoft.did.sdk.utilities
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Returns the current time in milliseconds since UNIX epoch
|
||||
*/
|
||||
actual fun getCurrentTime(): Long {
|
||||
return Date().time
|
||||
package com.microsoft.did.sdk.utilities
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Returns the current time in milliseconds since UNIX epoch
|
||||
*/
|
||||
actual fun getCurrentTime(): Long {
|
||||
return Date().time
|
||||
}
|
|
@ -1,30 +1,30 @@
|
|||
package com.microsoft.did.sdk.auth
|
||||
|
||||
enum class OAuthRequestParameter(val value: String) {
|
||||
// Required
|
||||
Scope("scope"),
|
||||
ResponseType("response_type"),
|
||||
ClientId("client_id"),
|
||||
RedirectUri("redirect_uri"),
|
||||
|
||||
// Recommended
|
||||
State("state"),
|
||||
|
||||
// Optional
|
||||
ResponseMode("response_mode"),
|
||||
Nonce("nonce"),
|
||||
MaxAge("max_age"),
|
||||
UiLocales("ui_locales"),
|
||||
IdTokenHint("id_token_hint"),
|
||||
|
||||
// Self-issued parameters (optional)
|
||||
Registration("registration"),
|
||||
Request("request"),
|
||||
RequestUri("request_uri"),
|
||||
Claims("claims"),
|
||||
|
||||
IdToken("id_token"),
|
||||
|
||||
// custom parameters
|
||||
Offer("vc_offer")
|
||||
package com.microsoft.did.sdk.auth
|
||||
|
||||
enum class OAuthRequestParameter(val value: String) {
|
||||
// Required
|
||||
Scope("scope"),
|
||||
ResponseType("response_type"),
|
||||
ClientId("client_id"),
|
||||
RedirectUri("redirect_uri"),
|
||||
|
||||
// Recommended
|
||||
State("state"),
|
||||
|
||||
// Optional
|
||||
ResponseMode("response_mode"),
|
||||
Nonce("nonce"),
|
||||
MaxAge("max_age"),
|
||||
UiLocales("ui_locales"),
|
||||
IdTokenHint("id_token_hint"),
|
||||
|
||||
// Self-issued parameters (optional)
|
||||
Registration("registration"),
|
||||
Request("request"),
|
||||
RequestUri("request_uri"),
|
||||
Claims("claims"),
|
||||
|
||||
IdToken("id_token"),
|
||||
|
||||
// custom parameters
|
||||
Offer("vc_offer")
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
package com.microsoft.did.sdk.auth
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class IdToken(
|
||||
val iss: String,
|
||||
val sub: String,
|
||||
val aud: String,
|
||||
val exp: Int,
|
||||
val iat: Int,
|
||||
@SerialName("auth_time")
|
||||
val authTime: Int? = null,
|
||||
val nonce: String,
|
||||
@SerialName("acr")
|
||||
val authenticationContextClass: String? = null,
|
||||
@SerialName("amr")
|
||||
val authenticationMethods: List<String>? = null,
|
||||
@SerialName("azp")
|
||||
val authorizedParty: String? = null
|
||||
package com.microsoft.did.sdk.auth
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class IdToken(
|
||||
val iss: String,
|
||||
val sub: String,
|
||||
val aud: String,
|
||||
val exp: Int,
|
||||
val iat: Int,
|
||||
@SerialName("auth_time")
|
||||
val authTime: Int? = null,
|
||||
val nonce: String,
|
||||
@SerialName("acr")
|
||||
val authenticationContextClass: String? = null,
|
||||
@SerialName("amr")
|
||||
val authenticationMethods: List<String>? = null,
|
||||
@SerialName("azp")
|
||||
val authorizedParty: String? = null
|
||||
)
|
|
@ -1,33 +1,33 @@
|
|||
package com.microsoft.did.sdk.auth.oidc
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Registration(
|
||||
@SerialName("redirect_uris")
|
||||
val redirectUris: List<String>?,
|
||||
@SerialName("response_types")
|
||||
val responseTypes: List<String>?,
|
||||
@SerialName("grant_types")
|
||||
val grantTypes: List<String>?,
|
||||
@SerialName("application_type")
|
||||
val applicationType: String?,
|
||||
val contacts: List<String>?,
|
||||
@SerialName("client_name")
|
||||
val clientName: String?,
|
||||
@SerialName("logo_uri")
|
||||
val logoUri: String?,
|
||||
@SerialName("client_uri")
|
||||
val clientUri: String?,
|
||||
@SerialName("policy_uri")
|
||||
val policyUri: String?,
|
||||
@SerialName("tos_uri")
|
||||
val TermsOfServiceUri: String?,
|
||||
@SerialName("Jwks_uri")
|
||||
val JsonWebKeySetUri: String?,
|
||||
// @SerialName("jwks")
|
||||
// val JsonWebKeySet: TODO: Implement JsonWebKeySet,
|
||||
@SerialName("request_uris")
|
||||
val requestUris: List<String>?
|
||||
package com.microsoft.did.sdk.auth.oidc
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Registration(
|
||||
@SerialName("redirect_uris")
|
||||
val redirectUris: List<String>?,
|
||||
@SerialName("response_types")
|
||||
val responseTypes: List<String>?,
|
||||
@SerialName("grant_types")
|
||||
val grantTypes: List<String>?,
|
||||
@SerialName("application_type")
|
||||
val applicationType: String?,
|
||||
val contacts: List<String>?,
|
||||
@SerialName("client_name")
|
||||
val clientName: String?,
|
||||
@SerialName("logo_uri")
|
||||
val logoUri: String?,
|
||||
@SerialName("client_uri")
|
||||
val clientUri: String?,
|
||||
@SerialName("policy_uri")
|
||||
val policyUri: String?,
|
||||
@SerialName("tos_uri")
|
||||
val TermsOfServiceUri: String?,
|
||||
@SerialName("Jwks_uri")
|
||||
val JsonWebKeySetUri: String?,
|
||||
// @SerialName("jwks")
|
||||
// val JsonWebKeySet: TODO: Implement JsonWebKeySet,
|
||||
@SerialName("request_uris")
|
||||
val requestUris: List<String>?
|
||||
)
|
|
@ -1,92 +1,92 @@
|
|||
package com.microsoft.did.sdk.auth.oidc
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RequestClaimParameter (
|
||||
val userInfo: UserInfoRequest? = null,
|
||||
@SerialName("id_token")
|
||||
val idToken: Map<String, MemberScope<String>>? = null
|
||||
) {
|
||||
@Serializable
|
||||
data class UserInfoRequest(
|
||||
val name: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("given_name")
|
||||
val givenName: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("family_name")
|
||||
val familyName: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("middle_name")
|
||||
val middleName: MemberScope<String>? = MemberScope.default(),
|
||||
val nickname: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("preferred_username")
|
||||
val preferredUsername: MemberScope<String>? = MemberScope.default(),
|
||||
val profile: MemberScope<String>? = MemberScope.default(),
|
||||
val picture: MemberScope<String>? = MemberScope.default(),
|
||||
val website: MemberScope<String>? = MemberScope.default(),
|
||||
val email: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("email_verified")
|
||||
val emailVerified: MemberScope<Boolean>? = MemberScope.default(),
|
||||
val gender: MemberScope<String>? = MemberScope.default(),
|
||||
val birthdate: MemberScope<String>? = MemberScope.default(),
|
||||
val zoneinfo: MemberScope<String>? = MemberScope.default(),
|
||||
val locale: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("phone_number")
|
||||
val phoneNumber: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("phone_number_verified")
|
||||
val phoneNumberVerified: MemberScope<Boolean>? = MemberScope.default(),
|
||||
val address: MemberScope<Address>? = MemberScope.default(),
|
||||
@SerialName("updated_at")
|
||||
val updatedAt: MemberScope<Int>? = MemberScope.default()
|
||||
) {
|
||||
@Serializable
|
||||
data class Address(
|
||||
val formatted: String?,
|
||||
@SerialName("street_address")
|
||||
val streetAddress: String?,
|
||||
val locality: String?,
|
||||
val region: String?,
|
||||
@SerialName("postal_code")
|
||||
val postalCode: String?,
|
||||
val country: String?
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class MemberScope<T>(
|
||||
val essential: Boolean? = false,
|
||||
val value: T? = null,
|
||||
val values: List<T>? = null,
|
||||
val undefined: Boolean = false
|
||||
) {
|
||||
companion object {
|
||||
fun <T>default(): MemberScope<T> {
|
||||
return MemberScope(undefined = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getRequestedClaimClasses(): Map<String, Boolean> {
|
||||
return if (idToken == null) {
|
||||
emptyMap()
|
||||
} else {
|
||||
val idTokenClaims = listOf("iss",
|
||||
"sub",
|
||||
"aud",
|
||||
"exp",
|
||||
"iat",
|
||||
"auth_time",
|
||||
"nonce",
|
||||
"acr",
|
||||
"amr",
|
||||
"azp")
|
||||
val requestedClasses = mutableMapOf<String, Boolean>()
|
||||
idToken.filter {
|
||||
!(it.key in idTokenClaims)
|
||||
}.forEach {
|
||||
requestedClasses[it.key] = it.value.essential ?: false
|
||||
}
|
||||
requestedClasses
|
||||
}
|
||||
}
|
||||
package com.microsoft.did.sdk.auth.oidc
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RequestClaimParameter (
|
||||
val userInfo: UserInfoRequest? = null,
|
||||
@SerialName("id_token")
|
||||
val idToken: Map<String, MemberScope<String>>? = null
|
||||
) {
|
||||
@Serializable
|
||||
data class UserInfoRequest(
|
||||
val name: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("given_name")
|
||||
val givenName: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("family_name")
|
||||
val familyName: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("middle_name")
|
||||
val middleName: MemberScope<String>? = MemberScope.default(),
|
||||
val nickname: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("preferred_username")
|
||||
val preferredUsername: MemberScope<String>? = MemberScope.default(),
|
||||
val profile: MemberScope<String>? = MemberScope.default(),
|
||||
val picture: MemberScope<String>? = MemberScope.default(),
|
||||
val website: MemberScope<String>? = MemberScope.default(),
|
||||
val email: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("email_verified")
|
||||
val emailVerified: MemberScope<Boolean>? = MemberScope.default(),
|
||||
val gender: MemberScope<String>? = MemberScope.default(),
|
||||
val birthdate: MemberScope<String>? = MemberScope.default(),
|
||||
val zoneinfo: MemberScope<String>? = MemberScope.default(),
|
||||
val locale: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("phone_number")
|
||||
val phoneNumber: MemberScope<String>? = MemberScope.default(),
|
||||
@SerialName("phone_number_verified")
|
||||
val phoneNumberVerified: MemberScope<Boolean>? = MemberScope.default(),
|
||||
val address: MemberScope<Address>? = MemberScope.default(),
|
||||
@SerialName("updated_at")
|
||||
val updatedAt: MemberScope<Int>? = MemberScope.default()
|
||||
) {
|
||||
@Serializable
|
||||
data class Address(
|
||||
val formatted: String?,
|
||||
@SerialName("street_address")
|
||||
val streetAddress: String?,
|
||||
val locality: String?,
|
||||
val region: String?,
|
||||
@SerialName("postal_code")
|
||||
val postalCode: String?,
|
||||
val country: String?
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class MemberScope<T>(
|
||||
val essential: Boolean? = false,
|
||||
val value: T? = null,
|
||||
val values: List<T>? = null,
|
||||
val undefined: Boolean = false
|
||||
) {
|
||||
companion object {
|
||||
fun <T>default(): MemberScope<T> {
|
||||
return MemberScope(undefined = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getRequestedClaimClasses(): Map<String, Boolean> {
|
||||
return if (idToken == null) {
|
||||
emptyMap()
|
||||
} else {
|
||||
val idTokenClaims = listOf("iss",
|
||||
"sub",
|
||||
"aud",
|
||||
"exp",
|
||||
"iat",
|
||||
"auth_time",
|
||||
"nonce",
|
||||
"acr",
|
||||
"amr",
|
||||
"azp")
|
||||
val requestedClasses = mutableMapOf<String, Boolean>()
|
||||
idToken.filter {
|
||||
!(it.key in idTokenClaims)
|
||||
}.forEach {
|
||||
requestedClasses[it.key] = it.value.essential ?: false
|
||||
}
|
||||
requestedClasses
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +1,47 @@
|
|||
package com.microsoft.did.sdk.auth.oidc
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class UserInfo (
|
||||
val name: String? = null,
|
||||
@SerialName("given_name")
|
||||
val givenName: String? = null,
|
||||
@SerialName("family_name")
|
||||
val familyName: String? = null,
|
||||
@SerialName("middle_name")
|
||||
val middleName: String? = null,
|
||||
val nickname: String? = null,
|
||||
@SerialName("preferred_username")
|
||||
val preferredUsername: String? = null,
|
||||
val profile: String? = null,
|
||||
val picture: String? = null,
|
||||
val website: String? = null,
|
||||
val email: String? = null,
|
||||
@SerialName("email_verified")
|
||||
val emailVerified: Boolean? = null,
|
||||
val gender: String? = null,
|
||||
val birthdate: String? = null,
|
||||
val zoneinfo: String? = null,
|
||||
val locale: String? = null,
|
||||
@SerialName("phone_number")
|
||||
val phoneNumber: String? = null,
|
||||
@SerialName("phone_number_verified")
|
||||
val phoneNumberVerified: Boolean? = null,
|
||||
val address: Address? = null,
|
||||
@SerialName("updated_at")
|
||||
val updatedAt: Int? = null
|
||||
) {
|
||||
@Serializable
|
||||
data class Address (
|
||||
val formatted: String?,
|
||||
@SerialName("street_address")
|
||||
val streetAddress: String?,
|
||||
val locality: String?,
|
||||
val region: String?,
|
||||
@SerialName("postal_code")
|
||||
val postalCode: String?,
|
||||
val country: String?
|
||||
)
|
||||
package com.microsoft.did.sdk.auth.oidc
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class UserInfo (
|
||||
val name: String? = null,
|
||||
@SerialName("given_name")
|
||||
val givenName: String? = null,
|
||||
@SerialName("family_name")
|
||||
val familyName: String? = null,
|
||||
@SerialName("middle_name")
|
||||
val middleName: String? = null,
|
||||
val nickname: String? = null,
|
||||
@SerialName("preferred_username")
|
||||
val preferredUsername: String? = null,
|
||||
val profile: String? = null,
|
||||
val picture: String? = null,
|
||||
val website: String? = null,
|
||||
val email: String? = null,
|
||||
@SerialName("email_verified")
|
||||
val emailVerified: Boolean? = null,
|
||||
val gender: String? = null,
|
||||
val birthdate: String? = null,
|
||||
val zoneinfo: String? = null,
|
||||
val locale: String? = null,
|
||||
@SerialName("phone_number")
|
||||
val phoneNumber: String? = null,
|
||||
@SerialName("phone_number_verified")
|
||||
val phoneNumberVerified: Boolean? = null,
|
||||
val address: Address? = null,
|
||||
@SerialName("updated_at")
|
||||
val updatedAt: Int? = null
|
||||
) {
|
||||
@Serializable
|
||||
data class Address (
|
||||
val formatted: String?,
|
||||
@SerialName("street_address")
|
||||
val streetAddress: String?,
|
||||
val locality: String?,
|
||||
val region: String?,
|
||||
@SerialName("postal_code")
|
||||
val postalCode: String?,
|
||||
val country: String?
|
||||
)
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
package com.microsoft.did.sdk.auth.oidc
|
||||
|
||||
import com.microsoft.did.sdk.auth.OAuthRequestParameter
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import com.microsoft.did.sdk.utilities.PercentEncoding
|
||||
|
||||
|
||||
fun getQueryStringParameter(name: OAuthRequestParameter, url: String, required: Boolean = false, logger: ILogger): String? {
|
||||
val findResults = Regex("${name.value}=([^&]+)").find(url)
|
||||
if (findResults != null) {
|
||||
return PercentEncoding.decode(findResults.groupValues[1], logger = logger)
|
||||
} else if (required) {
|
||||
throw logger.error("Openid requires a \"${name.value}\" parameter")
|
||||
}
|
||||
return null
|
||||
package com.microsoft.did.sdk.auth.oidc
|
||||
|
||||
import com.microsoft.did.sdk.auth.OAuthRequestParameter
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import com.microsoft.did.sdk.utilities.PercentEncoding
|
||||
|
||||
|
||||
fun getQueryStringParameter(name: OAuthRequestParameter, url: String, required: Boolean = false, logger: ILogger): String? {
|
||||
val findResults = Regex("${name.value}=([^&]+)").find(url)
|
||||
if (findResults != null) {
|
||||
return PercentEncoding.decode(findResults.groupValues[1], logger = logger)
|
||||
} else if (required) {
|
||||
throw logger.error("Openid requires a \"${name.value}\" parameter")
|
||||
}
|
||||
return null
|
||||
}
|
|
@ -1,82 +1,82 @@
|
|||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsFormat
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsToken
|
||||
import com.microsoft.did.sdk.identifier.Identifier
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import com.microsoft.did.sdk.utilities.MinimalJson
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.stringify
|
||||
|
||||
class ClaimBuilder(forClass: ClaimClass? = null, private val logger: ILogger) {
|
||||
var context: String? = null
|
||||
var type: String? = null
|
||||
var issuerName: String? = forClass?.issuerName
|
||||
var claimLogo: ClaimClass.ClaimLogo? = forClass?.claimLogo
|
||||
var claimName: String? = forClass?.claimName
|
||||
var hexBackgroundColor: String? = forClass?.hexBackgroundColor
|
||||
var hexFontColor: String? = forClass?.hexFontColor
|
||||
var moreInfo: String? = forClass?.moreInfo
|
||||
val helpLinks: MutableMap<String, String> = forClass?.helpLinks?.toMutableMap() ?: mutableMapOf()
|
||||
private val claimClassDescriptions: MutableList<ClaimDescription> = forClass?.claimDescriptions?.toMutableList() ?: mutableListOf()
|
||||
var readPermissionDescription: PermissionDescription? = forClass?.readPermissionDescription
|
||||
|
||||
private val claimDescriptions: MutableList<ClaimDescription> = mutableListOf()
|
||||
private val claimDetails: MutableList<Map<String, String>> = mutableListOf()
|
||||
|
||||
|
||||
fun addClassDescription(header: String, body: String) {
|
||||
claimClassDescriptions.add(ClaimDescription(header, body))
|
||||
}
|
||||
|
||||
fun addClaimDescription(header: String, body: String) {
|
||||
claimDescriptions.add(ClaimDescription(header, body))
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
fun addClaimDetail(claim: Map<String, String>) {
|
||||
claimDetails.add(claim)
|
||||
}
|
||||
|
||||
fun buildClass(): ClaimClass {
|
||||
return ClaimClass(
|
||||
issuerName,
|
||||
claimLogo,
|
||||
claimName,
|
||||
hexBackgroundColor,
|
||||
hexFontColor,
|
||||
moreInfo,
|
||||
helpLinks,
|
||||
claimClassDescriptions,
|
||||
readPermissionDescription
|
||||
)
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
fun buildObject(classUri: String, identifier: Identifier, cryptoOperations: CryptoOperations? = null): ClaimObject {
|
||||
if (context.isNullOrBlank() || type.isNullOrBlank()) {
|
||||
throw logger.error("Context and Type must be set.")
|
||||
}
|
||||
val claims = if (cryptoOperations != null) {
|
||||
val serializedData = MinimalJson.serializer.stringify(claimDetails)
|
||||
val token = JwsToken(serializedData, logger = logger)
|
||||
token.sign(identifier.signatureKeyReference, cryptoOperations)
|
||||
SignedClaimDetail(
|
||||
data = token.serialize(JwsFormat.Compact)
|
||||
)
|
||||
} else {
|
||||
UnsignedClaimDetail(
|
||||
data = claimDetails.toList()
|
||||
)
|
||||
}
|
||||
return ClaimObject(
|
||||
classUri,
|
||||
context!!,
|
||||
type!!,
|
||||
claimDescriptions,
|
||||
identifier.document.id,
|
||||
claims
|
||||
)
|
||||
}
|
||||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsFormat
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsToken
|
||||
import com.microsoft.did.sdk.identifier.Identifier
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import com.microsoft.did.sdk.utilities.MinimalJson
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.stringify
|
||||
|
||||
class ClaimBuilder(forClass: ClaimClass? = null, private val logger: ILogger) {
|
||||
var context: String? = null
|
||||
var type: String? = null
|
||||
var issuerName: String? = forClass?.issuerName
|
||||
var claimLogo: ClaimClass.ClaimLogo? = forClass?.claimLogo
|
||||
var claimName: String? = forClass?.claimName
|
||||
var hexBackgroundColor: String? = forClass?.hexBackgroundColor
|
||||
var hexFontColor: String? = forClass?.hexFontColor
|
||||
var moreInfo: String? = forClass?.moreInfo
|
||||
val helpLinks: MutableMap<String, String> = forClass?.helpLinks?.toMutableMap() ?: mutableMapOf()
|
||||
private val claimClassDescriptions: MutableList<ClaimDescription> = forClass?.claimDescriptions?.toMutableList() ?: mutableListOf()
|
||||
var readPermissionDescription: PermissionDescription? = forClass?.readPermissionDescription
|
||||
|
||||
private val claimDescriptions: MutableList<ClaimDescription> = mutableListOf()
|
||||
private val claimDetails: MutableList<Map<String, String>> = mutableListOf()
|
||||
|
||||
|
||||
fun addClassDescription(header: String, body: String) {
|
||||
claimClassDescriptions.add(ClaimDescription(header, body))
|
||||
}
|
||||
|
||||
fun addClaimDescription(header: String, body: String) {
|
||||
claimDescriptions.add(ClaimDescription(header, body))
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
fun addClaimDetail(claim: Map<String, String>) {
|
||||
claimDetails.add(claim)
|
||||
}
|
||||
|
||||
fun buildClass(): ClaimClass {
|
||||
return ClaimClass(
|
||||
issuerName,
|
||||
claimLogo,
|
||||
claimName,
|
||||
hexBackgroundColor,
|
||||
hexFontColor,
|
||||
moreInfo,
|
||||
helpLinks,
|
||||
claimClassDescriptions,
|
||||
readPermissionDescription
|
||||
)
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
fun buildObject(classUri: String, identifier: Identifier, cryptoOperations: CryptoOperations? = null): ClaimObject {
|
||||
if (context.isNullOrBlank() || type.isNullOrBlank()) {
|
||||
throw logger.error("Context and Type must be set.")
|
||||
}
|
||||
val claims = if (cryptoOperations != null) {
|
||||
val serializedData = MinimalJson.serializer.stringify(claimDetails)
|
||||
val token = JwsToken(serializedData, logger = logger)
|
||||
token.sign(identifier.signatureKeyReference, cryptoOperations)
|
||||
SignedClaimDetail(
|
||||
data = token.serialize(JwsFormat.Compact)
|
||||
)
|
||||
} else {
|
||||
UnsignedClaimDetail(
|
||||
data = claimDetails.toList()
|
||||
)
|
||||
}
|
||||
return ClaimObject(
|
||||
classUri,
|
||||
context!!,
|
||||
type!!,
|
||||
claimDescriptions,
|
||||
identifier.document.id,
|
||||
claims
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,45 +1,45 @@
|
|||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.utilities.MinimalJson
|
||||
import com.microsoft.did.sdk.utilities.getHttpClient
|
||||
import io.ktor.client.request.get
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ClaimClass(
|
||||
val issuerName: String? = null,
|
||||
val claimLogo: ClaimLogo? = null,
|
||||
val claimName: String? = null,
|
||||
val hexBackgroundColor: String? = null,
|
||||
val hexFontColor: String? = null,
|
||||
val moreInfo: String? = null,
|
||||
val helpLinks: Map<String, String>? = null,
|
||||
val claimDescriptions: List<ClaimDescription>? = null,
|
||||
val readPermissionDescription: PermissionDescription? = null
|
||||
) {
|
||||
@Serializable
|
||||
data class ClaimLogo(
|
||||
val sourceUri: SourceUri
|
||||
) {
|
||||
@Serializable
|
||||
data class SourceUri(
|
||||
val uri: String,
|
||||
val description: String? = null
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
suspend fun resolve(url: String): ClaimClass {
|
||||
val document = getHttpClient().get<String>(url)
|
||||
return deserialize(document)
|
||||
}
|
||||
|
||||
fun deserialize(claimClass: String): ClaimClass {
|
||||
return MinimalJson.serializer.parse(ClaimClass.serializer(), claimClass)
|
||||
}
|
||||
}
|
||||
|
||||
fun serialize(): String {
|
||||
return MinimalJson.serializer.stringify(ClaimClass.serializer(), this)
|
||||
}
|
||||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.utilities.MinimalJson
|
||||
import com.microsoft.did.sdk.utilities.getHttpClient
|
||||
import io.ktor.client.request.get
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ClaimClass(
|
||||
val issuerName: String? = null,
|
||||
val claimLogo: ClaimLogo? = null,
|
||||
val claimName: String? = null,
|
||||
val hexBackgroundColor: String? = null,
|
||||
val hexFontColor: String? = null,
|
||||
val moreInfo: String? = null,
|
||||
val helpLinks: Map<String, String>? = null,
|
||||
val claimDescriptions: List<ClaimDescription>? = null,
|
||||
val readPermissionDescription: PermissionDescription? = null
|
||||
) {
|
||||
@Serializable
|
||||
data class ClaimLogo(
|
||||
val sourceUri: SourceUri
|
||||
) {
|
||||
@Serializable
|
||||
data class SourceUri(
|
||||
val uri: String,
|
||||
val description: String? = null
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
suspend fun resolve(url: String): ClaimClass {
|
||||
val document = getHttpClient().get<String>(url)
|
||||
return deserialize(document)
|
||||
}
|
||||
|
||||
fun deserialize(claimClass: String): ClaimClass {
|
||||
return MinimalJson.serializer.parse(ClaimClass.serializer(), claimClass)
|
||||
}
|
||||
}
|
||||
|
||||
fun serialize(): String {
|
||||
return MinimalJson.serializer.stringify(ClaimClass.serializer(), this)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ClaimDescription (val header: String, val body: String)
|
|
@ -1,11 +1,11 @@
|
|||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
interface ClaimDetail {
|
||||
val type: String
|
||||
|
||||
suspend fun verify(cryptoOperations: CryptoOperations, resolver: IResolver, logger: ILogger)
|
||||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
interface ClaimDetail {
|
||||
val type: String
|
||||
|
||||
suspend fun verify(cryptoOperations: CryptoOperations, resolver: IResolver, logger: ILogger)
|
||||
}
|
|
@ -1,40 +1,40 @@
|
|||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.DidKeyResolver
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsToken
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import com.microsoft.did.sdk.utilities.MinimalJson
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ClaimObject(val claimClass: String,
|
||||
@SerialName("@context")
|
||||
val context: String,
|
||||
@SerialName("@type")
|
||||
val type: String,
|
||||
val claimDescriptions: List<ClaimDescription>,
|
||||
val claimIssuer: String,
|
||||
val claimDetails: ClaimDetail) {
|
||||
companion object {
|
||||
fun deserialize(claimObject: String): ClaimObject {
|
||||
return MinimalJson.serializer.parse(ClaimObject.serializer(), claimObject)
|
||||
}
|
||||
}
|
||||
|
||||
fun serialize(): String {
|
||||
return MinimalJson.serializer.stringify(ClaimObject.serializer(), this)
|
||||
}
|
||||
|
||||
suspend fun getClaimClass(): ClaimClass {
|
||||
return ClaimClass.resolve(claimClass)
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
suspend fun verify(cryptoOperations: CryptoOperations, resolver: IResolver, logger: ILogger) {
|
||||
claimDetails.verify(cryptoOperations, resolver, logger = logger)
|
||||
}
|
||||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.DidKeyResolver
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsToken
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import com.microsoft.did.sdk.utilities.MinimalJson
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ClaimObject(val claimClass: String,
|
||||
@SerialName("@context")
|
||||
val context: String,
|
||||
@SerialName("@type")
|
||||
val type: String,
|
||||
val claimDescriptions: List<ClaimDescription>,
|
||||
val claimIssuer: String,
|
||||
val claimDetails: ClaimDetail) {
|
||||
companion object {
|
||||
fun deserialize(claimObject: String): ClaimObject {
|
||||
return MinimalJson.serializer.parse(ClaimObject.serializer(), claimObject)
|
||||
}
|
||||
}
|
||||
|
||||
fun serialize(): String {
|
||||
return MinimalJson.serializer.stringify(ClaimObject.serializer(), this)
|
||||
}
|
||||
|
||||
suspend fun getClaimClass(): ClaimClass {
|
||||
return ClaimClass.resolve(claimClass)
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
suspend fun verify(cryptoOperations: CryptoOperations, resolver: IResolver, logger: ILogger) {
|
||||
claimDetails.verify(cryptoOperations, resolver, logger = logger)
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class ClaimResponse(
|
||||
@SerialName("credential-id")
|
||||
val id: String,
|
||||
val state: String,
|
||||
@SerialName("credential")
|
||||
val claimObject: ClaimObject
|
||||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class ClaimResponse(
|
||||
@SerialName("credential-id")
|
||||
val id: String,
|
||||
val state: String,
|
||||
@SerialName("credential")
|
||||
val claimObject: ClaimObject
|
||||
)
|
|
@ -1,13 +1,13 @@
|
|||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class PermissionDescription (
|
||||
val name: String,
|
||||
val description: String,
|
||||
@SerialName("icon_uri")
|
||||
val iconUri: String
|
||||
) {
|
||||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class PermissionDescription (
|
||||
val name: String,
|
||||
val description: String,
|
||||
@SerialName("icon_uri")
|
||||
val iconUri: String
|
||||
) {
|
||||
}
|
|
@ -1,27 +1,27 @@
|
|||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.DidKeyResolver
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsToken
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.Required
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("jws")
|
||||
data class SignedClaimDetail(
|
||||
val data: String
|
||||
): ClaimDetail {
|
||||
@Required
|
||||
override val type: String
|
||||
get() = "jws"
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
override suspend fun verify(cryptoOperations: CryptoOperations, resolver: IResolver, logger: ILogger) {
|
||||
val claimDetail = JwsToken(data, logger = logger)
|
||||
DidKeyResolver.verifyJws(claimDetail, cryptoOperations, resolver, logger = logger)
|
||||
}
|
||||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.DidKeyResolver
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsToken
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.Required
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("jws")
|
||||
data class SignedClaimDetail(
|
||||
val data: String
|
||||
): ClaimDetail {
|
||||
@Required
|
||||
override val type: String
|
||||
get() = "jws"
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
override suspend fun verify(cryptoOperations: CryptoOperations, resolver: IResolver, logger: ILogger) {
|
||||
val claimDetail = JwsToken(data, logger = logger)
|
||||
DidKeyResolver.verifyJws(claimDetail, cryptoOperations, resolver, logger = logger)
|
||||
}
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import kotlinx.serialization.Required
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("unsigned")
|
||||
data class UnsignedClaimDetail(
|
||||
val data: List<Map<String, String>>
|
||||
): ClaimDetail {
|
||||
@Required
|
||||
override val type: String
|
||||
get() = "unsigned"
|
||||
|
||||
override suspend fun verify(cryptoOperations: CryptoOperations, resolver: IResolver, logger: ILogger) {
|
||||
// nothing to do
|
||||
}
|
||||
package com.microsoft.did.sdk.credentials
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import kotlinx.serialization.Required
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("unsigned")
|
||||
data class UnsignedClaimDetail(
|
||||
val data: List<Map<String, String>>
|
||||
): ClaimDetail {
|
||||
@Required
|
||||
override val type: String
|
||||
get() = "unsigned"
|
||||
|
||||
override suspend fun verify(cryptoOperations: CryptoOperations, resolver: IResolver, logger: ILogger) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
|
@ -1,148 +1,148 @@
|
|||
package com.microsoft.did.sdk.crypto.keyStore
|
||||
|
||||
import com.microsoft.did.sdk.crypto.keys.*
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.JwaCryptoConverter
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class InMemoryKeyStore(logger: ILogger): IKeyStore(logger) {
|
||||
private val secretKeys: MutableMap<String, KeyContainer<SecretKey>> = mutableMapOf()
|
||||
private val privateKeys: MutableMap<String, KeyContainer<PrivateKey>> = mutableMapOf()
|
||||
private val publicKeys: MutableMap<String, KeyContainer<PublicKey>> = mutableMapOf()
|
||||
|
||||
override fun getSecretKey(keyReference: String): KeyContainer<SecretKey> {
|
||||
return secretKeys[keyReference]?: throw logger.error("key $keyReference does not exist.")
|
||||
}
|
||||
|
||||
override fun getPrivateKey(keyReference: String): KeyContainer<PrivateKey> {
|
||||
return privateKeys[keyReference]?: throw logger.error("key $keyReference does not exist.")
|
||||
}
|
||||
|
||||
override fun getPublicKey(keyReference: String): KeyContainer<PublicKey> {
|
||||
return if (publicKeys.containsKey(keyReference)) {
|
||||
publicKeys[keyReference]!!
|
||||
} else {
|
||||
val keyContainer = privateKeys[keyReference] ?: throw logger.error("key $keyReference does not exist.")
|
||||
KeyContainer(
|
||||
keyContainer.kty,
|
||||
keyContainer.keys.map { it.getPublicKey() },
|
||||
keyContainer.use,
|
||||
keyContainer.alg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSecretKeyById(keyId: String): SecretKey? {
|
||||
return findKeyMatchingIdIn(secretKeys, keyId)
|
||||
}
|
||||
|
||||
override fun getPrivateKeyById(keyId: String): PrivateKey? {
|
||||
return findKeyMatchingIdIn(privateKeys, keyId)
|
||||
}
|
||||
|
||||
override fun getPublicKeyById(keyId: String): PublicKey? {
|
||||
return findKeyMatchingIdIn(publicKeys, keyId) ?:
|
||||
findKeyMatchingIdIn(privateKeys, keyId)?.getPublicKey()
|
||||
}
|
||||
|
||||
private fun <T: IKeyStoreItem> findKeyMatchingIdIn(map: Map<String, KeyContainer<T>>, keyId: String): T? {
|
||||
return map.map {
|
||||
val key = it.value.keys.firstOrNull {
|
||||
it.kid == keyId
|
||||
}
|
||||
if (key != null) {
|
||||
key
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.firstOrNull {
|
||||
it != null
|
||||
}
|
||||
}
|
||||
|
||||
override fun save(keyReference: String, key: SecretKey) {
|
||||
if (secretKeys.containsKey(keyReference)) {
|
||||
val keyContainer = secretKeys[keyReference]!!
|
||||
val keys = keyContainer.keys.toMutableList()
|
||||
keys.add(key)
|
||||
secretKeys[keyReference] = KeyContainer(
|
||||
keyContainer.kty,
|
||||
keys,
|
||||
keyContainer.use,
|
||||
keyContainer.alg
|
||||
)
|
||||
} else {
|
||||
secretKeys[keyReference] = KeyContainer<SecretKey>(
|
||||
key.kty,
|
||||
listOf(key),
|
||||
key.use,
|
||||
key.alg?.let { JwaCryptoConverter.jwaAlgToWebCrypto(it, logger = logger) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun save(keyReference: String, key: PrivateKey) {
|
||||
if (privateKeys.containsKey(keyReference)) {
|
||||
val keyContainer = privateKeys[keyReference]!!
|
||||
val keys = keyContainer.keys.toMutableList()
|
||||
keys.add(key)
|
||||
privateKeys[keyReference] = KeyContainer(
|
||||
keyContainer.kty,
|
||||
keys,
|
||||
keyContainer.use,
|
||||
keyContainer.alg
|
||||
)
|
||||
} else {
|
||||
privateKeys[keyReference] = KeyContainer<PrivateKey>(
|
||||
key.kty,
|
||||
listOf(key),
|
||||
key.use,
|
||||
key.alg?.let { JwaCryptoConverter.jwaAlgToWebCrypto(it, logger = logger) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun save(keyReference: String, key: PublicKey) {
|
||||
if (publicKeys.containsKey(keyReference)) {
|
||||
val keyContainer = publicKeys[keyReference]!!
|
||||
val keys = keyContainer.keys.toMutableList()
|
||||
keys.add(key)
|
||||
publicKeys[keyReference] = KeyContainer(
|
||||
keyContainer.kty,
|
||||
keys,
|
||||
keyContainer.use,
|
||||
keyContainer.alg
|
||||
)
|
||||
} else {
|
||||
publicKeys[keyReference] = KeyContainer<PublicKey>(
|
||||
key.kty,
|
||||
listOf(key),
|
||||
key.use,
|
||||
key.alg?.let { JwaCryptoConverter.jwaAlgToWebCrypto(it, logger = logger) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun list(): Map<String, KeyStoreListItem> {
|
||||
val result = mutableMapOf<String, KeyStoreListItem>()
|
||||
secretKeys.forEach {
|
||||
result[it.key] = KeyStoreListItem(
|
||||
it.value.kty,
|
||||
it.value.keys.filter { it.kid != null }.map { it.kid!! }.toMutableList()
|
||||
)
|
||||
}
|
||||
privateKeys.forEach {
|
||||
result[it.key] = KeyStoreListItem(
|
||||
it.value.kty,
|
||||
it.value.keys.filter { it.kid != null }.map { it.kid!! }.toMutableList()
|
||||
)
|
||||
}
|
||||
publicKeys.forEach {
|
||||
result[it.key] = KeyStoreListItem(
|
||||
it.value.kty,
|
||||
it.value.keys.filter { it.kid != null }.map { it.kid!! }.toMutableList()
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
package com.microsoft.did.sdk.crypto.keyStore
|
||||
|
||||
import com.microsoft.did.sdk.crypto.keys.*
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.JwaCryptoConverter
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class InMemoryKeyStore(logger: ILogger): IKeyStore(logger) {
|
||||
private val secretKeys: MutableMap<String, KeyContainer<SecretKey>> = mutableMapOf()
|
||||
private val privateKeys: MutableMap<String, KeyContainer<PrivateKey>> = mutableMapOf()
|
||||
private val publicKeys: MutableMap<String, KeyContainer<PublicKey>> = mutableMapOf()
|
||||
|
||||
override fun getSecretKey(keyReference: String): KeyContainer<SecretKey> {
|
||||
return secretKeys[keyReference]?: throw logger.error("key $keyReference does not exist.")
|
||||
}
|
||||
|
||||
override fun getPrivateKey(keyReference: String): KeyContainer<PrivateKey> {
|
||||
return privateKeys[keyReference]?: throw logger.error("key $keyReference does not exist.")
|
||||
}
|
||||
|
||||
override fun getPublicKey(keyReference: String): KeyContainer<PublicKey> {
|
||||
return if (publicKeys.containsKey(keyReference)) {
|
||||
publicKeys[keyReference]!!
|
||||
} else {
|
||||
val keyContainer = privateKeys[keyReference] ?: throw logger.error("key $keyReference does not exist.")
|
||||
KeyContainer(
|
||||
keyContainer.kty,
|
||||
keyContainer.keys.map { it.getPublicKey() },
|
||||
keyContainer.use,
|
||||
keyContainer.alg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSecretKeyById(keyId: String): SecretKey? {
|
||||
return findKeyMatchingIdIn(secretKeys, keyId)
|
||||
}
|
||||
|
||||
override fun getPrivateKeyById(keyId: String): PrivateKey? {
|
||||
return findKeyMatchingIdIn(privateKeys, keyId)
|
||||
}
|
||||
|
||||
override fun getPublicKeyById(keyId: String): PublicKey? {
|
||||
return findKeyMatchingIdIn(publicKeys, keyId) ?:
|
||||
findKeyMatchingIdIn(privateKeys, keyId)?.getPublicKey()
|
||||
}
|
||||
|
||||
private fun <T: IKeyStoreItem> findKeyMatchingIdIn(map: Map<String, KeyContainer<T>>, keyId: String): T? {
|
||||
return map.map {
|
||||
val key = it.value.keys.firstOrNull {
|
||||
it.kid == keyId
|
||||
}
|
||||
if (key != null) {
|
||||
key
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.firstOrNull {
|
||||
it != null
|
||||
}
|
||||
}
|
||||
|
||||
override fun save(keyReference: String, key: SecretKey) {
|
||||
if (secretKeys.containsKey(keyReference)) {
|
||||
val keyContainer = secretKeys[keyReference]!!
|
||||
val keys = keyContainer.keys.toMutableList()
|
||||
keys.add(key)
|
||||
secretKeys[keyReference] = KeyContainer(
|
||||
keyContainer.kty,
|
||||
keys,
|
||||
keyContainer.use,
|
||||
keyContainer.alg
|
||||
)
|
||||
} else {
|
||||
secretKeys[keyReference] = KeyContainer<SecretKey>(
|
||||
key.kty,
|
||||
listOf(key),
|
||||
key.use,
|
||||
key.alg?.let { JwaCryptoConverter.jwaAlgToWebCrypto(it, logger = logger) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun save(keyReference: String, key: PrivateKey) {
|
||||
if (privateKeys.containsKey(keyReference)) {
|
||||
val keyContainer = privateKeys[keyReference]!!
|
||||
val keys = keyContainer.keys.toMutableList()
|
||||
keys.add(key)
|
||||
privateKeys[keyReference] = KeyContainer(
|
||||
keyContainer.kty,
|
||||
keys,
|
||||
keyContainer.use,
|
||||
keyContainer.alg
|
||||
)
|
||||
} else {
|
||||
privateKeys[keyReference] = KeyContainer<PrivateKey>(
|
||||
key.kty,
|
||||
listOf(key),
|
||||
key.use,
|
||||
key.alg?.let { JwaCryptoConverter.jwaAlgToWebCrypto(it, logger = logger) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun save(keyReference: String, key: PublicKey) {
|
||||
if (publicKeys.containsKey(keyReference)) {
|
||||
val keyContainer = publicKeys[keyReference]!!
|
||||
val keys = keyContainer.keys.toMutableList()
|
||||
keys.add(key)
|
||||
publicKeys[keyReference] = KeyContainer(
|
||||
keyContainer.kty,
|
||||
keys,
|
||||
keyContainer.use,
|
||||
keyContainer.alg
|
||||
)
|
||||
} else {
|
||||
publicKeys[keyReference] = KeyContainer<PublicKey>(
|
||||
key.kty,
|
||||
listOf(key),
|
||||
key.use,
|
||||
key.alg?.let { JwaCryptoConverter.jwaAlgToWebCrypto(it, logger = logger) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun list(): Map<String, KeyStoreListItem> {
|
||||
val result = mutableMapOf<String, KeyStoreListItem>()
|
||||
secretKeys.forEach {
|
||||
result[it.key] = KeyStoreListItem(
|
||||
it.value.kty,
|
||||
it.value.keys.filter { it.kid != null }.map { it.kid!! }.toMutableList()
|
||||
)
|
||||
}
|
||||
privateKeys.forEach {
|
||||
result[it.key] = KeyStoreListItem(
|
||||
it.value.kty,
|
||||
it.value.keys.filter { it.kid != null }.map { it.kid!! }.toMutableList()
|
||||
)
|
||||
}
|
||||
publicKeys.forEach {
|
||||
result[it.key] = KeyStoreListItem(
|
||||
it.value.kty,
|
||||
it.value.keys.filter { it.kid != null }.map { it.kid!! }.toMutableList()
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package com.microsoft.did.sdk.crypto.keyStore
|
||||
|
||||
import com.microsoft.did.sdk.crypto.keys.KeyType
|
||||
|
||||
data class KeyStoreListItem (val kty: KeyType, val kids: MutableList<String>) {
|
||||
fun getLatestKeyId(): String {
|
||||
return kids.first()
|
||||
}
|
||||
package com.microsoft.did.sdk.crypto.keyStore
|
||||
|
||||
import com.microsoft.did.sdk.crypto.keys.KeyType
|
||||
|
||||
data class KeyStoreListItem (val kty: KeyType, val kids: MutableList<String>) {
|
||||
fun getLatestKeyId(): String {
|
||||
return kids.first()
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package com.microsoft.did.sdk.crypto.keys
|
||||
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
interface IKeyStoreItem {
|
||||
val kid: String
|
||||
package com.microsoft.did.sdk.crypto.keys
|
||||
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
interface IKeyStoreItem {
|
||||
val kid: String
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package com.microsoft.did.sdk.crypto.keys
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.KeyUse
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
|
||||
class KeyContainer<T: IKeyStoreItem> (val kty: KeyType, val keys: List<T> = emptyList(), val use: KeyUse? = null, val alg: Algorithm? = null) {
|
||||
fun getKey(id: String? = null): T {
|
||||
return if (id.isNullOrBlank()) {
|
||||
keys.first()
|
||||
} else {
|
||||
keys.first { it.kid == id }
|
||||
}
|
||||
}
|
||||
package com.microsoft.did.sdk.crypto.keys
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.KeyUse
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
|
||||
class KeyContainer<T: IKeyStoreItem> (val kty: KeyType, val keys: List<T> = emptyList(), val use: KeyUse? = null, val alg: Algorithm? = null) {
|
||||
fun getKey(id: String? = null): T {
|
||||
return if (id.isNullOrBlank()) {
|
||||
keys.first()
|
||||
} else {
|
||||
keys.first { it.kid == id }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +1,46 @@
|
|||
package com.microsoft.did.sdk.crypto.keys.rsa
|
||||
|
||||
import com.microsoft.did.sdk.crypto.keys.KeyType
|
||||
import com.microsoft.did.sdk.crypto.keys.PrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.PublicKey
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.JsonWebKey
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class RsaPrivateKey(jwk: JsonWebKey, logger: ILogger): PrivateKey(jwk, logger) {
|
||||
override var kty = KeyType.RSA
|
||||
override var alg: String? = if (key.alg != null) key.alg!! else "RS256"
|
||||
|
||||
val n = key.n
|
||||
val e = key.e
|
||||
val d = key.d
|
||||
val p = key.p
|
||||
val q = key.q
|
||||
val dp = key.dp
|
||||
val dq = key.dq
|
||||
val qi = key.qi
|
||||
val oth = key.oth
|
||||
|
||||
override fun toJWK(): JsonWebKey {
|
||||
return JsonWebKey(
|
||||
kty = kty.value,
|
||||
alg = alg,
|
||||
kid = kid,
|
||||
key_ops = key_ops?.map { use -> use.value },
|
||||
use = use?.value,
|
||||
|
||||
n = this.n,
|
||||
e = this.e,
|
||||
d = this.d,
|
||||
p = this.p,
|
||||
q = this.q,
|
||||
dp = this.dp,
|
||||
dq = this.dq,
|
||||
qi = this.qi,
|
||||
oth = this.oth
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPublicKey(): PublicKey {
|
||||
return RsaPublicKey(this.toJWK(), logger = logger)
|
||||
}
|
||||
package com.microsoft.did.sdk.crypto.keys.rsa
|
||||
|
||||
import com.microsoft.did.sdk.crypto.keys.KeyType
|
||||
import com.microsoft.did.sdk.crypto.keys.PrivateKey
|
||||
import com.microsoft.did.sdk.crypto.keys.PublicKey
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.JsonWebKey
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class RsaPrivateKey(jwk: JsonWebKey, logger: ILogger): PrivateKey(jwk, logger) {
|
||||
override var kty = KeyType.RSA
|
||||
override var alg: String? = if (key.alg != null) key.alg!! else "RS256"
|
||||
|
||||
val n = key.n
|
||||
val e = key.e
|
||||
val d = key.d
|
||||
val p = key.p
|
||||
val q = key.q
|
||||
val dp = key.dp
|
||||
val dq = key.dq
|
||||
val qi = key.qi
|
||||
val oth = key.oth
|
||||
|
||||
override fun toJWK(): JsonWebKey {
|
||||
return JsonWebKey(
|
||||
kty = kty.value,
|
||||
alg = alg,
|
||||
kid = kid,
|
||||
key_ops = key_ops?.map { use -> use.value },
|
||||
use = use?.value,
|
||||
|
||||
n = this.n,
|
||||
e = this.e,
|
||||
d = this.d,
|
||||
p = this.p,
|
||||
q = this.q,
|
||||
dp = this.dp,
|
||||
dq = this.dq,
|
||||
qi = this.qi,
|
||||
oth = this.oth
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPublicKey(): PublicKey {
|
||||
return RsaPublicKey(this.toJWK(), logger = logger)
|
||||
}
|
||||
}
|
|
@ -1,29 +1,29 @@
|
|||
package com.microsoft.did.sdk.crypto.keys.rsa
|
||||
|
||||
import com.microsoft.did.sdk.crypto.keys.KeyType
|
||||
import com.microsoft.did.sdk.crypto.keys.PublicKey
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.JsonWebKey
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class RsaPublicKey(jwk: JsonWebKey, logger: ILogger): PublicKey(jwk, logger = logger) {
|
||||
override fun minimumAlphabeticJwk(): String {
|
||||
return "{\"e\":\"${e}\",\"kty\":\"${kty.value}\",\"n\":\"${n}\"}"
|
||||
}
|
||||
|
||||
override var kty = KeyType.RSA
|
||||
|
||||
val n = key.n
|
||||
val e = key.e
|
||||
|
||||
override fun toJWK(): JsonWebKey {
|
||||
return JsonWebKey(
|
||||
kty = kty.value,
|
||||
alg = alg,
|
||||
use = use?.value,
|
||||
key_ops = key_ops?.map { use -> use.value },
|
||||
kid = kid,
|
||||
e = e,
|
||||
n = n
|
||||
)
|
||||
}
|
||||
package com.microsoft.did.sdk.crypto.keys.rsa
|
||||
|
||||
import com.microsoft.did.sdk.crypto.keys.KeyType
|
||||
import com.microsoft.did.sdk.crypto.keys.PublicKey
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.JsonWebKey
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
class RsaPublicKey(jwk: JsonWebKey, logger: ILogger): PublicKey(jwk, logger = logger) {
|
||||
override fun minimumAlphabeticJwk(): String {
|
||||
return "{\"e\":\"${e}\",\"kty\":\"${kty.value}\",\"n\":\"${n}\"}"
|
||||
}
|
||||
|
||||
override var kty = KeyType.RSA
|
||||
|
||||
val n = key.n
|
||||
val e = key.e
|
||||
|
||||
override fun toJWK(): JsonWebKey {
|
||||
return JsonWebKey(
|
||||
kty = kty.value,
|
||||
alg = alg,
|
||||
use = use?.value,
|
||||
key_ops = key_ops?.map { use -> use.value },
|
||||
kid = kid,
|
||||
e = e,
|
||||
n = n
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,37 +1,37 @@
|
|||
package com.microsoft.did.sdk.crypto.models
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.W3cCryptoApiConstants
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
|
||||
// convenience class for SHA algorithms
|
||||
class Sha(algorithm: Algorithm) {
|
||||
companion object {
|
||||
val Sha1 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha1.value
|
||||
)
|
||||
val Sha224 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha224.value
|
||||
)
|
||||
val Sha256 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha256.value
|
||||
)
|
||||
val Sha384 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha384.value
|
||||
)
|
||||
val Sha512 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha512.value
|
||||
)
|
||||
fun get(length: Int, logger: ILogger): Algorithm {
|
||||
return when (length) {
|
||||
1 -> Sha1
|
||||
224 -> Sha224
|
||||
256 -> Sha256
|
||||
384 -> Sha384
|
||||
512 -> Sha512
|
||||
else -> throw logger.error("No SHA at this length.")
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.microsoft.did.sdk.crypto.models
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.W3cCryptoApiConstants
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
|
||||
|
||||
// convenience class for SHA algorithms
|
||||
class Sha(algorithm: Algorithm) {
|
||||
companion object {
|
||||
val Sha1 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha1.value
|
||||
)
|
||||
val Sha224 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha224.value
|
||||
)
|
||||
val Sha256 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha256.value
|
||||
)
|
||||
val Sha384 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha384.value
|
||||
)
|
||||
val Sha512 = Algorithm(
|
||||
name = W3cCryptoApiConstants.Sha512.value
|
||||
)
|
||||
fun get(length: Int, logger: ILogger): Algorithm {
|
||||
return when (length) {
|
||||
1 -> Sha1
|
||||
224 -> Sha224
|
||||
256 -> Sha256
|
||||
384 -> Sha384
|
||||
512 -> Sha512
|
||||
else -> throw logger.error("No SHA at this length.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithms
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
|
||||
class AesKeyGenParams(name: String, val length: UShort, additionalParams: Map<String, String> = emptyMap()): Algorithm(name, additionalParams) {
|
||||
package com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithms
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
|
||||
class AesKeyGenParams(name: String, val length: UShort, additionalParams: Map<String, String> = emptyMap()): Algorithm(name, additionalParams) {
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
package com.microsoft.did.sdk.crypto.models.webCryptoApi
|
||||
|
||||
open class RsaHashedKeyAlgorithm (modulusLength: ULong, publicExponent: ULong, val hash: Algorithm,
|
||||
additionalParams: Map<String, String> = emptyMap()):
|
||||
package com.microsoft.did.sdk.crypto.models.webCryptoApi
|
||||
|
||||
open class RsaHashedKeyAlgorithm (modulusLength: ULong, publicExponent: ULong, val hash: Algorithm,
|
||||
additionalParams: Map<String, String> = emptyMap()):
|
||||
RsaKeyAlgorithm(modulusLength, publicExponent, additionalParams)
|
|
@ -1,8 +1,8 @@
|
|||
package com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithms
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.RsaKeyAlgorithm
|
||||
|
||||
open class RsaHashedKeyGenParams(modulusLength: ULong, publicExponent: ULong,
|
||||
val hash: Algorithm, additionalParams: Map<String, String> = emptyMap()):
|
||||
package com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithms
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.Algorithm
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.RsaKeyAlgorithm
|
||||
|
||||
open class RsaHashedKeyGenParams(modulusLength: ULong, publicExponent: ULong,
|
||||
val hash: Algorithm, additionalParams: Map<String, String> = emptyMap()):
|
||||
RsaKeyAlgorithm(modulusLength, publicExponent, additionalParams)
|
|
@ -1,4 +1,4 @@
|
|||
package com.microsoft.did.sdk.crypto.models.webCryptoApi
|
||||
|
||||
open class RsaKeyAlgorithm(val modulusLength: ULong, val publicExponent: ULong,
|
||||
package com.microsoft.did.sdk.crypto.models.webCryptoApi
|
||||
|
||||
open class RsaKeyAlgorithm(val modulusLength: ULong, val publicExponent: ULong,
|
||||
additionalParams: Map<String, String> = emptyMap()): Algorithm(W3cCryptoApiConstants.RsaSsaPkcs1V15.value, additionalParams)
|
|
@ -1,5 +1,5 @@
|
|||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.SubtleCrypto
|
||||
|
||||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
import com.microsoft.did.sdk.crypto.models.webCryptoApi.SubtleCrypto
|
||||
|
||||
data class SubtleCryptoMapItem (val subtleCrypto: SubtleCrypto, val scope: SubtleCryptoScope)
|
|
@ -1,7 +1,7 @@
|
|||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
enum class SubtleCryptoScope {
|
||||
All,
|
||||
Private,
|
||||
Public
|
||||
package com.microsoft.did.sdk.crypto.plugins
|
||||
|
||||
enum class SubtleCryptoScope {
|
||||
All,
|
||||
Private,
|
||||
Public
|
||||
}
|
|
@ -1,49 +1,49 @@
|
|||
package com.microsoft.did.sdk.crypto.protocols.jose
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.keys.PublicKey
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsToken
|
||||
import com.microsoft.did.sdk.identifier.Identifier
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
|
||||
object DidKeyResolver {
|
||||
suspend fun resolveIdentiferFromKid(kid: String, crypto: CryptoOperations, resolver: IResolver, logger: ILogger): Identifier {
|
||||
val did = Regex("^([^#]+)#.+$").matchEntire(kid) ?: throw logger.error("No identifier found in key id")
|
||||
return resolver.resolve(did.groupValues[1], crypto)
|
||||
}
|
||||
|
||||
suspend fun resolveKeyFromKid(kid: String, crypto: CryptoOperations, resolver: IResolver, logger: ILogger): PublicKey {
|
||||
val identifier = resolveIdentiferFromKid(kid, crypto, resolver, logger = logger)
|
||||
val did = Regex("^[^#]+(#.+)$").matchEntire(kid)!!
|
||||
return identifier.document.publicKeys.filter {
|
||||
it.publicKeyJwk.kid?.endsWith(did.groupValues[1]) ?: false ||
|
||||
it.id.endsWith(did.groupValues[1])
|
||||
}.firstOrNull()?.toPublicKey(logger = logger) ?: throw logger.error("Could not find key $kid")
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
suspend fun verifyJws(jws: JwsToken, crypto: CryptoOperations, forDid: Identifier, logger: ILogger) {
|
||||
val keys = forDid.document.publicKeys.map {
|
||||
it.toPublicKey(logger = logger)
|
||||
}
|
||||
jws.verify(crypto, keys)
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
suspend fun verifyJws(jws: JwsToken, crypto: CryptoOperations, resolver: IResolver, forDid: String? = null, logger: ILogger) {
|
||||
if (forDid.isNullOrBlank()) {
|
||||
val sender = resolver.resolve(forDid!!, crypto)
|
||||
// verify the request
|
||||
verifyJws(jws, crypto, sender, logger = logger)
|
||||
} else {
|
||||
val keys = mutableListOf<PublicKey>()
|
||||
jws.signatures.forEachIndexed { index, signature ->
|
||||
val kid = signature.getKid(logger = logger) ?: throw logger.error("Could not find kid in signature $index")
|
||||
keys.add(resolveKeyFromKid(kid, crypto, resolver, logger))
|
||||
}
|
||||
jws.verify(crypto, keys)
|
||||
}
|
||||
}
|
||||
package com.microsoft.did.sdk.crypto.protocols.jose
|
||||
|
||||
import com.microsoft.did.sdk.crypto.CryptoOperations
|
||||
import com.microsoft.did.sdk.crypto.keys.PublicKey
|
||||
import com.microsoft.did.sdk.crypto.protocols.jose.jws.JwsToken
|
||||
import com.microsoft.did.sdk.identifier.Identifier
|
||||
import com.microsoft.did.sdk.resolvers.IResolver
|
||||
import com.microsoft.did.sdk.utilities.ILogger
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
|
||||
object DidKeyResolver {
|
||||
suspend fun resolveIdentiferFromKid(kid: String, crypto: CryptoOperations, resolver: IResolver, logger: ILogger): Identifier {
|
||||
val did = Regex("^([^#]+)#.+$").matchEntire(kid) ?: throw logger.error("No identifier found in key id")
|
||||
return resolver.resolve(did.groupValues[1], crypto)
|
||||
}
|
||||
|
||||
suspend fun resolveKeyFromKid(kid: String, crypto: CryptoOperations, resolver: IResolver, logger: ILogger): PublicKey {
|
||||
val identifier = resolveIdentiferFromKid(kid, crypto, resolver, logger = logger)
|
||||
val did = Regex("^[^#]+(#.+)$").matchEntire(kid)!!
|
||||
return identifier.document.publicKeys.filter {
|
||||
it.publicKeyJwk.kid?.endsWith(did.groupValues[1]) ?: false ||
|
||||
it.id.endsWith(did.groupValues[1])
|
||||
}.firstOrNull()?.toPublicKey(logger = logger) ?: throw logger.error("Could not find key $kid")
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
suspend fun verifyJws(jws: JwsToken, crypto: CryptoOperations, forDid: Identifier, logger: ILogger) {
|
||||
val keys = forDid.document.publicKeys.map {
|
||||
it.toPublicKey(logger = logger)
|
||||
}
|
||||
jws.verify(crypto, keys)
|
||||
}
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
suspend fun verifyJws(jws: JwsToken, crypto: CryptoOperations, resolver: IResolver, forDid: String? = null, logger: ILogger) {
|
||||
if (forDid.isNullOrBlank()) {
|
||||
val sender = resolver.resolve(forDid!!, crypto)
|
||||
// verify the request
|
||||
verifyJws(jws, crypto, sender, logger = logger)
|
||||
} else {
|
||||
val keys = mutableListOf<PublicKey>()
|
||||
jws.signatures.forEachIndexed { index, signature ->
|
||||
val kid = signature.getKid(logger = logger) ?: throw logger.error("Could not find kid in signature $index")
|
||||
keys.add(resolveKeyFromKid(kid, crypto, resolver, logger))
|
||||
}
|
||||
jws.verify(crypto, keys)
|
||||
}
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче