Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Description

AlisonSDK Mobile is a native library for IOS and Android platform. 

This library generates CAdES digital signatures for strings, and allows installation of digital certificates under a security policy that protect the certificate usage.

This library offers a similar behaviour under both platforms, complying with a common security policy layer. This security policy protects certificate usage, controlled by the certificate issuer.

Platform compatibility

Android


IOS


Latest ✔Latest ✔

Download

You can download latest stable release of AlisonSDK-Mobile:

Future Releases

It is possible to access future releases of the library to test new features to be implemented.

.


Security Policy defines rules to protect certificate usage.

Following fields can be included into a security policy.

FieldexampleDescription
nameclient_nameName of the policy. It can be used to recognise between different policies and values.
version5.0.1Version of the policy.
security_level2Security level required to protect the certificate. This value is explained below.
max_try5Limit of tries to enter a password or biometric factor. After the second try, a "toast or flash" message is displayed on screen. After reach the limit defined with this value, action defined in "behaviour" is executed.
fail_behavior*lock || erase

This action is executed after reaching the max_try value. 

  • lock disables certificate usage for fail_timeout minutes. 
  • erase destroy the certificate from the keystore.
fail_timeout10Minutes to lock the certificate usage.

Note: a typo was introduced in original library and is maintained by backward compatibility.

Security Level table

DecHexDescription
00x0000none security. Each certificate can be used without any kind of control.
10x0001requires lock-screen of the device to activate the certificate usage.
20x0001requires biometric lock. If biometric lock is not available, level 1 is requiered.
30x0003any kind of lock (level_1 or level_2).
40x0004the password that protect the certificate is requiered.
10250x0401password AND lock-screen is required. Both factor must be entered by the user.
10260x0402password AND biometric is required.

.


Methods

List of available methods.


AlisonMobileSDK is a static class used to define some general behaviours of the library and to obtain execution results..

This class must be used to initialize the library before of using any of its methods or classes.

This method must be invoked after OnCreate callback and before of SetContent function. This is required condition to setup the language correctly.

Code Block
languagejs
titleInitialize
initializeAlisonMobile(	String initializeName, String language, 
					   	boolean notification, Context context, 
						String baseUrl );


ArgumentValuesDescription
initializeNamestring

it must indicate a unique identifier for the library. This identifier is used to mark those certificates related to this application. 

languageen | es

language of the messages to show to the user. Other values can be used but programmer must extend the language definitions provided by the library.

if none (null) value is defined, then the library uses the OS language.

notificationfalsethis value indicates to the library if toast messages must be displayed or not.
context
this is the context of the activity requiring this function.
baseUrl
URL where to call to fetch renewal or inform the installation of a new request. This URL is provided by the certification authority, like: "https://panel.certisur.com/panel/acme"

.


Code Block
languagejs
titleInitialize
String getLibVersion();


This method can be used to determine the library version. This can be useful under certain development conditions. This is a static method of AlisonMobileSDK.

.

## Clases y Methodos ##
Cada tipo de operación es administrada por un clase específica y sus métodos asociados, el resultado de todas las operacion se vera reflejado en la funcion onActivityResult(int requestCode, int resultCode, Intent data) mediante el uso del parametro data como corresponde a la devolucion de un activity

This library is implemented as an general administrator of operation to be performed. Each operation has its own result, which is communicated to the caller throw the corresponding callback.

The function onActivityResult(int requestCode, int resultCode, Intent data) is used as a callback.

Each kind of operation has a specific class, and specific methods to use.



#### AlisonAuthOperation

Esta clase permite realizar un proceso de autenticación. La contraseña puede ser requerida dependiendo de la Security Policy del certificado seleccionado.

__AlisonAuthOperation(String aliasCert)__
  
  Constructor del objeto. Se puede indicar el alias del certificado que desea utilizar, de lo contrario utilizar seleccionara automaticamente el certificado a utilizar.


#### AlisonSignatureOperation

Esta operación es utilizada para generar firmar en formato CAdES.

__AlisonSignatureOperation(String  text2Sign)__
 
  Crea una operación de firma. El texto a ser firmado debe ser indicado.

#### AlisonImportOperation

Esta operación permite la importación de un certificado dentro del KeyStore propio de la librería.

