ios - ¿Puedo obtener el módulo o exponente de un objeto SecKeyRef en Swift?
encryption cryptography (9)
En Swift, creé un objeto SecTrustCopyPublicKey
llamando a SecTrustCopyPublicKey
en algunos datos sin procesar del certificado X509. Este es el SecKeyRef
este objeto SecKeyRef
.
Optional(<SecKeyRef algorithm id: 1,
key type: RSAPublicKey,
version: 3, block size: 2048 bits,
exponent: {hex: 10001, decimal: 65537},
modulus: <omitted a bunch of hex data>,
addr: 0xsomeaddresshere>)
Básicamente, este objeto SecKeyRef
contiene un montón de información sobre la clave pública, pero parece que no hay forma de convertir realmente este SecKeyRef
en una cadena, NSData
o cualquier otra cosa (este es mi objetivo, es solo obtener una base64 pública llave).
Sin embargo, tengo una función que le puedo dar un modulus
y un exponent
, y solo calculará cuál es la clave pública. Lo probé pasando los datos que se registraron desde el SecKeyRef
anterior.
Pero de alguna manera no puedo acceder a esas propiedades desde el objeto SecKeyRef
(solo puedo ver todo el objeto en la consola, por ejemplo, no puedo hacer SecKeyRef.modulus
ni nada por el estilo, parece) .
Mi pregunta: ¿cómo puedo acceder a SecKeyRef.modulus
o, alternativamente, convertir este SecKeyRef
en NSData
o algo similar? Gracias
Editar
(para más información)
Estoy creando mi SecKeyRef
dinámicamente, a través de esta función tengo:
func bytesToPublicKey(certData: NSData) -> SecKeyRef? {
guard let certRef = SecCertificateCreateWithData(nil, certData) else { return nil }
var secTrust: SecTrustRef?
let secTrustStatus = SecTrustCreateWithCertificates(certRef, nil, &secTrust)
if secTrustStatus != errSecSuccess { return nil }
var resultType: SecTrustResultType = UInt32(0) // result will be ignored.
let evaluateStatus = SecTrustEvaluate(secTrust!, &resultType)
if evaluateStatus != errSecSuccess { return nil }
let publicKeyRef = SecTrustCopyPublicKey(secTrust!)
return publicKeyRef
}
Lo que hace es tomar la corriente de bytes brutos de un certificado (que puede ser transmitido desde, digamos, una pieza de hardware usando PKI), y luego lo convierte en SecKeyRef
.
Editar 2
(comentarios sobre las respuestas existentes al 7 de enero de 2015)
Esto no funciona:
let mirror = Mirror(reflecting: mySecKeyObject)
for case let (label?, value) in mirror.children {
print (label, value)
}
Esto da como resultado esta salida en la consola:
Some <Raw SecKeyRef object>
No estoy seguro de qué significa la cadena "Algunos".
Además, mirror.descendant("exponent")
(o "módulo") resulta nil
, aunque al imprimir el objeto sin procesar en la consola, puedo ver claramente que esas propiedades existen, y que de hecho están pobladas.
Además, si es posible, me gustaría evitar tener que guardar en el llavero, leer como NSData
y luego eliminarlo del llavero . Como se indica en la descripción de la recompensa, si esta es la única forma posible, cite una referencia autorizada. Gracias por todas las respuestas proporcionadas hasta ahora.
¿ SecCertificateCopyData()
usar SecCertificateCopyData()
? El CFData
resultante tiene un puente gratuito, creo.
Consulte https://developer.apple.com/library/ios/documentation/Security/Reference/certifkeytrustservices/ para ver la documentación relevante de la API.
De hecho, es posible extraer el módulo y el exponente sin usar ni llaveros ni API privada.
Está la función SecKeyCopyAttributes
( pública pero no documentada ) que extrae un CFDictionary
de una SecKey
. Una fuente útil para las claves de atributo es SecItemConstants.c
Al inspeccionar el contenido de este diccionario, encontramos una entrada "v_Data" : <binary>
. Su contenido es ASN con codificación DER para
SEQUENCE {
modulus INTEGER,
publicExponent INTEGER
}
Tenga en cuenta que los enteros se rellenan con un byte cero si son positivos y tienen un valor de 1 bit (para no confundirlos con un número negativo de dos complementos), por lo que puede encontrar un byte más de lo esperado. Si eso sucede, simplemente córtalo.
Puede implementar un analizador para este formato o, conociendo el tamaño de su clave, codificar la extracción. Para claves de 2048 bits (y exponente de 3 bytes), el formato resulta ser:
30|82010(a|0) # Sequence of length 0x010(a|0)
02|82010(1|0) # Integer of length 0x010(1|0)
(00)?<modulus>
02|03 # Integer of length 0x03
<exponent>
Para un total de 10 + 1? + 256 + 3 = 269 o 270 bytes.
import Foundation
extension String: Error {}
func parsePublicSecKey(publicKey: SecKey) -> (mod: Data, exp: Data) {
let pubAttributes = SecKeyCopyAttributes(publicKey) as! [String: Any]
// Check that this is really an RSA key
guard Int(pubAttributes[kSecAttrKeyType as String] as! String)
== Int(kSecAttrKeyTypeRSA as String) else {
throw "Tried to parse non-RSA key as RSA key"
}
// Check that this is really a public key
guard Int(pubAttributes[kSecAttrKeyClass as String] as! String)
== Int(kSecAttrKeyClassPublic as String)
else {
throw "Tried to parse non-public key as public key"
}
let keySize = pubAttributes[kSecAttrKeySizeInBits as String] as! Int
// Extract values
let pubData = pubAttributes[kSecValueData as String] as! Data
var modulus = pubData.subdata(in: 8..<(pubData.count - 5))
let exponent = pubData.subdata(in: (pubData.count - 3)..<pubData.count)
if modulus.count > keySize / 8 { // --> 257 bytes
modulus.removeFirst(1)
}
return (mod: modulus, exp: exponent)
}
(Terminé escribiendo un analizador ASN completo, por lo que este código no se prueba, ¡cuidado!)
Tenga en cuenta que puede extraer detalles de claves privadas de la misma manera. Usando la terminología DER, este es el formato de v_Data
:
PrivateKey ::= SEQUENCE {
version INTEGER,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1) (dmp1)
exponent2 INTEGER, -- d mod (q-1) (dmq1)
coefficient INTEGER, -- (inverse of q) mod p (coeff)
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
Analizar esto a mano es probablemente desacertado ya que cualquiera de los enteros puede haber sido rellenado.
Nota bene: El formato de la clave pública es diferente si la clave se ha generado en macOS; la estructura dada arriba está envuelta así:
SEQUENCE {
id OBJECTID,
PublicKey BITSTRING
}
La cadena de bits es ASN con codificación DER de la forma anterior.
Encontré cómo obtener datos para SecKey
.
let publicKey: SecKey = ...
let data = SecKeyCopyExternalRepresentation(publicKey, nil)
Esto parece funcionar bien y he podido comparar claves públicas con éxito.
Esto es en Swift 3 (Xcode 8 beta 3)
Escribí esta base en la respuesta de otra persona en . Actualmente lo estoy usando en mi producción, pero me complace utilizar otra solución que no requiere escribir en el llavero.
- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey host:(NSString*)host {
NSString *tag = [NSString stringWithFormat:@"%@.%@",[[NSBundle mainBundle] bundleIdentifier], host];
const char* publicKeyIdentifier = [tag cStringUsingEncoding:NSUTF8StringEncoding];
NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:strlen(publicKeyIdentifier) * sizeof(char)];
OSStatus sanityCheck = noErr;
// NSData * publicKeyBits = nil;
CFTypeRef publicKeyBits;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
// Set the public key query dictionary.
[queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(id)kSecAttrApplicationTag];
[queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnData];
[queryPublicKey setObject:(__bridge id)givenKey forKey:(__bridge id)kSecValueRef];
// Get the key bits.
NSData *data = nil;
sanityCheck = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, &publicKeyBits);
if (sanityCheck == errSecSuccess) {
data = CFBridgingRelease(publicKeyBits);
//I don''t want to leak this information
(void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
}else {
sanityCheck = SecItemAdd((CFDictionaryRef)queryPublicKey, &publicKeyBits);
if (sanityCheck == errSecSuccess)
{
data = CFBridgingRelease(publicKeyBits);
(void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
}
}
return data;
}
He encontrado una sola re-implementación de Obj-c del analizador ASN.1 en un proyecto abandonado, que parece funcionar. El problema es que utiliza una gran cantidad de trucos de puntero que no sé cómo traducir a Swift (ni siquiera estoy seguro de que algo de eso sea posible). Debería ser posible crear un contenedor amistoso veloz a su alrededor, ya que la única entrada que toma es el NSData.
Todo en la red está utilizando la tienda y recupera en el truco de Llavero para llegar a los datos clave de pub, incluso libs realmente populares como TrustKit . Encontré referencia en los documentos de Apple en SecKeyRef a la causa raíz (creo):
Un objeto SecKeyRef para una clave que se almacena en un llavero se puede convertir de forma segura en SecKeychainItemRef para su manipulación como elemento de llavero. Por otro lado, si SecKeyRef no está almacenado en un llavero, el lanzar el objeto a SecKeychainItemRef y pasarlo a las funciones de Keychain Services devuelve errores.
Como SecCertificateCopyValues
no está disponible en iOS en este momento, está limitado a analizar los datos del certificado o a reorganizar los elementos de Keychain.
He recorrido el mismo camino tratando de hacer SSL Public Key Pinning. Las API son casi inexistentes, y la solución que encontré fue ponerlo en el llavero, que luego puede recuperar como NSData (que luego puede ser codificado en Base64). Es horrible, pero es lo único que pude encontrar después de un día de investigación (sin recurrir a la integración de OpenSSL con mi aplicación).
Transmití parte de mi código a Swift, pero no lo he probado mucho, así que no estoy 100% seguro de que funcione: https://gist.github.com/chedabob/64a4cdc4a1194d815814
Se basa en este código Obj-C (que estoy seguro funciona como en una aplicación de producción): https://gist.github.com/chedabob/49eed109a3dfcad4bd41
La respuesta está en el archivo SecRSAKey.h del sitio web de código SecRSAKey.h de Apple (la seguridad es parte del código que Apple abre). El archivo no es grande y, entre otras cosas, declara las siguientes dos funciones importantes:
CFDataRef SecKeyCopyModulus(SecKeyRef rsaPublicKey);
CFDataRef SecKeyCopyExponent(SecKeyRef rsaPublicKey);
Puede agregar esas funciones a su encabezado de puente para poder llamarlas desde Swift, y al mismo tiempo puede cambiar de CFDataRef
a NSData*
como los dos tipos de puente sin cargo:
NSData* SecKeyCopyModulus(SecKeyRef rsaPublicKey);
NSData* SecKeyCopyExponent(SecKeyRef rsaPublicKey);
Uso Demo Swift:
let key = bytesToPublicKey(keyData)
let modulus = SecKeyCopyModulus(key)
let exponent = SecKeyCopyExponent(key)
print(modulus, exponent)
Sin embargo, esta es una API privada, y es posible que ya no esté disponible en algún momento, sin embargo revisé las versiones de Security
hechas públicas ( http://www.opensource.apple.com/source/Security ) , y parece que las dos funciones están presentes en todos ellos. Además, dado que la Security
es un componente crítico del sistema operativo, es poco probable que Apple haga cambios importantes al respecto.
Probado en iOS 8.1, iOS 9.2 y OSX 10.10.5, y el código funciona en las tres plataformas.
¿Cómo codifico una <SecKey> no administrada a base64 para enviar a otro servidor? :
func convertSecKeyToBase64(inputKey: SecKey) ->String? {
// Add to keychain
let tempTag = "net.example." + NSUUID().UUIDString
let addParameters :[String:AnyObject] = [
String(kSecClass): kSecClassKey,
String(kSecAttrApplicationTag): tempTag,
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecValueRef): inputKey,
String(kSecReturnData):kCFBooleanTrue
]
var result: String?
var keyPtr: AnyObject?
if (SecItemAdd(addParameters, &keyPtr) == noErr) {
let data = keyPtr! as! NSData
result = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
}
// Remove from Keychain:
SecItemDelete(addParameters)
return result
}
Pero si quiere evitar agregar a keychain, puede usar Mirror:
let mirrorKey = Mirror(reflecting: secKey)
let exponent = mirrorKey.descendant("exponent")
let modulus = mirrorKey.descendant("modulus");
[Editar: Espejo no funciona de acuerdo con Josh]
SecKeyRef
es una estructura, por lo que existe la posibilidad de que se refleje con Mirror()
para recuperar los valores deseados.
struct myStruct {
let firstString = "FirstValue"
let secondString = "SecondValue"}
let testStruct = myStruct()
let mirror = Mirror(reflecting: testStruct)
for case let (label?, value) in mirror.children {
print (label, value)
}
/**
Prints:
firstString FirstValue
secondString SecondValue
*/