Merge "Add crypto libraries"
This commit is contained in:
commit
68ed7fcc63
5 changed files with 332 additions and 0 deletions
|
@ -44,6 +44,7 @@ rust_test {
|
|||
"libandroid_hardware_keymint",
|
||||
"libanyhow",
|
||||
"libbinder_rs",
|
||||
"libkeystore2_crypto_bindgen",
|
||||
"libkeystore2_selinux",
|
||||
"liblazy_static",
|
||||
"liblibsqlite3_sys",
|
||||
|
@ -51,6 +52,24 @@ rust_test {
|
|||
"librusqlite",
|
||||
"libthiserror",
|
||||
],
|
||||
shared_libs: ["libkeystore2_crypto"],
|
||||
}
|
||||
|
||||
cc_library {
|
||||
name: "libkeystore2_crypto",
|
||||
srcs: ["src/crypto.cpp"],
|
||||
shared_libs: [
|
||||
"libcrypto",
|
||||
"liblog",
|
||||
],
|
||||
}
|
||||
|
||||
rust_bindgen {
|
||||
name: "libkeystore2_crypto_bindgen",
|
||||
wrapper_src: "src/crypto.hpp",
|
||||
crate_name: "keystore2_crypto_bindgen",
|
||||
source_stem: "bindings",
|
||||
host_supported: true,
|
||||
}
|
||||
|
||||
rust_binary {
|
||||
|
|
194
keystore2/src/crypto.cpp
Normal file
194
keystore2/src/crypto.cpp
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "keystore2"
|
||||
|
||||
#include "crypto.hpp"
|
||||
|
||||
#include <log/log.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
// Copied from system/security/keystore/blob.h.
|
||||
|
||||
constexpr size_t kGcmTagLength = 128 / 8;
|
||||
constexpr size_t kAes128KeySizeBytes = 128 / 8;
|
||||
|
||||
// Copied from system/security/keystore/blob.cpp.
|
||||
|
||||
#if defined(__clang__)
|
||||
#define OPTNONE __attribute__((optnone))
|
||||
#elif defined(__GNUC__)
|
||||
#define OPTNONE __attribute__((optimize("O0")))
|
||||
#else
|
||||
#error Need a definition for OPTNONE
|
||||
#endif
|
||||
|
||||
class ArrayEraser {
|
||||
public:
|
||||
ArrayEraser(uint8_t* arr, size_t size) : mArr(arr), mSize(size) {}
|
||||
OPTNONE ~ArrayEraser() { std::fill(mArr, mArr + mSize, 0); }
|
||||
|
||||
private:
|
||||
volatile uint8_t* mArr;
|
||||
size_t mSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a EVP_CIPHER appropriate for the given key size.
|
||||
*/
|
||||
const EVP_CIPHER* getAesCipherForKey(size_t key_size) {
|
||||
const EVP_CIPHER* cipher = EVP_aes_256_gcm();
|
||||
if (key_size == kAes128KeySizeBytes) {
|
||||
cipher = EVP_aes_128_gcm();
|
||||
}
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encrypt 'len' data at 'in' with AES-GCM, using 128-bit or 256-bit key at 'key', 96-bit IV at
|
||||
* 'iv' and write output to 'out' (which may be the same location as 'in') and 128-bit tag to
|
||||
* 'tag'.
|
||||
*/
|
||||
bool AES_gcm_encrypt(const uint8_t* in, uint8_t* out, size_t len, const uint8_t* key,
|
||||
size_t key_size, const uint8_t* iv, uint8_t* tag) {
|
||||
|
||||
// There can be 128-bit and 256-bit keys
|
||||
const EVP_CIPHER* cipher = getAesCipherForKey(key_size);
|
||||
|
||||
bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
|
||||
|
||||
EVP_EncryptInit_ex(ctx.get(), cipher, nullptr /* engine */, key, iv);
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), 0 /* no padding needed with GCM */);
|
||||
|
||||
std::vector<uint8_t> out_tmp(len);
|
||||
uint8_t* out_pos = out_tmp.data();
|
||||
int out_len;
|
||||
|
||||
EVP_EncryptUpdate(ctx.get(), out_pos, &out_len, in, len);
|
||||
out_pos += out_len;
|
||||
EVP_EncryptFinal_ex(ctx.get(), out_pos, &out_len);
|
||||
out_pos += out_len;
|
||||
if (out_pos - out_tmp.data() != static_cast<ssize_t>(len)) {
|
||||
ALOGD("Encrypted ciphertext is the wrong size, expected %zu, got %zd", len,
|
||||
out_pos - out_tmp.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::copy(out_tmp.data(), out_pos, out);
|
||||
EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, kGcmTagLength, tag);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decrypt 'len' data at 'in' with AES-GCM, using 128-bit or 256-bit key at 'key', 96-bit IV at
|
||||
* 'iv', checking 128-bit tag at 'tag' and writing plaintext to 'out'(which may be the same
|
||||
* location as 'in').
|
||||
*/
|
||||
bool AES_gcm_decrypt(const uint8_t* in, uint8_t* out, size_t len, const uint8_t* key,
|
||||
size_t key_size, const uint8_t* iv, const uint8_t* tag) {
|
||||
|
||||
// There can be 128-bit and 256-bit keys
|
||||
const EVP_CIPHER* cipher = getAesCipherForKey(key_size);
|
||||
|
||||
bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
|
||||
|
||||
EVP_DecryptInit_ex(ctx.get(), cipher, nullptr /* engine */, key, iv);
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), 0 /* no padding needed with GCM */);
|
||||
EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, kGcmTagLength, const_cast<uint8_t*>(tag));
|
||||
|
||||
std::vector<uint8_t> out_tmp(len);
|
||||
ArrayEraser out_eraser(out_tmp.data(), len);
|
||||
uint8_t* out_pos = out_tmp.data();
|
||||
int out_len;
|
||||
|
||||
EVP_DecryptUpdate(ctx.get(), out_pos, &out_len, in, len);
|
||||
out_pos += out_len;
|
||||
if (!EVP_DecryptFinal_ex(ctx.get(), out_pos, &out_len)) {
|
||||
ALOGE("Failed to decrypt blob; ciphertext or tag is likely corrupted");
|
||||
return false;
|
||||
}
|
||||
out_pos += out_len;
|
||||
if (out_pos - out_tmp.data() != static_cast<ssize_t>(len)) {
|
||||
ALOGE("Encrypted plaintext is the wrong size, expected %zu, got %zd", len,
|
||||
out_pos - out_tmp.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::copy(out_tmp.data(), out_pos, out);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copied from system/security/keystore/keymaster_enforcement.cpp.
|
||||
|
||||
class EvpMdCtx {
|
||||
public:
|
||||
EvpMdCtx() { EVP_MD_CTX_init(&ctx_); }
|
||||
~EvpMdCtx() { EVP_MD_CTX_cleanup(&ctx_); }
|
||||
|
||||
EVP_MD_CTX* get() { return &ctx_; }
|
||||
|
||||
private:
|
||||
EVP_MD_CTX ctx_;
|
||||
};
|
||||
|
||||
bool CreateKeyId(const uint8_t* key_blob, size_t len, km_id_t* out_id) {
|
||||
EvpMdCtx ctx;
|
||||
|
||||
uint8_t hash[EVP_MAX_MD_SIZE];
|
||||
unsigned int hash_len;
|
||||
if (EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr /* ENGINE */) &&
|
||||
EVP_DigestUpdate(ctx.get(), key_blob, len) &&
|
||||
EVP_DigestFinal_ex(ctx.get(), hash, &hash_len)) {
|
||||
assert(hash_len >= sizeof(*out_id));
|
||||
memcpy(out_id, hash, sizeof(*out_id));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copied from system/security/keystore/user_state.h
|
||||
|
||||
static constexpr size_t SALT_SIZE = 16;
|
||||
|
||||
// Copied from system/security/keystore/user_state.cpp.
|
||||
|
||||
void generateKeyFromPassword(uint8_t* key, size_t key_len, const char* pw, size_t pw_len,
|
||||
uint8_t* salt) {
|
||||
size_t saltSize;
|
||||
if (salt != nullptr) {
|
||||
saltSize = SALT_SIZE;
|
||||
} else {
|
||||
// Pre-gingerbread used this hardwired salt, readMasterKey will rewrite these when found
|
||||
salt = (uint8_t*)"keystore";
|
||||
// sizeof = 9, not strlen = 8
|
||||
saltSize = sizeof("keystore");
|
||||
}
|
||||
|
||||
const EVP_MD* digest = EVP_sha256();
|
||||
|
||||
// SHA1 was used prior to increasing the key size
|
||||
if (key_len == kAes128KeySizeBytes) {
|
||||
digest = EVP_sha1();
|
||||
}
|
||||
|
||||
PKCS5_PBKDF2_HMAC(pw, pw_len, salt, saltSize, 8192, digest, key_len, key);
|
||||
}
|
40
keystore2/src/crypto.hpp
Normal file
40
keystore2/src/crypto.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __CRYPTO_H__
|
||||
#define __CRYPTO_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
extern "C" {
|
||||
bool AES_gcm_encrypt(const uint8_t* in, uint8_t* out, size_t len,
|
||||
const uint8_t* key, size_t key_size, const uint8_t* iv, uint8_t* tag);
|
||||
bool AES_gcm_decrypt(const uint8_t* in, uint8_t* out, size_t len,
|
||||
const uint8_t* key, size_t key_size, const uint8_t* iv,
|
||||
const uint8_t* tag);
|
||||
|
||||
// Copied from system/security/keystore/keymaster_enforcement.h.
|
||||
typedef uint64_t km_id_t;
|
||||
|
||||
bool CreateKeyId(const uint8_t* key_blob, size_t len, km_id_t* out_id);
|
||||
|
||||
void generateKeyFromPassword(uint8_t* key, size_t key_len, const char* pw,
|
||||
size_t pw_len, uint8_t* salt);
|
||||
}
|
||||
|
||||
#endif // __CRYPTO_H__
|
78
keystore2/src/crypto.rs
Normal file
78
keystore2/src/crypto.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2020, The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use keystore2_crypto_bindgen::{
|
||||
generateKeyFromPassword, AES_gcm_decrypt, AES_gcm_encrypt, CreateKeyId,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt() {
|
||||
let input = vec![0; 16];
|
||||
let mut out = vec![0; 16];
|
||||
let mut out2 = vec![0; 16];
|
||||
let key = vec![0; 16];
|
||||
let iv = vec![0; 12];
|
||||
let mut tag = vec![0; 16];
|
||||
unsafe {
|
||||
let res = AES_gcm_encrypt(
|
||||
input.as_ptr(),
|
||||
out.as_mut_ptr(),
|
||||
16,
|
||||
key.as_ptr(),
|
||||
16,
|
||||
iv.as_ptr(),
|
||||
tag.as_mut_ptr(),
|
||||
);
|
||||
assert!(res);
|
||||
assert_ne!(out, input);
|
||||
assert_ne!(tag, input);
|
||||
let res = AES_gcm_decrypt(
|
||||
out.as_ptr(),
|
||||
out2.as_mut_ptr(),
|
||||
16,
|
||||
key.as_ptr(),
|
||||
16,
|
||||
iv.as_ptr(),
|
||||
tag.as_ptr(),
|
||||
);
|
||||
assert!(res);
|
||||
assert_eq!(out2, input);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_key_id() {
|
||||
let blob = vec![0; 16];
|
||||
let mut out: u64 = 0;
|
||||
unsafe {
|
||||
let res = CreateKeyId(blob.as_ptr(), 16, &mut out);
|
||||
assert!(res);
|
||||
assert_ne!(out, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_key_from_password() {
|
||||
let mut key = vec![0; 16];
|
||||
let pw = vec![0; 16];
|
||||
let mut salt = vec![0; 16];
|
||||
unsafe {
|
||||
generateKeyFromPassword(key.as_mut_ptr(), 16, pw.as_ptr(), 16, salt.as_mut_ptr());
|
||||
}
|
||||
assert_ne!(key, vec![0; 16]);
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
//! This crate implements the Android Keystore 2.0 service.
|
||||
|
||||
mod crypto;
|
||||
pub mod database;
|
||||
pub mod error;
|
||||
pub mod globals;
|
||||
|
|
Loading…
Reference in a new issue