__AlisonImportOperation(url)__
    Utiliza como parametro la url base obtenida del escaneo del QR luego la operacion por si misma realizara la descarga y el tratamiento de errores de la misma
  

#### AlisonListOperation

Permite listar los certificados administrados por la librería.
AlisonListOperation(Context context, boolean eraseCert): Utiliza como parametros el Context del activity y un boolean que corresponde al manejo de borrado de certificado en caso de que el mismo este en estado "INVALIDADO".

__ArrayList <Webcertificate> ListCertificates()__

  Esta operación devuelve una lista de los Webcertificates instalados y administrados por la librería, almacenados dentro del KeyStore específico de la aplicación.
  
  Luego de obtenida la lista puede acceder a las propiedades del certificado:
  
 *__String getAlias()__ = Obtiene el alias del certificado
 
 *__Int getDaysToExpire()__ = Obtiene los dias que falta para que el certificado expire.
 
 *__Int getCertificateStatus()__ = Obtiene el estado actual del certificado correspondiente con la policy. Los estados pueden ser los siguientes:
 
    * 0:Estado del certificado correcto
    * 22009(AlisonCodes.STATUS_INVALIDATED_C): Estado invalidado, producido cuando las credenciales de seguridad del dispositivo fueron removidas, dejando al certificado en estado invalido.
    * 22010(AlisonCodes.STATUS_CHECK_POLICY_ERROR_C): El certificado no cumple las politicas establecidas en la policy
    * 22011(AlisonCodes.STATUS_POLICY_ERROR_AND_INVALIDATED_CERT_C): El certificado no cumple la policy y ademas esta en estado invalidado.
 
  Si no hay ningún certificado instalado devuelve una lista vacia.

#### AlisonRenewOperation

Esta operación permite la renovacion  de un certificado que esta dentro del periodo de renovacion,
instalando el nuevo y eliminando el anterior, para poder seguir operando.

__AlisonRenewOperation(url)__


#### AlisonEraseOperation

__int deleteSelectedCertificate(String certToDelete)__

  Operacion que borra el certificado representado por el String certToDelete, el cual sera el correspondiente alias del certificado a borrar
  
  Los valores devueltos son:
   * 1 : operación exitosa.
   * 0 : no existe ningún certificado con el identificador indicado.
   * -1 : se ha producido un error al intentar borrar el certificado.

__int deleteCertificates()__

  Operacion que borra __todos__ los certificados del KeyStore de la aplicacion.
  
  Los valores devueltos son:
   * 1 : operación exitosa.
   * 0 : no existe ningún certificado para borrar.
   * -1 : se ha producido un error al intentar borrar algún certificado.
 
#### Obtencion de Resultados
    Los resultados se obtienen en onActivityResult

 * __void onActivityResult(int requestCode, int resultCode, Intent data)__
 
    *__requestCode__ : Codigo de pedido del Intent utilizado en la operacion seleccionada.
    *__resultCode__ : Codigo de resultado de la operacion la cual puede ser:
                    *0: Finalizo correctamente.
                    *2: Finalizo con error.
    *__data__ : Informacion adicional del resultado de la operacion. La informacion se obtiene de la siguiente manera: 
    * __data.getStringExtra("key")__: Donde la key representa la estructura a obtener y puede ser una de las siguientes:
        * __opResult__ : Indica informacion adicional sobre el resultado de la operacion, y tiene la siguiente estructura.
        {"opResult":[{"code":,"detail":}],"opType":,"resultStatus":}
                    *__code(int)__: Codigo de error de la operacion.
                    *__detail(String)__:Detalle del codigo de error obtenido.
                    *__opType(int)__: Codigo de la operacion que fue utilizada.
                    __resultStatus(int)__: Resultado general de la operacion:
                        0:Resultado correcto.
                        2:Resultado fallido.    

        * __signatureB64__: Firma en base 64. Obtenida en operaciones de firma y autenticacion en caso de que se hayan ejecutado correctamente.
                
        * __signedText__: Texto que fue firmado. Obtenida en operaciones de firma y autenticacion en caso de que se hayan ejecutado correctamente. 
        
        * __lockedTime__: Cuando la operacion sale por el error 22311(AlisonCodes.ERR_C_CERT_LOCKED) es posible consultar cuanto tiempo permanecera bloqueada la operacion, util para enviarselo al usuario con el mensaje correspondiente
    

#### Codigos de error mas usados
 __Generales (Todas las operaciones)__
    * 0 (AlisonCodes.C_OPERATION_SUCCESS): Operacion realizada correctamente.
    * 2 (AlisonCodes.STATUS_ERROR): La operacion no finalizo correctamente.
    * 22105(AlisonCodes.C_CANCELLED): El usuario cancelo la operacion.
    * 22700(AlisonCodes.ERR_C_ERROR_BACKEND): Se produjo un error insalvable desde Colt, por perdida de conexion o problemas en la misma, se debe repetir la operacion.

__AlisonImportOperation__
    * 22315(AlisonCodes.ERR_C_1_CERTIFICATE_INSTALLED): Se intento instalar un certificado cuando ya hay un certificado instalado.
    * 22307(AlisonCodes.ERR_C_WRONG_PASS): Password ingresado incorrecto.
    * 22311(AlisonCodes.ERR_C_CERT_LOCKED): Certificado bloqueado por cantidad de intentos fallidos.
    * 22310(AlisonCodes.ERR_C_CERT_ERASED): Certificado eliminado por cantidad de intentos fallidos.
  
__AlisonSignatureOperation__
    * 22402(AlisonCodes.PKCS12_PASSWORD_HASH_DOES_NOT_MATCH_C): El password ingresado no coincide con el del certificado instalado.
    * 22311(AlisonCodes.ERR_C_CERT_LOCKED): Certificado bloqueado por cantidad de intentos fallidos.
    * 22310(AlisonCodes.ERR_C_CERT_ERASED): Certificado eliminado por cantidad de intentos fallidos.
    * 22401(AlisonCodes.ERR_NO_C_CANDIDATE_CERT_FOUND): No hay un certificado instalado.
    * 22008(AlisonCodes.AUTHENTICATION_MODE_NOT_SETTED_C): No hay seteado un tipo de lockeo de pantalla
    

__AlisonAuthOperation__
    * 22401(AlisonCodes.ERR_NO_C_CANDIDATE_CERT_FOUND): No hay un certificado instalado.
    * 22008(AlisonCodes.AUTHENTICATION_MODE_NOT_SETTED_C): No hay seteado un tipo de lockeo de pantalla

__AlisonListOperation getCertificateStatus__

    * 0:Estado del certificado correcto
    * 22009(AlisonCodes.STATUS_INVALIDATED_C): Estado invalidado, producido cuando las credenciales de seguridad del dispositivo fueron removidas, dejando al certificado en estado invalido.
    * 22010(AlisonCodes.STATUS_CHECK_POLICY_ERROR_C): El certificado no cumple las politicas establecidas en la policy
    * 22011(AlisonCodes.STATUS_POLICY_ERROR_AND_INVALIDATED_CERT_C): El certificado no cumple la policy y ademas esta en estado invalidado.


## Archivos adicionales ##

Los siguientes archivos adicionales deben ser agregados al proyecto que haya uso de la librería.

Las siguientes carpetas tienen que ser completadas. En el caso que alguna de ellas no exista, entonces hay que crearla dentro de la jerarquía correspondiente:

__layout__
  * Password_dialog.xml: ventana que solicita la contraseña para acceder al certificado. Esto puede ocurrir en la operación de firma o instalación.
  * Renew_dialog.xml: Ventana que solicita las contraseñas necesarias para la renovacion.
  * sdk_activity: Contenedor utilizado para mostrasr los Fragmentos que corresponden a las ventanas de    firma y renovacion.
  * popup_loading.xml: popup del formato de loading de progreso de descarga, con el estilo del BRC

__values__:
  * Alison-leng.xml: definiciones de los strings en lenguaje inglés.
  * Colors.xml: Dado que se agrego un color del BRC es necesario este archivo para que el               popup_loading.xml funcione correctamente
  
__values-es__
  * Alison-leng.xml: definiciones de los strings en lenguaje español.

ArgumentRequiredDescription
urlsArray of interfaces to communicate with Alison-Desktop or Alison-Server. The default value is ['https://127.0.0.1:8004', https://127.0.0.1:8005 ].



Detect if some certificate provider (AlisonDesktop) is installed and enabled into the browser.

These methods allow to check if Alison-Desktop is running, and if it is enabled into the browser used. These method must be used after Alison-Desktop initialization returning an error code 20404.

If method initialize() was successful, it's not necessary to call any of these methods.

Available from version 3.0.1+.

isRunning()

Detect if Alison-Desktop is running.

Code Block
languagejs
titleisRunning
isRunning(): Promise<{ result: boolean }>


isEnabled()

Detect if Alison-Desktop is enabled into the browser used.

Code Block
languagejs
titleisEnabled
isEnabled(): Promise<{ result: boolean }>


enable()

Request AlisonDesktop to ingrate with the browser used.

Code Block
languagejs
titleenable
enable(): Promise<{}>


Note: all these methods depend on security features enabled by browsers, and its accuracy cannot be guaranteed working on any platform and/or browser. 


.


Generates a keyPair and a Certificate Signing Request.


Code Block
languagejs
titleGenerate CSR
generateCsr({
	keyStore: KeyStore
	options?: {
 		algorithm?: string
		size?: number
		signatureAlgorithm?: string
	}
	securityPolicy?: SecurityPolicy
}): Promise<{ csr: string }>


ArgumentRequiredDescription
keyStore

950519 where to generate the certificate request.

optionsDefault values are: { algorithm: 'RSA', size: 2048, csrAlgorithm: 'SHA256WITHRSA'  }
securityPolicy

Security requirements to be applied to the keyStore/profile.


Code Block
languagejs
titleExample
alison.generateCsr({
	keyStore: { id: "WIN-ENH" },
	options: { size: 2048 },
	securityPolicy: { exportable: true }
}).then(
	function({ csr }) { 
		// handle success
	},
	function({ code, detail }) {
		// handle failure
	}
)


.


Installs the certificate into the Keystore/profile indicated. Returns 950519


Code Block
languagejs
titleinstallCertificate
installPkcs7({
	keyStore: KeyStore
	pkcs7: string
	securityPolicy?: SecurityPolicy
}): Promise<{ certificate: WebCertificate }>


ArgumentRequiredDescription
keyStore950519 where the privateKey is stored
pkcs7

Certificate (X.509) and/or Certificate chain (PKCS#7 structure) to be installed (in base64 format). None PEM header must be included.

securityPolicySecurity requirements to be applied to the keyStore/profile.


Code Block
languagejs
titleExample
alison.installPkcs7({
	keyStore: { id: "WIN-ENH" },
	pkcs7: "MIICU5iNXuudGfc="
}).then(
	function({ certificate }) { 
		// handle success
	},
	function({ code, detail }) {
		// handle failure
	}
)


.


Lists ids of available Keystores found in Alison-Desktop.


Code Block
languagejs
titlekeyStore List
listKeyStores(): Promise<{
	keyStores: {
		id: string
	}[]
}>


Code Block
languagejs
titleExample
alison.listKeyStores().then(
	function({ keyStores }) { 
		// handle success
	},
	function({ code, detail }) {
		// handle failure
	}
)


.


Returns information about a specific Keystore.


Code Block
languagejs
titleKeyStore Information
keyStoreInfo(KeyStore): Promise<{ keyStore: KeyStoreInfo }>


Code Block
languagejs
titleExample
alison.getKeystoreInfo({
	id: "CSK"
}).then(
	function({ keyStore }) { 
		// handle success
	},
	function({ code, detail }) {
		// handle failure
	}
)


.


Lists certificates found in Alison-Desktop. Returns array of 950519.


Code Block
languagejs
titleCertificate List
certificateList(): Promise<{
	certificates: WebCertificate[];
}>;


Code Block
languagejs
titleExample
alison.certificateList().then(
	function({ certificates }) { 
		// handle success
	},
	function(response) {
		// handle failure
	}
)


.


Uses a certificate to sign a string.


Code Block
languagejs
titleSignature
sign({
    text: string
    certificate: string
    keyStore: KeyStore;
    options?: {
		algorithm?: string;
	  	format?: string;
	 	params?: string;
	}
}): Promise<{ signature: string }>;


ArgumentRequiredDescription
textText to be signed in base64 format.
certificateThumbPrint of the certificate to use.
optionsdefaults are {

algorithm: 'SHA1withRSA', format: 'CAdES', params: ''

}
keyStore

KeyStore where the certificate is located.


Code Block
languagejs
titleExample
alison.sign({
	text: 'A43G3RWG224...',
	certificate: 'C22E8C20D6042B2BF6A6E054B7378FEC57414765',
	keyStore: { id: "WIN-ENH" }
}).then(
	function({ signature }) { 
		// handle success
	},
	function(response) {
		// handle failure
	}
)


.


The following interfaces are used by this library.




Code Block
languagejs
titleInterface
KeyStore {
	id: string
	profile?: string
}


This structure was extended with more information from AlisonJS version 3.0.1 and Alison-Desktop 3.1.x.

Code Block
titleKeyStore Structure
KeyStore {
	id: string,
	keyStoreId?: string,
	keyStoreType?: string,
	capabilities?: string,
	friendlyName?: string,
	status: {
		resultStatus: int,
		resultList: []
	},
	profiles?: string
}


A security policy defines behaviour of the keystore or profile, depending each kind of them. Go to this link for a better description of them.

An empty JSON structure must be used to indicate the default one. 

{ }


Code Block
languagetext
titleSecurity Policy
CapiSecurityPolicy {
	exportable?: boolean;
	protectionLevel?: number;
	title?: string;
	friendlyName?: string;
	description?: string;
}

DeviceSecurityPolicy {
	installDummy?: boolean
	generateOnBoard?: boolean
}

PassSecurityPolicy {
	passMinLength: number
	passComplexity: number
}

Pkcs11SecurityPolicy {
	installDummy?: boolean
	generateOnBoard?: boolean
	passMinLength?: number
	passComplexity?: number
}

CskSecurityPolicy {
	id: string
	passMinLength: number
	passComplexity: number
	passLockCount: number
	passExpiration: number
	lockTimeout: number
	idleTimeout: number
	certExport: number
}

SecurityPolicy =
  | CapiSecurityPolicy
  | CskSecurityPolicy
  | DeviceSecurityPolicy
  | PassSecurityPolicy
  | Pkcs11SecurityPolicy
  | {}




Code Block
languagetext
titleWeb Certificate
WebCertificate {
	serialNumber: string
	thumbPrint string
	subjectDN: string
	issuerDN: string
	validity: {
		from: string
		left: number
		until: string
		total: number
		isExpired: boolean
	}
}




Generate and Install a certificate.


Code Block
languagejs
themeEclipse
titleGenerate and Install a certificate
<script src="./alisonJS.umd.js"></script>

const alison = new AlisonJS.Desktop()

// Call initialize method with License provided by Certisur
alison.initialize({
	accessToken: "eyJ2MSI6eyJpZHgiOiI0LUxpY2Vuc2UiLCJ2YWx1ZSI6IlErZ0ErZE1pSXBkN3pYRCtJNWpJY0g3TnhLY1J5ZndUM3ZTWUk2TmNoWE9lUURmL1JMZHltWFRNRXpsbndlcGJIR1lKVEJQQjFwcklnTE16Qlc5cnBiZFNZcEJBV3pvamhEdW9Ra29ZUWc3dENxSUpHbk9KV1ZYUkZFakhid3h2aVBtbEFkZ3Y3ZXdSNldxd0xpaHRNVVArNzlnRmg5TDMvKzBXd3JDbmd0MHhjbnNnRmpvZkhSeUowS21TQi9Ud25KK3ljZ2tod2ZwUkpUTmpYaGZnbmxIWTRyQXY0WkY0WGNtTkdjYUFkQmpIWFpMK1FOUnV3NitBbnFwekxmRkFrdTB0d2NOVVFoL3l1SlBIZlJYSjdZOGtTa0tzWDh2eTVjdW95ZG9kR3d5YndEUmFxUXprcHlyWkIzaFNKRU5RLzc4c1NYbGNwMDNuNWs0bFp0N0lNK0lOQmJMUlZFby9MUEtYTWIyTXFoaTFub3R0RDlEQ1pZRkJkMzU5ZG5Wd3piQUdlOFozTXpBam4wNUZPRkQzcUhtUGJZcFk1QXNwTzR5bE4wUUhGMUprYlRXVmk4MWh2Z244d1l4MEU1Nm9WRXV0UFg0SSs0TktnODVGV0hReGh5dmJDeVovNm1xeE9WVWhUQ3cyS2lvejRMZEpuVTVmRDZqck9rdCtlSU0yWFo5NUF4NEcvUHh2V2lnaWg1VFFhQT09In0sInYyIjp7ImlkeCI6IjItTGljZW5zZSIsInZhbHVlIjoiZXlKaGJHY2lPaUpTVXpJMU5pSjkuZXlKcFlYUWlPakUxTnpjNU56ZzJOemtzSW1WNGNDSTZNVGMwTkRJMU5EQXdNQ3dpWTI5dGNHRnVlVTVoYldVaU9pSkRaWEowYVZOMWNpQlRMa0V1SWl3aVlXeHNiM2RsWkU5eWFXZHBibk1pT2xzaUtpNWpaWEowYVhOMWNpNWpiMjBpTENJcUxtTmxjblJwYzNWeUxtNWxkQ0lzSW1Gc2FYTnZibVJsYzJ0MGIzQXVjR3RwYUc5emRDNWpiMjBpTENKaGJHbHpiMjVrWlhOcmRHOXdMbkJyYVdodmMzUXVZMjl0T2pnd01EVWlMQ0pqYUhKdmJXVXRaWGgwWlc1emFXOXVPaTh2YTNCcWJHRm5aR3B3WVdscGJXNWxZMlJpWkdOdVpHcGphV3B2WVd4aWJHRWlMQ0pzYjJOaGJHaHZjM1E2T0RBd015SXNJbTF2ZWkxbGVIUmxibk5wYjI0Nkx5OWtabUl3WldJM01pMDFOelU0TFRRek4ySXRZVFV3T0MweU9HUmhNVGs0Tm1Wa1ptUWlMQ0pzYjJOaGJHaHZjM1E2T0RBd05DSXNJbXh2WTJGc2FHOXpkRG80TURBMUlpd2liRzlqWVd4b2IzTjBJaXdpYkc5allXeG9iM04wT2pJd01EUWlMQ0pzYjJOaGJHaHZjM1E2TWpBd05TSXNJakV5Tnk0d0xqQXVNU0lzSWpFeU55NHdMakF1TVRvNE1EQTBJaXdpTVRJM0xqQXVNQzR4T2pnd01EVWlYWDAuYXlhY2hfcmxURzd5XzdDcGZ0c09MQ0MzZF80cko1M3NBZVRPeEVfLU43Qzh6QzdlWk9GUDhJNTBYUm03VTJLTTN4ZjBoQi0xdm0tcGQ1d1lfOFhpbUszZ3hVWl94eTRtNmVidmxmdmZudmFIZmxDTHZ4ZkpRellkclRFeXJtZXNBeEFySVUyX0JrTnJkRHAwUXJLRmhEcXpVbC1Sd291TG9hWEE0RVhhbURlcE1waDVsdDRKc2ZQQ0Q4X3JnMHB0d3lMRGprWXY1YkZtNXJaaEJMOGZJS1Zhd255eUhSY2VEekpZZzZnUXU1OTk1V3MyZExlYUVidVFQSWZVNmE5c29WR1o0WG5WX1pGX08tSnlwNkpVcFZTY1UzS0pjTkM1WDlSdG9mOGpMVjRfZ2pCcmhSUXh5UEgwQlpPUC1jZGhKUWlUa1pwYS1wb3RjZTdNUnVKVE1nIn19",
}).then(function() {
	// Generate KeyPair and Certificate Signing Request in MACOS keyStore

	alison.generateCsr( {
		keyStore: { id: "MACOS" },
		securityPolicy: { 
			installDummy: true
		}
	}).then(function({ csr }) {
		// issue the certificate through an external CA
		const pkcs7 = requestCertificateFromYourCA(csr)

		// install issued certificate
		alison.installPkcs7({
			keyStore: { id: "MACOS" },
			pkcs7: pkcs7
		}).then(function({ certificate }) {
			// Certificate is currently installed in the MACOS keyStore	


			// In case you want to use or test the certificate we just installed, 
			// You can use the "sign" method 
			alison.sign({
				text: 'test signature',
				certificate: certificate.thumbPrint,
				keyStore: { id: "MACOS" }
			}).then(function({ signature }){
				// Print signature result
				console.log(signature)
			}, printError)
		}, printError)
	}, printError)
}, printError)


function printError({ code, detail }) {
	console.log(`error ${code}: ${detail}`)
}